refactor(architecture): harden module boundaries
Some checks failed
ci / release-gate (push) Has been cancelled

This commit is contained in:
2026-08-04 12:10:36 +08:00
parent b38f12e60b
commit 33375a38d7
218 changed files with 5124 additions and 3828 deletions

View File

@@ -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);
}