portal_auth.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. """门户跳转登录接口:验签/按需落库 + 首次设密。"""
  2. from __future__ import annotations
  3. from flask import request
  4. from dms.api.v1.blueprint import api_v1
  5. from dms.common.errors import InvalidArgumentError, LoginIpForbiddenError
  6. from dms.common.response import success_response
  7. from dms.services.audit_service import record_login_failure
  8. from dms.services.portal_auth_service import setup_portal_password, verify_portal_launch
  9. def _verify_payload() -> str:
  10. if not request.is_json:
  11. raise InvalidArgumentError("请求体必须是JSON对象")
  12. body = request.get_json(silent=True)
  13. if not isinstance(body, dict) or set(body) != {"query"}:
  14. raise InvalidArgumentError("请求体必须且只能包含query")
  15. query = body["query"]
  16. if not isinstance(query, str) or not query.strip():
  17. raise InvalidArgumentError("query不能为空")
  18. return query.strip()
  19. @api_v1.post("/auth/portal/verify")
  20. def portal_verify():
  21. """门户跳转验签:透传浏览器地址栏query串,返回跳转阶段与用户名。"""
  22. return success_response(verify_portal_launch(_verify_payload()))
  23. def _setup_payload() -> tuple[str, str, bool, str | None]:
  24. if not request.is_json:
  25. raise InvalidArgumentError("请求体必须是JSON对象")
  26. body = request.get_json(silent=True)
  27. if not isinstance(body, dict):
  28. raise InvalidArgumentError("请求体必须是JSON对象")
  29. required = {"portalTicket", "newPassword", "keepSignedIn"}
  30. allowed = required | {"organizationId"}
  31. if not required.issubset(set(body)) or set(body) - allowed:
  32. raise InvalidArgumentError(
  33. "请求体必须包含portalTicket、newPassword、keepSignedIn"
  34. + "(organizationId可选)"
  35. )
  36. ticket = body["portalTicket"]
  37. password = body["newPassword"]
  38. keep_signed_in = body["keepSignedIn"]
  39. organization_id = body.get("organizationId")
  40. if not isinstance(ticket, str) or not ticket.strip():
  41. raise InvalidArgumentError("portalTicket不能为空")
  42. if not isinstance(password, str) or not password:
  43. raise InvalidArgumentError("newPassword不能为空")
  44. if not isinstance(keep_signed_in, bool):
  45. raise InvalidArgumentError("keepSignedIn必须是布尔值")
  46. if organization_id is not None and (
  47. not isinstance(organization_id, str) or not organization_id.strip()
  48. ):
  49. raise InvalidArgumentError("organizationId必须是非空字符串")
  50. return (
  51. ticket.strip(),
  52. password,
  53. keep_signed_in,
  54. organization_id.strip() if organization_id is not None else None,
  55. )
  56. @api_v1.post("/auth/portal/setup-password")
  57. def portal_setup_password():
  58. """门户用户首次设置独立密码并登录(票据一次性,5分钟内有效)。"""
  59. ticket, password, keep_signed_in, organization_id = _setup_payload()
  60. try:
  61. payload = setup_portal_password(
  62. ticket, password, keep_signed_in, organization_id
  63. )
  64. except LoginIpForbiddenError:
  65. record_login_failure(
  66. user=None,
  67. attempted_username="(portal-setup)",
  68. failure_reason="IP_NOT_ALLOWED",
  69. )
  70. raise
  71. return success_response(payload)