Premiere brique de l'app d'admin independante : une surface /admin/*
ajoutee a apps/api, avec une authentification totalement distincte de
celle des utilisateurs.
- Table AdminUser isolee (aucune relation vers UserProfile), migration
20260828120000_admin_user.
- lib/admin-jwt.ts : sign/verify d'un JWT admin, secret ADMIN_JWT_SECRET
propre (jamais interchangeable avec JWT_SECRET).
- middlewares/require-admin.ts : cookie admin_session dedie, re-check
tokenVersion, echoue ferme si ADMIN_JWT_SECRET absent (posture
requireInternalWorker). res.locals.adminUser type via AdminLocals.
- modules/admin/ : admin-auth.{routes,service}.ts (POST /login, POST
/logout, GET /me), admin.routes.ts agregateur monte /admin. Pas de
signup expose.
- lib/safe-admin.ts : mapping AdminUser -> AdminUserView (drop passwordHash
+ tokenVersion, dates ISO).
- scripts/create-admin.ts : creation du 1er admin hors-bande (flags ou
ADMIN_INITIAL_*).
- CORS : setupCore accepte string[] ; app.ts autorise CORS_ORIGIN +
ADMIN_CORS_ORIGIN.
- Shared : schemas/admin.ts (adminLoginSchema), types/admin.ts
(AdminUserView).
- Env : ADMIN_JWT_SECRET (optionnel), ADMIN_COOKIE_NAME, ADMIN_CORS_ORIGIN,
ADMIN_INITIAL_* ; .env.example, .env.test.example, docker-compose.yml,
ci.yml mis a jour.
- reset-db.ts truncate admin_users.
- Tests Mocha admin-auth.test.ts : 400 sans body, 401 email inconnu /
mauvais mdp, login OK (cookie pose, lastLoginAt, pas de hash/tokenVersion
dans la reponse), /me derriere requireAdmin, logout, et un cookie
`session` d'utilisateur normal ne donne pas acces a /admin/*.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
20 lines
806 B
TypeScript
20 lines
806 B
TypeScript
import type { AdminUserView } from "@batch-cooking/shared";
|
|
import type { AdminUser } from "@prisma/client";
|
|
|
|
/**
|
|
* Shapes a Prisma `AdminUser` into the {@link AdminUserView} sent to the
|
|
* admin client — drops `passwordHash` **and** `tokenVersion` (an internal
|
|
* invalidation counter the client never needs, unlike `SafeUserProfile`
|
|
* which does expose it), and serializes the two dates to ISO strings. The
|
|
* one place this security-relevant stripping happens, same role as
|
|
* `toSafeProfile` (`lib/safe-profile.ts`).
|
|
*/
|
|
export function toSafeAdmin(admin: AdminUser): AdminUserView {
|
|
return {
|
|
id: admin.id,
|
|
email: admin.email,
|
|
name: admin.name,
|
|
createdAt: admin.createdAt.toISOString(),
|
|
lastLoginAt: admin.lastLoginAt === null ? null : admin.lastLoginAt.toISOString(),
|
|
};
|
|
}
|