Skip to content

Commit 239e721

Browse files
committed
refactor: port admin activation resend to the email outbox
develop replaced direct IEmailService sends with the durable EmailOutbox, where the activation token is minted by the consumer at send time rather than by the caller. Re-express ResendActivationEmailAsync in those terms: seed a token hash when the activation request is missing and enqueue EmailOutboxMessage.ForAccountActivation instead of calling _emailService with a self-minted token. The outbox coalesce key supersedes any pending activation for the user, so the explicit EmailSendAttempts bookkeeping is no longer needed.
1 parent 6d49577 commit 239e721

1 file changed

Lines changed: 8 additions & 14 deletions

File tree

‎API/Services/Account/AccountService.cs‎

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -274,29 +274,23 @@ public async Task<OneOf<Success, AccountAlreadyActivated, AccountDeactivated, No
274274
if (user.UserDeactivation is not null) return new AccountDeactivated();
275275
if (user.ActivatedAt is not null) return new AccountAlreadyActivated();
276276

277-
var token = CryptoUtils.RandomAlphaNumericString(AuthConstants.GeneratedTokenLength);
278-
var tokenHash = HashingUtils.HashToken(token);
279-
280277
if (user.UserActivationRequest is null)
281278
{
282-
// Legacy / OAuth-trusted accounts may never have had an activation request created.
279+
// Legacy / OAuth-trusted accounts may never have had an activation request created. The real
280+
// token is minted by the outbox consumer at send time, so only seed the hash here.
283281
user.UserActivationRequest = new UserActivationRequest
284282
{
285283
UserId = user.Id,
286-
TokenHash = tokenHash,
287-
EmailSendAttempts = 1
284+
TokenHash = SeedTokenHash()
288285
};
289286
}
290-
else
291-
{
292-
user.UserActivationRequest.TokenHash = tokenHash;
293-
user.UserActivationRequest.EmailSendAttempts++;
294-
}
295287

296-
await _db.SaveChangesAsync(cancellationToken);
288+
// Durably enqueue the activation email. Its coalesce key supersedes any still-pending activation
289+
// for this user, and the consumer re-mints the token on send, invalidating previously sent links.
290+
_db.EmailOutbox.Add(EmailOutboxMessage.ForAccountActivation(user.Id, user.Email, user.Name));
297291

298-
await _emailService.ActivateAccount(new Contact(user.Email, user.Name),
299-
new Uri(_frontendConfig.BaseUrl, $"/activate?token={token}"), cancellationToken);
292+
await _db.SaveChangesAsync(cancellationToken);
293+
await NotifyEmailOutboxAsync();
300294

301295
return new Success();
302296
}

0 commit comments

Comments
 (0)