Skip to content

Commit 079181f

Browse files
committed
Added Session Concurrency
1 parent a395d72 commit 079181f

File tree

22 files changed

+396
-185
lines changed

22 files changed

+396
-185
lines changed

src/CodeBeam.UltimateAuth.Core/Abstractions/Issuers/ISessionIssuer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions;
66

77
public interface ISessionIssuer
88
{
9-
Task<IssuedSession> IssueLoginSessionAsync(AuthenticatedSessionContext context, CancellationToken cancellationToken = default);
9+
Task<IssuedSession> IssueSessionAsync(AuthenticatedSessionContext context, CancellationToken cancellationToken = default);
1010

1111
Task<IssuedSession> RotateSessionAsync(SessionRotationContext context, CancellationToken cancellationToken = default);
1212

src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreKernel.cs renamed to src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStore.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@
22

33
namespace CodeBeam.UltimateAuth.Core.Abstractions;
44

5-
public interface ISessionStoreKernel
5+
public interface ISessionStore
66
{
77
Task ExecuteAsync(Func<CancellationToken, Task> action, CancellationToken ct = default);
88
Task<TResult> ExecuteAsync<TResult>(Func<CancellationToken, Task<TResult>> action, CancellationToken ct = default);
99

1010
Task<UAuthSession?> GetSessionAsync(AuthSessionId sessionId);
11-
Task SaveSessionAsync(UAuthSession session);
11+
Task SaveSessionAsync(UAuthSession session, long expectedVersion);
12+
Task CreateSessionAsync(UAuthSession session);
1213
Task<bool> RevokeSessionAsync(AuthSessionId sessionId, DateTimeOffset at);
1314

1415
Task<UAuthSessionChain?> GetChainAsync(SessionChainId chainId);
15-
Task SaveChainAsync(UAuthSessionChain chain);
16+
Task SaveChainAsync(UAuthSessionChain chain, long expectedVersion);
17+
Task CreateChainAsync(UAuthSessionChain chain);
1618
Task RevokeChainAsync(SessionChainId chainId, DateTimeOffset at);
17-
Task<AuthSessionId?> GetActiveSessionIdAsync(SessionChainId chainId);
18-
Task SetActiveSessionIdAsync(SessionChainId chainId, AuthSessionId sessionId);
19+
//Task<AuthSessionId?> GetActiveSessionIdAsync(SessionChainId chainId);
20+
//Task SetActiveSessionIdAsync(SessionChainId chainId, AuthSessionId sessionId);
1921

20-
Task<UAuthSessionRoot?> GetSessionRootByUserAsync(UserKey userKey);
21-
Task<UAuthSessionRoot?> GetSessionRootByIdAsync(SessionRootId rootId);
22-
Task SaveSessionRootAsync(UAuthSessionRoot root);
23-
Task RevokeSessionRootAsync(UserKey userKey, DateTimeOffset at);
22+
Task<UAuthSessionRoot?> GetRootByUserAsync(UserKey userKey);
23+
Task<UAuthSessionRoot?> GetRootByIdAsync(SessionRootId rootId);
24+
Task SaveRootAsync(UAuthSessionRoot root, long expectedVersion);
25+
Task CreateRootAsync(UAuthSessionRoot root);
26+
Task RevokeRootAsync(UserKey userKey, DateTimeOffset at);
2427

2528
Task<SessionChainId?> GetChainIdBySessionAsync(AuthSessionId sessionId);
2629
Task<IReadOnlyList<UAuthSessionChain>> GetChainsByUserAsync(UserKey userKey);

src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreKernelFactory.cs renamed to src/CodeBeam.UltimateAuth.Core/Abstractions/Stores/ISessionStoreFactory.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ namespace CodeBeam.UltimateAuth.Core.Abstractions;
55
/// <summary>
66
/// Provides a factory abstraction for creating tenant-scoped session store
77
/// instances capable of persisting sessions, chains, and session roots.
8-
/// Implementations typically resolve concrete <see cref="ISessionStoreKernel"/> types from the dependency injection container.
8+
/// Implementations typically resolve concrete <see cref="ISessionStore"/> types from the dependency injection container.
99
/// </summary>
10-
public interface ISessionStoreKernelFactory
10+
public interface ISessionStoreFactory
1111
{
1212
/// <summary>
1313
/// Creates and returns a session store instance for the specified user ID type within the given tenant context.
1414
/// </summary>
1515
/// <returns>
16-
/// An <see cref="ISessionStoreKernel"/> implementation able to perform session persistence operations.
16+
/// An <see cref="ISessionStore"/> implementation able to perform session persistence operations.
1717
/// </returns>
18-
ISessionStoreKernel Create(TenantKey tenant);
18+
ISessionStore Create(TenantKey tenant);
1919
}

src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSession.cs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public static UAuthSession Create(
5959
SessionChainId chainId,
6060
DateTimeOffset now,
6161
DateTimeOffset expiresAt,
62+
long securityVersion,
6263
DeviceContext device,
6364
ClaimsSnapshot? claims,
6465
SessionMetadata metadata)
@@ -73,37 +74,14 @@ public static UAuthSession Create(
7374
lastSeenAt: now,
7475
isRevoked: false,
7576
revokedAt: null,
76-
securityVersionAtCreation: 0,
77+
securityVersionAtCreation: securityVersion,
7778
device: device,
7879
claims: claims ?? ClaimsSnapshot.Empty,
7980
metadata: metadata,
8081
version: 0
8182
);
8283
}
8384

84-
public UAuthSession WithSecurityVersion(long securityVersion)
85-
{
86-
if (SecurityVersionAtCreation == securityVersion)
87-
return this;
88-
89-
return new UAuthSession(
90-
SessionId,
91-
Tenant,
92-
UserKey,
93-
ChainId,
94-
CreatedAt,
95-
ExpiresAt,
96-
LastSeenAt,
97-
IsRevoked,
98-
RevokedAt,
99-
securityVersion,
100-
Device,
101-
Claims,
102-
Metadata,
103-
Version + 1
104-
);
105-
}
106-
10785
public UAuthSession Touch(DateTimeOffset at)
10886
{
10987
return new UAuthSession(

src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSessionChain.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ public UAuthSessionChain AttachSession(AuthSessionId sessionId)
8080
SecurityVersionAtCreation,
8181
ClaimsSnapshot,
8282
activeSessionId: sessionId,
83-
isRevoked: false,
84-
revokedAt: null,
83+
isRevoked: IsRevoked,
84+
revokedAt: RevokedAt,
8585
version: Version + 1
8686
);
8787
}

src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSessionRoot.cs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,27 @@ public static UAuthSessionRoot Create(
4646
SessionRootId.New(),
4747
tenant,
4848
userKey,
49-
isRevoked: false,
50-
revokedAt: null,
51-
securityVersion: 0,
49+
false,
50+
null,
51+
0,
5252
chains: Array.Empty<UAuthSessionChain>(),
53-
lastUpdatedAt: issuedAt,
54-
version: 0
53+
issuedAt,
54+
0
55+
);
56+
}
57+
58+
public UAuthSessionRoot IncreaseSecurityVersion(DateTimeOffset at)
59+
{
60+
return new UAuthSessionRoot(
61+
RootId,
62+
Tenant,
63+
UserKey,
64+
IsRevoked,
65+
RevokedAt,
66+
SecurityVersion + 1,
67+
Chains,
68+
at,
69+
Version + 1
5570
);
5671
}
5772

@@ -64,12 +79,12 @@ public UAuthSessionRoot Revoke(DateTimeOffset at)
6479
RootId,
6580
Tenant,
6681
UserKey,
67-
isRevoked: true,
68-
revokedAt: at,
69-
securityVersion: SecurityVersion,
70-
chains: Chains,
71-
lastUpdatedAt: at,
72-
version: Version + 1
82+
true,
83+
at,
84+
SecurityVersion + 1,
85+
Chains,
86+
at,
87+
Version + 1
7388
);
7489
}
7590

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace CodeBeam.UltimateAuth.Core.Errors;
2+
3+
public class UAuthNotFoundException : UAuthRuntimeException
4+
{
5+
public override int StatusCode => 400;
6+
7+
public override string Title => "The resource is not found.";
8+
9+
public override string TypePrefix => "https://docs.ultimateauth.com/errors/notfound";
10+
11+
public UAuthNotFoundException(string code = "resource_not_found") : base(code, code)
12+
{
13+
}
14+
}

src/CodeBeam.UltimateAuth.Core/Errors/UAuthNotFoundException.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/CodeBeam.UltimateAuth.Server/Composition/UltimateAuthServerBuilderValidation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static IServiceCollection Build(this UltimateAuthServerBuilder builder)
1515
//if (!services.Any(sd => sd.ServiceType.IsAssignableTo(typeof(IUAuthUserStore<>))))
1616
// throw new InvalidOperationException("No credential store registered.");
1717

18-
if (!services.Any(sd => sd.ServiceType.IsAssignableTo(typeof(ISessionStoreKernel))))
18+
if (!services.Any(sd => sd.ServiceType.IsAssignableTo(typeof(ISessionStore))))
1919
throw new InvalidOperationException("No session store registered.");
2020

2121
return services;

src/CodeBeam.UltimateAuth.Server/Flows/Refresh/SessionTouchService.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ namespace CodeBeam.UltimateAuth.Server.Flows;
55

66
public sealed class SessionTouchService : ISessionTouchService
77
{
8-
private readonly ISessionStoreKernelFactory _kernelFactory;
8+
private readonly ISessionStoreFactory _kernelFactory;
99

10-
public SessionTouchService(ISessionStoreKernelFactory kernelFactory)
10+
public SessionTouchService(ISessionStoreFactory kernelFactory)
1111
{
1212
_kernelFactory = kernelFactory;
1313
}
@@ -29,14 +29,17 @@ public async Task<SessionRefreshResult> RefreshAsync(SessionValidationResult val
2929
await kernel.ExecuteAsync(async _ =>
3030
{
3131
var session = await kernel.GetSessionAsync(validation.SessionId.Value);
32+
3233
if (session is null || session.IsRevoked)
3334
return;
3435

3536
if (sessionTouchMode == SessionTouchMode.IfNeeded && now - session.LastSeenAt < policy.TouchInterval.Value)
3637
return;
3738

39+
var expectedVersion = session.Version;
3840
var touched = session.Touch(now);
39-
await kernel.SaveSessionAsync(touched);
41+
42+
await kernel.SaveSessionAsync(touched, expectedVersion);
4043
didTouch = true;
4144
}, ct);
4245

0 commit comments

Comments
 (0)