feat: harden SaaS authentication and authorization

This commit is contained in:
2026-07-28 12:15:51 +08:00
parent f22f329d33
commit 5d2248efee
123 changed files with 9090 additions and 2822 deletions

View File

@@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using System.Reflection;
using Tiku.Application.Security;
using Tiku.Domain.Catalog;
@@ -18,7 +21,7 @@ namespace Tiku.Infrastructure.Persistence;
public sealed class TikuDbContext(
DbContextOptions<TikuDbContext> options,
ITenantContext tenantContext) : DbContext(options)
ITenantContext tenantContext) : IdentityUserContext<User, Guid>(options), IDataProtectionKeyContext
{
public TikuDbContext(DbContextOptions<TikuDbContext> options)
: this(options, CreateToolingTenantContext())
@@ -37,7 +40,8 @@ public sealed class TikuDbContext(
return context;
}
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<User> Users => Set<User>();
public new DbSet<User> Users => Set<User>();
public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();
public DbSet<UserIdentity> UserIdentities => Set<UserIdentity>();
public DbSet<TenantMembership> TenantMemberships => Set<TenantMembership>();
public DbSet<TenantDomain> TenantDomains => Set<TenantDomain>();
@@ -49,8 +53,8 @@ public sealed class TikuDbContext(
public DbSet<SmsVerificationCode> SmsVerificationCodes => Set<SmsVerificationCode>();
public DbSet<AuthLoginEvent> AuthLoginEvents => Set<AuthLoginEvent>();
public DbSet<AuthSession> AuthSessions => Set<AuthSession>();
public DbSet<AuthChallenge> AuthChallenges => Set<AuthChallenge>();
public DbSet<SmsSendRateLimit> SmsSendRateLimits => Set<SmsSendRateLimit>();
public DbSet<TenantRoleTemplate> TenantRoleTemplates => Set<TenantRoleTemplate>();
public DbSet<TenantClass> TenantClasses => Set<TenantClass>();
public DbSet<TenantClassMember> TenantClassMembers => Set<TenantClassMember>();
public DbSet<TenantStudentNote> TenantStudentNotes => Set<TenantStudentNote>();
@@ -186,9 +190,14 @@ public sealed class TikuDbContext(
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUserClaim<Guid>>().ToTable("user_claims");
modelBuilder.Entity<IdentityUserLogin<Guid>>().ToTable("user_logins");
modelBuilder.Entity<IdentityUserToken<Guid>>().ToTable("user_tokens");
modelBuilder.HasPostgresExtension("citext");
modelBuilder.HasPostgresExtension("ltree");
modelBuilder.ApplyConfigurationsFromAssembly(typeof(TikuDbContext).Assembly);
modelBuilder.Entity<DataProtectionKey>().ToTable("data_protection_keys");
ApplyTenantQueryFilters(modelBuilder);
ValidateTenantModel(modelBuilder);
modelBuilder.UseSnakeCaseIdentifiers();
@@ -298,6 +307,15 @@ public sealed class TikuDbContext(
{
var now = DateTimeOffset.UtcNow;
foreach (var entry in ChangeTracker.Entries<User>().Where(entry => entry.State == EntityState.Modified))
{
if (entry.Property(user => user.Status).IsModified ||
entry.Property(user => user.PasswordHash).IsModified)
{
entry.Entity.SecurityStamp = Guid.NewGuid().ToString("N");
}
}
foreach (var entry in ChangeTracker.Entries<IHasTimestamps>())
{
if (entry.State == EntityState.Added)