Files

3.7 KiB

id, epic, title, type, status, feature, depends-on, blocks, created, updated
id epic title type status feature depends-on blocks created updated
05-auth-signin-rate-limit-backfill security-headers-rate-limit-sbom auth.signIn rate-limit backfill technical-story done auth
01-rate-limit-type-primitives
02-rate-limit-implementations
03-no-undeclared-rate-limit-eslint-rule
04-with-rate-limit-wrapper-and-conformance
2026-05-20T00:00:00Z 2026-05-20T09:27:20.152Z

Goal

Apply the rate-limit primitive to auth.signIn as the canonical reference example that every downstream consumer will copy — manifest declaration, dual consume calls in the use-case body with TooManyRequestsError throws, binder wiring, and extended tests — demonstrating end-to-end credential-stuffing defence that passes lint and conformance.

Why

auth.signIn is the highest-risk write path in the template. Without rate-limit gates, credential-stuffing and account-enumeration windows stay open until a real consumer notices their auth logs. Backfilling this use case with the complete pattern (manifest → use-case body → binders → tests) also validates that all four preceding stories form a coherent system: if any type, impl, ESLint rule, or wrapper is mis-shaped, this story's conformance gate will surface it.

Done when

  • packages/auth/src/feature.manifest.ts signIn entry declares rateLimit: [{ name: "ip", window: "1m", budget: 5 }, { name: "account", window: "1h", budget: 10 }].
  • signInUseCase deps include rateLimit: IRateLimit; body calls rateLimit.consume("ip", \signIn:ip:${input.clientIp}`)andrateLimit.consume("account", `signIn:account:${input.email}`), throwing TooManyRequestsErroron!allowed`.
  • packages/auth/src/di/bind-production.ts + bind-dev-seed.ts pass ctx.rateLimit ?? new NoopRateLimit() into signIn's wireUseCase.
  • Existing signIn unit tests extended: RecordingRateLimit asserts both consume calls captured; InMemoryRateLimit at budget 1 asserts second call throws TooManyRequestsError.
  • no-undeclared-rate-limit ESLint rule passes (no warnings) on both "ip" and "account" call sites.
  • assertFeatureConformance boot assertion passes (signIn is RateLimited branded).
  • pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff all pass.

In scope

  • feature.manifest.tsrateLimit declaration for signIn.
  • sign-in.use-case.tsrateLimit: IRateLimit in deps, dual consume + TooManyRequestsError throw.
  • TooManyRequestsError class (if not already present in auth/entities/errors/) — add alongside other auth errors.
  • bind-production.ts + bind-dev-seed.ts — wire ctx.rateLimit ?? new NoopRateLimit() into signIn.
  • Unit tests: RecordingRateLimit dual-consume assertion; InMemoryRateLimit budget-1 rejection.

Out of scope

  • auth.signUp rate-limit — not declared in the PRD; consumer adds when needed.
  • Rate-limit for any other auth use case — beyond the canonical example scope.
  • Redis-backed wiring — consumer adds via ADR-022 library evaluation.

Tasks

  • Add rateLimit: [{ name: "ip", window: "1m", budget: 5 }, { name: "account", window: "1h", budget: 10 }] to the signIn entry in packages/auth/src/feature.manifest.ts; add rateLimit: IRateLimit to signInUseCase deps; add TooManyRequestsError to auth error types if absent; insert dual rateLimit.consume calls with TooManyRequestsError throws in the use-case body; update bind-production.ts + bind-dev-seed.ts to pass ctx.rateLimit ?? new NoopRateLimit() into signIn's wireUseCase; extend signIn unit tests with RecordingRateLimit dual-consume assertion and InMemoryRateLimit budget-1 rejection; all gates pass.