feat: 支持租户伪装登录与管理员重置链接

This commit is contained in:
2025-12-15 14:43:50 +08:00
parent d64545dd26
commit 2249588e07
16 changed files with 478 additions and 2 deletions

View File

@@ -1,6 +1,8 @@
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Commands;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Constants;
@@ -14,10 +16,11 @@ namespace TakeoutSaaS.AdminApi.Controllers;
/// </summary>
/// <remarks>提供登录、刷新 Token 以及用户权限查询能力。</remarks>
/// <param name="authService">认证服务</param>
/// <param name="mediator">中介者。</param>
[ApiVersion("1.0")]
[Authorize]
[Route("api/admin/v{version:apiVersion}/auth")]
public sealed class AuthController(IAdminAuthService authService) : BaseApiController
public sealed class AuthController(IAdminAuthService authService, IMediator mediator) : BaseApiController
{
/// <summary>
/// 登录获取 Token
@@ -65,6 +68,26 @@ public sealed class AuthController(IAdminAuthService authService) : BaseApiContr
return ApiResponse<TokenResponse>.Ok(response);
}
/// <summary>
/// 通过重置链接令牌重置管理员密码。
/// </summary>
/// <remarks>令牌为一次性使用;成功后即可使用新密码登录。</remarks>
[HttpPost("reset-password")]
[AllowAnonymous]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
public async Task<ApiResponse<object>> ResetPassword([FromBody] ResetAdminPasswordRequest request, CancellationToken cancellationToken)
{
// 1. 通过令牌重置密码
await mediator.Send(new ResetAdminPasswordByTokenCommand
{
Token = request.Token,
NewPassword = request.NewPassword
}, cancellationToken);
// 2. 返回成功
return ApiResponse.Success("密码重置成功");
}
/// <summary>
/// 获取当前用户信息
/// </summary>

View File

@@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Module.Authorization.Attributes;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
@@ -318,6 +319,49 @@ public sealed class TenantsController(IMediator mediator) : BaseApiController
return ApiResponse<PagedResult<TenantAuditLogDto>>.Ok(result);
}
/// <summary>
/// 伪装登录租户(仅平台超级管理员可用)。
/// </summary>
/// <returns>目标租户主管理员的令牌对。</returns>
[HttpPost("{tenantId:long}/impersonate")]
[PermissionAuthorize("tenant:read")]
[ProducesResponseType(typeof(ApiResponse<TokenResponse>), StatusCodes.Status200OK)]
public async Task<ApiResponse<TokenResponse>> Impersonate(long tenantId, CancellationToken cancellationToken)
{
// 1. 执行伪装登录
var result = await mediator.Send(new ImpersonateTenantCommand { TenantId = tenantId }, cancellationToken);
// 2. 返回令牌
return ApiResponse<TokenResponse>.Ok(result);
}
/// <summary>
/// 生成租户主管理员重置密码链接(仅平台超级管理员可用)。
/// </summary>
/// <remarks>链接默认 24 小时有效且仅可使用一次。</remarks>
/// <returns>重置密码链接。</returns>
[HttpPost("{tenantId:long}/admin/reset-link")]
[PermissionAuthorize("tenant:read")]
[ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status200OK)]
public async Task<ApiResponse<string>> CreateAdminResetLink(long tenantId, CancellationToken cancellationToken)
{
// 1. 生成一次性令牌
var token = await mediator.Send(new CreateTenantAdminResetLinkTokenCommand { TenantId = tenantId }, cancellationToken);
// 2. (空行后) 解析前端来源(优先 Origin避免拼成 AdminApi 域名)
var origin = Request.Headers.Origin.ToString();
if (string.IsNullOrWhiteSpace(origin))
{
origin = $"{Request.Scheme}://{Request.Host}";
}
origin = origin.TrimEnd('/');
var resetUrl = $"{origin}/#/auth/reset-password?token={Uri.EscapeDataString(token)}";
// 3. (空行后) 返回链接
return ApiResponse<string>.Ok(resetUrl);
}
/// <summary>
/// 配额校验并占用额度(门店/账号/短信/配送)。
/// </summary>