refactor(architecture): harden module boundaries
Some checks failed
ci / release-gate (push) Has been cancelled
Some checks failed
ci / release-gate (push) Has been cancelled
This commit is contained in:
@@ -18,7 +18,9 @@ using ZLinq;
|
||||
namespace Tiku.Infrastructure.Auth;
|
||||
|
||||
public sealed class AuthSessionStore(
|
||||
TikuDbContext dbContext,
|
||||
IIdentityPersistence identityPersistence,
|
||||
ITenancyPersistence tenancyPersistence,
|
||||
IJobsOperationsPersistence jobsOperationsPersistence,
|
||||
ITokenService tokenService,
|
||||
IOptions<JwtOptions> options,
|
||||
IAccessSecurityCache? configuredAccessSecurityCache = null,
|
||||
@@ -80,8 +82,8 @@ public sealed class AuthSessionStore(
|
||||
var session = CreateSession(request, Guid.NewGuid());
|
||||
var refreshToken = GenerateRefreshToken(session.Realm, session.TenantId, session.Id);
|
||||
session.TokenHash = HashRefreshToken(refreshToken);
|
||||
dbContext.AuthSessions.Add(session);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
identityPersistence.AuthSessions.Add(session);
|
||||
await identityPersistence.SaveChangesAsync(cancellationToken);
|
||||
return CreatePair(request, session, refreshToken);
|
||||
}
|
||||
|
||||
@@ -96,8 +98,8 @@ public sealed class AuthSessionStore(
|
||||
var tokenHash = HashRefreshToken(refreshToken);
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
AuthorizationCacheTelemetry.PostgresFallback();
|
||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
|
||||
var current = await dbContext.AuthSessions.SingleOrDefaultAsync(
|
||||
await using var transaction = await identityPersistence.Database.BeginTransactionAsync(cancellationToken);
|
||||
var current = await identityPersistence.AuthSessions.SingleOrDefaultAsync(
|
||||
item => item.Id == locator.SessionId && item.Realm == locator.Realm &&
|
||||
item.TenantId == locator.TenantId && item.TokenHash == tokenHash,
|
||||
cancellationToken);
|
||||
@@ -110,7 +112,7 @@ public sealed class AuthSessionStore(
|
||||
throw new SessionRevokedException();
|
||||
}
|
||||
|
||||
var user = await dbContext.Users.SingleOrDefaultAsync(item => item.Id == current.UserId, cancellationToken);
|
||||
var user = await identityPersistence.Users.SingleOrDefaultAsync(item => item.Id == current.UserId, cancellationToken);
|
||||
if (user is null || user.Status != UserStatus.Active ||
|
||||
!string.Equals(user.SecurityStamp, current.SecurityStamp, StringComparison.Ordinal))
|
||||
{
|
||||
@@ -132,7 +134,7 @@ public sealed class AuthSessionStore(
|
||||
}
|
||||
|
||||
var nextId = Guid.NewGuid();
|
||||
var updated = await dbContext.AuthSessions
|
||||
var updated = await identityPersistence.AuthSessions
|
||||
.Where(item => item.Id == current.Id && item.RevokedAt == null && item.ReplacedBySessionId == null)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(item => item.RevokedAt, now)
|
||||
@@ -152,8 +154,8 @@ public sealed class AuthSessionStore(
|
||||
var next = CreateSession(request, nextId);
|
||||
var nextToken = GenerateRefreshToken(next.Realm, next.TenantId, next.Id);
|
||||
next.TokenHash = HashRefreshToken(nextToken);
|
||||
dbContext.AuthSessions.Add(next);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
identityPersistence.AuthSessions.Add(next);
|
||||
await identityPersistence.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
await stateInvalidator.InvalidateSessionAsync(current.Id, cancellationToken);
|
||||
return CreatePair(request, next, nextToken);
|
||||
@@ -199,8 +201,8 @@ public sealed class AuthSessionStore(
|
||||
try
|
||||
{
|
||||
state = await (
|
||||
from session in dbContext.AuthSessions.AsNoTracking()
|
||||
join user in dbContext.Users.AsNoTracking() on session.UserId equals user.Id
|
||||
from session in identityPersistence.AuthSessions.AsNoTracking()
|
||||
join user in identityPersistence.Users.AsNoTracking() on session.UserId equals user.Id
|
||||
where session.Id == sessionId &&
|
||||
session.UserId == userId &&
|
||||
session.Realm == realm &&
|
||||
@@ -211,32 +213,32 @@ public sealed class AuthSessionStore(
|
||||
session.SecurityStamp,
|
||||
realm != AuthRealm.Tenant ||
|
||||
(tenantId != null &&
|
||||
dbContext.Tenants.Any(item => item.Id == tenantId && item.Status == TenantStatus.Active) &&
|
||||
dbContext.TenantMemberships.Any(item =>
|
||||
tenancyPersistence.Tenants.Any(item => item.Id == tenantId && item.Status == TenantStatus.Active) &&
|
||||
identityPersistence.TenantMemberships.Any(item =>
|
||||
item.TenantId == tenantId &&
|
||||
item.UserId == userId &&
|
||||
item.Status == MembershipStatus.Active)),
|
||||
realm != AuthRealm.Platform ||
|
||||
(from userRole in dbContext.PlatformBackendUserRoles
|
||||
join role in dbContext.PlatformBackendRoles on userRole.RoleId equals role.Id
|
||||
join binding in dbContext.PlatformBackendRolePermissions on role.Id equals binding.RoleId
|
||||
join permission in dbContext.BackendPermissions on binding.PermissionCode equals permission
|
||||
.Code
|
||||
where userRole.UserId == userId &&
|
||||
role.Status == BackendRoleStatus.Active &&
|
||||
(permission.Area == BackendPermissionArea.Platform ||
|
||||
permission.Area == BackendPermissionArea.Both)
|
||||
select permission.Id).Any(),
|
||||
(from userRole in jobsOperationsPersistence.PlatformBackendUserRoles
|
||||
join role in jobsOperationsPersistence.PlatformBackendRoles on userRole.RoleId equals role.Id
|
||||
join binding in jobsOperationsPersistence.PlatformBackendRolePermissions on role.Id equals binding.RoleId
|
||||
join permission in jobsOperationsPersistence.BackendPermissions on binding.PermissionCode equals permission
|
||||
.Code
|
||||
where userRole.UserId == userId &&
|
||||
role.Status == BackendRoleStatus.Active &&
|
||||
(permission.Area == BackendPermissionArea.Platform ||
|
||||
permission.Area == BackendPermissionArea.Both)
|
||||
select permission.Id).Any(),
|
||||
realm == AuthRealm.Tenant && tenantId != null
|
||||
? dbContext.Tenants.Where(item => item.Id == tenantId)
|
||||
? tenancyPersistence.Tenants.Where(item => item.Id == tenantId)
|
||||
.Select(item => (TenantStatus?)item.Status).FirstOrDefault()
|
||||
: null,
|
||||
realm == AuthRealm.Tenant && tenantId != null
|
||||
? dbContext.TenantMemberships
|
||||
? identityPersistence.TenantMemberships
|
||||
.Where(item => item.TenantId == tenantId && item.UserId == userId)
|
||||
.Select(item => (MembershipStatus?)item.Status).FirstOrDefault()
|
||||
: null,
|
||||
dbContext.AuthorizationScopeVersions
|
||||
jobsOperationsPersistence.AuthorizationScopeVersions
|
||||
.Where(item => item.Realm == realm && item.TenantId == tenantId)
|
||||
.Select(item => (long?)item.Version).FirstOrDefault() ?? 1L,
|
||||
session.ExpiresAt,
|
||||
@@ -297,7 +299,7 @@ public sealed class AuthSessionStore(
|
||||
Guid userId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var session = await dbContext.AuthSessions.AsNoTracking()
|
||||
var session = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.Where(item => item.Id == sessionId && item.UserId == userId)
|
||||
.Select(item => new { item.Realm, item.TenantId })
|
||||
.SingleOrDefaultAsync(cancellationToken);
|
||||
@@ -317,7 +319,7 @@ public sealed class AuthSessionStore(
|
||||
if (!TryParseRefreshToken(refreshToken, out var locator)) return;
|
||||
|
||||
var hash = HashRefreshToken(refreshToken);
|
||||
var session = await dbContext.AuthSessions.AsNoTracking().SingleOrDefaultAsync(
|
||||
var session = await identityPersistence.AuthSessions.AsNoTracking().SingleOrDefaultAsync(
|
||||
item => item.Id == locator.SessionId && item.TokenHash == hash, cancellationToken);
|
||||
if (session is not null)
|
||||
await RevokeFamilyCoreAsync(session.TokenFamilyId, reason, DateTimeOffset.UtcNow, cancellationToken);
|
||||
@@ -326,16 +328,16 @@ public sealed class AuthSessionStore(
|
||||
public async Task RevokeAllAsync(Guid userId, string reason, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var sessionIds = await dbContext.AuthSessions.AsNoTracking()
|
||||
var sessionIds = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.Where(item => item.UserId == userId && item.RevokedAt == null)
|
||||
.Select(item => item.Id).ToArrayAsync(cancellationToken);
|
||||
var count = await dbContext.AuthSessions.Where(item => item.UserId == userId && item.RevokedAt == null)
|
||||
var count = await identityPersistence.AuthSessions.Where(item => item.UserId == userId && item.RevokedAt == null)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(item => item.RevokedAt, DateTimeOffset.UtcNow)
|
||||
.SetProperty(item => item.RevokedReason, reason), cancellationToken);
|
||||
if (count > 0)
|
||||
{
|
||||
dbContext.AuditLogs.Add(new AuditLog
|
||||
jobsOperationsPersistence.AuditLogs.Add(new AuditLog
|
||||
{
|
||||
ActorUserId = userId,
|
||||
Action = "auth.sessions.revoked_all",
|
||||
@@ -343,7 +345,7 @@ public sealed class AuthSessionStore(
|
||||
TargetId = userId.ToString(),
|
||||
Details = JsonSerializer.SerializeToElement(new { reason, count, revokedAt = now })
|
||||
});
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
await identityPersistence.SaveChangesAsync(cancellationToken);
|
||||
foreach (var sessionId in sessionIds)
|
||||
await stateInvalidator.InvalidateSessionAsync(sessionId, cancellationToken);
|
||||
await stateInvalidator.InvalidateUserAsync(userId, cancellationToken);
|
||||
@@ -359,11 +361,11 @@ public sealed class AuthSessionStore(
|
||||
{
|
||||
ValidateRealm(realm, tenantId);
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var sessionIds = await dbContext.AuthSessions.AsNoTracking()
|
||||
var sessionIds = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.Where(item => item.UserId == userId && item.Realm == realm && item.TenantId == tenantId &&
|
||||
item.RevokedAt == null)
|
||||
.Select(item => item.Id).ToArrayAsync(cancellationToken);
|
||||
var count = await dbContext.AuthSessions
|
||||
var count = await identityPersistence.AuthSessions
|
||||
.Where(item => item.UserId == userId && item.Realm == realm && item.TenantId == tenantId &&
|
||||
item.RevokedAt == null)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
@@ -371,7 +373,7 @@ public sealed class AuthSessionStore(
|
||||
.SetProperty(item => item.RevokedReason, reason), cancellationToken);
|
||||
if (count > 0)
|
||||
{
|
||||
dbContext.AuditLogs.Add(new AuditLog
|
||||
jobsOperationsPersistence.AuditLogs.Add(new AuditLog
|
||||
{
|
||||
TenantId = tenantId,
|
||||
ActorUserId = userId,
|
||||
@@ -380,7 +382,7 @@ public sealed class AuthSessionStore(
|
||||
TargetId = userId.ToString(),
|
||||
Details = JsonSerializer.SerializeToElement(new { realm, reason, count, revokedAt = now })
|
||||
});
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
await identityPersistence.SaveChangesAsync(cancellationToken);
|
||||
foreach (var sessionId in sessionIds)
|
||||
await stateInvalidator.InvalidateSessionAsync(sessionId, cancellationToken);
|
||||
}
|
||||
@@ -391,12 +393,12 @@ public sealed class AuthSessionStore(
|
||||
Guid currentSessionId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var current = await dbContext.AuthSessions.AsNoTracking()
|
||||
var current = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.SingleOrDefaultAsync(item => item.Id == currentSessionId && item.UserId == userId,
|
||||
cancellationToken)
|
||||
?? throw new SessionRevokedException();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var sessions = await dbContext.AuthSessions.AsNoTracking()
|
||||
var sessions = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.Where(item => item.UserId == userId && item.Realm == current.Realm && item.TenantId == current.TenantId)
|
||||
.OrderBy(item => item.CreatedAt)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
@@ -432,13 +434,13 @@ public sealed class AuthSessionStore(
|
||||
Guid sessionFamilyId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var current = await dbContext.AuthSessions.AsNoTracking()
|
||||
var current = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.SingleOrDefaultAsync(item => item.Id == currentSessionId && item.UserId == userId,
|
||||
cancellationToken)
|
||||
?? throw new SessionRevokedException();
|
||||
if (current.TokenFamilyId == sessionFamilyId) throw new CurrentAuthSessionCannotBeRevokedException();
|
||||
|
||||
var owned = await dbContext.AuthSessions.AsNoTracking().AnyAsync(
|
||||
var owned = await identityPersistence.AuthSessions.AsNoTracking().AnyAsync(
|
||||
item => item.UserId == userId && item.TokenFamilyId == sessionFamilyId &&
|
||||
item.Realm == current.Realm && item.TenantId == current.TenantId,
|
||||
cancellationToken);
|
||||
@@ -523,9 +525,9 @@ public sealed class AuthSessionStore(
|
||||
if (realm == AuthRealm.Tenant && tenantId.HasValue)
|
||||
{
|
||||
var active =
|
||||
await dbContext.Tenants.AnyAsync(item => item.Id == tenantId && item.Status == TenantStatus.Active,
|
||||
await tenancyPersistence.Tenants.AnyAsync(item => item.Id == tenantId && item.Status == TenantStatus.Active,
|
||||
cancellationToken) &&
|
||||
await dbContext.TenantMemberships.AnyAsync(
|
||||
await identityPersistence.TenantMemberships.AnyAsync(
|
||||
item => item.TenantId == tenantId && item.UserId == userId &&
|
||||
item.Status == MembershipStatus.Active, cancellationToken);
|
||||
if (active) return;
|
||||
@@ -533,10 +535,10 @@ public sealed class AuthSessionStore(
|
||||
else if (realm == AuthRealm.Platform)
|
||||
{
|
||||
var active = await (
|
||||
from userRole in dbContext.PlatformBackendUserRoles
|
||||
join role in dbContext.PlatformBackendRoles on userRole.RoleId equals role.Id
|
||||
join binding in dbContext.PlatformBackendRolePermissions on role.Id equals binding.RoleId
|
||||
join permission in dbContext.BackendPermissions on binding.PermissionCode equals permission.Code
|
||||
from userRole in jobsOperationsPersistence.PlatformBackendUserRoles
|
||||
join role in jobsOperationsPersistence.PlatformBackendRoles on userRole.RoleId equals role.Id
|
||||
join binding in jobsOperationsPersistence.PlatformBackendRolePermissions on role.Id equals binding.RoleId
|
||||
join permission in jobsOperationsPersistence.BackendPermissions on binding.PermissionCode equals permission.Code
|
||||
where userRole.UserId == userId && role.Status == BackendRoleStatus.Active &&
|
||||
(permission.Area == BackendPermissionArea.Platform ||
|
||||
permission.Area == BackendPermissionArea.Both)
|
||||
@@ -550,20 +552,20 @@ public sealed class AuthSessionStore(
|
||||
private async Task<int> RevokeFamilyCoreAsync(Guid familyId, string reason, DateTimeOffset now,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var sessionIds = await dbContext.AuthSessions.AsNoTracking()
|
||||
var sessionIds = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.Where(item => item.TokenFamilyId == familyId && item.RevokedAt == null)
|
||||
.Select(item => item.Id).ToArrayAsync(cancellationToken);
|
||||
var owner = await dbContext.AuthSessions.AsNoTracking()
|
||||
var owner = await identityPersistence.AuthSessions.AsNoTracking()
|
||||
.Where(item => item.TokenFamilyId == familyId)
|
||||
.Select(item => new { item.UserId, item.TenantId })
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
var count = await dbContext.AuthSessions.Where(item => item.TokenFamilyId == familyId && item.RevokedAt == null)
|
||||
var count = await identityPersistence.AuthSessions.Where(item => item.TokenFamilyId == familyId && item.RevokedAt == null)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(item => item.RevokedAt, now)
|
||||
.SetProperty(item => item.RevokedReason, reason), cancellationToken);
|
||||
if (count > 0 && owner is not null)
|
||||
{
|
||||
dbContext.AuditLogs.Add(new AuditLog
|
||||
jobsOperationsPersistence.AuditLogs.Add(new AuditLog
|
||||
{
|
||||
TenantId = owner.TenantId,
|
||||
ActorUserId = owner.UserId,
|
||||
@@ -572,7 +574,7 @@ public sealed class AuthSessionStore(
|
||||
TargetId = familyId.ToString(),
|
||||
Details = JsonSerializer.SerializeToElement(new { reason, count, revokedAt = now })
|
||||
});
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
await identityPersistence.SaveChangesAsync(cancellationToken);
|
||||
foreach (var sessionId in sessionIds)
|
||||
await stateInvalidator.InvalidateSessionAsync(sessionId, cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user