UNPKG

@inngest/middleware-encryption

Version:
257 lines (256 loc) 12.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isV0EncryptedValue = exports.isEncryptedValue = exports.EncryptionService = exports.encryptionMiddleware = void 0; const inngest_1 = require("inngest"); const legacy_1 = require("./strategies/legacy"); const libSodium_1 = require("./strategies/libSodium"); /** * Encrypts and decrypts data sent to and from Inngest. */ const encryptionMiddleware = ( /** * Options used to configure the encryption middleware. If a custom * `encryptionService` is not provided, the `key` option is required. */ opts) => { var _a; const keys = [opts.key, ...((_a = opts.fallbackDecryptionKeys) !== null && _a !== void 0 ? _a : [])].filter(Boolean); const service = opts.encryptionService || new libSodium_1.LibSodiumEncryptionService(keys); let __v0LegacyService; const getV0LegacyService = () => { var _a; if (!__v0LegacyService) { __v0LegacyService = new legacy_1.LEGACY_V0Service(Object.assign({ key: keys, forceEncryptWithV0: Boolean((_a = opts.legacyV0Service) === null || _a === void 0 ? void 0 : _a.forceEncryptWithV0) }, opts.legacyV0Service)); } return __v0LegacyService; }; const encryptValue = (value) => __awaiter(void 0, void 0, void 0, function* () { var _a; if ((0, exports.isEncryptedValue)(value) || (0, exports.isV0EncryptedValue)(value)) { console.warn("Encryption middleware is encrypting a value that appears to be already encrypted. Did you add the middleware twice?"); } if ((_a = opts.legacyV0Service) === null || _a === void 0 ? void 0 : _a.forceEncryptWithV0) { return { [EncryptionService.ENCRYPTION_MARKER]: true, data: getV0LegacyService().service.encrypt(value), }; } return { [EncryptionService.ENCRYPTION_MARKER]: true, [EncryptionService.STRATEGY_MARKER]: service.identifier, data: yield service.encrypt(value), }; }); const decryptValue = (value) => __awaiter(void 0, void 0, void 0, function* () { if ((0, exports.isEncryptedValue)(value)) { return service.decrypt(value.data); } if ((0, exports.isV0EncryptedValue)(value)) { return getV0LegacyService().service.decrypt(value.data); } return value; }); const fieldShouldBeEncrypted = (field) => { if (typeof opts.eventEncryptionField === "undefined") { return field === EncryptionService.DEFAULT_ENCRYPTED_EVENT_FIELD; } return opts.eventEncryptionField === field; }; const encryptEventData = (eventData) => __awaiter(void 0, void 0, void 0, function* () { var _a; if ((_a = opts.legacyV0Service) === null || _a === void 0 ? void 0 : _a.forceEncryptWithV0) { const result = getV0LegacyService().encryptEventData(eventData); if (!isRecord(result)) { return eventData; } return result; } const encryptedEntries = yield Promise.all(Object.keys(eventData).map((key) => __awaiter(void 0, void 0, void 0, function* () { let value = eventData[key]; if (fieldShouldBeEncrypted(key)) { value = yield encryptValue(eventData[key]); } return [key, value]; }))); const encryptedData = encryptedEntries.reduce((acc, [key, value]) => { return Object.assign(Object.assign({}, acc), { [key]: value }); }, {}); return encryptedData; }); const decryptEventData = (eventData) => __awaiter(void 0, void 0, void 0, function* () { const decryptedEntries = yield Promise.all(Object.keys(eventData).map((key) => __awaiter(void 0, void 0, void 0, function* () { return [key, yield decryptValue(eventData[key])]; }))); const decryptedData = decryptedEntries.reduce((acc, [key, value]) => { return Object.assign(Object.assign({}, acc), { [key]: value }); }, {}); return decryptedData; }); class EncryptionMiddleware extends inngest_1.Middleware.BaseMiddleware { constructor() { super(...arguments); this.id = "inngest:encryption"; } // Decrypt event data before it reaches the function handler. // IMPORTANT: Do not decrypt step data here. That won't work with // checkpointing because we don't "enter" the function once per step. transformFunctionInput(arg) { return __awaiter(this, void 0, void 0, function* () { var _a; let decryptedEvent = arg.ctx.event; if ((_a = arg.ctx.event) === null || _a === void 0 ? void 0 : _a.data) { decryptedEvent = Object.assign(Object.assign({}, arg.ctx.event), { data: yield decryptEventData(arg.ctx.event.data) }); } let decryptedEvents = arg.ctx.events; if (arg.ctx.events) { // @ts-expect-error - Promise.all returns T[] but events is a non-empty tuple decryptedEvents = yield Promise.all(arg.ctx.events.map((event) => __awaiter(this, void 0, void 0, function* () { if (!event.data) { return event; } return Object.assign(Object.assign({}, event), { data: yield decryptEventData(event.data) }); }))); } return Object.assign(Object.assign({}, arg), { ctx: Object.assign(Object.assign({}, arg.ctx), { event: decryptedEvent, events: decryptedEvents }) }); }); } // Encrypt the `step.invoke` input data. transformStepInput(arg) { return __awaiter(this, void 0, void 0, function* () { if (opts.decryptOnly) { return arg; } // `step.invoke` is the only step whose input needs to be encrypted. We'll // encrypt `step.sendEvent` using the `transformSendEvent` hook. if (arg.stepInfo.stepType !== "invoke") { return arg; } const encryptedInput = yield Promise.all(arg.input.map((item) => __awaiter(this, void 0, void 0, function* () { if (!isRecord(item)) { return item; } const { payload } = item; if (!isRecord(payload)) { return item; } const { data } = payload; if (!isRecord(data)) { return item; } return Object.assign(Object.assign({}, item), { payload: Object.assign(Object.assign({}, payload), { // Encrypt invoke data as if it's an event (i.e. only encrypt the // specific field) data: yield encryptEventData(data) }) }); }))); return Object.assign(Object.assign({}, arg), { input: encryptedInput }); }); } // Encrypt the Inngest function's return value before it's sent to the server. wrapFunctionHandler(_a) { return __awaiter(this, arguments, void 0, function* ({ next, }) { const output = yield next(); if (opts.decryptOnly) { return output; } if (output !== undefined && output !== null) { return encryptValue(output); } return output; }); } // Encrypt step output before it's sent to the server. wrapStepHandler(_a) { return __awaiter(this, arguments, void 0, function* ({ next }) { const output = yield next(); if (opts.decryptOnly) { return output; } // No need to encrypt `undefined` or `null` values, since they inherently // do not contain any sensitive data if (output !== undefined && output !== null) { return encryptValue(output); } return output; }); } // Decrypt memoized step data before it's returned into the Inngest function // handler. wrapStep(_a) { return __awaiter(this, arguments, void 0, function* ({ next }) { return decryptValue(yield next()); }); } // Encrypt event data before sending to the server. transformSendEvent(arg) { return __awaiter(this, void 0, void 0, function* () { if (opts.decryptOnly) { return arg; } const encryptedEvents = yield Promise.all(arg.events.map((event) => __awaiter(this, void 0, void 0, function* () { if (!event.data) { return event; } return Object.assign(Object.assign({}, event), { data: yield encryptEventData(event.data) }); }))); return Object.assign(Object.assign({}, arg), { events: encryptedEvents }); }); } } return EncryptionMiddleware; }; exports.encryptionMiddleware = encryptionMiddleware; /** * A service that encrypts and decrypts data. You can implement this abstract * class to provide your own encryption service, or use the default encryption * service provided by this package. */ class EncryptionService { } exports.EncryptionService = EncryptionService; (function (EncryptionService) { /** * A marker used to identify encrypted values without having to guess. */ EncryptionService.ENCRYPTION_MARKER = "__ENCRYPTED__"; /** * A marker used to identify the strategy used for encryption. */ EncryptionService.STRATEGY_MARKER = "__STRATEGY__"; /** * The default field used to store encrypted values in events. */ EncryptionService.DEFAULT_ENCRYPTED_EVENT_FIELD = "encrypted"; })(EncryptionService || (exports.EncryptionService = EncryptionService = {})); const isRecord = (value) => { return typeof value === "object" && value !== null && !Array.isArray(value); }; const isEncryptedValue = (value) => { return (typeof value === "object" && value !== null && EncryptionService.ENCRYPTION_MARKER in value && value[EncryptionService.ENCRYPTION_MARKER] === true && "data" in value && typeof value["data"] === "string" && EncryptionService.STRATEGY_MARKER in value && typeof value[EncryptionService.STRATEGY_MARKER] === "string"); }; exports.isEncryptedValue = isEncryptedValue; const isV0EncryptedValue = (value) => { return (typeof value === "object" && value !== null && EncryptionService.ENCRYPTION_MARKER in value && value[EncryptionService.ENCRYPTION_MARKER] === true && "data" in value && typeof value["data"] === "string" && !(EncryptionService.STRATEGY_MARKER in value)); }; exports.isV0EncryptedValue = isV0EncryptedValue;