feat(r0.10): apps/api — auth fermée par défaut, matrice en base, démo-login ADR-002, seed

- packages/shared : rôles/catégories, schémas Zod, contrat d'API ;
  pnpm contract → docs/openapi.json committée (règle d'or ADR-001)
- apps/api : NestJS 11 + Prisma 6, migration r0_identity (Role/Permission/User) ;
  guard JWT global + @Public() ; PermissionsGuard (@RequirePermission,
  matrice relue en base, cache 60 s) ; FileStorage (seul import MinIO) ; /health
- démo-login ADR-002 : module conditionnel DEMO_MODE (404 sinon, testé e2e),
  double verrou production, refus des comptes isDemo=false
- seed idempotent : 7 rôles, matrice complète (70 lignes), 7 comptes démo
- 19 tests Jest (unit + e2e) ; smoke test sur build de prod

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-15 21:55:04 +01:00
parent ddc9dc52b5
commit 8adb561b63
62 changed files with 8509 additions and 2 deletions

View File

@@ -0,0 +1,42 @@
import { z } from 'zod';
import { ROLE_NAMES } from '../permissions';
export const AuthUserSchema = z.object({
id: z.uuid(),
email: z.email(),
displayName: z.string(),
role: z.object({ id: z.uuid(), name: z.enum(ROLE_NAMES) }),
isDemo: z.boolean(),
});
export type AuthUser = z.infer<typeof AuthUserSchema>;
export const LoginRequestSchema = z.object({
email: z.email(),
password: z.string().min(1),
});
export type LoginRequest = z.infer<typeof LoginRequestSchema>;
export const AuthResponseSchema = z.object({
accessToken: z.string(),
user: AuthUserSchema,
});
export type AuthResponse = z.infer<typeof AuthResponseSchema>;
/** ADR-002 — jamais de secret dans cette liste. */
export const DemoAccountSchema = z.object({
id: z.uuid(),
displayName: z.string(),
roleName: z.enum(ROLE_NAMES),
initials: z.string().min(1).max(3),
});
export type DemoAccount = z.infer<typeof DemoAccountSchema>;
export const DemoAccountsResponseSchema = z.object({
accounts: z.array(DemoAccountSchema),
});
export type DemoAccountsResponse = z.infer<typeof DemoAccountsResponseSchema>;
export const DemoLoginRequestSchema = z.object({
userId: z.uuid(),
});
export type DemoLoginRequest = z.infer<typeof DemoLoginRequestSchema>;

View File

@@ -0,0 +1,13 @@
import { z } from 'zod';
const ServiceStateSchema = z.enum(['up', 'down']);
export const HealthResponseSchema = z.object({
status: z.enum(['ok', 'degraded']),
services: z.object({
database: ServiceStateSchema,
redis: ServiceStateSchema,
storage: ServiceStateSchema,
}),
});
export type HealthResponse = z.infer<typeof HealthResponseSchema>;

View File

@@ -0,0 +1,11 @@
import { z } from 'zod';
import { AuthUserSchema } from './auth';
import { PermissionEntrySchema } from '../permissions';
/** Profil courant : identité + matrice du rôle (le JWT, lui, ne porte jamais
* de droits — le web lit cette réponse pour adapter l'UI, l'API re-vérifie
* chaque requête via PermissionsGuard). */
export const MeResponseSchema = AuthUserSchema.extend({
permissions: z.array(PermissionEntrySchema),
});
export type MeResponse = z.infer<typeof MeResponseSchema>;