Skip to content

Commit 2289c08

Browse files
committed
Add user session management to Identity & Blazor UI
- Introduce UserSession entity and migration for session tracking - Implement ISessionService for session CRUD, validation, and cleanup - Add API endpoints for listing/revoking sessions (user & admin) - Integrate session logic into token issuance/refresh flows - Add session management permissions and register dependencies - Update Blazor UI: new /sessions page, navigation link, and tenant settings stub - Update OpenAPI client for new session endpoints and DTOs - Add UAParser for device info; improve tenant provisioning startup logic
1 parent 5971125 commit 2289c08

39 files changed

Lines changed: 3309 additions & 111 deletions

File tree

src/BuildingBlocks/Shared/Identity/IdentityPermissionConstants.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,12 @@ public static class Roles
1717
public const string Update = "Permissions.Roles.Update";
1818
public const string Delete = "Permissions.Roles.Delete";
1919
}
20+
21+
public static class Sessions
22+
{
23+
public const string View = "Permissions.Sessions.View";
24+
public const string Revoke = "Permissions.Sessions.Revoke";
25+
public const string ViewAll = "Permissions.Sessions.ViewAll";
26+
public const string RevokeAll = "Permissions.Sessions.RevokeAll";
27+
}
2028
}

src/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
8383
<PackageVersion Include="Serilog.Enrichers.CorrelationId" Version="3.0.1" />
8484
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.17.0.131074" />
85+
<PackageVersion Include="UAParser" Version="3.1.47" />
8586
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
8687
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
8788
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace FSH.Modules.Identity.Contracts.DTOs;
2+
3+
public class UserSessionDto
4+
{
5+
public Guid Id { get; set; }
6+
public string? UserId { get; set; }
7+
public string? UserName { get; set; }
8+
public string? UserEmail { get; set; }
9+
public string? IpAddress { get; set; }
10+
public string? DeviceType { get; set; }
11+
public string? Browser { get; set; }
12+
public string? BrowserVersion { get; set; }
13+
public string? OperatingSystem { get; set; }
14+
public string? OsVersion { get; set; }
15+
public DateTime CreatedAt { get; set; }
16+
public DateTime LastActivityAt { get; set; }
17+
public DateTime ExpiresAt { get; set; }
18+
public bool IsActive { get; set; }
19+
public bool IsCurrentSession { get; set; }
20+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
3+
namespace FSH.Modules.Identity.Contracts.Services;
4+
5+
public interface ISessionService
6+
{
7+
Task<UserSessionDto> CreateSessionAsync(
8+
string userId,
9+
string refreshTokenHash,
10+
string ipAddress,
11+
string userAgent,
12+
DateTime expiresAt,
13+
CancellationToken cancellationToken = default);
14+
15+
Task<List<UserSessionDto>> GetUserSessionsAsync(
16+
string userId,
17+
CancellationToken cancellationToken = default);
18+
19+
Task<List<UserSessionDto>> GetUserSessionsForAdminAsync(
20+
string userId,
21+
CancellationToken cancellationToken = default);
22+
23+
Task<UserSessionDto?> GetSessionAsync(
24+
Guid sessionId,
25+
CancellationToken cancellationToken = default);
26+
27+
Task<bool> RevokeSessionAsync(
28+
Guid sessionId,
29+
string revokedBy,
30+
string? reason = null,
31+
CancellationToken cancellationToken = default);
32+
33+
Task<int> RevokeAllSessionsAsync(
34+
string userId,
35+
string revokedBy,
36+
Guid? exceptSessionId = null,
37+
string? reason = null,
38+
CancellationToken cancellationToken = default);
39+
40+
Task<int> RevokeAllSessionsForAdminAsync(
41+
string userId,
42+
string revokedBy,
43+
string? reason = null,
44+
CancellationToken cancellationToken = default);
45+
46+
Task<bool> RevokeSessionForAdminAsync(
47+
Guid sessionId,
48+
string revokedBy,
49+
string? reason = null,
50+
CancellationToken cancellationToken = default);
51+
52+
Task UpdateSessionActivityAsync(
53+
string refreshTokenHash,
54+
CancellationToken cancellationToken = default);
55+
56+
Task UpdateSessionRefreshTokenAsync(
57+
string oldRefreshTokenHash,
58+
string newRefreshTokenHash,
59+
DateTime newExpiresAt,
60+
CancellationToken cancellationToken = default);
61+
62+
Task<bool> ValidateSessionAsync(
63+
string refreshTokenHash,
64+
CancellationToken cancellationToken = default);
65+
66+
Task<Guid?> GetSessionIdByRefreshTokenAsync(
67+
string refreshTokenHash,
68+
CancellationToken cancellationToken = default);
69+
70+
Task CleanupExpiredSessionsAsync(
71+
CancellationToken cancellationToken = default);
72+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using Mediator;
2+
3+
namespace FSH.Modules.Identity.Contracts.v1.Sessions.AdminRevokeAllSessions;
4+
5+
public sealed record AdminRevokeAllSessionsCommand(Guid UserId, string? Reason = null) : ICommand<int>;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using Mediator;
2+
3+
namespace FSH.Modules.Identity.Contracts.v1.Sessions.AdminRevokeSession;
4+
5+
public sealed record AdminRevokeSessionCommand(Guid UserId, Guid SessionId, string? Reason = null) : ICommand<bool>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
using Mediator;
3+
4+
namespace FSH.Modules.Identity.Contracts.v1.Sessions.GetMySessions;
5+
6+
public sealed record GetMySessionsQuery : IQuery<List<UserSessionDto>>;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using FSH.Modules.Identity.Contracts.DTOs;
2+
using Mediator;
3+
4+
namespace FSH.Modules.Identity.Contracts.v1.Sessions.GetUserSessions;
5+
6+
public sealed record GetUserSessionsQuery(Guid UserId) : IQuery<List<UserSessionDto>>;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using Mediator;
2+
3+
namespace FSH.Modules.Identity.Contracts.v1.Sessions.RevokeAllSessions;
4+
5+
public sealed record RevokeAllSessionsCommand(Guid? ExceptSessionId = null) : ICommand<int>;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using Mediator;
2+
3+
namespace FSH.Modules.Identity.Contracts.v1.Sessions.RevokeSession;
4+
5+
public sealed record RevokeSessionCommand(Guid SessionId) : ICommand<bool>;

0 commit comments

Comments
 (0)