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

@@ -108,9 +108,6 @@ public sealed class CommissionEndpointTests
User(admin, "tenant_admin"),
User(referrer, "sales"),
User(student, "student"),
Identity(admin),
Identity(referrer),
Identity(student),
Membership(admin, TenantRole.TenantAdmin),
Membership(referrer, TenantRole.Sales),
Membership(student, TenantRole.Student),
@@ -156,22 +153,13 @@ public sealed class CommissionEndpointTests
return new CommissionSeed(tenantId, admin, referrer, student);
}
private static User User(LoginSeed seed, string role) => new() { Id = seed.UserId, Phone = seed.Phone, Name = role, PrimaryRole = role };
private static User User(LoginSeed seed, string role) =>
new User { Id = seed.UserId, Phone = seed.Phone, Name = role, PrimaryRole = role }.WithTestPassword();
private static TenantMembership Membership(LoginSeed seed, TenantRole role) => new() { TenantId = seed.TenantId, UserId = seed.UserId, Role = role, Status = MembershipStatus.Active };
private static UserIdentity Identity(LoginSeed seed) => new() { UserId = seed.UserId, Provider = "password", ProviderSubject = seed.Phone, Phone = seed.Phone, SecretPayload = CreateSecretPayload(new PasswordHasher().Hash("passw0rd!")) };
private static async Task LoginAsync(HttpClient client, LoginSeed seed)
{
var loginResponse = await client.PostAsJsonAsync("/api/auth/login/password", new PasswordLoginDto { TenantCode = seed.TenantId.ToString("N"), Phone = seed.Phone, Password = "passw0rd!" });
loginResponse.EnsureSuccessStatusCode();
using var loginJson = await JsonDocument.ParseAsync(await loginResponse.Content.ReadAsStreamAsync());
client.DefaultRequestHeaders.Authorization = new("Bearer", loginJson.RootElement.GetProperty("tokens").GetProperty("accessToken").GetString());
}
private static JsonElement CreateSecretPayload(string passwordHash)
{
using var document = JsonDocument.Parse($$"""{"passwordHash":{{JsonSerializer.Serialize(passwordHash)}}}""");
return document.RootElement.Clone();
client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));
}
private sealed record LoginSeed(Guid TenantId, Guid UserId, string Phone);