Skip to content

Commit 2176cff

Browse files
authored
Merge pull request #96 from cipherstash/next
feat(protect): searchable encryption and protect schemas
2 parents fd77171 + fea3a67 commit 2176cff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1804
-683
lines changed

.changeset/young-waves-worry.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@cipherstash/protect": minor
3+
---
4+
5+
* Added support for searching encrypted data
6+
* Added a schema strategy for defining your schema
7+
* Required schema to initialize the protect client

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ mise.local.toml
6565
# cipherstash
6666
cipherstash.toml
6767
cipherstash.secret.toml
68+
sql/cipherstash-*.sql

CONTRIBUTE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,27 @@ We use [**Changesets**](https://github.com/changesets/changesets) to manage vers
9898
- Follow the prompts to indicate the type of version bump (patch, minor, major).
9999
- The [GitHub Actions](./.github/workflows/) (or other CI pipeline) will handle the **publish** step to npm once your PR is merged and the changeset is committed to `main`.
100100

101+
## Pre release process
102+
103+
We currently use [changesets to manage pre-releasing](https://github.com/changesets/changesets/blob/main/docs/prereleases.md) the `next` version of the package, and the process is executed manually.
104+
105+
To do so, you need to:
106+
107+
1. Check out the `next` branch
108+
2. Run `pnpm changeset pre enter next`
109+
3. Run `pnpm changeset version`
110+
4. Run `git add .`
111+
5. Run `git commit -m "Enter prerelease mode and version packages"`
112+
6. Run `pnpm changeset publish --tag next`
113+
7. Run `git push --follow-tags`
114+
115+
When you are ready to release, you can run `pnpm changeset pre exit` to exit prerelease mode and commit the changes.
116+
When you merge the PR, the `next` branch will be merged into `main`, and the package will be published to npm without the prerelease tag.
117+
118+
> [!IMPORTANT]
119+
> This process can be dangerous, so please be careful when using it as it's difficult to undo mistakes.
120+
> If you are unfamiliar with the process, please reach out to the maintainers for help.
121+
101122
## Additional Resources
102123

103124
- [Turborepo Documentation](https://turbo.build/repo/docs)

README.md

Lines changed: 146 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
# Protect.js
1+
<h1 align="center">
2+
<img alt="CipherStash Logo" loading="lazy" width="200" height="60" decoding="async" data-nimg="1" style="color:transparent" src="https://cipherstash.com/assets/cs-github.png">
3+
</br>
24

3-
[![Tests](https://github.com/cipherstash/protectjs/actions/workflows/tests.yml/badge.svg)](https://github.com/cipherstash/protectjs/actions/workflows/tests.yml)
4-
[![Built by CipherStash](https://raw.githubusercontent.com/cipherstash/meta/refs/heads/main/csbadge.svg)](https://cipherstash.com)
5+
Protect.js</h1>
6+
<p align="center">
7+
Implement robust data security without sacrificing performance or usability
8+
<br/>
9+
<a href="https://cipherstash.com">Built by CipherStash</a>
10+
</p>
11+
<br/>
512

6-
Protect.js is a JavaScript/TypeScript package for encrypting and decrypting data in PostgreSQL databases.
7-
Encryption operations happen directly in your app, and the ciphertext is stored in your PostgreSQL database.
13+
<!-- start -->
814

9-
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.
10-
Under the hood Protect.js uses CipherStash [Encrypt Query Language (EQL)](https://github.com/cipherstash/encrypt-query-language), and all ZeroKMS data keys are backed by a root key in [AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html).
15+
## What's Protect.js?
16+
17+
Protect.js is a TypeScript package for encrypting and decrypting data.
18+
Encryption operations happen directly in your app, and the ciphertext is stored in your database.
19+
20+
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).
21+
22+
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.
23+
24+
> [!IMPORTANT]
25+
> Searching, sorting, and filtering on encrypted data is only supported in PostgreSQL at the moment.
26+
> Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md).
1127
1228
## Table of contents
1329

@@ -29,7 +45,7 @@ For more specific documentation, please refer to the [docs](https://github.com/c
2945

3046
## Features
3147

32-
Protect.js protects data in PostgreSQL databases using industry-standard AES encryption.
48+
Protect.js protects data in using industry-standard AES encryption.
3349
Protect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for bulk encryption and decryption operations.
3450
This enables every encrypted value, in every column, in every row in your database to have a unique key — without sacrificing performance.
3551

@@ -38,6 +54,8 @@ This enables every encrypted value, in every column, in every row in your databa
3854
- **Single item encryption and decryption**: Just looking for a way to encrypt and decrypt single values? Protect.js has you covered.
3955
- **Really fast:** ZeroKMS's performance makes using millions of unique keys feasible and performant for real-world applications built with Protect.js.
4056
- **Identity-aware encryption**: Lock down access to sensitive data by requiring a valid JWT to perform a decryption.
57+
- **Audit trail**: Every decryption event will be logged in ZeroKMS to help you prove compliance.
58+
- **Searchable encryption**: Protect.js supports searching encrypted data in PostgreSQL.
4159
- **TypeScript support**: Strongly typed with TypeScript interfaces and types.
4260

4361
**Use cases:**
@@ -54,7 +72,7 @@ Check out the example applications:
5472
- [Drizzle example](/apps/drizzle) demonstrates how to use Protect.js with an ORM
5573
- [Next.js and lock contexts example using Clerk](/apps/nextjs-clerk) demonstrates how to protect data with identity-aware encryption
5674

57-
`@cipherstash/protect` can be used with most ORMs that support PostgreSQL.
75+
`@cipherstash/protect` can be used with most ORMs.
5876
If you're interested in using `@cipherstash/protect` with a specific ORM, please [create an issue](https://github.com/cipherstash/protectjs/issues/new).
5977

6078
## Installing Protect.js
@@ -115,33 +133,86 @@ At the end of `stash setup`, you will have two files in your project:
115133
> `cipherstash.secret.toml` should not be committed to git, because it contains sensitive credentials.
116134
> The `stash setup` command will attempt to append to your `.gitignore` file with the `cipherstash.secret.toml` file.
117135
118-
You can read more about [configuration via toml file or environment variables here](./docs/configuration.md).
136+
You can read more about [configuration via toml file or environment variables here](./docs/reference/configuration.md).
119137

120-
### Initializing the EQL client
138+
### Basic file structure
121139

122-
In your application, import the `protect` function from the `@cipherstash/protect` package, and initialize a client with your CipherStash credentials.
140+
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`.
123141

124-
```typescript
125-
const { protect } = require('@cipherstash/protect')
126-
const protectClient = await protect()
142+
```
143+
📦 <project root>
144+
├ 📂 src
145+
│ ├ 📂 protect
146+
│ │ ├ 📜 index.ts
147+
│ │ └ 📜 schema.ts
148+
│ └ 📜 index.ts
149+
├ 📜 .env
150+
├ 📜 cipherstash.toml
151+
├ 📜 cipherstash.secret.toml
152+
├ 📜 package.json
153+
└ 📜 tsconfig.json
127154
```
128155

129-
If you are using ES6:
156+
### Defining your schema
130157

131-
```typescript
158+
Protect.js uses a schema to define the tables and columns that you want to encrypt and decrypt.
159+
160+
In the `src/protect/schema.ts` file, you can define your tables and columns.
161+
162+
```ts
163+
import { csTable, csColumn } from '@cipherstash/protect'
164+
165+
export const users = csTable('users', {
166+
email: csColumn('email'),
167+
})
168+
169+
export const orders = csTable('orders', {
170+
address: csColumn('address'),
171+
})
172+
```
173+
174+
**Searchable encryption**
175+
176+
If you are looking to enable searchable encryption in a PostgreSQL database, you must declaratively enable the indexes in your schema.
177+
178+
```ts
179+
import { csTable, csColumn } from '@cipherstash/protect'
180+
181+
export const users = csTable('users', {
182+
email: csColumn('email').freeTextSearch().equality().orderAndRange(),
183+
})
184+
```
185+
186+
Read more about [defining your schema here](./docs/reference/schema.md).
187+
188+
### Initializing the Protect client
189+
190+
To initialize the protect client, import the `protect` function and initialize a client with your defined schema.
191+
192+
In the `src/protect/index.ts` file:
193+
194+
```ts
132195
import { protect } from '@cipherstash/protect'
133-
const protectClient = await protect()
196+
import { users } from './schema'
197+
198+
// Pass all your tables to the protect function to initialize the client
199+
export const protectClient = await protect(users, orders)
134200
```
135201

202+
The `protect` function requires at least one `csTable` to be passed in.
203+
136204
### Encrypting data
137205

138206
Use the `encrypt` function to encrypt data.
139-
`encrypt` takes a plaintext string, and an object with the table and column name as parameters.
207+
`encrypt` takes a plaintext string, and an object with the table and column as parameters.
140208

141209
```typescript
210+
import { users } from './protect/schema'
211+
import { protectClient } from './protect'
212+
142213
const encryptResult = await protectClient.encrypt('secret@squirrel.example', {
143-
column: 'email',
144-
table: 'users',
214+
column: users.email,
215+
table: users,
145216
})
146217

147218
if (encryptResult.failure) {
@@ -177,9 +248,11 @@ The `encryptResult` will return one of the following:
177248
### Decrypting data
178249

179250
Use the `decrypt` function to decrypt data.
180-
`decrypt` takes an encrypted data object, and an object with the lock context as parameters.
251+
`decrypt` takes an encrypted data object as a parameter.
181252

182253
```typescript
254+
import { protectClient } from './protect'
255+
183256
const decryptResult = await protectClient.decrypt(ciphertext)
184257

185258
if (decryptResult.failure) {
@@ -212,7 +285,9 @@ The `decryptResult` will return one of the following:
212285
213286
### Storing encrypted data in a database
214287

215-
To store the encrypted data in PostgreSQL, you will need to specify the column type as `jsonb`.
288+
Encrypted data can be stored in any database that supports JSONB, noting that searchable encryption is only supported in PostgreSQL at the moment.
289+
290+
To store the encrypted data, you will need to specify the column type as `jsonb`.
216291

217292
```sql
218293
CREATE TABLE users (
@@ -221,8 +296,37 @@ CREATE TABLE users (
221296
);
222297
```
223298

299+
#### Searchable encryption in PostgreSQL
300+
301+
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).
302+
303+
1. Download the latest EQL install script:
304+
305+
```sh
306+
curl -sLo cipherstash-encrypt.sql https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql
307+
```
308+
309+
2. Run this command to install the custom types and functions:
310+
311+
```sh
312+
psql -f cipherstash-encrypt.sql
313+
```
314+
315+
EQL is now installed in your database and you can enable searchable encryption by adding the `cs_encrypted_v1` type to a column.
316+
317+
```sql
318+
CREATE TABLE users (
319+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
320+
email cs_encrypted_v1
321+
);
322+
```
323+
224324
## Identity-aware encryption
225325

326+
> [!IMPORTANT]
327+
> Right now identity-aware encryption is only supported if you are using [Clerk](https://clerk.com/) as your identity provider.
328+
> Read more about [lock contexts with Clerk and Next.js](./docs/how-to/lock-contexts-with-clerk.md).
329+
226330
Protect.js can add an additional layer of protection to your data by requiring a valid JWT to perform a decryption.
227331

228332
This ensures that only the user who encrypted data is able to decrypt it.
@@ -270,9 +374,12 @@ const lockContext = identifyResult.data
270374
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:
271375

272376
```typescript
377+
import { protectClient } from './protect'
378+
import { users } from './protect/schema'
379+
273380
const encryptResult = await protectClient.encrypt('plaintext', {
274-
table: 'users',
275-
column: 'email',
381+
table: users,
382+
column: users.email,
276383
}).withLockContext(lockContext)
277384

278385
if (encryptResult.failure) {
@@ -287,6 +394,8 @@ const ciphertext = encryptResult.data
287394
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:
288395

289396
```typescript
397+
import { protectClient } from './protect'
398+
290399
const decryptResult = await protectClient.decrypt(ciphertext).withLockContext(lockContext)
291400

292401
if (decryptResult.failure) {
@@ -338,8 +447,8 @@ const encryptedValues = encryptedResults.data
338447

339448
// encryptedValues might look like:
340449
// [
341-
// { c: 'ENCRYPTED_VALUE_1', id: '1' },
342-
// { c: 'ENCRYPTED_VALUE_2', id: '2' },
450+
// { encryptedData: { c: 'ENCRYPTED_VALUE_1', k: 'ct' }, id: '1' },
451+
// { encryptedData: { c: 'ENCRYPTED_VALUE_2', k: 'ct' }, id: '2' },
343452
// ]
344453
```
345454

@@ -350,12 +459,12 @@ encryptedValues.forEach((result) => {
350459
// Find the corresponding user
351460
const user = users.find((u) => u.id === result.id)
352461
if (user) {
353-
user.email = result.c // Store ciphertext back into the user object
462+
user.email = result.encryptedData // Store the encrypted data back into the user object
354463
}
355464
})
356465
```
357466

358-
Learn more about [bulk encryption](./docs/bulk-encryption-decryption.md#bulk-encrypting-data)
467+
Learn more about [bulk encryption](./docs/reference/bulk-encryption-decryption.md#bulk-encrypting-data)
359468

360469
### Bulk decrypting data
361470

@@ -406,25 +515,18 @@ decryptedValues.forEach((result) => {
406515
})
407516
```
408517

409-
Learn more about [bulk decryption](./docs/bulk-encryption-decryption.md#bulk-decrypting-data)
518+
Learn more about [bulk decryption](./docs/reference/bulk-encryption-decryption.md#bulk-decrypting-data)
410519

411520
## Supported data types
412521

413-
`@cipherstash/protect` currently supports encrypting and decrypting text.
414-
Other data types like booleans, dates, ints, floats, and JSON are extremely well supported in other CipherStash products, and will be coming to `@cipherstash/protect`.
415-
Until support for other data types are available in `@cipherstash/protect`, you can:
522+
Protect.js currently supports encrypting and decrypting text.
523+
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.
416524

417-
- Read [about how these data types work in EQL](https://github.com/cipherstash/encrypt-query-language/blob/main/docs/reference/INDEX.md)
418-
- Express interest in this feature by adding a :+1: on this [GitHub Issue](https://github.com/cipherstash/protectjs/issues/48).
525+
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).
419526

420527
## Searchable encryption
421528

422-
`@cipherstash/protect` does not currently support searching encrypted data.
423-
Searchable encryption is an extremely well supported capability in other CipherStash products, and will be coming to `@cipherstash/protect`.
424-
Until searchable encryption support is released in `@cipherstash/protect`, you can:
425-
426-
- Read [about how searchable encryption works in EQL](https://github.com/cipherstash/encrypt-query-language)
427-
- Express interest in this feature by adding a :+1: on this [GitHub Issue](https://github.com/cipherstash/protectjs/issues/46).
529+
Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md) in the docs.
428530

429531
## Logging
430532

@@ -446,16 +548,16 @@ PROTECT_LOG_LEVEL=error # Enable error logging
446548
Protect.js is built on top of the CipherStash Client Rust SDK which is embedded with the `@cipherstash/protect-ffi` package.
447549
The `@cipherstash/protect-ffi` source code is available on [GitHub](https://github.com/cipherstash/protectjs-ffi).
448550

449-
Read more about configuring the CipherStash client in the [configuration docs](./docs/configuration.md).
551+
Read more about configuring the CipherStash client in the [configuration docs](./docs/reference/configuration.md).
450552

451553
## Builds and bundling
452554

453555
`@cipherstash/protect` is a native Node.js module, and relies on native Node.js `require` to load the package.
454556

455557
Here are a few resources to help based on your tool set:
456558

457-
- [Required Next.js configuration](./docs/nextjs.md).
458-
- [SST and AWS serverless functions](./docs/sst.md).
559+
- [Required Next.js configuration](./docs/how-to/nextjs-external-packages.md).
560+
- [SST and AWS serverless functions](./docs/how-to/sst-external-packages.md).
459561

460562
## Contributing
461563

0 commit comments

Comments
 (0)