Files
agentic-dev/packages/auth/src/feature.manifest.ts
Danijel Martinek b61bb0c11e feat(auth): add signIn rate-limit backfill with dual ip/account budgets
Wires the rate-limit primitive end-to-end through auth.signIn as the
canonical credential-stuffing defence example:

- manifest: rateLimit [ip 5/1m, account 10/1h] on signIn use case
- use case: rateLimit: IRateLimit dep; dual consume + TooManyRequestsError
- binders: ctx.rateLimit ?? new NoopRateLimit() in bind-production + bind-dev-seed
- tRPC: TooManyRequestsError → TOO_MANY_REQUESTS error code in authProcedure
- tests: RecordingRateLimit dual-consume assertion; InMemoryRateLimit
  budget-1 ip + account rejection; coverage 100% on use-cases layer
- ESLint: _manifest-ast.js extractRateLimitNames handles RateLimitBudget
  objects ({name,window,budget}) in addition to plain string literals,
  no-undeclared-rate-limit passes on both "ip" and "account" call sites

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 09:22:41 +00:00

66 lines
1.7 KiB
TypeScript

import { defineFeature } from "@repo/core-shared/conformance";
/**
* The auth feature's conformance manifest. Drives binding-slot types in
* `di/bind-production.ts` and is read by ESLint, the boot assertion, and
* the CI drift gate (later milestones).
*
* Conventions:
* - `mutates: true` for any use case that creates, updates, or deletes state
* - `audits` lists every audit event the use case emits (must match calls
* to `auditLog.record(...)` in the factory body — ESLint enforces this
* in a later story)
* - `publishes` / `consumes` cover cross-feature events through `IEventBus`
*/
export const authManifest = defineFeature({
name: "auth",
requiredCores: [],
useCases: {
signIn: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
rateLimit: [
{ name: "ip", window: "1m", budget: 5 },
{ name: "account", window: "1h", budget: 10 },
],
},
signUp: {
mutates: true,
audits: [],
publishes: [],
consumes: [],
},
signOut: {
mutates: true,
audits: [],
publishes: [],
consumes: [],
},
},
realtimeChannels: [],
jobs: [],
coverage: {
bands: {
baseline: { statements: 80, branches: 75, functions: 80, lines: 80 },
entities: { statements: 100, branches: 100, functions: 100, lines: 100 },
"use-cases": {
statements: 100,
branches: 95,
functions: 100,
lines: 100,
},
controllers: {
statements: 100,
branches: 95,
functions: 100,
lines: 100,
},
},
mutationTargets: ["entities", "use-cases"],
},
} as const);
export type AuthManifest = typeof authManifest;