Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples: add encryption example. #2117

Merged
merged 6 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions examples/encryption/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build/**
41 changes: 41 additions & 0 deletions examples/encryption/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
`eslint:recommended`,
`plugin:@typescript-eslint/recommended`,
`plugin:prettier/recommended`,
],
parserOptions: {
ecmaVersion: 2022,
requireConfigFile: false,
sourceType: `module`,
ecmaFeatures: {
jsx: true,
},
},
parser: `@typescript-eslint/parser`,
plugins: [`prettier`],
rules: {
quotes: [`error`, `single`],
"no-unused-vars": `off`,
"@typescript-eslint/no-unused-vars": [
`error`,
{
argsIgnorePattern: `^_`,
varsIgnorePattern: `^_`,
caughtErrorsIgnorePattern: `^_`,
},
],
},
ignorePatterns: [
`**/node_modules/**`,
`**/dist/**`,
`tsup.config.ts`,
`vitest.config.ts`,
`.eslintrc.js`,
],
};
2 changes: 2 additions & 0 deletions examples/encryption/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
.env.local
6 changes: 6 additions & 0 deletions examples/encryption/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}
64 changes: 64 additions & 0 deletions examples/encryption/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

# Encryption example

This is an example of encryption with Electric. It's a React app with a very simple Express API server.

The Electric-specific code is in [`./src/Example.tsx`](./src/Example.tsx). It demonstrates:

- encrypting data before sending to the API server
- decrypting data after it syncs in through Electric

## Setup

This example is part of the [ElectricSQL monorepo](../..) and is designed to be built and run as part of the [pnpm workspace](https://pnpm.io/workspaces) defined in [`../../pnpm-workspace.yaml`](../../pnpm-workspace.yaml).

Navigate to the root directory of the monorepo, e.g.:

```shell
cd ../../
```

Install and build all of the workspace packages and examples:

```shell
pnpm install
pnpm run -r build
```

Navigate back to this directory:

```shell
cd examples/basic-example
```

Start the example backend services using [Docker Compose](https://docs.docker.com/compose/):

```shell
pnpm backend:up
```

Now start the dev server:

```shell
pnpm dev
```

Open [localhost:5173]http://localhost:5173] in your web browser. When you add items, the plaintext is encrypted before it leaves the app. You can see the ciphertext in Postgres, e.g.:

```console
$ psql "postgresql://postgres:password@localhost:54321/electric"
psql (16.4)
Type "help" for help.

electric=# select * from items;
id | ciphertext | iv
--------------------------------------+------------------------------+------------------
491b2654-5714-48bb-a206-59f87a2dc33c | vDwv3IX5AGXJVi2jNJJDPE25MwiS | 0gwdqHvqiJ8lJqaS
(1 row)
```

When you're done, stop the backend services using:

```shell
pnpm backend:down
```
70 changes: 70 additions & 0 deletions examples/encryption/backend/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import bodyParser from 'body-parser'
import cors from 'cors'
import express from 'express'
import pg from 'pg'

import { z } from 'zod'

// Connect to Postgres.
const DATABASE_URL = process.env.DATABASE_URL || 'postgresql://postgres:password@localhost:54321/electric'
const DATABASE_USE_SSL = process.env.DATABASE_USE_SSL === 'true' || false
const pool = new pg.Pool({connectionString: DATABASE_URL, ssl: DATABASE_USE_SSL})
const db = await pool.connect()

// Expose an HTTP server.
const PORT = parseInt(process.env.PORT || '3001')
const app = express()
app.use(bodyParser.json())
app.use(cors())

// Validate user input
const createSchema = z.object({
id: z.string().uuid(),
ciphertext: z.string(),
iv: z.string()
})

// Expose `POST {data} /items`.
app.post(`/items`, async (req, res) => {
let data
try {
data = createSchema.parse(req.body)
}
catch (err) {
return res.status(400).json({ errors: err.errors })
}

// Insert the item into the database.
const sql = `
INSERT INTO items (
id,
ciphertext,
iv
)
VALUES (
$1,
$2,
$3
)
`

const params = [
data.id,
data.ciphertext,
data.iv
]

try {
await db.query(sql, params)
}
catch (err) {
return res.status(500).json({ errors: err })
}

return res.status(200).json({ status: 'OK' })
})

// Start the server
app.listen(PORT, () => {
console.log(`Server listening at http://localhost:${PORT}`)
})
5 changes: 5 additions & 0 deletions examples/encryption/db/migrations/01-create_items_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS items (
id UUID PRIMARY KEY NOT NULL,
ciphertext TEXT NOT NULL,
iv TEXT NOT NULL
);
13 changes: 13 additions & 0 deletions examples/encryption/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Web Example - ElectricSQL</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
43 changes: 43 additions & 0 deletions examples/encryption/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@electric-examples/encryption",
"private": true,
"version": "0.0.1",
"author": "ElectricSQL",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"backend:up": "PROJECT_NAME=basic-example pnpm -C ../../ run example-backend:up && pnpm db:migrate",
"backend:down": "PROJECT_NAME=basic-example pnpm -C ../../ run example-backend:down",
"db:migrate": "dotenv -e ../../.env.dev -- pnpm exec pg-migrations apply --directory ./db/migrations",
"dev": "concurrently \"vite\" \"node backend/api.js\"",
"build": "vite build",
"format": "eslint . --ext ts,tsx --fix",
"stylecheck": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@electric-sql/react": "workspace:*",
"base64-js": "^1.5.1",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"pg": "^8.12.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"uuid": "^10.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@databases/pg-migrations": "^5.0.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.3.1",
"concurrently": "^8.2.2",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"typescript": "^5.5.3",
"vite": "^5.3.4"
}
}
Binary file added examples/encryption/public/favicon.ico
Binary file not shown.
3 changes: 3 additions & 0 deletions examples/encryption/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
25 changes: 25 additions & 0 deletions examples/encryption/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.App {
text-align: center;
}

.App-logo {
height: 64px;
pointer-events: none;
margin-top: min(40px, 5vmin);
margin-bottom: min(20px, 4vmin);
}

.App-header {
background-color: #1c1e20;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: top;
justify-content: top;
font-size: calc(10px + 2vmin);
color: white;
}

.App-link {
color: #61dafb;
}
16 changes: 16 additions & 0 deletions examples/encryption/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import logo from './assets/logo.svg'
import './App.css'
import './style.css'

import { Example } from './Example'

export default function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Example />
</header>
</div>
)
}
79 changes: 79 additions & 0 deletions examples/encryption/src/Example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.controls {
margin-bottom: 1.5rem;
}

.button {
display: inline-block;
line-height: 1.3;
text-align: center;
text-decoration: none;
vertical-align: middle;
cursor: pointer;
user-select: none;
width: calc(15vw + 100px);
margin-right: 0.5rem !important;
margin-left: 0.5rem !important;
border-radius: 32px;
text-shadow: 2px 6px 20px rgba(0, 0, 0, 0.4);
box-shadow: rgba(0, 0, 0, 0.5) 1px 2px 8px 0px;
background: #1e2123;
border: 2px solid #229089;
color: #f9fdff;
font-size: 16px;
font-weight: 500;
padding: 10px 18px;
}

.item {
display: block;
line-height: 1.3;
text-align: center;
vertical-align: middle;
width: calc(30vw - 1.5rem + 200px);
margin-right: auto;
margin-left: auto;
border-radius: 9px;
border: 1px solid #D0BCFF;
background: #1e2123;
color: #D0BCFF;
font-size: 13px;
padding: 10px 18px;
}

form {
border-top: 0.5px solid rgba(227, 227, 239, 0.32);
width: calc(30vw - 1.5rem + 200px);
margin: 20px auto;
padding: 20px 0;
}

form input[type=text] {
padding: 12px 18px;
margin-bottom: 18px;
background: #1e2123;
border: 1px solid rgba(227, 227, 239, 0.92);
border-radius: 9px;
color: #f5f5f5;
outline: none;
font-size: 14px;
display: block;
text-align: center;
width: calc(30vw - 1.5rem + 160px);
margin-right: auto;
margin-left: auto;
}

form input[type=text]::placeholder {
color: rgba(227, 227, 239, 0.62);
}

form button[type=submit] {
background: #D0BCFF;
border: none;
padding: 8px 20px;
border-radius: 9px;
color: #1e2123;
font-size: 15px;
font-weight: 500;
cursor: pointer;
}
Loading
Loading