From 14c30cbe33d21dfbfa02e43f6cb7ef8f684eab8e Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 15:33:58 -0700 Subject: [PATCH 01/13] Add @types/node as a dev dependency --- packages/embed-sdk/package.json | 3 ++- pnpm-lock.yaml | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/embed-sdk/package.json b/packages/embed-sdk/package.json index 32cac6e..aace962 100644 --- a/packages/embed-sdk/package.json +++ b/packages/embed-sdk/package.json @@ -44,6 +44,7 @@ ], "devDependencies": { "@sigmacomputing/eslint-config": "workspace:*", - "@sigmacomputing/typescript-config": "workspace:*" + "@sigmacomputing/typescript-config": "workspace:*", + "@types/node": "^20.17.16" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa123f3..a076aac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,6 +111,9 @@ importers: '@sigmacomputing/typescript-config': specifier: workspace:* version: link:../../tooling/typescript-config + '@types/node': + specifier: ^20.17.16 + version: 20.17.16 packages/react-embed-sdk: dependencies: @@ -5332,7 +5335,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.4(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -5377,7 +5380,7 @@ snapshots: is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -5453,7 +5456,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 From bd2508c44e3c3ad17940211775d64e1c72fbb087 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 15:34:24 -0700 Subject: [PATCH 02/13] Add encryption module --- packages/embed-sdk/src/encryption.ts | 469 +++++++++++++++++++++++++++ 1 file changed, 469 insertions(+) create mode 100644 packages/embed-sdk/src/encryption.ts diff --git a/packages/embed-sdk/src/encryption.ts b/packages/embed-sdk/src/encryption.ts new file mode 100644 index 0000000..b0f18e2 --- /dev/null +++ b/packages/embed-sdk/src/encryption.ts @@ -0,0 +1,469 @@ +import crypto from 'node:crypto'; + +/* + * Configuration Constants + */ + +/** + * Constants for PBKDF2-HMAC-SHA256 key derivation. + * + * We are using PBKDF2 with SHA-256, 600000 iterations, and a 128-bit salt in accordance with + * {@link https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf NIST} + * and + * {@link https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 OWASP} + * recommendations. + * + * The key length is 256 bits since we are using AES-256-GCM. + */ +const PBKDF2_HMAC_SHA256_KEY_DERIVATION = { + DIGEST: 'sha256', + ITERATIONS: 600_000, + KEY_LENGTH_BYTES: 32, // 256 bits + SALT_LENGTH_BYTES: 16, // 128 bits +} as const; + +/** + * Constants for AES-256-GCM encryption. + * + * We are using a 96-bit IV and a 128-bit tag in accordance with + * {@link https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf NIST} + * recommendations. + */ +const AES_256_GCM_ENCRYPTION = { + ALGORITHM: 'aes-256-gcm', + IV_LENGTH_BYTES: 12, // 96 bits + TAG_LENGTH_BYTES: 16, // 128 bits +} as const; + +/* + * Branded Types for Cryptographic Buffers + */ + +interface PassphraseBrand { + readonly Passphrase: unique symbol; +} +type Passphrase_t = Buffer & PassphraseBrand; + +interface SaltBrand { + readonly Salt: unique symbol; +} +type Salt_t = Buffer & SaltBrand; + +interface TagBrand { + readonly Tag: unique symbol; +} +type Tag_t = Buffer & TagBrand; + +interface IVBrand { + readonly IV: unique symbol; +} +type IV_t = Buffer & IVBrand; + +interface CiphertextBrand { + readonly Ciphertext: unique symbol; +} +type Ciphertext_t = Buffer & CiphertextBrand; + +interface PlaintextBrand { + readonly Plaintext: unique symbol; +} +type Plaintext_t = Buffer & PlaintextBrand; + +interface SymmetricKeyBrand { + readonly SymmetricKey: unique symbol; +} +type SymmetricKey_t = Buffer & SymmetricKeyBrand; + +/* + * Interface Definitions for Cryptographic Operations + */ + +interface KeyDerivationOutput_t { + key: SymmetricKey_t; +} + +interface KeyEncryptionOutput_t { + tag: Tag_t; + iv: IV_t; + ciphertext: Ciphertext_t; +} + +interface KeyDecryptionOutput_t { + plaintext: Plaintext_t; +} + +type PassphraseEncryptionOutput_t = KeyEncryptionOutput_t & { + salt: Salt_t; +}; + +type PassphraseDecryptionOutput_t = KeyDecryptionOutput_t; + +interface EncodedPassphraseEncryptionOutputBrand { + readonly EncodedPassphraseEncryptionOutput: unique symbol; +} + +type EncodedPassphraseEncryptionOutput_t = string & + EncodedPassphraseEncryptionOutputBrand; + +/* + * Type Validation Functions + */ + +function isPassphrase(value: unknown): value is Passphrase_t { + if (!Buffer.isBuffer(value)) return false; + // The passphrase can be any length, so we don't have anything else to check. + return true; +} + +function isSalt(value: unknown): value is Salt_t { + if (!Buffer.isBuffer(value)) return false; + if (value.length !== PBKDF2_HMAC_SHA256_KEY_DERIVATION.SALT_LENGTH_BYTES) + return false; + return true; +} + +function isTag(value: unknown): value is Tag_t { + if (!Buffer.isBuffer(value)) return false; + if (value.length !== AES_256_GCM_ENCRYPTION.TAG_LENGTH_BYTES) return false; + return true; +} + +function isIV(value: unknown): value is IV_t { + if (!Buffer.isBuffer(value)) return false; + if (value.length !== AES_256_GCM_ENCRYPTION.IV_LENGTH_BYTES) return false; + return true; +} + +function isCiphertext(value: unknown): value is Ciphertext_t { + if (!Buffer.isBuffer(value)) return false; + // The ciphertext can be any length, so we don't have anything else to check. + return true; +} + +function isPlaintext(value: unknown): value is Plaintext_t { + if (!Buffer.isBuffer(value)) return false; + // The plaintext can be any length, so we don't have anything else to check. + return true; +} + +function isSymmetricKey(value: unknown): value is SymmetricKey_t { + if (!Buffer.isBuffer(value)) return false; + if (value.length !== PBKDF2_HMAC_SHA256_KEY_DERIVATION.KEY_LENGTH_BYTES) + return false; + return true; +} + +function isPassphraseEncryptionOutput( + value: unknown, +): value is PassphraseEncryptionOutput_t { + // The input should be a non-null object + if (!(value && typeof value === 'object')) return false; + // The object should have these properties + if (!('salt' in value)) return false; + if (!('iv' in value)) return false; + if (!('tag' in value)) return false; + if (!('ciphertext' in value)) return false; + // The properties should be the correct type + if (!isSalt(value.salt)) return false; + if (!isIV(value.iv)) return false; + if (!isTag(value.tag)) return false; + if (!isCiphertext(value.ciphertext)) return false; + return true; +} + +function isEncodedPassphraseEncryptionOutput( + value: unknown, +): value is EncodedPassphraseEncryptionOutput_t { + if (typeof value !== 'string') return false; + const parts = value.split('.'); + if (parts.length !== 4) return false; + const [salt, iv, tag, ciphertext] = parts; + if (!isSalt(Buffer.from(salt, 'base64'))) return false; + if (!isIV(Buffer.from(iv, 'base64'))) return false; + if (!isTag(Buffer.from(tag, 'base64'))) return false; + if (!isCiphertext(Buffer.from(ciphertext, 'base64'))) return false; + return true; +} + +/* + * Type Conversion Functions + */ + +function asPassphrase(value: unknown): Passphrase_t { + if (!isPassphrase(value)) { + throw new Error('Invalid passphrase.'); + } + return value; +} + +function asSalt(value: unknown): Salt_t { + if (!isSalt(value)) { + throw new Error('Invalid salt.'); + } + return value; +} + +function asTag(value: unknown): Tag_t { + if (!isTag(value)) { + throw new Error('Invalid tag.'); + } + return value; +} + +function asIV(value: unknown): IV_t { + if (!isIV(value)) { + throw new Error('Invalid IV.'); + } + return value; +} + +function asCiphertext(value: unknown): Ciphertext_t { + if (!isCiphertext(value)) { + throw new Error('Invalid ciphertext.'); + } + return value; +} + +function asPlaintext(value: unknown): Plaintext_t { + if (!isPlaintext(value)) { + throw new Error('Invalid plaintext.'); + } + return value; +} + +function asSymmetricKey(value: unknown): SymmetricKey_t { + if (!isSymmetricKey(value)) { + throw new Error('Invalid symmetric key.'); + } + return value; +} + +function asPassphraseEncryptionOutput( + value: unknown, +): PassphraseEncryptionOutput_t { + if (!isPassphraseEncryptionOutput(value)) { + throw new Error('Invalid encryption output.'); + } + return value; +} + +/** + * Validates and brands a given value as a properly-encoded encryption output. + * + * @param value the value to validate and brand + * @returns the branded value + */ +function asEncodedPassphraseEncryptionOutput( + value: unknown, +): EncodedPassphraseEncryptionOutput_t { + if (!isEncodedPassphraseEncryptionOutput(value)) { + throw new Error('Invalid encoded encryption output.'); + } + return value; +} + +/* + * Utility Functions for Generating Cryptographic Values + */ + +function generateIV(): IV_t { + return asIV(crypto.randomBytes(AES_256_GCM_ENCRYPTION.IV_LENGTH_BYTES)); +} + +function generateSalt(): Salt_t { + return asSalt( + crypto.randomBytes(PBKDF2_HMAC_SHA256_KEY_DERIVATION.SALT_LENGTH_BYTES), + ); +} + +/* + * Core Cryptographic Functions + */ + +function deriveKeyFromPassphrase( + passphrase: Passphrase_t, + salt: Salt_t, +): KeyDerivationOutput_t { + return { + key: asSymmetricKey( + crypto.pbkdf2Sync( + passphrase, + salt, + PBKDF2_HMAC_SHA256_KEY_DERIVATION.ITERATIONS, + PBKDF2_HMAC_SHA256_KEY_DERIVATION.KEY_LENGTH_BYTES, + PBKDF2_HMAC_SHA256_KEY_DERIVATION.DIGEST, + ), + ), + }; +} + +function encryptWithKey( + key: SymmetricKey_t, + plaintext: Plaintext_t, +): KeyEncryptionOutput_t { + const iv = generateIV(); + const cipher = crypto.createCipheriv( + AES_256_GCM_ENCRYPTION.ALGORITHM, + key, + iv, + { + authTagLength: AES_256_GCM_ENCRYPTION.TAG_LENGTH_BYTES, + }, + ); + const ciphertext = asCiphertext( + Buffer.concat([cipher.update(plaintext), cipher.final()]), + ); + const tag = asTag(cipher.getAuthTag()); + return { + tag, + iv, + ciphertext, + }; +} + +function decryptWithKey( + key: SymmetricKey_t, + iv: IV_t, + tag: Tag_t, + ciphertext: Ciphertext_t, +): KeyDecryptionOutput_t { + const decipher = crypto.createDecipheriv( + AES_256_GCM_ENCRYPTION.ALGORITHM, + key, + iv, + { + authTagLength: tag.length, + }, + ); + decipher.setAuthTag(tag); + const plaintext = asPlaintext( + Buffer.concat([decipher.update(ciphertext), decipher.final()]), + ); + return { plaintext }; +} + +function encryptWithPassphrase( + passphrase: Passphrase_t, + plaintext: Plaintext_t, +): PassphraseEncryptionOutput_t { + const salt = generateSalt(); + const { key } = deriveKeyFromPassphrase(passphrase, salt); + const { tag, iv, ciphertext } = encryptWithKey(key, plaintext); + return { salt, iv, tag, ciphertext }; +} + +function decryptWithPassphrase( + passphrase: Passphrase_t, + salt: Salt_t, + iv: IV_t, + tag: Tag_t, + ciphertext: Ciphertext_t, +): PassphraseDecryptionOutput_t { + const { key } = deriveKeyFromPassphrase(passphrase, salt); + return decryptWithKey(key, iv, tag, ciphertext); +} + +/* + * Encoding and Decoding Functions + */ + +function encodeEncryptedToken( + salt: Salt_t, + iv: IV_t, + tag: Tag_t, + ciphertext: Ciphertext_t, +): string { + const encodedSalt = salt.toString('base64'); + const encodedIV = iv.toString('base64'); + const encodedTag = tag.toString('base64'); + const encodedCiphertext = ciphertext.toString('base64'); + return `${encodedSalt}.${encodedIV}.${encodedTag}.${encodedCiphertext}`; +} + +function decodeEncryptedToken( + encodedToken: EncodedPassphraseEncryptionOutput_t, +): PassphraseEncryptionOutput_t { + const parts = encodedToken.split('.'); + if (parts.length !== 4) { + throw new Error('Expected 4 components in encoded token.'); + } + const [encodedSalt, encodedIV, encodedTag, encodedCiphertext] = parts; + const salt = asSalt(Buffer.from(encodedSalt, 'base64')); + const iv = asIV(Buffer.from(encodedIV, 'base64')); + const tag = asTag(Buffer.from(encodedTag, 'base64')); + const ciphertext = asCiphertext(Buffer.from(encodedCiphertext, 'base64')); + return { salt, iv, tag, ciphertext }; +} + +function encodeEncryptionOutput( + encryptionOutput: PassphraseEncryptionOutput_t, +): EncodedPassphraseEncryptionOutput_t { + return asEncodedPassphraseEncryptionOutput( + encodeEncryptedToken( + encryptionOutput.salt, + encryptionOutput.iv, + encryptionOutput.tag, + encryptionOutput.ciphertext, + ), + ); +} + +function decodeEncryptionOutput( + encodedOutput: EncodedPassphraseEncryptionOutput_t, +): PassphraseEncryptionOutput_t { + return asPassphraseEncryptionOutput(decodeEncryptedToken(encodedOutput)); +} + +/* + * API for encrypting and decrypting OAuth tokens + */ + +/** + * Encrypt the OAuth token using the embed secret. + * + * @param param0 + * @param param0.embedSecret the embed secret to use for encryption + * @param param0.oauthToken the OAuth token to encrypt + * @returns the encrypted token, encoded as a string + */ +export function encrypt({ + embedSecret, + oauthToken, +}: { + embedSecret: string; + oauthToken: string; +}): string { + const passphrase = asPassphrase(Buffer.from(embedSecret, 'utf8')); + const plaintext = asPlaintext(Buffer.from(oauthToken, 'utf8')); + const encryptionOutput = encryptWithPassphrase(passphrase, plaintext); + return encodeEncryptionOutput(encryptionOutput); +} + +/** + * Decrypt the OAuth token using the embed secret. + * + * @param param0 + * @param param0.embedSecret the embed secret to use for decryption + * @param param0.encryptedToken the encrypted OAuth token to decrypt + * @returns the decrypted token + */ +export function decrypt({ + embedSecret, + encryptedToken, +}: { + embedSecret: string; + encryptedToken: string; +}): string { + const passphrase = asPassphrase(Buffer.from(embedSecret, 'utf8')); + const encryptionOutput = decodeEncryptionOutput( + asEncodedPassphraseEncryptionOutput(encryptedToken), + ); + const { plaintext } = decryptWithPassphrase( + passphrase, + encryptionOutput.salt, + encryptionOutput.iv, + encryptionOutput.tag, + encryptionOutput.ciphertext, + ); + return plaintext.toString('utf8'); +} From 12e24cd17071da27afb93bda2fffbd2acac554ac Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 15:35:03 -0700 Subject: [PATCH 03/13] Export from index --- packages/embed-sdk/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/embed-sdk/src/index.ts b/packages/embed-sdk/src/index.ts index 319ab9f..79b8fa8 100644 --- a/packages/embed-sdk/src/index.ts +++ b/packages/embed-sdk/src/index.ts @@ -1,3 +1,4 @@ export type * from "./types"; export * from "./wrapper"; export * from "./mutations"; +export * from "./encryption"; From 7e1dd88076e5669637ff5540b44f9d53b9ba1591 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 15:41:02 -0700 Subject: [PATCH 04/13] Change crypto import --- packages/embed-sdk/src/encryption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/embed-sdk/src/encryption.ts b/packages/embed-sdk/src/encryption.ts index b0f18e2..91144ad 100644 --- a/packages/embed-sdk/src/encryption.ts +++ b/packages/embed-sdk/src/encryption.ts @@ -1,4 +1,4 @@ -import crypto from 'node:crypto'; +import crypto from 'crypto'; /* * Configuration Constants From 3ef345b95fbb47deaf2c2038a0e65e54e7280213 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:05:41 -0700 Subject: [PATCH 05/13] Move to new package --- packages/embed-sdk/src/index.ts | 1 - packages/node-embed-sdk/.eslintrc.js | 10 ++++ packages/node-embed-sdk/README.md | 58 +++++++++++++++++++ packages/node-embed-sdk/package.json | 54 +++++++++++++++++ .../src/encryption.ts | 0 packages/node-embed-sdk/src/index.ts | 1 + packages/node-embed-sdk/tsconfig.json | 16 +++++ packages/node-embed-sdk/tsconfig.lint.json | 8 +++ packages/node-embed-sdk/tsup.config.js | 15 +++++ packages/node-embed-sdk/turbo.json | 12 ++++ pnpm-lock.yaml | 12 ++++ 11 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 packages/node-embed-sdk/.eslintrc.js create mode 100644 packages/node-embed-sdk/README.md create mode 100644 packages/node-embed-sdk/package.json rename packages/{embed-sdk => node-embed-sdk}/src/encryption.ts (100%) create mode 100644 packages/node-embed-sdk/src/index.ts create mode 100644 packages/node-embed-sdk/tsconfig.json create mode 100644 packages/node-embed-sdk/tsconfig.lint.json create mode 100644 packages/node-embed-sdk/tsup.config.js create mode 100644 packages/node-embed-sdk/turbo.json diff --git a/packages/embed-sdk/src/index.ts b/packages/embed-sdk/src/index.ts index 79b8fa8..319ab9f 100644 --- a/packages/embed-sdk/src/index.ts +++ b/packages/embed-sdk/src/index.ts @@ -1,4 +1,3 @@ export type * from "./types"; export * from "./wrapper"; export * from "./mutations"; -export * from "./encryption"; diff --git a/packages/node-embed-sdk/.eslintrc.js b/packages/node-embed-sdk/.eslintrc.js new file mode 100644 index 0000000..8e1c45f --- /dev/null +++ b/packages/node-embed-sdk/.eslintrc.js @@ -0,0 +1,10 @@ +/** @type {import("eslint").Linter.Config} */ +module.exports = { + root: true, + extends: ["@sigmacomputing/eslint-config/react-internal.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.lint.json", + tsconfigRootDir: __dirname, + }, +}; diff --git a/packages/node-embed-sdk/README.md b/packages/node-embed-sdk/README.md new file mode 100644 index 0000000..8138a67 --- /dev/null +++ b/packages/node-embed-sdk/README.md @@ -0,0 +1,58 @@ +# Sigma Node.js Embed SDK + +This package provides Node.js utilities for working with Sigma Computing's Embed API, with a focus on server-side functionality like token encryption and decryption. + +## Getting Started + +To use the node-embed-sdk in your project, you can install it using your node package manager. + +**Using npm:** + +```code +npm install @sigmacomputing/node-embed-sdk +``` + +**yarn:** + +```code +yarn add @sigmacomputing/node-embed-sdk +``` + +**pnpm:** + +```code +pnpm add @sigmacomputing/node-embed-sdk +``` + +## Features + +### Token Encryption and Decryption + +The SDK provides utilities for encrypting and decrypting OAuth tokens using AES-256-GCM encryption: + +```typescript +import { encrypt, decrypt } from '@sigmacomputing/node-embed-sdk'; + +// Encrypt an OAuth token +const encryptedToken = encrypt({ + embedSecret: 'your-embed-secret', + oauthToken: 'your-oauth-token' +}); + +// Decrypt an encrypted token +const decryptedToken = decrypt({ + embedSecret: 'your-embed-secret', + encryptedToken: encryptedToken +}); +``` + +## Security + +This package implements industry-standard encryption practices: + +- PBKDF2 with SHA-256, 600,000 iterations, and a 128-bit salt for key derivation +- AES-256-GCM for encryption with a 96-bit IV and a 128-bit authentication tag + +## Requirements + +- Node.js 18 or higher diff --git a/packages/node-embed-sdk/package.json b/packages/node-embed-sdk/package.json new file mode 100644 index 0000000..77e77a8 --- /dev/null +++ b/packages/node-embed-sdk/package.json @@ -0,0 +1,54 @@ +{ + "name": "@sigmacomputing/node-embed-sdk", + "author": "sigmacomputing", + "version": "0.1.0", + "description": "Node.js SDK for Sigma Computing with encryption/decryption utilities", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/sigmacomputing/embed-sdk.git", + "directory": "packages/node-embed-sdk" + }, + "homepage": "https://sigmacomputing.com", + "bugs": { + "url": "https://github.com/sigmacomputing/embed-sdk/issues" + }, + "keywords": [ + "embed", + "sdk", + "sigma", + "node" + ], + "scripts": { + "prepublish": "turbo run build", + "build": "tsup", + "lint": "eslint . --ext .ts", + "watch": "tsup --watch", + "typecheck": "tsc --noEmit", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + "import": { + "import": "./dist/index.mjs", + "types": "./dist/index.d.mts" + }, + "require": { + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "devDependencies": { + "@sigmacomputing/eslint-config": "workspace:*", + "@sigmacomputing/typescript-config": "workspace:*", + "@types/node": "^20.17.16" + }, + "engines": { + "node": ">=18" + } +} diff --git a/packages/embed-sdk/src/encryption.ts b/packages/node-embed-sdk/src/encryption.ts similarity index 100% rename from packages/embed-sdk/src/encryption.ts rename to packages/node-embed-sdk/src/encryption.ts diff --git a/packages/node-embed-sdk/src/index.ts b/packages/node-embed-sdk/src/index.ts new file mode 100644 index 0000000..73ebae8 --- /dev/null +++ b/packages/node-embed-sdk/src/index.ts @@ -0,0 +1 @@ +export * from './encryption'; diff --git a/packages/node-embed-sdk/tsconfig.json b/packages/node-embed-sdk/tsconfig.json new file mode 100644 index 0000000..595f168 --- /dev/null +++ b/packages/node-embed-sdk/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "lib": ["ES2022"], + "outDir": "./dist", + "types": ["node"] + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/node-embed-sdk/tsconfig.lint.json b/packages/node-embed-sdk/tsconfig.lint.json new file mode 100644 index 0000000..0b3de5e --- /dev/null +++ b/packages/node-embed-sdk/tsconfig.lint.json @@ -0,0 +1,8 @@ +{ + "extends": "@sigmacomputing/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/node-embed-sdk/tsup.config.js b/packages/node-embed-sdk/tsup.config.js new file mode 100644 index 0000000..c127571 --- /dev/null +++ b/packages/node-embed-sdk/tsup.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: { + index: "src/index.ts", + }, + sourcemap: false, + minify: false, + dts: true, + clean: true, + format: ["esm", "cjs"], + loader: { + ".js": "jsx", + }, +}); diff --git a/packages/node-embed-sdk/turbo.json b/packages/node-embed-sdk/turbo.json new file mode 100644 index 0000000..52e8c76 --- /dev/null +++ b/packages/node-embed-sdk/turbo.json @@ -0,0 +1,12 @@ +{ + "extends": [ + "//" + ], + "tasks": { + "build": { + "outputs": [ + "dist/**" + ] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a076aac..147300c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,18 @@ importers: specifier: ^20.17.16 version: 20.17.16 + packages/node-embed-sdk: + devDependencies: + '@sigmacomputing/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint-config + '@sigmacomputing/typescript-config': + specifier: workspace:* + version: link:../../tooling/typescript-config + '@types/node': + specifier: ^20.17.16 + version: 20.17.16 + packages/react-embed-sdk: dependencies: '@sigmacomputing/embed-sdk': From fea8a7045bab0b2eaf11e1dbe48ca2f3b6734720 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:06:48 -0700 Subject: [PATCH 06/13] Undo embed-sdk dev dependency --- packages/embed-sdk/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/embed-sdk/package.json b/packages/embed-sdk/package.json index aace962..32cac6e 100644 --- a/packages/embed-sdk/package.json +++ b/packages/embed-sdk/package.json @@ -44,7 +44,6 @@ ], "devDependencies": { "@sigmacomputing/eslint-config": "workspace:*", - "@sigmacomputing/typescript-config": "workspace:*", - "@types/node": "^20.17.16" + "@sigmacomputing/typescript-config": "workspace:*" } } From 76a6245bbdc23e084cd9d114d4e1c60433d45c91 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:07:04 -0700 Subject: [PATCH 07/13] Update pnpm-lock --- pnpm-lock.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 147300c..8c1867b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,9 +111,6 @@ importers: '@sigmacomputing/typescript-config': specifier: workspace:* version: link:../../tooling/typescript-config - '@types/node': - specifier: ^20.17.16 - version: 20.17.16 packages/node-embed-sdk: devDependencies: From 47347ce05b98b6fd2068e078c93d855dfc3b5f95 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:10:23 -0700 Subject: [PATCH 08/13] Update readme --- packages/node-embed-sdk/README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/node-embed-sdk/README.md b/packages/node-embed-sdk/README.md index 8138a67..2aa8cda 100644 --- a/packages/node-embed-sdk/README.md +++ b/packages/node-embed-sdk/README.md @@ -45,14 +45,3 @@ const decryptedToken = decrypt({ encryptedToken: encryptedToken }); ``` - -## Security - -This package implements industry-standard encryption practices: - -- PBKDF2 with SHA-256, 600,000 iterations, and a 128-bit salt for key derivation -- AES-256-GCM for encryption with a 96-bit IV and a 128-bit authentication tag - -## Requirements - -- Node.js 18 or higher From ad4fcd33c1434b8dcfe7830429b6c7fe9f30f7e7 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:11:07 -0700 Subject: [PATCH 09/13] Another readme revision --- packages/node-embed-sdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-embed-sdk/README.md b/packages/node-embed-sdk/README.md index 2aa8cda..38b9f61 100644 --- a/packages/node-embed-sdk/README.md +++ b/packages/node-embed-sdk/README.md @@ -1,6 +1,6 @@ # Sigma Node.js Embed SDK -This package provides Node.js utilities for working with Sigma Computing's Embed API, with a focus on server-side functionality like token encryption and decryption. +This package provides Node.js utilities for working with Sigma Computing's Embed API. ## Getting Started From f42740b346ffe5febc9820ad503f3dd1fae81a0e Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:42:02 -0700 Subject: [PATCH 10/13] Update function definitions --- packages/node-embed-sdk/README.md | 16 ++++++------ packages/node-embed-sdk/src/encryption.ts | 32 +++++++++-------------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/node-embed-sdk/README.md b/packages/node-embed-sdk/README.md index 38b9f61..38f258f 100644 --- a/packages/node-embed-sdk/README.md +++ b/packages/node-embed-sdk/README.md @@ -34,14 +34,14 @@ The SDK provides utilities for encrypting and decrypting OAuth tokens using AES- import { encrypt, decrypt } from '@sigmacomputing/node-embed-sdk'; // Encrypt an OAuth token -const encryptedToken = encrypt({ - embedSecret: 'your-embed-secret', - oauthToken: 'your-oauth-token' -}); +const encryptedToken = encrypt( + 'your-embed-secret', + 'your-oauth-token' +); // Decrypt an encrypted token -const decryptedToken = decrypt({ - embedSecret: 'your-embed-secret', - encryptedToken: encryptedToken -}); +const decryptedToken = decrypt( + 'your-embed-secret', + encryptedToken +); ``` diff --git a/packages/node-embed-sdk/src/encryption.ts b/packages/node-embed-sdk/src/encryption.ts index 91144ad..cb21cd7 100644 --- a/packages/node-embed-sdk/src/encryption.ts +++ b/packages/node-embed-sdk/src/encryption.ts @@ -421,18 +421,14 @@ function decodeEncryptionOutput( /** * Encrypt the OAuth token using the embed secret. * - * @param param0 - * @param param0.embedSecret the embed secret to use for encryption - * @param param0.oauthToken the OAuth token to encrypt + * @param embedSecret the embed secret to use for encryption + * @param oauthToken the OAuth token to encrypt * @returns the encrypted token, encoded as a string */ -export function encrypt({ - embedSecret, - oauthToken, -}: { - embedSecret: string; - oauthToken: string; -}): string { +export function encrypt( + embedSecret: string, + oauthToken: string, +): string { const passphrase = asPassphrase(Buffer.from(embedSecret, 'utf8')); const plaintext = asPlaintext(Buffer.from(oauthToken, 'utf8')); const encryptionOutput = encryptWithPassphrase(passphrase, plaintext); @@ -442,18 +438,14 @@ export function encrypt({ /** * Decrypt the OAuth token using the embed secret. * - * @param param0 - * @param param0.embedSecret the embed secret to use for decryption - * @param param0.encryptedToken the encrypted OAuth token to decrypt + * @param embedSecret the embed secret to use for decryption + * @param encryptedToken the encrypted OAuth token to decrypt * @returns the decrypted token */ -export function decrypt({ - embedSecret, - encryptedToken, -}: { - embedSecret: string; - encryptedToken: string; -}): string { +export function decrypt( + embedSecret: string, + encryptedToken: string, +): string { const passphrase = asPassphrase(Buffer.from(embedSecret, 'utf8')); const encryptionOutput = decodeEncryptionOutput( asEncodedPassphraseEncryptionOutput(encryptedToken), From f373649ab6df15ce42ec0ff857b251f08d7f7d6a Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:46:45 -0700 Subject: [PATCH 11/13] Crypto import --- packages/node-embed-sdk/src/encryption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-embed-sdk/src/encryption.ts b/packages/node-embed-sdk/src/encryption.ts index cb21cd7..2cf0128 100644 --- a/packages/node-embed-sdk/src/encryption.ts +++ b/packages/node-embed-sdk/src/encryption.ts @@ -1,4 +1,4 @@ -import crypto from 'crypto'; +import crypto from 'node:crypto'; /* * Configuration Constants From b82b507818061c1a85a82608797fd12736d5edbb Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:50:15 -0700 Subject: [PATCH 12/13] Clean up tsup.config.js --- packages/node-embed-sdk/tsup.config.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/node-embed-sdk/tsup.config.js b/packages/node-embed-sdk/tsup.config.js index c127571..bb41745 100644 --- a/packages/node-embed-sdk/tsup.config.js +++ b/packages/node-embed-sdk/tsup.config.js @@ -9,7 +9,4 @@ export default defineConfig({ dts: true, clean: true, format: ["esm", "cjs"], - loader: { - ".js": "jsx", - }, }); From f24ef02d6b7c26122a77d9397f7b5a6d00408ac6 Mon Sep 17 00:00:00 2001 From: Colin Greybosh Date: Wed, 14 May 2025 16:54:57 -0700 Subject: [PATCH 13/13] Add changeset --- .changeset/friendly-files-train.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/friendly-files-train.md diff --git a/.changeset/friendly-files-train.md b/.changeset/friendly-files-train.md new file mode 100644 index 0000000..4c05e1b --- /dev/null +++ b/.changeset/friendly-files-train.md @@ -0,0 +1,5 @@ +--- +"@sigmacomputing/node-embed-sdk": minor +--- + +Add OAuth token encryption utils for JWT embeds