auth.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from __future__ import annotations
  2. from fastapi import APIRouter, HTTPException, Request, Response, status
  3. from api.schemas.auth import CreateAuthUserBody, LoginBody, UpdateAuthUserBody
  4. from api.services.auth import (
  5. SESSION_COOKIE_NAME,
  6. DuplicateUsernameError,
  7. InvalidCredentialsError,
  8. authenticate,
  9. create_user,
  10. delete_user,
  11. list_users,
  12. revoke_session,
  13. update_user,
  14. )
  15. from supply_infra.config import get_infra_settings
  16. router = APIRouter(prefix="/api", tags=["authentication"])
  17. @router.post("/auth/login")
  18. def login(body: LoginBody, request: Request, response: Response) -> dict:
  19. try:
  20. user, token = authenticate(
  21. username=body.username,
  22. password=body.password,
  23. ip_address=request.client.host if request.client else None,
  24. user_agent=request.headers.get("user-agent"),
  25. )
  26. except InvalidCredentialsError:
  27. raise HTTPException(
  28. status_code=status.HTTP_401_UNAUTHORIZED,
  29. detail="用户名或密码错误",
  30. ) from None
  31. settings = get_infra_settings()
  32. response.set_cookie(
  33. key=SESSION_COOKIE_NAME,
  34. value=token,
  35. max_age=settings.auth_session_hours * 60 * 60,
  36. httponly=True,
  37. secure=settings.auth_cookie_secure,
  38. samesite="lax",
  39. path="/",
  40. )
  41. return {"user": user}
  42. @router.get("/auth/me")
  43. def me(request: Request) -> dict:
  44. return {"user": request.state.current_user}
  45. @router.post("/auth/logout")
  46. def logout(request: Request, response: Response) -> dict[str, bool]:
  47. token = request.cookies.get(SESSION_COOKIE_NAME)
  48. if token:
  49. revoke_session(token)
  50. response.delete_cookie(
  51. key=SESSION_COOKIE_NAME,
  52. path="/",
  53. secure=get_infra_settings().auth_cookie_secure,
  54. httponly=True,
  55. samesite="lax",
  56. )
  57. return {"ok": True}
  58. @router.get("/admin/users")
  59. def admin_users() -> dict:
  60. return {"items": list_users()}
  61. @router.post("/admin/users", status_code=201)
  62. def admin_create_user(body: CreateAuthUserBody) -> dict:
  63. try:
  64. return create_user(
  65. username=body.username,
  66. password=body.password,
  67. display_name=body.display_name,
  68. role=body.role,
  69. )
  70. except DuplicateUsernameError:
  71. raise HTTPException(status_code=409, detail="用户名已存在") from None
  72. @router.patch("/admin/users/{user_id}")
  73. def admin_update_user(
  74. user_id: int,
  75. body: UpdateAuthUserBody,
  76. request: Request,
  77. ) -> dict:
  78. current_user = request.state.current_user
  79. if current_user["id"] == user_id and (
  80. (body.role is not None and body.role != current_user["role"])
  81. or body.status == "disabled"
  82. or body.password is not None
  83. ):
  84. raise HTTPException(status_code=409, detail="不能降级、禁用或重置当前登录账号")
  85. result = update_user(
  86. user_id,
  87. display_name=body.display_name,
  88. role=body.role,
  89. status=body.status,
  90. password=body.password,
  91. )
  92. if result is None:
  93. raise HTTPException(status_code=404, detail="用户不存在")
  94. return result
  95. @router.delete("/admin/users/{user_id}", status_code=204)
  96. def admin_delete_user(user_id: int, request: Request) -> Response:
  97. if request.state.current_user["id"] == user_id:
  98. raise HTTPException(status_code=409, detail="不能删除当前登录账号")
  99. if not delete_user(user_id):
  100. raise HTTPException(status_code=404, detail="用户不存在")
  101. return Response(status_code=204)