@inngest/middleware-encryption
Version:
E2E encryption middleware for Inngest.
186 lines (185 loc) • 9.36 kB
JavaScript
;
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.getEncryptionStages = void 0;
const middleware_1 = require("./middleware");
const legacy_1 = require("./strategies/legacy");
const libSodium_1 = require("./strategies/libSodium");
/**
* Encrypts and decrypts data sent to and from Inngest.
*/
const getEncryptionStages = (
/**
* Options used to configure the encryption middleware. If a custom
* `encryptionService` is not provided, the `key` option is required.
*/
opts) => {
var _a;
/**
* The keys used to encrypt and decrypt data. If multiple keys are provided,
* the first key will be used to encrypt data and all keys will be tried when
* decrypting data.
*
* We perform this internally to make the way users provide these keys to us
* much more explicit; it is confusing to add a new key to the end of a keys
* array as the first part of a migration.
*/
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;
/**
* Lazy-load the V0 service. This is used to ensure that the V0 service is
* only loaded if it's needed.
*/
const getV0LegacyService = () => {
var _a;
return (__v0LegacyService !== null && __v0LegacyService !== void 0 ? __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))));
};
const encryptValue = (value) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
// Show a warning if we believe the value is already encrypted. This may
// happen if user accidentally adds encryption middleware at both the
// client and function levels.
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 {
[middleware_1.EncryptionService.ENCRYPTION_MARKER]: true,
data: getV0LegacyService().service.encrypt(value),
};
}
return {
[middleware_1.EncryptionService.ENCRYPTION_MARKER]: true,
[middleware_1.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 === middleware_1.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) {
return getV0LegacyService().encryptEventData(eventData);
}
const encryptedEntries = yield Promise.all(Object.keys(eventData).map((key) => __awaiter(void 0, void 0, void 0, function* () {
const value = fieldShouldBeEncrypted(key)
? yield encryptValue(eventData[key])
: 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;
});
return {
encrypt: {
onFunctionRun: () => {
if (opts.decryptOnly) {
return {};
}
return {
transformOutput: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
return {
result: {
data: ctx.result.data && (yield encryptValue(ctx.result.data)),
},
};
}),
};
},
onSendEvent: () => {
return {
transformInput: (_a) => __awaiter(void 0, [_a], void 0, function* ({ payloads }) {
return {
payloads: yield Promise.all(payloads.map((payload) => __awaiter(void 0, void 0, void 0, function* () {
return (Object.assign(Object.assign({}, payload), { data: payload.data && (yield encryptEventData(payload.data)) }));
}))),
};
}),
};
},
},
decrypt: {
onFunctionRun: () => {
return {
transformInput: (_a) => __awaiter(void 0, [_a], void 0, function* ({ ctx, steps }) {
var _b;
const decryptedSteps = Promise.all(steps.map((step) => __awaiter(void 0, void 0, void 0, function* () {
return (Object.assign(Object.assign({}, step), { data: step.data && (yield decryptValue(step.data)) }));
})));
const decryptedEvent = ctx.event &&
(() => __awaiter(void 0, void 0, void 0, function* () {
return (Object.assign(Object.assign({}, ctx.event), { data: ctx.event.data && (yield decryptEventData(ctx.event.data)) }));
}))();
const decryptedEvents = ctx.events &&
Promise.all((_b = ctx.events) === null || _b === void 0 ? void 0 : _b.map((event) => __awaiter(void 0, void 0, void 0, function* () {
return (Object.assign(Object.assign({}, event), { data: event.data && (yield decryptEventData(event.data)) }));
})));
const inputTransformer = {
steps: yield decryptedSteps,
ctx: {
event: yield decryptedEvent,
events: yield decryptedEvents,
},
};
return inputTransformer;
}),
};
},
},
};
};
exports.getEncryptionStages = getEncryptionStages;
const isEncryptedValue = (value) => {
return (typeof value === "object" &&
value !== null &&
middleware_1.EncryptionService.ENCRYPTION_MARKER in value &&
value[middleware_1.EncryptionService.ENCRYPTION_MARKER] === true &&
"data" in value &&
typeof value["data"] === "string" &&
middleware_1.EncryptionService.STRATEGY_MARKER in value &&
typeof value[middleware_1.EncryptionService.STRATEGY_MARKER] === "string");
};
exports.isEncryptedValue = isEncryptedValue;
const isV0EncryptedValue = (value) => {
return (typeof value === "object" &&
value !== null &&
middleware_1.EncryptionService.ENCRYPTION_MARKER in value &&
value[middleware_1.EncryptionService.ENCRYPTION_MARKER] === true &&
"data" in value &&
typeof value["data"] === "string" &&
!(middleware_1.EncryptionService.STRATEGY_MARKER in value));
};
exports.isV0EncryptedValue = isV0EncryptedValue;