Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/concepts/searchable-encryption.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Searchable encryption

Protect.js supports searching encrypted data, which enabled trusted data access so that you can:
Protect.js supports searching encrypted data, which enables trusted data access so that you can:

1. Prove to your customers that you can track exactly what data is being accessed in your application.
2. Provide evidence for compliance requirements, such as [SOC 2](https://cipherstash.com/compliance/soc2) and [BDSG](https://cipherstash.com/compliance/bdsg).
Expand Down Expand Up @@ -69,20 +69,21 @@ CipherStash uses [EQL](https://github.com/cipherstash/encrypt-query-language) to
// 1) Encrypt the search term
const searchTerm = 'alice.johnson@example.com'

const encryptedParam = await protectClient.encrypt(searchTerm, {
const encryptedParam = await protectClient.createSearchTerms([{
value: searchTerm,
table: protectedUsers, // Reference to the Protect table schema
column: protectedUsers.email, // Your Protect column definition
table: protectedUsers, // Reference to the table schema
})
}])

if (encryptedParam.failure) {
// Handle the failure
}

// 2) Build an equality query using EQL
// 2) Build an equality query noting that EQL must be installed in order for the operation to work successfully
const equalitySQL = `
SELECT email
FROM users
WHERE cs_unique_v2($1) = cs_unique_v2($2)
WHERE email = $1
`

// 3) Execute the query, passing in the Postgres column name
Expand Down
5 changes: 5 additions & 0 deletions examples/next-drizzle-mysql/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DATABASE_URL=mysql://protect_example:password@127.0.0.1:3306/protect_example
CS_CLIENT_ID=
CS_CLIENT_KEY=
CS_CLIENT_ACCESS_KEY=
CS_WORKSPACE_CRN=
77 changes: 77 additions & 0 deletions examples/next-drizzle-mysql/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Next.js + Drizzle ORM + MySQL + Protect.js Example

This example demonstrates how to build a modern web application using:
- [Next.js](https://nextjs.org/) - React framework for production
- [Drizzle ORM](https://orm.drizzle.team/) - TypeScript ORM for SQL databases
- [MySQL](https://www.mysql.com/) - Popular open-source relational database
- [Protect.js](https://cipherstash.com/protect) - Data protection and encryption library

## Features

- Full-stack TypeScript application
- Database migrations and schema management with Drizzle
- Data protection and encryption with Protect.js
- Modern UI with Tailwind CSS
- Form handling with React Hook Form and Zod validation
- Docker-based MySQL database setup

## Prerequisites

- Node.js 18+
- Docker and Docker Compose
- MySQL (if running locally without Docker)

## Getting Started

1. Clone the repository and install dependencies:
```bash
npm install
```

2. Set up your environment variables:
Copy the `.env.example` file to `.env.local`:
```bash
cp .env.example .env.local
```
Then update the environment variables in `.env.local` with your Protect.js configuration values.

3. Start the MySQL database using Docker:
```bash
docker-compose up -d
```

4. Run database migrations:
```bash
npm run db:generate
npm run db:migrate
```

5. Start the development server:
```bash
npm run dev
```

The application will be available at `http://localhost:3000`.

## Project Structure

- `/src` - Application source code
- `/drizzle` - Database migrations and schema
- `/public` - Static assets
- `drizzle.config.ts` - Drizzle ORM configuration
- `docker-compose.yml` - Docker configuration for MySQL

## Available Scripts

- `npm run dev` - Start development server
- `npm run build` - Build for production
- `npm run start` - Start production server
- `npm run db:generate` - Generate database migrations
- `npm run db:migrate` - Run database migrations

## Learn More

- [Next.js Documentation](https://nextjs.org/docs)
- [Drizzle ORM Documentation](https://orm.drizzle.team/docs/overview)
- [Protect.js Documentation](https://cipherstash.com/protect/docs)
- [MySQL Documentation](https://dev.mysql.com/doc/)
16 changes: 16 additions & 0 deletions examples/next-drizzle-mysql/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.8'
services:
db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: protect_example
MYSQL_USER: protect_example
MYSQL_PASSWORD: password
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql

volumes:
mysql_data:
15 changes: 15 additions & 0 deletions examples/next-drizzle-mysql/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'dotenv/config'
import { defineConfig } from 'drizzle-kit'
export default defineConfig({
dialect: 'mysql',
schema: './src/db/schema.ts',
dbCredentials: {
host: '127.0.0.1',
port: 3306,
user: 'protect_example',
password: 'password',
database: 'protect_example',
},
})

// mysql://protect_example:password@127.0.0.1:3306/protect_example
6 changes: 6 additions & 0 deletions examples/next-drizzle-mysql/drizzle/0000_brave_madrox.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE `users` (
`id` int AUTO_INCREMENT NOT NULL,
`name` json,
`email` json,
CONSTRAINT `users_id` PRIMARY KEY(`id`)
);
56 changes: 56 additions & 0 deletions examples/next-drizzle-mysql/drizzle/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"version": "5",
"dialect": "mysql",
"id": "03335511-a5f1-45e4-bcf7-227e326b28a5",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"users": {
"name": "users",
"columns": {
"id": {
"name": "id",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "json",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "json",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"users_id": {
"name": "users_id",
"columns": [
"id"
]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}
13 changes: 13 additions & 0 deletions examples/next-drizzle-mysql/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "mysql",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1748545269720,
"tag": "0000_brave_madrox",
"breakpoints": true
}
]
}
7 changes: 7 additions & 0 deletions examples/next-drizzle-mysql/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
serverExternalPackages: ['@cipherstash/protect', 'mysql2'],
}

export default nextConfig
33 changes: 33 additions & 0 deletions examples/next-drizzle-mysql/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "next-drizzle-mysql",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate"
},
"dependencies": {
"@cipherstash/protect": "^8.3.0",
"@hookform/resolvers": "^5.0.1",
"drizzle-orm": "^0.44.0",
"mysql2": "^3.14.1",
"next": "15.3.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.4",
"zod": "^3.24.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"dotenv": "^16.4.7",
"drizzle-kit": "^0.30.5",
"tailwindcss": "^4",
"typescript": "^5"
}
}
5 changes: 5 additions & 0 deletions examples/next-drizzle-mysql/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};

export default config;
1 change: 1 addition & 0 deletions examples/next-drizzle-mysql/public/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/next-drizzle-mysql/public/globe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/next-drizzle-mysql/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/next-drizzle-mysql/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/next-drizzle-mysql/public/window.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions examples/next-drizzle-mysql/src/app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use server'

import { db } from '@/db'
import { users } from '@/db/schema'
import type { FormData } from '@/components/form'
import { protectClient } from '@/protect'
import { users as protectedUsers } from '@/protect/schema'

export async function createUser(data: FormData) {
console.log(data)

const result = await protectClient.encryptModel(data, protectedUsers)

if (result.failure) {
console.error(result.failure.message)
return
}

console.log(result.data)

await db.insert(users).values({
name: result.data.name,
email: result.data.email,
})

return {
success: true,
}
}
Binary file added examples/next-drizzle-mysql/src/app/favicon.ico
Binary file not shown.
26 changes: 26 additions & 0 deletions examples/next-drizzle-mysql/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@import "tailwindcss";

:root {
--background: #ffffff;
--foreground: #171717;
}

@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
34 changes: 34 additions & 0 deletions examples/next-drizzle-mysql/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
Loading