From 34ec059605a8d947e8d103aa4afcac481db2a183 Mon Sep 17 00:00:00 2001 From: Jack Williams <1736957+jpwilliams@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:22:58 +0000 Subject: [PATCH] Refactor to allow encrypting with metadata (such as salt) in the future --- .../middleware-encryption/src/middleware.ts | 40 ++++++++++++++-- packages/middleware-encryption/src/stages.ts | 47 +++++++------------ .../src/strategies/aes.ts | 6 ++- .../src/strategies/libSodium.ts | 8 +++- 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/packages/middleware-encryption/src/middleware.ts b/packages/middleware-encryption/src/middleware.ts index 0535b13e5..124c61720 100644 --- a/packages/middleware-encryption/src/middleware.ts +++ b/packages/middleware-encryption/src/middleware.ts @@ -82,10 +82,11 @@ export abstract class EncryptionService { public abstract identifier: string; /** - * Given an `unknown` value, encrypts it and returns the encrypted value as a - * `string`. + * Given an `unknown` value, encrypts it and returns the the encrypted value. */ - public abstract encrypt(value: unknown): MaybePromise; + public abstract encrypt( + value: unknown + ): MaybePromise; /** * Given an encrypted `string`, decrypts it and returns the decrypted value as @@ -93,3 +94,36 @@ export abstract class EncryptionService { */ public abstract decrypt(value: string): MaybePromise; } + +export namespace EncryptionService { + /** + * A marker used to identify encrypted values without having to guess. + */ + export const ENCRYPTION_MARKER = "__ENCRYPTED__"; + + /** + * A marker used to identify the strategy used for encryption. + */ + export const STRATEGY_MARKER = "__STRATEGY__"; + + /** + * The encrypted value as it will be sent to Inngest. + */ + export interface EncryptedValue { + [ENCRYPTION_MARKER]: true; + [STRATEGY_MARKER]: string | undefined; + data: string; + } + + /** + * A partial encrypted value, allowing an encryption service to specify the + * data and any other metadata needed to decrypt the value. + */ + export interface PartialEncryptedValue + extends Omit< + EncryptedValue, + typeof ENCRYPTION_MARKER | typeof STRATEGY_MARKER + > { + [key: string]: unknown; + } +} diff --git a/packages/middleware-encryption/src/stages.ts b/packages/middleware-encryption/src/stages.ts index 939833cfb..f585f8eb3 100644 --- a/packages/middleware-encryption/src/stages.ts +++ b/packages/middleware-encryption/src/stages.ts @@ -1,5 +1,5 @@ import { type MiddlewareRegisterReturn } from "inngest"; -import { type EncryptionService } from "./middleware"; +import { EncryptionService } from "./middleware"; import { LEGACY_V0Service } from "./strategies/legacy"; import { LibSodiumEncryptionService } from "./strategies/libSodium"; @@ -64,11 +64,13 @@ export const getEncryptionStages = ( ...opts.legacyV0Service, }); - const encryptValue = async (value: unknown): Promise => { + const encryptValue = async ( + value: unknown + ): Promise => { return { - [ENCRYPTION_MARKER]: true, - [STRATEGY_MARKER]: service.identifier, - data: await service.encrypt(value), + [EncryptionService.ENCRYPTION_MARKER]: true, + [EncryptionService.STRATEGY_MARKER]: service.identifier, + ...(await service.encrypt(value)), }; }; @@ -103,9 +105,11 @@ export const getEncryptionStages = ( ): Promise => { // if the entire value is encrypted, match it and decrypt if (isEncryptedValue(data)) { - if (service.identifier !== data[STRATEGY_MARKER]) { + if (service.identifier !== data[EncryptionService.STRATEGY_MARKER]) { throw new Error( - `Mismatched encryption service; received an event payload using "${data[STRATEGY_MARKER]}", but the configured encryption service is "${service.identifier}"` + `Mismatched encryption service; received an event payload using "${ + data[EncryptionService.STRATEGY_MARKER] + }", but the configured encryption service is "${service.identifier}"` ); } @@ -198,14 +202,6 @@ export const getEncryptionStages = ( }, }; }; -/** - * The encrypted value as it will be sent to Inngest. - */ -export interface EncryptedValue { - [ENCRYPTION_MARKER]: true; - [STRATEGY_MARKER]: string | undefined; - data: string; -} type InputTransformer = NonNullable< Awaited< @@ -223,24 +219,17 @@ type FunctionRunHook = NonNullable; type SendEventHook = NonNullable; -export const isEncryptedValue = (value: unknown): value is EncryptedValue => { +export const isEncryptedValue = ( + value: unknown +): value is EncryptionService.EncryptedValue => { return ( typeof value === "object" && value !== null && - ENCRYPTION_MARKER in value && - value[ENCRYPTION_MARKER] === true && + EncryptionService.ENCRYPTION_MARKER in value && + value[EncryptionService.ENCRYPTION_MARKER] === true && "data" in value && typeof value["data"] === "string" && - (!(STRATEGY_MARKER in value) || typeof value[STRATEGY_MARKER] === "string") + (!(EncryptionService.STRATEGY_MARKER in value) || + typeof value[EncryptionService.STRATEGY_MARKER] === "string") ); }; - -/** - * A marker used to identify encrypted values without having to guess. - */ -const ENCRYPTION_MARKER = "__ENCRYPTED__"; - -/** - * A marker used to identify the strategy used for encryption. - */ -const STRATEGY_MARKER = "__STRATEGY__"; diff --git a/packages/middleware-encryption/src/strategies/aes.ts b/packages/middleware-encryption/src/strategies/aes.ts index 524867f20..85e286b99 100644 --- a/packages/middleware-encryption/src/strategies/aes.ts +++ b/packages/middleware-encryption/src/strategies/aes.ts @@ -34,8 +34,10 @@ export class AESEncryptionService implements EncryptionService { this.keys = keys as [string, ...string[]]; } - encrypt(value: unknown): string { - return AES.encrypt(JSON.stringify(value), this.keys[0]).toString(); + encrypt(value: unknown): EncryptionService.PartialEncryptedValue { + return { + data: AES.encrypt(JSON.stringify(value), this.keys[0]).toString(), + }; } decrypt(value: string): unknown { diff --git a/packages/middleware-encryption/src/strategies/libSodium.ts b/packages/middleware-encryption/src/strategies/libSodium.ts index a585f5f51..e7e49411e 100644 --- a/packages/middleware-encryption/src/strategies/libSodium.ts +++ b/packages/middleware-encryption/src/strategies/libSodium.ts @@ -42,7 +42,9 @@ export class LibSodiumEncryptionService implements EncryptionService { }); } - async encrypt(value: unknown): Promise { + async encrypt( + value: unknown + ): Promise { const keys = await this.keys; const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); @@ -53,7 +55,9 @@ export class LibSodiumEncryptionService implements EncryptionService { combined.set(nonce); combined.set(ciphertext, nonce.length); - return sodium.to_base64(combined, sodium.base64_variants.ORIGINAL); + return { + data: sodium.to_base64(combined, sodium.base64_variants.ORIGINAL), + }; } async decrypt(value: string): Promise {