AuthModule implements the M1 core as one coherent unit: Signup (#13): POST signup/verify-email/resend-verification with shared Zod validation (field-level error details), double opt-in via hashed single-use tokens (24h, superseding reissue), registration_mode enforcement, and per-IP rate limits. Sessions (#14): opaque 32-byte cookie tokens stored as SHA-256 row ids, sliding 30-day expiry (refresh at most hourly), global AuthGuard with @Public() opt-out attaching the user to every request, CSRF origin check on mutating requests, per-account login backoff (5/15min, reset on success), generic 401 for wrong-vs-unknown credentials, logout with immediate invalidation, GET /auth/me. Reset (#15): forgot-password without account enumeration, one-hour single-use tokens, reset destroys all existing sessions. A 14-case supertest e2e suite drives every flow against the test database, reading verification/reset links from the mail outbox. Closes #13 Closes #14 Closes #15 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
25 lines
903 B
TypeScript
25 lines
903 B
TypeScript
import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
|
|
import type { ZodTypeAny, z } from 'zod';
|
|
|
|
/**
|
|
* Validates request bodies against a shared Zod schema. Failures become a
|
|
* 400 with field-level details: { field: [i18nKey, …] } — the web client
|
|
* renders the keys next to the matching inputs.
|
|
*/
|
|
@Injectable()
|
|
export class ZodValidationPipe<Schema extends ZodTypeAny> implements PipeTransform {
|
|
constructor(private readonly schema: Schema) {}
|
|
|
|
transform(value: unknown): z.infer<Schema> {
|
|
const result = this.schema.safeParse(value);
|
|
if (result.success) return result.data;
|
|
|
|
const details: Record<string, string[]> = {};
|
|
for (const issue of result.error.issues) {
|
|
const field = issue.path.join('.') || '_';
|
|
(details[field] ??= []).push(issue.message);
|
|
}
|
|
throw new BadRequestException({ code: 'bad_request', details });
|
|
}
|
|
}
|