forgot_password.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import base64
  2. import secrets
  3. from flask import request
  4. from flask_restful import Resource, reqparse # type: ignore
  5. from sqlalchemy import select
  6. from sqlalchemy.orm import Session
  7. from constants.languages import languages
  8. from controllers.console import api
  9. from controllers.console.auth.error import EmailCodeError, InvalidEmailError, InvalidTokenError, PasswordMismatchError
  10. from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError
  11. from controllers.console.wraps import setup_required
  12. from events.tenant_event import tenant_was_created
  13. from extensions.ext_database import db
  14. from libs.helper import email, extract_remote_ip
  15. from libs.password import hash_password, valid_password
  16. from models.account import Account
  17. from services.account_service import AccountService, TenantService
  18. from services.errors.account import AccountRegisterError
  19. from services.errors.workspace import WorkSpaceNotAllowedCreateError
  20. from services.feature_service import FeatureService
  21. class ForgotPasswordSendEmailApi(Resource):
  22. @setup_required
  23. def post(self):
  24. parser = reqparse.RequestParser()
  25. parser.add_argument("email", type=email, required=True, location="json")
  26. parser.add_argument("language", type=str, required=False, location="json")
  27. args = parser.parse_args()
  28. ip_address = extract_remote_ip(request)
  29. if AccountService.is_email_send_ip_limit(ip_address):
  30. raise EmailSendIpLimitError()
  31. if args["language"] is not None and args["language"] == "zh-Hans":
  32. language = "zh-Hans"
  33. else:
  34. language = "en-US"
  35. with Session(db.engine) as session:
  36. account = session.execute(select(Account).filter_by(email=args["email"])).scalar_one_or_none()
  37. token = None
  38. if account is None:
  39. if FeatureService.get_system_features().is_allow_register:
  40. token = AccountService.send_reset_password_email(email=args["email"], language=language)
  41. return {"result": "fail", "data": token, "code": "account_not_found"}
  42. else:
  43. raise AccountNotFound()
  44. else:
  45. token = AccountService.send_reset_password_email(account=account, email=args["email"], language=language)
  46. return {"result": "success", "data": token}
  47. class ForgotPasswordCheckApi(Resource):
  48. @setup_required
  49. def post(self):
  50. parser = reqparse.RequestParser()
  51. parser.add_argument("email", type=str, required=True, location="json")
  52. parser.add_argument("code", type=str, required=True, location="json")
  53. parser.add_argument("token", type=str, required=True, nullable=False, location="json")
  54. args = parser.parse_args()
  55. user_email = args["email"]
  56. token_data = AccountService.get_reset_password_data(args["token"])
  57. if token_data is None:
  58. raise InvalidTokenError()
  59. if user_email != token_data.get("email"):
  60. raise InvalidEmailError()
  61. if args["code"] != token_data.get("code"):
  62. raise EmailCodeError()
  63. return {"is_valid": True, "email": token_data.get("email")}
  64. class ForgotPasswordResetApi(Resource):
  65. @setup_required
  66. def post(self):
  67. parser = reqparse.RequestParser()
  68. parser.add_argument("token", type=str, required=True, nullable=False, location="json")
  69. parser.add_argument("new_password", type=valid_password, required=True, nullable=False, location="json")
  70. parser.add_argument("password_confirm", type=valid_password, required=True, nullable=False, location="json")
  71. args = parser.parse_args()
  72. new_password = args["new_password"]
  73. password_confirm = args["password_confirm"]
  74. if str(new_password).strip() != str(password_confirm).strip():
  75. raise PasswordMismatchError()
  76. token = args["token"]
  77. reset_data = AccountService.get_reset_password_data(token)
  78. if reset_data is None:
  79. raise InvalidTokenError()
  80. AccountService.revoke_reset_password_token(token)
  81. salt = secrets.token_bytes(16)
  82. base64_salt = base64.b64encode(salt).decode()
  83. password_hashed = hash_password(new_password, salt)
  84. base64_password_hashed = base64.b64encode(password_hashed).decode()
  85. with Session(db.engine) as session:
  86. account = session.execute(select(Account).filter_by(email=reset_data.get("email"))).scalar_one_or_none()
  87. if account:
  88. account.password = base64_password_hashed
  89. account.password_salt = base64_salt
  90. db.session.commit()
  91. tenant = TenantService.get_join_tenants(account)
  92. if not tenant and not FeatureService.get_system_features().is_allow_create_workspace:
  93. tenant = TenantService.create_tenant(f"{account.name}'s Workspace")
  94. TenantService.create_tenant_member(tenant, account, role="owner")
  95. account.current_tenant = tenant
  96. tenant_was_created.send(tenant)
  97. else:
  98. try:
  99. account = AccountService.create_account_and_tenant(
  100. email=reset_data.get("email", ""),
  101. name=reset_data.get("email", ""),
  102. password=password_confirm,
  103. interface_language=languages[0],
  104. )
  105. except WorkSpaceNotAllowedCreateError:
  106. pass
  107. except AccountRegisterError:
  108. raise AccountInFreezeError()
  109. return {"result": "success"}
  110. api.add_resource(ForgotPasswordSendEmailApi, "/forgot-password")
  111. api.add_resource(ForgotPasswordCheckApi, "/forgot-password/validity")
  112. api.add_resource(ForgotPasswordResetApi, "/forgot-password/resets")