langgenius/dify · error · EmailCodeError

email_code_error

email_code_error

Error message

Email code is invalid or expired.

What it means

Raised by EmailCodeError in ForgotPasswordCheckApi.post when req_data.code differs from the code stored in the reset token. Each failure increments add_forgot_password_error_rate_limit and can lead to error 412. The code is the pin delivered in the reset email.

Solutions

  1. Trim whitespace and enter the code from the most recent reset email.
  2. Request a new reset email if uncertain, and use the new token+code pair together.
  3. Track attempts client-side and warn before the rate-limit threshold.
  4. Disable autofill on the code input to prevent stale-code substitution.

Example fix

// before
checkValidity({ email, token, code: rawInput });
// after
checkValidity({ email, token: latestToken, code: rawInput.trim() });
Defensive patterns

Strategy: validation

Validate before calling

const cleanCode = String(code).trim();
if (!/^\d{4,8}$/.test(cleanCode)) {
  warnInvalidCodeFormat();
  return;
}

Type guard

function looksLikeResetCode(c) { return typeof c === 'string' && /^\d{4,8}$/.test(c.trim()); }

Try / catch

try {
  await checkValidity({ email, token, code: cleanCode });
} catch (e) {
  if (e.code === 'email_code_error') bumpAttempts();
  else if (e.code === 'email_password_reset_limit') showCooldown();
  else throw e;
}

Prevention

When it happens

Trigger: POST /console/api/forgot-password/validity with a wrong, stale, or typo'd reset code while the token and email are valid.

Common situations: User mistypes the reset code; code from an older reset email is used after a resend; autofill inserts a saved but outdated code; whitespace or formatting in the pasted code.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/0358c85ba7c6f0f0. Report an issue: GitHub.

Appendix: source

Thrown at api/controllers/console/auth/forgot_password.py:132

        is_forgot_password_error_rate_limit = AccountService.is_forgot_password_error_rate_limit(user_email)
        if is_forgot_password_error_rate_limit:
            raise EmailPasswordResetLimitError()

        token_data = AccountService.get_reset_password_data(req_data.token)
        if token_data is None:
            raise InvalidTokenError()

        token_email = token_data.get("email")
        if not isinstance(token_email, str):
            raise InvalidEmailError()
        normalized_token_email = token_email.lower()

        if user_email != normalized_token_email:
            raise InvalidEmailError()

        if req_data.code != token_data.get("code"):
            AccountService.add_forgot_password_error_rate_limit(user_email)
            raise EmailCodeError()

        # Verified, revoke the first token
        AccountService.revoke_reset_password_token(req_data.token)

        # Refresh token data by generating a new token
        _, new_token = AccountService.generate_reset_password_token(
            token_email, code=req_data.code, additional_data={"phase": "reset"}
        )

        AccountService.reset_forgot_password_error_rate_limit(user_email)
        return {"is_valid": True, "email": normalized_token_email, "token": new_token}


@console_ns.route("/forgot-password/resets")
class ForgotPasswordResetApi(Resource):
    @console_ns.doc("reset_password")
    @console_ns.doc(description="Reset password with verification token")
    @console_ns.expect(console_ns.models[ForgotPasswordResetPayload.__name__])

View on GitHub (pinned to ef8544b173)