From 11399bb860902c01f81593a25d0f1638883a08f2 Mon Sep 17 00:00:00 2001 From: Asger Geel Weirsoee Date: Sat, 3 Oct 2026 08:56:24 +0200 Subject: [PATCH] fix(auth): admin-sider sender til login i stedet for 403 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Et link til fx /admin/claude-login (fra en ntfy) i en browser uden portal-session gav "403 Kun for administratorer". Tre af dem på /admin fik CrowdSec til at bandlyse IP'en som admin-probing, midt i et login. Sidevisninger uden session sendes nu til /auth/login?next= og efter login tilbage dertil; next accepteres kun som en sti på denne side. Fetch- og POST-kald uden session får 401. Logget ind uden admin er stadig 403. claude-login skriver nu brugernavnet i request-filen. Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG --- main.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 045ddc4..cf4b351 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,7 @@ from fastapi.templating import Jinja2Templates from starlette.middleware.sessions import SessionMiddleware import time +import urllib.parse import authentik as ak import cache @@ -56,14 +57,29 @@ def is_admin(user) -> bool: def require_admin(request: Request): user = current_user(request) + if not user and request.method == "GET" and "text/html" in request.headers.get("accept", ""): + # Ikke logget ind: login og tilbage hertil (fx et link fra en ntfy), + # i stedet for en 403-blindgyde. + nxt = request.url.path + (f"?{request.url.query}" if request.url.query else "") + raise HTTPException(303, headers={"Location": "/auth/login?next=" + urllib.parse.quote(nxt, safe="")}) + if not user: + raise HTTPException(401, "Log ind først") if not is_admin(user): raise HTTPException(403, "Kun for administratorer") return user +def _sikker_next(nxt: str | None) -> str: + # Kun stier på denne side; "//host" og "/\\host" ville sende videre ud af huset. + if nxt and nxt.startswith("/") and not nxt.startswith(("//", "/\\")): + return nxt + return "/" + + # ---------------- auth ---------------- @app.get("/auth/login") -async def login(request: Request): +async def login(request: Request, next: str = ""): + request.session["next"] = _sikker_next(next) return await oauth.authentik.authorize_redirect(request, config.REDIRECT_URI) @@ -78,7 +94,7 @@ async def callback(request: Request): "name": info.get("name") or info.get("preferred_username"), "username": info.get("preferred_username"), "email": info.get("email"), "groups": info.get("groups") or []} - return RedirectResponse("/", status_code=302) + return RedirectResponse(_sikker_next(request.session.pop("next", "/")), status_code=302) @app.get("/auth/logout") @@ -370,7 +386,7 @@ async def admin_claude_login_status(request: Request): @app.post("/admin/claude-login/start") async def admin_claude_login_start(request: Request): user = require_admin(request) - claudelogin.request((user or {}).get("preferred_username") or "?") + claudelogin.request((user or {}).get("username") or "?") return JSONResponse(claudelogin.state())