UNPKG

@cipherstash/jseql

Version:

Encrypted Query Language JavaScript Library

641 lines (632 loc) 17.8 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { LockContext: () => LockContext, createEqlPayload: () => createEqlPayload, eql: () => eql, getPlaintext: () => getPlaintext }); module.exports = __toCommonJS(index_exports); // src/ffi/index.ts var import_jseql_ffi = require("@cipherstash/jseql-ffi"); // ../utils/logger/index.ts function getLevelValue(level) { switch (level) { case "debug": return 10; case "info": return 20; case "error": return 30; default: return 30; } } var envLogLevel = process.env.JSEQL_LOG_LEVEL || "info"; var currentLevel = getLevelValue(envLogLevel); function debug(...args) { if (currentLevel <= getLevelValue("debug")) { console.debug("[jseql] DEBUG", ...args); } } function info(...args) { if (currentLevel <= getLevelValue("info")) { console.info("[jseql] INFO", ...args); } } function error(...args) { if (currentLevel <= getLevelValue("error")) { console.error("[jseql] ERROR", ...args); } } var logger = { debug, info, error }; // src/ffi/env-check.ts var message = ""; var errorMessage = (message2) => `Initialization error: ${message2}`; var checkEnvironmentVariables = () => { if (!process.env.CS_WORKSPACE_ID) { message = errorMessage( 'The environment variable "CS_WORKSPACE_ID" must be set. You can find your workspace ID in the CipherStash dashboard.' ); logger.error(message); throw new Error(`[jseql]: ${message}`); } if (!process.env.CS_CLIENT_ID || !process.env.CS_CLIENT_KEY) { message = errorMessage( 'The environment variables "CS_CLIENT_ID" and "CS_CLIENT_KEY" must be set. You must use the CipherStash CLI to generate a new client key pair.' ); logger.error(message); throw new Error(`[jseql]: ${message}`); } if (!process.env.CS_CLIENT_ACCESS_KEY) { message = errorMessage( 'The environment variable "CS_CLIENT_ACCESS_KEY" must be set. Generate a new access token in the CipherStash dashboard or CLI.' ); logger.error(message); throw new Error(`[jseql]: ${message}`); } }; // src/ffi/payload-helpers.ts var getLockContextPayload = (lockContext) => { const context = lockContext.getLockContext(); if (!context.ctsToken?.accessToken) { throw new Error( "[jseql]: LockContext must be initialized with a valid CTS token before using it." ); } return { lockContext: context.context }; }; var normalizeBulkDecryptPayloads = (encryptedPayloads) => encryptedPayloads?.reduce((acc, encryptedPayload) => { const payload = { ciphertext: encryptedPayload.c }; acc.push(payload); return acc; }, []); var normalizeBulkEncryptPayloads = (plaintexts, column) => plaintexts.reduce((acc, plaintext) => { const payload = { plaintext: plaintext.plaintext, column }; acc.push(payload); return acc; }, []); var normalizeBulkDecryptPayloadsWithLockContext = (encryptedPayloads, lockContext) => encryptedPayloads?.reduce((acc, encryptedPayload) => { const payload = { ciphertext: encryptedPayload.c, ...getLockContextPayload(lockContext) }; acc.push(payload); return acc; }, []); var normalizeBulkEncryptPayloadsWithLockContext = (plaintexts, column, lockContext) => plaintexts.reduce((acc, plaintext) => { const payload = { plaintext: plaintext.plaintext, column, ...getLockContextPayload(lockContext) }; acc.push(payload); return acc; }, []); // src/ffi/index.ts var noClientError = () => new Error( "The EQL client has not been initialized. Please call init() before using the client." ); var EncryptOperation = class { client; plaintext; column; table; constructor(client, plaintext, opts) { this.client = client; this.plaintext = plaintext; this.column = opts.column; this.table = opts.table; } withLockContext(lockContext) { return new EncryptOperationWithLockContext(this, lockContext); } /** Implement the PromiseLike interface so `await` works. */ then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } /** Actual encryption logic, deferred until `then()` is called. */ async execute() { if (!this.client) { throw noClientError(); } if (this.plaintext === null) { return null; } logger.debug("Encrypting data WITHOUT a lock context", { column: this.column, table: this.table }); const val = await (0, import_jseql_ffi.encrypt)(this.client, this.plaintext, this.column); return { c: val }; } getOperation() { return { client: this.client, plaintext: this.plaintext, column: this.column, table: this.table }; } }; var EncryptOperationWithLockContext = class { operation; lockContext; constructor(operation, lockContext) { this.operation = operation; this.lockContext = lockContext; } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { const { client, plaintext, column, table } = this.operation.getOperation(); if (!client) { throw noClientError(); } if (plaintext === null) { return null; } logger.debug("Encrypting data WITH a lock context"); const context = this.lockContext?.getLockContext(); if (!context?.success) { throw new Error(`[jseql]: ${context?.error}`); } const val = await (0, import_jseql_ffi.encrypt)( client, plaintext, column, context.context, context.ctsToken ); return { c: val }; } }; var DecryptOperation = class { client; encryptedPayload; constructor(client, encryptedPayload) { this.client = client; this.encryptedPayload = encryptedPayload; } withLockContext(lockContext) { return new DecryptOperationWithLockContext(this, lockContext); } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { if (!this.client) { throw noClientError(); } if (this.encryptedPayload === null) { return null; } logger.debug("Decrypting data WITHOUT a lock context"); return await (0, import_jseql_ffi.decrypt)(this.client, this.encryptedPayload.c); } getOperation() { return { client: this.client, encryptedPayload: this.encryptedPayload }; } }; var DecryptOperationWithLockContext = class { operation; lockContext; constructor(operation, lockContext) { this.operation = operation; this.lockContext = lockContext; } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { const { client, encryptedPayload } = this.operation.getOperation(); if (!client) { throw noClientError(); } if (encryptedPayload === null) { return null; } logger.debug("Decrypting data WITH a lock context"); const context = this.lockContext?.getLockContext(); if (!context?.success) { throw new Error(`[jseql]: ${context?.error}`); } return await (0, import_jseql_ffi.decrypt)( client, encryptedPayload.c, context.context, context.ctsToken ); } }; var BulkEncryptOperation = class { client; plaintexts; column; table; constructor(client, plaintexts, opts) { this.client = client; this.plaintexts = plaintexts; this.column = opts.column; this.table = opts.table; } withLockContext(lockContext) { return new BulkEncryptOperationWithLockContext(this, lockContext); } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { if (!this.client) { throw noClientError(); } if (!this.plaintexts || this.plaintexts.length === 0) { return null; } const encryptPayloads = normalizeBulkEncryptPayloads( this.plaintexts, this.column ); logger.debug("Bulk encrypting data WITHOUT a lock context", { column: this.column, table: this.table }); const encryptedData = await (0, import_jseql_ffi.encryptBulk)(this.client, encryptPayloads); return encryptedData.map((enc, index) => ({ c: enc, id: this.plaintexts[index].id })); } getOperation() { return { client: this.client, plaintexts: this.plaintexts, column: this.column, table: this.table }; } }; var BulkEncryptOperationWithLockContext = class { operation; lockContext; constructor(operation, lockContext) { this.operation = operation; this.lockContext = lockContext; } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { const { client, plaintexts, column, table } = this.operation.getOperation(); if (!client) { throw noClientError(); } if (!plaintexts || plaintexts.length === 0) { return null; } const encryptPayloads = normalizeBulkEncryptPayloadsWithLockContext( plaintexts, column, this.lockContext ); logger.debug("Bulk encrypting data WITH a lock context", { column, table }); const context = this.lockContext.getLockContext(); if (!context.success) { throw new Error(`[jseql]: ${context?.error}`); } const encryptedData = await (0, import_jseql_ffi.encryptBulk)( client, encryptPayloads, context.ctsToken ); return encryptedData.map((enc, index) => ({ c: enc, id: plaintexts[index].id })); } }; var BulkDecryptOperation = class { client; encryptedPayloads; constructor(client, encryptedPayloads) { this.client = client; this.encryptedPayloads = encryptedPayloads; } withLockContext(lockContext) { return new BulkDecryptOperationWithLockContext(this, lockContext); } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { if (!this.client) { throw noClientError(); } if (!this.encryptedPayloads) { return null; } const decryptPayloads = normalizeBulkDecryptPayloads(this.encryptedPayloads); if (!decryptPayloads) { return null; } logger.debug("Bulk decrypting data WITHOUT a lock context"); const decryptedData = await (0, import_jseql_ffi.decryptBulk)(this.client, decryptPayloads); return decryptedData.map((dec, index) => { if (!this.encryptedPayloads) return null; return { plaintext: dec, id: this.encryptedPayloads[index].id }; }); } getOperation() { return { client: this.client, encryptedPayloads: this.encryptedPayloads }; } }; var BulkDecryptOperationWithLockContext = class { operation; lockContext; constructor(operation, lockContext) { this.operation = operation; this.lockContext = lockContext; } then(onfulfilled, onrejected) { return this.execute().then(onfulfilled, onrejected); } async execute() { const { client, encryptedPayloads } = this.operation.getOperation(); if (!client) { throw noClientError(); } if (!encryptedPayloads) { return null; } const decryptPayloads = normalizeBulkDecryptPayloadsWithLockContext( encryptedPayloads, this.lockContext ); if (!decryptPayloads) { return null; } logger.debug("Bulk decrypting data WITH a lock context"); const context = this.lockContext.getLockContext(); if (!context.success) { throw new Error(`[jseql]: ${context?.error}`); } const decryptedData = await (0, import_jseql_ffi.decryptBulk)( client, decryptPayloads, context.ctsToken ); return decryptedData.map((dec, index) => { if (!encryptedPayloads) return null; return { plaintext: dec, id: encryptedPayloads[index].id }; }); } }; var EqlClient = class { client; workspaceId; constructor() { checkEnvironmentVariables(); logger.info( "Successfully initialized the EQL client with your defined environment variables." ); this.workspaceId = process.env.CS_WORKSPACE_ID; } async init() { const c = await (0, import_jseql_ffi.newClient)(); this.client = c; return this; } /** * Encryption - returns a thenable object. * Usage: * await eqlClient.encrypt(plaintext, { column, table }) * await eqlClient.encrypt(plaintext, { column, table }).withLockContext(lockContext) */ encrypt(plaintext, opts) { if (!this.client) { throw noClientError(); } return new EncryptOperation(this.client, plaintext, opts); } /** * Decryption - returns a thenable object. * Usage: * await eqlClient.decrypt(encryptedPayload) * await eqlClient.decrypt(encryptedPayload).withLockContext(lockContext) */ decrypt(encryptedPayload) { if (!this.client) { throw noClientError(); } return new DecryptOperation(this.client, encryptedPayload); } /** * Bulk Encrypt - returns a thenable object. * Usage: * await eqlClient.bulkEncrypt([{ plaintext, id }, ...], { column, table }) * await eqlClient * .bulkEncrypt([{ plaintext, id }, ...], { column, table }) * .withLockContext(lockContext) */ bulkEncrypt(plaintexts, opts) { if (!this.client) { throw noClientError(); } return new BulkEncryptOperation(this.client, plaintexts, opts); } /** * Bulk Decrypt - returns a thenable object. * Usage: * await eqlClient.bulkDecrypt(encryptedPayloads) * await eqlClient.bulkDecrypt(encryptedPayloads).withLockContext(lockContext) */ bulkDecrypt(encryptedPayloads) { if (!this.client) { throw noClientError(); } return new BulkDecryptOperation(this.client, encryptedPayloads); } /** e.g., debugging or environment info */ clientInfo() { return { workspaceId: this.workspaceId }; } }; // src/identify/index.ts var LockContext = class { ctsToken; workspaceId; context; constructor({ context = { identityClaim: ["sub"] }, ctsToken } = {}) { if (!process.env.CS_WORKSPACE_ID) { const errorMessage2 = "CS_WORKSPACE_ID environment variable is not set, and is required to initialize a LockContext."; logger.error(errorMessage2); throw new Error(`[jseql]: ${errorMessage2}`); } if (ctsToken) { this.ctsToken = ctsToken; } this.workspaceId = process.env.CS_WORKSPACE_ID; this.context = context; logger.debug("Successfully initialized the EQL lock context."); } async identify(jwtToken) { const workspaceId = this.workspaceId; const ctsEndoint = process.env.CS_CTS_ENDPOINT || "https://ap-southeast-2.aws.auth.viturhosted.net"; const ctsResponse = await fetch(`${ctsEndoint}/api/authorize`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId, oidcToken: jwtToken }) }); if (!ctsResponse.ok) { throw new Error( `[jseql]: Failed to fetch CTS token: ${ctsResponse.statusText}` ); } const ctsToken = await ctsResponse.json(); if (!ctsToken.accessToken) { const errorMessage2 = "The response from the CipherStash API did not contain an access token. Please contact support."; logger.error(errorMessage2); throw new Error(errorMessage2); } this.ctsToken = ctsToken; return this; } getLockContext() { if (!this.ctsToken?.accessToken && !this.ctsToken?.expiry) { return { success: false, error: "The CTS token is not set. Please call identify() with a users JWT token, or pass an existing CTS token to the LockContext constructor before calling getLockContext()." }; } return { success: true, context: this.context, ctsToken: this.ctsToken }; } }; // src/eql/index.ts var createEqlPayload = ({ plaintext, table, column, schemaVersion = 1, queryType = null }) => { const payload = { v: schemaVersion, k: "pt", p: plaintext ?? "", i: { t: table, c: column } }; if (queryType) { payload.q = queryType; } logger.debug("Creating the EQL payload", payload); return payload; }; var getPlaintext = (payload) => { if (payload?.p && payload?.k === "pt") { logger.debug("Returning the plaintext data from the EQL payload", payload); return { failure: false, plaintext: payload.p }; } logger.error("No plaintext data found in the EQL payload", payload ?? {}); return { failure: true, error: new Error("No plaintext data found in the EQL payload") }; }; // src/index.ts var eql = () => { const client = new EqlClient(); return client.init(); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { LockContext, createEqlPayload, eql, getPlaintext }); //# sourceMappingURL=index.cjs.map