Skip to content

feat(auth): add login rate limiting to prevent brute-force attacks#977

Merged
aaight merged 1 commit intodevfrom
feature/login-rate-limiting
Mar 22, 2026
Merged

feat(auth): add login rate limiting to prevent brute-force attacks#977
aaight merged 1 commit intodevfrom
feature/login-rate-limiting

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 22, 2026

Summary

  • Add in-memory sliding-window rate limiter (src/api/auth/rateLimiter.ts) that tracks login attempts per IP address
  • Integrate the rate limiter into the login handler (src/api/auth/login.ts) — checks before any DB/bcrypt work, resets counter on success
  • Return HTTP 429 with Retry-After header after 10 failed attempts from the same IP within 1 minute
  • Periodic cleanup interval (every 5 min, .unref()'d) evicts expired entries to prevent memory leaks

Technical Details

  • Rate limit: 10 attempts / 1-minute window per IP
  • IP extraction: x-forwarded-for header (first value for comma-separated lists), falls back to 'unknown'
  • Successful logins: counter is reset (recordSuccessfulLogin) so they are not counted against the limit
  • Cleanup: setInterval every 5 minutes sweeps expired entries; timer is .unref()'d so it won't prevent process exit

Test plan

  • tests/unit/api/auth/rateLimiter.test.ts — 14 tests covering: under limit, at limit, over limit, window reset, successful-login reset, cleanup sweep
  • tests/unit/api/auth/login.test.ts — extended with 7 new rate-limiting tests: 429 response, Retry-After header, IP extraction from x-forwarded-for, comma-separated list, recordSuccessfulLogin called/not-called
  • All 6252 unit tests pass
  • Lint (biome) passes with zero errors
  • TypeScript type checking passes with zero errors

Related

Trello: https://trello.com/c/8y9Sd1Xa/498-as-a-developer-i-want-login-rate-limiting-so-that-brute-force-attacks-against-the-dashboard-are-prevented

Card ID: 69c00fbbb87d445a13b03294

🤖 Generated with Claude Code

🕵️ claude-code · claude-sonnet-4-6 · run details

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 22, 2026

Codecov Report

❌ Patch coverage is 91.30435% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/api/auth/rateLimiter.ts 88.88% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

LGTM — well-implemented in-memory rate limiter with correct logic, good test coverage, and clean integration into the login handler.

Code Issues

Should Fix

  • src/api/auth/login.ts:26 — The 'unknown' sentinel fallback means every client that connects without a reverse proxy (no x-forwarded-for) shares a single rate-limit bucket. A burst of unauthenticated requests from any direct-connect IP would lock out all other direct-connect users. Hono's Node.js adapter ships getConnInfo from @hono/node-server/conninfo which returns the TCP remote address — using it as a fallback before 'unknown' would give real per-client rate limiting in non-proxied environments (dev, Docker direct-connect) without affecting the proxy path.

Notes (non-blocking)

  • The design choice to reset the counter on successful login is a reasonable tradeoff: it favors legitimate users at the cost of a small brute-force window for attackers sharing a NAT IP. This is well-documented and standard practice.
  • Deletion during Map iteration in the cleanup sweep is safe per the JS spec — verified.
  • The count logic is correct: 10 attempts are allowed (calls 1-10), and the 11th triggers the block — matches MAX_ATTEMPTS = 10.

🕵️ claude-code · claude-opus-4-6 · run details

@aaight aaight merged commit 461c784 into dev Mar 22, 2026
9 checks passed
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