Admin Impersonation

Summary

Authenticated admins can impersonate pre-configured demo accounts to test the application as either a Pro or an Acheteur. The endpoint creates a real JWT access token and a refresh cookie for the demo account, exactly as if that user had logged in normally. Every impersonation is recorded in the audit log. The feature requires seed data (pnpm db:seed) to populate the demo accounts (demo-pro@sealion.fr, demo-acheteur@sealion.fr).

State Diagram

stateDiagram-v2
    state "Validation du rôle" as Validating
    state "Rôle invalide" as Rejected
    state "Recherche compte Pro" as LookupPro
    state "Recherche compte Acheteur" as LookupAcheteur
    state "Compte démo introuvable" as NotFound
    state "Session émise" as TokenCreated
    [*] --> Validating: admin requests impersonation
    Validating --> Rejected: invalid role value
    Validating --> LookupPro: role == "pro"
    Validating --> LookupAcheteur: role == "acheteur"
    LookupPro --> NotFound: demo-pro@sealion.fr not in DB
    LookupAcheteur --> NotFound: demo-acheteur@sealion.fr not in DB
    LookupPro --> TokenCreated: signAccessToken + signRefreshToken + writeAuditLog
    LookupAcheteur --> TokenCreated: signAccessToken + signRefreshToken + writeAuditLog
    TokenCreated --> [*]: accessToken + httpOnly cookie returned

Steps

1. Admin Authentication (Actor: system)

authenticateAdmin preHandler validates the admin JWT. request.adminId is populated.

2. Validate Role (Actor: system)

role from request body must be one of "pro" or "acheteur". Returns 400 INVALID_ROLE with the valid options if not.

3. Look Up Demo Account (Actor: system)

Based on role:

  • "pro": finds prisma.pro.findUnique({ where: { email: "demo-pro@sealion.fr" } })
  • "acheteur": finds prisma.acheteur.findUnique({ where: { email: "demo-acheteur@sealion.fr" } })

If not found: 404 DEMO_ACCOUNT_NOT_FOUND with a message instructing to run pnpm db:seed.

4. Issue Tokens and Write Audit Log (Actor: system)

Prisma $transaction:

  1. Create a refresh token record (proRefreshToken or acheteurRefreshToken) with a signRefreshToken() opaque 96-char hex, SHA-256 hashed, 7-day expiry.
  2. writeAuditLog() with action: "pro.impersonate" or "acheteur.impersonate", actorId: request.adminId, targetId: demo account ID.

Access token: signAccessToken(userId, jwtSecret) — 15-minute HS256.

The refresh token cookie (proRefreshToken or acheteurRefreshToken) is set as httpOnly, secure (non-dev), SameSite lax, 7-day maxAge.

5. Response (Actor: system)

Returns { accessToken, role, identity: { id, email, firstName, lastName } }. The admin-side UI can now redirect to the Pro or Acheteur interface using the returned access token.

Error States

  • Invalid role → 400 INVALID_ROLE
  • Demo account not seeded → 404 DEMO_ACCOUNT_NOT_FOUND

Security Notes

  • Impersonation only targets hardcoded demo accounts — it is not possible to impersonate arbitrary users via this endpoint.
  • All impersonation events are audited in the DB with actor and target IDs.
  • The created session is real and subject to the same token TTLs as a normal login.
  • No upstream process required. Seed data must exist.
  • acheteur-analytics — admin can navigate to the acheteur’s account after impersonating to verify behaviour