diff options
| author | JP Appel <jeanpierre.appel01@gmail.com> | 2024-10-15 23:56:50 -0400 |
|---|---|---|
| committer | JP Appel <jeanpierre.appel01@gmail.com> | 2024-10-15 23:57:30 -0400 |
| commit | bd6f2159931b5877922efed11f7ea9c54b172379 (patch) | |
| tree | 12bca5fbf677a0d0146686128cdcb39800536206 | |
| parent | 9b6e79c40b98d8e36e4b7f5e1cc6f6a9f0feabbc (diff) | |
Add authentication middlewaredashboard
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | db/auth.go | 80 | ||||
| -rw-r--r-- | db/db.go | 46 | ||||
| -rw-r--r-- | go.mod | 5 | ||||
| -rw-r--r-- | middleware/auth.go | 35 | ||||
| -rw-r--r-- | nonsense-time.go | 16 |
6 files changed, 180 insertions, 4 deletions
@@ -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)) +} @@ -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()) } |
