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>
66 lines
1.7 KiB
TypeScript
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;
|