box-node-sdk
Version:
Official SDK for Box Plaform APIs
399 lines • 17.9 kB
JavaScript
"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