|
| 1 | +import { createHmac } from 'node:crypto'; |
| 2 | + |
1 | 3 | import { describe, it, expect } from 'vitest'; |
2 | 4 | import { ServerMessage, ServerMessage_PeerType, TrackType } from '@fishjam-cloud/fishjam-proto'; |
3 | | -import { decodeServerNotifications } from '../src/webhook'; |
| 5 | +import { decodeServerNotifications, verifyWebhookSignature } from '../src/webhook'; |
4 | 6 |
|
5 | 7 | const encode = (message: Parameters<typeof ServerMessage.encode>[0]): Uint8Array => |
6 | 8 | ServerMessage.encode(message).finish(); |
@@ -94,3 +96,33 @@ describe('decodeServerNotifications', () => { |
94 | 96 | expect(decodeServerNotifications(arrayBuffer)).toHaveLength(1); // ArrayBuffer |
95 | 97 | }); |
96 | 98 | }); |
| 99 | + |
| 100 | +describe('verifyWebhookSignature', () => { |
| 101 | + const secret = 'test-secret'; |
| 102 | + const body = encode(peerConnected); |
| 103 | + // Mirrors the server: Base.encode16(:crypto.mac(:hmac, :sha256, secret, body), case: :lower) |
| 104 | + const validHex = createHmac('sha256', secret).update(body).digest('hex'); |
| 105 | + |
| 106 | + it('accepts the sha256=<hex> header format sent by the server', () => { |
| 107 | + expect(verifyWebhookSignature(body, `sha256=${validHex}`, secret)).toBe(true); |
| 108 | + }); |
| 109 | + |
| 110 | + it('accepts a bare hex signature and an ArrayBuffer body', () => { |
| 111 | + const arrayBuffer = new Uint8Array(body).buffer as ArrayBuffer; |
| 112 | + expect(verifyWebhookSignature(arrayBuffer, validHex, secret)).toBe(true); |
| 113 | + }); |
| 114 | + |
| 115 | + it('rejects a signature computed with a different secret', () => { |
| 116 | + const other = createHmac('sha256', 'other-secret').update(body).digest('hex'); |
| 117 | + expect(verifyWebhookSignature(body, `sha256=${other}`, secret)).toBe(false); |
| 118 | + }); |
| 119 | + |
| 120 | + it('rejects a tampered body', () => { |
| 121 | + expect(verifyWebhookSignature(encode(trackAdded), `sha256=${validHex}`, secret)).toBe(false); |
| 122 | + }); |
| 123 | + |
| 124 | + it('rejects malformed signatures without throwing', () => { |
| 125 | + expect(verifyWebhookSignature(body, '', secret)).toBe(false); |
| 126 | + expect(verifyWebhookSignature(body, 'sha256=deadbeef', secret)).toBe(false); |
| 127 | + }); |
| 128 | +}); |
0 commit comments