{"record":{"id":"a054f7e4e759ce13","repo":"apereo/cas","slug":"unable-to-login-at-this-time","errorCode":null,"errorMessage":"Unable to login at this time","messagePattern":"Unable to login at this time","errorType":"exception","errorClass":"InvalidLoginTimeException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-generic/src/main/java/org/apereo/cas/adaptors/generic/JsonResourceAuthenticationHandler.java","lineNumber":93,"sourceCode":"            case EXPIRED -> throw new AccountExpiredException();\n            case LOCKED -> throw new AccountLockedException();\n            case MUST_CHANGE_PASSWORD -> throw new AccountPasswordMustChangeException();\n            case OK -> LOGGER.debug(\"Account status is OK\");\n        }\n\n        val clientInfo = ClientInfoHolder.getClientInfo();\n        if (clientInfo != null && StringUtils.isNotBlank(account.getLocation())\n            && !RegexUtils.find(account.getLocation(), clientInfo.getClientIpAddress())) {\n            throw new InvalidLoginLocationException(\"Unable to login from this location\");\n        }\n\n        if (StringUtils.isNotBlank(account.getAvailability())) {\n            val range = Splitter.on(\"~\").splitToList(account.getAvailability());\n            val startDate = DateTimeUtils.convertToZonedDateTime(range.getFirst());\n            val endDate = DateTimeUtils.convertToZonedDateTime(range.get(1));\n            val now = ZonedDateTime.now(Clock.systemUTC());\n            if (now.isBefore(startDate) || now.isAfter(endDate)) {\n                throw new InvalidLoginTimeException(\"Unable to login at this time\");\n            }\n        }\n\n        val warnings = new ArrayList<MessageDescriptor>();\n        if (account.getExpirationDate() != null) {\n            val now = LocalDate.now(ZoneOffset.UTC);\n            if (now.isEqual(account.getExpirationDate()) || now.isAfter(account.getExpirationDate())) {\n                throw new AccountExpiredException();\n            }\n            if (getPasswordPolicyConfiguration() != null) {\n                val warningPeriod = account.getExpirationDate()\n                    .minusDays(getPasswordPolicyConfiguration().getPasswordWarningNumberOfDays());\n                if (now.isAfter(warningPeriod) || now.isEqual(warningPeriod)) {\n                    val daysRemaining = ChronoUnit.DAYS.between(now, account.getExpirationDate());\n                    warnings.add(new DefaultMessageDescriptor(\n                        \"password.expiration.loginsRemaining\",\n                        \"You have {0} logins remaining before you MUST change your password.\",\n                        new Serializable[]{daysRemaining}));","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-generic/src/main/java/org/apereo/cas/adaptors/generic/JsonResourceAuthenticationHandler.java#L75-L111","documentation":"JsonResourceAuthenticationHandler supports per-account availability windows formatted as 'start~end' datetimes. If the current UTC time is before the start or after the end of that window, it throws InvalidLoginTimeException 'Unable to login at this time'.","triggerScenarios":"authenticateUsernamePasswordInternal for an account whose availability field is non-blank and ZonedDateTime.now(UTC) is outside the parsed start~end range.","commonSituations":"Account temporarily enabled only during certain hours/dates and login attempted outside them; timezone confusion — window stored in local time but compared against UTC; window left set from a past temporary grant; malformed date failing conversion is a related failure.","solutions":["Widen or remove the availability window on the account in the JSON resource","Confirm window datetimes are expressed in a timezone consistent with UTC comparison (prefer offsets like 2026-09-08T09:00:00+00:00)","Check the server clock/timezone if the window looks correct","If the account should be permanently available, clear the availability field entirely"],"exampleFix":"// before\n\"availability\": \"2024-01-01T00:00:00Z~2024-02-01T00:00:00Z\"\n// after\n\"availability\": \"2024-01-01T00:00:00Z~2030-01-01T00:00:00Z\"","handlingStrategy":"validation","validationCode":"ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);\n// parse account.availability 'start~end' and confirm now is inside before authenticating","typeGuard":null,"tryCatchPattern":"try {\n    return handler.authenticate(credential);\n} catch (InvalidLoginTimeException e) {\n    // inform user of allowed window\n}","preventionTips":["Store availability windows with explicit UTC offsets","Remove stale temporary windows after grants expire","Verify server clock/NTP if windows look correct but fail"],"tags":["authentication","time-window","json-resource","account-policy"],"backgroundTag":"invalid-date-format","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"}