From 9f423135edc6ca0eebc9f285325f4aa101f0d8e2 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Wed, 6 Aug 2025 10:09:55 -0600 Subject: [PATCH] Refactor to not use status: success, but HTTP codes, and renamed the error key to detail to match FastAPI's own. --- frontend/src/components/DeviceLinkView.vue | 2 +- frontend/src/stores/auth.js | 10 ++++---- frontend/src/utils/awaitable-websocket.js | 4 ++-- passkey/fastapi/api.py | 27 ++++++++++------------ passkey/fastapi/reset.py | 11 +++++---- passkey/fastapi/ws.py | 22 +++++++++--------- 6 files changed, 37 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/DeviceLinkView.vue b/frontend/src/components/DeviceLinkView.vue index d86fa48..5a1a73b 100644 --- a/frontend/src/components/DeviceLinkView.vue +++ b/frontend/src/components/DeviceLinkView.vue @@ -48,7 +48,7 @@ onMounted(async () => { try { const response = await fetch('/auth/create-link', { method: 'POST' }) 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 diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index bb158ce..8db2392 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -36,8 +36,8 @@ export const useAuthStore = defineStore('auth', { headers: {'Authorization': `Bearer ${sessionToken}`}, }) const result = await response.json() - if (result.error) { - throw new Error(result.error) + if (result.detail) { + throw new Error(result.detail) } return result }, @@ -73,7 +73,7 @@ export const useAuthStore = defineStore('auth', { async loadUserInfo() { const response = await fetch('/auth/user-info', {method: 'POST'}) 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.currentCredentials = result.credentials || [] @@ -82,9 +82,9 @@ export const useAuthStore = defineStore('auth', { console.log('User info loaded:', result) }, 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() - if (result.error) throw new Error(`Server: ${result.error}`) + if (result.detail) throw new Error(`Server: ${result.detail}`) await this.loadUserInfo() }, diff --git a/frontend/src/utils/awaitable-websocket.js b/frontend/src/utils/awaitable-websocket.js index 34dab89..befc237 100644 --- a/frontend/src/utils/awaitable-websocket.js +++ b/frontend/src/utils/awaitable-websocket.js @@ -57,8 +57,8 @@ class AwaitableWebSocket extends WebSocket { console.error("Failed to parse JSON from WebSocket message", data, err) throw new Error("Failed to parse JSON from WebSocket message") } - if (parsed.error) { - throw new Error(`Server: ${parsed.error}`) + if (parsed.detail) { + throw new Error(`Server: ${parsed.detail}`) } return parsed } diff --git a/passkey/fastapi/api.py b/passkey/fastapi/api.py index 911996a..7eba391 100644 --- a/passkey/fastapi/api.py +++ b/passkey/fastapi/api.py @@ -28,17 +28,17 @@ def register_api_routes(app: FastAPI): """Register all API routes on the FastAPI app.""" @app.post("/auth/validate") - async def validate_token(auth=Cookie(None)): + async def validate_token(response: Response, auth=Cookie(None)): """Lightweight token validation endpoint.""" try: s = await get_session(auth) return { - "status": "success", "valid": True, "user_uuid": str(s.user_uuid), } except ValueError: - return {"status": "error", "valid": False} + response.status_code = 401 + return {"valid": False} @app.post("/auth/user-info") 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"]) return { - "status": "success", "authenticated": not reset, "session_type": s.info["type"], "user": { @@ -99,24 +98,23 @@ def register_api_routes(app: FastAPI): } except ValueError as e: response.status_code = 400 - return {"error": f"Failed to get user info: {e}"} + return {"detail": f"Failed to get user info: {e}"} except Exception: response.status_code = 500 - - return {"error": "Failed to get user info"} + return {"detail": "Failed to get user info"} @app.post("/auth/logout") async def api_logout(response: Response, auth=Cookie(None)): """Log out the current user by clearing the session cookie and deleting from database.""" if not auth: - return {"status": "success", "message": "Already logged out"} + return {"message": "Already logged out"} # Remove from database if possible try: await db.instance.delete_session(session_key(auth)) except Exception: ... response.delete_cookie("auth") - return {"status": "success", "message": "Logged out successfully"} + return {"message": "Logged out successfully"} @app.post("/auth/set-session") 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) return { - "status": "success", "message": "Session cookie set successfully", "user_uuid": str(user.user_uuid), } except ValueError as e: response.status_code = 400 - return {"error": str(e)} + return {"detail": str(e)} except Exception: response.status_code = 500 - return {"error": "Failed to set session"} + return {"detail": "Failed to set session"} @app.delete("/auth/credential/{uuid}") async def api_delete_credential( @@ -147,11 +144,11 @@ def register_api_routes(app: FastAPI): """Delete a specific credential for the current user.""" try: await delete_credential(uuid, auth) - return {"status": "success", "message": "Credential deleted successfully"} + return {"message": "Credential deleted successfully"} except ValueError as e: response.status_code = 400 - return {"error": str(e)} + return {"detail": str(e)} except Exception: response.status_code = 500 - return {"error": "Failed to delete credential"} + return {"detail": "Failed to delete credential"} diff --git a/passkey/fastapi/reset.py b/passkey/fastapi/reset.py index 806fe7b..b4d5ec7 100644 --- a/passkey/fastapi/reset.py +++ b/passkey/fastapi/reset.py @@ -1,6 +1,6 @@ import logging -from fastapi import Cookie, HTTPException, Request +from fastapi import Cookie, HTTPException, Request, Response from fastapi.responses import RedirectResponse 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.""" @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.""" try: # Require authentication @@ -33,16 +33,17 @@ def register_reset_routes(app): url = f"{request.headers['origin']}{path}" return { - "status": "success", "message": "Registration link generated successfully", "url": url, "expires": expires().isoformat(), } except ValueError: - return {"error": "Authentication required"} + response.status_code = 401 + return {"detail": "Authentication required"} 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}") async def reset_authentication( diff --git a/passkey/fastapi/ws.py b/passkey/fastapi/ws.py index b7ddd9b..1ed569f 100644 --- a/passkey/fastapi/ws.py +++ b/passkey/fastapi/ws.py @@ -16,9 +16,10 @@ import uuid7 from fastapi import Cookie, FastAPI, Query, WebSocket, WebSocketDisconnect 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 ..sansio import Passkey +from ..util import passphrase from ..util.tokens import create_token, session_key from .session import infodict @@ -80,18 +81,17 @@ async def websocket_register_new( await ws.send_json( { - "status": "success", "user_uuid": str(user_uuid), "session_token": token, } ) except ValueError as e: - await ws.send_json({"error": str(e)}) + await ws.send_json({"detail": str(e)}) except WebSocketDisconnect: pass except Exception: 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") @@ -100,7 +100,9 @@ async def websocket_register_add(ws: WebSocket, auth=Cookie(None)): await ws.accept() origin = ws.headers["origin"] 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 # 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( { - "status": "success", "user_uuid": str(user.uuid), "credential_uuid": str(credential.uuid), "session_token": token, @@ -134,12 +135,12 @@ async def websocket_register_add(ws: WebSocket, auth=Cookie(None)): } ) except ValueError as e: - await ws.send_json({"error": str(e)}) + await ws.send_json({"detail": str(e)}) except WebSocketDisconnect: pass except Exception: logging.exception("Internal Server Error") - await ws.send_json({"error": "Internal Server Error"}) + await ws.send_json({"detail": "Internal Server Error"}) @app.websocket("/authenticate") @@ -168,16 +169,15 @@ async def websocket_authenticate(ws: WebSocket): await ws.send_json( { - "status": "success", "user_uuid": str(stored_cred.user_uuid), "session_token": token, } ) except (ValueError, InvalidAuthenticationResponse) as e: logging.exception("ValueError") - await ws.send_json({"error": str(e)}) + await ws.send_json({"detail": str(e)}) except WebSocketDisconnect: pass except Exception: logging.exception("Internal Server Error") - await ws.send_json({"error": "Internal Server Error"}) + await ws.send_json({"detail": "Internal Server Error"})