Acheteur Profile Management
Summary
Authenticated acheteurs can read and update their profile (name, phone), initiate an email change (which requires password verification and a new email verification step), and change their password (which invalidates all sessions). All routes are behind authenticateAcheteur — the auth middleware enforces emailVerified status and the 48h deadline. Sensitive fields (passwordHash) are never returned in responses via an explicit SAFE_SELECT projection.
State Diagram
stateDiagram-v2 state "Authentifié" as Authenticated state "Profil consulté" as ProfileRead state "Profil mis à jour" as ProfileUpdated state "Changement email en attente" as EmailChangePending state "Email changé et vérifié" as EmailChangeVerified state "Échec envoi email" as EmailChangeFailed state "Mot de passe changé" as PasswordChanged state "Sessions révoquées" as SessionsRevoked [*] --> Authenticated: Bearer token valid Authenticated --> ProfileRead: acheteur reads profile Authenticated --> ProfileUpdated: acheteur updates profile fields Authenticated --> EmailChangePending: acheteur requests email change EmailChangePending --> EmailChangeVerified: acheteur clicks email-change verify link EmailChangePending --> EmailChangeFailed: email send failure (503) Authenticated --> PasswordChanged: acheteur changes password PasswordChanged --> SessionsRevoked: all refresh tokens deleted ProfileRead --> [*] ProfileUpdated --> [*] EmailChangeVerified --> [*]: email updated, pendingEmail cleared SessionsRevoked --> [*]: must re-authenticate
Steps
1. Read Profile (Actor: acheteur)
GET /acheteur/profile returns the acheteur’s safe fields: id, email, firstName, lastName, phone, emailVerified, pendingEmail, createdAt. No sensitive fields.
Outcome: { data: acheteur }.
2. Update Profile Fields (Actor: acheteur)
PUT /acheteur/profile accepts { firstName?, lastName?, phone? }. Validated against acheteurProfileUpdateSchema. Prisma update with SAFE_SELECT projection.
Outcome: { data: updatedAcheteur }.
3. Initiate Email Change (Actor: acheteur)
PUT /acheteur/profile/email accepts { newEmail, password }.
Rate limit: 3 requests per hour, keyed on acheteurId (not IP).
Validations:
- Current password must match
passwordHash(Google-only accounts —passwordHash: null— are blocked with 401). newEmailmust differ from current email (400SAME_EMAIL).newEmailmust not be taken by another acheteur (409CONFLICT).
On success:
pendingEmailis set tonewEmail.- A verification email is sent via Resend using the
acheteur-verifytemplate with apurpose: "email_change"JWT (48h expiry) viabuildEmailChangeUrl(). - On email send failure: Slack sentry-notif + 503
EMAIL_SEND_FAILED.
Outcome: { data: { pendingEmail } } or 503.
4. Confirm Email Change (Actor: acheteur)
Acheteur clicks the link in the email. The browser hits GET /acheteur/auth/verify-email?token=.... The handler detects purpose === "email_change" and validates that acheteur.pendingEmail === payload.newEmail. On match: email is updated to newEmail, pendingEmail is cleared, emailVerified remains true. Redirect to /verify-email?status=success.
Outcome: Acheteur’s email address permanently changed.
5. Change Password (Actor: acheteur)
PUT /acheteur/profile/password accepts { currentPassword, newPassword }.
Rate limit: 10 requests per minute, keyed on acheteurId.
Validates current password against passwordHash (Google-only accounts blocked with 401). In a Prisma transaction:
- New bcrypt hash stored.
- All
acheteur_refresh_tokensfor this acheteur deleted.
Outcome: { data: { message: "Mot de passe modifié. Veuillez vous reconnecter." } }. Acheteur must re-authenticate.
Error States
- Google-only account (no passwordHash) tries email or password change → 401
UNAUTHORIZED— “Mot de passe incorrect.” / “Mot de passe actuel incorrect.” - New email same as current → 400
SAME_EMAIL - New email already taken → 409
CONFLICT - Email send failure on email change → 503
EMAIL_SEND_FAILED - Email change token: pending email mismatch → redirect
?status=invalid - Account not found (race condition / deleted) → 404
NOT_FOUND
Related Processes
- registration-email-verification —
verify-emailendpoint handles both initial verification (purpose=email_verify) and email change (purpose=email_change) - google-oauth — Google-only accounts cannot use email or password change flows
- account-deletion — for full account removal