diff --git a/app/main.py b/app/main.py index 320bf53..c949ebd 100644 --- a/app/main.py +++ b/app/main.py @@ -291,16 +291,58 @@ async def auth_me(user=Depends(get_current_user)): return {"authenticated": False} +@app.get("/api/auth/callback") +async def auth_callback(request: Request, code: str = "", state: str = ""): + """OIDC Authorization Code → Access Token Exchange. + + Keycloak redirects hierher nach Login mit ?code=... Parameter. + Wir tauschen den Code gegen ein Access Token und setzen es als Cookie. + """ + if not _is_auth_enabled() or not code: + from fastapi.responses import RedirectResponse + return RedirectResponse("/") + + from .auth import _keycloak_issuer + token_url = f"{_keycloak_issuer()}/protocol/openid-connect/token" + + # Construct the same redirect_uri used for the auth request + base = str(request.base_url).rstrip("/").replace("http://", "https://") + redirect_uri = f"{base}/api/auth/callback" + + async with httpx.AsyncClient(timeout=10) as client: + resp = await client.post(token_url, data={ + "grant_type": "authorization_code", + "client_id": settings.keycloak_client_id, + "code": code, + "redirect_uri": redirect_uri, + }) + + if resp.status_code != 200: + logger.error("Token exchange failed: %s %s", resp.status_code, resp.text[:200]) + raise HTTPException(status_code=401, detail="Login fehlgeschlagen") + + tokens = resp.json() + access_token = tokens.get("access_token", "") + + from fastapi.responses import RedirectResponse + response = RedirectResponse("/") + response.set_cookie( + "access_token", access_token, + httponly=True, secure=True, samesite="lax", + max_age=tokens.get("expires_in", 3600), + ) + return response + + @app.get("/api/auth/login-url") async def auth_login_url(request: Request, redirect: str = "/"): """Keycloak-Login-URL für den Browser-Redirect.""" if not _is_auth_enabled(): return {"enabled": False, "url": ""} - # Construct absolute redirect URI. Traefik terminiert TLS, deshalb - # sieht FastAPI http:// als base_url. Erzwinge https:// damit die - # redirect_uri zum Keycloak-Client-Config passt. - base = str(request.base_url).rstrip("/").replace("http://", "https://" ) - url = keycloak_login_url(f"{base}{redirect}") + # redirect_uri muss auf den Callback-Endpoint zeigen, nicht auf die + # Zielseite — der Callback tauscht den Code gegen ein Token. + base = str(request.base_url).rstrip("/").replace("http://", "https://") + url = keycloak_login_url(f"{base}/api/auth/callback") return {"enabled": True, "url": url}