Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.Errors;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using System.Collections;

Expand Down Expand Up @@ -29,7 +30,7 @@ public sealed class AccessContext
public UserKey GetTargetUserKey()
{
if (TargetUserKey is not UserKey targetUserKey)
throw new InvalidOperationException("Target user is not found.");
throw new UAuthNotFoundException("Target user is not found.");

return targetUserKey;
}
Expand Down
17 changes: 6 additions & 11 deletions src/CodeBeam.UltimateAuth.Core/Domain/Session/UAuthSession.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using CodeBeam.UltimateAuth.Core.Abstractions;
using CodeBeam.UltimateAuth.Core.Errors;
using CodeBeam.UltimateAuth.Core.MultiTenancy;

namespace CodeBeam.UltimateAuth.Core.Domain;

// TODO: Add ISoftDeleteable
public sealed class UAuthSession : IVersionedEntity
{
public AuthSessionId SessionId { get; }
Expand All @@ -12,21 +12,21 @@ public sealed class UAuthSession : IVersionedEntity
public SessionChainId ChainId { get; }
public DateTimeOffset CreatedAt { get; }
public DateTimeOffset ExpiresAt { get; }
public bool IsRevoked { get; }
public DateTimeOffset? RevokedAt { get; }
public long SecurityVersionAtCreation { get; }
public ClaimsSnapshot Claims { get; }
public SessionMetadata Metadata { get; }
public long Version { get; set; }

public bool IsRevoked => RevokedAt != null;

private UAuthSession(
AuthSessionId sessionId,
TenantKey tenant,
UserKey userKey,
SessionChainId chainId,
DateTimeOffset createdAt,
DateTimeOffset expiresAt,
bool isRevoked,
DateTimeOffset? revokedAt,
long securityVersionAtCreation,
ClaimsSnapshot claims,
Expand All @@ -39,7 +39,6 @@ private UAuthSession(
ChainId = chainId;
CreatedAt = createdAt;
ExpiresAt = expiresAt;
IsRevoked = isRevoked;
RevokedAt = revokedAt;
SecurityVersionAtCreation = securityVersionAtCreation;
Claims = claims;
Expand All @@ -65,7 +64,6 @@ public static UAuthSession Create(
chainId,
createdAt: now,
expiresAt: expiresAt,
isRevoked: false,
revokedAt: null,
securityVersionAtCreation: securityVersion,
claims: claims ?? ClaimsSnapshot.Empty,
Expand All @@ -76,7 +74,8 @@ public static UAuthSession Create(

public UAuthSession Revoke(DateTimeOffset at)
{
if (IsRevoked) return this;
if (IsRevoked)
return this;

return new UAuthSession(
SessionId,
Expand All @@ -85,7 +84,6 @@ public UAuthSession Revoke(DateTimeOffset at)
ChainId,
CreatedAt,
ExpiresAt,
true,
at,
SecurityVersionAtCreation,
Claims,
Expand All @@ -101,7 +99,6 @@ internal static UAuthSession FromProjection(
SessionChainId chainId,
DateTimeOffset createdAt,
DateTimeOffset expiresAt,
bool isRevoked,
DateTimeOffset? revokedAt,
long securityVersionAtCreation,
ClaimsSnapshot claims,
Expand All @@ -115,7 +112,6 @@ internal static UAuthSession FromProjection(
chainId,
createdAt,
expiresAt,
isRevoked,
revokedAt,
securityVersionAtCreation,
claims,
Expand All @@ -138,7 +134,7 @@ public SessionState GetState(DateTimeOffset at)
public UAuthSession WithChain(SessionChainId chainId)
{
if (!ChainId.IsUnassigned)
throw new InvalidOperationException("Chain already assigned.");
throw new UAuthConflictException("Chain already assigned.");

return new UAuthSession(
sessionId: SessionId,
Expand All @@ -147,7 +143,6 @@ public UAuthSession WithChain(SessionChainId chainId)
chainId: chainId,
createdAt: CreatedAt,
expiresAt: ExpiresAt,
isRevoked: IsRevoked,
revokedAt: RevokedAt,
securityVersionAtCreation: SecurityVersionAtCreation,
claims: Claims,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using CodeBeam.UltimateAuth.Core.Abstractions;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using static CodeBeam.UltimateAuth.Core.Defaults.UAuthActions;

namespace CodeBeam.UltimateAuth.Core.Domain;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ public sealed class UAuthSessionRoot : IVersionedEntity

public DateTimeOffset CreatedAt { get; }
public DateTimeOffset? UpdatedAt { get; }

public bool IsRevoked { get; }
public DateTimeOffset? RevokedAt { get; }

public long SecurityVersion { get; }
public long Version { get; set; }

public bool IsRevoked => RevokedAt != null;

private UAuthSessionRoot(
SessionRootId rootId,
TenantKey tenant,
UserKey userKey,
DateTimeOffset createdAt,
DateTimeOffset? updatedAt,
bool isRevoked,
DateTimeOffset? revokedAt,
long securityVersion,
long version)
Expand All @@ -34,7 +33,6 @@ private UAuthSessionRoot(
UserKey = userKey;
CreatedAt = createdAt;
UpdatedAt = updatedAt;
IsRevoked = isRevoked;
RevokedAt = revokedAt;
SecurityVersion = securityVersion;
Version = version;
Expand All @@ -51,7 +49,6 @@ public static UAuthSessionRoot Create(
userKey,
at,
null,
false,
null,
0,
0
Expand All @@ -66,7 +63,6 @@ public UAuthSessionRoot IncreaseSecurityVersion(DateTimeOffset at)
UserKey,
CreatedAt,
at,
IsRevoked,
RevokedAt,
SecurityVersion + 1,
Version + 1
Expand All @@ -84,7 +80,6 @@ public UAuthSessionRoot Revoke(DateTimeOffset at)
UserKey,
CreatedAt,
at,
true,
at,
SecurityVersion + 1,
Version + 1
Expand All @@ -97,7 +92,6 @@ internal static UAuthSessionRoot FromProjection(
UserKey userKey,
DateTimeOffset createdAt,
DateTimeOffset? updatedAt,
bool isRevoked,
DateTimeOffset? revokedAt,
long securityVersion,
long version)
Expand All @@ -108,7 +102,6 @@ internal static UAuthSessionRoot FromProjection(
userKey,
createdAt,
updatedAt,
isRevoked,
revokedAt,
securityVersion,
version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CodeBeam.UltimateAuth.Core.Abstractions;
using CodeBeam.UltimateAuth.Core.Contracts;
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.Errors;
using CodeBeam.UltimateAuth.Server.Infrastructure;

namespace CodeBeam.UltimateAuth.Authorization.Reference;
Expand Down Expand Up @@ -33,7 +34,7 @@ public async Task AssignAsync(AccessContext context, UserKey targetUserKey, stri
var role = await _roles.GetByNameAsync(context.ResourceTenant, normalized, innerCt);

if (role is null || role.IsDeleted)
throw new InvalidOperationException("role_not_found");
throw new UAuthNotFoundException("role_not_found");

await _userRoles.AssignAsync(context.ResourceTenant, targetUserKey, role.Id, now, innerCt);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using CodeBeam.UltimateAuth.Authorization.Contracts;
using CodeBeam.UltimateAuth.Authorization.Domain;
using CodeBeam.UltimateAuth.Core.Abstractions;
using CodeBeam.UltimateAuth.Core.Errors;
using CodeBeam.UltimateAuth.Core.MultiTenancy;

namespace CodeBeam.UltimateAuth.Authorization;
Expand Down Expand Up @@ -35,7 +36,7 @@ public static Role Create(
DateTimeOffset now)
{
if (string.IsNullOrWhiteSpace(name))
throw new InvalidOperationException("role_name_required");
throw new UAuthValidationException("role_name_required");

var normalized = Normalize(name);

Expand All @@ -61,7 +62,7 @@ public static Role Create(
public Role Rename(string newName, DateTimeOffset now)
{
if (string.IsNullOrWhiteSpace(newName))
throw new InvalidOperationException("role_name_required");
throw new UAuthValidationException("role_name_required");

if (NormalizedName == Normalize(newName))
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ protected override void OnModelCreating(ModelBuilder b)
e.HasKey(x => x.Id);

e.Property(x => x.Version).IsConcurrencyToken();

e.Property(x => x.UserKey)
e.Property(x => x.UserKey).IsRequired();
e.Property(x => x.CreatedAt).IsRequired();
e.Property(x => x.Tenant)
.HasConversion(
v => v.Value,
v => TenantKey.FromInternal(v))
.HasMaxLength(128)
.IsRequired();

e.HasIndex(x => new { x.Tenant, x.UserKey }).IsUnique();
Expand All @@ -48,6 +53,7 @@ protected override void OnModelCreating(ModelBuilder b)
.HasConversion(
v => v.Value,
v => SessionRootId.From(v))
.HasMaxLength(128)
.IsRequired();
});

Expand All @@ -56,18 +62,43 @@ protected override void OnModelCreating(ModelBuilder b)
e.HasKey(x => x.Id);

e.Property(x => x.Version).IsConcurrencyToken();

e.Property(x => x.UserKey)
e.Property(x => x.UserKey).IsRequired();
e.Property(x => x.CreatedAt).IsRequired();
e.Property(x => x.Tenant)
.HasConversion(
v => v.Value,
v => TenantKey.FromInternal(v))
.HasMaxLength(128)
.IsRequired();

e.HasIndex(x => new { x.Tenant, x.ChainId }).IsUnique();
e.HasIndex(x => new { x.Tenant, x.UserKey });
e.HasIndex(x => new { x.Tenant, x.UserKey, x.DeviceId });
e.HasIndex(x => new { x.Tenant, x.RootId });

e.HasOne<SessionRootProjection>()
.WithMany()
.HasForeignKey(x => new { x.Tenant, x.RootId })
.HasPrincipalKey(x => new { x.Tenant, x.RootId })
.OnDelete(DeleteBehavior.Restrict);

e.Property(x => x.ChainId)
.HasConversion(
v => v.Value,
v => SessionChainId.From(v))
.IsRequired();

e.Property(x => x.DeviceId)
.HasConversion(
v => v.Value,
v => DeviceId.Create(v))
.HasMaxLength(64)
.IsRequired();

e.Property(x => x.Device)
.HasConversion(new JsonValueConverter<DeviceContext>())
.IsRequired();

e.Property(x => x.ActiveSessionId)
.HasConversion(new NullableAuthSessionIdConverter());

Expand All @@ -83,9 +114,26 @@ protected override void OnModelCreating(ModelBuilder b)
{
e.HasKey(x => x.Id);
e.Property(x => x.Version).IsConcurrencyToken();
e.Property(x => x.CreatedAt).IsRequired();
e.Property(x => x.Tenant)
.HasConversion(
v => v.Value,
v => TenantKey.FromInternal(v))
.HasMaxLength(128)
.IsRequired();

e.HasIndex(x => new { x.Tenant, x.SessionId }).IsUnique();
e.HasIndex(x => new { x.Tenant, x.ChainId });
e.HasIndex(x => new { x.Tenant, x.ChainId, x.RevokedAt });
e.HasIndex(x => new { x.Tenant, x.UserKey, x.RevokedAt });
e.HasIndex(x => new { x.Tenant, x.ExpiresAt });
e.HasIndex(x => new { x.Tenant, x.RevokedAt });

e.HasOne<SessionChainProjection>()
.WithMany()
.HasForeignKey(x => new { x.Tenant, x.ChainId })
.HasPrincipalKey(x => new { x.Tenant, x.ChainId })
.OnDelete(DeleteBehavior.Restrict);

e.Property(x => x.SessionId)
.HasConversion(new AuthSessionIdConverter())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset LastSeenAt { get; set; }
public DateTimeOffset? AbsoluteExpiresAt { get; set; }
public DeviceId DeviceId { get; set; }
public DeviceContext Device { get; set; }

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (10.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (8.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 19 in src/sessions/CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore/EntityProjections/SessionChainProjection.cs

View workflow job for this annotation

GitHub Actions / Build, Test & Coverage (9.0.x)

Non-nullable property 'Device' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public ClaimsSnapshot ClaimsSnapshot { get; set; } = ClaimsSnapshot.Empty;
public AuthSessionId? ActiveSessionId { get; set; }
public int RotationCount { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ internal sealed class SessionProjection

public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset ExpiresAt { get; set; }
public DateTimeOffset? LastSeenAt { get; set; }

public bool IsRevoked { get; set; }

public DateTimeOffset? RevokedAt { get; set; }

public long SecurityVersionAtCreation { get; set; }
Expand All @@ -27,4 +26,6 @@ internal sealed class SessionProjection
public SessionMetadata Metadata { get; set; } = SessionMetadata.Empty;

public long Version { get; set; }

public bool IsRevoked => RevokedAt != null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ internal sealed class SessionRootProjection
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset? UpdatedAt { get; set; }

public bool IsRevoked { get; set; }
public DateTimeOffset? RevokedAt { get; set; }

public long SecurityVersion { get; set; }
public long Version { get; set; }

public bool IsRevoked => RevokedAt != null;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Microsoft.EntityFrameworkCore;
using CodeBeam.UltimateAuth.Core.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddUltimateAuthEntityFrameworkCoreSessions<TUserId>(this IServiceCollection services,Action<DbContextOptionsBuilder> configureDb)where TUserId : notnull
public static IServiceCollection AddUltimateAuthEntityFrameworkCoreSessions(this IServiceCollection services,Action<DbContextOptionsBuilder> configureDb)
{
services.AddDbContext<UltimateAuthSessionDbContext>(configureDb);
services.AddScoped<EfCoreSessionStore>();
services.AddDbContextPool<UltimateAuthSessionDbContext>(configureDb);
services.AddScoped<ISessionStoreFactory, EfCoreSessionStoreFactory>();

return services;
}
Expand Down
Loading
Loading