UNPKG

vhook-js

Version:

Verifyable Webhooks

147 lines (122 loc) 5.73 kB
// test/vhook.test.js const assert = require('assert'); const vhook = require('../vhook.js'); const jwt = require('jsonwebtoken'); describe('VHooks Module', function () { let keys; let privateKey; let publicKey; let token; let payload; let now before(async function () { // Generate key pair before running tests keys = await vhook.create_vhook_keypair(); privateKey = keys.privateKey.pem; publicKey = keys.publicKey.pem; now = Math.floor(Date.now()/1000); }); describe('create_vhook_keypair()', function () { it('should generate a valid RSA key pair', function () { assert.ok(keys.privateKey); assert.ok(keys.publicKey); assert.ok(keys.privateKey.pem); assert.ok(keys.publicKey.pem); // Check that keys are in PEM format assert.ok(keys.privateKey.pem.includes('-----BEGIN RSA PRIVATE KEY-----')); assert.ok(keys.publicKey.pem.includes('-----BEGIN PUBLIC KEY-----')); }); }); describe('create_vhook()', function () { it('should create a signed VHook token', function () { payload = { issuer: 'https://yourservice.com', audience: 'https://receiver-service.com/webhook-endpoint', origin: 'customer', event: 'customer.updated', data: { customer_id: '12345', name: 'Jane Doe', email: 'jane.doe@example.com', }, issued_at: now, expiration: now + 60, // Expires in 60 seconds message_id: 'test-message-id', }; token = vhook.create_vhook(payload, privateKey); assert.strictEqual(typeof token, 'string'); // Token should have three parts separated by dots assert.strictEqual(token.split('.').length, 3); }); it('should include msg_id in the payload', function () { const decoded = jwt.decode(token); assert.ok(decoded.jti); assert.strictEqual(typeof decoded.jti, 'string'); }); }); describe('decode_vhook()', function () { it('should decode and verify the VHook token', function () { const decodedPayload = vhook.decode_vhook(token, publicKey); // Check that the decoded payload matches the original payload assert.deepStrictEqual(decodedPayload.origin, payload.origin); assert.deepStrictEqual(decodedPayload.event, payload.event); assert.deepStrictEqual(decodedPayload.data, payload.data); // Check that standard claims are present assert.strictEqual(decodedPayload.issuer, payload.issuer); assert.strictEqual(decodedPayload.audience, payload.audience); // Compare date fields as Date objects assert.deepStrictEqual(decodedPayload.expiration, new Date(payload.expiration * 1000)); assert.deepStrictEqual(decodedPayload.issued_at, new Date(payload.issued_at * 1000)); // Ensure message_id is present and matches assert.strictEqual(decodedPayload.message_id, payload.message_id); }); it('should throw an error for invalid signature', function () { // Tamper with the token const parts = token.split('.'); const tamperedPayload = Buffer.from(parts[1], 'base64').toString('utf8'); const tamperedPayloadObj = JSON.parse(tamperedPayload); tamperedPayloadObj.origin = 'attacker'; const newPayload = Buffer.from(JSON.stringify(tamperedPayloadObj)).toString('base64').replace(/=/g, ''); const tamperedToken = `${parts[0]}.${newPayload}.${parts[2]}`; assert.throws(() => { vhook.decode_vhook(tamperedToken, publicKey); }, /invalid signature/); }); it('raw_token should contain the same info as vhook', function () { const decodedPayload = vhook.decode_vhook(token, publicKey); const raw_token = decodedPayload.raw_token; // Check that the decoded payload matches the original payload assert.deepStrictEqual(decodedPayload.origin, raw_token.origin); assert.deepStrictEqual(decodedPayload.event, raw_token.event); assert.deepStrictEqual(decodedPayload.data, raw_token.data); // Check that standard claims are present assert.strictEqual(decodedPayload.issuer, raw_token.iss); assert.strictEqual(decodedPayload.audience, raw_token.aud); // Compare date fields as Date objects assert.deepStrictEqual(decodedPayload.expiration, new Date(raw_token.exp * 1000)); assert.deepStrictEqual(decodedPayload.issued_at, new Date(raw_token.iat * 1000)); // Ensure message_id is present and matches assert.strictEqual(decodedPayload.message_id, raw_token.jti); }); }); describe('decode_vhook_without_validation()', function () { it('should decode the VHook token without verifying the signature', function () { const decodedPayload = vhook.decode_vhook_without_validation(token); assert.deepStrictEqual(decodedPayload.origin, payload.origin); assert.deepStrictEqual(decodedPayload.event, payload.event); assert.deepStrictEqual(decodedPayload.data, payload.data); assert.ok(decodedPayload.message_id); }); it('should decode a tampered token without error', function () { // Use the previously tampered token const parts = token.split('.'); const tamperedPayload = Buffer.from(parts[1], 'base64').toString('utf8'); const tamperedPayloadObj = JSON.parse(tamperedPayload); tamperedPayloadObj.origin = 'attacker'; const newPayload = Buffer.from(JSON.stringify(tamperedPayloadObj)).toString('base64').replace(/=/g, ''); const tamperedToken = `${parts[0]}.${newPayload}.${parts[2]}`; const decodedPayload = vhook.decode_vhook_without_validation(tamperedToken); assert.strictEqual(decodedPayload.origin, 'attacker'); }); }); });