mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(Slack Trigger Node): Add support for signature verification (#17838)
This commit is contained in:
162
packages/nodes-base/nodes/Slack/test/SlackTriggerHelper.test.ts
Normal file
162
packages/nodes-base/nodes/Slack/test/SlackTriggerHelper.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { createHmac, timingSafeEqual } from 'crypto';
|
||||
import { verifySignature } from '../SlackTriggerHelpers';
|
||||
|
||||
// Mock crypto module
|
||||
jest.mock('crypto', () => ({
|
||||
createHmac: jest.fn().mockReturnValue({
|
||||
update: jest.fn().mockReturnThis(),
|
||||
digest: jest
|
||||
.fn()
|
||||
.mockReturnValue('a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503'),
|
||||
}),
|
||||
timingSafeEqual: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('SlackTriggerHelpers', () => {
|
||||
let mockWebhookFunctions: any;
|
||||
const testSignatureSecret = 'xyzz0WbapA4vBCDEFasx0q6G';
|
||||
const testTimestamp = '1531420618';
|
||||
const testBody =
|
||||
'token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c';
|
||||
const testSignature = 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Mock Date.now() to return a fixed timestamp
|
||||
const fixedDate = new Date(parseInt(testTimestamp, 10) * 1000);
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => fixedDate.getTime());
|
||||
|
||||
mockWebhookFunctions = {
|
||||
getCredentials: jest.fn(),
|
||||
getRequestObject: jest.fn(),
|
||||
getNode: jest.fn().mockReturnValue({ name: 'Slack Trigger' }),
|
||||
};
|
||||
|
||||
// Default mock return values
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-slack-signature') return testSignature;
|
||||
if (header === 'x-slack-request-timestamp') return testTimestamp;
|
||||
return null;
|
||||
}),
|
||||
rawBody: testBody,
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifySignature', () => {
|
||||
it('should return true when no credentials are provided', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue(null);
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockWebhookFunctions.getCredentials).toHaveBeenCalledWith('slackApi');
|
||||
});
|
||||
|
||||
it('should return true when no signature secret is provided', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
apiToken: 'test-token',
|
||||
});
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockWebhookFunctions.getCredentials).toHaveBeenCalledWith('slackApi');
|
||||
});
|
||||
|
||||
it('should return false when signature header is missing', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
signatureSecret: testSignatureSecret,
|
||||
});
|
||||
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-slack-request-timestamp') return testTimestamp;
|
||||
return null;
|
||||
}),
|
||||
rawBody: testBody,
|
||||
});
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when timestamp header is missing', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
signatureSecret: testSignatureSecret,
|
||||
});
|
||||
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-slack-signature') return testSignature;
|
||||
return null;
|
||||
}),
|
||||
rawBody: testBody,
|
||||
});
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when timestamp is too old', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
signatureSecret: testSignatureSecret,
|
||||
});
|
||||
|
||||
// Mock Date.now() to return a timestamp that's more than 5 minutes after the request timestamp
|
||||
const futureDate = new Date((parseInt(testTimestamp, 10) + 301) * 1000);
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => futureDate.getTime());
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when signature is valid', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
signatureSecret: testSignatureSecret,
|
||||
});
|
||||
|
||||
// Mock the timingSafeEqual to return true for valid signature
|
||||
(timingSafeEqual as jest.Mock).mockReturnValue(true);
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(createHmac).toHaveBeenCalledWith('sha256', testSignatureSecret);
|
||||
expect(timingSafeEqual).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false when signature is invalid', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
signatureSecret: testSignatureSecret,
|
||||
});
|
||||
|
||||
// Mock the timingSafeEqual to return false for invalid signature
|
||||
(timingSafeEqual as jest.Mock).mockReturnValue(false);
|
||||
|
||||
const result = await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(createHmac).toHaveBeenCalledWith('sha256', testSignatureSecret);
|
||||
expect(timingSafeEqual).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should correctly build the signature string with the provided timestamp', async () => {
|
||||
mockWebhookFunctions.getCredentials.mockResolvedValue({
|
||||
signatureSecret: testSignatureSecret,
|
||||
});
|
||||
|
||||
await verifySignature.call(mockWebhookFunctions);
|
||||
|
||||
expect(createHmac).toHaveBeenCalledWith('sha256', testSignatureSecret);
|
||||
const mockHmac = createHmac('sha256', testSignatureSecret);
|
||||
|
||||
// Verify that update was called with the expected string (using the timestamp from the request)
|
||||
expect(mockHmac.update).toHaveBeenCalledWith(`v0:${testTimestamp}:${testBody}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user