This project uses the following technologies:
- Turborepo for monorepo management
- pnpm for package management
- Next.js for the application framework
- Clerk for auth
- Vercel for hosting
- Supabase for database
- Drizzle ORM for database access
- CipherStash for data encryption
- jseql for interacting with CipherStash Encrypt
First, install dependencies:
pnpm install
Second, create a .env.local
file in the root directory with the following content:
# Clerk auth
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
# Supabase postgres connection string
POSTGRES_URL=
# CipherStash encryption and access keys
CS_CLIENT_ID=
CS_CLIENT_KEY=
CS_CLIENT_ACCESS_KEY=
CS_WORKSPACE_ID=
Finally, run the development server:
pnpm run dev
Open http://localhost:3000 with your browser to see the result.
The database is hosted on Supabase and has the following schema which is defined using the Drizzle ORM:
// Data that is encrypted using jseql is stored as jsonb in postgres
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
email: jsonb("email").notNull(),
role: varchar("role").notNull(),
});
Note
This example does not include any searchable encrypted fields. If you want to search on encrypted fields, you will need to install EQL. The EQL library ships with custom types that are used to define encrypted fields. See the EQL documentation for more information.
All the email data is encrypted using jseql and CipherStash.
The cipherstext is stored in the email
column of the users
table.
The application is configured to only decrypt the data when the user is signed in, otherwise it will display the encrypted data.
@cipherstash/jseql
uses custom Rust bindings in order to perform encryptions and decryptions.
We leverage the Neon project to provide a JavaScript API for these bindings.
There is a helper script which will insert records into the database:
pnpm tsx packages/core/helpers/insert.ts --name 'user_name' --email 'user_email'
This will insert a record into the database with an encrypted email field.
To view the decrpytion implementation, see the getUsers
function in src/app/page.tsx.
Since @cipherstash/jseql
is a native Node.js module, you need to opt-out from the Server Components bundling and use native Node.js require
instead.
next.config.ts
configuration:
const nextConfig = {
...
serverExternalPackages: ['@cipherstash/jseql'],
}
next.config.mjs
configuration:
const nextConfig = {
...
experimental: {
serverComponentsExternalPackages: ['@cipherstash/jseql'],
},
}
Note
At the time of this writing, some Node-API functions are not implemented so this environment may not work with Bun.
See the .npmrc in the root of the project which contains the following contents. This is requires when @cipherstash/jseql is a nested dependency of your application:
public-hoist-pattern[]=*@cipherstash/jseql*