Files
speech/app/core/auth.py
du5t 6b9a9cce8f Add Authentik OIDC login
Same Authlib-based pattern as v1: SessionMiddleware + core/auth.py
(login/callback/logout + require_login dependency), gating "/",
/asr/* and /tts/* behind Authentik (application slug "asr-v2",
provider pk 14). The websocket route can't use a FastAPI dependency
(no Request in its scope) so it's split into its own router and
checks websocket.session manually before accept().

v2 has no public domain yet, so the redirect_uri points at the
internal 172.30.1.41:18101 address over plain http — hence
https_only=False on the session cookie. Client id/secret and the
session secret live in /srv/asr/env/asr.env under ASR_V2_-prefixed
names (that env file is shared with v1, which already owns the
unprefixed OIDC_* names).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:44:20 +09:00

67 lines
2.0 KiB
Python

from __future__ import annotations
from typing import Any, Dict
from authlib.integrations.starlette_client import OAuth
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import RedirectResponse
from core.config import env_str
OIDC_ISSUER = env_str("ASR_V2_OIDC_ISSUER", "")
OIDC_CLIENT_ID = env_str("ASR_V2_OIDC_CLIENT_ID", "")
OIDC_CLIENT_SECRET = env_str("ASR_V2_OIDC_CLIENT_SECRET", "")
OIDC_REDIRECT_URI = env_str("ASR_V2_OIDC_REDIRECT_URI", "")
SESSION_SECRET_KEY = env_str("ASR_V2_SESSION_SECRET_KEY", "")
oauth = OAuth()
oauth.register(
name="authentik",
client_id=OIDC_CLIENT_ID,
client_secret=OIDC_CLIENT_SECRET,
server_metadata_url=f"{OIDC_ISSUER}.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"},
)
router = APIRouter()
@router.get("/login")
async def login(request: Request, next: str = "/"):
request.session["next"] = next if next.startswith("/") else "/"
return await oauth.authentik.authorize_redirect(request, OIDC_REDIRECT_URI)
@router.get("/callback")
async def callback(request: Request):
try:
token = await oauth.authentik.authorize_access_token(request)
except Exception as exc:
raise HTTPException(status_code=401, detail=f"로그인 실패: {exc}") from exc
userinfo = token.get("userinfo") or {}
request.session["user"] = {
"sub": userinfo.get("sub"),
"username": userinfo.get("preferred_username") or userinfo.get("nickname"),
"email": userinfo.get("email"),
"name": userinfo.get("name"),
}
next_path = request.session.pop("next", "/") or "/"
return RedirectResponse(url=next_path)
@router.get("/logout")
async def logout(request: Request):
request.session.pop("user", None)
return RedirectResponse(url="/")
def require_login(request: Request) -> Dict[str, Any]:
user = request.session.get("user")
if not user:
raise HTTPException(
status_code=303,
headers={"Location": f"/auth/login?next={request.url.path}"},
)
return user