feat(auth): require DID owner JWT to manage webhooks

Both POST /:did/webhooks and DELETE /:did/webhooks were unauthenticated:
anyone could register a webhook for someone else's DID (privacy leak)
or wipe a DID's webhook list (DoS on legitimate subscribers). Now both
endpoints require a Bluesky session bearer token, verified end-to-end
against the DID's PDS via the existing authenticateRequest helper, and
the verified DID must match the URL :did.
This commit is contained in:
Julien Calixte
2026-05-05 12:27:22 +02:00
parent 7b53909c52
commit 1ce5d9150d

View File

@@ -7,6 +7,7 @@ import {
getNotesByDids,
type WebhookVerb,
} from "./src/data/db.ts";
import { authenticateRequest } from "./src/auth/verify.ts";
import { log } from "./src/log.ts";
const router = new Router();
@@ -89,6 +90,21 @@ const ALLOWED_VERBS = ["create", "delete", "bulk-create"] as const;
router.post("/:did/webhooks", async (ctx) => {
const { did } = ctx.params;
let verifiedDid: string;
try {
verifiedDid = await authenticateRequest(
ctx.request.headers.get("Authorization"),
);
} catch {
ctx.response.status = 401;
ctx.response.body = { error: "Unauthorized" };
return;
}
if (verifiedDid !== did) {
ctx.response.status = 403;
ctx.response.body = { error: "You can only manage your own webhooks" };
return;
}
const body = await ctx.request.body.json();
const { method, url, token, verb } = body ?? {};
if (!method || !url) {
@@ -111,8 +127,23 @@ router.post("/:did/webhooks", async (ctx) => {
ctx.response.body = subscriptions;
});
router.delete("/:did/webhooks", (ctx) => {
router.delete("/:did/webhooks", async (ctx) => {
const { did } = ctx.params;
let verifiedDid: string;
try {
verifiedDid = await authenticateRequest(
ctx.request.headers.get("Authorization"),
);
} catch {
ctx.response.status = 401;
ctx.response.body = { error: "Unauthorized" };
return;
}
if (verifiedDid !== did) {
ctx.response.status = 403;
ctx.response.body = { error: "You can only manage your own webhooks" };
return;
}
deleteWebhooksByDid(did);
ctx.response.status = 204;
});