import time from fastapi import APIRouter, HTTPException, Request from fastapi.responses import RedirectResponse from sqlalchemy import select from app.db import SessionLocal from app.models import User from app.security.oidc import oauth from app.services import diagnostics from app.settings import settings router = APIRouter(prefix="/auth", tags=["auth"]) @router.get("/login") async def login(request: Request): # If OIDC isn't configured, the client was never registered and calling it # would raise an opaque 500. Send the operator to the setup check instead, # which spells out exactly which variables are missing. if not diagnostics.oidc_configured(): return RedirectResponse(url="/setup") redirect_uri = request.url_for("auth_callback") return await oauth.pocketid.authorize_redirect(request, redirect_uri) @router.get("/callback", name="auth_callback") async def auth_callback(request: Request): token = await oauth.pocketid.authorize_access_token(request) userinfo = token.get("userinfo") or await oauth.pocketid.userinfo(token=token) sub = userinfo["sub"] email = userinfo.get("email") name = userinfo.get("name") now = time.time() # Enforce the allowlist here, before creating any user row or session, so a # disallowed identity never gets persisted or a cookie (require_auth also # checks it on every request, but that's after the fact). if settings.allowed_email and email != settings.allowed_email: raise HTTPException(status_code=403, detail="This account is not authorized to use alembic.") with SessionLocal() as db: user = db.execute(select(User).where(User.oidc_sub == sub)).scalar_one_or_none() if user is None: user = User(oidc_sub=sub, email=email, name=name, last_login_at=now) db.add(user) else: user.email = email user.name = name user.last_login_at = now db.commit() request.session["user"] = {"sub": sub, "email": email, "name": name} return RedirectResponse(url="/") @router.post("/logout") async def logout(request: Request): request.session.clear() end_session_endpoint = None metadata = await oauth.pocketid.load_server_metadata() end_session_endpoint = metadata.get("end_session_endpoint") if end_session_endpoint: return RedirectResponse(url=end_session_endpoint) return RedirectResponse(url="/")