package api import ( "net/http" "time" "geniusrun/backend/internal/auth" ) // SessionConfig configures how the app-login session cookie is minted and // validated. Secure should mirror config.Config.SessionSecure (true once // the app is served over HTTPS). type SessionConfig struct { Secret []byte Duration time.Duration Secure bool } type sessionMeResponse struct { Name string `json:"name"` Email string `json:"email"` } func (s *Server) handleSessionLogin(w http.ResponseWriter, r *http.Request) { authURL, txn, err := s.Auth.BeginLogin() if err != nil { writeError(w, http.StatusBadGateway, err.Error()) return } cookie, err := auth.MintTxnCookie(txn, s.Session.Secret, s.Session.Secure) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } http.SetCookie(w, cookie) http.Redirect(w, r, authURL, http.StatusFound) } func (s *Server) handleSessionCallback(w http.ResponseWriter, r *http.Request) { txnCookie, err := r.Cookie(auth.TxnCookieName) if err != nil { http.Redirect(w, r, "/?auth_error=failed", http.StatusFound) return } http.SetCookie(w, auth.ClearCookie(auth.TxnCookieName, s.Session.Secure)) txn, err := auth.ParseTxnCookie(txnCookie, s.Session.Secret) if err != nil { http.Redirect(w, r, "/?auth_error=failed", http.StatusFound) return } result, err := s.Auth.HandleCallback(r.Context(), txn, r.URL.Query()) if err != nil { http.Redirect(w, r, "/?auth_error=failed", http.StatusFound) return } if !result.Authorized { http.Redirect(w, r, "/?auth_error=forbidden", http.StatusFound) return } sessionCookie, err := auth.MintSessionCookie(result.Claims, s.Session.Secret, s.Session.Duration, s.Session.Secure) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } http.SetCookie(w, sessionCookie) http.Redirect(w, r, "/", http.StatusFound) } func (s *Server) handleSessionLogout(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, auth.ClearCookie(auth.SessionCookieName, s.Session.Secure)) http.Redirect(w, r, s.Auth.EndSessionURL("/"), http.StatusFound) } func (s *Server) handleSessionMe(w http.ResponseWriter, r *http.Request) { claims, ok := auth.ClaimsFromContext(r.Context()) if !ok { // Unreachable in practice -- RequireSession already 401s before this // handler runs -- but fail closed rather than panic if that ever changes. writeError(w, http.StatusUnauthorized, "not authenticated") return } writeJSON(w, http.StatusOK, sessionMeResponse{Name: claims.Name, Email: claims.Email}) }