Skip to content

Commit 33f23a9

Browse files
committed
Enhance Identifier Handling (Part 1)
1 parent 8b799df commit 33f23a9

File tree

70 files changed

+669
-675
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+669
-675
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
@using CodeBeam.UltimateAuth.Users.Contracts
2+
@inject IUAuthClient UAuthClient
3+
@inject ISnackbar Snackbar
4+
5+
<MudDialog Class="mud-width-full" ContentStyle="height: 60vh">
6+
<TitleContent>
7+
<MudText>Identifier Management</MudText>
8+
<MudText Typo="Typo.subtitle2" Color="Color.Primary">User: @AuthState?.Identity?.DisplayName</MudText>
9+
</TitleContent>
10+
<DialogContent>
11+
<MudDataGrid T="UserIdentifierDto" Items="_identifiers" Hover="true" Bordered="true" Striped="true" Dense="true"
12+
EditMode="DataGridEditMode.Form" ReadOnly="false" CommittedItemChanges="@CommittedItemChanges">
13+
<ToolBarContent>
14+
<MudText>Identifiers</MudText>
15+
</ToolBarContent>
16+
<Columns>
17+
<PropertyColumn Property="x => x.Id" Title="Id" Editable="false" />
18+
<PropertyColumn Property="x => x.Type" Title="Type" Editable="false" />
19+
<PropertyColumn Property="x => x.Value" Title="Value" />
20+
<PropertyColumn Property="x => x.IsPrimary" Title="Primary" Editable="false" />
21+
<PropertyColumn Property="x => x.IsVerified" Title="Verified" Editable="false" />
22+
<TemplateColumn Title="Actions" Editable="false">
23+
<CellTemplate>
24+
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Edit" OnClick="@context.Actions.StartEditingItemAsync" />
25+
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => DeleteIdentifier(context.Item.Id))" />
26+
</CellTemplate>
27+
</TemplateColumn>
28+
</Columns>
29+
<PagerContent>
30+
<MudDataGridPager T="UserIdentifierDto" PageSizeOptions="new int[] { 5, 10, 20 }" />
31+
</PagerContent>
32+
</MudDataGrid>
33+
34+
<MudExpansionPanels>
35+
<MudExpansionPanel Text="Add New Identifier" Expanded="false">
36+
<MudStack Spacing="2">
37+
<MudSelectExtended @bind-Value="_newIdentifierType" ItemCollection="@(Enum.GetValues<UserIdentifierType>())" Variant="Variant.Outlined" Label="Type" />
38+
<MudTextField @bind-Value="_newIdentifierValue" Variant="Variant.Outlined" Label="Value" />
39+
<MudButton Color="Color.Primary" OnClick="AddNewIdentifier">Add</MudButton>
40+
</MudStack>
41+
</MudExpansionPanel>
42+
</MudExpansionPanels>
43+
44+
45+
</DialogContent>
46+
<DialogActions>
47+
<MudButton OnClick="Cancel">Cancel</MudButton>
48+
<MudButton Color="Color.Primary" OnClick="Submit">OK</MudButton>
49+
</DialogActions>
50+
</MudDialog>
51+
52+
@code {
53+
private UserIdentifierType _newIdentifierType;
54+
private string? _newIdentifierValue;
55+
56+
[CascadingParameter]
57+
private IMudDialogInstance MudDialog { get; set; } = default!;
58+
59+
[Parameter]
60+
public UAuthState AuthState { get; set; } = default!;
61+
62+
private List<UserIdentifierDto> _identifiers = new();
63+
64+
protected override async Task OnAfterRenderAsync(bool firstRender)
65+
{
66+
await base.OnAfterRenderAsync(firstRender);
67+
68+
if (firstRender)
69+
{
70+
var result = await UAuthClient.Identifiers.GetUserIdentifiersAsync(AuthState.Identity!.UserKey);
71+
if (result != null && result.IsSuccess && result.Value != null)
72+
{
73+
_identifiers = result.Value.ToList();
74+
StateHasChanged();
75+
}
76+
}
77+
}
78+
79+
private async Task<DataGridEditFormAction> CommittedItemChanges(UserIdentifierDto item)
80+
{
81+
UpdateUserIdentifierRequest updateRequest = new()
82+
{
83+
Id = item.Id,
84+
NewValue = item.Value
85+
};
86+
var result = await UAuthClient.Identifiers.UpdateSelfAsync(updateRequest);
87+
if (result.IsSuccess)
88+
{
89+
Snackbar.Add("Identifier updated successfully", Severity.Success);
90+
}
91+
else
92+
{
93+
Snackbar.Add("Failed to update identifier", Severity.Error);
94+
}
95+
96+
return DataGridEditFormAction.Close;
97+
}
98+
99+
private async Task AddNewIdentifier()
100+
{
101+
if (string.IsNullOrEmpty(_newIdentifierValue))
102+
{
103+
Snackbar.Add("Value cannot be empty", Severity.Warning);
104+
return;
105+
}
106+
107+
AddUserIdentifierRequest request = new()
108+
{
109+
Type = _newIdentifierType,
110+
Value = _newIdentifierValue
111+
};
112+
113+
var result = await UAuthClient.Identifiers.AddSelfAsync(request);
114+
if (result.IsSuccess)
115+
{
116+
Snackbar.Add("Identifier added successfully", Severity.Success);
117+
var getResult = await UAuthClient.Identifiers.GetUserIdentifiersAsync(AuthState.Identity!.UserKey);
118+
_identifiers = getResult.Value?.ToList() ?? new List<UserIdentifierDto>();
119+
StateHasChanged();
120+
}
121+
else
122+
{
123+
Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Failed to add identifier", Severity.Error);
124+
}
125+
}
126+
127+
private async Task DeleteIdentifier(Guid id)
128+
{
129+
DeleteUserIdentifierRequest request = new() { IdentifierId = id };
130+
var result = await UAuthClient.Identifiers.DeleteSelfAsync(request);
131+
if (result.IsSuccess)
132+
{
133+
Snackbar.Add("Identifier deleted successfully", Severity.Success);
134+
var getResult = await UAuthClient.Identifiers.GetUserIdentifiersAsync(AuthState.Identity!.UserKey);
135+
_identifiers = getResult.Value?.ToList() ?? new List<UserIdentifierDto>();
136+
StateHasChanged();
137+
}
138+
else
139+
{
140+
Snackbar.Add("Failed to delete identifier", Severity.Error);
141+
}
142+
}
143+
144+
private void Submit() => MudDialog.Close(DialogResult.Ok(true));
145+
146+
private void Cancel() => MudDialog.Cancel();
147+
}

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Home.razor

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@inject UAuthClientDiagnostics Diagnostics
77
@inject AuthenticationStateProvider AuthStateProvider
88
@inject ISnackbar Snackbar
9+
@inject IDialogService DialogService
910
@using System.Security.Claims
1011

1112
<MudPage Class="mud-width-full" FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
@@ -217,29 +218,36 @@
217218
<MudDivider Class="my-2" />
218219

219220
<MudText Typo="Typo.subtitle2">Account</MudText>
220-
<MudButton FullWidth Variant="Variant.Filled" Color="Color.Info"
221-
StartIcon="@Icons.Material.Filled.Password">
221+
<MudButton FullWidth Variant="Variant.Outlined" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Abc" OnClick="OpenIdentifierDialog">
222+
Manage Identifiers
223+
</MudButton>
224+
225+
<MudButton FullWidth Variant="Variant.Outlined" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Password">
222226
Change Password
223227
</MudButton>
224228

225229
<MudDivider Class="my-2" />
226230

227231
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
228232
<MudText Typo="Typo.subtitle2">Admin</MudText>
229-
<MudSwitchM3 @bind-Value="_showAdminPreview" Color="Color.Warning" Label="Preview" />
233+
<UAuthStateView Roles="Admin">
234+
<NotAuthorized>
235+
<MudSwitchM3 @bind-Value="_showAdminPreview" Color="Color.Primary" Label="Preview" />
236+
</NotAuthorized>
237+
</UAuthStateView>
230238
</MudStack>
231239

232240
@if (AuthState.IsInRole("Admin") || _showAdminPreview)
233241
{
234242
<MudGrid Spacing="2">
235243
<MudItem xs="12" sm="6">
236-
<MudButton FullWidth Variant="Variant.Filled" Color="Color.Primary"
244+
<MudButton FullWidth Variant="Variant.Outlined" Color="Color.Primary"
237245
StartIcon="@Icons.Material.Filled.PersonAdd">
238246
Add User
239247
</MudButton>
240248
</MudItem>
241249
<MudItem xs="12" sm="6">
242-
<MudButton FullWidth Variant="Variant.Filled" Color="Color.Warning"
250+
<MudButton FullWidth Variant="Variant.Outlined" Color="Color.Primary"
243251
StartIcon="@Icons.Material.Filled.AdminPanelSettings">
244252
Assign Role
245253
</MudButton>

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Home.razor.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CodeBeam.UltimateAuth.Client.Errors;
33
using CodeBeam.UltimateAuth.Core.Domain;
44
using CodeBeam.UltimateAuth.Core.Errors;
5+
using CodeBeam.UltimateAuth.Sample.BlazorServer.Components.Dialogs;
56
using Microsoft.AspNetCore.Components.Authorization;
67
using MudBlazor;
78
using System.Security.Claims;
@@ -150,6 +151,30 @@ private string GetHealthText()
150151
return utc?.ToLocalTime().ToString("dd MMM yyyy • HH:mm:ss");
151152
}
152153

154+
private async Task OpenIdentifierDialog()
155+
{
156+
157+
await DialogService.ShowAsync<IdentifierDialog>("Manage Identifiers", GetDialogParameters(), GetDialogOptions());
158+
}
159+
160+
private DialogOptions GetDialogOptions()
161+
{
162+
return new DialogOptions
163+
{
164+
MaxWidth = MaxWidth.Medium,
165+
FullWidth = true,
166+
CloseButton = true
167+
};
168+
}
169+
170+
private DialogParameters GetDialogParameters()
171+
{
172+
return new DialogParameters
173+
{
174+
["AuthState"] = AuthState
175+
};
176+
}
177+
153178
public override void Dispose()
154179
{
155180
base.Dispose();

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Program.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,22 @@
2727
options.DetailedErrors = true;
2828
});
2929

30-
builder.Services.AddMudServices();
30+
builder.Services.AddMudServices(o => {
31+
o.SnackbarConfiguration.PreventDuplicates = false;
32+
});
3133
builder.Services.AddMudExtensions();
3234
builder.Services.AddEndpointsApiExplorer();
3335
builder.Services.AddOpenApi();
3436

3537
builder.Services.AddUltimateAuthServer(o =>
3638
{
3739
o.Diagnostics.EnableRefreshDetails = true;
38-
o.Session.MaxLifetime = TimeSpan.FromSeconds(32);
39-
o.Session.Lifetime = TimeSpan.FromSeconds(32);
40-
o.Session.TouchInterval = TimeSpan.FromSeconds(9);
41-
o.Session.IdleTimeout = TimeSpan.FromSeconds(15);
42-
o.Token.AccessTokenLifetime = TimeSpan.FromSeconds(30);
43-
o.Token.RefreshTokenLifetime = TimeSpan.FromSeconds(32);
40+
//o.Session.MaxLifetime = TimeSpan.FromSeconds(32);
41+
//o.Session.Lifetime = TimeSpan.FromSeconds(32);
42+
//o.Session.TouchInterval = TimeSpan.FromSeconds(9);
43+
//o.Session.IdleTimeout = TimeSpan.FromSeconds(15);
44+
//o.Token.AccessTokenLifetime = TimeSpan.FromSeconds(30);
45+
//o.Token.RefreshTokenLifetime = TimeSpan.FromSeconds(32);
4446
o.Login.MaxFailedAttempts = 2;
4547
o.Login.LockoutDuration = TimeSpan.FromSeconds(10);
4648
})
@@ -56,7 +58,7 @@
5658

5759
builder.Services.AddUltimateAuthClient(o =>
5860
{
59-
o.AutoRefresh.Interval = TimeSpan.FromSeconds(5);
61+
//o.AutoRefresh.Interval = TimeSpan.FromSeconds(5);
6062
o.Reauth.Behavior = ReauthBehavior.RaiseEvent;
6163
});
6264

src/CodeBeam.UltimateAuth.Client/Contracts/RefreshResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace CodeBeam.UltimateAuth.Client.Contracts;
44

55
public sealed record RefreshResult
66
{
7-
public bool Ok { get; init; }
7+
public bool IsSuccess { get; init; }
88
public int Status { get; init; }
99
public RefreshOutcome Outcome { get; init; }
1010
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
using System.Text.Json;
2+
using System.Text.Json.Serialization;
23

34
namespace CodeBeam.UltimateAuth.Client.Contracts;
45

56
public sealed class UAuthTransportResult
67
{
8+
[JsonPropertyName("ok")]
79
public bool Ok { get; init; }
10+
11+
[JsonPropertyName("status")]
812
public int Status { get; init; }
13+
14+
[JsonPropertyName("refreshOutcome")]
915
public string? RefreshOutcome { get; init; }
16+
17+
[JsonPropertyName("body")]
1018
public JsonElement? Body { get; init; }
1119
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace CodeBeam.UltimateAuth.Client.Errors;
2+
3+
using CodeBeam.UltimateAuth.Core.Errors;
4+
5+
public abstract class UAuthClientException : UAuthException
6+
{
7+
protected UAuthClientException(string code, string message) : base(code, message)
8+
{
9+
}
10+
11+
protected UAuthClientException(string code, string message, Exception? inner) : base(code, message, inner)
12+
{
13+
}
14+
}
Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
using CodeBeam.UltimateAuth.Core.Errors;
1+
namespace CodeBeam.UltimateAuth.Client.Errors;
22

3-
namespace CodeBeam.UltimateAuth.Client.Errors;
4-
5-
/// <summary>
6-
/// Represents a protocol-level failure in the UltimateAuth client.
7-
/// Thrown when the server response does not conform to the expected contract,
8-
/// such as invalid JSON, missing required fields, or unexpected response structure.
9-
/// </summary>
10-
public sealed class UAuthProtocolException : UAuthException
3+
public sealed class UAuthProtocolException : UAuthClientException
114
{
12-
public UAuthProtocolException(string message) : base(message)
5+
public UAuthProtocolException(string message) : base("protocol_error", message)
136
{
147
}
158

16-
public UAuthProtocolException(string message, Exception? inner) : base(message, inner)
9+
public UAuthProtocolException(string message, Exception? inner) : base("protocol_error", message, inner)
1710
{
1811
}
19-
}
12+
}
Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,26 @@
1-
using CodeBeam.UltimateAuth.Core.Errors;
2-
using System.Net;
1+
using System.Net;
32

43
namespace CodeBeam.UltimateAuth.Client.Errors;
54

6-
/// <summary>
7-
/// Represents a transport-level failure while communicating with the UltimateAuth server.
8-
/// This includes network errors, HTTP status failures, and connectivity issues.
9-
/// </summary>
10-
public sealed class UAuthTransportException : UAuthException
5+
public sealed class UAuthTransportException : UAuthClientException
116
{
12-
/// <summary>
13-
/// Gets the HTTP status code associated with the failure, if available.
14-
/// </summary>
157
public HttpStatusCode? StatusCode { get; }
168

17-
public UAuthTransportException(string message) : base(message)
9+
public UAuthTransportException(string message) : base("transport_error", message)
1810
{
1911
}
2012

21-
public UAuthTransportException(string message, Exception? inner) : base(message, inner)
13+
public UAuthTransportException(string message, Exception? inner) : base("transport_error", message, inner)
2214
{
2315
}
2416

25-
public UAuthTransportException(string message, HttpStatusCode statusCode) : base(message)
17+
public UAuthTransportException(string message, HttpStatusCode statusCode) : base("transport_error", message)
2618
{
2719
StatusCode = statusCode;
2820
}
2921

30-
public UAuthTransportException(string message, HttpStatusCode statusCode, Exception? inner) : base(message, inner)
22+
public UAuthTransportException(string message, HttpStatusCode statusCode, Exception? inner) : base("transport_error", message, inner)
3123
{
3224
StatusCode = statusCode;
3325
}
34-
}
26+
}

src/CodeBeam.UltimateAuth.Client/Infrastructure/IUAuthRequestClient.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,5 @@ public interface IUAuthRequestClient
88

99
Task<UAuthTransportResult> SendFormAsync(string endpoint, IDictionary<string, string>? form = null, CancellationToken ct = default);
1010

11-
Task<UAuthTransportResult> SendFormForJsonAsync(string endpoint, IDictionary<string, string>? form = null, CancellationToken ct = default);
12-
1311
Task<UAuthTransportResult> SendJsonAsync(string endpoint, object? payload = null, CancellationToken ct = default);
1412
}

0 commit comments

Comments
 (0)