{"record":{"id":"583e551f83e8e2a6","repo":"langgenius/dify","slug":"email-password-reset-limit","errorCode":"email_password_reset_limit","errorMessage":"Too many failed password reset attempts. Please try again in 24 hours.","messagePattern":"Too many failed password reset attempts\\. Please try again in 24 hours\\.","errorType":"error_code","errorClass":"EmailPasswordResetLimitError","httpStatus":429,"severity":"warning","filePath":"api/controllers/console/auth/forgot_password.py","lineNumber":116,"sourceCode":"    @console_ns.doc(\"check_forgot_password_code\")\n    @console_ns.doc(description=\"Verify password reset code\")\n    @console_ns.expect(console_ns.models[ForgotPasswordCheckPayload.__name__])\n    @console_ns.response(\n        200,\n        \"Code verified successfully\",\n        console_ns.models[ForgotPasswordCheckResponse.__name__],\n    )\n    @console_ns.response(400, \"Invalid code or token\")\n    @setup_required\n    @email_password_login_enabled\n    @model_validate(ForgotPasswordCheckPayload)\n    def post(self, req_data: ForgotPasswordCheckPayload):\n\n        user_email = req_data.email.lower()\n\n        is_forgot_password_error_rate_limit = AccountService.is_forgot_password_error_rate_limit(user_email)\n        if is_forgot_password_error_rate_limit:\n            raise EmailPasswordResetLimitError()\n\n        token_data = AccountService.get_reset_password_data(req_data.token)\n        if token_data is None:\n            raise InvalidTokenError()\n\n        token_email = token_data.get(\"email\")\n        if not isinstance(token_email, str):\n            raise InvalidEmailError()\n        normalized_token_email = token_email.lower()\n\n        if user_email != normalized_token_email:\n            raise InvalidEmailError()\n\n        if req_data.code != token_data.get(\"code\"):\n            AccountService.add_forgot_password_error_rate_limit(user_email)\n            raise EmailCodeError()\n\n        # Verified, revoke the first token","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/auth/forgot_password.py#L98-L134","documentation":"Raised by EmailPasswordResetLimitError in ForgotPasswordCheckApi.post when AccountService.is_forgot_password_error_rate_limit(user_email) returns true. Like the register counterpart, this accumulates failed code-verifications per email and locks the email out of the reset flow for 24 hours once the threshold is crossed.","triggerScenarios":"POST /console/api/forgot-password/validity with an email that has too many prior failed reset-code verifications. Evaluated before the token is looked up.","commonSituations":"User repeatedly enters wrong reset codes; brute-force attempt on the reset code; tests hammering the validity endpoint with a fixed email; a previous session's failed attempts still counting against the email.","solutions":["Wait 24 hours for the per-email lockout to expire, or have a privileged flow call reset_forgot_password_error_rate_limit(email).","Request a fresh reset email and enter the code carefully on the first try.","In tests, mock is_forgot_password_error_rate_limit to false or use unique emails per case.","Surface 'email_password_reset_limit' as a cooldown message in the UI."],"exampleFix":"// before\nconst res = await checkValidity({ email, token, code });\n// after\nif (res.code === 'email_password_reset_limit') showResetCooldown();","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await checkValidity({ email, token, code });\n} catch (e) {\n  if (e.code === 'email_password_reset_limit') showResetCooldown(24 * 3600);\n  else if (e.code === 'email_code_error') bumpAttempts();\n  else throw e;\n}","preventionTips":["Show an attempt counter for the reset code entry.","Throttle retries client-side after each failure.","Mock is_forgot_password_error_rate_limit in tests."],"tags":["auth","forgot-password","rate-limit","brute-force-protection"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}