From b115a4c5122980adc6920977fe357e2e318df25f Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 23 Apr 2026 21:15:38 +0200 Subject: [PATCH] feat(worker): MCP bearer-token auth middleware --- .../Planning/PlanningMcpContext.cs | 6 +++ .../Planning/PlanningTokenAuth.cs | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/ClaudeDo.Worker/Planning/PlanningMcpContext.cs create mode 100644 src/ClaudeDo.Worker/Planning/PlanningTokenAuth.cs diff --git a/src/ClaudeDo.Worker/Planning/PlanningMcpContext.cs b/src/ClaudeDo.Worker/Planning/PlanningMcpContext.cs new file mode 100644 index 0000000..bf29dcd --- /dev/null +++ b/src/ClaudeDo.Worker/Planning/PlanningMcpContext.cs @@ -0,0 +1,6 @@ +namespace ClaudeDo.Worker.Planning; + +public sealed class PlanningMcpContext +{ + public required string ParentTaskId { get; init; } +} diff --git a/src/ClaudeDo.Worker/Planning/PlanningTokenAuth.cs b/src/ClaudeDo.Worker/Planning/PlanningTokenAuth.cs new file mode 100644 index 0000000..6cf7043 --- /dev/null +++ b/src/ClaudeDo.Worker/Planning/PlanningTokenAuth.cs @@ -0,0 +1,40 @@ +using ClaudeDo.Data.Repositories; +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) + { + 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 null || parent.Status != ClaudeDo.Data.Models.TaskStatus.Planning) + { + ctx.Response.StatusCode = 401; + await ctx.Response.WriteAsync("Invalid or expired planning token"); + return; + } + + ctx.Items["PlanningContext"] = new PlanningMcpContext { ParentTaskId = parent.Id }; + await _next(ctx); + } +}