@cipherstash/jseql
Version:
Encrypted Query Language JavaScript Library
641 lines (632 loc) • 17.8 kB
JavaScript
"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