53 lines
1.8 KiB
Python
53 lines
1.8 KiB
Python
"""routers/auth.py —— 用户端鉴权端点(登录/登出/me/status)。"""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
|
|
from ..auth import require_user
|
|
from ..config import get_settings
|
|
from ..db import Database, get_db
|
|
from ..logwriter import log_event
|
|
from ..services.auth import verify_login, issue_token, revoke_token
|
|
from ..upstream import envelope
|
|
|
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
|
|
|
|
class LoginBody(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
|
|
@router.get("/status")
|
|
async def status():
|
|
"""免鉴权:告知前端是否开启鉴权(决定是否显示登录页)。"""
|
|
return {"ok": True, "auth_enabled": get_settings().auth_enabled}
|
|
|
|
|
|
@router.post("/login")
|
|
async def login(body: LoginBody, db: Database = Depends(get_db)):
|
|
user = await verify_login(db, body.username, body.password)
|
|
if user is None:
|
|
log_event("login_fail", detail={"username": body.username})
|
|
content, status = envelope("UNAUTHORIZED", "用户名或密码错误", status=401)
|
|
return JSONResponse(status_code=status, content=content)
|
|
token = await issue_token(db, user["id"], ttl_seconds=get_settings().user_token_ttl_seconds)
|
|
log_event("login_ok", user_id=user["id"], detail={"username": body.username})
|
|
return {"ok": True, "token": token, "user": user}
|
|
|
|
|
|
@router.post("/logout")
|
|
async def logout(request: Request, db: Database = Depends(get_db), _user: dict = Depends(require_user)):
|
|
header = request.headers.get("Authorization", "")
|
|
token = header[7:] if header.startswith("Bearer ") else ""
|
|
if token:
|
|
await revoke_token(db, token)
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/me")
|
|
async def me(_user: dict = Depends(require_user)):
|
|
return {"ok": True, "user": _user}
|