Files
vibesonic/internal/web/auth_handlers.go
T

177 lines
4.6 KiB
Go

package web
import (
"context"
"net/http"
"os/exec"
"git.noroutes.net/NoRoutes/vibetunes/internal/auth"
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
)
func (h *Web) handleLoginPage(w http.ResponseWriter, r *http.Request) {
if h.Sessions.CurrentUser(r) != nil {
http.Redirect(w, r, "/", http.StatusFound)
return
}
render(w, "login", PageData{
Title: "Log in",
CSRFToken: h.CSRF.Token(w, r),
Data: struct{ Next string }{Next: r.URL.Query().Get("next")},
})
}
func (h *Web) handleLogin(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
next := r.FormValue("next")
user, err := database.GetUserByUsername(h.DB, username)
if err != nil || user.Disabled || !user.PasswordHash.Valid {
h.loginFailed(w, r, next)
return
}
ok, err := auth.VerifyPassword(password, user.PasswordHash.String)
if err != nil || !ok {
h.loginFailed(w, r, next)
return
}
if err := h.Sessions.Issue(w, r, user.ID); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
_ = database.TouchLastLogin(h.DB, user.ID)
http.Redirect(w, r, safeRedirectPath(next), http.StatusFound)
}
func (h *Web) loginFailed(w http.ResponseWriter, r *http.Request, next string) {
render(w, "login", PageData{
Title: "Log in",
CSRFToken: h.CSRF.Token(w, r),
Flash: "Invalid username or password",
FlashKind: "danger",
Data: struct{ Next string }{Next: next},
})
}
func (h *Web) handleLogout(w http.ResponseWriter, r *http.Request) {
h.Sessions.Clear(w, r)
dest := "/login"
if h.Config.ExtAuth.Enabled && h.Config.ExtAuth.LogoutURL != "" {
dest = h.Config.ExtAuth.LogoutURL
}
http.Redirect(w, r, dest, http.StatusFound)
}
// --- First-run setup ---
func (h *Web) handleSetupPage(w http.ResponseWriter, r *http.Request) {
n, err := database.CountUsers(h.DB)
if err == nil && n > 0 {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
render(w, "setup", PageData{
Title: "Setup",
CSRFToken: h.CSRF.Token(w, r),
Data: h.setupData(),
})
}
func (h *Web) setupData() any {
ffmpegPath := resolveOr(h.Config.FFmpegPath, "ffmpeg")
ffprobePath := resolveOr(h.Config.FFprobePath, "ffprobe")
_, ffmpegErr := exec.LookPath(ffmpegPath)
_, ffprobeErr := exec.LookPath(ffprobePath)
return struct {
FFmpegPath string
FFmpegFound bool
FFprobePath string
FFprobeFound bool
}{
FFmpegPath: ffmpegPath,
FFmpegFound: ffmpegErr == nil,
FFprobePath: ffprobePath,
FFprobeFound: ffprobeErr == nil,
}
}
func resolveOr(configured, def string) string {
if configured != "" {
return configured
}
return def
}
func (h *Web) handleSetup(w http.ResponseWriter, r *http.Request) {
n, err := database.CountUsers(h.DB)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if n > 0 {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
folderName := r.FormValue("folder_name")
folderPath := r.FormValue("folder_path")
if username == "" || len(password) < 8 || folderPath == "" {
render(w, "setup", PageData{
Title: "Setup", CSRFToken: h.CSRF.Token(w, r),
Flash: "Please fill in all fields (password must be at least 8 characters)", FlashKind: "danger",
Data: h.setupData(),
})
return
}
user, err := database.CreateUser(h.DB, username, "", "", true)
if err != nil {
render(w, "setup", PageData{
Title: "Setup", CSRFToken: h.CSRF.Token(w, r),
Flash: "Could not create the administrator account: " + err.Error(), FlashKind: "danger",
Data: h.setupData(),
})
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 {
render(w, "setup", PageData{
Title: "Setup", CSRFToken: h.CSRF.Token(w, r),
Flash: "Administrator account created, but the music folder could not be added: " + err.Error(), FlashKind: "warning",
Data: h.setupData(),
})
return
}
if err := h.Sessions.Issue(w, r, user.ID); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if h.Scanner != nil {
go func() {
_ = h.Scanner.ScanFolder(context.Background(), folder.ID)
}()
}
http.Redirect(w, r, "/", http.StatusFound)
}