Skip to content

Commit fda853e

Browse files
committed
Update AccountService.cs
1 parent 56e9dfd commit fda853e

1 file changed

Lines changed: 34 additions & 7 deletions

File tree

API/Services/Account/AccountService.cs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,37 @@ public async Task<OneOf<Success<User>, AccountWithEmailOrUsernameExists>> Create
144144

145145
await using var tx = await _db.Database.BeginTransactionAsync();
146146

147+
string? activationToken = null;
148+
Guid userId;
149+
147150
try
148151
{
149152
var user = new User
150153
{
151154
Id = Guid.CreateVersion7(),
152155
Name = username,
153156
Email = email,
154-
PasswordHash = null, // OAuth-only account
155-
ActivatedAt = DateTime.UtcNow // no activation flow
157+
PasswordHash = null,
158+
ActivatedAt = isEmailTrusted ? DateTime.UtcNow : null
156159
};
157160

158161
_db.Users.Add(user);
159162
await _db.SaveChangesAsync();
160163

164+
// If email isn't trusted, create an activation request (email verification)
165+
if (!isEmailTrusted)
166+
{
167+
activationToken = CryptoUtils.RandomString(AuthConstants.GeneratedTokenLength);
168+
169+
user.UserActivationRequest = new UserActivationRequest
170+
{
171+
UserId = user.Id,
172+
TokenHash = HashingUtils.HashToken(activationToken)
173+
};
174+
175+
await _db.SaveChangesAsync();
176+
}
177+
161178
// Link external identity
162179
_db.UserOAuthConnections.Add(new UserOAuthConnection
163180
{
@@ -167,27 +184,37 @@ public async Task<OneOf<Success<User>, AccountWithEmailOrUsernameExists>> Create
167184
DisplayName = providerAccountName
168185
});
169186

187+
// Tidy-up if clock skew makes CreatedAt > ActivatedAt (trusted case only)
188+
if (user.ActivatedAt is not null && user.CreatedAt > user.ActivatedAt)
189+
{
190+
user.ActivatedAt = user.CreatedAt;
191+
}
192+
170193
await _db.SaveChangesAsync();
171194

195+
userId = user.Id;
196+
172197
await tx.CommitAsync();
173198

174-
// Ensure ActivatedAt <= CreatedAt (optional monotonic tidy-up)
175-
if (user.CreatedAt > user.ActivatedAt)
199+
// Send verification email only after successful commit
200+
if (!isEmailTrusted && activationToken is not null)
176201
{
177-
user.ActivatedAt = user.CreatedAt;
178-
await _db.SaveChangesAsync();
202+
await _emailService.VerifyEmail(
203+
new Contact(email, username),
204+
new Uri(_frontendConfig.BaseUrl, $"/#/account/activate/{userId}/{activationToken}")
205+
);
179206
}
180207

181208
return new Success<User>(user);
182209
}
183210
catch (DbUpdateException ex) when (ex.InnerException is PostgresException { SqlState: "23505" })
184211
{
185-
// Unique constraint hit: either username/email already exists, or (provider, externalId) is already linked.
186212
await tx.RollbackAsync();
187213
return new AccountWithEmailOrUsernameExists();
188214
}
189215
}
190216

217+
191218
public async Task<bool> TryActivateAccountAsync(string secret, CancellationToken cancellationToken = default)
192219
{
193220
var hash = HashingUtils.HashToken(secret);

0 commit comments

Comments
 (0)