package handlers import ( "net/http" "strings" "amnezia-share/internal/auth" "amnezia-share/internal/settings" "amnezia-share/internal/web" ) func registerLogin(a *web.App, mux *http.ServeMux) { mux.HandleFunc("GET /login", LoginForm(a)) mux.HandleFunc("POST /login", a.CSRFProtect(LoginSubmit(a))) mux.HandleFunc("GET /logout", Logout(a)) } // oidcConfigFromSettings loads the Pocket ID OIDC settings (shared by login.go // and oidc.go). func oidcConfigFromSettings(a *web.App, r *http.Request) auth.OIDCConfig { ctx := r.Context() vals, _ := a.Settings.GetMany(ctx, settings.KeyPocketURL, settings.KeyPocketClientID, settings.KeyPocketSecret) return auth.OIDCConfig{ URL: vals[settings.KeyPocketURL], ClientID: vals[settings.KeyPocketClientID], ClientSecret: vals[settings.KeyPocketSecret], } } type loginView struct { Error string PocketEnabled bool NoAdmins bool } // LoginForm renders the admin login page, or starts the Pocket ID SSO flow when // called as GET /login?oidc=1 (login.php). func LoginForm(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if a.CurrentAdmin(r) != nil { redirectTo(w, r, a, "admin") return } cfg := oidcConfigFromSettings(a, r) pocketEnabled := cfg.Configured() if pocketEnabled && r.URL.Query().Has("oidc") { state, err := auth.GenerateState() if err != nil { http.Error(w, "не удалось начать вход", http.StatusInternalServerError) return } verifier, challenge, err := auth.GeneratePKCE() if err != nil { http.Error(w, "не удалось начать вход", http.StatusInternalServerError) return } a.Sessions.Put(ctx, "oidc_state", state) a.Sessions.Put(ctx, "oidc_verifier", verifier) redirectURI := a.AbsoluteURL(r, "oidc/callback") http.Redirect(w, r, auth.AuthURL(cfg, redirectURI, state, challenge), http.StatusSeeOther) return } count, _ := a.Auth.AdminCount(ctx) a.Render(w, r, "page_login", loginView{PocketEnabled: pocketEnabled, NoAdmins: count == 0}) } } // LoginSubmit verifies username/password and signs the admin in (login.php POST). func LoginSubmit(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() username := strings.TrimSpace(r.FormValue("username")) password := r.FormValue("password") admin, err := a.Auth.LoginAdmin(ctx, username, password) if err != nil { cfg := oidcConfigFromSettings(a, r) count, _ := a.Auth.AdminCount(ctx) a.Render(w, r, "page_login", loginView{Error: err.Error(), PocketEnabled: cfg.Configured(), NoAdmins: count == 0}) return } _ = a.Sessions.RenewToken(ctx) a.Sessions.Put(ctx, web.SessionAdminID, admin.ID) redirectTo(w, r, a, "admin") } } // Logout clears the admin session (logout.php). func Logout(a *web.App) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { a.Sessions.Remove(r.Context(), web.SessionAdminID) _ = a.Sessions.RenewToken(r.Context()) redirectTo(w, r, a, "login") } }