Spaces:
Running
Running
| """HF OAuth flow — anonymous-first, recognition-only. | |
| When OAUTH_CLIENT_ID is not set (local / unconfigured), all routes degrade | |
| gracefully: /login returns a friendly unavailable response, /me returns | |
| signed_in:false, /logout still clears the session and redirects. | |
| Privacy contract: ONLY hash_uid(sub) is stored in the session. | |
| Never the username, preferred_username, name, or raw sub. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from authlib.integrations.starlette_client import OAuth | |
| from fastapi import APIRouter, Request | |
| from fastapi.responses import JSONResponse, RedirectResponse | |
| from app.identity import hash_uid | |
| def make_oauth() -> OAuth | None: | |
| cid = os.environ.get("OAUTH_CLIENT_ID") | |
| if not cid: | |
| return None | |
| oauth = OAuth() | |
| oauth.register( | |
| name="hf", | |
| client_id=cid, | |
| client_secret=os.environ.get("OAUTH_CLIENT_SECRET"), | |
| server_metadata_url=( | |
| f"{os.environ.get('OPENID_PROVIDER_URL', 'https://huggingface.co')}" | |
| "/.well-known/openid-configuration" | |
| ), | |
| client_kwargs={"scope": "openid profile"}, | |
| ) | |
| return oauth | |
| # Module-level oauth instance; tests may monkeypatch this. | |
| _oauth: OAuth | None = make_oauth() | |
| def make_auth_router() -> APIRouter: | |
| router = APIRouter() | |
| async def login(request: Request): | |
| if _oauth is None: | |
| return JSONResponse( | |
| {"error": "login_unavailable", "detail": "OAuth not configured"}, | |
| status_code=503, | |
| ) | |
| redirect_uri = request.url_for("callback") | |
| return await _oauth.hf.authorize_redirect(request, redirect_uri) | |
| async def callback(request: Request): | |
| if _oauth is None: | |
| return RedirectResponse("/") | |
| token = await _oauth.hf.authorize_access_token(request) | |
| # Prefer userinfo claim embedded in token; fall back to id_token sub. | |
| userinfo = token.get("userinfo") or {} | |
| sub = userinfo.get("sub") or token.get("sub", "") | |
| if sub: | |
| request.session["uid"] = hash_uid(sub) | |
| request.session["auth"] = True # real OAuth sign-in (vs anonymous cookie uid) | |
| return RedirectResponse("/") | |
| async def logout(request: Request): | |
| request.session.clear() | |
| return RedirectResponse("/") | |
| async def me(request: Request): | |
| # anonymous cookie uid is NOT "signed in"; only a real OAuth login sets auth | |
| return {"signed_in": bool(request.session.get("auth"))} | |
| return router | |