using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Options; using Tiku.Application.Security; using Tiku.Application.Tenancy; namespace Tiku.Api.Middleware; public sealed class TenantResolutionMiddleware(RequestDelegate next) { public async Task InvokeAsync( HttpContext context, ITenantDirectory tenantDirectory, ITenantContextInitializer tenantInitializer, IOptions options) { var host = NormalizeHost(context.Request.Host.Host); var isPlatformHost = options.Value.PlatformHosts.Any(candidate => string.Equals(NormalizeHost(candidate), host, StringComparison.OrdinalIgnoreCase)); TenantDirectoryEntry? tenant = null; if (!isPlatformHost && host is not null) { tenant = await tenantDirectory.FindByHostAsync(host, context.RequestAborted); if (tenant is null && !IsExemptPath(context.Request.Path, options.Value.ExemptPathPrefixes)) { context.Response.StatusCode = StatusCodes.Status404NotFound; await context.Response.WriteAsJsonAsync(new ProblemDetails { Title = "Tenant was not found.", Status = StatusCodes.Status404NotFound, Detail = "The request host is not assigned to an active tenant." }, context.RequestAborted); return; } } else if (isPlatformHost && IsAllowedTenantCodePath( context.Request.Path, options.Value.TenantCodePathPrefixes)) { var tenantCode = context.Request.Headers["x-tenant-code"].FirstOrDefault() ?? context.Request.Query["tenantCode"].FirstOrDefault(); if (!string.IsNullOrWhiteSpace(tenantCode)) { tenant = await tenantDirectory.FindByCodeAsync(tenantCode.Trim(), context.RequestAborted); } } if (tenant is not null) { tenantInitializer.Initialize( tenant.TenantId, tenant.TenantCode, tenant.Host is null ? TenantResolutionSource.TenantCode : TenantResolutionSource.Host); } await next(context); } private static string? NormalizeHost(string? host) { return string.IsNullOrWhiteSpace(host) ? null : host.Trim().TrimEnd('.').ToLowerInvariant(); } private static bool IsExemptPath(PathString path, IEnumerable prefixes) { return prefixes.Any(prefix => path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase)); } private static bool IsAllowedTenantCodePath(PathString path, IEnumerable prefixes) { return prefixes.Any(prefix => path.StartsWithSegments(prefix, StringComparison.OrdinalIgnoreCase)); } }