fix: 自助注册回填主管理员

This commit is contained in:
2025-12-15 15:41:43 +08:00
parent 2150ae8f8c
commit 9d80f02bc5
3 changed files with 50 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
using MediatR; using MediatR;
using TakeoutSaaS.Application.App.Tenants.Commands; using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Domain.Tenants.Repositories;
@@ -17,6 +18,8 @@ namespace TakeoutSaaS.Application.App.Tenants.Handlers;
public sealed class CreateTenantAdminResetLinkTokenCommandHandler( public sealed class CreateTenantAdminResetLinkTokenCommandHandler(
ITenantRepository tenantRepository, ITenantRepository tenantRepository,
ITenantProvider tenantProvider, ITenantProvider tenantProvider,
ITenantContextAccessor tenantContextAccessor,
IIdentityUserRepository identityUserRepository,
ICurrentUserAccessor currentUserAccessor, ICurrentUserAccessor currentUserAccessor,
IAdminAuthService adminAuthService, IAdminAuthService adminAuthService,
IAdminPasswordResetTokenStore tokenStore) IAdminPasswordResetTokenStore tokenStore)
@@ -38,9 +41,28 @@ public sealed class CreateTenantAdminResetLinkTokenCommandHandler(
var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
// 2.1 (空行后) 若缺少主管理员则自动回填(兼容历史数据)
if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0) if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0)
{ {
throw new BusinessException(ErrorCodes.BadRequest, "该租户未配置主管理员账号,无法生成重置链接"); var originalContextForFix = tenantContextAccessor.Current;
tenantContextAccessor.Current = new TenantContext(tenant.Id, tenant.Code, "admin:reset-link:fix-owner");
try
{
var users = await identityUserRepository.SearchAsync(tenant.Id, keyword: null, cancellationToken);
var ownerCandidate = users.OrderBy(x => x.CreatedAt).FirstOrDefault();
if (ownerCandidate == null)
{
throw new BusinessException(ErrorCodes.BadRequest, "该租户未配置主管理员账号,且未找到可用管理员账号");
}
tenant.PrimaryOwnerUserId = ownerCandidate.Id;
await tenantRepository.UpdateTenantAsync(tenant, cancellationToken);
await tenantRepository.SaveChangesAsync(cancellationToken);
}
finally
{
tenantContextAccessor.Current = originalContextForFix;
}
} }
// 3. (空行后) 签发一次性重置令牌(默认 24 小时有效) // 3. (空行后) 签发一次性重置令牌(默认 24 小时有效)
@@ -70,4 +92,3 @@ public sealed class CreateTenantAdminResetLinkTokenCommandHandler(
return token; return token;
} }
} }

View File

@@ -2,6 +2,7 @@ using MediatR;
using TakeoutSaaS.Application.App.Tenants.Commands; using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.Identity.Abstractions; using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Contracts; using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Domain.Tenants.Entities; using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums; using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Repositories; using TakeoutSaaS.Domain.Tenants.Repositories;
@@ -19,6 +20,7 @@ public sealed class ImpersonateTenantCommandHandler(
ITenantRepository tenantRepository, ITenantRepository tenantRepository,
ITenantProvider tenantProvider, ITenantProvider tenantProvider,
ITenantContextAccessor tenantContextAccessor, ITenantContextAccessor tenantContextAccessor,
IIdentityUserRepository identityUserRepository,
ICurrentUserAccessor currentUserAccessor, ICurrentUserAccessor currentUserAccessor,
IAdminAuthService adminAuthService, IAdminAuthService adminAuthService,
IJwtTokenService jwtTokenService) IJwtTokenService jwtTokenService)
@@ -46,9 +48,28 @@ public sealed class ImpersonateTenantCommandHandler(
var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken) var tenant = await tenantRepository.FindByIdAsync(request.TenantId, cancellationToken)
?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在"); ?? throw new BusinessException(ErrorCodes.NotFound, "租户不存在");
// 2.1 (空行后) 若缺少主管理员则自动回填(兼容历史数据)
if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0) if (!tenant.PrimaryOwnerUserId.HasValue || tenant.PrimaryOwnerUserId.Value == 0)
{ {
throw new BusinessException(ErrorCodes.BadRequest, "该租户未配置主管理员账号,无法伪装登录"); var originalContextForFix = tenantContextAccessor.Current;
tenantContextAccessor.Current = new TenantContext(tenant.Id, tenant.Code, "admin:impersonate:fix-owner");
try
{
var users = await identityUserRepository.SearchAsync(tenant.Id, keyword: null, cancellationToken);
var ownerCandidate = users.OrderBy(x => x.CreatedAt).FirstOrDefault();
if (ownerCandidate == null)
{
throw new BusinessException(ErrorCodes.BadRequest, "该租户未配置主管理员账号,且未找到可用管理员账号");
}
tenant.PrimaryOwnerUserId = ownerCandidate.Id;
await tenantRepository.UpdateTenantAsync(tenant, cancellationToken);
await tenantRepository.SaveChangesAsync(cancellationToken);
}
finally
{
tenantContextAccessor.Current = originalContextForFix;
}
} }
// 3. (空行后) 进入目标租户上下文以读取租户内用户(避免多租户查询过滤导致找不到用户) // 3. (空行后) 进入目标租户上下文以读取租户内用户(避免多租户查询过滤导致找不到用户)

View File

@@ -95,6 +95,11 @@ public sealed class SelfRegisterTenantCommandHandler(
await identityUserRepository.AddAsync(adminUser, cancellationToken); await identityUserRepository.AddAsync(adminUser, cancellationToken);
await identityUserRepository.SaveChangesAsync(cancellationToken); await identityUserRepository.SaveChangesAsync(cancellationToken);
// 7.1 (空行后) 回填主管理员标识,确保后续伪装登录/重置管理员等能力可用
tenant.PrimaryOwnerUserId = adminUser.Id;
await tenantRepository.UpdateTenantAsync(tenant, cancellationToken);
await tenantRepository.SaveChangesAsync(cancellationToken);
// 8. 初始化租户管理员角色模板 // 8. 初始化租户管理员角色模板
await mediator.Send(new InitializeRoleTemplatesCommand await mediator.Send(new InitializeRoleTemplatesCommand
{ {