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.

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)

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.