mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> for assistance with diagram creation and documentation prose updates.
85 lines
3.8 KiB
Plaintext
85 lines
3.8 KiB
Plaintext
sequenceDiagram
|
|
actor User
|
|
participant API as API Controller
|
|
participant Mediator as MediatR pipeline<br/>(ValidationBehavior)
|
|
box Handler Layer
|
|
participant UpdatePasswordHandler
|
|
participant UserMgr as UserManager(ApplicationUser)
|
|
end
|
|
box Infrastructure Layer
|
|
participant Argon2 as Argon2PasswordHasher
|
|
end
|
|
box Repository / Cross-slice
|
|
participant UserStore as DapperUserStore<br/>(IUserStore/IUserPasswordStore/IUserEmailStore)
|
|
end
|
|
participant DB as SQL Server
|
|
|
|
User->>API: PATCH /api/auth/password {currentPassword, newPassword} (requires Bearer access token)
|
|
activate API
|
|
API->>Mediator: Send(UpdatePasswordCommand(GetAuthenticatedUserId(), currentPassword, newPassword))
|
|
activate Mediator
|
|
Note right of Mediator: ValidationBehavior runs UpdatePasswordValidator (same password strength rules as registration)
|
|
|
|
alt Validation fails
|
|
Mediator->>API: throws FluentValidation.ValidationException
|
|
API->>User: 400 Bad Request
|
|
else Validation succeeds
|
|
Mediator->>UpdatePasswordHandler: Handle(command)
|
|
activate UpdatePasswordHandler
|
|
|
|
UpdatePasswordHandler->>UserMgr: FindByIdAsync(userAccountId)
|
|
activate UserMgr
|
|
UserMgr->>UserStore: FindByIdAsync(userAccountId)
|
|
UserStore-->>UserMgr: ApplicationUser or null
|
|
UserMgr-->>UpdatePasswordHandler: ApplicationUser or null
|
|
deactivate UserMgr
|
|
|
|
alt Account not found
|
|
UpdatePasswordHandler->>Mediator: throw NotFoundException
|
|
Mediator->>API: propagate
|
|
API->>User: 404 Not Found
|
|
else Account found
|
|
UpdatePasswordHandler->>UserMgr: ChangePasswordAsync(user, currentPassword, newPassword)
|
|
activate UserMgr
|
|
UserMgr->>Argon2: VerifyHashedPassword(user, user.PasswordHash, currentPassword)
|
|
activate Argon2
|
|
Argon2-->>UserMgr: Success/Failed
|
|
deactivate Argon2
|
|
|
|
alt Current password mismatch
|
|
UserMgr-->>UpdatePasswordHandler: IdentityResult.Failed (PasswordMismatch)
|
|
else Current password matches
|
|
UserMgr->>Argon2: HashPassword(user, newPassword)
|
|
activate Argon2
|
|
Argon2-->>UserMgr: new hash
|
|
deactivate Argon2
|
|
UserMgr->>UserStore: SetPasswordHashAsync(user, newHash)
|
|
activate UserStore
|
|
UserStore->>DB: UPDATE UserCredential SET IsRevoked = 1, RevokedAt = GETDATE() WHERE UserAccountId = @Id AND IsRevoked = 0
|
|
UserStore->>DB: INSERT INTO UserCredential (UserAccountId, Hash)
|
|
Note right of UserStore: Revoke-then-insert runs inside one DbTransaction (credential rotation)
|
|
UserStore-->>UserMgr: (void)
|
|
deactivate UserStore
|
|
UserMgr-->>UpdatePasswordHandler: IdentityResult.Success
|
|
end
|
|
deactivate UserMgr
|
|
|
|
alt Current password is incorrect
|
|
UpdatePasswordHandler->>Mediator: throw UnauthorizedException "Current password is incorrect"
|
|
Mediator->>API: propagate
|
|
API->>User: 401 Unauthorized
|
|
else !result.Succeeded (other failure)
|
|
UpdatePasswordHandler->>Mediator: throw ConflictException
|
|
Mediator->>API: propagate
|
|
API->>User: 409 Conflict
|
|
else result.Succeeded
|
|
UpdatePasswordHandler-->>Mediator: UpdatePasswordPayload(user.Id, DateTime.UtcNow)
|
|
deactivate UpdatePasswordHandler
|
|
Mediator-->>API: UpdatePasswordPayload
|
|
deactivate Mediator
|
|
API->>User: 200 OK {message: "Password updated successfully.", payload: {...}}
|
|
end
|
|
end
|
|
end
|
|
deactivate API
|