feat(plugin-auth): account lockout + rate-limit tuning (ADR-0069 D2, P1)#2365
Merged
Conversation
Per-identity brute-force protection, the second ADR-0069 slice (after HIBP), reusing the setting -> enforcement-seam pattern. - Account lockout [custom][field]: sys_user.failed_login_count / locked_until columns; `lockout_threshold` (0=off) + `lockout_duration_minutes` settings. Enforced in the /sign-in/email before/after hooks: failures increment, crossing the threshold stamps locked_until, and a locked account is rejected even with the correct password (survives IP rotation). Success resets both. - Admin Unlock: admin-guarded POST /api/v1/auth/admin/unlock-user + sys_user `unlock_user` action. - Rate-limit tuning [native]: `rate_limit_max` / `rate_limit_window_seconds` wire better-auth core `rateLimit` with stricter customRules on the auth endpoints. Default-off / additive (no upgrade behavior change); ADR-0049 (enforcement ships with the setting); timestamps written as Date, never epoch-ms (ADR-0074). Verified live (dogfood): 3 failed sign-ins lock the account; the correct password is then rejected with ACCOUNT_LOCKED while OTHER users (incl. admin) sign in normally; admin unlock restores access; unauthenticated unlock 401; rate-limit burst trips 429 after the cap. Unit: 158 plugin-auth + 129 service-settings + 63 platform-objects green; the lockout mock honours the ObjectQL `where` key so the filter/where bug dogfood caught cannot regress. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jun 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second slice of ADR-0069 (enterprise authentication hardening) — tracking issue #2281 — implementing D2: per-identity account lockout + native rate-limit tuning. Reuses the setting→enforcement-seam pattern established by the HIBP PR (#2361).
What
Account lockout
[custom][field]sys_user.failed_login_count+sys_user.locked_until.authsettingslockout_threshold(0 = off, default) andlockout_duration_minutes(default 15), in a new Anti-abuse settings group./sign-in/emailbetter-authbefore/afterhooks: failures increment the counter; crossing the threshold stampslocked_until; a locked account is rejected even with the correct password (per-identity — survives IP rotation, unlike rate limiting). A successful sign-in resets both fields.POST /api/v1/auth/admin/unlock-userroute + anunlock_useraction onsys_user.Rate-limit tuning
[native]authsettingsrate_limit_max/rate_limit_window_secondswire better-auth's corerateLimitwith strictercustomRulesfor/sign-in/email,/sign-up/email,/request-password-reset,/reset-password.ADR-0049 / ADR-0074 compliance
Every setting ships with its enforcement (no false surface). All default off / to safe values and are additive — no behavior change on upgrade.
locked_untilis written as aDate, never epoch-ms (ADR-0074).Verification
403 ACCOUNT_LOCKEDwhile other users (incl. the admin) sign in normally → admin unlock restores access → unauthenticated unlock401→ a rate-limit burst trips429after the cap.Out of scope (follow-up ADR-0069)
D3 enforced MFA; remaining D1
[custom](complexity / expiry / history); P2 (sessions / IP allowlist / OIDC trust-list); P3 (SAML). A locked-accounts list-view filter is a small follow-up.🤖 Generated with Claude Code