Flux d'execution

Diagrammes de sequence des flux metier principaux - login, registration, refresh token, audit

Flux d'execution

Flux de connexion (Login)

Cas standard (sans 2FA)

mermaid
sequenceDiagram
    participant C as Client
    participant E as LoginEndpoint
    participant M as MediatR
    participant V as ValidationBehavior
    participant A as AuditCommandBehavior
    participant H as LoginCommandHandler
    participant UM as UserManager
    participant SM as SignInManager
    participant TB as AuthTokenResponseBuilder
    participant SS as SessionService
    participant DB as IdentityContext

    C->>E: POST /api/v1/identity/auth/login
    Note right of C: {email, password, deviceId}
    E->>M: Send(LoginCommand)
    M->>V: Pipeline: Validation
    V->>V: Valider email, password, deviceId
    V->>A: Pipeline: Audit
    A->>H: Handle(command, ct)

    H->>UM: FindByEmailAsync(normalizedEmail)
    UM-->>H: User

    alt Utilisateur non trouve
        H-->>E: UnauthorizedError
    end

    H->>SM: CheckPasswordSignInAsync(user, password, lockout: true)
    SM-->>H: SignInResult

    alt Lockout
        H-->>E: AccountLockedError(lockoutEnd)
    end

    alt Mot de passe invalide
        H-->>E: UnauthorizedError
    end

    alt Compte inactif ou email non confirme
        H-->>E: UnauthorizedError
    end

    H->>TB: BuildAsync(user, device, ct)
    TB->>SS: CreateSessionAsync(sessionData)
    SS->>DB: Persister UserSession
    TB-->>H: AuthTokensResponse

    H->>UM: UpdateAsync(user)
    Note right of H: User.RecordLogin() met a jour LastLoginAt

    H-->>A: AuthTokensResponse
    A->>A: Emettre AuditEventV1 (success)
    A-->>E: AuthTokensResponse
    E-->>C: 200 OK {accessToken, refreshToken, user}

Cas avec 2FA active

mermaid
sequenceDiagram
    participant C as Client
    participant H as LoginCommandHandler
    participant TF as TwoFactorService
    participant TC as TwoFactorChallengeService

    C->>H: LoginCommand (email, password, deviceId)
    Note right of H: Apres verification du mot de passe...

    H->>TF: IsTwoFactorEnabledAsync(userId)
    TF-->>H: true

    H->>TC: CreateChallengeAsync(userId)
    TC-->>H: challengeToken

    H-->>C: TwoFactorRequiredError(challengeToken)
    Note right of C: Le client doit maintenant<br/>appeler /auth/2fa/login

    C->>H: Login2FaCommand (challengeToken, code, deviceId)
    H->>TC: ValidateAndConsumeAsync(challengeToken)
    TC-->>H: userId

    H->>TF: ValidateTwoFactorAsync(userId, code)
    TF-->>H: true

    Note right of H: Construction des tokens...
    H-->>C: 200 OK {accessToken, refreshToken, user}

Flux d'inscription (Register)

mermaid
sequenceDiagram
    participant C as Client
    participant E as RegisterEndpoint
    participant M as MediatR
    participant V as ValidationBehavior
    participant H as RegisterCommandHandler
    participant RS as UserRegistrationService
    participant UM as UserManager
    participant TB as AuthTokenResponseBuilder
    participant MQ as MediatR (Notification)
    participant MSG as SendEmailCommandHandler
    participant SMTP as SMTP Server

    C->>E: POST /api/v1/identity/auth/register
    Note right of C: {email, password, confirmPassword, deviceId}
    E->>M: Send(RegisterCommand)
    M->>V: Validation
    V->>V: Valider email, password, confirmPassword, deviceId
    V->>H: Handle(command, ct)

    H->>RS: CreateUserWithPasswordAsync(profile, password)

    RS->>UM: FindByEmailAsync(normalizedEmail)

    alt Email deja utilise
        RS-->>H: DuplicateEmail
        H-->>C: 409 Conflict
    end

    RS->>UM: CreateAsync(user, password)
    UM-->>RS: IdentityResult

    alt Mot de passe trop faible
        RS-->>H: WeakPassword(errors)
        H-->>C: 400 Bad Request
    end

    RS->>UM: AddToRoleAsync(user, "User")
    RS-->>H: Success(user, roles)

    H->>TB: BuildAsync(user, device, ct)
    Note right of TB: Cree la session + genere les tokens
    TB-->>H: AuthTokensResponse

    H-->>C: 200 OK {accessToken, refreshToken, user}

    Note over MQ,SMTP: Notification asynchrone (meme processus)
    MQ->>MSG: SendConfirmationEmailOnUserRegistered
    MSG->>MSG: Generer OTP
    MSG->>SMTP: Envoyer email de confirmation

Flux de rafraichissement de token (Refresh Token)

Cas standard (rotation reussie)

mermaid
sequenceDiagram
    participant C as Client
    participant E as RefreshTokenEndpoint
    participant H as RefreshTokenCommandHandler
    participant SR as SessionReader
    participant SW as SessionWriter
    participant TS as TokenService
    participant UM as UserManager
    participant DB as IdentityContext

    C->>E: POST /api/v1/identity/auth/refresh
    Note right of C: {refreshToken, deviceId}
    E->>H: Handle(RefreshTokenCommand, ct)

    H->>H: HashedToken.FromRaw(refreshToken, pepper)
    Note right of H: Hash HMAC-SHA256 du token recu

    H->>SR: FindByRefreshTokenHashAsync(tokenHash)
    SR-->>H: UserSession

    H->>H: Verifier: session non revoquee
    H->>H: Verifier: session non expiree
    H->>H: Verifier: binding IP/DeviceId

    H->>UM: FindByIdAsync(session.UserId)
    UM-->>H: User (actif)

    H->>TS: GenerateAccessToken(claims)
    TS-->>H: newAccessToken

    H->>TS: GenerateRefreshToken()
    TS-->>H: newRefreshToken

    H->>H: HashedToken.FromRaw(newRefreshToken, pepper)

    H->>SW: RotateTokenAsync(session, newHash)
    Note right of SW: Ancien hash -> PreviousRefreshTokenHash<br/>Nouveau hash -> RefreshTokenHash
    SW->>DB: SaveChangesAsync()
    SW-->>H: true

    H-->>C: 200 OK {newAccessToken, newRefreshToken, user}

Detection de replay

mermaid
sequenceDiagram
    participant A as Attaquant
    participant L as Client legitime
    participant H as RefreshTokenHandler
    participant SR as SessionReader
    participant SW as SessionWriter
    participant ED as EventDispatcher

    Note over A,L: Scenario: l'attaquant a vole un refresh token

    L->>H: RefreshTokenCommand(token_v1)
    H->>SR: FindByRefreshTokenHashAsync(hash_v1)
    SR-->>H: Session (RefreshTokenHash = hash_v1)
    H->>SW: RotateTokenAsync(session, hash_v2)
    Note right of SW: hash_v1 -> PreviousRefreshTokenHash<br/>hash_v2 -> RefreshTokenHash
    H-->>L: 200 OK {token_v2}

    Note over A: L'attaquant tente d'utiliser<br/>le token vole (token_v1)
    A->>H: RefreshTokenCommand(token_v1)
    H->>SR: FindByRefreshTokenHashAsync(hash_v1)
    SR-->>H: null (plus le token courant)

    H->>SR: FindByPreviousTokenHashAsync(hash_v1)
    SR-->>H: Session trouvee!
    Note right of H: Token precedent reutilise<br/>= REPLAY DETECTE

    H->>H: Verifier grace period (30s)
    Note right of H: Si > 30s depuis rotation

    H->>ED: TokenReplayDetectedEvent
    H->>SW: RevokeAllUserSessionsAsync(userId, ReplayDetected)
    Note right of SW: TOUTES les sessions revoquees<br/>Le client legitime devra se reconnecter

    H-->>A: 401 Unauthorized

Flux d'audit

mermaid
sequenceDiagram
    participant C as Client HTTP
    participant MW as AuditHttpMiddleware
    participant EP as Endpoint
    participant AB as AuditCommandBehavior
    participant H as Handler
    participant AE as AuditEmitter
    participant CE as ConsumeAuditEvent
    participant AW as AuditLogWriter
    participant DB as AuditContext
    participant SH as AuditHub (SignalR)
    participant AD as Admin Dashboard

    C->>MW: HTTP Request
    MW->>MW: Capturer contexte (IP, UA, corrID)
    MW->>EP: Passer au suivant

    EP->>AB: MediatR Pipeline
    AB->>H: Handler metier
    H-->>AB: Resultat
    AB->>AE: EmitAsync(AuditEventV1 - command)
    AE->>CE: MediatR Notification

    EP-->>MW: HTTP Response

    MW->>AE: EmitAsync(AuditEventV1 - http)
    AE->>CE: MediatR Notification

    Note over CE,AD: Pour chaque notification

    CE->>AW: WriteAsync(auditEvent)
    AW->>DB: AddAsync + SaveChangesAsync
    AW->>SH: SendAsync("AuditLogInserted", summary)
    SH->>AD: Temps reel via WebSocket

    Note right of AD: L'admin voit les logs<br/>en temps reel

Flux d'envoi d'email

mermaid
sequenceDiagram
    participant ID as Module Identity
    participant M as MediatR
    participant PB as MessagingPersistenceBehavior
    participant EH as SendEmailCommandHandler
    participant TR as MjmlTemplateRenderer
    participant ES as SmtpEmailSender
    participant DB as MessagingContext
    participant SMTP as Serveur SMTP

    ID->>M: Send(SendEmailCommand(EmailMessage))
    M->>PB: Pipeline Behavior

    PB->>DB: Creer SentMessage (status: Pending)
    PB->>DB: SaveChangesAsync()

    PB->>EH: Handle(SendEmailCommand, ct)
    EH->>TR: Render("confirm-email", variables)
    TR-->>EH: HTML responsive

    EH->>ES: SendAsync(to, subject, htmlBody, replyTo)
    ES->>SMTP: Envoyer email via MailKit

    alt Envoi reussi
        SMTP-->>ES: OK
        ES-->>EH: OK
        EH-->>PB: Unit.Value
        PB->>PB: SentMessage.Status = Sent
        PB->>PB: SentMessage.SentAt = UtcNow
    else Envoi echoue
        SMTP-->>ES: Erreur
        ES-->>EH: Exception
        EH-->>PB: Exception
        PB->>PB: SentMessage.Status = Failed
        PB->>PB: SentMessage.ErrorMessage = exception.Message
    end

    PB->>DB: SaveChangesAsync()
    Note right of DB: Statut final toujours persiste

Flux d'authentification sociale

mermaid
sequenceDiagram
    participant C as Client
    participant H as SocialLoginHandler
    participant STV as SocialTokenValidator
    participant UM as UserManager
    participant RS as UserRegistrationService
    participant PL as ProviderLinkingService
    participant TB as AuthTokenResponseBuilder

    C->>H: SocialLoginCommand (provider, token, deviceId)
    H->>STV: ValidateAsync(provider, token)
    STV-->>H: SocialUserInfo (email, name, picture)

    H->>UM: FindByLoginAsync(provider, providerId)

    alt Compte deja lie
        UM-->>H: User existant
        H->>TB: BuildAsync(user, device)
        H-->>C: {tokens, isNewAccount: false}
    else Premier login social
        H->>UM: FindByEmailAsync(email)

        alt Email existe deja (compte local)
            UM-->>H: User existant
            H->>PL: LinkAsync(user, provider, providerKey)
            H->>TB: BuildAsync(user, device)
            H-->>C: {tokens, isNewAccount: false}
        else Nouvel utilisateur
            H->>RS: CreateUserWithExternalProviderAsync(profile)
            RS-->>H: Success(newUser, roles)
            H->>PL: LinkAsync(newUser, provider, providerKey)
            H->>TB: BuildAsync(newUser, device)
            H-->>C: {tokens, isNewAccount: true}
        end
    end