{"record":{"id":"de56ee1c3a7074be","repo":"langgenius/dify","slug":"invalid-or-expired-token-de56ee","errorCode":"invalid_or_expired_token","errorMessage":"The token is invalid or has expired.","messagePattern":"The token is invalid or has expired\\.","errorType":"error_code","errorClass":"InvalidTokenError","httpStatus":400,"severity":"error","filePath":"api/controllers/console/auth/forgot_password.py","lineNumber":120,"sourceCode":"        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\n        AccountService.revoke_reset_password_token(req_data.token)\n\n        # Refresh token data by generating a new token\n        _, new_token = AccountService.generate_reset_password_token(","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/auth/forgot_password.py#L102-L138","documentation":"Raised by InvalidTokenError in ForgotPasswordCheckApi.post when AccountService.get_reset_password_data(req_data.token) returns None. The reset token is the handle embedded in the reset email link; None means it is unknown, expired, malformed, or already revoked. Symmetric to error 402 but for the password-reset flow.","triggerScenarios":"POST /console/api/forgot-password/validity with a token that does not resolve in the reset-token store — stale link, already-used token, truncated string, or fabricated value.","commonSituations":"User clicks an old reset link past its TTL; user already reset the password and clicks the same link again; email client truncated/wrapped the link; client lost the token and substituted garbage; cross-environment token (token issued by prod used against staging).","solutions":["Request a new reset email via /forgot-password/email-send and use the fresh token.","Ensure the full reset link is copied without truncation (watch for line-wrap in some email clients).","Confirm the client targets the same Dify instance that sent the email.","Verify reset-token TTL is configured long enough for email delivery + user read time."],"exampleFix":"// before\ncheckValidity({ email, token: oldLinkToken, code });\n// after\nif (res.code === 'invalid_or_expired_token') {\n  const s = await sendReset({ email });\n  // user clicks new link, then call checkValidity with the new token\n}","handlingStrategy":"try-catch","validationCode":"// Sanity check token shape before calling\nif (!token || token.split('.').length < 2) {\n  await restartResetFlow(email);\n  return;\n}","typeGuard":"function isPlausibleResetToken(t) { return typeof t === 'string' && t.length > 16 && t.includes('.'); }","tryCatchPattern":"try {\n  await checkValidity({ email, token, code });\n} catch (e) {\n  if (e.code === 'invalid_or_expired_token') await restartResetFlow(email);\n  else throw e;\n}","preventionTips":["Use the reset link promptly within the token TTL.","Copy the full reset link; watch for email-client line wrapping.","Target the same Dify instance that issued the token."],"tags":["auth","forgot-password","token","expired"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}