import { createHmac } from 'node:crypto'; import { describe, expect, it } from 'vitest'; import { computeHmacSha256Hex, verifyHmacSha256 } from '../../src/webhooks/signature.js'; const SECRET = 'super-secret-key-32chars-min-len'; const BODY = JSON.stringify({ event_id: 'abc', event_type: 'rows.created' }); function refSig(body: string, secret: string): string { return createHmac('sha256', secret).update(body, 'utf8').digest('hex'); } describe('computeHmacSha256Hex', () => { it('match Node crypto reference', () => { expect(computeHmacSha256Hex(BODY, SECRET)).toBe(refSig(BODY, SECRET)); }); it('different secrets -> different output', () => { expect(computeHmacSha256Hex(BODY, SECRET)).not.toBe( computeHmacSha256Hex(BODY, `${SECRET}-other`), ); }); it('different body -> different output', () => { expect(computeHmacSha256Hex(BODY, SECRET)).not.toBe(computeHmacSha256Hex(`${BODY}x`, SECRET)); }); it('hex length is 64 (sha256)', () => { expect(computeHmacSha256Hex(BODY, SECRET)).toHaveLength(64); }); }); describe('verifyHmacSha256', () => { it('accepte hex pur valide', () => { const sig = refSig(BODY, SECRET); expect(verifyHmacSha256(BODY, SECRET, sig)).toBe(true); }); it('accepte format prefixe sha256=', () => { const sig = `sha256=${refSig(BODY, SECRET)}`; expect(verifyHmacSha256(BODY, SECRET, sig)).toBe(true); }); it('rejette signature null', () => { expect(verifyHmacSha256(BODY, SECRET, null)).toBe(false); }); it('rejette signature longueur differente', () => { expect(verifyHmacSha256(BODY, SECRET, 'tooshort')).toBe(false); }); it('rejette signature meme longueur mais wrong digest', () => { const wrong = 'a'.repeat(64); expect(verifyHmacSha256(BODY, SECRET, wrong)).toBe(false); }); it('rejette si body modifie', () => { const sig = refSig(BODY, SECRET); expect(verifyHmacSha256(`${BODY}x`, SECRET, sig)).toBe(false); }); it('rejette si secret different', () => { const sig = refSig(BODY, SECRET); expect(verifyHmacSha256(BODY, `${SECRET}-other`, sig)).toBe(false); }); it('signatures non-ascii ne crashent pas', () => { expect(verifyHmacSha256(BODY, SECRET, 'é'.repeat(64))).toBe(false); }); });