From 809b9b0f725b43378d908a487c9d23f37d2c9829 Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Mon, 31 Aug 2026 22:03:39 -0400 Subject: [PATCH] Fix Argon2 hash-format claims and add the missing profile-creation step to the auth diagrams --- docs/web/diagrams/auth/delete-account.mmd | 2 +- docs/web/diagrams/auth/login.mmd | 2 +- docs/web/diagrams/auth/registration.mmd | 20 +++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/web/diagrams/auth/delete-account.mmd b/docs/web/diagrams/auth/delete-account.mmd index b80b396a..8467d2d5 100644 --- a/docs/web/diagrams/auth/delete-account.mmd +++ b/docs/web/diagrams/auth/delete-account.mmd @@ -37,7 +37,7 @@ sequenceDiagram UserMgr->>UserStore: DeleteAsync(user) activate UserStore UserStore->>DB: DELETE FROM UserAccount WHERE UserAccountID = @Id - Note right of UserStore: UserCredential/UserVerification/UserAvatar cascade on delete. Posts, comments, photos, and follows use ON DELETE NO ACTION — if any reference this account, the DELETE throws a foreign key violation (SQL error 547) + Note right of UserStore: UserCredential, UserVerification, and UserProfile cascade on delete (UserAvatar cascades transitively through UserProfile). Posts, comments, photos, and follows use ON DELETE NO ACTION — if any reference this account, the DELETE throws a foreign key violation (SQL error 547) alt Foreign key violation (dependent records exist) UserStore-->>UserMgr: IdentityResult.Failed (AccountHasDependentRecords) diff --git a/docs/web/diagrams/auth/login.mmd b/docs/web/diagrams/auth/login.mmd index e6d87273..06de7dcc 100644 --- a/docs/web/diagrams/auth/login.mmd +++ b/docs/web/diagrams/auth/login.mmd @@ -55,7 +55,7 @@ sequenceDiagram 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: Split stored "salt:hash", rehash password with same salt, constant-time compare (CryptographicOperations.FixedTimeEquals) + 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 diff --git a/docs/web/diagrams/auth/registration.mmd b/docs/web/diagrams/auth/registration.mmd index c8f58484..d5ae2494 100644 --- a/docs/web/diagrams/auth/registration.mmd +++ b/docs/web/diagrams/auth/registration.mmd @@ -15,6 +15,7 @@ sequenceDiagram end box rgb(219,238,221) Repository / Cross-slice participant UserStore as DapperUserStore
(IUserStore/IUserPasswordStore/IUserEmailStore) + participant ProfileHandler as CreateUserProfileHandler
(Profile slice) participant EmailDispatcher as EmailDispatcher
(Features.Emails) end participant DB as SQL Server @@ -39,8 +40,8 @@ sequenceDiagram activate UserMgr UserMgr->>Argon2: HashPassword(user, password) activate Argon2 - Note right of Argon2: Argon2id — salt 16 bytes, memory 64MB, iterations 4, parallelism = CPU count, hash output 32 bytes - Argon2-->>UserMgr: "base64(salt):base64(hash)" + Note right of Argon2: Argon2id — salt 16 bytes, memory 64MB, iterations 4, parallelism 2 (fixed, to avoid thread-pool exhaustion), hash output 32 bytes + Argon2-->>UserMgr: "$argon2id$v=19$m=65536,t=4,p=2$<salt>$<hash>" (PHC string) deactivate Argon2 UserMgr->>UserStore: SetPasswordHashAsync(user, hash) @@ -84,6 +85,19 @@ sequenceDiagram Mediator->>API: propagate API->>User: 409 Conflict "Username or email already exists" else result.Succeeded + RegHandler->>Mediator: Send(CreateUserProfileCommand(user.Id, "")) + activate Mediator + Mediator->>ProfileHandler: Handle(command) + activate ProfileHandler + ProfileHandler->>DB: INSERT INTO UserProfile (UserAccountId, Biography) + activate DB + DB-->>ProfileHandler: UserProfileId + deactivate DB + ProfileHandler-->>Mediator: UserProfileId + deactivate ProfileHandler + Mediator-->>RegHandler: UserProfileId + deactivate Mediator + RegHandler->>TokenSvc: GenerateAccessToken(user) activate TokenSvc TokenSvc->>JWT: GenerateJwt(userId, username, expiry) @@ -117,7 +131,7 @@ sequenceDiagram RegHandler->>Mediator: Send(SendRegistrationEmailCommand) (Shared.Application cross-slice contract) activate Mediator - Note right of Mediator: Routed to Features.Emails' SendRegistrationEmailHandler — the only cross-slice call in the backend + Note right of Mediator: Routed to Features.Emails' SendRegistrationEmailHandler — the second of two cross-slice dispatches in this flow (profile creation is the first) Mediator->>EmailDispatcher: SendRegistrationEmailAsync(firstName, email, confirmationToken) activate EmailDispatcher