feat: enforce tenant isolation and shared question bank

This commit is contained in:
2026-07-27 16:59:12 +08:00
parent 28e9a9fa41
commit db4c7b4496
137 changed files with 6402 additions and 112274 deletions

View File

@@ -2,13 +2,21 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Tiku.Application.Auth;
using Tiku.Api.Contracts;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
using Tiku.Infrastructure.Content;
namespace Tiku.Api.Controllers;
[ApiController]
[Route("api/auth")]
[Produces("application/json")]
public sealed class AuthController(IAuthService authService) : ControllerBase
public sealed class AuthController(
IAuthService authService,
ISessionService sessionService,
ITenantContext tenantContext,
ITenantContextInitializer tenantContextInitializer,
ITenantDirectory tenantDirectory) : ControllerBase
{
[AllowAnonymous]
[HttpPost("login/password")]
@@ -22,7 +30,7 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
{
var result = await authService.LoginWithPasswordAsync(
new PasswordLoginRequest(
request.TenantId,
await ResolveTenantIdAsync(request.TenantCode, cancellationToken),
request.Phone,
request.Password,
GetIpAddress(),
@@ -44,7 +52,7 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
{
var result = await authService.LoginWithSmsAsync(
new SmsLoginRequest(
request.TenantId,
await ResolveTenantIdAsync(request.TenantCode, cancellationToken),
request.Phone,
request.Code,
GetIpAddress(),
@@ -67,7 +75,7 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
{
var result = await authService.LoginWithWechatWebAsync(
new WechatLoginRequest(
request.TenantId,
await ResolveTenantIdAsync(request.TenantCode, cancellationToken),
request.Code,
GetIpAddress(),
Request.Headers.UserAgent.ToString()),
@@ -89,7 +97,7 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
{
var result = await authService.LoginWithWechatMiniAppAsync(
new WechatLoginRequest(
request.TenantId,
await ResolveTenantIdAsync(request.TenantCode, cancellationToken),
request.Code,
GetIpAddress(),
Request.Headers.UserAgent.ToString()),
@@ -106,6 +114,7 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
[FromBody] RefreshSessionDto request,
CancellationToken cancellationToken)
{
ResolveRefreshTokenTenant(request.RefreshToken);
var result = await authService.RefreshAsync(
new RefreshSessionRequest(
request.RefreshToken,
@@ -125,6 +134,7 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
[FromBody] RefreshSessionDto request,
CancellationToken cancellationToken)
{
ResolveRefreshTokenTenant(request.RefreshToken);
await authService.LogoutAsync(
new LogoutSessionRequest(request.RefreshToken),
cancellationToken);
@@ -132,8 +142,51 @@ public sealed class AuthController(IAuthService authService) : ControllerBase
return NoContent();
}
private void ResolveRefreshTokenTenant(string refreshToken)
{
if (!sessionService.TryParseRefreshToken(refreshToken, out var locator))
{
return;
}
tenantContextInitializer.Initialize(locator.TenantId, null, TenantResolutionSource.RefreshToken);
}
private string? GetIpAddress()
{
return HttpContext.Connection.RemoteIpAddress?.ToString();
}
private async Task<Guid> ResolveTenantIdAsync(string? tenantCode, CancellationToken cancellationToken)
{
if (tenantContext.TenantId.HasValue)
{
if (!string.IsNullOrWhiteSpace(tenantCode) &&
!string.Equals(tenantContext.TenantCode, tenantCode.Trim(), StringComparison.OrdinalIgnoreCase))
{
var supplied = await tenantDirectory.FindByCodeAsync(tenantCode.Trim(), cancellationToken);
if (supplied?.TenantId != tenantContext.TenantId.Value)
{
throw new TenantContextConflictException(
tenantContext.TenantId.Value,
supplied?.TenantId ?? Guid.Empty);
}
}
return tenantContext.TenantId.Value;
}
if (string.IsNullOrWhiteSpace(tenantCode))
{
throw new RequiredFieldException("tenantCode is required when the request host does not resolve a tenant.");
}
var tenant = await tenantDirectory.FindByCodeAsync(tenantCode.Trim(), cancellationToken)
?? throw new TenantNotFoundException();
tenantContextInitializer.Initialize(
tenant.TenantId,
tenant.TenantCode,
TenantResolutionSource.TenantCode);
return tenant.TenantId;
}
}