using ClaudeDo.Worker.Config; using Microsoft.AspNetCore.Http; namespace ClaudeDo.Worker.External; public sealed class ExternalMcpAuthMiddleware { private const string HeaderName = "X-ClaudeDo-Key"; /// Header carrying an SSH session's token on the remote MCP listener. public const string RemoteTokenHeader = "X-ClaudeDo-Remote-Token"; private readonly RequestDelegate _next; public ExternalMcpAuthMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext ctx, WorkerConfig cfg, RemoteSessionTokenRegistry remoteTokens) { // Tunnelled traffic arrives as loopback too, so the remote listener never falls back to loopback trust. if (cfg.RemoteMcpPort > 0 && ctx.Connection.LocalPort == cfg.RemoteMcpPort) { if (!remoteTokens.IsValid(ctx.Request.Headers[RemoteTokenHeader].ToString())) { ctx.Response.StatusCode = 401; await ctx.Response.WriteAsync($"Missing or invalid {RemoteTokenHeader} header"); return; } await _next(ctx); return; } if (string.IsNullOrEmpty(cfg.ExternalMcpApiKey)) { await _next(ctx); return; } var provided = ctx.Request.Headers[HeaderName].ToString(); if (!string.Equals(provided, cfg.ExternalMcpApiKey, StringComparison.Ordinal)) { ctx.Response.StatusCode = 401; await ctx.Response.WriteAsync($"Missing or invalid {HeaderName} header"); return; } await _next(ctx); } }