{"record":{"id":"4cd3bb8f496f741f","repo":"halo-dev/halo","slug":"problemdetail-user-email-verify-maxattempts","errorCode":"problemDetail.user.email.verify.maxAttempts","errorMessage":"Too many attempts. Please try again later.","messagePattern":"Too many attempts\\. Please try again later\\.","errorType":"http","errorClass":"EmailVerificationFailed","httpStatus":400,"severity":"error","filePath":"application/src/main/java/run/halo/app/core/user/service/impl/EmailVerificationServiceImpl.java","lineNumber":214,"sourceCode":"                .expireAfterWrite(CODE_EXPIRATION_MINUTES, TimeUnit.MINUTES)\n                .maximumSize(10000)\n                .build();\n\n        private final Cache<UsernameEmail, Boolean> blackListCache = CacheBuilder.newBuilder()\n                .expireAfterWrite(Duration.ofHours(1))\n                .maximumSize(1000)\n                .build();\n\n        public boolean verifyCode(String username, String email, String code) {\n            var key = new UsernameEmail(username, email);\n            var verification = emailVerificationCodeCache.getIfPresent(key);\n            if (verification == null) {\n                // expired or not generated\n                return false;\n            }\n            if (blackListCache.getIfPresent(key) != null) {\n                // in blacklist\n                throw new EmailVerificationFailed(\n                        \"Too many attempts. Please try again later.\",\n                        null,\n                        \"problemDetail.user.email.verify.maxAttempts\",\n                        null);\n            }\n            synchronized (verification) {\n                if (verification.getAttempts().get() >= MAX_ATTEMPTS) {\n                    // add to blacklist to prevent brute force attack\n                    blackListCache.put(key, true);\n                    return false;\n                }\n                if (!verification.getCode().equals(code)) {\n                    verification.getAttempts().incrementAndGet();\n                    return false;\n                }\n            }\n            return true;\n        }","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/core/user/service/impl/EmailVerificationServiceImpl.java#L196-L232","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: retry wrong code many times -> locked for 1h\n// after:  wait out the window, request a new code, enter it carefully","handlingStrategy":"try-catch","validationCode":"// track local failed-attempt count; stop before hitting the lockout\nif (localAttempts >= MAX_ATTEMPTS) {\n    showUserError(\"Too many attempts. Request a new code later.\");\n    return;\n}","typeGuard":null,"tryCatchPattern":"// handle the lockout code without brute-forcing further\ntry {\n    verifyApi.verify(username, email, code);\n} catch (EmailVerificationFailed e) {\n    if (\"problemDetail.user.email.verify.maxAttempts\".equals(e.getCode())) {\n        scheduleRetryAfter(Duration.ofHours(1));\n    } else throw e;\n}","preventionTips":["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."],"tags":["email","verification","rate-limit","brute-force","i18n"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}