Pro Registration

Summary

A real-estate professional registers an account by providing their personal details, agency info, SIRET, and carte T (CPI format). The system validates both identifiers synchronously, hashes the password, issues tokens, and fires a Slack notification to admins so the pending CNI review is visible immediately.

State Diagram

stateDiagram-v2
    state "Validation en cours" as Validating
    state "Rejeté (SIRET invalide)" as Rejected_SIRET
    state "Rejeté (carte T invalide)" as Rejected_CarteT
    state "Rejeté (email existant)" as Rejected_Conflict
    state "Compte créé" as Created
    state "Tokens émis" as TokensIssued
    state "Slack notifié" as SlackNotified

    [*] --> Validating: pro submits registration form
    Validating --> Rejected_SIRET: invalid SIRET (Luhn)
    Validating --> Rejected_CarteT: invalid carte T format
    Validating --> Rejected_Conflict: email already exists
    Validating --> Created: all checks pass
    Created --> TokensIssued: access + refresh tokens minted
    TokensIssued --> SlackNotified: notifyProRegistration (fire-and-forget)
    SlackNotified --> [*]: 201 returned to client
    Rejected_SIRET --> [*]
    Rejected_CarteT --> [*]
    Rejected_Conflict --> [*]

Steps

1. Submit Registration Form (Actor: pro)

The pro sends a POST request to /pro/auth/register with the full registration payload. Rate limited to 3 requests per hour per IP.

Required fields: email, password, firstName, lastName, phone, siret, carteT, address, city, postalCode. Optional: rcp, agencyName, jobTitle, latitude, longitude.

Triggers: Pro fills out registration form and submits Outcome: Request reaches the API handler

2. Validate SIRET and Carte T (Actor: system)

isValidSiret(siret) runs a Luhn check on the 14-digit SIRET. isValidCarteT(carteT) validates the CPI format (CPI XXXX YYYY 000 ZZZ ZZZ). Email is normalised to lowercase before the uniqueness check.

Triggers: Request passes JSON schema validation Outcome: 400 on failure; proceeds on pass

3. Create Pro Record (Actor: system)

Password is hashed via hashPassword. A Pro row is inserted. The isActive field defaults to true; cniVerifiedAt defaults to null.

Triggers: All validations pass Outcome: Pro record persisted in DB

4. Issue Tokens (Actor: system)

An access token (15 min, HS256, signed with PRO_JWT_SECRET) and a refresh token (7 days, 96-char opaque hex, SHA-256 hashed in DB) are issued. The refresh token is set as an httpOnly cookie (proRefreshToken, sameSite: lax). Device type is recorded as "web" for the initial registration token.

Triggers: Pro record created Outcome: ProRefreshToken row inserted; tokens returned to client

5. Slack Notification (Actor: system)

notifyProRegistration(pro) posts to #pro-registration (channel env var SLACK_PRO_REGISTRATION_CHANNEL_ID) with the pro’s name, email, agency, SIRET, and a link to their admin fiche. The Slack message ts is stored in pro.slackThreadTs for future thread replies (CNI upload, AI analysis). This is fire-and-forget — errors are logged but do not affect the response.

Triggers: Pro record created Outcome: Admin team alerted; slackThreadTs persisted

Error States

  • Invalid SIRET → 400 INVALID_SIRET
  • Invalid carte T format → 400 INVALID_CARTE_T
  • Email already registered → 409 CONFLICT (generic message, does not confirm email existence)
  • Slack notification failure → logged, not surfaced to client