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 } 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) 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 } 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) }