Refactor to not use status: success, but HTTP codes, and renamed the error key to detail to match FastAPI's own.

This commit is contained in:
Leo Vasanko 2025-08-06 10:09:55 -06:00
parent cf138d90c5
commit 9f423135ed
6 changed files with 37 additions and 39 deletions

View File

@ -48,7 +48,7 @@ onMounted(async () => {
try { try {
const response = await fetch('/auth/create-link', { method: 'POST' }) const response = await fetch('/auth/create-link', { method: 'POST' })
const result = await response.json() const result = await response.json()
if (result.error) throw new Error(result.error) if (result.detail) throw new Error(result.detail)
url.value = result.url url.value = result.url

View File

@ -36,8 +36,8 @@ export const useAuthStore = defineStore('auth', {
headers: {'Authorization': `Bearer ${sessionToken}`}, headers: {'Authorization': `Bearer ${sessionToken}`},
}) })
const result = await response.json() const result = await response.json()
if (result.error) { if (result.detail) {
throw new Error(result.error) throw new Error(result.detail)
} }
return result return result
}, },
@ -73,7 +73,7 @@ export const useAuthStore = defineStore('auth', {
async loadUserInfo() { async loadUserInfo() {
const response = await fetch('/auth/user-info', {method: 'POST'}) const response = await fetch('/auth/user-info', {method: 'POST'})
const result = await response.json() const result = await response.json()
if (result.error) throw new Error(`Server: ${result.error}`) if (result.detail) throw new Error(`Server: ${result.detail}`)
this.currentUser = result.user this.currentUser = result.user
this.currentCredentials = result.credentials || [] this.currentCredentials = result.credentials || []
@ -82,9 +82,9 @@ export const useAuthStore = defineStore('auth', {
console.log('User info loaded:', result) console.log('User info loaded:', result)
}, },
async deleteCredential(uuid) { async deleteCredential(uuid) {
const response = await fetch(`/auth/credential/${uuid}`, {method: 'DELETE'}) const response = await fetch(`/auth/credential/${uuid}`, {method: 'Delete'})
const result = await response.json() const result = await response.json()
if (result.error) throw new Error(`Server: ${result.error}`) if (result.detail) throw new Error(`Server: ${result.detail}`)
await this.loadUserInfo() await this.loadUserInfo()
}, },

View File

@ -57,8 +57,8 @@ class AwaitableWebSocket extends WebSocket {
console.error("Failed to parse JSON from WebSocket message", data, err) console.error("Failed to parse JSON from WebSocket message", data, err)
throw new Error("Failed to parse JSON from WebSocket message") throw new Error("Failed to parse JSON from WebSocket message")
} }
if (parsed.error) { if (parsed.detail) {
throw new Error(`Server: ${parsed.error}`) throw new Error(`Server: ${parsed.detail}`)
} }
return parsed return parsed
} }

View File

@ -28,17 +28,17 @@ def register_api_routes(app: FastAPI):
"""Register all API routes on the FastAPI app.""" """Register all API routes on the FastAPI app."""
@app.post("/auth/validate") @app.post("/auth/validate")
async def validate_token(auth=Cookie(None)): async def validate_token(response: Response, auth=Cookie(None)):
"""Lightweight token validation endpoint.""" """Lightweight token validation endpoint."""
try: try:
s = await get_session(auth) s = await get_session(auth)
return { return {
"status": "success",
"valid": True, "valid": True,
"user_uuid": str(s.user_uuid), "user_uuid": str(s.user_uuid),
} }
except ValueError: except ValueError:
return {"status": "error", "valid": False} response.status_code = 401
return {"valid": False}
@app.post("/auth/user-info") @app.post("/auth/user-info")
async def api_user_info(response: Response, auth=Cookie(None)): async def api_user_info(response: Response, auth=Cookie(None)):
@ -84,7 +84,6 @@ def register_api_routes(app: FastAPI):
credentials.sort(key=lambda cred: cred["created_at"]) credentials.sort(key=lambda cred: cred["created_at"])
return { return {
"status": "success",
"authenticated": not reset, "authenticated": not reset,
"session_type": s.info["type"], "session_type": s.info["type"],
"user": { "user": {
@ -99,24 +98,23 @@ def register_api_routes(app: FastAPI):
} }
except ValueError as e: except ValueError as e:
response.status_code = 400 response.status_code = 400
return {"error": f"Failed to get user info: {e}"} return {"detail": f"Failed to get user info: {e}"}
except Exception: except Exception:
response.status_code = 500 response.status_code = 500
return {"detail": "Failed to get user info"}
return {"error": "Failed to get user info"}
@app.post("/auth/logout") @app.post("/auth/logout")
async def api_logout(response: Response, auth=Cookie(None)): async def api_logout(response: Response, auth=Cookie(None)):
"""Log out the current user by clearing the session cookie and deleting from database.""" """Log out the current user by clearing the session cookie and deleting from database."""
if not auth: if not auth:
return {"status": "success", "message": "Already logged out"} return {"message": "Already logged out"}
# Remove from database if possible # Remove from database if possible
try: try:
await db.instance.delete_session(session_key(auth)) await db.instance.delete_session(session_key(auth))
except Exception: except Exception:
... ...
response.delete_cookie("auth") response.delete_cookie("auth")
return {"status": "success", "message": "Logged out successfully"} return {"message": "Logged out successfully"}
@app.post("/auth/set-session") @app.post("/auth/set-session")
async def api_set_session(response: Response, auth=Depends(bearer_auth)): async def api_set_session(response: Response, auth=Depends(bearer_auth)):
@ -128,17 +126,16 @@ def register_api_routes(app: FastAPI):
session.set_session_cookie(response, auth.credentials) session.set_session_cookie(response, auth.credentials)
return { return {
"status": "success",
"message": "Session cookie set successfully", "message": "Session cookie set successfully",
"user_uuid": str(user.user_uuid), "user_uuid": str(user.user_uuid),
} }
except ValueError as e: except ValueError as e:
response.status_code = 400 response.status_code = 400
return {"error": str(e)} return {"detail": str(e)}
except Exception: except Exception:
response.status_code = 500 response.status_code = 500
return {"error": "Failed to set session"} return {"detail": "Failed to set session"}
@app.delete("/auth/credential/{uuid}") @app.delete("/auth/credential/{uuid}")
async def api_delete_credential( async def api_delete_credential(
@ -147,11 +144,11 @@ def register_api_routes(app: FastAPI):
"""Delete a specific credential for the current user.""" """Delete a specific credential for the current user."""
try: try:
await delete_credential(uuid, auth) await delete_credential(uuid, auth)
return {"status": "success", "message": "Credential deleted successfully"} return {"message": "Credential deleted successfully"}
except ValueError as e: except ValueError as e:
response.status_code = 400 response.status_code = 400
return {"error": str(e)} return {"detail": str(e)}
except Exception: except Exception:
response.status_code = 500 response.status_code = 500
return {"error": "Failed to delete credential"} return {"detail": "Failed to delete credential"}

View File

@ -1,6 +1,6 @@
import logging import logging
from fastapi import Cookie, HTTPException, Request from fastapi import Cookie, HTTPException, Request, Response
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from ..authsession import expires, get_session from ..authsession import expires, get_session
@ -13,7 +13,7 @@ def register_reset_routes(app):
"""Register all device addition/reset routes on the FastAPI app.""" """Register all device addition/reset routes on the FastAPI app."""
@app.post("/auth/create-link") @app.post("/auth/create-link")
async def api_create_link(request: Request, auth=Cookie(None)): async def api_create_link(request: Request, response: Response, auth=Cookie(None)):
"""Create a device addition link for the authenticated user.""" """Create a device addition link for the authenticated user."""
try: try:
# Require authentication # Require authentication
@ -33,16 +33,17 @@ def register_reset_routes(app):
url = f"{request.headers['origin']}{path}" url = f"{request.headers['origin']}{path}"
return { return {
"status": "success",
"message": "Registration link generated successfully", "message": "Registration link generated successfully",
"url": url, "url": url,
"expires": expires().isoformat(), "expires": expires().isoformat(),
} }
except ValueError: except ValueError:
return {"error": "Authentication required"} response.status_code = 401
return {"detail": "Authentication required"}
except Exception as e: except Exception as e:
return {"error": f"Failed to create registration link: {str(e)}"} response.status_code = 500
return {"detail": f"Failed to create registration link: {str(e)}"}
@app.get("/auth/{reset_token}") @app.get("/auth/{reset_token}")
async def reset_authentication( async def reset_authentication(

View File

@ -16,9 +16,10 @@ import uuid7
from fastapi import Cookie, FastAPI, Query, WebSocket, WebSocketDisconnect from fastapi import Cookie, FastAPI, Query, WebSocket, WebSocketDisconnect
from webauthn.helpers.exceptions import InvalidAuthenticationResponse from webauthn.helpers.exceptions import InvalidAuthenticationResponse
from ..authsession import EXPIRES, create_session, get_session from ..authsession import EXPIRES, create_session, get_reset, get_session
from ..db import User, db from ..db import User, db
from ..sansio import Passkey from ..sansio import Passkey
from ..util import passphrase
from ..util.tokens import create_token, session_key from ..util.tokens import create_token, session_key
from .session import infodict from .session import infodict
@ -80,18 +81,17 @@ async def websocket_register_new(
await ws.send_json( await ws.send_json(
{ {
"status": "success",
"user_uuid": str(user_uuid), "user_uuid": str(user_uuid),
"session_token": token, "session_token": token,
} }
) )
except ValueError as e: except ValueError as e:
await ws.send_json({"error": str(e)}) await ws.send_json({"detail": str(e)})
except WebSocketDisconnect: except WebSocketDisconnect:
pass pass
except Exception: except Exception:
logging.exception("Internal Server Error") logging.exception("Internal Server Error")
await ws.send_json({"error": "Internal Server Error"}) await ws.send_json({"detail": "Internal Server Error"})
@app.websocket("/add_credential") @app.websocket("/add_credential")
@ -100,7 +100,9 @@ async def websocket_register_add(ws: WebSocket, auth=Cookie(None)):
await ws.accept() await ws.accept()
origin = ws.headers["origin"] origin = ws.headers["origin"]
try: try:
s = await get_session(auth, reset_allowed=True) # Try to get either a regular session or a reset session
reset = passphrase.is_well_formed(auth)
s = await (get_reset if reset else get_session)(auth)
user_uuid = s.user_uuid user_uuid = s.user_uuid
# Get user information to get the user_name # Get user information to get the user_name
@ -126,7 +128,6 @@ async def websocket_register_add(ws: WebSocket, auth=Cookie(None)):
await ws.send_json( await ws.send_json(
{ {
"status": "success",
"user_uuid": str(user.uuid), "user_uuid": str(user.uuid),
"credential_uuid": str(credential.uuid), "credential_uuid": str(credential.uuid),
"session_token": token, "session_token": token,
@ -134,12 +135,12 @@ async def websocket_register_add(ws: WebSocket, auth=Cookie(None)):
} }
) )
except ValueError as e: except ValueError as e:
await ws.send_json({"error": str(e)}) await ws.send_json({"detail": str(e)})
except WebSocketDisconnect: except WebSocketDisconnect:
pass pass
except Exception: except Exception:
logging.exception("Internal Server Error") logging.exception("Internal Server Error")
await ws.send_json({"error": "Internal Server Error"}) await ws.send_json({"detail": "Internal Server Error"})
@app.websocket("/authenticate") @app.websocket("/authenticate")
@ -168,16 +169,15 @@ async def websocket_authenticate(ws: WebSocket):
await ws.send_json( await ws.send_json(
{ {
"status": "success",
"user_uuid": str(stored_cred.user_uuid), "user_uuid": str(stored_cred.user_uuid),
"session_token": token, "session_token": token,
} }
) )
except (ValueError, InvalidAuthenticationResponse) as e: except (ValueError, InvalidAuthenticationResponse) as e:
logging.exception("ValueError") logging.exception("ValueError")
await ws.send_json({"error": str(e)}) await ws.send_json({"detail": str(e)})
except WebSocketDisconnect: except WebSocketDisconnect:
pass pass
except Exception: except Exception:
logging.exception("Internal Server Error") logging.exception("Internal Server Error")
await ws.send_json({"error": "Internal Server Error"}) await ws.send_json({"detail": "Internal Server Error"})