using w4c_workflows.Services;
namespace w4c_workflows.Middleware;
///
/// Two auth surfaces for the control plane:
/// - /api/keys* → the tenant's main JWT (shared Auth:JwtSigningKey). Lets a
/// logged-in tenant mint/rotate/revoke its operator keys.
/// - everything else → per-tenant operator key (Authorization: Bearer ).
/// Used by both the frontend and the per-tenant worker.
/// Resolved identity is exposed via HttpContext.Items: TenantId, Scopes,
/// AuthKind ("jwt" | "operator").
///
public class AuthMiddleware
{
private static readonly PathString[] PublicPaths =
{
new("/health"),
new("/health/ready"),
new("/openapi"),
new("/scalar"), // interactive API explorer (Scalar) — no operator key needed
new("/h"), // webhook receiver (external callers have no operator key)
};
private readonly RequestDelegate _next;
private readonly string _signingKey;
private readonly ILogger _logger;
public AuthMiddleware(RequestDelegate next, IConfiguration config, ILogger logger)
{
_next = next;
_signingKey = config["Auth:JwtSigningKey"] ?? string.Empty;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path;
if (IsPublic(path))
{
await _next(context);
return;
}
// /api/keys* — main JWT surface.
if (path.StartsWithSegments("/api/keys"))
{
var principal = JwtValidator.Validate(context.Request.Headers.Authorization.ToString(), _signingKey);
var tenantId = principal?.FindFirst("tenant_id")?.Value;
if (principal == null || string.IsNullOrEmpty(tenantId))
{
await Unauthorized(context, "Valid main JWT required for key management");
return;
}
context.Items["TenantId"] = tenantId;
context.Items["AuthKind"] = "jwt";
await _next(context);
return;
}
// The rich HTML preview endpoint (/api/workflows/{id}/html) is reached
// from an