fix(webhook): GET on /wh/* returns 405 helper page instead of SPA fallback

Previously a browser click on the webhook URL fell through the POST-only
handler to MapFallbackToFile('index.html'), landing the user on the
workflows page and looking like a bug. Now non-POST verbs on the same
route return 405 with Allow: POST — GETs render a small HTML page showing
the correct curl invocation, other methods get JSON. Two new xUnit tests
cover the HTML and JSON branches and assert we don't hand back the SPA.
This commit is contained in:
Claude
2026-08-28 20:16:07 +00:00
parent 5d04c0a2d4
commit 1a1f519144
2 changed files with 74 additions and 0 deletions

View File

@@ -51,5 +51,51 @@ public static class WebhookEndpoint
if (payload is null) return Results.NoContent();
return Results.Text(payload.Body, contentType: payload.ContentType, statusCode: payload.StatusCode);
});
// Non-POST requests to the same path — without this, browsers clicking
// the webhook URL (GET) fall through to MapFallbackToFile("index.html")
// and land on the SPA instead of getting a useful hint.
app.MapMethods(
"/wh/{workflowId}/{**path}",
new[] { "GET", "HEAD", "PUT", "PATCH", "DELETE", "OPTIONS" },
(string workflowId, string? path, HttpRequest req, HttpResponse res) =>
{
res.Headers.Allow = "POST";
var origin = $"{req.Scheme}://{req.Host}";
var url = $"{origin}/wh/{workflowId}/{path ?? ""}";
if (HttpMethods.IsHead(req.Method))
return Results.StatusCode(405);
if (HttpMethods.IsGet(req.Method))
{
// $$""" so single {/} are literal (CSS + curl JSON) and {{url}} interpolates.
var html = $$"""
<!doctype html>
<html><head><meta charset="utf-8"><title>VolcanicCore webhook</title>
<style>
body { font-family: system-ui, sans-serif; background: #020617; color: #e2e8f0;
padding: 2rem; max-width: 40rem; margin: 0 auto; }
code { background: #1e293b; padding: 0.1rem 0.4rem; border-radius: 0.25rem;
font-family: ui-monospace, monospace; font-size: 0.85rem; word-break: break-all; }
pre { background: #1e293b; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; }
h1 { font-size: 1.1rem; color: #fbbf24; }
</style></head><body>
<h1>This is a POST-only webhook endpoint.</h1>
<p>You reached it with a browser (GET). To trigger the workflow, send a POST with a JSON body:</p>
<pre>curl -X POST -H 'content-type: application/json' \
-d '{"hello":"world"}' \
{{url}}</pre>
<p>The workflow must be <strong>active</strong> for this endpoint to respond;
an inactive workflow returns 404 on POST.</p>
</body></html>
""";
return Results.Content(html, contentType: "text/html; charset=utf-8", statusCode: 405);
}
return Results.Json(new
{
error = "method_not_allowed",
message = "This endpoint accepts POST only.",
endpoint = url,
}, statusCode: 405);
});
}
}