Files
the-biergarten-app/docs/web/diagrams/auth/login.mmd
Aaron Po 30317326ce Refactor documentation and diagrams for web/pipeline. (#272)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> for assistance with diagram creation and documentation prose updates.
2026-09-02 01:58:40 -04:00

94 lines
3.9 KiB
Plaintext

sequenceDiagram
actor User
participant API as API Controller
participant Mediator as MediatR pipeline<br/>(ValidationBehavior)
box Handler Layer
participant LoginHandler
participant UserMgr as UserManager(ApplicationUser)
participant TokenSvc as TokenService
end
box Infrastructure Layer
participant Argon2 as Argon2PasswordHasher
participant JWT as JWT Infrastructure
end
box Repository / Cross-slice
participant UserStore as DapperUserStore<br/>(IUserStore/IUserPasswordStore/IUserEmailStore)
end
participant DB as SQL Server
User->>API: POST /api/auth/login {username, password}
activate API
API->>Mediator: Send(LoginCommand)
activate Mediator
Note right of Mediator: ValidationBehavior runs LoginValidator — username and password must both be non-empty
alt Validation fails
Mediator->>API: throws FluentValidation.ValidationException
API->>User: 400 Bad Request {message, errors}
else Validation succeeds
Mediator->>LoginHandler: Handle(command)
activate LoginHandler
LoginHandler->>UserMgr: FindByNameAsync(username)
activate UserMgr
UserMgr->>UserStore: FindByNameAsync(username)
activate UserStore
UserStore->>DB: SELECT ... FROM UserAccount WHERE Username = @Username
activate DB
DB-->>UserStore: UserAccount row or null
deactivate DB
UserStore->>UserStore: Hydrate (active UserCredential.Hash, UserVerification → EmailConfirmed)
UserStore-->>UserMgr: ApplicationUser or null
deactivate UserStore
UserMgr-->>LoginHandler: ApplicationUser or null
deactivate UserMgr
alt User not found
LoginHandler->>Mediator: throw UnauthorizedException "Invalid username or password."
Mediator->>API: propagate
API->>User: 401 Unauthorized
else User found
LoginHandler->>UserMgr: CheckPasswordAsync(user, password)
activate UserMgr
Note right of UserMgr: HasPasswordAsync short-circuits to false (no active credential hydrated) without calling the hasher — otherwise delegates to Argon2PasswordHasher
UserMgr->>Argon2: VerifyHashedPassword(user, user.PasswordHash, password)
activate Argon2
Note right of Argon2: Parse stored PHC string ($argon2id$v=19$m=...,t=...,p=...$salt$hash), rehash password with the embedded salt/parameters, constant-time compare (CryptographicOperations.FixedTimeEquals)
Argon2-->>UserMgr: Success/Failed
deactivate Argon2
UserMgr-->>LoginHandler: true/false
deactivate UserMgr
alt Password invalid
LoginHandler->>Mediator: throw UnauthorizedException
Mediator->>API: propagate
API->>User: 401 Unauthorized
else Password valid
LoginHandler->>TokenSvc: GenerateAccessToken(user)
activate TokenSvc
TokenSvc->>JWT: GenerateJwt(...)
activate JWT
JWT-->>TokenSvc: Access Token
deactivate JWT
TokenSvc-->>LoginHandler: Access Token
deactivate TokenSvc
LoginHandler->>TokenSvc: GenerateRefreshToken(user)
activate TokenSvc
TokenSvc->>JWT: GenerateJwt(...)
activate JWT
JWT-->>TokenSvc: Refresh Token
deactivate JWT
TokenSvc-->>LoginHandler: Refresh Token
deactivate TokenSvc
LoginHandler-->>Mediator: LoginPayload(userAccountId, username, refreshToken, accessToken)
deactivate LoginHandler
Mediator-->>API: LoginPayload
deactivate Mediator
API->>User: 200 OK {message: "Logged in successfully.", payload: {...}}
end
end
end
deactivate API