From e963ecf531815712e3522972b33a9076317629cf Mon Sep 17 00:00:00 2001 From: CJ Brewer Date: Mon, 10 Mar 2025 12:28:22 -0600 Subject: [PATCH 1/3] docs(protect): :memo: symbolic link --- README.md | 569 +---------------------------------- packages/protect/README.md | 589 ++++++++++++++++++++++++++++++++++++- 2 files changed, 587 insertions(+), 571 deletions(-) mode change 100644 => 120000 README.md diff --git a/README.md b/README.md deleted file mode 100644 index db4b250b..00000000 --- a/README.md +++ /dev/null @@ -1,568 +0,0 @@ -

- CipherStash Logo -
- - Protect.js

-

- Implement robust data security without sacrificing performance or usability -
- Built by CipherStash -

-
- - - -## What's Protect.js? - -Protect.js is a TypeScript package for encrypting and decrypting data. -Encryption operations happen directly in your app, and the ciphertext is stored in your database. - -Every value you encrypt with Protect.js has a unique key, made possible by CipherStash [ZeroKMS](https://cipherstash.com/products/zerokms)'s blazing fast bulk key operations, and backed by a root key in [AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html). - -The encrypted data is structured as an [EQL](https://github.com/cipherstash/encrypt-query-language) JSON payload, and can be stored in any database that supports JSONB. - -> [!IMPORTANT] -> Searching, sorting, and filtering on encrypted data is only supported in PostgreSQL at the moment. -> Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md). - -## Table of contents - -- [Features](#features) -- [Example applications](#example-applications) -- [Installing Protect.js](#installing-protectjs) -- [Getting started](#getting-started) -- [Identity-aware encryption](#identity-aware-decryption) -- [Bulk encryption and decryption](#bulk-encryption-and-decryption) -- [Supported data types](#supported-data-types) -- [Searchable encryption](#searchable-encryption) -- [Logging](#logging) -- [CipherStash Client](#cipherstash-client) -- [Builds and bundling](#builds-and-bundling) -- [Contributing](#contributing) -- [License](#license) - -For more specific documentation, please refer to the [docs](https://github.com/cipherstash/protectjs/tree/main/docs). - -## Features - -Protect.js protects data in using industry-standard AES encryption. -Protect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for bulk encryption and decryption operations. -This enables every encrypted value, in every column, in every row in your database to have a unique key — without sacrificing performance. - -**Features:** -- **Bulk encryption and decryption**: Protect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for encrypting and decrypting thousands of records at once, while using a unique key for every value. -- **Single item encryption and decryption**: Just looking for a way to encrypt and decrypt single values? Protect.js has you covered. -- **Really fast:** ZeroKMS's performance makes using millions of unique keys feasible and performant for real-world applications built with Protect.js. -- **Identity-aware encryption**: Lock down access to sensitive data by requiring a valid JWT to perform a decryption. -- **Audit trail**: Every decryption event will be logged in ZeroKMS to help you prove compliance. -- **Searchable encryption**: Protect.js supports searching encrypted data in PostgreSQL. -- **TypeScript support**: Strongly typed with TypeScript interfaces and types. - -**Use cases:** -- **Trusted data access**: make sure only your end-users can access their sensitive data stored in your product. -- **Meet compliance requirements faster:** achieve and exceed the data encryption requirements of SOC2 and ISO27001. -- **Reduce the blast radius of data breaches:** limit the impact of exploited vulnerabilities to only the data your end-users can decrypt. - -## Example applications - -New to Protect.js? -Check out the example applications: - -- [Basic example](/apps/basic) demonstrates how to perform encryption operations -- [Drizzle example](/apps/drizzle) demonstrates how to use Protect.js with an ORM -- [Next.js and lock contexts example using Clerk](/apps/nextjs-clerk) demonstrates how to protect data with identity-aware encryption - -`@cipherstash/protect` can be used with most ORMs. -If you're interested in using `@cipherstash/protect` with a specific ORM, please [create an issue](https://github.com/cipherstash/protectjs/issues/new). - -## Installing Protect.js - -Install the [`@cipherstash/protect` package](https://www.npmjs.com/package/@cipherstash/protect) with your package manager of choice: - -```bash -npm install @cipherstash/protect -# or -yarn add @cipherstash/protect -# or -pnpm add @cipherstash/protect -``` - -> [!TIP] -> [Bun](https://bun.sh/) is not currently supported due to a lack of [Node-API compatibility](https://github.com/oven-sh/bun/issues/158). Under the hood, Protect.js uses [CipherStash Client](#cipherstash-client) which is written in Rust and embedded using [Neon](https://github.com/neon-bindings/neon). - -Lastly, install the CipherStash CLI: - -- On macOS: - - ```bash - brew install cipherstash/tap/stash - ``` - -- On Linux, download the binary for your platform, and put it on your `PATH`: - - [Linux ARM64](https://github.com/cipherstash/cli-releases/releases/latest/download/stash-aarch64-unknown-linux-gnu) - - [Linux x86_64](https://github.com/cipherstash/cli-releases/releases/latest/download/stash-x86_64-unknown-linux-gnu) - -### Opt-out of bundling - -Protect.js uses Node.js specific features and requires the use of the native Node.js `require`. -You need to opt-out of bundling for tools like [Webpack](https://webpack.js.org/configuration/externals/), [esbuild](https://webpack.js.org/configuration/externals/), or [Next.js](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages). - -Read more about bundling [here](#builds-and-bundling). - -## Getting started - -### Configuration - -> [!IMPORTANT] -> Make sure you have [installed the CipherStash CLI](#installation) before following these steps. - -To set up all the configuration and credentials required for Protect.js: - -```bash -stash setup -``` - -If you have not already signed up for a CipherStash account, this will prompt you to do so along the way. - -At the end of `stash setup`, you will have two files in your project: - -- `cipherstash.toml` which contains the configuration for Protect.js -- `cipherstash.secret.toml`: which contains the credentials for Protect.js - -> [!WARNING] -> `cipherstash.secret.toml` should not be committed to git, because it contains sensitive credentials. -> The `stash setup` command will attempt to append to your `.gitignore` file with the `cipherstash.secret.toml` file. - -You can read more about [configuration via toml file or environment variables here](./docs/reference/configuration.md). - -### Basic file structure - -This is the basic file structure of the project. In the `src/protect` directory, we have table definition in `schema.ts` and the protect client in `index.ts`. - -``` -📦 - ├ 📂 src - │ ├ 📂 protect - │ │ ├ 📜 index.ts - │ │ └ 📜 schema.ts - │ └ 📜 index.ts - ├ 📜 .env - ├ 📜 cipherstash.toml - ├ 📜 cipherstash.secret.toml - ├ 📜 package.json - └ 📜 tsconfig.json -``` - -### Defining your schema - -Protect.js uses a schema to define the tables and columns that you want to encrypt and decrypt. - -In the `src/protect/schema.ts` file, you can define your tables and columns. - -```ts -import { csTable, csColumn } from '@cipherstash/protect' - -export const users = csTable('users', { - email: csColumn('email'), -}) - -export const orders = csTable('orders', { - address: csColumn('address'), -}) -``` - -**Searchable encryption** - -If you are looking to enable searchable encryption in a PostgreSQL database, you must declaratively enable the indexes in your schema. - -```ts -import { csTable, csColumn } from '@cipherstash/protect' - -export const users = csTable('users', { - email: csColumn('email').freeTextSearch().equality().orderAndRange(), -}) -``` - -Read more about [defining your schema here](./docs/reference/schema.md). - -### Initializing the Protect client - -To initialize the protect client, import the `protect` function and initialize a client with your defined schema. - -In the `src/protect/index.ts` file: - -```ts -import { protect } from '@cipherstash/protect' -import { users } from './schema' - -// Pass all your tables to the protect function to initialize the client -export const protectClient = await protect(users, orders) -``` - -The `protect` function requires at least one `csTable` to be passed in. - -### Encrypting data - -Use the `encrypt` function to encrypt data. -`encrypt` takes a plaintext string, and an object with the table and column as parameters. - -```typescript -import { users } from './protect/schema' -import { protectClient } from './protect' - -const encryptResult = await protectClient.encrypt('secret@squirrel.example', { - column: users.email, - table: users, -}) - -if (encryptResult.failure) { - // Handle the failure -} - -const ciphertext = encryptResult.data -``` - -The `encrypt` function will return a `Result` object with either a `data` key, or a `failure` key. -The `encryptResult` will return one of the following: - -```typescript -// Success -{ - data: { - c: '\\\\\\\\\\\\\\\\x61202020202020472aaf602219d48c4a...' - } -} - -// Failure -{ - failure: { - type: 'EncryptionError', - message: 'A message about the error' - } -} -``` - -> [!TIP] -> Get significantly better encryption performance by using the [`bulkEncrypt` function](#bulk-encrypting-data) for large payloads. - -### Decrypting data - -Use the `decrypt` function to decrypt data. -`decrypt` takes an encrypted data object as a parameter. - -```typescript -import { protectClient } from './protect' - -const decryptResult = await protectClient.decrypt(ciphertext) - -if (decryptResult.failure) { - // Handle the failure -} - -const plaintext = decryptResult.data -``` - -The `decrypt` function returns a `Result` object with either a `data` key, or a `failure` key. -The `decryptResult` will return one of the following: - -```typescript -// Success -{ - data: 'secret@squirrel.example' -} - -// Failure -{ - failure: { - type: 'DecryptionError', - message: 'A message about the error' - } -} -``` - -> [!TIP] -> Get significantly better decryption performance by using the [`bulkDecrypt` function](#bulk-decrypting-data) for large payloads. - -### Storing encrypted data in a database - -Encrypted data can be stored in any database that supports JSONB, noting that searchable encryption is only supported in PostgreSQL at the moment. - -To store the encrypted data, you will need to specify the column type as `jsonb`. - -```sql -CREATE TABLE users ( - id SERIAL PRIMARY KEY, - email jsonb NOT NULL, -); -``` - -#### Searchable encryption in PostgreSQL - -To enable searchable encryption in PostgreSQL, you need to [install the EQL custom types and functions](https://github.com/cipherstash/encrypt-query-language?tab=readme-ov-file#installation). - -1. Download the latest EQL install script: - - ```sh - curl -sLo cipherstash-encrypt.sql https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql - ``` - -2. Run this command to install the custom types and functions: - - ```sh - psql -f cipherstash-encrypt.sql - ``` - -EQL is now installed in your database and you can enable searchable encryption by adding the `cs_encrypted_v1` type to a column. - -```sql -CREATE TABLE users ( - id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - email cs_encrypted_v1 -); -``` - -## Identity-aware encryption - -> [!IMPORTANT] -> Right now identity-aware encryption is only supported if you are using [Clerk](https://clerk.com/) as your identity provider. -> Read more about [lock contexts with Clerk and Next.js](./docs/how-to/lock-contexts-with-clerk.md). - -Protect.js can add an additional layer of protection to your data by requiring a valid JWT to perform a decryption. - -This ensures that only the user who encrypted data is able to decrypt it. - -Protect.js does this through a mechanism called a _lock context_. - -### Lock context - -Lock contexts ensure that only specific users can access sensitive data. - -> [!CAUTION] -> You must use the same lock context to encrypt and decrypt data. -> If you use different lock contexts, you will be unable to decrypt the data. - -To use a lock context, initialize a `LockContext` object with the identity claims. - -```typescript -import { LockContext } from '@cipherstash/protect/identify' - -// protectClient from the previous steps -const lc = new LockContext() -``` - -> [!NOTE] -> When initializing a `LockContext`, the default context is set to use the `sub` Identity Claim. - -### Identifying a user for a lock context - -A lock context needs to be locked to a user. -To identify the user, call the `identify` method on the lock context object, and pass a valid JWT from a user's session: - -```typescript -const identifyResult = await lc.identify(jwt) - -// The identify method returns the same Result pattern as the encrypt and decrypt methods. -if (identifyResult.failure) { - // Hanlde the failure -} - -const lockContext = identifyResult.data -``` - -### Encrypting data with a lock context - -To encrypt data with a lock context, call the optional `withLockContext` method on the `encrypt` function and pass the lock context object as a parameter: - -```typescript -import { protectClient } from './protect' -import { users } from './protect/schema' - -const encryptResult = await protectClient.encrypt('plaintext', { - table: users, - column: users.email, -}).withLockContext(lockContext) - -if (encryptResult.failure) { - // Handle the failure -} - -const ciphertext = encryptResult.data -``` - -### Decrypting data with a lock context - -To decrypt data with a lock context, call the optional `withLockContext` method on the `decrypt` function and pass the lock context object as a parameter: - -```typescript -import { protectClient } from './protect' - -const decryptResult = await protectClient.decrypt(ciphertext).withLockContext(lockContext) - -if (decryptResult.failure) { - // Handle the failure -} - -const plaintext = decryptResult.data -``` - -## Bulk encryption and decryption - -If you have a large list of items to encrypt or decrypt, you can use the **`bulkEncrypt`** and **`bulkDecrypt`** methods to batch encryption/decryption. -`bulkEncrypt` and `bulkDecrypt` give your app significantly better throughput than the single-item [`encrypt`](#encrypting-data) and [`decrypt`](#decrypting-data) methods. - - -### Bulk encrypting data - -Build a list of records to encrypt: - -```ts -const users = [ - { id: '1', name: 'CJ', email: 'cj@example.com' }, - { id: '2', name: 'Alex', email: 'alex@example.com' }, -] -``` - -Prepare the array for bulk encryption: - -```ts -const plaintextsToEncrypt = users.map((user) => ({ - plaintext: user.email, // The data to encrypt - id: user.id, // Keep track by user ID -})) -``` - -Perform the bulk encryption: - -```ts -const encryptedResults = await bulkEncrypt(plaintextsToEncrypt, { - column: 'email', - table: 'Users', -}) - -if (encryptedResults.failure) { - // Handle the failure -} - -const encryptedValues = encryptedResults.data - -// encryptedValues might look like: -// [ -// { encryptedData: { c: 'ENCRYPTED_VALUE_1', k: 'ct' }, id: '1' }, -// { encryptedData: { c: 'ENCRYPTED_VALUE_2', k: 'ct' }, id: '2' }, -// ] -``` - -Reassemble data by matching IDs: - -```ts -encryptedValues.forEach((result) => { - // Find the corresponding user - const user = users.find((u) => u.id === result.id) - if (user) { - user.email = result.encryptedData // Store the encrypted data back into the user object - } -}) -``` - -Learn more about [bulk encryption](./docs/reference/bulk-encryption-decryption.md#bulk-encrypting-data) - -### Bulk decrypting data - -Build an array of records to decrypt: - -```ts -const users = [ - { id: '1', name: 'CJ', email: 'ENCRYPTED_VALUE_1' }, - { id: '2', name: 'Alex', email: 'ENCRYPTED_VALUE_2' }, -] -``` - -Prepare the array for bulk decryption: - -```ts -const encryptedPayloads = users.map((user) => ({ - c: user.email, - id: user.id, -})) -``` - -Perform the bulk decryption: - -```ts -const decryptedResults = await bulkDecrypt(encryptedPayloads) - -if (decryptedResults.failure) { - // Handle the failure -} - -const decryptedValues = decryptedResults.data - -// decryptedValues might look like: -// [ -// { plaintext: 'cj@example.com', id: '1' }, -// { plaintext: 'alex@example.com', id: '2' }, -// ] -``` - -Reassemble data by matching IDs: - -```ts -decryptedValues.forEach((result) => { - const user = users.find((u) => u.id === result.id) - if (user) { - user.email = result.plaintext // Put the decrypted value back in place - } -}) -``` - -Learn more about [bulk decryption](./docs/reference/bulk-encryption-decryption.md#bulk-decrypting-data) - -## Supported data types - -Protect.js currently supports encrypting and decrypting text. -Other data types like booleans, dates, ints, floats, and JSON are well supported in other CipherStash products, and will be coming to Protect.js soon. - -Until support for other data types are available, you can express interest in this feature by adding a :+1: on this [GitHub Issue](https://github.com/cipherstash/protectjs/issues/48). - -## Searchable encryption - -Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md) in the docs. - -## Logging - -> [!IMPORTANT] -> `@cipherstash/protect` will NEVER log plaintext data. -> This is by design to prevent sensitive data from leaking into logs. - -`@cipherstash/protect` and `@cipherstash/nextjs` will log to the console with a log level of `info` by default. -To enable the logger, configure the following environment variable: - -```bash -PROTECT_LOG_LEVEL=debug # Enable debug logging -PROTECT_LOG_LEVEL=info # Enable info logging -PROTECT_LOG_LEVEL=error # Enable error logging -``` - -## CipherStash Client - -Protect.js is built on top of the CipherStash Client Rust SDK which is embedded with the `@cipherstash/protect-ffi` package. -The `@cipherstash/protect-ffi` source code is available on [GitHub](https://github.com/cipherstash/protectjs-ffi). - -Read more about configuring the CipherStash client in the [configuration docs](./docs/reference/configuration.md). - -## Builds and bundling - -`@cipherstash/protect` is a native Node.js module, and relies on native Node.js `require` to load the package. - -Here are a few resources to help based on your tool set: - -- [Required Next.js configuration](./docs/how-to/nextjs-external-packages.md). -- [SST and AWS serverless functions](./docs/how-to/sst-external-packages.md). - -## Contributing - -Please read the [contribution guide](CONTRIBUTE.md). - -## License - -Protect.js is [MIT licensed](./LICENSE.md). diff --git a/README.md b/README.md new file mode 120000 index 00000000..befcae27 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +packages/protect/README.md \ No newline at end of file diff --git a/packages/protect/README.md b/packages/protect/README.md index c94758ec..ea5f7b6e 100644 --- a/packages/protect/README.md +++ b/packages/protect/README.md @@ -1,4 +1,587 @@ -# @cipherstash/protect +

+ CipherStash Logo +
+ Protect.js +

+

+ Implement robust data security without sacrificing performance or usability +
+

+ + Built by CipherStash + + + NPM version + + + License + +
+

+
-This is the main package for the CipherStash Protect.js package. -Please refer to the [main README](https://github.com/cipherstash/protectjs) for more information. \ No newline at end of file + + +## What's Protect.js? + +Protect.js is a TypeScript package for encrypting and decrypting data. +Encryption operations happen directly in your app, and the ciphertext is stored in your database. + +Every value you encrypt with Protect.js has a unique key, made possible by CipherStash [ZeroKMS](https://cipherstash.com/products/zerokms)'s blazing fast bulk key operations, and backed by a root key in [AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html). + +The encrypted data is structured as an [EQL](https://github.com/cipherstash/encrypt-query-language) JSON payload, and can be stored in any database that supports JSONB. + +> [!IMPORTANT] +> Searching, sorting, and filtering on encrypted data is only supported in PostgreSQL at the moment. +> Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md). + +## Table of contents + +- [Features](#features) +- [Example applications](#example-applications) +- [Installing Protect.js](#installing-protectjs) +- [Getting started](#getting-started) +- [Identity-aware encryption](#identity-aware-decryption) +- [Bulk encryption and decryption](#bulk-encryption-and-decryption) +- [Supported data types](#supported-data-types) +- [Searchable encryption](#searchable-encryption) +- [Logging](#logging) +- [CipherStash Client](#cipherstash-client) +- [Builds and bundling](#builds-and-bundling) +- [Contributing](#contributing) +- [License](#license) + +For more specific documentation, please refer to the [docs](https://github.com/cipherstash/protectjs/tree/main/docs). + +## Features + +Protect.js protects data in using industry-standard AES encryption. +Protect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for bulk encryption and decryption operations. +This enables every encrypted value, in every column, in every row in your database to have a unique key — without sacrificing performance. + +**Features:** +- **Bulk encryption and decryption**: Protect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for encrypting and decrypting thousands of records at once, while using a unique key for every value. +- **Single item encryption and decryption**: Just looking for a way to encrypt and decrypt single values? Protect.js has you covered. +- **Really fast:** ZeroKMS's performance makes using millions of unique keys feasible and performant for real-world applications built with Protect.js. +- **Identity-aware encryption**: Lock down access to sensitive data by requiring a valid JWT to perform a decryption. +- **Audit trail**: Every decryption event will be logged in ZeroKMS to help you prove compliance. +- **Searchable encryption**: Protect.js supports searching encrypted data in PostgreSQL. +- **TypeScript support**: Strongly typed with TypeScript interfaces and types. + +**Use cases:** +- **Trusted data access**: make sure only your end-users can access their sensitive data stored in your product. +- **Meet compliance requirements faster:** achieve and exceed the data encryption requirements of SOC2 and ISO27001. +- **Reduce the blast radius of data breaches:** limit the impact of exploited vulnerabilities to only the data your end-users can decrypt. + +## Example applications + +New to Protect.js? +Check out the example applications: + +- [Basic example](/apps/basic) demonstrates how to perform encryption operations +- [Drizzle example](/apps/drizzle) demonstrates how to use Protect.js with an ORM +- [Next.js and lock contexts example using Clerk](/apps/nextjs-clerk) demonstrates how to protect data with identity-aware encryption + +`@cipherstash/protect` can be used with most ORMs. +If you're interested in using `@cipherstash/protect` with a specific ORM, please [create an issue](https://github.com/cipherstash/protectjs/issues/new). + +## Installing Protect.js + +Install the [`@cipherstash/protect` package](https://www.npmjs.com/package/@cipherstash/protect) with your package manager of choice: + +```bash +npm install @cipherstash/protect +# or +yarn add @cipherstash/protect +# or +pnpm add @cipherstash/protect +``` + +> [!TIP] +> [Bun](https://bun.sh/) is not currently supported due to a lack of [Node-API compatibility](https://github.com/oven-sh/bun/issues/158). Under the hood, Protect.js uses [CipherStash Client](#cipherstash-client) which is written in Rust and embedded using [Neon](https://github.com/neon-bindings/neon). + +Lastly, install the CipherStash CLI: + +- On macOS: + + ```bash + brew install cipherstash/tap/stash + ``` + +- On Linux, download the binary for your platform, and put it on your `PATH`: + - [Linux ARM64](https://github.com/cipherstash/cli-releases/releases/latest/download/stash-aarch64-unknown-linux-gnu) + - [Linux x86_64](https://github.com/cipherstash/cli-releases/releases/latest/download/stash-x86_64-unknown-linux-gnu) + +### Opt-out of bundling + +Protect.js uses Node.js specific features and requires the use of the native Node.js `require`. +You need to opt-out of bundling for tools like [Webpack](https://webpack.js.org/configuration/externals/), [esbuild](https://webpack.js.org/configuration/externals/), or [Next.js](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages). + +Read more about bundling [here](#builds-and-bundling). + +## Getting started + +### Configuration + +> [!IMPORTANT] +> Make sure you have [installed the CipherStash CLI](#installation) before following these steps. + +To set up all the configuration and credentials required for Protect.js: + +```bash +stash setup +``` + +If you have not already signed up for a CipherStash account, this will prompt you to do so along the way. + +At the end of `stash setup`, you will have two files in your project: + +- `cipherstash.toml` which contains the configuration for Protect.js +- `cipherstash.secret.toml`: which contains the credentials for Protect.js + +> [!WARNING] +> `cipherstash.secret.toml` should not be committed to git, because it contains sensitive credentials. +> The `stash setup` command will attempt to append to your `.gitignore` file with the `cipherstash.secret.toml` file. + +You can read more about [configuration via toml file or environment variables here](./docs/reference/configuration.md). + +### Basic file structure + +This is the basic file structure of the project. In the `src/protect` directory, we have table definition in `schema.ts` and the protect client in `index.ts`. + +``` +📦 + ├ 📂 src + │ ├ 📂 protect + │ │ ├ 📜 index.ts + │ │ └ 📜 schema.ts + │ └ 📜 index.ts + ├ 📜 .env + ├ 📜 cipherstash.toml + ├ 📜 cipherstash.secret.toml + ├ 📜 package.json + └ 📜 tsconfig.json +``` + +### Defining your schema + +Protect.js uses a schema to define the tables and columns that you want to encrypt and decrypt. + +In the `src/protect/schema.ts` file, you can define your tables and columns. + +```ts +import { csTable, csColumn } from '@cipherstash/protect' + +export const users = csTable('users', { + email: csColumn('email'), +}) + +export const orders = csTable('orders', { + address: csColumn('address'), +}) +``` + +**Searchable encryption** + +If you are looking to enable searchable encryption in a PostgreSQL database, you must declaratively enable the indexes in your schema. + +```ts +import { csTable, csColumn } from '@cipherstash/protect' + +export const users = csTable('users', { + email: csColumn('email').freeTextSearch().equality().orderAndRange(), +}) +``` + +Read more about [defining your schema here](./docs/reference/schema.md). + +### Initializing the Protect client + +To initialize the protect client, import the `protect` function and initialize a client with your defined schema. + +In the `src/protect/index.ts` file: + +```ts +import { protect } from '@cipherstash/protect' +import { users } from './schema' + +// Pass all your tables to the protect function to initialize the client +export const protectClient = await protect(users, orders) +``` + +The `protect` function requires at least one `csTable` to be passed in. + +### Encrypting data + +Use the `encrypt` function to encrypt data. +`encrypt` takes a plaintext string, and an object with the table and column as parameters. + +```typescript +import { users } from './protect/schema' +import { protectClient } from './protect' + +const encryptResult = await protectClient.encrypt('secret@squirrel.example', { + column: users.email, + table: users, +}) + +if (encryptResult.failure) { + // Handle the failure +} + +const ciphertext = encryptResult.data +``` + +The `encrypt` function will return a `Result` object with either a `data` key, or a `failure` key. +The `encryptResult` will return one of the following: + +```typescript +// Success +{ + data: { + c: '\\\\\\\\\\\\\\\\x61202020202020472aaf602219d48c4a...' + } +} + +// Failure +{ + failure: { + type: 'EncryptionError', + message: 'A message about the error' + } +} +``` + +> [!TIP] +> Get significantly better encryption performance by using the [`bulkEncrypt` function](#bulk-encrypting-data) for large payloads. + +### Decrypting data + +Use the `decrypt` function to decrypt data. +`decrypt` takes an encrypted data object as a parameter. + +```typescript +import { protectClient } from './protect' + +const decryptResult = await protectClient.decrypt(ciphertext) + +if (decryptResult.failure) { + // Handle the failure +} + +const plaintext = decryptResult.data +``` + +The `decrypt` function returns a `Result` object with either a `data` key, or a `failure` key. +The `decryptResult` will return one of the following: + +```typescript +// Success +{ + data: 'secret@squirrel.example' +} + +// Failure +{ + failure: { + type: 'DecryptionError', + message: 'A message about the error' + } +} +``` + +> [!TIP] +> Get significantly better decryption performance by using the [`bulkDecrypt` function](#bulk-decrypting-data) for large payloads. + +### Storing encrypted data in a database + +Encrypted data can be stored in any database that supports JSONB, noting that searchable encryption is only supported in PostgreSQL at the moment. + +To store the encrypted data, you will need to specify the column type as `jsonb`. + +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email jsonb NOT NULL, +); +``` + +#### Searchable encryption in PostgreSQL + +To enable searchable encryption in PostgreSQL, you need to [install the EQL custom types and functions](https://github.com/cipherstash/encrypt-query-language?tab=readme-ov-file#installation). + +1. Download the latest EQL install script: + + ```sh + curl -sLo cipherstash-encrypt.sql https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql + ``` + +2. Run this command to install the custom types and functions: + + ```sh + psql -f cipherstash-encrypt.sql + ``` + +EQL is now installed in your database and you can enable searchable encryption by adding the `cs_encrypted_v1` type to a column. + +```sql +CREATE TABLE users ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + email cs_encrypted_v1 +); +``` + +## Identity-aware encryption + +> [!IMPORTANT] +> Right now identity-aware encryption is only supported if you are using [Clerk](https://clerk.com/) as your identity provider. +> Read more about [lock contexts with Clerk and Next.js](./docs/how-to/lock-contexts-with-clerk.md). + +Protect.js can add an additional layer of protection to your data by requiring a valid JWT to perform a decryption. + +This ensures that only the user who encrypted data is able to decrypt it. + +Protect.js does this through a mechanism called a _lock context_. + +### Lock context + +Lock contexts ensure that only specific users can access sensitive data. + +> [!CAUTION] +> You must use the same lock context to encrypt and decrypt data. +> If you use different lock contexts, you will be unable to decrypt the data. + +To use a lock context, initialize a `LockContext` object with the identity claims. + +```typescript +import { LockContext } from '@cipherstash/protect/identify' + +// protectClient from the previous steps +const lc = new LockContext() +``` + +> [!NOTE] +> When initializing a `LockContext`, the default context is set to use the `sub` Identity Claim. + +### Identifying a user for a lock context + +A lock context needs to be locked to a user. +To identify the user, call the `identify` method on the lock context object, and pass a valid JWT from a user's session: + +```typescript +const identifyResult = await lc.identify(jwt) + +// The identify method returns the same Result pattern as the encrypt and decrypt methods. +if (identifyResult.failure) { + // Hanlde the failure +} + +const lockContext = identifyResult.data +``` + +### Encrypting data with a lock context + +To encrypt data with a lock context, call the optional `withLockContext` method on the `encrypt` function and pass the lock context object as a parameter: + +```typescript +import { protectClient } from './protect' +import { users } from './protect/schema' + +const encryptResult = await protectClient.encrypt('plaintext', { + table: users, + column: users.email, +}).withLockContext(lockContext) + +if (encryptResult.failure) { + // Handle the failure +} + +const ciphertext = encryptResult.data +``` + +### Decrypting data with a lock context + +To decrypt data with a lock context, call the optional `withLockContext` method on the `decrypt` function and pass the lock context object as a parameter: + +```typescript +import { protectClient } from './protect' + +const decryptResult = await protectClient.decrypt(ciphertext).withLockContext(lockContext) + +if (decryptResult.failure) { + // Handle the failure +} + +const plaintext = decryptResult.data +``` + +## Bulk encryption and decryption + +If you have a large list of items to encrypt or decrypt, you can use the **`bulkEncrypt`** and **`bulkDecrypt`** methods to batch encryption/decryption. +`bulkEncrypt` and `bulkDecrypt` give your app significantly better throughput than the single-item [`encrypt`](#encrypting-data) and [`decrypt`](#decrypting-data) methods. + + +### Bulk encrypting data + +Build a list of records to encrypt: + +```ts +const users = [ + { id: '1', name: 'CJ', email: 'cj@example.com' }, + { id: '2', name: 'Alex', email: 'alex@example.com' }, +] +``` + +Prepare the array for bulk encryption: + +```ts +const plaintextsToEncrypt = users.map((user) => ({ + plaintext: user.email, // The data to encrypt + id: user.id, // Keep track by user ID +})) +``` + +Perform the bulk encryption: + +```ts +const encryptedResults = await bulkEncrypt(plaintextsToEncrypt, { + column: 'email', + table: 'Users', +}) + +if (encryptedResults.failure) { + // Handle the failure +} + +const encryptedValues = encryptedResults.data + +// encryptedValues might look like: +// [ +// { encryptedData: { c: 'ENCRYPTED_VALUE_1', k: 'ct' }, id: '1' }, +// { encryptedData: { c: 'ENCRYPTED_VALUE_2', k: 'ct' }, id: '2' }, +// ] +``` + +Reassemble data by matching IDs: + +```ts +encryptedValues.forEach((result) => { + // Find the corresponding user + const user = users.find((u) => u.id === result.id) + if (user) { + user.email = result.encryptedData // Store the encrypted data back into the user object + } +}) +``` + +Learn more about [bulk encryption](./docs/reference/bulk-encryption-decryption.md#bulk-encrypting-data) + +### Bulk decrypting data + +Build an array of records to decrypt: + +```ts +const users = [ + { id: '1', name: 'CJ', email: 'ENCRYPTED_VALUE_1' }, + { id: '2', name: 'Alex', email: 'ENCRYPTED_VALUE_2' }, +] +``` + +Prepare the array for bulk decryption: + +```ts +const encryptedPayloads = users.map((user) => ({ + c: user.email, + id: user.id, +})) +``` + +Perform the bulk decryption: + +```ts +const decryptedResults = await bulkDecrypt(encryptedPayloads) + +if (decryptedResults.failure) { + // Handle the failure +} + +const decryptedValues = decryptedResults.data + +// decryptedValues might look like: +// [ +// { plaintext: 'cj@example.com', id: '1' }, +// { plaintext: 'alex@example.com', id: '2' }, +// ] +``` + +Reassemble data by matching IDs: + +```ts +decryptedValues.forEach((result) => { + const user = users.find((u) => u.id === result.id) + if (user) { + user.email = result.plaintext // Put the decrypted value back in place + } +}) +``` + +Learn more about [bulk decryption](./docs/reference/bulk-encryption-decryption.md#bulk-decrypting-data) + +## Supported data types + +Protect.js currently supports encrypting and decrypting text. +Other data types like booleans, dates, ints, floats, and JSON are well supported in other CipherStash products, and will be coming to Protect.js soon. + +Until support for other data types are available, you can express interest in this feature by adding a :+1: on this [GitHub Issue](https://github.com/cipherstash/protectjs/issues/48). + +## Searchable encryption + +Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md) in the docs. + +## Logging + +> [!IMPORTANT] +> `@cipherstash/protect` will NEVER log plaintext data. +> This is by design to prevent sensitive data from leaking into logs. + +`@cipherstash/protect` and `@cipherstash/nextjs` will log to the console with a log level of `info` by default. +To enable the logger, configure the following environment variable: + +```bash +PROTECT_LOG_LEVEL=debug # Enable debug logging +PROTECT_LOG_LEVEL=info # Enable info logging +PROTECT_LOG_LEVEL=error # Enable error logging +``` + +## CipherStash Client + +Protect.js is built on top of the CipherStash Client Rust SDK which is embedded with the `@cipherstash/protect-ffi` package. +The `@cipherstash/protect-ffi` source code is available on [GitHub](https://github.com/cipherstash/protectjs-ffi). + +Read more about configuring the CipherStash client in the [configuration docs](./docs/reference/configuration.md). + +## Builds and bundling + +`@cipherstash/protect` is a native Node.js module, and relies on native Node.js `require` to load the package. + +Here are a few resources to help based on your tool set: + +- [Required Next.js configuration](./docs/how-to/nextjs-external-packages.md). +- [SST and AWS serverless functions](./docs/how-to/sst-external-packages.md). + +## Contributing + +Please read the [contribution guide](CONTRIBUTE.md). + +## License + +Protect.js is [MIT licensed](./LICENSE.md). From 96e8dffae5e328a23dc3c1e9049239f4e25811a8 Mon Sep 17 00:00:00 2001 From: CJ Brewer Date: Mon, 10 Mar 2025 12:28:43 -0600 Subject: [PATCH 2/3] chore: add commit scope --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a11b3e27 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "conventionalCommits.scopes": ["protect"] +} From ba8eefa8df8d04c0a13fbb96df93da7e96977d62 Mon Sep 17 00:00:00 2001 From: CJ Brewer Date: Mon, 10 Mar 2025 12:32:17 -0600 Subject: [PATCH 3/3] fix(apps): :bug: define type module in basic app --- .vscode/settings.json | 2 +- apps/basic/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a11b3e27..dbae6255 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "conventionalCommits.scopes": ["protect"] + "conventionalCommits.scopes": ["protect", "apps"] } diff --git a/apps/basic/package.json b/apps/basic/package.json index 1c81ec15..c2ab3418 100644 --- a/apps/basic/package.json +++ b/apps/basic/package.json @@ -2,7 +2,7 @@ "name": "@cipherstash/basic-example", "private": true, "version": "1.0.4", - "main": "index.mjs", + "type": "module", "scripts": { "start": "tsx index.ts" },