177 lines
4.6 KiB
Go
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)
|
|
}
|