@cipherstash/jseql
Version:
Encrypted Query Language JavaScript Library
523 lines (517 loc) • 14 kB
JavaScript
import {
LockContext,
logger
} from "./chunk-2GFWYC6S.js";
// src/ffi/index.ts
import {
newClient,
encrypt as ffiEncrypt,
decrypt as ffiDecrypt,
encryptBulk as ffiEncryptBulk,
decryptBulk as ffiDecryptBulk
} from "@cipherstash/jseql-ffi";
// 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 ffiEncrypt(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 ffiEncrypt(
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 ffiDecrypt(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 ffiDecrypt(
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 ffiEncryptBulk(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 ffiEncryptBulk(
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 ffiDecryptBulk(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 ffiDecryptBulk(
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 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/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();
};
export {
LockContext,
createEqlPayload,
eql,
getPlaintext
};
//# sourceMappingURL=index.js.map