UNPKG

n8n-nodes-toggl

Version:

n8n community node for Toggl Track time tracking integration with comprehensive operations including client management and webhook support

242 lines (241 loc) 9.71 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TogglWebhookTrigger = void 0; const crypto = __importStar(require("crypto-js")); const TogglApi_helper_1 = require("../TogglApi.helper"); class TogglWebhookTrigger { constructor() { this.description = { displayName: 'Toggl Webhook Trigger', name: 'togglWebhookTrigger', icon: 'file:toggl.png', group: ['trigger'], version: 1, subtitle: '={{$parameter["events"].join(", ")}}', description: 'Handle Toggl Track webhook events', defaults: { name: 'Toggl Webhook Trigger', }, inputs: [], outputs: ["main" /* NodeConnectionType.Main */], credentials: [ { name: 'togglApi', required: true, }, ], webhooks: [ { name: 'default', httpMethod: 'POST', responseMode: 'onReceived', path: 'webhook', }, ], properties: [ { displayName: 'Workspace ID', name: 'workspaceId', type: 'number', default: 0, required: true, description: 'ID of the workspace to create webhook for', }, { displayName: 'Events', name: 'events', type: 'multiOptions', options: [ { name: 'Project Created', value: 'project:created', description: 'Triggered when a project is created', }, { name: 'Project Updated', value: 'project:updated', description: 'Triggered when a project is updated', }, { name: 'Project Deleted', value: 'project:deleted', description: 'Triggered when a project is deleted', }, { name: 'Time Entry Created', value: 'time_entry:created', description: 'Triggered when a time entry is created', }, { name: 'Time Entry Updated', value: 'time_entry:updated', description: 'Triggered when a time entry is updated', }, { name: 'Time Entry Deleted', value: 'time_entry:deleted', description: 'Triggered when a time entry is deleted', }, { name: 'Task Created', value: 'task:created', description: 'Triggered when a task is created', }, { name: 'Task Updated', value: 'task:updated', description: 'Triggered when a task is updated', }, { name: 'Task Deleted', value: 'task:deleted', description: 'Triggered when a task is deleted', }, ], default: ['time_entry:created'], required: true, description: 'The events to listen for', }, { displayName: 'Description', name: 'description', type: 'string', default: 'n8n webhook', description: 'Description for the webhook subscription', }, ], }; } // The function to run when the webhook is activated async webhookActivate() { const workspaceId = this.getNodeParameter('workspaceId'); const events = this.getNodeParameter('events'); const description = this.getNodeParameter('description'); const webhookUrl = this.getNodeWebhookUrl('default'); // Generate a random secret for webhook verification const secret = crypto.lib.WordArray.random(32).toString(); const body = { url: webhookUrl, events, description, secret, }; try { const responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'POST', `/workspaces/${workspaceId}/webhooks`, body); // Store subscription data const webhookData = this.getWorkflowStaticData('node'); webhookData.subscriptionId = responseData.id; webhookData.secret = secret; // Validate webhook if synchronous echo fails try { await TogglApi_helper_1.togglApiRequest.call(this, 'GET', `/workspaces/${workspaceId}/webhooks/${responseData.id}/validate`); } catch (error) { console.warn('Webhook validation failed, but subscription created successfully'); } } catch (error) { throw new Error(`Failed to create webhook: ${error.message}`); } } // The function to run when the webhook is deactivated async webhookDeactivate() { const webhookData = this.getWorkflowStaticData('node'); const workspaceId = this.getNodeParameter('workspaceId'); if (webhookData.subscriptionId) { try { await TogglApi_helper_1.togglApiRequest.call(this, 'DELETE', `/workspaces/${workspaceId}/webhooks/${webhookData.subscriptionId}`); } catch (error) { console.warn(`Failed to delete webhook: ${error.message}`); } // Clear stored data delete webhookData.subscriptionId; delete webhookData.secret; } } // The function to run when a webhook request is received async webhook() { const req = this.getRequestObject(); const webhookData = this.getWorkflowStaticData('node'); // Verify webhook signature const signature = req.headers['x-webhook-signature-256']; const body = JSON.stringify(req.body); if (!signature || !webhookData.secret) { return { webhookResponse: { status: 401, body: 'Unauthorized: Missing signature or secret', }, }; } // Calculate expected signature const expectedSignature = crypto.HmacSHA256(body, webhookData.secret).toString(); const receivedSignature = signature.replace('sha256=', ''); // Compare signatures using timing-safe comparison if (!TogglWebhookTrigger.compareSignatures(expectedSignature, receivedSignature)) { return { webhookResponse: { status: 401, body: 'Unauthorized: Invalid signature', }, }; } // Process the webhook payload const responseData = req.body; return { workflowData: [ [ { json: responseData, }, ], ], }; } // Timing-safe string comparison to prevent timing attacks static compareSignatures(expected, received) { if (expected.length !== received.length) { return false; } let result = 0; for (let i = 0; i < expected.length; i++) { result |= expected.charCodeAt(i) ^ received.charCodeAt(i); } return result === 0; } } exports.TogglWebhookTrigger = TogglWebhookTrigger;