Skip to content

AUTHLIB-180 Lockout Cooldown - #97

Merged
saligiad merged 5 commits into
mainfrom
AUTHLIB-180
Sep 21, 2026
Merged

saligiad merged 5 commits into
mainfrom
AUTHLIB-180

Conversation

@saligiad

@saligiad saligiad commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Added a scheduled job that looks for locked user accounts and, if a configured cooldown period has elapsed, unlocks those accounts. Added configuration includes:

  • octri.authentication.lockout-cooldown.enabled: Feature flag, defaults to false.
  • octri.authentication.lockout-cooldown.duration: Defaults to 30 minutes.
  • octri.authentication.lockout-cooldown.polling-schedule: Defaults to every minute. Controls frequency with which the unlock job runs.

Issues

AUTHLIB-180

[x] Added to CHANGELOG.md

Discussion

Issue with implementation

The job handler checkLockoutCooldown currently checks whether lockoutCooldownPeriod is null, because my original design was use octri.authentication.lockout-cooldown-period=null as the primary way to disable the feature. However I couldn't actually get that to work as intended while testing locally:

  • octri.authentication.lockout-cooldown-period= simply fell back to the library default value.
  • octri.authentication.lockout-cooldown-period=null and octri.authentication.lockout-cooldown-period=#{null} both caused build errors
    • Reason: failed to convert java.lang.String to java.lang.Integer (caused by java.lang.NumberFormatException: For input string: "{null}"

I wanted to get somebody else's feedback on this. Perhaps the default should be null, and so that applications must consciously enable this feature? Or maybe a dedicated enabled/disabled flag would be the preference?

Already addressed in discussion.

Other notes

  • The current implementation checks that user.enabled is true but, otherwise, does not check things such as account or credential expiration.
  • Unlocking the account resets the number of login failures to 0.

Added a scheduled job that looks for locked user accounts and, if a
configured cooldown period has elapsed, unlocks those accounts. Added
configuration includes:

* `octri.authentication.lockout-cooldown-period`: Defaults to 15
  minutes.
* `octri.authentication.lockout-polling-schedule`: Defaults to 30
  minutes. Controls frequency with which the unlock job runs.

Implementation notes:

* The current implementation checks that `user.enabled` is true but,
  otherwise, does not check things such as account or credential
expiration.
* Unlocking the account resets the number of login failures to 0.
Removed commend about null configuration, due to issues testing the
configured value is null.
/**
* Schedule for cron task to check cooldown on locked accounts. Defaults to every 15 minutes.
*/
private String lockoutPollingSchedule = "0 */15 * * * *";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would default this to every minute to minimize the time that an account might remain locked after the cooldown period ends.

* Minimum time (in minutes) that must elapse between the most recent failed login and automatic account unlock.
* Defaults to 30 minutes.
*/
private Integer lockoutCooldownPeriod = 30;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be more natural as a Duration. See passwordTokenValidFor in this file for an example.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other thought: I might group the auto-unlock properties in their own configuration property class, which would make adding an explicit feature flag easier.

Comment on lines +51 to +52
if (cooldownPeriod == null)
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always include curly braces in if tests to prevent bugs when updating code. Our code formatting rules should have prevented this.

@heathharrelson

heathharrelson commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

The job handler checkLockoutCooldown currently checks whether lockoutCooldownPeriod is null, because my original design was use octri.authentication.lockout-cooldown-period=null as the primary way to disable the feature. However I couldn't actually get that to work as intended while testing locally:

The cleanest way to implement conditional behavior is to prevent bean creation using one of the @ConditionalOn... annotations. You would do this by adding the annotation to the LockoutCooldownJob class, or by removing @Component from LockoutCooldownJob and creating the bean programmatically in OctriAuthenticationConfiguration (see the defaultPasswordEncoder method for an example).

In this case, you could add an octri.authentication.lockout-cooldown.enabled property to use as a feature flag and use @ConditionalOnProperty to enable the bean. This is the route that I would go, because it has the added benefit of being explicit. You could possibly implement your original design (enable if cooldown period is not null) using @ConditionalOnExpression, but there are no comparable examples in our code (see this search).

I wanted to get somebody else's feedback on this. Perhaps the default should be null, and so that applications must consciously enable this feature? Or maybe a dedicated enabled/disabled flag would be the preference?

I would probably initially default to disabling automatic unlock to preserve existing behavior, but we might want to revisit the default the next time we do a major version release.

Implement changes based on feedback:

* Convert cooldown from Integer to Duration - updated naming from
  `lockoutCooldownPeriod` to `lockoutCooldownDuration` to match.
* Update the polling default to every minute, to minimize default
  lockout time.

@heathharrelson heathharrelson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty close, so I expect to approve this once you've reworked the configuration to make the LockoutCooldownJob bean conditional.

Don't forget to document your configuration properties in docs/CONFIGURATION_PROPERTIES.md.

Use the `@ConditionalOnProperty` annotation to make `LockoutCooldownJob`
a conditional bean, branching off of whether
octri.authentication.lockout-cooldown.enabled` is set. With the feature
flag in place, the code no longer uses `duration=null` to control
behavior.

Additionally, isolates properties in new `LockoutCooldownProperties`
class.
private Duration duration = DEFAULT_COOLDOWN_DURATION;

/**
* Schedule for cron task to check cooldown on locked accounts. Defaults to every 15 minutes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated to reflect the new default.

Comment thread docs/CONFIGURATION_PROPERTIES.md Outdated
| octri.authentication.enable-password-visibility-toggle | OCTRI_AUTHENTICATION_ENABLE_PASSWORD_VISIBILITY_TOGGLE | boolean | true | Whether to enable the password visibility toggle button. |
| octri.authentication.lockout-cooldown.enabled | OCTRI_AUTHENTICATION_LOCKOUTCOOLDOWN_ENABLED | boolean | false | Whether to enable automatic account unlocking with configurable cooldown. |
| octri.authentication.lockout-cooldown.duration | OCTRI_AUTHENTICATION_LOCKOUTCOOLDOWN_DURATION | duration | 30m | Minimum lockout duration before account is unlocked. |
| octri.authentication.lockout-cooldown.pollingSchedule | OCTRI_AUTHENTICATION_LOCKOUTCOOLDOWN_POLLINGSCHEDULE | string | "0 */1 * * * *" | Cron schedule to unlock eligible accounts. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's save ourselves from having to mentally parse cron expressions.

Suggested change
| octri.authentication.lockout-cooldown.pollingSchedule | OCTRI_AUTHENTICATION_LOCKOUTCOOLDOWN_POLLINGSCHEDULE | string | "0 */1 * * * *" | Cron schedule to unlock eligible accounts. |
| octri.authentication.lockout-cooldown.pollingSchedule | OCTRI_AUTHENTICATION_LOCKOUTCOOLDOWN_POLLINGSCHEDULE | string | "0 */1 * * * *" | Cron schedule to unlock eligible accounts. Defaults to every minute. |

@heathharrelson heathharrelson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for refining this!

Update comment to reflect accurate polling schedule default, and state
the default schedule behavior in plain English in
CONFIGURATION_PROPERTIES.md
@saligiad
saligiad merged commit 9565d8a into main Sep 21, 2026
2 checks passed
@saligiad
saligiad deleted the AUTHLIB-180 branch September 21, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants