Skip to content

Commit 77a11bc

Browse files
committed
Complete Identifier and Add New Tests
1 parent 588da38 commit 77a11bc

File tree

8 files changed

+481
-46
lines changed

8 files changed

+481
-46
lines changed

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Dialogs/IdentifierDialog.razor

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@
5454
</PagerContent>
5555
</MudDataGrid>
5656

57-
<MudExpansionPanels>
57+
<MudExpansionPanels Class="mt-2">
5858
<MudExpansionPanel Text="Add New Identifier" Expanded="false">
59-
<MudStack Spacing="2">
59+
<MudStack Row="true" Wrap="Wrap.Wrap" Spacing="2">
6060
<MudSelectExtended @bind-Value="_newIdentifierType" ItemCollection="@(Enum.GetValues<UserIdentifierType>())" Variant="Variant.Outlined" Label="Type" />
6161
<MudTextField @bind-Value="_newIdentifierValue" Variant="Variant.Outlined" Label="Value" />
62+
<MudSwitchM3 @bind-Value="_newIdentifierPrimary" Label="Primary" Color="Color.Primary" />
6263
<MudButton Color="Color.Primary" OnClick="AddNewIdentifier">Add</MudButton>
6364
</MudStack>
6465
</MudExpansionPanel>
@@ -75,6 +76,7 @@
7576
@code {
7677
private UserIdentifierType _newIdentifierType;
7778
private string? _newIdentifierValue;
79+
private bool _newIdentifierPrimary;
7880

7981
[CascadingParameter]
8082
private IMudDialogInstance MudDialog { get; set; } = default!;
@@ -113,9 +115,11 @@
113115
}
114116
else
115117
{
116-
Snackbar.Add("Failed to update identifier", Severity.Error);
118+
Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Failed to update identifier", Severity.Error);
117119
}
118120

121+
var getResult = await UAuthClient.Identifiers.GetMyIdentifiersAsync();
122+
_identifiers = getResult.Value?.ToList() ?? new List<UserIdentifierDto>();
119123
return DataGridEditFormAction.Close;
120124
}
121125

@@ -130,7 +134,8 @@
130134
AddUserIdentifierRequest request = new()
131135
{
132136
Type = _newIdentifierType,
133-
Value = _newIdentifierValue
137+
Value = _newIdentifierValue,
138+
IsPrimary = _newIdentifierPrimary
134139
};
135140

136141
var result = await UAuthClient.Identifiers.AddSelfAsync(request);

src/CodeBeam.UltimateAuth.Server/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ private static IServiceCollection AddUltimateAuthServerInternal(this IServiceCol
208208
services.TryAddScoped<IRefreshResponsePolicy, RefreshResponsePolicy>();
209209
services.TryAddSingleton<IAuthStore, InMemoryAuthStore>();
210210
services.TryAddScoped<ICurrentUser, HttpContextCurrentUser>();
211-
services.TryAddScoped<IIdentifierNormalizer, IdentifierNormalizer>();
211+
services.TryAddSingleton<IIdentifierNormalizer, IdentifierNormalizer>();
212212

213213
services.TryAddScoped<IHubCapabilities, HubCapabilities>();
214214

src/users/CodeBeam.UltimateAuth.Users.InMemory/Infrastructure/InMemoryUserSeedContributor.cs

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ public async Task SeedAsync(TenantKey tenant, CancellationToken ct = default)
4141
await SeedUserAsync(tenant, _ids.GetUserUserId(), "Standard User", "user", "user@ultimateauth.com", "9876543210", ct);
4242
}
4343

44-
private async Task SeedUserAsync(TenantKey tenant, UserKey userKey, string displayName, string primaryUsername,
45-
string primaryEmail, string primaryPhone, CancellationToken ct)
44+
private async Task SeedUserAsync(TenantKey tenant, UserKey userKey, string displayName, string username, string email, string phone, CancellationToken ct)
4645
{
4746
if (await _lifecycle.ExistsAsync(tenant, userKey, ct))
4847
return;
@@ -65,19 +64,23 @@ await _profiles.CreateAsync(tenant,
6564
CreatedAt = _clock.UtcNow
6665
}, ct);
6766

68-
await _identifiers.CreateAsync(tenant,
69-
new UserIdentifier
70-
{
71-
Id = Guid.NewGuid(),
72-
Tenant = tenant,
73-
UserKey = userKey,
74-
Type = UserIdentifierType.Username,
75-
Value = primaryUsername,
76-
NormalizedValue = _identifierNormalizer.Normalize(UserIdentifierType.Username, primaryUsername).Normalized,
77-
IsPrimary = true,
78-
IsVerified = true,
79-
CreatedAt = _clock.UtcNow
80-
}, ct);
67+
var usernameIdentifier = new UserIdentifier
68+
{
69+
Id = Guid.NewGuid(),
70+
Tenant = tenant,
71+
UserKey = userKey,
72+
Type = UserIdentifierType.Username,
73+
Value = username,
74+
NormalizedValue = _identifierNormalizer
75+
.Normalize(UserIdentifierType.Username, username)
76+
.Normalized,
77+
IsPrimary = true,
78+
IsVerified = true,
79+
CreatedAt = _clock.UtcNow
80+
};
81+
82+
await _identifiers.CreateAsync(tenant, usernameIdentifier, ct);
83+
await _identifiers.SetPrimaryAsync(usernameIdentifier.Id, _clock.UtcNow, ct);
8184

8285
await _identifiers.CreateAsync(tenant,
8386
new UserIdentifier
@@ -86,8 +89,10 @@ await _identifiers.CreateAsync(tenant,
8689
Tenant = tenant,
8790
UserKey = userKey,
8891
Type = UserIdentifierType.Email,
89-
Value = primaryEmail,
90-
NormalizedValue = _identifierNormalizer.Normalize(UserIdentifierType.Username, primaryEmail).Normalized,
92+
Value = email,
93+
NormalizedValue = _identifierNormalizer
94+
.Normalize(UserIdentifierType.Email, email)
95+
.Normalized,
9196
IsPrimary = true,
9297
IsVerified = true,
9398
CreatedAt = _clock.UtcNow
@@ -100,8 +105,10 @@ await _identifiers.CreateAsync(tenant,
100105
Tenant = tenant,
101106
UserKey = userKey,
102107
Type = UserIdentifierType.Phone,
103-
Value = primaryPhone,
104-
NormalizedValue = _identifierNormalizer.Normalize(UserIdentifierType.Username, primaryPhone).Normalized,
108+
Value = phone,
109+
NormalizedValue = _identifierNormalizer
110+
.Normalize(UserIdentifierType.Phone, phone)
111+
.Normalized,
105112
IsPrimary = true,
106113
IsVerified = true,
107114
CreatedAt = _clock.UtcNow

src/users/CodeBeam.UltimateAuth.Users.InMemory/Stores/InMemoryUserIdentifierStore.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ public Task CreateAsync(TenantKey tenant, UserIdentifier identifier, Cancellatio
9191

9292
identifier.Tenant = tenant;
9393

94+
if (identifier.IsPrimary)
95+
{
96+
foreach (var existing in _store.Values.Where(x =>
97+
x.Tenant == tenant &&
98+
x.UserKey == identifier.UserKey &&
99+
x.Type == identifier.Type &&
100+
x.IsPrimary &&
101+
!x.IsDeleted))
102+
{
103+
existing.IsPrimary = false;
104+
existing.UpdatedAt = identifier.CreatedAt;
105+
}
106+
}
107+
94108
_store[identifier.Id] = identifier;
95109

96110
return Task.CompletedTask;

src/users/CodeBeam.UltimateAuth.Users.Reference/Services/UserApplicationService.cs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,11 @@ public async Task<IReadOnlyList<UserIdentifierDto>> GetIdentifiersByUserAsync(Ac
228228
{
229229
var command = new GetUserIdentifierCommand(async innerCt =>
230230
{
231-
var identifier = await _identifierStore.GetAsync(context.ResourceTenant, type, value, innerCt);
231+
var normalized = _identifierNormalizer.Normalize(type, value);
232+
if (!normalized.IsValid)
233+
return null;
234+
235+
var identifier = await _identifierStore.GetAsync(context.ResourceTenant, type, normalized.Normalized, innerCt);
232236
return identifier is null ? null : UserIdentifierMapper.ToDto(identifier);
233237
});
234238

@@ -243,7 +247,9 @@ public async Task<bool> UserIdentifierExistsAsync(AccessContext context, UserIde
243247
if (!normalized.IsValid)
244248
return false;
245249

246-
var result = await _identifierStore.ExistsAsync(new IdentifierExistenceQuery(context.ResourceTenant, type, normalized.Normalized, scope), innerCt);
250+
UserKey? userKey = scope == IdentifierExistenceScope.WithinUser ? context.GetTargetUserKey() : null;
251+
252+
var result = await _identifierStore.ExistsAsync(new IdentifierExistenceQuery(context.ResourceTenant, type, normalized.Normalized, scope, userKey), innerCt);
247253
return result.Exists;
248254
});
249255

@@ -338,6 +344,19 @@ public async Task UpdateUserIdentifierAsync(AccessContext context, UpdateUserIde
338344
if (string.Equals(identifier.NormalizedValue, normalized.Normalized, StringComparison.Ordinal))
339345
throw new UAuthIdentifierValidationException("identifier_value_unchanged");
340346

347+
var withinUserResult = await _identifierStore.ExistsAsync(
348+
new IdentifierExistenceQuery(
349+
identifier.Tenant,
350+
identifier.Type,
351+
normalized.Normalized,
352+
IdentifierExistenceScope.WithinUser,
353+
UserKey: identifier.UserKey,
354+
ExcludeIdentifierId: identifier.Id),
355+
innerCt);
356+
357+
if (withinUserResult.Exists)
358+
throw new UAuthIdentifierConflictException("identifier_already_exists_for_user");
359+
341360
var mustBeUnique = _options.LoginIdentifiers.EnforceGlobalUniquenessForAllIdentifiers ||
342361
(identifier.IsPrimary && _options.LoginIdentifiers.AllowedTypes.Contains(identifier.Type));
343362

@@ -360,7 +379,7 @@ public async Task UpdateUserIdentifierAsync(AccessContext context, UpdateUserIde
360379
throw new UAuthIdentifierConflictException("identifier_already_exists");
361380
}
362381

363-
await _identifierStore.UpdateValueAsync(identifier.Id, request.NewValue, normalized.Normalized, resetVerification: true, _clock.UtcNow, innerCt);
382+
await _identifierStore.UpdateValueAsync(identifier.Id, request.NewValue, normalized.Normalized, _clock.UtcNow, innerCt);
364383
});
365384

366385
await _accessOrchestrator.ExecuteAsync(context, command, ct);
@@ -381,30 +400,13 @@ public async Task SetPrimaryUserIdentifierAsync(AccessContext context, SetPrimar
381400

382401
EnsureVerificationRequirements(identifier.Type, identifier.IsVerified);
383402

384-
//var identifiers = await _identifierStore.GetByUserAsync(identifier.Tenant, identifier.UserKey, innerCt);
385-
//var activeIdentifiers = identifiers.Where(i => !i.IsDeleted).ToList();
386-
387403
var result = await _identifierStore.ExistsAsync(
388404
new IdentifierExistenceQuery(identifier.Tenant, identifier.Type, identifier.NormalizedValue, IdentifierExistenceScope.TenantPrimaryOnly, ExcludeIdentifierId: identifier.Id), innerCt);
389405

390406
if (result.Exists)
391407
throw new UAuthIdentifierConflictException("identifier_already_exists");
392408

393-
//var userIdentifiers =
394-
//await _identifierStore.GetByUserAsync(identifier.Tenant, identifier.UserKey, innerCt);
395-
396-
//var existingPrimaryOfSameType = userIdentifiers
397-
// .FirstOrDefault(i =>
398-
// !i.IsDeleted &&
399-
// i.Type == identifier.Type &&
400-
// i.IsPrimary);
401-
402-
//if (existingPrimaryOfSameType is not null)
403-
//{
404-
// await _identifierStore.UnsetPrimaryAsync(existingPrimaryOfSameType.Id, innerCt);
405-
//}
406-
407-
await _identifierStore.SetPrimaryAsync(request.IdentifierId, innerCt);
409+
await _identifierStore.SetPrimaryAsync(request.IdentifierId, _clock.UtcNow, innerCt);
408410
});
409411

410412
await _accessOrchestrator.ExecuteAsync(context, command, ct);
@@ -439,7 +441,7 @@ public async Task UnsetPrimaryUserIdentifierAsync(AccessContext context, UnsetPr
439441
throw new UAuthIdentifierConflictException("cannot_unset_last_login_identifier");
440442
}
441443

442-
await _identifierStore.UnsetPrimaryAsync(request.IdentifierId, innerCt);
444+
await _identifierStore.UnsetPrimaryAsync(request.IdentifierId, _clock.UtcNow, innerCt);
443445
});
444446

445447
await _accessOrchestrator.ExecuteAsync(context, command, ct);

tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestAccessContext.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using CodeBeam.UltimateAuth.Core.Contracts;
2+
using CodeBeam.UltimateAuth.Core.Domain;
23
using CodeBeam.UltimateAuth.Core.MultiTenancy;
34

45
namespace CodeBeam.UltimateAuth.Tests.Unit.Helpers;
@@ -19,4 +20,21 @@ public static AccessContext WithAction(string action)
1920
attributes: EmptyAttributes.Instance
2021
);
2122
}
23+
24+
public static AccessContext ForUser(UserKey userKey, string action, TenantKey? tenant = null)
25+
{
26+
var t = tenant ?? TenantKey.Single;
27+
28+
return new AccessContext(
29+
actorUserKey: userKey,
30+
actorTenant: t,
31+
isAuthenticated: true,
32+
isSystemActor: false,
33+
resource: "identifier",
34+
targetUserKey: userKey,
35+
resourceTenant: t,
36+
action: action,
37+
attributes: EmptyAttributes.Instance
38+
);
39+
}
2240
}

tests/CodeBeam.UltimateAuth.Tests.Unit/Helpers/TestAuthRuntime.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,10 @@ public ValueTask<AuthFlowContext> CreateLoginFlowAsync(TenantKey? tenant = null)
6767
var httpContext = TestHttpContext.Create(tenant);
6868
return Services.GetRequiredService<IAuthFlowContextFactory>().CreateAsync(httpContext, AuthFlowType.Login);
6969
}
70+
71+
public IUserApplicationService GetUserApplicationService()
72+
{
73+
var scope = Services.CreateScope();
74+
return scope.ServiceProvider.GetRequiredService<IUserApplicationService>();
75+
}
7076
}

0 commit comments

Comments
 (0)