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.
This commit is contained in:
@@ -113,6 +113,7 @@ func (s *Server) UserInterface() {
|
|||||||
s.mux.HandleFunc("/api/auth/challenge", s.handleAuthChallenge)
|
s.mux.HandleFunc("/api/auth/challenge", s.handleAuthChallenge)
|
||||||
s.mux.HandleFunc("/api/auth/login", s.handleAuthLogin)
|
s.mux.HandleFunc("/api/auth/login", s.handleAuthLogin)
|
||||||
s.mux.HandleFunc("/api/auth/status", s.handleAuthStatus)
|
s.mux.HandleFunc("/api/auth/status", s.handleAuthStatus)
|
||||||
|
s.mux.HandleFunc("/api/auth/logout", s.handleAuthLogout)
|
||||||
s.mux.HandleFunc("/api/permissions/", s.handlePermissions)
|
s.mux.HandleFunc("/api/permissions/", s.handlePermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +207,16 @@ func (s *Server) handleAuthLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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"}`))
|
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)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
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}`))
|
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
|
// handlePermissions returns the permission level for a given pubkey
|
||||||
func (s *Server) handlePermissions(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handlePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
|
|||||||
@@ -6,9 +6,14 @@ function App() {
|
|||||||
const [statusType, setStatusType] = useState('info');
|
const [statusType, setStatusType] = useState('info');
|
||||||
const [profileData, setProfileData] = useState(null);
|
const [profileData, setProfileData] = useState(null);
|
||||||
|
|
||||||
|
const [checkingAuth, setCheckingAuth] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check authentication status on page load
|
// Check authentication status on page load
|
||||||
checkStatus();
|
(async () => {
|
||||||
|
await checkStatus();
|
||||||
|
setCheckingAuth(false);
|
||||||
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Effect to fetch profile when user changes
|
// Effect to fetch profile when user changes
|
||||||
@@ -30,17 +35,20 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('/api/auth/status');
|
const response = await fetch('/api/auth/status');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.authenticated) {
|
if (data.authenticated && data.pubkey) {
|
||||||
setUser(data.pubkey);
|
// Fetch permission first, then set user and profile
|
||||||
updateStatus(`Already authenticated as: ${data.pubkey.slice(0, 16)}...`, 'success');
|
try {
|
||||||
|
|
||||||
// Check permissions if authenticated
|
|
||||||
if (data.pubkey) {
|
|
||||||
const permResponse = await fetch(`/api/permissions/${data.pubkey}`);
|
const permResponse = await fetch(`/api/permissions/${data.pubkey}`);
|
||||||
const permData = await permResponse.json();
|
const permData = await permResponse.json();
|
||||||
if (permData && permData.permission) {
|
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) {
|
} catch (error) {
|
||||||
@@ -306,11 +314,19 @@ function App() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
async function logout() {
|
||||||
|
try {
|
||||||
|
await fetch('/api/auth/logout', { method: 'POST' });
|
||||||
|
} catch (_) {}
|
||||||
setUser(null);
|
setUser(null);
|
||||||
updateStatus('Logged out', 'info');
|
updateStatus('Logged out', 'info');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent UI flash: wait until we checked auth status
|
||||||
|
if (checkingAuth) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{user?.permission ? (
|
{user?.permission ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user