Pireon signs each webhook with the secret of the client that registered it, and the API has two clients (the web app's and the password grant's). Only PIREON_WEBHOOK_SECRET was accepted, so a token revoked for someone signed in with a password went unheard. A delivery now verifies with PIREON_WEBHOOK_SECRET or PIREON_PASSWORD_WEBHOOK_SECRET; the same event arriving from both clients changes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 lines
1.8 KiB
TypeScript
49 lines
1.8 KiB
TypeScript
import { UnauthorizedException } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { IdentityService } from '@task-stream/api/identity/application';
|
|
import { createHmac } from 'node:crypto';
|
|
import { PireonWebhookController } from './pireon-webhook.controller';
|
|
|
|
describe('PireonWebhookController', () => {
|
|
const handled: string[] = [];
|
|
const controller = (secrets: Record<string, string>) =>
|
|
new PireonWebhookController(
|
|
{
|
|
handleWebhook: async (event: string) => {
|
|
handled.push(event);
|
|
},
|
|
} as unknown as IdentityService,
|
|
{ get: (key: string) => secrets[key] } as unknown as ConfigService,
|
|
);
|
|
const body = { event: 'token.revoked', data: { token_id: 't-1' } };
|
|
const raw = Buffer.from(JSON.stringify(body));
|
|
const signed = (secret: string) =>
|
|
createHmac('sha256', secret).update(raw).digest('hex');
|
|
const request = { rawBody: raw, body } as never;
|
|
|
|
beforeEach(() => (handled.length = 0));
|
|
|
|
it("accepts a delivery signed with either Pireon client's secret", async () => {
|
|
const both = controller({
|
|
PIREON_WEBHOOK_SECRET: 'web-secret',
|
|
PIREON_PASSWORD_WEBHOOK_SECRET: 'password-secret',
|
|
});
|
|
await both.receive(request, signed('web-secret'));
|
|
await both.receive(request, `sha256=${signed('password-secret')}`);
|
|
expect(handled).toEqual(['token.revoked', 'token.revoked']);
|
|
});
|
|
|
|
it('refuses any other signature, and everything when no secret is set', async () => {
|
|
await expect(
|
|
controller({ PIREON_WEBHOOK_SECRET: 'web-secret' }).receive(
|
|
request,
|
|
signed('password-secret'),
|
|
),
|
|
).rejects.toThrow(UnauthorizedException);
|
|
await expect(
|
|
controller({}).receive(request, signed('web-secret')),
|
|
).rejects.toThrow(/disabled/);
|
|
expect(handled).toEqual([]);
|
|
});
|
|
});
|