Phase 2: Subsonic REST API framework (auth, response formatting, error handling, routing) plus user permission model

This commit is contained in:
2026-08-22 23:58:54 -05:00
parent f81c8704a5
commit b5e2bf01e2
26 changed files with 1772 additions and 75 deletions
+2 -2
View File
@@ -36,12 +36,12 @@ func (h *Web) handleAdminUserCreate(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/users", http.StatusFound)
return
}
hash, err := auth.HashPassword(password)
user, err := database.CreateUser(h.DB, username, "", "", isAdmin)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if _, err := database.CreateUser(h.DB, username, "", hash, isAdmin); err != nil {
if err := auth.SetUserPassword(h.DB, h.EncryptionKey, user.ID, password); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
+5 -6
View File
@@ -137,12 +137,7 @@ func (h *Web) handleSetup(w http.ResponseWriter, r *http.Request) {
return
}
hash, err := auth.HashPassword(password)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
user, err := database.CreateUser(h.DB, username, "", hash, true)
user, err := database.CreateUser(h.DB, username, "", "", true)
if err != nil {
render(w, "setup", PageData{
Title: "Setup", CSRFToken: h.CSRF.Token(w, r),
@@ -151,6 +146,10 @@ func (h *Web) handleSetup(w http.ResponseWriter, r *http.Request) {
})
return
}
if err := auth.SetUserPassword(h.DB, h.EncryptionKey, user.ID, password); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
folder, err := database.CreateMusicFolder(h.DB, folderName, folderPath, "music")
if err != nil {
+14 -8
View File
@@ -32,15 +32,21 @@ func newTestWeb(t *testing.T) (*Web, *sql.DB) {
extauth := &auth.ExtAuth{DB: db, Config: config.ExtAuthConfig{}}
mw := &auth.Middleware{Sessions: sessions, ExtAuth: extauth}
key, err := auth.EncryptionKey(db, "test-secret")
if err != nil {
t.Fatalf("EncryptionKey: %v", err)
}
h := &Web{
DB: db,
Config: config.Config{},
Sessions: sessions,
CSRF: &auth.CSRF{CookieSecure: false},
Middleware: mw,
Scanner: scanner.New(db, &metadata.Prober{}, log),
CoverArt: coverart.New(t.TempDir(), ""),
Log: log,
DB: db,
Config: config.Config{},
Sessions: sessions,
CSRF: &auth.CSRF{CookieSecure: false},
Middleware: mw,
Scanner: scanner.New(db, &metadata.Prober{}, log),
CoverArt: coverart.New(t.TempDir(), ""),
EncryptionKey: key,
Log: log,
}
return h, db
}
+1 -6
View File
@@ -52,12 +52,7 @@ func (h *Web) handlePasswordChange(w http.ResponseWriter, r *http.Request) {
h.renderSettings(w, r, "", "New password must be at least 8 characters.", "danger")
return
}
hash, err := auth.HashPassword(newPassword)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if err := database.SetUserPassword(h.DB, user.ID, hash); err != nil {
if err := auth.SetUserPassword(h.DB, h.EncryptionKey, user.ID, newPassword); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
+9 -8
View File
@@ -14,14 +14,15 @@ import (
)
type Web struct {
DB *sql.DB
Config config.Config
Sessions *auth.Sessions
CSRF *auth.CSRF
Middleware *auth.Middleware
Scanner *scanner.Scanner
CoverArt *coverart.Store
Log *slog.Logger
DB *sql.DB
Config config.Config
Sessions *auth.Sessions
CSRF *auth.CSRF
Middleware *auth.Middleware
Scanner *scanner.Scanner
CoverArt *coverart.Store
EncryptionKey []byte // for auth.SetUserPassword; see internal/auth/subsonic_secret.go
Log *slog.Logger
}
// page renders a template with the standard envelope (current user, CSRF