UNPKG

box-node-sdk

Version:

Official SDK for Box Plaform APIs

399 lines 17.9 kB
"use strict"; /** * @fileoverview Manager for the Box Webhooks resource */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; // ----------------------------------------------------------------------------- // Requirements // ----------------------------------------------------------------------------- var url_path_1 = __importDefault(require("../util/url-path")); var crypto_1 = __importDefault(require("crypto")); // ----------------------------------------------------------------------------- // Typedefs // ----------------------------------------------------------------------------- /** * A webhook trigger type constant * @typedef {string} WebhookTriggerType */ var WebhookTriggerType; (function (WebhookTriggerType) { WebhookTriggerType["FILE_UPLOADED"] = "FILE.UPLOADED"; WebhookTriggerType["FILE_PREVIEWED"] = "FILE.PREVIEWED"; WebhookTriggerType["FILE_DOWNLOADED"] = "FILE.DOWNLOADED"; WebhookTriggerType["FILE_TRASHED"] = "FILE.TRASHED"; WebhookTriggerType["FILE_DELETED"] = "FILE.DELETED"; WebhookTriggerType["FILE_RESTORED"] = "FILE.RESTORED"; WebhookTriggerType["FILE_COPIED"] = "FILE.COPIED"; WebhookTriggerType["FILE_MOVED"] = "FILE.MOVED"; WebhookTriggerType["FILE_LOCKED"] = "FILE.LOCKED"; WebhookTriggerType["FILE_UNLOCKED"] = "FILE.UNLOCKED"; WebhookTriggerType["FILE_RENAMED"] = "FILE.RENAMED"; WebhookTriggerType["COMMENT_CREATED"] = "COMMENT.CREATED"; WebhookTriggerType["COMMENT_UPDATED"] = "COMMENT.UPDATED"; WebhookTriggerType["COMMENT_DELETED"] = "COMMENT.DELETED"; WebhookTriggerType["TASK_ASSIGNMENT_CREATED"] = "TASK_ASSIGNMENT.CREATED"; WebhookTriggerType["TASK_ASSIGNMENT_UPDATED"] = "TASK_ASSIGNMENT.UPDATED"; WebhookTriggerType["METADATA_INSTANCE_CREATED"] = "METADATA_INSTANCE.CREATED"; WebhookTriggerType["METADATA_INSTANCE_UPDATED"] = "METADATA_INSTANCE.UPDATED"; WebhookTriggerType["METADATA_INSTANCE_DELETED"] = "METADATA_INSTANCE.DELETED"; WebhookTriggerType["FOLDER_CREATED"] = "FOLDER.CREATED"; WebhookTriggerType["FOLDER_DOWNLOADED"] = "FOLDER.DOWNLOADED"; WebhookTriggerType["FOLDER_RESTORED"] = "FOLDER.RESTORED"; WebhookTriggerType["FOLDER_DELETED"] = "FOLDER.DELETED"; WebhookTriggerType["FOLDER_COPIED"] = "FOLDER.COPIED"; WebhookTriggerType["FOLDER_MOVED"] = "FOLDER.MOVED"; WebhookTriggerType["FOLDER_TRASHED"] = "FOLDER.TRASHED"; WebhookTriggerType["FOLDER_RENAMED"] = "FOLDER.RENAMED"; WebhookTriggerType["WEBHOOK_DELETED"] = "WEBHOOK.DELETED"; WebhookTriggerType["COLLABORATION_CREATED"] = "COLLABORATION.CREATED"; WebhookTriggerType["COLLABORATION_ACCEPTED"] = "COLLABORATION.ACCEPTED"; WebhookTriggerType["COLLABORATION_REJECTED"] = "COLLABORATION.REJECTED"; WebhookTriggerType["COLLABORATION_REMOVED"] = "COLLABORATION.REMOVED"; WebhookTriggerType["COLLABORATION_UPDATED"] = "COLLABORATION.UPDATED"; WebhookTriggerType["SHARED_LINK_DELETED"] = "SHARED_LINK.DELETED"; WebhookTriggerType["SHARED_LINK_CREATED"] = "SHARED_LINK.CREATED"; WebhookTriggerType["SHARED_LINK_UPDATED"] = "SHARED_LINK.UPDATED"; WebhookTriggerType["SIGN_REQUEST_COMPLETED"] = "SIGN_REQUEST.COMPLETED"; WebhookTriggerType["SIGN_REQUEST_DECLINED"] = "SIGN_REQUEST.DECLINED"; WebhookTriggerType["SIGN_REQUEST_EXPIRED"] = "SIGN_REQUEST.EXPIRED"; })(WebhookTriggerType || (WebhookTriggerType = {})); // ----------------------------------------------------------------------------- // Private // ----------------------------------------------------------------------------- // Base path for all webhooks endpoints var BASE_PATH = '/webhooks'; // This prevents replay attacks var MAX_MESSAGE_AGE = 10 * 60; // 10 minutes /** * Compute the message signature * @see {@Link https://developer.box.com/en/guides/webhooks/handle/setup-signatures/} * * @param {string} body - The request body of the webhook message * @param {Object} headers - The request headers of the webhook message * @param {?string} signatureKey - The signature to verify the message with * @returns {?string} - The message signature (or null, if it can't be computed) * @private */ function computeSignature(body, headers, signatureKey) { if (!signatureKey) { return null; } if (headers['box-signature-version'] !== '1') { return null; } if (headers['box-signature-algorithm'] !== 'HmacSHA256') { return null; } var hmac = crypto_1.default.createHmac('sha256', signatureKey); hmac.update(body); hmac.update(headers['box-delivery-timestamp']); var signature = hmac.digest('base64'); return signature; } /** * Validate the message signature * @see {@Link https://developer.box.com/en/guides/webhooks/handle/verify-signatures/} * * @param {string} body - The request body of the webhook message * @param {Object} headers - The request headers of the webhook message * @param {string} [primarySignatureKey] - The primary signature to verify the message with * @param {string} [secondarySignatureKey] - The secondary signature to verify the message with * @returns {boolean} - True or false * @private */ function validateSignature(body, headers, primarySignatureKey, secondarySignatureKey) { // Either the primary or secondary signature must match the corresponding signature from Box // (The use of two signatures allows the signing keys to be rotated one at a time) var primarySignature = computeSignature(body, headers, primarySignatureKey); if (primarySignature && compareSignatures(primarySignature, headers['box-signature-primary'])) { return true; } var secondarySignature = computeSignature(body, headers, secondarySignatureKey); if (secondarySignature && compareSignatures(secondarySignature, headers['box-signature-secondary'])) { return true; } return false; } /** * Compare two signatures using a timing-safe comparison * @param expectedSignature Expected signature in base64 format * @param receivedSignature Received signature in base64 format */ function compareSignatures(expectedSignature, receivedSignature) { var expectedBuffer = Buffer.from(expectedSignature, 'base64'); var receivedBuffer = Buffer.from(receivedSignature, 'base64'); return crypto_1.default.timingSafeEqual(expectedBuffer, receivedBuffer); } /** * Validate that the delivery timestamp is not too far in the past (to prevent replay attacks) * * @param {Object} headers - The request headers of the webhook message * @param {int} maxMessageAge - The maximum message age (in seconds) * @returns {boolean} - True or false * @private */ function validateDeliveryTimestamp(headers, maxMessageAge) { var deliveryTime = Date.parse(headers['box-delivery-timestamp']); var currentTime = Date.now(); var messageAge = (currentTime - deliveryTime) / 1000; if (messageAge > maxMessageAge) { return false; } return true; } /** * Stringify JSON with escaped multibyte Unicode characters to ensure computed signatures match PHP's default behavior * * @param {Object} body - The parsed JSON object * @returns {string} - Stringified JSON with escaped multibyte Unicode characters * @private */ function jsonStringifyWithEscapedUnicode(body) { return JSON.stringify(body).replace(/[\u007f-\uffff]/g, function (char) { return "\\u".concat("0000".concat(char.charCodeAt(0).toString(16)).slice(-4)); }); } // ----------------------------------------------------------------------------- // Public // ----------------------------------------------------------------------------- /** * Simple manager for interacting with all 'Webhooks' endpoints and actions. * * @param {BoxClient} client The Box API Client that is responsible for making calls to the API * @constructor */ var Webhooks = /** @class */ (function () { function Webhooks(client) { // Attach the client, for making API calls this.client = client; } /** * Sets primary and secondary signatures that are used to verify the Webhooks messages * * @param {string} primaryKey - The primary signature to verify the message with * @param {string} [secondaryKey] - The secondary signature to verify the message with * @returns {void} */ Webhooks.setSignatureKeys = function (primaryKey, secondaryKey) { Webhooks.primarySignatureKey = primaryKey; if (typeof secondaryKey === 'string') { Webhooks.secondarySignatureKey = secondaryKey; } }; /** * Validate a webhook message by verifying the signature and the delivery timestamp * * @param {string|Object} body - The request body of the webhook message * @param {Object} headers - The request headers of the webhook message * @param {string} [primaryKey] - The primary signature to verify the message with. If it is sent as a parameter, it overrides the static variable primarySignatureKey * @param {string} [secondaryKey] - The secondary signature to verify the message with. If it is sent as a parameter, it overrides the static variable primarySignatureKey * @param {int} [maxMessageAge] - The maximum message age (in seconds). Defaults to 10 minutes * @returns {boolean} - True or false */ Webhooks.validateMessage = function (body, headers, primaryKey, secondaryKey, maxMessageAge) { if (!primaryKey && Webhooks.primarySignatureKey) { primaryKey = Webhooks.primarySignatureKey; } if (!secondaryKey && Webhooks.secondarySignatureKey) { secondaryKey = Webhooks.secondarySignatureKey; } if (typeof maxMessageAge !== 'number') { maxMessageAge = MAX_MESSAGE_AGE; } // For frameworks like Express that automatically parse JSON // bodies into Objects, re-stringify for signature testing if (typeof body === 'object') { // Escape forward slashes to ensure a matching signature body = jsonStringifyWithEscapedUnicode(body).replace(/\//g, '\\/'); } if (!validateSignature(body, headers, primaryKey, secondaryKey)) { return false; } if (!validateDeliveryTimestamp(headers, maxMessageAge)) { return false; } return true; }; /** * Create a new webhook on a given Box object, specified by type and ID. * * API Endpoint: '/webhooks' * Method: POST * * @param {string} targetID - Box ID of the item to create webhook on * @param {ItemType} targetType - Type of item the webhook will be created on * @param {string} notificationURL - The URL of your application where Box will notify you of events triggers * @param {WebhookTriggerType[]} triggerTypes - Array of event types that trigger notification for the target * @param {Function} [callback] - Passed the new webhook information if it was acquired successfully * @returns {Promise<Object>} A promise resolving to the new webhook object */ Webhooks.prototype.create = function (targetID, targetType, notificationURL, triggerTypes, callback) { var params = { body: { target: { id: targetID, type: targetType, }, address: notificationURL, triggers: triggerTypes, }, }; var apiPath = (0, url_path_1.default)(BASE_PATH); return this.client.wrapWithDefaultHandler(this.client.post)(apiPath, params, callback); }; /** * Returns a webhook object with the specified Webhook ID * * API Endpoint: '/webhooks/:webhookID' * Method: GET * * @param {string} webhookID - ID of the webhook to retrieve * @param {Object} [options] - Additional options for the request. Can be left null in most cases. * @param {Function} [callback] - Passed the webhook information if it was acquired successfully * @returns {Promise<Object>} A promise resolving to the webhook object */ Webhooks.prototype.get = function (webhookID, options, callback) { var params = { qs: options, }; var apiPath = (0, url_path_1.default)(BASE_PATH, webhookID); return this.client.wrapWithDefaultHandler(this.client.get)(apiPath, params, callback); }; /** * Get a list of webhooks that are active for the current application and user. * * API Endpoint: '/webhooks' * Method: GET * * @param {Object} [options] - Additional options for the request. Can be left null in most cases. * @param {int} [options.limit=100] - The number of webhooks to return * @param {string} [options.marker] - Pagination marker * @param {Function} [callback] - Passed the list of webhooks if successful, error otherwise * @returns {Promise<Object>} A promise resolving to the collection of webhooks */ Webhooks.prototype.getAll = function (options, callback) { var params = { qs: options, }; var apiPath = (0, url_path_1.default)(BASE_PATH); return this.client.wrapWithDefaultHandler(this.client.get)(apiPath, params, callback); }; /** * Update a webhook * * API Endpoint: '/webhooks/:webhookID' * Method: PUT * * @param {string} webhookID - The ID of the webhook to be updated * @param {Object} updates - Webhook fields to update * @param {string} [updates.address] - The new URL used by Box to send a notification when webhook is triggered * @param {WebhookTriggerType[]} [updates.triggers] - The new events that triggers a notification * @param {Function} [callback] - Passed the updated webhook information if successful, error otherwise * @returns {Promise<Object>} A promise resolving to the updated webhook object */ Webhooks.prototype.update = function (webhookID, updates, callback) { var apiPath = (0, url_path_1.default)(BASE_PATH, webhookID), params = { body: updates, }; return this.client.wrapWithDefaultHandler(this.client.put)(apiPath, params, callback); }; /** * Delete a specified webhook by ID * * API Endpoint: '/webhooks/:webhookID' * Method: DELETE * * @param {string} webhookID - ID of webhook to be deleted * @param {Function} [callback] - Empty response body passed if successful. * @returns {Promise<void>} A promise resolving to nothing */ Webhooks.prototype.delete = function (webhookID, callback) { var apiPath = (0, url_path_1.default)(BASE_PATH, webhookID); return this.client.wrapWithDefaultHandler(this.client.del)(apiPath, null, callback); }; /** * Primary signature key to protect webhooks against attacks. * @static * @type {?string} */ Webhooks.primarySignatureKey = null; /** * Secondary signature key to protect webhooks against attacks. * @static * @type {?string} */ Webhooks.secondarySignatureKey = null; return Webhooks; }()); /** * Enum of valid webhooks event triggers * * @readonly * @enum {WebhookTriggerType} */ Webhooks.prototype.triggerTypes = { FILE: { UPLOADED: WebhookTriggerType.FILE_UPLOADED, PREVIEWED: WebhookTriggerType.FILE_PREVIEWED, DOWNLOADED: WebhookTriggerType.FILE_DOWNLOADED, TRASHED: WebhookTriggerType.FILE_TRASHED, DELETED: WebhookTriggerType.FILE_DELETED, RESTORED: WebhookTriggerType.FILE_RESTORED, COPIED: WebhookTriggerType.FILE_COPIED, MOVED: WebhookTriggerType.FILE_MOVED, LOCKED: WebhookTriggerType.FILE_LOCKED, UNLOCKED: WebhookTriggerType.FILE_UNLOCKED, RENAMED: WebhookTriggerType.FILE_RENAMED, }, COMMENT: { CREATED: WebhookTriggerType.COMMENT_CREATED, UPDATED: WebhookTriggerType.COMMENT_UPDATED, DELETED: WebhookTriggerType.COMMENT_DELETED, }, TASK_ASSIGNMENT: { CREATED: WebhookTriggerType.TASK_ASSIGNMENT_CREATED, UPDATED: WebhookTriggerType.TASK_ASSIGNMENT_UPDATED, }, METADATA_INSTANCE: { CREATED: WebhookTriggerType.METADATA_INSTANCE_CREATED, UPDATED: WebhookTriggerType.METADATA_INSTANCE_UPDATED, DELETED: WebhookTriggerType.METADATA_INSTANCE_DELETED, }, FOLDER: { CREATED: WebhookTriggerType.FOLDER_CREATED, DOWNLOADED: WebhookTriggerType.FOLDER_DOWNLOADED, RESTORED: WebhookTriggerType.FOLDER_RESTORED, DELETED: WebhookTriggerType.FOLDER_DELETED, COPIED: WebhookTriggerType.FOLDER_COPIED, MOVED: WebhookTriggerType.FOLDER_MOVED, TRASHED: WebhookTriggerType.FOLDER_TRASHED, RENAMED: WebhookTriggerType.FOLDER_RENAMED, }, WEBHOOK: { DELETED: WebhookTriggerType.WEBHOOK_DELETED, }, COLLABORATION: { CREATED: WebhookTriggerType.COLLABORATION_CREATED, ACCEPTED: WebhookTriggerType.COLLABORATION_ACCEPTED, REJECTED: WebhookTriggerType.COLLABORATION_REJECTED, REMOVED: WebhookTriggerType.COLLABORATION_REMOVED, UPDATED: WebhookTriggerType.COLLABORATION_UPDATED, }, SHARED_LINK: { DELETED: WebhookTriggerType.SHARED_LINK_DELETED, CREATED: WebhookTriggerType.SHARED_LINK_CREATED, UPDATED: WebhookTriggerType.SHARED_LINK_UPDATED, }, SIGN_REQUEST: { COMPLETED: WebhookTriggerType.SIGN_REQUEST_COMPLETED, DECLINED: WebhookTriggerType.SIGN_REQUEST_DECLINED, EXPIRED: WebhookTriggerType.SIGN_REQUEST_EXPIRED, }, }; Webhooks.prototype.validateMessage = Webhooks.validateMessage; module.exports = Webhooks; //# sourceMappingURL=webhooks.js.map