mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
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:
52
apps/api/src/config/env.ts
Normal file
52
apps/api/src/config/env.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'dotenv/config';
|
||||
import { z } from 'zod';
|
||||
|
||||
const EnvSchema = z.object({
|
||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||
PORT: z.coerce.number().default(3000),
|
||||
DATABASE_URL: z.string().min(1),
|
||||
REDIS_URL: z.string().default('redis://localhost:6379'),
|
||||
JWT_SECRET: z.string().min(16, 'JWT_SECRET : 16 caractères minimum'),
|
||||
JWT_EXPIRES_IN: z.string().default('8h'),
|
||||
MINIO_ENDPOINT: z.string().default('localhost'),
|
||||
MINIO_PORT: z.coerce.number().default(9000),
|
||||
MINIO_USE_SSL: z.string().optional(),
|
||||
MINIO_ACCESS_KEY: z.string().default('siop'),
|
||||
MINIO_SECRET_KEY: z.string().default('siop-minio'),
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof EnvSchema>;
|
||||
|
||||
/** Validée au démarrage — l'API refuse de booter sur une config invalide. */
|
||||
export function loadEnv(): Env {
|
||||
const parsed = EnvSchema.safeParse(process.env);
|
||||
if (!parsed.success) {
|
||||
throw new Error(
|
||||
`Configuration invalide :\n${parsed.error.issues
|
||||
.map((i) => ` - ${i.path.join('.')}: ${i.message}`)
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
|
||||
/** Lues à l'appel (et non à l'import) pour rester testables. */
|
||||
export function demoModeEnabled(): boolean {
|
||||
return process.env.DEMO_MODE === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* ADR-002, double verrou : DEMO_MODE=true en production exige l'aveu explicite
|
||||
* DEMO_MODE_I_KNOW=true (réservé à l'instance de démonstration publique).
|
||||
*/
|
||||
export function assertDemoModeAllowed(): void {
|
||||
if (
|
||||
demoModeEnabled() &&
|
||||
process.env.NODE_ENV === 'production' &&
|
||||
process.env.DEMO_MODE_I_KNOW !== 'true'
|
||||
) {
|
||||
throw new Error(
|
||||
'DEMO_MODE=true en production sans DEMO_MODE_I_KNOW=true — démarrage refusé (ADR-002).',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user