diff --git a/passkey/fastapi/admin.py b/passkey/fastapi/admin.py index 276fa5d..efee19c 100644 --- a/passkey/fastapi/admin.py +++ b/passkey/fastapi/admin.py @@ -2,7 +2,7 @@ import logging from datetime import timezone from uuid import UUID, uuid4 -from fastapi import Body, Cookie, FastAPI, HTTPException, Request +from fastapi import Body, FastAPI, HTTPException, Request from fastapi.responses import FileResponse, JSONResponse from ..authsession import reset_expires @@ -18,6 +18,7 @@ from ..util import ( ) from ..util.tokens import encode_session_key, session_key from . import authz +from .session import AUTH_COOKIE app = FastAPI() @@ -34,7 +35,7 @@ async def general_exception_handler(_request, exc: Exception): @app.get("/") -async def adminapp(request: Request, auth=Cookie(None, alias="__Host-auth")): +async def adminapp(request: Request, auth=AUTH_COOKIE): """Serve admin SPA only for authenticated users with admin/org permissions. On missing/invalid session or insufficient permissions, serve restricted SPA. @@ -57,7 +58,7 @@ async def adminapp(request: Request, auth=Cookie(None, alias="__Host-auth")): @app.get("/orgs") -async def admin_list_orgs(request: Request, auth=Cookie(None, alias="__Host-auth")): +async def admin_list_orgs(request: Request, auth=AUTH_COOKIE): ctx = await authz.verify( auth, ["auth:admin", "auth:org:*"], @@ -100,7 +101,7 @@ async def admin_list_orgs(request: Request, auth=Cookie(None, alias="__Host-auth @app.post("/orgs") async def admin_create_org( - request: Request, payload: dict = Body(...), auth=Cookie(None, alias="__Host-auth") + request: Request, payload: dict = Body(...), auth=AUTH_COOKIE ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all @@ -132,7 +133,7 @@ async def admin_update_org( org_uuid: UUID, request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): ctx = await authz.verify( auth, @@ -165,9 +166,7 @@ async def admin_update_org( @app.delete("/orgs/{org_uuid}") -async def admin_delete_org( - org_uuid: UUID, request: Request, auth=Cookie(None, alias="__Host-auth") -): +async def admin_delete_org(org_uuid: UUID, request: Request, auth=AUTH_COOKIE): ctx = await authz.verify( auth, ["auth:admin", f"auth:org:{org_uuid}"], @@ -200,7 +199,7 @@ async def admin_add_org_permission( org_uuid: UUID, permission_id: str, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all @@ -214,7 +213,7 @@ async def admin_remove_org_permission( org_uuid: UUID, permission_id: str, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all @@ -231,7 +230,7 @@ async def admin_create_role( org_uuid: UUID, request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, @@ -266,7 +265,7 @@ async def admin_update_role( role_uuid: UUID, request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): # Verify caller is global admin or admin of provided org ctx = await authz.verify( @@ -315,7 +314,7 @@ async def admin_delete_role( org_uuid: UUID, role_uuid: UUID, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): ctx = await authz.verify( auth, @@ -343,7 +342,7 @@ async def admin_create_user( org_uuid: UUID, request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, @@ -379,7 +378,7 @@ async def admin_update_user_role( user_uuid: UUID, request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): ctx = await authz.verify( auth, @@ -422,7 +421,7 @@ async def admin_create_user_registration_link( org_uuid: UUID, user_uuid: UUID, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): try: user_org, _role_name = await db.instance.get_user_organization(user_uuid) @@ -472,7 +471,7 @@ async def admin_get_user_detail( org_uuid: UUID, user_uuid: UUID, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): try: user_org, role_name = await db.instance.get_user_organization(user_uuid) @@ -619,7 +618,7 @@ async def admin_update_user_display_name( user_uuid: UUID, request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): try: user_org, _role_name = await db.instance.get_user_organization(user_uuid) @@ -653,7 +652,7 @@ async def admin_delete_user_credential( user_uuid: UUID, credential_uuid: UUID, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): try: user_org, _role_name = await db.instance.get_user_organization(user_uuid) @@ -680,9 +679,7 @@ async def admin_delete_user_credential( @app.get("/permissions") -async def admin_list_permissions( - request: Request, auth=Cookie(None, alias="__Host-auth") -): +async def admin_list_permissions(request: Request, auth=AUTH_COOKIE): ctx = await authz.verify( auth, ["auth:admin", "auth:org:*"], @@ -705,7 +702,7 @@ async def admin_list_permissions( async def admin_create_permission( request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all @@ -726,7 +723,7 @@ async def admin_update_permission( permission_id: str, display_name: str, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all @@ -746,7 +743,7 @@ async def admin_update_permission( async def admin_rename_permission( request: Request, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all @@ -777,7 +774,7 @@ async def admin_rename_permission( async def admin_delete_permission( permission_id: str, request: Request, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): await authz.verify( auth, ["auth:admin"], host=request.headers.get("host"), match=permutil.has_all diff --git a/passkey/fastapi/api.py b/passkey/fastapi/api.py index ea50c58..8c6b997 100644 --- a/passkey/fastapi/api.py +++ b/passkey/fastapi/api.py @@ -3,7 +3,6 @@ from contextlib import suppress from datetime import datetime, timedelta, timezone from fastapi import ( - Cookie, Depends, FastAPI, HTTPException, @@ -29,6 +28,7 @@ from ..globals import passkey as global_passkey from ..util import hostutil, passphrase, permutil from ..util.tokens import encode_session_key, session_key from . import authz, session, user +from .session import AUTH_COOKIE bearer_auth = HTTPBearer(auto_error=True) @@ -69,7 +69,7 @@ async def validate_token( request: Request, response: Response, perm: list[str] = Query([]), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): """Validate the current session and extend its expiry. @@ -110,7 +110,7 @@ async def forward_authentication( request: Request, response: Response, perm: list[str] = Query([]), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): """Forward auth validation for Caddy/Nginx. @@ -175,7 +175,7 @@ async def api_user_info( request: Request, response: Response, reset: str | None = None, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): authenticated = False session_record = None @@ -360,9 +360,7 @@ async def api_user_info( @app.post("/logout") -async def api_logout( - request: Request, response: Response, auth=Cookie(None, alias="__Host-auth") -): +async def api_logout(request: Request, response: Response, auth=AUTH_COOKIE): if not auth: return {"message": "Already logged out"} try: diff --git a/passkey/fastapi/mainapp.py b/passkey/fastapi/mainapp.py index ea8c129..f785e55 100644 --- a/passkey/fastapi/mainapp.py +++ b/passkey/fastapi/mainapp.py @@ -2,13 +2,14 @@ import logging import os from contextlib import asynccontextmanager -from fastapi import Cookie, FastAPI, HTTPException, Request, Response +from fastapi import FastAPI, HTTPException, Request, Response from fastapi.responses import FileResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from passkey.util import frontend, hostutil, passphrase from . import admin, api, auth_host, ws +from .session import AUTH_COOKIE @asynccontextmanager @@ -63,9 +64,7 @@ app.mount( @app.get("/") @app.get("/auth/") -async def frontapp( - request: Request, response: Response, auth=Cookie(None, alias="__Host-auth") -): +async def frontapp(request: Request, response: Response, auth=AUTH_COOKIE): """Serve the user profile SPA only for authenticated sessions; otherwise restricted SPA. Login / authentication UX is centralized in the restricted app. @@ -98,7 +97,7 @@ async def admin_root_redirect(): @app.get("/admin/", include_in_schema=False) -async def admin_root(request: Request, auth=Cookie(None, alias="__Host-auth")): +async def admin_root(request: Request, auth=AUTH_COOKIE): return await admin.adminapp(request, auth) # Delegated (enforces access control) diff --git a/passkey/fastapi/session.py b/passkey/fastapi/session.py index f2f5ff3..cdd1c64 100644 --- a/passkey/fastapi/session.py +++ b/passkey/fastapi/session.py @@ -8,11 +8,12 @@ This module provides FastAPI-specific session management functionality: Generic session management functions have been moved to authsession.py """ -from fastapi import Request, Response, WebSocket +from fastapi import Cookie, Request, Response, WebSocket from ..authsession import EXPIRES AUTH_COOKIE_NAME = "__Host-auth" +AUTH_COOKIE = Cookie(None, alias=AUTH_COOKIE_NAME) def infodict(request: Request | WebSocket, type: str) -> dict: diff --git a/passkey/fastapi/user.py b/passkey/fastapi/user.py index 9aa81b9..11d0bf1 100644 --- a/passkey/fastapi/user.py +++ b/passkey/fastapi/user.py @@ -3,7 +3,6 @@ from uuid import UUID from fastapi import ( Body, - Cookie, FastAPI, HTTPException, Request, @@ -19,6 +18,7 @@ from ..globals import db from ..util import hostutil, passphrase, tokens from ..util.tokens import decode_session_key, session_key from . import session +from .session import AUTH_COOKIE app = FastAPI() @@ -28,7 +28,7 @@ async def user_update_display_name( request: Request, response: Response, payload: dict = Body(...), - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): if not auth: raise HTTPException(status_code=401, detail="Authentication Required") @@ -46,9 +46,7 @@ async def user_update_display_name( @app.post("/logout-all") -async def api_logout_all( - request: Request, response: Response, auth=Cookie(None, alias="__Host-auth") -): +async def api_logout_all(request: Request, response: Response, auth=AUTH_COOKIE): if not auth: return {"message": "Already logged out"} try: @@ -65,7 +63,7 @@ async def api_delete_session( request: Request, response: Response, session_id: str, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): if not auth: raise HTTPException(status_code=401, detail="Authentication Required") @@ -97,7 +95,7 @@ async def api_delete_credential( request: Request, response: Response, uuid: UUID, - auth: str = Cookie(None, alias="__Host-auth"), + auth: str = AUTH_COOKIE, ): try: await delete_credential(uuid, auth, host=request.headers.get("host")) @@ -110,7 +108,7 @@ async def api_delete_credential( async def api_create_link( request: Request, response: Response, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): try: s = await get_session(auth, host=request.headers.get("host")) diff --git a/passkey/fastapi/ws.py b/passkey/fastapi/ws.py index 988186c..ebd0871 100644 --- a/passkey/fastapi/ws.py +++ b/passkey/fastapi/ws.py @@ -2,14 +2,14 @@ import logging from functools import wraps from uuid import UUID -from fastapi import Cookie, FastAPI, WebSocket, WebSocketDisconnect +from fastapi import FastAPI, WebSocket, WebSocketDisconnect from webauthn.helpers.exceptions import InvalidAuthenticationResponse from ..authsession import create_session, get_reset, get_session from ..globals import db, passkey from ..util import passphrase from ..util.tokens import create_token, session_key -from .session import infodict +from .session import AUTH_COOKIE, infodict # WebSocket error handling decorator @@ -59,7 +59,7 @@ async def websocket_register_add( ws: WebSocket, reset: str | None = None, name: str | None = None, - auth=Cookie(None, alias="__Host-auth"), + auth=AUTH_COOKIE, ): """Register a new credential for an existing user.