From 82665444f41d2936e46d9ed6bab300106ca2517b Mon Sep 17 00:00:00 2001 From: mleku Date: Sat, 20 Sep 2025 20:30:14 +0100 Subject: [PATCH] Add `/api/auth/logout` endpoint and improve auth flow. - Implemented `handleAuthLogout` to support user logout by clearing session cookies. - Improved `/api/auth/status` with authentication cookie validation for persistent login state. - Enhanced `App.jsx` to prevent UI flash during auth status checks and streamline logout flow. - Refined user profile handling and permission fetch logic for better reliability. --- app/server.go | 41 +++++++++++++++++++++++++++++++++++++++-- app/web/src/App.jsx | 34 +++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/app/server.go b/app/server.go index 49cea09..c194fa0 100644 --- a/app/server.go +++ b/app/server.go @@ -113,6 +113,7 @@ func (s *Server) UserInterface() { s.mux.HandleFunc("/api/auth/challenge", s.handleAuthChallenge) s.mux.HandleFunc("/api/auth/login", s.handleAuthLogin) s.mux.HandleFunc("/api/auth/status", s.handleAuthStatus) + s.mux.HandleFunc("/api/auth/logout", s.handleAuthLogout) s.mux.HandleFunc("/api/permissions/", s.handlePermissions) } @@ -206,7 +207,16 @@ func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) { return } - // Authentication successful + // Authentication successful: set a simple session cookie with the pubkey + cookie := &http.Cookie{ + Name: "orly_auth", + Value: hex.Enc(evt.Pubkey), + Path: "/", + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + MaxAge: 60 * 60 * 24 * 30, // 30 days + } + http.SetCookie(w, cookie) w.Write([]byte(`{"success": true, "pubkey": "` + hex.Enc(evt.Pubkey) + `", "message": "Authentication successful"}`)) } @@ -216,11 +226,38 @@ func (s *Server) handleAuthStatus(w http.ResponseWriter, r *http.Request) { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } - + w.Header().Set("Content-Type", "application/json") + // Check for auth cookie + if c, err := r.Cookie("orly_auth"); err == nil && c.Value != "" { + // Validate pubkey format (hex) + if _, err := hex.Dec(c.Value); !chk.E(err) { + w.Write([]byte(`{"authenticated": true, "pubkey": "` + c.Value + `"}`)) + return + } + } w.Write([]byte(`{"authenticated": false}`)) } +// handleAuthLogout clears the auth cookie +func (s *Server) handleAuthLogout(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + // Expire the cookie + http.SetCookie(w, &http.Cookie{ + Name: "orly_auth", + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + }) + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"success": true}`)) +} + // handlePermissions returns the permission level for a given pubkey func (s *Server) handlePermissions(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { diff --git a/app/web/src/App.jsx b/app/web/src/App.jsx index f06da40..073f1ed 100644 --- a/app/web/src/App.jsx +++ b/app/web/src/App.jsx @@ -6,9 +6,14 @@ function App() { const [statusType, setStatusType] = useState('info'); const [profileData, setProfileData] = useState(null); + const [checkingAuth, setCheckingAuth] = useState(true); + useEffect(() => { // Check authentication status on page load - checkStatus(); + (async () => { + await checkStatus(); + setCheckingAuth(false); + })(); }, []); // Effect to fetch profile when user changes @@ -30,17 +35,20 @@ function App() { try { const response = await fetch('/api/auth/status'); const data = await response.json(); - if (data.authenticated) { - setUser(data.pubkey); - updateStatus(`Already authenticated as: ${data.pubkey.slice(0, 16)}...`, 'success'); - - // Check permissions if authenticated - if (data.pubkey) { + if (data.authenticated && data.pubkey) { + // Fetch permission first, then set user and profile + try { const permResponse = await fetch(`/api/permissions/${data.pubkey}`); const permData = await permResponse.json(); if (permData && permData.permission) { - setUser({...data, permission: permData.permission}); + const fullUser = { pubkey: data.pubkey, permission: permData.permission }; + setUser(fullUser); + updateStatus(`Already authenticated as: ${data.pubkey.slice(0, 16)}...`, 'success'); + // Fire and forget profile fetch + fetchUserProfile(data.pubkey); } + } catch (_) { + // ignore permission fetch errors } } } catch (error) { @@ -306,11 +314,19 @@ function App() { } } - function logout() { + async function logout() { + try { + await fetch('/api/auth/logout', { method: 'POST' }); + } catch (_) {} setUser(null); updateStatus('Logged out', 'info'); } + // Prevent UI flash: wait until we checked auth status + if (checkingAuth) { + return null; + } + return ( <> {user?.permission ? (