halo-dev/halo · error · EmailVerificationFailed

problemDetail.user.email.verify.maxAttempts

problemDetail.user.email.verify.maxAttempts

Error message

Too many attempts. Please try again later.

What it means

Thrown as EmailVerificationFailed (a ServerWebInputException, HTTP 400) with code 'problemDetail.user.email.verify.maxAttempts' by EmailVerificationServiceImpl.verifyCode when the (username,email) key is present in blackListCache. The blacklist is populated after repeated failed code attempts and expires after 1 hour, acting as a brute-force lockout on email verification.

Source

Thrown at application/src/main/java/run/halo/app/core/user/service/impl/EmailVerificationServiceImpl.java:214

                .expireAfterWrite(CODE_EXPIRATION_MINUTES, TimeUnit.MINUTES)
                .maximumSize(10000)
                .build();

        private final Cache<UsernameEmail, Boolean> blackListCache = CacheBuilder.newBuilder()
                .expireAfterWrite(Duration.ofHours(1))
                .maximumSize(1000)
                .build();

        public boolean verifyCode(String username, String email, String code) {
            var key = new UsernameEmail(username, email);
            var verification = emailVerificationCodeCache.getIfPresent(key);
            if (verification == null) {
                // expired or not generated
                return false;
            }
            if (blackListCache.getIfPresent(key) != null) {
                // in blacklist
                throw new EmailVerificationFailed(
                        "Too many attempts. Please try again later.",
                        null,
                        "problemDetail.user.email.verify.maxAttempts",
                        null);
            }
            synchronized (verification) {
                if (verification.getAttempts().get() >= MAX_ATTEMPTS) {
                    // add to blacklist to prevent brute force attack
                    blackListCache.put(key, true);
                    return false;
                }
                if (!verification.getCode().equals(code)) {
                    verification.getAttempts().incrementAndGet();
                    return false;
                }
            }
            return true;
        }

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Wait until the 1-hour blacklist window expires, then request and enter a fresh verification code.
  2. Request a new verification code via sendVerificationCode (which regenerates and resets attempts) once the blacklist clears.
  3. Ensure the code is delivered (check spam/SMTP) before retrying to avoid re-lockout.
  4. If urgent, an admin can restart the service to clear in-memory caches, though this resets all verification state.

Example fix

// before: retry wrong code many times -> locked for 1h
// after:  wait out the window, request a new code, enter it carefully
Defensive patterns

Strategy: try-catch

Validate before calling

// track local failed-attempt count; stop before hitting the lockout
if (localAttempts >= MAX_ATTEMPTS) {
    showUserError("Too many attempts. Request a new code later.");
    return;
}

Try / catch

// handle the lockout code without brute-forcing further
try {
    verifyApi.verify(username, email, code);
} catch (EmailVerificationFailed e) {
    if ("problemDetail.user.email.verify.maxAttempts".equals(e.getCode())) {
        scheduleRetryAfter(Duration.ofHours(1));
    } else throw e;
}

Prevention

When it happens

Trigger: POSTing an email-verification code check for a username/email pair that exhausted its MAX_ATTEMPTS failed tries (the synchronized block added the key to blackListCache). Any further verify attempts for that pair within the 1-hour window throw immediately.

Common situations: User mistyped the code repeatedly; legitimate user locked out after failed attempts; automated/scripted brute-force guessing; the code was never received (SMTP issue) so the user kept guessing wrong.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/4cd3bb8f496f741f. Report an issue: GitHub.