Skip to content

security: Add strict rate limiting on auth endpoints #188

@Xhristin3

Description

@Xhristin3

Problem Statement

The global ThrottlerGuard (api/src/app.module.ts:17-18) applies 100 requests per 60 seconds to every route including authentication endpoints (POST /auth/login, POST /auth/register). This default is far too generous for sensitive auth operations. Credential brute-force attacks can make 100 login attempts per minute per IP address without triggering any throttling response. There is no progressive backoff, no account lockout, and no CAPTCHA integration.

Evidence

// api/src/app.module.ts:17-18
ThrottlerModule.forRoot([{
  ttl: parseInt(process.env.THROTTLE_TTL ?? "60000"),
  limit: parseInt(process.env.THROTTLE_LIMIT ?? "100"),
}])

The auth controller has no route-specific throttling override:

// api/src/auth/auth.controller.ts — no @Throttle() or @SkipThrottle() decorator
@Post("login")
login(@Body() dto: LoginDto): Promise<AuthResponse> { ... }

Impact

Attackers can attempt 100 credential guesses per minute (6,000 per hour) per IP without triggering any defense. With distributed IPs, this scales to millions of attempts. Successful credential stuffing can lead to account takeover, data exfiltration, and stream hijacking.

Proposed Solution

  1. Apply @Throttle({ default: { limit: 5, ttl: 900000 } }) (5 attempts per 15 minutes) on login and register endpoints
  2. Add IP-based progressive delay on repeated failures (store attempt count in cache)
  3. Return consistent error messages regardless of whether the email exists (already implemented in AuthService — good)
  4. Log auth failures to audit log for monitoring
  5. Add Retry-After header on throttled responses (already supported by ThrottlerExceptionFilter)

Technical Requirements

  • Must use NestJS @Throttle() decorator without breaking global throttler config
  • Must log auth failures with user-agent and IP for threat monitoring
  • Must maintain backward compatibility with existing ThrottlerExceptionFilter

Acceptance Criteria

  • Login endpoint returns 429 after 5 failed attempts within 15 minutes from same IP
  • Register endpoint returns 429 after 5 failed attempts within 15 minutes from same IP
  • Other endpoints (GET /streams, etc.) continue with global 100/60s limit
  • Auth failure events are logged with structured data
  • Retry-After header is present on 429 responses
  • All existing tests pass

File Map

  • api/src/auth/auth.controller.ts — add @Throttle decorators
  • api/src/auth/auth.service.ts — add failure logging
  • api/src/app.module.ts — verify global throttler config

Dependencies

  • Related: REPO-007 (token refresh needs rate limiting too)
  • Related: REPO-001 (real auth needed for accurate failure tracking)

Testing Strategy

  • Unit: Test ThrottlerGuard behavior with mocked request counts
  • Integration: Test that 6 rapid login requests trigger 429 on the 6th
  • Verify that different IPs get independent rate limits
  • Test that non-auth endpoints are not affected

Security Considerations

The error message must remain consistently "invalid email or password" (already done) to prevent email enumeration. Rate limit responses should not distinguish between valid and invalid credentials. Consider adding a rate-limit bypass for trusted internal services.

Definition of Done

  • Code implemented and peer-reviewed
  • Tests written and passing (unit + integration where applicable)
  • Documentation updated if behavior changed
  • No new linting errors or type errors introduced
  • Security considerations addressed
  • PR linked to this issue and merged

Labels: security, high impact
Priority: High
Difficulty: Intermediate
Estimated Effort: 1d
Milestone: v1.0-alpha


Labels: security,high impact
Priority: High | Difficulty: Intermediate | Estimated Effort: 1d
Backlog ID: REPO-005

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions