Files
vpwg/Go/internal/web/handlers/login.go
T

101 lines
3.0 KiB
Go

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