--- id: 05-auth-signin-rate-limit-backfill epic: security-headers-rate-limit-sbom title: auth.signIn rate-limit backfill type: technical-story status: done feature: auth depends-on: [ 01-rate-limit-type-primitives, 02-rate-limit-implementations, 03-no-undeclared-rate-limit-eslint-rule, 04-with-rate-limit-wrapper-and-conformance, ] blocks: [] created: 2026-05-20T00:00:00Z updated: 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}\`)` and `rateLimit.consume("account", \`signIn:account:${input.email}\`)`, throwing `TooManyRequestsError`on`!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.ts` — `rateLimit` declaration for `signIn`. - `sign-in.use-case.ts` — `rateLimit: 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 - [x] 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.