59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
from typing import Optional
|
||
|
||
try: # Python 3.9+
|
||
from typing import Annotated # type: ignore[attr-defined]
|
||
except ImportError: # Python 3.8 fallback
|
||
from typing_extensions import Annotated
|
||
|
||
import httpx
|
||
from fastapi import Depends, Header, HTTPException, status
|
||
|
||
from app.config import settings
|
||
|
||
|
||
@dataclass
|
||
class AuthContext:
|
||
user_id: str
|
||
access_token: str
|
||
|
||
|
||
async def get_current_user(
|
||
authorization: Annotated[Optional[str], Header(convert_underscores=False)] = None,
|
||
) -> AuthContext:
|
||
"""
|
||
验证 Supabase JWT,stage0 直接依赖 service_role 解析 token。
|
||
生产环境应通过 API Gateway 注入 user。
|
||
"""
|
||
if not authorization or not authorization.startswith("Bearer "):
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing bearer token")
|
||
|
||
token = authorization.replace("Bearer ", "", 1).strip()
|
||
if not token:
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Empty token")
|
||
|
||
auth_url = f"{settings.supabase_url.rstrip('/')}/auth/v1/user"
|
||
headers = {
|
||
"Authorization": f"Bearer {token}",
|
||
"apikey": settings.supabase_service_role_key,
|
||
}
|
||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||
try:
|
||
response = await client.get(auth_url, headers=headers)
|
||
response.raise_for_status()
|
||
except httpx.HTTPError as exc: # pragma: no cover - 网络异常
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Supabase token") from exc
|
||
|
||
data = response.json()
|
||
user_id = data.get("id") or data.get("user", {}).get("id")
|
||
if not user_id:
|
||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Supabase token")
|
||
|
||
return AuthContext(user_id=str(user_id), access_token=token)
|
||
|
||
|
||
AuthDep = Annotated[AuthContext, Depends(get_current_user)]
|