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 |
|
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.tssignInentry declaresrateLimit: [{ name: "ip", window: "1m", budget: 5 }, { name: "account", window: "1h", budget: 10 }].signInUseCasedeps includerateLimit: IRateLimit; body callsrateLimit.consume("ip", \signIn:ip:${input.clientIp}`)andrateLimit.consume("account", `signIn:account:${input.email}`), throwingTooManyRequestsErroron!allowed`.packages/auth/src/di/bind-production.ts+bind-dev-seed.tspassctx.rateLimit ?? new NoopRateLimit()into signIn'swireUseCase.- Existing signIn unit tests extended:
RecordingRateLimitasserts bothconsumecalls captured;InMemoryRateLimitat budget 1 asserts second call throwsTooManyRequestsError. no-undeclared-rate-limitESLint rule passes (no warnings) on both"ip"and"account"call sites.assertFeatureConformanceboot assertion passes (signIn isRateLimitedbranded).pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diffall pass.
In scope
feature.manifest.ts—rateLimitdeclaration forsignIn.sign-in.use-case.ts—rateLimit: IRateLimitin deps, dualconsume+TooManyRequestsErrorthrow.TooManyRequestsErrorclass (if not already present inauth/entities/errors/) — add alongside other auth errors.bind-production.ts+bind-dev-seed.ts— wirectx.rateLimit ?? new NoopRateLimit()into signIn.- Unit tests:
RecordingRateLimitdual-consume assertion;InMemoryRateLimitbudget-1 rejection.
Out of scope
auth.signUprate-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 thesignInentry inpackages/auth/src/feature.manifest.ts; addrateLimit: IRateLimittosignInUseCasedeps; addTooManyRequestsErrorto auth error types if absent; insert dualrateLimit.consumecalls withTooManyRequestsErrorthrows in the use-case body; updatebind-production.ts+bind-dev-seed.tsto passctx.rateLimit ?? new NoopRateLimit()into signIn'swireUseCase; extend signIn unit tests withRecordingRateLimitdual-consume assertion andInMemoryRateLimitbudget-1 rejection; all gates pass.