using Microsoft.Extensions.Options; using Tiku.Api.Options; using Tiku.Application.Auth; using Tiku.Application.Content; using Tiku.Application.Security; using Tiku.Application.Tenancy; using Tiku.Domain.Tenancy; namespace Tiku.Api.Controllers; public sealed class AuthRequestContextResolver( IAuthSessionStore sessionStore, ITenantContext tenantContext, ITenantContextInitializer tenantContextInitializer, ITenantDirectory tenantDirectory, IOptions tenantResolutionOptions) { internal void ResolveRefreshTokenTenant(string refreshToken, HttpRequest request) { if (!sessionStore.TryParseRefreshToken(refreshToken, out var locator)) return; if (locator.Realm == AuthRealm.Platform) { EnsurePlatformHost(request); if (tenantContext.IsResolved) throw new TenantContextConflictException(tenantContext.TenantId!.Value, Guid.Empty); return; } if (!tenantContext.IsResolved) throw new RequiredFieldException( "tenant refresh/logout requires a tenant host or x-tenant-code matching the refresh token."); tenantContextInitializer.Initialize(locator.TenantId!.Value, null, TenantResolutionSource.RefreshToken); } internal void ResolveAuthChallengeTenant(string challengeToken, HttpRequest request) { var parts = challengeToken.Split('.', 4); if (parts.Length != 4 || parts[0] != "c1") return; if (parts[1] == "p" && parts[2] == "-") { EnsurePlatformHost(request); if (tenantContext.IsResolved) throw new TenantContextConflictException(tenantContext.TenantId!.Value, Guid.Empty); return; } if (parts[1] != "t" || !Guid.TryParseExact(parts[2], "N", out var tenantId) || !tenantContext.IsResolved) throw new RequiredFieldException( "tenant authentication challenge requires a tenant host or x-tenant-code."); tenantContextInitializer.Initialize(tenantId, null, TenantResolutionSource.RefreshToken); } internal async Task ResolveRealmTenantIdAsync( AuthRealm realm, string? tenantCode, HttpRequest request, CancellationToken cancellationToken) { if (realm == AuthRealm.Platform) { EnsurePlatformHost(request); if (tenantContext.IsResolved || !string.IsNullOrWhiteSpace(tenantCode)) throw new RequiredFieldException( "platform realm does not accept tenantCode and must use a platform host."); return null; } 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; } internal void EnsureRouteRealm(AuthRealm realm, HttpRequest request) { var path = request.Path.Value ?? string.Empty; var expectedRealm = path.StartsWith("/api/platform/auth/", StringComparison.OrdinalIgnoreCase) ? AuthRealm.Platform : AuthRealm.Tenant; if (realm != expectedRealm) throw new RequiredFieldException( $"{realm.ToString().ToLowerInvariant()} realm must use the {expectedRealm.ToString().ToLowerInvariant()} authentication route."); } private void EnsurePlatformHost(HttpRequest request) { var requestHost = request.Host.Host.Trim().TrimEnd('.'); if (!tenantResolutionOptions.Value.PlatformHosts.Any(host => string.Equals( host.Trim().TrimEnd('.'), requestHost, StringComparison.OrdinalIgnoreCase))) throw new RequiredFieldException("platform realm is only available on a configured platform host."); } }