diff --git a/offline-backend-20260512/backend/app/gateway/auth_middleware.py b/offline-backend-20260512/backend/app/gateway/auth_middleware.py index e6c84b4d..9c039c50 100644 --- a/offline-backend-20260512/backend/app/gateway/auth_middleware.py +++ b/offline-backend-20260512/backend/app/gateway/auth_middleware.py @@ -9,6 +9,7 @@ owner filtering works automatically via the sentinel pattern. Fine-grained permission checks remain in authz.py decorators. """ +import re from collections.abc import Callable from fastapi import HTTPException, Request, Response @@ -64,6 +65,20 @@ def _is_public(path: str) -> bool: return any(path.startswith(prefix) for prefix in _PUBLIC_PATH_PREFIXES) +# 「发起问答」的 run 创建接口(会真正调用大模型): +# POST /api/threads/{id}/runs[/stream|/wait] +# POST /api/runs/{stream|wait} +# (含 nginx 未改写到的 /api/langgraph/... 前缀,双保险) +# 只匹配这些「提交问答」的 POST,不碰 GET 列表 / join / cancel / feedback 等, +# 这样登录白名单开启后,未放行用户(凭旧 token)无法再用接口问答,但普通浏览不受影响。 +_QA_RUN_RE = re.compile(r"^/api/(?:langgraph/)?(?:threads/[^/]+/)?runs(?:/stream|/wait)?$") + + +def _is_qa_run_request(method: str, path: str) -> bool: + """True for an authenticated「发起问答」run-creation request (see ``_QA_RUN_RE``).""" + return method.upper() == "POST" and bool(_QA_RUN_RE.match(path.rstrip("/"))) + + class AuthMiddleware(BaseHTTPMiddleware): """Strict auth gate: reject requests without a valid session. @@ -148,6 +163,18 @@ class AuthMiddleware(BaseHTTPMiddleware): # JWT-decode + DB-lookup pipeline a second time per request). request.state.user = user request.state.auth = AuthContext(user=user, permissions=_ALL_PERMISSIONS) + + # 登录白名单:开关开启时,未放行用户(非 admin)不仅被拦在登录外,连「接口 + # 问答」也封掉——已拿到 token 的未放行用户用旧会话直接调问答接口同样被拒。 + # 仅作用于「发起问答」的 run 接口;内部/渠道调用(internal_user)不受限。 + if internal_user is None and _is_qa_run_request(request.method, app_relative_path(request)): + from app.gateway.routers.auth import enforce_user_approved + + try: + enforce_user_approved(user) + except HTTPException as exc: + return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}) + token = set_current_user(user) try: return await call_next(request)