Skip to content

Commit b545078

Browse files
committed
Added Concurrency Support & Identifier Implementation
1 parent 9218fa4 commit b545078

28 files changed

Lines changed: 828 additions & 298 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace CodeBeam.UltimateAuth.Core.Abstractions;
2+
3+
public interface IVersionedEntity
4+
{
5+
long Version { get; }
6+
}

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

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using CodeBeam.UltimateAuth.Core.MultiTenancy;
1+
using CodeBeam.UltimateAuth.Core.Abstractions;
2+
using CodeBeam.UltimateAuth.Core.MultiTenancy;
23

34
namespace CodeBeam.UltimateAuth.Core.Domain;
45

5-
public sealed class UAuthSession
6+
public sealed class UAuthSession : IVersionedEntity
67
{
78
public AuthSessionId SessionId { get; }
89
public TenantKey Tenant { get; }
@@ -17,21 +18,23 @@ public sealed class UAuthSession
1718
public DeviceContext Device { get; }
1819
public ClaimsSnapshot Claims { get; }
1920
public SessionMetadata Metadata { get; }
21+
public long Version { get; }
2022

2123
private UAuthSession(
22-
AuthSessionId sessionId,
23-
TenantKey tenant,
24-
UserKey userKey,
25-
SessionChainId chainId,
26-
DateTimeOffset createdAt,
27-
DateTimeOffset expiresAt,
28-
DateTimeOffset? lastSeenAt,
29-
bool isRevoked,
30-
DateTimeOffset? revokedAt,
31-
long securityVersionAtCreation,
32-
DeviceContext device,
33-
ClaimsSnapshot claims,
34-
SessionMetadata metadata)
24+
AuthSessionId sessionId,
25+
TenantKey tenant,
26+
UserKey userKey,
27+
SessionChainId chainId,
28+
DateTimeOffset createdAt,
29+
DateTimeOffset expiresAt,
30+
DateTimeOffset? lastSeenAt,
31+
bool isRevoked,
32+
DateTimeOffset? revokedAt,
33+
long securityVersionAtCreation,
34+
DeviceContext device,
35+
ClaimsSnapshot claims,
36+
SessionMetadata metadata,
37+
long version)
3538
{
3639
SessionId = sessionId;
3740
Tenant = tenant;
@@ -46,6 +49,7 @@ private UAuthSession(
4649
Device = device;
4750
Claims = claims;
4851
Metadata = metadata;
52+
Version = version;
4953
}
5054

5155
public static UAuthSession Create(
@@ -72,13 +76,14 @@ public static UAuthSession Create(
7276
securityVersionAtCreation: 0,
7377
device: device,
7478
claims: claims ?? ClaimsSnapshot.Empty,
75-
metadata: metadata
79+
metadata: metadata,
80+
version: 0
7681
);
7782
}
7883

79-
public UAuthSession WithSecurityVersion(long version)
84+
public UAuthSession WithSecurityVersion(long securityVersion)
8085
{
81-
if (SecurityVersionAtCreation == version)
86+
if (SecurityVersionAtCreation == securityVersion)
8287
return this;
8388

8489
return new UAuthSession(
@@ -91,10 +96,11 @@ public UAuthSession WithSecurityVersion(long version)
9196
LastSeenAt,
9297
IsRevoked,
9398
RevokedAt,
94-
version,
99+
securityVersion,
95100
Device,
96101
Claims,
97-
Metadata
102+
Metadata,
103+
Version + 1
98104
);
99105
}
100106

@@ -113,7 +119,8 @@ public UAuthSession Touch(DateTimeOffset at)
113119
SecurityVersionAtCreation,
114120
Device,
115121
Claims,
116-
Metadata
122+
Metadata,
123+
Version + 1
117124
);
118125
}
119126

@@ -134,7 +141,8 @@ public UAuthSession Revoke(DateTimeOffset at)
134141
SecurityVersionAtCreation,
135142
Device,
136143
Claims,
137-
Metadata
144+
Metadata,
145+
Version + 1
138146
);
139147
}
140148

@@ -151,7 +159,8 @@ internal static UAuthSession FromProjection(
151159
long securityVersionAtCreation,
152160
DeviceContext device,
153161
ClaimsSnapshot claims,
154-
SessionMetadata metadata)
162+
SessionMetadata metadata,
163+
long version)
155164
{
156165
return new UAuthSession(
157166
sessionId,
@@ -166,7 +175,8 @@ internal static UAuthSession FromProjection(
166175
securityVersionAtCreation,
167176
device,
168177
claims,
169-
metadata
178+
metadata,
179+
version
170180
);
171181
}
172182

@@ -202,7 +212,8 @@ public UAuthSession WithChain(SessionChainId chainId)
202212
securityVersionAtCreation: SecurityVersionAtCreation,
203213
device: Device,
204214
claims: Claims,
205-
metadata: Metadata
215+
metadata: Metadata,
216+
version: Version + 1
206217
);
207218
}
208219

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using CodeBeam.UltimateAuth.Core.MultiTenancy;
1+
using CodeBeam.UltimateAuth.Core.Abstractions;
2+
using CodeBeam.UltimateAuth.Core.MultiTenancy;
23

34
namespace CodeBeam.UltimateAuth.Core.Domain;
45

5-
public sealed class UAuthSessionChain
6+
public sealed class UAuthSessionChain : IVersionedEntity
67
{
78
public SessionChainId ChainId { get; }
89
public SessionRootId RootId { get; }
@@ -14,6 +15,7 @@ public sealed class UAuthSessionChain
1415
public AuthSessionId? ActiveSessionId { get; }
1516
public bool IsRevoked { get; }
1617
public DateTimeOffset? RevokedAt { get; }
18+
public long Version { get; }
1719

1820
private UAuthSessionChain(
1921
SessionChainId chainId,
@@ -25,7 +27,8 @@ private UAuthSessionChain(
2527
ClaimsSnapshot claimsSnapshot,
2628
AuthSessionId? activeSessionId,
2729
bool isRevoked,
28-
DateTimeOffset? revokedAt)
30+
DateTimeOffset? revokedAt,
31+
long version)
2932
{
3033
ChainId = chainId;
3134
RootId = rootId;
@@ -37,6 +40,7 @@ private UAuthSessionChain(
3740
ActiveSessionId = activeSessionId;
3841
IsRevoked = isRevoked;
3942
RevokedAt = revokedAt;
43+
Version = version;
4044
}
4145

4246
public static UAuthSessionChain Create(
@@ -57,7 +61,8 @@ public static UAuthSessionChain Create(
5761
claimsSnapshot: claimsSnapshot,
5862
activeSessionId: null,
5963
isRevoked: false,
60-
revokedAt: null
64+
revokedAt: null,
65+
version: 0
6166
);
6267
}
6368

@@ -76,7 +81,8 @@ public UAuthSessionChain AttachSession(AuthSessionId sessionId)
7681
ClaimsSnapshot,
7782
activeSessionId: sessionId,
7883
isRevoked: false,
79-
revokedAt: null
84+
revokedAt: null,
85+
version: Version + 1
8086
);
8187
}
8288

@@ -95,7 +101,8 @@ public UAuthSessionChain RotateSession(AuthSessionId sessionId)
95101
ClaimsSnapshot,
96102
activeSessionId: sessionId,
97103
isRevoked: false,
98-
revokedAt: null
104+
revokedAt: null,
105+
version: Version + 1
99106
);
100107
}
101108

@@ -114,7 +121,8 @@ public UAuthSessionChain Revoke(DateTimeOffset at)
114121
ClaimsSnapshot,
115122
ActiveSessionId,
116123
isRevoked: true,
117-
revokedAt: at
124+
revokedAt: at,
125+
version: Version + 1
118126
);
119127
}
120128

@@ -128,7 +136,8 @@ internal static UAuthSessionChain FromProjection(
128136
ClaimsSnapshot claimsSnapshot,
129137
AuthSessionId? activeSessionId,
130138
bool isRevoked,
131-
DateTimeOffset? revokedAt)
139+
DateTimeOffset? revokedAt,
140+
long version)
132141
{
133142
return new UAuthSessionChain(
134143
chainId,
@@ -140,7 +149,8 @@ internal static UAuthSessionChain FromProjection(
140149
claimsSnapshot,
141150
activeSessionId,
142151
isRevoked,
143-
revokedAt
152+
revokedAt,
153+
version
144154
);
145155
}
146156

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using CodeBeam.UltimateAuth.Core.MultiTenancy;
1+
using CodeBeam.UltimateAuth.Core.Abstractions;
2+
using CodeBeam.UltimateAuth.Core.MultiTenancy;
23

34
namespace CodeBeam.UltimateAuth.Core.Domain;
45

5-
public sealed class UAuthSessionRoot
6+
public sealed class UAuthSessionRoot : IVersionedEntity
67
{
78
public SessionRootId RootId { get; }
89
public UserKey UserKey { get; }
@@ -12,6 +13,7 @@ public sealed class UAuthSessionRoot
1213
public long SecurityVersion { get; }
1314
public IReadOnlyList<UAuthSessionChain> Chains { get; }
1415
public DateTimeOffset LastUpdatedAt { get; }
16+
public long Version { get; }
1517

1618
private UAuthSessionRoot(
1719
SessionRootId rootId,
@@ -21,7 +23,8 @@ private UAuthSessionRoot(
2123
DateTimeOffset? revokedAt,
2224
long securityVersion,
2325
IReadOnlyList<UAuthSessionChain> chains,
24-
DateTimeOffset lastUpdatedAt)
26+
DateTimeOffset lastUpdatedAt,
27+
long version)
2528
{
2629
RootId = rootId;
2730
Tenant = tenant;
@@ -31,6 +34,7 @@ private UAuthSessionRoot(
3134
SecurityVersion = securityVersion;
3235
Chains = chains;
3336
LastUpdatedAt = lastUpdatedAt;
37+
Version = version;
3438
}
3539

3640
public static UAuthSessionRoot Create(
@@ -46,7 +50,8 @@ public static UAuthSessionRoot Create(
4650
revokedAt: null,
4751
securityVersion: 0,
4852
chains: Array.Empty<UAuthSessionChain>(),
49-
lastUpdatedAt: issuedAt
53+
lastUpdatedAt: issuedAt,
54+
version: 0
5055
);
5156
}
5257

@@ -63,7 +68,8 @@ public UAuthSessionRoot Revoke(DateTimeOffset at)
6368
revokedAt: at,
6469
securityVersion: SecurityVersion,
6570
chains: Chains,
66-
lastUpdatedAt: at
71+
lastUpdatedAt: at,
72+
version: Version + 1
6773
);
6874
}
6975

@@ -80,7 +86,8 @@ public UAuthSessionRoot AttachChain(UAuthSessionChain chain, DateTimeOffset at)
8086
RevokedAt,
8187
SecurityVersion,
8288
Chains.Concat(new[] { chain }).ToArray(),
83-
at
89+
at,
90+
Version + 1
8491
);
8592
}
8693

@@ -92,7 +99,8 @@ internal static UAuthSessionRoot FromProjection(
9299
DateTimeOffset? revokedAt,
93100
long securityVersion,
94101
IReadOnlyList<UAuthSessionChain> chains,
95-
DateTimeOffset lastUpdatedAt)
102+
DateTimeOffset lastUpdatedAt,
103+
long version)
96104
{
97105
return new UAuthSessionRoot(
98106
rootId,
@@ -102,9 +110,8 @@ internal static UAuthSessionRoot FromProjection(
102110
revokedAt,
103111
securityVersion,
104112
chains,
105-
lastUpdatedAt
113+
lastUpdatedAt,
114+
version
106115
);
107116
}
108-
109-
110117
}

src/CodeBeam.UltimateAuth.Core/Domain/Token/StoredRefreshToken.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using CodeBeam.UltimateAuth.Core.MultiTenancy;
1+
using CodeBeam.UltimateAuth.Core.Abstractions;
2+
using CodeBeam.UltimateAuth.Core.MultiTenancy;
23
using System.ComponentModel.DataAnnotations.Schema;
34

45
namespace CodeBeam.UltimateAuth.Core.Domain;
@@ -7,7 +8,7 @@ namespace CodeBeam.UltimateAuth.Core.Domain;
78
/// Represents a persisted refresh token bound to a session.
89
/// Stored as a hashed value for security reasons.
910
/// </summary>
10-
public sealed record StoredRefreshToken
11+
public sealed record StoredRefreshToken : IVersionedEntity
1112
{
1213
public string TokenHash { get; init; } = default!;
1314

@@ -24,6 +25,8 @@ public sealed record StoredRefreshToken
2425

2526
public string? ReplacedByTokenHash { get; init; }
2627

28+
public long Version { get; }
29+
2730
[NotMapped]
2831
public bool IsRevoked => RevokedAt.HasValue;
2932

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 sealed class UAuthConcurrencyException : UAuthRuntimeException
4+
{
5+
public override int StatusCode => 409;
6+
7+
public override string Title => "The resource was modified by another process.";
8+
9+
public override string TypePrefix => "https://docs.ultimateauth.com/errors/concurrency";
10+
11+
public UAuthConcurrencyException(string code = "concurrency_conflict") : base(code, code)
12+
{
13+
}
14+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using CodeBeam.UltimateAuth.Authorization.Domain;
2+
using CodeBeam.UltimateAuth.Core.Abstractions;
23

3-
public sealed class Role
4+
public sealed class Role : IVersionedEntity
45
{
56
public required string Name { get; init; }
67
public IReadOnlyCollection<Permission> Permissions { get; init; } = Array.Empty<Permission>();
8+
9+
public long Version { get; init; }
710
}

0 commit comments

Comments
 (0)