aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJP Appel <jeanpierre.appel01@gmail.com>2024-10-15 23:56:50 -0400
committerJP Appel <jeanpierre.appel01@gmail.com>2024-10-15 23:57:30 -0400
commitbd6f2159931b5877922efed11f7ea9c54b172379 (patch)
tree12bca5fbf677a0d0146686128cdcb39800536206
parent9b6e79c40b98d8e36e4b7f5e1cc6f6a9f0feabbc (diff)
Add authentication middlewaredashboard
-rw-r--r--Makefile2
-rw-r--r--db/auth.go80
-rw-r--r--db/db.go46
-rw-r--r--go.mod5
-rw-r--r--middleware/auth.go35
-rw-r--r--nonsense-time.go16
6 files changed, 180 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 45282a3..6ee92d1 100644
--- a/Makefile
+++ b/Makefile
@@ -20,5 +20,5 @@ info:
@echo "BIN: $(BIN)"
@echo "SERVICE: $(SERVICE)"
-nonsense-time: nonsense-time.go $(wildcard api/*.go) $(wildcard dashboard/*.go) $(wildcard util/*.go)
+nonsense-time: nonsense-time.go $(wildcard api/*.go) $(wildcard dashboard/*.go) $(wildcard util/*.go) $(wildcard db/*.go) $(wildcard middleware/*.go)
go build .
diff --git a/db/auth.go b/db/auth.go
new file mode 100644
index 0000000..e2bac38
--- /dev/null
+++ b/db/auth.go
@@ -0,0 +1,80 @@
+package db
+
+import (
+ "crypto/subtle"
+ "database/sql"
+)
+
+type AuthProvider interface {
+ SaltedHash(username string) [32]byte
+ Salt(username string) [16]byte
+ UserExists(username string) bool
+}
+
+type DBAuthProvider struct {
+ db *sql.DB
+}
+
+func NewDBAuthProvider(db *sql.DB) AuthProvider {
+ provider := new(DBAuthProvider)
+ provider.db = db
+
+ return provider
+}
+
+func (dbAuth DBAuthProvider) Close() {
+ dbAuth.db.Close()
+}
+
+func (dbAuth DBAuthProvider) SaltedHash(username string) [32]byte {
+ row := dbAuth.db.QueryRow(`
+ SELECT saltedhash FROM users WHERE username = ?
+ `, username)
+
+ buf := make([]byte, 32)
+ should_copy := 1
+
+ saltedHash := [32]byte{0}
+
+ err := row.Scan(&buf)
+ if err != nil {
+ should_copy = 0
+ }
+
+ subtle.ConstantTimeCopy(should_copy, saltedHash[:], buf[:32])
+
+ return saltedHash
+}
+
+func (dbAuth DBAuthProvider) Salt(username string) [16]byte {
+ row := dbAuth.db.QueryRow(`
+ SELECT salt FROM users WHERE username = ?
+ `, username)
+
+ buf := make([]byte, 16)
+ should_copy := 1
+
+ salt := [16]byte{0}
+
+ err := row.Scan(&buf)
+ if err != nil {
+ should_copy = 0
+ }
+
+ subtle.ConstantTimeCopy(should_copy, salt[:], buf[:16])
+
+ return salt
+}
+
+func (dbAuth DBAuthProvider) UserExists(username string) bool {
+ row := dbAuth.db.QueryRow(`
+ SELECT 1 FROM users WHERE username = ?
+ `, username)
+
+ err := row.Scan(new(int))
+ if err != nil {
+ return false
+ }
+
+ return true
+}
diff --git a/db/db.go b/db/db.go
new file mode 100644
index 0000000..8213903
--- /dev/null
+++ b/db/db.go
@@ -0,0 +1,46 @@
+package db
+
+import (
+ "database/sql"
+ "log/slog"
+ "os"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+var Logger *slog.Logger
+
+// Create a new database connection
+func New() *sql.DB {
+ conn, err := sql.Open("sqlite3", "./foo.db")
+ if err != nil {
+ Logger.Error("Failed to create database connection")
+ panic(err)
+ }
+
+ if err = InitTables(conn); err != nil {
+ panic(err)
+ }
+
+ return conn
+}
+
+func InitTables(conn *sql.DB) error {
+ _, err := conn.Exec(`
+ CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT UNIQUE NOT NULL,
+ salt BLOB NOT NULL,
+ saltedHash BLOB NOT NULL
+ )
+ `)
+
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func init() {
+ Logger = slog.New(slog.NewTextHandler(os.Stdout, nil))
+}
diff --git a/go.mod b/go.mod
index d5a8df3..2eb2858 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,10 @@ module nonsense-time
go 1.23.0
-require github.com/docker/docker v27.3.1+incompatible
+require (
+ github.com/docker/docker v27.3.1+incompatible
+ github.com/mattn/go-sqlite3 v1.14.24
+)
require (
github.com/Microsoft/go-winio v0.4.14 // indirect
diff --git a/middleware/auth.go b/middleware/auth.go
new file mode 100644
index 0000000..93af421
--- /dev/null
+++ b/middleware/auth.go
@@ -0,0 +1,35 @@
+package middleware
+
+import (
+ "crypto/sha256"
+ "crypto/subtle"
+ "net/http"
+ "nonsense-time/db"
+)
+
+func BasicAuth(next http.Handler, authProvider db.AuthProvider) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ username, password, ok := r.BasicAuth()
+
+ if !ok || !authProvider.UserExists(username) {
+ w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ salt := authProvider.Salt(username)
+ input := []byte(password)
+ input = append(input, salt[:]...)
+
+ passSaltHash := sha256.Sum256(input)
+ expectedSaltHash := authProvider.SaltedHash(username)
+
+ if subtle.ConstantTimeCompare(expectedSaltHash[:], passSaltHash[:]) != 1 {
+ w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/nonsense-time.go b/nonsense-time.go
index f148266..cd987e9 100644
--- a/nonsense-time.go
+++ b/nonsense-time.go
@@ -6,6 +6,7 @@ import (
"log/slog"
"net/http"
"nonsense-time/api"
+ "nonsense-time/db"
"nonsense-time/middleware"
"os"
"time"
@@ -49,14 +50,25 @@ func main() {
logger = slog.New(slog.NewTextHandler(os.Stdout, loggerOpts))
api.Logger = logger
+ db.Logger = logger
vtt := middleware.Timeout(http.HandlerFunc(api.VttOnline), *waitTime)
- vttLogs := middleware.Gzip(http.HandlerFunc(api.VttLogs))
+ vttLogs := middleware.Log(middleware.Gzip(http.HandlerFunc(api.VttLogs)), logger)
+
+ authProvider := db.NewDBAuthProvider(db.New())
+
+ vttStart := middleware.Log(middleware.BasicAuth(http.HandlerFunc(api.VttStart), authProvider), logger)
+ vttStop := middleware.Log(middleware.BasicAuth(http.HandlerFunc(api.VttStop), authProvider), logger)
+ vttRestart := middleware.Log(middleware.BasicAuth(http.HandlerFunc(api.VttRestart), authProvider), logger)
- mux.Handle("GET /vtt/status", vtt)
mux.HandleFunc("GET /vtt", api.VttRedirect)
+ mux.Handle("GET /vtt/status", vtt)
mux.Handle("GET /vtt/logs", vttLogs)
+ mux.Handle("POST /vtt", vttStart)
+ mux.Handle("DELETE /vtt", vttStop)
+ mux.Handle("PUT /vtt", vttRestart)
+
logger.Info(fmt.Sprint("Listening on ", addr))
logger.Info(http.ListenAndServe(addr, mux).Error())
}