feat(security): add distributed authorization foundation

This commit is contained in:
2026-07-29 10:40:10 +08:00
parent c7f9a4e3c9
commit df88fa19cb
76 changed files with 22020 additions and 88 deletions

View File

@@ -0,0 +1,13 @@
namespace Tiku.Infrastructure.Messaging;
public sealed class MessagingOptions
{
public string Host { get; set; } = string.Empty;
public string VirtualHost { get; set; } = "/";
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool ConfigureConsumers { get; set; }
public bool IsConfigured => Uri.TryCreate(Host, UriKind.Absolute, out var uri) &&
uri.Scheme is "rabbitmq" or "amqp" or "amqps";
}

View File

@@ -0,0 +1,70 @@
using MassTransit;
using Tiku.Contracts;
namespace Tiku.Infrastructure.Messaging;
public interface ISecurityEventPublisher
{
Task AuthorizationChangedAsync(
Guid? tenantId,
Guid? userId,
string changeKind,
long version,
string correlationId,
CancellationToken cancellationToken = default);
Task CapabilityChangedAsync(
Guid tenantId,
string moduleCode,
string changeKind,
long version,
string correlationId,
CancellationToken cancellationToken = default);
Task MembershipChangedAsync(
Guid tenantId,
Guid userId,
string previousStatus,
string currentStatus,
string correlationId,
CancellationToken cancellationToken = default);
}
internal sealed class NullSecurityEventPublisher : ISecurityEventPublisher
{
public Task AuthorizationChangedAsync(
Guid? tenantId, Guid? userId, string changeKind, long version,
string correlationId, CancellationToken cancellationToken = default) => Task.CompletedTask;
public Task CapabilityChangedAsync(
Guid tenantId, string moduleCode, string changeKind, long version,
string correlationId, CancellationToken cancellationToken = default) => Task.CompletedTask;
public Task MembershipChangedAsync(
Guid tenantId, Guid userId, string previousStatus, string currentStatus,
string correlationId, CancellationToken cancellationToken = default) => Task.CompletedTask;
}
internal sealed class MassTransitSecurityEventPublisher(IPublishEndpoint publishEndpoint) : ISecurityEventPublisher
{
public Task AuthorizationChangedAsync(
Guid? tenantId, Guid? userId, string changeKind, long version,
string correlationId, CancellationToken cancellationToken = default) =>
publishEndpoint.Publish(new AuthorizationStateChangedV1(
Guid.NewGuid(), tenantId, userId, changeKind, version,
DateTimeOffset.UtcNow, correlationId), cancellationToken);
public Task CapabilityChangedAsync(
Guid tenantId, string moduleCode, string changeKind, long version,
string correlationId, CancellationToken cancellationToken = default) =>
publishEndpoint.Publish(new TenantCapabilityChangedV1(
Guid.NewGuid(), tenantId, moduleCode, changeKind, version,
DateTimeOffset.UtcNow, correlationId), cancellationToken);
public Task MembershipChangedAsync(
Guid tenantId, Guid userId, string previousStatus, string currentStatus,
string correlationId, CancellationToken cancellationToken = default) =>
publishEndpoint.Publish(new MembershipLifecycleChangedV1(
Guid.NewGuid(), tenantId, userId, previousStatus, currentStatus,
DateTimeOffset.UtcNow, correlationId), cancellationToken);
}

View File

@@ -0,0 +1,26 @@
using MassTransit;
using Tiku.Application.Security;
using Tiku.Contracts;
namespace Tiku.Infrastructure.Messaging;
internal sealed class SecurityStateChangedConsumer(IRedisSecurityStore redisSecurityStore) :
IConsumer<AuthorizationStateChangedV1>,
IConsumer<TenantCapabilityChangedV1>,
IConsumer<MembershipLifecycleChangedV1>
{
public Task Consume(ConsumeContext<AuthorizationStateChangedV1> context) =>
redisSecurityStore.SetInvalidationVersionAsync(
"authorization", context.Message.TenantId, context.Message.UserId,
context.Message.Version, context.CancellationToken);
public Task Consume(ConsumeContext<TenantCapabilityChangedV1> context) =>
redisSecurityStore.SetInvalidationVersionAsync(
$"capability-{context.Message.ModuleCode}", context.Message.TenantId, null,
context.Message.Version, context.CancellationToken);
public Task Consume(ConsumeContext<MembershipLifecycleChangedV1> context) =>
redisSecurityStore.SetInvalidationVersionAsync(
"membership", context.Message.TenantId, context.Message.UserId,
context.Message.OccurredAt.ToUnixTimeMilliseconds(), context.CancellationToken);
}