Fixes for public mode authentication flows.
This commit is contained in:
+13
-8
@@ -95,14 +95,19 @@ async def control(req, ws):
|
|||||||
async def watch(req, ws):
|
async def watch(req, ws):
|
||||||
# Build user info from either built-in auth or SSO
|
# Build user info from either built-in auth or SSO
|
||||||
user_info = None
|
user_info = None
|
||||||
if sso_user := getattr(req.ctx, "sso_user", None):
|
if sso.paskia_enabled():
|
||||||
# SSO auth (paskia mode): extract from validation response
|
# SSO auth: call validation to get user info (don't enforce auth in public mode)
|
||||||
ctx = sso_user.get("ctx", {})
|
try:
|
||||||
perms = ctx.get("permissions", [])
|
await sso.validate_sso_request(req)
|
||||||
user_info = {
|
except Exception:
|
||||||
"username": ctx.get("user", {}).get("display_name", ""),
|
pass # Ignore auth errors, user_info stays None
|
||||||
"privileged": "cista:admin" in perms,
|
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:
|
elif req.ctx.user:
|
||||||
# Built-in auth: use local user database
|
# Built-in auth: use local user database
|
||||||
user_info = {
|
user_info = {
|
||||||
|
|||||||
+1
-4
@@ -257,10 +257,7 @@ def get_files(wanted: set) -> list[tuple[PurePosixPath, Path]]:
|
|||||||
@app.get("/zip/<keys>/<zipfile:ext=zip>")
|
@app.get("/zip/<keys>/<zipfile:ext=zip>")
|
||||||
async def zip_download(req, keys, zipfile, ext):
|
async def zip_download(req, keys, zipfile, ext):
|
||||||
"""Download a zip archive of the given keys"""
|
"""Download a zip archive of the given keys"""
|
||||||
if config.config.authentication == "paskia":
|
await auth.verify(req)
|
||||||
await auth.verify_sso(req)
|
|
||||||
else:
|
|
||||||
auth.verify(req)
|
|
||||||
|
|
||||||
wanted = set(keys.split("+"))
|
wanted = set(keys.split("+"))
|
||||||
files = get_files(wanted)
|
files = get_files(wanted)
|
||||||
|
|||||||
+13
-16
@@ -236,39 +236,36 @@ async def verify(request, *, privileged=False):
|
|||||||
|
|
||||||
For paskia mode (PASKIA_BACKEND_URL set), validates against the SSO backend.
|
For paskia mode (PASKIA_BACKEND_URL set), validates against the SSO backend.
|
||||||
For built-in mode, checks session-based authentication.
|
For built-in mode, checks session-based authentication.
|
||||||
For public mode (config.public=True), allows all requests.
|
For public mode (config.public=True), skips auth unless privileged is required.
|
||||||
|
|
||||||
All 401/403 responses include auth.iframe URL for consistent frontend handling
|
|
||||||
via the paskia library's showAuthIframe().
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: The Sanic request object
|
request: The Sanic request object
|
||||||
privileged: If True, requires admin privileges
|
privileged: If True, requires admin privileges (always enforced even in public mode)
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Unauthorized: If authentication is required
|
Unauthorized: If authentication is required
|
||||||
Forbidden: If access is denied
|
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()
|
sso = _get_sso()
|
||||||
if sso.paskia_enabled():
|
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"
|
perm = "cista:admin" if privileged else "cista:login"
|
||||||
await sso.validate_sso_request(request, perm=perm)
|
await sso.validate_sso_request(request, perm=perm)
|
||||||
return
|
return
|
||||||
|
|
||||||
user = getattr(request.ctx, "user", None)
|
user = getattr(request.ctx, "user", None)
|
||||||
if privileged:
|
if privileged:
|
||||||
if user:
|
if user and user.privileged:
|
||||||
if user.privileged:
|
return
|
||||||
return
|
raise Forbidden(
|
||||||
raise Forbidden(
|
"Access Forbidden: Only for privileged users",
|
||||||
"Access Forbidden: Only for privileged users",
|
quiet=True,
|
||||||
quiet=True,
|
)
|
||||||
)
|
if user:
|
||||||
elif config.config.public or user:
|
|
||||||
return
|
return
|
||||||
# Return iframe URL for paskia library to show login dialog
|
|
||||||
raise Unauthorized(
|
raise Unauthorized(
|
||||||
f"Login required for {request.path}",
|
f"Login required for {request.path}",
|
||||||
"cookie",
|
"cookie",
|
||||||
|
|||||||
@@ -94,11 +94,11 @@ const settingsMenu = (e: Event) => {
|
|||||||
|
|
||||||
if (store.user.isLoggedIn) {
|
if (store.user.isLoggedIn) {
|
||||||
items.push({ label: '🚪 Logout', onClick: () => store.logout() })
|
items.push({ label: '🚪 Logout', onClick: () => store.logout() })
|
||||||
} else if (!ssoStore.isExternalAuth) {
|
} else if (store.server.public) {
|
||||||
// Show login in paskia iframe overlay
|
// Show login option only in public mode (non-public modes trigger auth automatically)
|
||||||
items.push({ label: '🔐 Login', onClick: async () => {
|
items.push({ label: '🔐 Login', onClick: async () => {
|
||||||
try {
|
try {
|
||||||
await showAuthIframe('/auth/restricted')
|
await showAuthIframe('/auth/restricted#theme=light')
|
||||||
resumeWatching()
|
resumeWatching()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Login cancelled')
|
console.log('Login cancelled')
|
||||||
|
|||||||
Reference in New Issue
Block a user