{"record":{"id":"7f7d8f16a0c52bd8","repo":"apereo/cas","slug":"cannot-be-found-in-the-registry","errorCode":null,"errorMessage":" cannot be found in the registry","messagePattern":" cannot be found in the registry","errorType":"exception","errorClass":"AccountNotFoundException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/credential/GoogleAuthenticatorOneTimeTokenCredentialValidator.java","lineNumber":57,"sourceCode":"        return credential.getAccountId() == null || credential.getAccountId() == account.getId();\n    }\n\n    @Override\n    public @Nullable GoogleAuthenticatorToken validate(final Authentication authentication,\n                                                       final GoogleAuthenticatorTokenCredential tokenCredential) throws Throwable {\n\n        if (!StringUtils.isNumeric(tokenCredential.getToken())) {\n            throw new PreventedException(\"Invalid non-numeric OTP format specified.\");\n        }\n\n        val uid = authentication.getPrincipal().getId();\n        val otp = Integer.parseInt(tokenCredential.getToken());\n        LOGGER.trace(\"Received OTP [{}] assigned to account [{}]\", otp, tokenCredential.getAccountId());\n\n        LOGGER.trace(\"Received principal id [{}]. Attempting to locate account in credential repository...\", uid);\n        val accounts = credentialRepository.get(uid);\n        if (accounts == null || accounts.isEmpty()) {\n            throw new AccountNotFoundException(uid + \" cannot be found in the registry\");\n        }\n\n        if (accounts.size() > 1 && tokenCredential.getAccountId() == null) {\n            throw new PreventedException(\"Account identifier must be specified if multiple accounts are registered for \" + uid);\n        }\n        LOGGER.trace(\"Attempting to locate OTP token [{}] in token repository for [{}]...\", otp, uid);\n        if (tokenRepository.exists(uid, otp)) {\n            throw new AccountExpiredException(uid + \" cannot reuse OTP \" + otp + \" as it may be expired/invalid\");\n        }\n\n        LOGGER.debug(\"Attempting to authorize OTP token [{}]...\", otp);\n        val result = getAuthorizedAccountForToken(tokenCredential, accounts)\n            .or(() -> getAuthorizedScratchCodeForToken(tokenCredential, authentication, accounts));\n        return result\n            .map(acct -> new GoogleAuthenticatorToken(otp, uid))\n            .orElse(null);\n    }\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-gauth-core/src/main/java/org/apereo/cas/gauth/credential/GoogleAuthenticatorOneTimeTokenCredentialValidator.java#L39-L75","documentation":"During OTP validation, GoogleAuthenticatorOneTimeTokenCredentialValidator looks up the principal's registered accounts in the IGoogleAuthenticatorTokenCredentialRepository; when the repository returns null or an empty collection it throws AccountNotFoundException with '<uid> cannot be found in the registry'. This means no Google Authenticator account has been registered/created for that user yet.","triggerScenarios":"validator.validate() calls credentialRepository.get(uid) before verifying the OTP; if the user never registered a device (no record created via repository.save/registerAccount) or the repository backend (json, mongo, redis, jdbc) has no entry for the uid, the exception is thrown.","commonSituations":"User skips the GAuth registration flow and tries to submit an OTP directly; the account registry file/backend was wiped or points at a different storage (environment mismatch); registry save failed silently in a prior step; multiple CAS nodes with non-shared credential repositories.","solutions":["Ensure the user completes the GAuth account registration (scan QR / scratch codes) which persists the account via the credential repository","Check the configured cas.authn.mfa.gauth.credential-repository backend (json file path, db, mongo...) actually contains a record for the uid","Verify all CAS nodes share the same credential repository storage so accounts registered on one node are visible to others","If the record exists but is stale, re-register the account or import it via the repository API (save(account))"],"exampleFix":"// before: submitting OTP with no registration\n// POST otp for uid 'jsmith' -> AccountNotFoundException: jsmith cannot be found in the registry\n// after: register first\nGoogleAuthenticatorAccount account = GoogleAuthenticatorAccount.builder()\n    .username(\"jsmith\").secretKey(\"BASE32SECRET\").validationCode(123456).build();\ncredentialRepository.save(account, \"jsmith\");","handlingStrategy":"validation","validationCode":"// ensure account exists before validating OTP\nval accounts = credentialRepository.get(uid);\nif (accounts == null || accounts.isEmpty()) {\n    throw new AccountNotFoundException(uid + \" is not enrolled; complete GAuth registration first\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    validator.validate(tokenCredential, authentication);\n} catch (AccountNotFoundException e) {\n    return enrollmentRequired(e.getMessage());\n}","preventionTips":["Force users through the GAuth registration flow before OTP login","Share the credential repository storage across all CAS nodes","Verify the credential-repository backend config points at the environment that holds registrations","Monitor repository.save failures that silently drop registrations"],"tags":["gauth","otp","account-not-found","mfa"],"backgroundTag":"record-not-found","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}