Skip to content

Commit ebfe076

Browse files
committed
Improved Device Context Properties
1 parent 554cb9b commit ebfe076

File tree

29 files changed

+357
-226
lines changed

29 files changed

+357
-226
lines changed

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

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,40 @@
33
@inject IUAuthClient UAuthClient
44
@inject ISnackbar Snackbar
55
@inject IDialogService DialogService
6+
@inject NavigationManager Nav
67

78
<MudDialog Class="mud-width-full" ContentClass="uauth-dialog">
89
<TitleContent>
910
<MudText>Identifier Management</MudText>
1011
<MudText Typo="Typo.subtitle2" Color="Color.Primary">User: @AuthState?.Identity?.DisplayName</MudText>
1112
</TitleContent>
1213
<DialogContent>
14+
<MudStack Class="mud-width-full mb-4" Row="true">
15+
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Logout" OnClick="LogoutAllAsync">Logout All Devices</MudButton>
16+
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Logout" OnClick="LogoutAllExceptThisAsync">Logout Other Devices</MudButton>
17+
</MudStack>
1318
<MudDataGrid @ref="@_grid" T="SessionChainSummaryDto" ServerData="LoadServerData" Hover="true" Bordered="true" Striped="true" Dense="true" Elevation="0"
1419
EditMode="DataGridEditMode.Form" ReadOnly="false" Loading="@_loading">
1520
<ToolBarContent>
16-
<MudStack Class="mud-width-full" Row="true" AlignItems="AlignItems.Center" Justify="Justify.Center">
17-
<MudText>Identifiers</MudText>
18-
<MudSpacer />
19-
<MudIconButton Variant="Variant.Filled" Color="Color.Primary" Icon="@Icons.Material.Filled.Refresh" OnClick="ReloadAsync" />
20-
</MudStack>
21+
<MudText>Identifiers</MudText>
22+
<MudSpacer />
23+
<MudIconButton Variant="Variant.Filled" Color="Color.Primary" Icon="@Icons.Material.Filled.Refresh" OnClick="ReloadAsync" />
2124
</ToolBarContent>
2225
<Columns>
2326
<PropertyColumn Property="x => x.ChainId" Title="Chain Id" Editable="false" />
2427
<PropertyColumn Property="x => x.CreatedAt" Title="Created At" Editable="false" />
25-
<PropertyColumn Property="x => x.DeviceName" Title="Device Name" Editable="false" />
2628
<PropertyColumn Property="x => x.DeviceType" Title="Device Type" Editable="false" />
29+
<PropertyColumn Property="x => x.OperatingSystem" Title="Operating System" Editable="false" />
30+
<PropertyColumn Property="x => x.Platform" Title="Platform" Editable="false" />
31+
<PropertyColumn Property="x => x.Browser" Title="Browser" Editable="false" />
2732
<PropertyColumn Property="x => x.RotationCount" Title="Rotation Count" Editable="false" />
2833
<PropertyColumn Property="x => x.IsRevoked" Title="Revoked" Editable="false" />
2934
<PropertyColumn Property="x => x.IsCurrentDevice" Title="Current Device" Editable="false" />
3035
<TemplateColumn Title="Actions" Editable="false">
3136
<CellTemplate>
3237
<MudStack Style="min-width: 120px" Row="true" Spacing="2" Wrap="Wrap.Wrap">
33-
<MudTooltip Text="Edit" Color="Color.Primary" Delay="300" ShowOnFocus="false">
34-
@* <MudIconButton Color="Color.Primary" Variant="Variant.Filled" Icon="@Icons.Material.Filled.Edit" OnClick="@context.Actions.StartEditingItemAsync" /> *@
38+
<MudTooltip Text="Revoke Device" Color="Color.Primary" Delay="300" ShowOnFocus="false">
39+
<MudIconButton Color="Color.Primary" Variant="Variant.Filled" Icon="@Icons.Material.Filled.Logout" OnClick="@(() => LogoutChainAsync(context.Item.ChainId))" />
3540
</MudTooltip>
3641
</MudStack>
3742
</CellTemplate>
@@ -140,27 +145,47 @@
140145
}
141146
}
142147

143-
// private async Task<DataGridEditFormAction> CommittedItemChanges(SessionChainSummaryDto item)
144-
// {
145-
// UpdateUserIdentifierRequest updateRequest = new()
146-
// {
147-
// Id = item.Id,
148-
// NewValue = item.Value
149-
// };
150-
// var result = await UAuthClient.Identifiers.UpdateSelfAsync(updateRequest);
151-
// if (result.IsSuccess)
152-
// {
153-
// Snackbar.Add("Identifier updated successfully", Severity.Success);
154-
// }
155-
// else
156-
// {
157-
// Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Failed to update identifier", Severity.Error);
158-
// }
159-
160-
// await ReloadAsync();
161-
// return DataGridEditFormAction.Close;
162-
// }
148+
private async Task LogoutAllAsync()
149+
{
150+
var result = await UAuthClient.Sessions.RevokeAllMyChainsAsync();
151+
if (result.IsSuccess)
152+
{
153+
Snackbar.Add("Logged out of all devices", Severity.Success);
154+
Nav.NavigateTo("/login");
155+
}
156+
else
157+
{
158+
Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Failed to logout", Severity.Error);
159+
}
160+
}
163161

162+
private async Task LogoutAllExceptThisAsync()
163+
{
164+
var result = await UAuthClient.Sessions.RevokeMyOtherChainsAsync();
165+
if (result.IsSuccess)
166+
{
167+
Snackbar.Add("Logged out of all other devices", Severity.Success);
168+
await ReloadAsync();
169+
}
170+
else
171+
{
172+
Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Failed to logout", Severity.Error);
173+
}
174+
}
175+
176+
private async Task LogoutChainAsync(SessionChainId chainId)
177+
{
178+
var result = await UAuthClient.Sessions.RevokeMyChainAsync(chainId);
179+
if (result.IsSuccess)
180+
{
181+
Snackbar.Add("Logged out of device", Severity.Success);
182+
await ReloadAsync();
183+
}
184+
else
185+
{
186+
Snackbar.Add(result?.Problem?.Detail ?? result?.Problem?.Title ?? "Failed to logout", Severity.Error);
187+
}
188+
}
164189

165190
private void Submit() => MudDialog.Close(DialogResult.Ok(true));
166191

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ private async Task ProgrammaticLogin()
8484
{
8585
Identifier = "admin",
8686
Secret = "admin",
87-
Device = DeviceContext.FromDeviceId(deviceId),
87+
//Device = DeviceContext.Create(deviceId, null, null, null, null, null),
8888
};
8989
await UAuthClient.Flows.LoginAsync(request, "/home");
9090
}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
using CodeBeam.UltimateAuth.Authorization.InMemory.Extensions;
22
using CodeBeam.UltimateAuth.Authorization.Reference.Extensions;
3+
using CodeBeam.UltimateAuth.Client;
34
using CodeBeam.UltimateAuth.Client.Extensions;
45
using CodeBeam.UltimateAuth.Core.Domain;
56
using CodeBeam.UltimateAuth.Core.Infrastructure;
67
using CodeBeam.UltimateAuth.Credentials.InMemory.Extensions;
78
using CodeBeam.UltimateAuth.Credentials.Reference;
89
using CodeBeam.UltimateAuth.Sample.BlazorServer.Components;
10+
using CodeBeam.UltimateAuth.Sample.BlazorServer.Infrastructure;
911
using CodeBeam.UltimateAuth.Security.Argon2;
1012
using CodeBeam.UltimateAuth.Server.Extensions;
1113
using CodeBeam.UltimateAuth.Sessions.InMemory;
1214
using CodeBeam.UltimateAuth.Tokens.InMemory;
15+
using CodeBeam.UltimateAuth.Users.Contracts;
1316
using CodeBeam.UltimateAuth.Users.InMemory.Extensions;
1417
using CodeBeam.UltimateAuth.Users.Reference.Extensions;
15-
using CodeBeam.UltimateAuth.Client;
18+
using Microsoft.AspNetCore.HttpOverrides;
1619
using MudBlazor.Services;
1720
using MudExtensions.Services;
1821
using Scalar.AspNetCore;
19-
using CodeBeam.UltimateAuth.Sample.BlazorServer.Infrastructure;
20-
using CodeBeam.UltimateAuth.Users.Contracts;
2122

2223
var builder = WebApplication.CreateBuilder(args);
2324

@@ -66,6 +67,13 @@
6667

6768
builder.Services.AddScoped<DarkModeManager>();
6869

70+
builder.Services.Configure<ForwardedHeadersOptions>(options =>
71+
{
72+
options.ForwardedHeaders =
73+
ForwardedHeaders.XForwardedFor |
74+
ForwardedHeaders.XForwardedProto;
75+
});
76+
6977
var app = builder.Build();
7078

7179
if (!app.Environment.IsDevelopment())
@@ -83,6 +91,8 @@
8391
await seedRunner.RunAsync(null);
8492
}
8593

94+
app.UseForwardedHeaders();
95+
8696
app.UseHttpsRedirection();
8797
app.UseStaticFiles();
8898

samples/blazor-standalone-wasm/CodeBeam.UltimateAuth.Sample.BlazorStandaloneWasm/Pages/Home.razor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,11 @@ private void OnDiagnosticsChanged()
4141

4242
private async Task ProgrammaticLogin()
4343
{
44-
var device = await DeviceIdProvider.GetOrCreateAsync();
44+
var deviceId = await DeviceIdProvider.GetOrCreateAsync();
4545
var request = new LoginRequest
4646
{
4747
Identifier = "admin",
4848
Secret = "admin",
49-
Device = DeviceContext.FromDeviceId(device),
5049
};
5150
await UAuthClient.Flows.LoginAsync(request);
5251
}

src/CodeBeam.UltimateAuth.Client/Services/ISessionClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ namespace CodeBeam.UltimateAuth.Client.Services;
66
public interface ISessionClient
77
{
88
Task<UAuthResult<PagedResult<SessionChainSummaryDto>>> GetMyChainsAsync(PageRequest? request = null);
9-
Task<UAuthResult<SessionChainDetailDto>> GetMyChainAsync(SessionChainId chainId);
9+
Task<UAuthResult<SessionChainDetailDto>> GetMyChainDetailAsync(SessionChainId chainId);
1010
Task<UAuthResult> RevokeMyChainAsync(SessionChainId chainId);
11-
Task<UAuthResult> RevokeOtherChainsAsync();
11+
Task<UAuthResult> RevokeMyOtherChainsAsync();
1212
Task<UAuthResult> RevokeAllMyChainsAsync();
1313

1414

src/CodeBeam.UltimateAuth.Client/Services/UAuthSessionClient.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task<UAuthResult<PagedResult<SessionChainSummaryDto>>> GetMyChainsA
2828
return UAuthResultMapper.FromJson<PagedResult<SessionChainSummaryDto>>(raw);
2929
}
3030

31-
public async Task<UAuthResult<SessionChainDetailDto>> GetMyChainAsync(SessionChainId chainId)
31+
public async Task<UAuthResult<SessionChainDetailDto>> GetMyChainDetailAsync(SessionChainId chainId)
3232
{
3333
var raw = await _request.SendFormAsync(Url($"/session/me/chains/{chainId}"));
3434
return UAuthResultMapper.FromJson<SessionChainDetailDto>(raw);
@@ -40,7 +40,7 @@ public async Task<UAuthResult> RevokeMyChainAsync(SessionChainId chainId)
4040
return UAuthResultMapper.From(raw);
4141
}
4242

43-
public async Task<UAuthResult> RevokeOtherChainsAsync()
43+
public async Task<UAuthResult> RevokeMyOtherChainsAsync()
4444
{
4545
var raw = await _request.SendFormAsync(Url("/session/me/revoke-others"));
4646
return UAuthResultMapper.From(raw);

src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public interface ISessionStore
2626
Task RevokeRootCascadeAsync(UserKey userKey, DateTimeOffset at);
2727

2828
Task<SessionChainId?> GetChainIdBySessionAsync(AuthSessionId sessionId);
29-
Task<IReadOnlyList<UAuthSessionChain>> GetChainsByUserAsync(UserKey userKey);
29+
Task<IReadOnlyList<UAuthSessionChain>> GetChainsByUserAsync(UserKey userKey, bool includeHistoricalRoots = false);
30+
Task<IReadOnlyList<UAuthSessionChain>> GetChainsByRootAsync(SessionRootId rootId);
3031
Task<IReadOnlyList<UAuthSession>> GetSessionsByChainAsync(SessionChainId chainId);
3132
Task DeleteExpiredSessionsAsync(DateTimeOffset at);
3233
}

src/CodeBeam.UltimateAuth.Core/Contracts/Authority/DeviceInfo.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ public sealed class DeviceInfo
66
{
77
public required DeviceId DeviceId { get; init; }
88

9+
// TODO: Implement device type and device limits
910
/// <summary>
10-
/// High-level platform classification (web, mobile, desktop, iot).
11-
/// Used for analytics and policy decisions.
11+
/// Device type that can be used for device limits. Sends with header "X-Device-Type" or form field "device_type". Examples: "web", "mobile", "desktop", "tablet", "iot".
1212
/// </summary>
13-
public string? Platform { get; init; }
13+
public string? DeviceType { get; init; }
1414

1515
/// <summary>
1616
/// Operating system information (e.g. iOS 17, Android 14, Windows 11).
@@ -22,6 +22,12 @@ public sealed class DeviceInfo
2222
/// </summary>
2323
public string? Browser { get; init; }
2424

25+
/// <summary>
26+
/// High-level platform classification (web, mobile, desktop, iot).
27+
/// Used for analytics and policy decisions.
28+
/// </summary>
29+
public string? Platform { get; init; }
30+
2531
/// <summary>
2632
/// Raw user-agent string (optional).
2733
/// </summary>

src/CodeBeam.UltimateAuth.Core/Contracts/Login/LoginRequest.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ public sealed record LoginRequest
99
public string Identifier { get; init; } = default!;
1010
public string Secret { get; init; } = default!;
1111
public DateTimeOffset? At { get; init; }
12-
public required DeviceContext Device { get; init; }
1312
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
1413

1514
/// <summary>

src/CodeBeam.UltimateAuth.Core/Contracts/Session/Dtos/SessionChainSummaryDto.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ namespace CodeBeam.UltimateAuth.Core.Contracts;
55
public sealed record SessionChainSummaryDto
66
{
77
public required SessionChainId ChainId { get; init; }
8-
public string? DeviceName { get; init; }
98
public string? DeviceType { get; init; }
9+
public string? OperatingSystem { get; init; }
10+
public string? Platform { get; init; }
11+
public string? Browser { get; init; }
1012
public DateTimeOffset CreatedAt { get; init; }
1113
public DateTimeOffset? LastSeenAt { get; init; }
1214
public int RotationCount { get; init; }
15+
public int TouchCount { get; init; }
1316
public bool IsRevoked { get; init; }
17+
public DateTimeOffset? RevokedAt { get; init; }
1418
public AuthSessionId? ActiveSessionId { get; init; }
1519
public bool IsCurrentDevice { get; init; }
1620
}

0 commit comments

Comments
 (0)