Skip to content

R2 Security: login rate-limiting + account lockout + secrets fail-fast - #65

Merged
Akash29g merged 1 commit into
mainfrom
feat/round-2-login-hardening
Jul 15, 2026
Merged

R2 Security: login rate-limiting + account lockout + secrets fail-fast#65
Akash29g merged 1 commit into
mainfrom
feat/round-2-login-hardening

Conversation

@g9shubh

@g9shubh g9shubh commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Round 2 — Security half

Pairs with Akash's R2 CI/CD (Dependabot / npm audit / dotnet vuln + gitleaks diff-only + Trivy).

What

  • IP rate-limiting on POST /api/v1/auth/login (5/min, FixedWindow), returns our ApiResponse envelope with code RATE_LIMITED (429 + Retry-After). Applied via [EnableRateLimiting("login")] — only the login action is throttled.
  • DB-backed per-account lockout (login_attempts): 5 failures within 15 min → 15-min lock, keyed by normalized email, records IP. Survives restarts / spans multiple IPs. Counter cleared on successful login.
  • No user enumeration: unknown-email and wrong-password both return INVALID_CREDENTIALS; failures are recorded either way.
  • Secrets fail-fast: AddOptions<JwtSettings>().Validate(...).ValidateOnStart() (Key present, ≠ placeholder, ≥ 32 chars) + connection-string guard in Program.cs. New docs/security-secrets.md documents required env vars.

Pipeline / ordering

  • UseRateLimiter() placed after CORS, before UseAuthentication().
  • Client IP is correct behind nginx thanks to UseForwardedHeaders() (R1).

Tests

  • Api.Tests: 45 passed (added Login_returns_429_when_account_locked, Login_registers_failure_on_bad_password).
  • Service.Tests: 143 passed (added 6 LoginLockoutService tests: lock-after-5, unlocked-at-4, reset, case/whitespace-insensitive, window re-arm, retry-after range).

DB change

  • New migration AddLoginAttempts (creates login_attempts + unique index on email). Reviewer: run dotnet ef database update.

Known finding (deferred to R4 — refresh tokens)

  • JwtTokenService.CreateToken hardcodes expires: DateTime.UtcNow.AddHours(8) and ignores Jwt:ExpiryMinutes. Access-token lifetime will be reworked when refresh tokens land (R4). Documented in docs/security-secrets.md.

Watch-outs for reviewer (Akash)

  • Confirm 6th rapid login → 429 (verified locally ✅).
  • Confirm blank Jwt:Key → app refuses to start.
  • /auth/me and normal reads are NOT throttled.

@g9shubh
g9shubh requested a review from sumitgupta-cse July 15, 2026 05:54
@g9shubh
g9shubh requested a review from Akash29g as a code owner July 15, 2026 05:54
@Akash29g
Akash29g merged commit a669ecc into main Jul 15, 2026
2 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