langgenius/dify · warning · EmailPasswordLoginLimitError

email_code_login_limit

email_code_login_limit

Error message

Too many incorrect password attempts. Please try again later.

What it means

Raised by POST /console/api/login (HTTP 429, code email_code_login_limit) when AccountService.is_login_error_rate_limit(email) returns true. The counter login_error_rate_limit:<email> in Redis is incremented on each AccountPasswordError; once it exceeds LOGIN_MAX_ERROR_LIMITS (5), login is blocked for LOGIN_LOCKOUT_DURATION seconds.

Source

Thrown at api/controllers/console/auth/login.py:134

    @console_ns.expect(console_ns.models[LoginPayload.__name__])
    @console_ns.response(200, "Success", console_ns.models[SimpleResultOptionalDataResponse.__name__])
    @decrypt_password_field
    @model_validate(LoginPayload)
    def post(self, req_data: LoginPayload):
        """Authenticate user and login."""
        request_email = req_data.email
        normalized_email = request_email.lower()

        if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.CLOUD and BillingService.is_email_in_freeze(
            normalized_email
        ):
            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.ACCOUNT_IN_FREEZE)
            raise AccountInFreezeError()

        is_login_error_rate_limit = AccountService.is_login_error_rate_limit(normalized_email)
        if is_login_error_rate_limit:
            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.LOGIN_RATE_LIMITED)
            raise EmailPasswordLoginLimitError()

        invite_token = req_data.invite_token
        invitation_data: InvitationDetailDict | None = None
        if invite_token:
            invitation_data = RegisterService.get_invitation_with_case_fallback(
                None, request_email, invite_token, session=db.session()
            )
            if invitation_data is None:
                invite_token = None

        try:
            if invitation_data:
                data = invitation_data.get("data", {})
                invitee_email = data.get("email") if data else None
                invitee_email_normalized = invitee_email.lower() if isinstance(invitee_email, str) else invitee_email
                if invitee_email_normalized != normalized_email:
                    _log_console_login_failure(
                        email=normalized_email,

View on GitHub (pinned to ef8544b173)

Solutions

  1. Wait for LOGIN_LOCKOUT_DURATION seconds (default defined in configs/feature) for the Redis key to expire, then retry.
  2. Use the forgot-password flow to reset the password instead of continuing to guess.
  3. An operator can clear the lock immediately by deleting the Redis key: DEL login_error_rate_limit:<email>.
  4. Verify the user is submitting the correct (decrypted) password and that the @decrypt_password_field decorator is not double-encrypting on the client.

Example fix

# before
is_login_error_rate_limit = AccountService.is_login_error_rate_limit(normalized_email)
if is_login_error_rate_limit:
    raise EmailPasswordLoginLimitError()
# after - surface remaining lockout time to the client
if AccountService.is_login_error_rate_limit(normalized_email):
    ttl = redis_client.ttl(f'login_error_rate_limit:{normalized_email}')
    raise EmailPasswordLoginLimitError(description=f'Try again in {ttl} seconds')
Defensive patterns

Strategy: retry

Validate before calling

# Client-side: enforce a local attempt counter before hitting the server limit
import time
 attempts = get_local_attempts(email)
if attempts >= 5:
    wait = LOGIN_LOCKOUT_DURATION  # mirror server config
    show_message(f'Too many attempts. Try again in {wait} seconds.')
    return
submit_login(email, password)

Type guard

null

Try / catch

from controllers.console.auth.error import EmailPasswordLoginLimitError
try:
    do_login()
except EmailPasswordLoginLimitError:
    schedule_retry(after_seconds=LOGIN_LOCKOUT_DURATION)
    offer_password_reset()

Prevention

When it happens

Trigger: POST /console/api/login after more than 5 consecutive failed password attempts for the same normalized email within the LOGIN_LOCKOUT_DURATION window. The Redis key login_error_rate_limit:<email> holds a count > 5.

Common situations: User forgot their password and repeatedly tried wrong credentials; credential-stuffing or brute-force attempts triggering the lockout; shared email used by an automated client with stale credentials; Redis TTL misconfigured (LOGIN_LOCKOUT_DURATION too long) keeping the lock active.

Related errors


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