using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Runner; using Microsoft.AspNetCore.Http; namespace ClaudeDo.Worker.Planning; public sealed class PlanningTokenAuthMiddleware { private readonly RequestDelegate _next; public PlanningTokenAuthMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext ctx, TaskRepository tasks, TaskRunTokenRegistry runTokens) { if (!ctx.Request.Path.StartsWithSegments("/mcp")) { await _next(ctx); return; } var auth = ctx.Request.Headers["Authorization"].ToString(); if (!auth.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { ctx.Response.StatusCode = 401; await ctx.Response.WriteAsync("Missing bearer token"); return; } var token = auth.Substring("Bearer ".Length).Trim(); var parent = await tasks.FindByPlanningTokenAsync(token, ctx.RequestAborted); if (parent is not null && parent.PlanningPhase == ClaudeDo.Data.Models.PlanningPhase.Active) { ctx.Items["PlanningContext"] = new PlanningMcpContext { ParentTaskId = parent.Id }; await _next(ctx); return; } if (runTokens.TryResolve(token, out var callerTaskId)) { ctx.Items["TaskRunContext"] = new TaskRunMcpContext { CallerTaskId = callerTaskId }; await _next(ctx); return; } ctx.Response.StatusCode = 401; await ctx.Response.WriteAsync("Invalid or expired token"); } }