Files
speech/app/main.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

62 lines
2.1 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any, Dict
from fastapi import Depends, FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from asr.config import ensure_runtime_dirs
from asr.router import router as asr_router
from asr.router import ws_router as asr_ws_router
from core.auth import SESSION_SECRET_KEY, require_login
from core.auth import router as auth_router
from tts.router import router as tts_router
app = FastAPI(title="ASR/TTS Gateway", docs_url="/docs", redoc_url="/redoc")
app.add_middleware(
SessionMiddleware,
secret_key=SESSION_SECRET_KEY,
same_site="lax",
https_only=False, # 아직 전용 도메인/TLS가 없어 내부 IP:포트(http)로 접근하기 때문
max_age=14 * 24 * 3600,
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
UI_DIR = Path("/app/ui")
app.include_router(auth_router, prefix="/auth", tags=["auth"])
app.include_router(asr_router, prefix="/asr", tags=["asr"], dependencies=[Depends(require_login)])
app.include_router(asr_ws_router, prefix="/asr", tags=["asr"]) # 인증은 핸들러 내부에서 세션으로 직접 체크
app.include_router(tts_router, prefix="/tts", tags=["tts"], dependencies=[Depends(require_login)])
@app.on_event("startup")
def startup() -> None:
ensure_runtime_dirs()
if UI_DIR.exists():
app.mount("/ui", StaticFiles(directory=str(UI_DIR), html=True), name="ui")
app.mount("/assets", StaticFiles(directory=str(UI_DIR / "assets")), name="assets")
@app.get("/health")
def health() -> Dict[str, Any]:
return {"status": "ok", "ui_installed": UI_DIR.exists(), "api": "/docs"}
@app.get("/", response_class=HTMLResponse, dependencies=[Depends(require_login)])
def root() -> Any:
index = UI_DIR / "index.html"
if index.exists():
return FileResponse(index)
return JSONResponse({"status": "ok", "message": "UI not installed", "api": "/docs"})