From 16de7b5f1fe1c2262d252b625b8cb202ab7776b8 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sat, 30 Aug 2025 16:47:38 -0600 Subject: [PATCH] Allow specifying multiple permissions. --- passkey/fastapi/api.py | 4 ++-- passkey/fastapi/authz.py | 15 ++++++++------- passkey/fastapi/mainapp.py | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/passkey/fastapi/api.py b/passkey/fastapi/api.py index ce6a24d..231f558 100644 --- a/passkey/fastapi/api.py +++ b/passkey/fastapi/api.py @@ -44,12 +44,12 @@ def register_api_routes(app: FastAPI): @app.post("/auth/validate") async def validate_token( - response: Response, perm: str | None = None, auth=Cookie(None) + response: Response, perm: list[str] | None = None, auth=Cookie(None) ): """Lightweight token validation endpoint. Query Params: - - perm: optional permission ID the caller must possess + - perm: repeated permission IDs the caller must possess (ALL required) """ s = await authz.verify(auth, perm) diff --git a/passkey/fastapi/authz.py b/passkey/fastapi/authz.py index b5e6b4c..be6701b 100644 --- a/passkey/fastapi/authz.py +++ b/passkey/fastapi/authz.py @@ -12,23 +12,24 @@ from ..globals import db from ..util.tokens import session_key -async def verify(auth: str | None, perm: str | None): - """Validate session token and optional permission. +async def verify(auth: str | None, perms: list[str] | None): + """Validate session token and optional list of required permissions. Returns the Session object on success. Raises HTTPException on failure. 401: unauthenticated / invalid session - 403: missing required permission + 403: one or more required permissions missing """ if not auth: raise HTTPException(status_code=401, detail="Authentication required") session = await get_session(auth) - if perm: + if perms: ctx = await db.instance.get_session_context(session_key(auth)) if not ctx: raise HTTPException(status_code=401, detail="Session not found") - role_perms = set(ctx.role.permissions or []) - org_perms = set(ctx.org.permissions or []) if ctx.org else set() - if perm not in role_perms and perm not in org_perms: + available = set(ctx.role.permissions or []) | ( + set(ctx.org.permissions or []) if ctx.org else set() + ) + if any(p not in available for p in perms): raise HTTPException(status_code=403, detail="Permission required") return session diff --git a/passkey/fastapi/mainapp.py b/passkey/fastapi/mainapp.py index 4d90332..c6aa08f 100644 --- a/passkey/fastapi/mainapp.py +++ b/passkey/fastapi/mainapp.py @@ -73,12 +73,12 @@ app.mount("/auth/ws", ws.app) @app.get("/auth/forward-auth") async def forward_authentication( - request: Request, perm: str | None = None, auth=Cookie(None) + request: Request, perm: list[str] | None = None, auth=Cookie(None) ): """A validation endpoint to use with Caddy forward_auth or Nginx auth_request. Query Params: - - perm: optional permission ID the authenticated user must possess (role or org). + - perm: repeated permission IDs the authenticated user must possess (ALL required). Success: 204 No Content with x-auth-user-uuid header. Failure (unauthenticated / unauthorized): 4xx with index.html body so the