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
- Wait until the 1-hour blacklist window expires, then request and enter a fresh verification code.
- Request a new verification code via sendVerificationCode (which regenerates and resets attempts) once the blacklist clears.
- Ensure the code is delivered (check spam/SMTP) before retrying to avoid re-lockout.
- 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
- Request a fresh code via sendVerificationCode once the 1-hour window clears rather than retrying the old one.
- Ensure code delivery (check spam/SMTP) before retrying to avoid re-lockout.
- Cap client-side retries below MAX_ATTEMPTS.
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
- validation.error.email.pattern
- Email is required
- validation.error.password.size
- problemDetail.comment.turnedOff
- problemDetail.comment.systemUsersOnly
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/4cd3bb8f496f741f.
Report an issue: GitHub.