From 8f76d770ee68d7b4790ff45b5ab0cc6ab3182afd Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sat, 31 Jan 2026 04:48:44 +0000 Subject: [PATCH] Fixes for public mode authentication flows. --- cista/api.py | 21 ++++++++++++------- cista/app.py | 5 +---- cista/auth.py | 29 ++++++++++++-------------- frontend/src/components/HeaderMain.vue | 6 +++--- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/cista/api.py b/cista/api.py index 7a2799a..3f7fce0 100644 --- a/cista/api.py +++ b/cista/api.py @@ -95,14 +95,19 @@ async def control(req, ws): async def watch(req, ws): # Build user info from either built-in auth or SSO user_info = None - if sso_user := getattr(req.ctx, "sso_user", None): - # SSO auth (paskia mode): extract from validation response - ctx = sso_user.get("ctx", {}) - perms = ctx.get("permissions", []) - user_info = { - "username": ctx.get("user", {}).get("display_name", ""), - "privileged": "cista:admin" in perms, - } + if sso.paskia_enabled(): + # SSO auth: call validation to get user info (don't enforce auth in public mode) + try: + await sso.validate_sso_request(req) + except Exception: + pass # Ignore auth errors, user_info stays None + if sso_user := getattr(req.ctx, "sso_user", None): + ctx = sso_user.get("ctx", {}) + perms = ctx.get("permissions", []) + user_info = { + "username": ctx.get("user", {}).get("display_name", ""), + "privileged": "cista:admin" in perms, + } elif req.ctx.user: # Built-in auth: use local user database user_info = { diff --git a/cista/app.py b/cista/app.py index f65de02..42425b3 100644 --- a/cista/app.py +++ b/cista/app.py @@ -257,10 +257,7 @@ def get_files(wanted: set) -> list[tuple[PurePosixPath, Path]]: @app.get("/zip//") async def zip_download(req, keys, zipfile, ext): """Download a zip archive of the given keys""" - if config.config.authentication == "paskia": - await auth.verify_sso(req) - else: - auth.verify(req) + await auth.verify(req) wanted = set(keys.split("+")) files = get_files(wanted) diff --git a/cista/auth.py b/cista/auth.py index ff9bc1b..7f1961a 100644 --- a/cista/auth.py +++ b/cista/auth.py @@ -236,39 +236,36 @@ async def verify(request, *, privileged=False): For paskia mode (PASKIA_BACKEND_URL set), validates against the SSO backend. For built-in mode, checks session-based authentication. - For public mode (config.public=True), allows all requests. - - All 401/403 responses include auth.iframe URL for consistent frontend handling - via the paskia library's showAuthIframe(). + For public mode (config.public=True), skips auth unless privileged is required. Args: request: The Sanic request object - privileged: If True, requires admin privileges + privileged: If True, requires admin privileges (always enforced even in public mode) Raises: Unauthorized: If authentication is required Forbidden: If access is denied """ + # Public mode: skip auth unless privileged access is required + if config.config.public and not privileged: + return + sso = _get_sso() if sso.paskia_enabled(): - # SSO validation against auth backend - # Always check cista:login; privileged flag comes from response perm list perm = "cista:admin" if privileged else "cista:login" await sso.validate_sso_request(request, perm=perm) return user = getattr(request.ctx, "user", None) if privileged: - if user: - if user.privileged: - return - raise Forbidden( - "Access Forbidden: Only for privileged users", - quiet=True, - ) - elif config.config.public or user: + if user and user.privileged: + return + raise Forbidden( + "Access Forbidden: Only for privileged users", + quiet=True, + ) + if user: return - # Return iframe URL for paskia library to show login dialog raise Unauthorized( f"Login required for {request.path}", "cookie", diff --git a/frontend/src/components/HeaderMain.vue b/frontend/src/components/HeaderMain.vue index 8dd9509..6b066f8 100644 --- a/frontend/src/components/HeaderMain.vue +++ b/frontend/src/components/HeaderMain.vue @@ -94,11 +94,11 @@ const settingsMenu = (e: Event) => { if (store.user.isLoggedIn) { items.push({ label: '🚪 Logout', onClick: () => store.logout() }) - } else if (!ssoStore.isExternalAuth) { - // Show login in paskia iframe overlay + } else if (store.server.public) { + // Show login option only in public mode (non-public modes trigger auth automatically) items.push({ label: '🔐 Login', onClick: async () => { try { - await showAuthIframe('/auth/restricted') + await showAuthIframe('/auth/restricted#theme=light') resumeWatching() } catch (e) { console.log('Login cancelled')