UNPKG

@sourceloop/ctrl-plane-tenant-management-service

Version:

Tenant Management microservice for SaaS control plane

79 lines 3.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebhookVerifierProvider = void 0; const tslib_1 = require("tslib"); const core_1 = require("@loopback/core"); const rest_1 = require("@loopback/rest"); const keys_1 = require("../keys"); const services_1 = require("../services"); const repository_1 = require("@loopback/repository"); const repositories_1 = require("../repositories"); const core_2 = require("@sourceloop/core"); const crypto_1 = require("crypto"); const loopback4_authentication_1 = require("loopback4-authentication"); let WebhookVerifierProvider = class WebhookVerifierProvider { constructor(webhookConfig, cryptoHelperService, webhookSecretRepo, logger, setCurrentUser, systemUser) { this.webhookConfig = webhookConfig; this.cryptoHelperService = cryptoHelperService; this.webhookSecretRepo = webhookSecretRepo; this.logger = logger; this.setCurrentUser = setCurrentUser; this.systemUser = systemUser; } value() { return this.intercept.bind(this); } async intercept(invocationCtx, next) { const { request } = invocationCtx.parent; const value = request.body; const timestamp = Number(request.headers[this.webhookConfig.timestampHeaderName]); if (isNaN(timestamp)) { this.logger.error('Invalid timestamp'); throw new rest_1.HttpErrors.Unauthorized(); } const signature = request.headers[this.webhookConfig.signatureHeaderName]; if (!signature || typeof signature !== 'string') { this.logger.error('Missing signature string'); throw new rest_1.HttpErrors.Unauthorized(); } const initiatorId = value.initiatorId; const secretInfo = await this.webhookSecretRepo.get(initiatorId); if (!secretInfo) { this.logger.error('No secret found for this initiator'); throw new rest_1.HttpErrors.Unauthorized(); } const expectedSignature = this.cryptoHelperService.generateHmacForWebhookVerification(`${JSON.stringify(value)}${secretInfo.context}`, timestamp, secretInfo.secret); try { // actual signature should be equal to expected signature // timing safe equal is used to prevent timing attacks if (!(0, crypto_1.timingSafeEqual)(Buffer.from(signature), Buffer.from(expectedSignature))) { this.logger.error('Invalid signature'); throw new rest_1.HttpErrors.Unauthorized(); } const TIMESTAMP_TOLERANCE_MS = 20000; // 20 seconds // timestamp should be within 5-20 seconds if (Math.abs(timestamp - Date.now()) > TIMESTAMP_TOLERANCE_MS) { this.logger.error('Timestamp out of tolerance'); throw new rest_1.HttpErrors.Unauthorized(); } } catch (e) { this.logger.error(e); throw new rest_1.HttpErrors.Unauthorized(); } this.setCurrentUser(this.systemUser); return next(); } }; exports.WebhookVerifierProvider = WebhookVerifierProvider; exports.WebhookVerifierProvider = WebhookVerifierProvider = tslib_1.__decorate([ tslib_1.__param(0, (0, core_1.inject)(keys_1.WEBHOOK_CONFIG)), tslib_1.__param(1, (0, core_1.service)(services_1.CryptoHelperService)), tslib_1.__param(2, (0, repository_1.repository)(repositories_1.WebhookSecretRepository)), tslib_1.__param(3, (0, core_1.inject)(core_2.LOGGER.LOGGER_INJECT)), tslib_1.__param(4, core_1.inject.setter(loopback4_authentication_1.AuthenticationBindings.CURRENT_USER)), tslib_1.__param(5, (0, core_1.inject)(keys_1.SYSTEM_USER)), tslib_1.__metadata("design:paramtypes", [Object, services_1.CryptoHelperService, repositories_1.WebhookSecretRepository, Object, Function, Object]) ], WebhookVerifierProvider); //# sourceMappingURL=webhook-verifier.interceptor.js.map