"""Admin 认证:账号密码登录 → admin JWT(独立 secret)。""" from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException from app.admin.deps import AdminDb, CurrentAdmin from app.admin.repositories import admin_user as admin_repo from app.admin.schemas.auth import AdminLoginRequest, AdminLoginResponse, AdminOut from app.admin.security import create_admin_token from app.core.ratelimit import rate_limit from app.core.security import verify_password logger = logging.getLogger("shagua.admin.auth") router = APIRouter(prefix="/admin/api/auth", tags=["admin-auth"]) @router.post( "/login", response_model=AdminLoginResponse, summary="管理员登录", dependencies=[Depends(rate_limit(10, 60, "admin-login"))], # 同 IP 每分钟≤10 次,防爆破 ) def login(req: AdminLoginRequest, db: AdminDb) -> AdminLoginResponse: admin = admin_repo.get_by_username(db, req.username) # 用户名不存在 / 密码错统一 401 同文案(防账号枚举)。 # disabled 账号单独 403"账号已禁用":admin 是内部少数已知账号、无枚举价值, # 明确提示比防枚举更有运维价值(与 App 端 auth.py 对 disabled 用户的 403 一致)。 if admin is None or not verify_password(req.password, admin.password_hash): raise HTTPException(status_code=401, detail="用户名或密码错误") if admin.status != "active": raise HTTPException(status_code=403, detail="账号已禁用") token, expires_in = create_admin_token(admin_id=admin.id, role=admin.role) admin_repo.update_last_login(db, admin) logger.info("admin login ok id=%d username=%s role=%s", admin.id, admin.username, admin.role) return AdminLoginResponse( access_token=token, expires_in=expires_in, admin=AdminOut.model_validate(admin), ) @router.get("/me", response_model=AdminOut, summary="当前管理员") def me(admin: CurrentAdmin) -> AdminOut: return AdminOut.model_validate(admin)