diff --git a/Changelog/v1.2.2.md b/Changelog/v1.2.2.md index e602c3b..178050f 100644 --- a/Changelog/v1.2.2.md +++ b/Changelog/v1.2.2.md @@ -18,3 +18,7 @@ - Widget integrations can prefill the editable reporter name and email through `BugPin.init()` or script-tag data attributes. - Reporter-prefilled drafts are scoped by reporter email to avoid restoring another reporter's saved form data. - The Admin Console's Test Widget page now lets administrators edit the test reporter and switch between reporter and logged-out modes. + +### Fixes + +- GitHub webhook deliveries to `POST /api/webhooks/github/:integrationId` are no longer rejected with session `UNAUTHORIZED` before signature validation. The inbound GitHub route is mounted ahead of authenticated outbound webhook management, so GitHub authenticates with `X-Hub-Signature-256` as intended. diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 38047c4..ec7f61a 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -38,6 +38,7 @@ export function createApiRouter(): Hono { api.route('/integrations', integrationsRoutes); api.route('/notification-preferences', notificationPreferences); api.route('/license', licenseRoutes); + api.route('/webhooks/github', githubWebhookRoutes); // Mount EE routes first if available (they take priority over CE routes) // EE routes handle the actual feature implementation when licensed @@ -77,9 +78,6 @@ export function createApiRouter(): Hono { // Mount widget routes (public) api.route('/widget', widgetRoutes); - // Mount GitHub webhook routes (public, no auth) - api.route('/webhooks/github', githubWebhookRoutes); - // Mount public files routes (public, no auth) api.route('/public/files', publicFilesRoutes); diff --git a/tests/server/routes/github-webhook.routes.test.ts b/tests/server/routes/github-webhook.routes.test.ts index eff1a90..76dafec 100644 --- a/tests/server/routes/github-webhook.routes.test.ts +++ b/tests/server/routes/github-webhook.routes.test.ts @@ -357,4 +357,30 @@ describe('github-webhook routes', () => { expect(res.status).toBe(200); }); }); + + describe('API router mount order', () => { + it('reaches the GitHub handler when mounted before authenticated /webhooks', async () => { + const { webhooksRoutes } = await import('../../../src/server/routes/api/webhooks'); + const app = new Hono(); + app.route('/webhooks/github', githubWebhookRoutes); + app.route('/webhooks', webhooksRoutes); + + const res = await app.request('http://localhost/webhooks/github/int_1', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-github-event': 'ping', + 'x-github-delivery': 'delivery_1', + }, + body: JSON.stringify({ zen: 'test' }), + }); + + const body = (await res.json()) as { error?: string; message?: string }; + + expect(body.error).not.toBe('UNAUTHORIZED'); + expect(body.message).not.toBe('Authentication required'); + expect(res.status).toBe(401); + expect(body.error).toBe('Missing signature'); + }); + }); });