Acheteur Registration & Email Verification
Summary
An acheteur creates an account by submitting their name, email, and password. The system sends a verification email with a 48-hour deadline. Until the email is verified, the account is in a limited state — login is permitted but certain features require emailVerified: true. If the 48-hour deadline passes without verification, the account is considered stale and can be overwritten by a new registration attempt for the same email. On success, an access token and a refresh token cookie are issued immediately, and a Slack notification is fired.
State Diagram
stateDiagram-v2 state "Compte non vérifié" as Unverified state "Vérification en attente" as PendingVerification state "Compte vérifié" as Verified state "Délai expiré" as Expired [*] --> Unverified: acheteur registers (new email) [*] --> Unverified: acheteur registers (stale email — overwrite) Unverified --> PendingVerification: verification email sent PendingVerification --> Verified: acheteur clicks verify link (valid, within 48h) PendingVerification --> Expired: 48h deadline passes Expired --> Unverified: new registration attempt overwrites row Verified --> [*]: account fully active
Steps
1. Submit Registration (Actor: acheteur)
Client POSTs { email, password, firstName, lastName, phone } to POST /acheteur/auth/register.
Rate limit: 3 requests per hour per IP.
Outcome: Request reaches the handler.
2. Duplicate / Stale Account Check (Actor: system)
The system looks up the email:
- If a verified account exists → 409
CONFLICT. - If an unverified account within deadline exists → 409
VERIFICATION_PENDING. - If an expired unverified account exists → the row is atomically overwritten (refresh tokens deleted, account fields reset).
- If no account exists → a new row is created.
The emailVerifyDeadline is set to now + 48h.
Outcome: Acheteur row exists (new or recycled).
3. Send Verification Email (Actor: system)
A signed JWT (purpose: "email_verify", expires in 48h) is embedded in the verify URL via buildVerifyUrl(). The email is sent via Resend using the acheteur-verify template.
On email send failure:
- The acheteur row is kept (analytics value).
- A Slack alert is posted to the sentry-notif channel.
- The API returns 503
EMAIL_SEND_FAILED.
Outcome: Verification email delivered, or 503 returned.
4. Issue Tokens (Actor: system)
A 15-minute JWT access token is signed with ACHETEUR_JWT_SECRET. A 7-day opaque refresh token is generated, SHA-256 hashed, stored in acheteur_refresh_tokens, and set as an httpOnly cookie (acheteurRefreshToken).
A Slack notification is fired via notifyAcheteurRegistered (fire-and-forget, errors swallowed).
Outcome: 201 response with { acheteur, accessToken }, refresh token cookie set.
5. Click Verification Link (Actor: acheteur)
Acheteur clicks the link from their email. The browser hits GET /acheteur/auth/verify-email?token=....
The system verifies the JWT:
- Expired → redirect to
/verify-email?status=expired. - Invalid / wrong purpose → redirect to
/verify-email?status=invalid. - Valid →
emailVerifiedset totrue→ redirect to/verify-email?status=success.
Outcome: Acheteur row has emailVerified: true.
6. Resend Verification (Actor: acheteur)
If the acheteur did not receive the email, they can request a resend via POST /acheteur/auth/resend-verification (requires Bearer token). Rate limited to 1 request per 5 minutes. Re-uses the existing emailVerifyDeadline or sets a new 48h deadline if none exists.
Outcome: Verification email resent, or 503 on email failure.
Error States
- Email already verified → 409
CONFLICT— “Cet email est déjà utilisé.” - Unverified account within deadline → 409
VERIFICATION_PENDING— “Une inscription est déjà en cours pour cet email.” - Email send failure → 503
EMAIL_SEND_FAILED— instructs user to contact support. - Token expired → redirect
?status=expired - Token invalid / wrong purpose → redirect
?status=invalid - Resend on already-verified account (no pendingEmail) → 400
ALREADY_VERIFIED
Related Processes
- google-oauth — alternative registration path, bypasses email verification requirement
- password-recovery — depends on a verified account existing
- profile-management — email change re-uses the verify-email token flow
- account-deletion — terminates the lifecycle started here