Skip to content

feat(platform): login rate limiting with exponential backoff and lockout#1542

Merged
larryro merged 2 commits into
mainfrom
feat/platform-login-rate-limiting
Apr 15, 2026
Merged

feat(platform): login rate limiting with exponential backoff and lockout#1542
larryro merged 2 commits into
mainfrom
feat/platform-login-rate-limiting

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Apr 15, 2026

Closes #1501.

Summary

  • Gates /sign-in/email behind a per-IP flood limit and per-account exponential lockout (default 1s → 10s → 60s → 10min after 5 failures). Blocked retries never advance lockedUntil, so the displayed countdown reflects the real unlock time.
  • Moves high-volume "blocked at gate" events out of auditLogs into an hourly-bucketed loginBlockCounters table — keeps the audit table bounded even under 1M-req DDoS (150 audit rows vs 1M previously).
  • Admin-configurable via Settings → Governance → Security & Monitoring → Login & authentication: failure threshold, backoff schedule (seconds), trusted proxies (CIDR / keywords). No env vars.
  • In-app notification bell in the sidebar with i18n keys, relative time, expand-to-view, explicit "Mark as read".
  • Client IP extraction hardened with proxy-addr: walks X-Forwarded-For right-to-left skipping trusted-proxy CIDRs, defends against spoofed leftmost entries.
  • Login form shows "Account temporarily locked, try again in X" on 429 (seconds/minutes/hours, en + de).
  • Audit log metadata dialog auto-formats timestamp-suffix fields (at/until/time/timestamp) to local time via existing useFormatDate.
  • Better Auth's built-in rateLimit explicitly disabled — our gate owns all sign-in throttling.
  • Adds NIST/SOC 2 compliance gap: login_success audit event.

Tabs & components added

  • Settings → Logs → Sign-in blocks (admin-only): hourly rejected-attempt counters, last 7 days
  • Governance → Security & Monitoring split into sub-tabs: Login & auth / PII / Usage
  • Login policy editor: threshold, backoff schedule, trusted proxies; Save dirty-tracks
  • Notification bell: sidebar popover, unread badge, severity indicators

Schema additions

  • loginAttempts — per-email failure counter + lockedUntil
  • loginBlockCounters — hourly (email, windowStart) coalesced blocked-attempt counts
  • notifications — org-scoped, i18n keys + params, readBy array

Known gaps (documented, deferred)

  • /request-password-reset and /send-verification-email not yet gated (separate issue)
  • Email delivery of lockout notifications (infra choice TBD)
  • E2E Playwright coverage (repo has no Playwright suite yet)

Test plan

  • npx tsc --noEmit clean
  • bunx oxlint --type-aware clean (0 errors, 0 warnings)
  • vitest run 21/21 pass (backoff math, strictest-policy, policy parsing, IP parsing + spoof defense)
  • Manual: 5 wrong passwords → lockout; correct password resets counter; login form shows retry-after
  • Manual: blocked attempts during lockout accumulate in loginBlockCounters without advancing lockedUntil
  • Manual: Sign-in blocks tab renders counter rows
  • Manual: notification bell opens/closes, expand-to-view, Mark as read persists
  • Manual: German locale renders all new strings

Summary by CodeRabbit

Release Notes

  • New Features

    • Added notification bell icon in navigation displaying unread security alerts.
    • Added sign-in block tracking dashboard showing rejected login attempts by account and IP over the past 7 days.
    • Added login attempt limits configuration to set custom account lockout thresholds, backoff delays, and trusted proxies.
    • Implemented account lockout protection with configurable failed-attempt thresholds and time-based delays.
  • Messaging & Localization

    • Added account lockout notifications with countdown timers for temporary account suspension.

larryro added 2 commits April 15, 2026 19:07
Implements issue #1501. Protects sign-in against per-account brute force
and distributed password-spray, records lockout events to the audit log
and in-app notifications, and exposes an admin settings UI + logs view.

- Better Auth hooks.before gates /sign-in/email on per-IP flood + per-account
  lockout state, returning max(accountLockout, ipLimit) as retry-after so
  the user sees the real unlock time. jitter masks the timing channel.
- hooks.after bumps the failure counter on real wrong-password attempts
  only; lockedUntil never advances on blocked retries. Blocked attempts
  go to a coalesced hourly loginBlockCounters table instead of audit logs
  to keep the audit table bounded under DDoS.
- login_policy governance type (Zod): maxAttemptsBeforeLockout,
  backoffSchedule, trustedProxies (admin-configurable). Defaults
  [1s,10s,60s,10min] after 5 failures.
- Client IP extraction via proxy-addr, walking X-Forwarded-For right-to-
  left with trusted-proxy CIDRs. Defaults loopback+uniquelocal; admins
  can widen via Login policy editor. Defends against spoofed leftmost
  entries.
- Audit log additions: login_attempt (failure, with lockedUntil in
  metadata once past threshold), login_lockout (first threshold crossing),
  login_success (NIST/SOC2 compliance).
- Notifications: new notifications table + in-app bell in the sidebar,
  i18n-keyed titles/bodies, relative-time display, expand-to-view with
  explicit "Mark as read".
- Audit log details dialog formats timestamp-suffix metadata fields
  (at/until/time/timestamp) via the existing useFormatDate.
- Settings UI: Security & Monitoring group split into sub-tabs
  (Login & auth / PII / Usage). Login policy editor with trusted-proxy
  field; Save button dirty-tracks.
- Logs page: new "Sign-in blocks" tab (admin-only) shows hourly counter
  rows filtered to org members.
- Login form shows a specific "locked, try again in X" message on 429,
  with seconds/minutes/hours pluralization in en + de.
- Better Auth's built-in rateLimit disabled — our gate owns all sign-in
  throttling.
- Tests: delayForFailureCount, selectStrictestPolicy, parseLoginPolicy,
  client_ip with IPv4/IPv6/spoof/CIDR cases (21 tests, all green).
The login form's Better Auth onError callback had a required ctx
parameter, but existing tests (and Better Auth itself under certain
conditions) invoke it with no argument. Make ctx optional so the
callback safely handles both call styles.
@larryro larryro merged commit 87a4a06 into main Apr 15, 2026
25 checks passed
@larryro larryro deleted the feat/platform-login-rate-limiting branch April 15, 2026 11:19
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This pull request implements login attempt rate limiting with exponential backoff and account lockout functionality. The changes introduce login failure tracking (consecutive failure counts, lockout state), per-email and per-IP throttling via Better Auth middleware hooks, configurable lockout policies (max attempts and backoff schedules) via governance settings, hourly block counters for admin visibility, and a notification system for lockout events. New frontend components include a NotificationBell in the navigation, a LoginPolicyEditor for configuring attempt limits, and a BlockCountersTable for reviewing rejected sign-in attempts. Backend enhancements include trusted proxy IP detection, internal queries and mutations for failure recording, governance policy validation, and audit log/notification writes on lockout. Database schemas for login attempts, counters, and notifications are introduced alongside internationalization strings for lockout messaging and policy configuration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main feature: login rate limiting with exponential backoff and lockout. It is concise, specific, and clearly summarizes the primary change.
Linked Issues check ✅ Passed The PR fully implements all requirements from #1501: per-email and per-IP rate limiting with exponential backoff, configurable org policies, audit logging for lockout events, admin notifications, Better Auth hook integration, and E2E-testable behavior.
Out of Scope Changes check ✅ Passed All changes directly support login rate limiting and lockout. Auxiliary changes (audit log formatting, notification UI, governance schema) are necessary components. No unrelated refactoring or out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/platform-login-rate-limiting

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Comment @coderabbitai help to get the list of available commands and usage tips.

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.

Feature: Login attempt rate limiting with lockout / exponential backoff

1 participant