admin_auth.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """后台管理员登录和当前用户接口。"""
  2. from typing import Annotated, Any
  3. from fastapi import APIRouter, Depends, Request, Response
  4. from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
  5. from pydantic import BaseModel, Field
  6. from app.api.responses import success_response
  7. from app.api.v1.h5_auth import RefreshRequest
  8. from app.core.config import Settings
  9. from app.core.errors import AppError
  10. from app.domains.identity.models import AdminUser
  11. from app.domains.identity.service import AdminAuthService
  12. admin_bearer_scheme = HTTPBearer(auto_error=False)
  13. ADMIN_REFRESH_COOKIE = "zbt_admin_refresh"
  14. ADMIN_REFRESH_COOKIE_PATH = "/api/v1/admin/auth"
  15. class AdminLoginRequest(BaseModel):
  16. """管理员登录请求体;Field 同时生成接口文档并执行长度校验。"""
  17. username: str = Field(min_length=3, max_length=64)
  18. password: str = Field(min_length=8, max_length=128)
  19. def create_admin_router(auth_service: AdminAuthService, settings: Settings) -> APIRouter:
  20. """创建后台认证路由。"""
  21. router = APIRouter(tags=["admin-auth"])
  22. def current_admin(
  23. credentials: Annotated[
  24. HTTPAuthorizationCredentials | None,
  25. Depends(admin_bearer_scheme),
  26. ],
  27. ) -> AdminUser:
  28. # 该依赖校验 Bearer Token,成功后把管理员对象传给具体接口。
  29. if credentials is None:
  30. raise AppError("AUTH_REQUIRED", "请先登录后台", 401)
  31. return auth_service.authenticate_access(credentials.credentials)
  32. def set_refresh_cookie(response: Response, refresh_token: str) -> None:
  33. response.set_cookie(
  34. key=ADMIN_REFRESH_COOKIE,
  35. value=refresh_token,
  36. max_age=settings.refresh_token_days * 24 * 60 * 60,
  37. httponly=True,
  38. secure=settings.app_env == "production",
  39. samesite="lax",
  40. path=ADMIN_REFRESH_COOKIE_PATH,
  41. )
  42. @router.post("/admin/auth/login")
  43. def login(
  44. payload: AdminLoginRequest,
  45. request: Request,
  46. response: Response,
  47. ) -> dict[str, Any]:
  48. result = auth_service.login(payload.username, payload.password)
  49. set_refresh_cookie(response, result["tokens"]["refresh_token"])
  50. return success_response(request, result)
  51. @router.post("/admin/auth/refresh")
  52. def refresh(
  53. request: Request,
  54. response: Response,
  55. payload: RefreshRequest | None = None,
  56. ) -> dict[str, Any]:
  57. refresh_token = (
  58. payload.refresh_token
  59. if payload is not None
  60. else request.cookies.get(ADMIN_REFRESH_COOKIE)
  61. )
  62. if refresh_token is None:
  63. raise AppError("AUTH_REQUIRED", "缺少刷新凭证", 401)
  64. result = auth_service.refresh(refresh_token)
  65. set_refresh_cookie(response, result["tokens"]["refresh_token"])
  66. return success_response(request, result)
  67. @router.post("/admin/auth/logout")
  68. def logout(
  69. request: Request,
  70. response: Response,
  71. user: Annotated[AdminUser, Depends(current_admin)],
  72. credentials: Annotated[
  73. HTTPAuthorizationCredentials,
  74. Depends(admin_bearer_scheme),
  75. ],
  76. ) -> dict[str, Any]:
  77. del user
  78. auth_service.logout(credentials.credentials)
  79. response.delete_cookie(
  80. key=ADMIN_REFRESH_COOKIE,
  81. path=ADMIN_REFRESH_COOKIE_PATH,
  82. samesite="lax",
  83. )
  84. return success_response(request, {"logged_out": True})
  85. @router.get("/admin/me")
  86. def me(
  87. request: Request,
  88. user: Annotated[AdminUser, Depends(current_admin)],
  89. ) -> dict[str, Any]:
  90. return success_response(
  91. request,
  92. {
  93. "id": user.id,
  94. "username": user.username,
  95. "display_name": user.display_name,
  96. "status": user.status,
  97. "roles": sorted(user.roles),
  98. "permissions": sorted(user.permissions),
  99. "data_scope": user.data_scope,
  100. },
  101. )
  102. return router