Skip to content

Commit

Permalink
examples: add encryption example. (#2117)
Browse files Browse the repository at this point in the history
  • Loading branch information
thruflo committed Dec 9, 2024
1 parent a81c771 commit 1aa0302
Show file tree
Hide file tree
Showing 22 changed files with 8,256 additions and 0 deletions.
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

0 comments on commit 1aa0302

Please sign in to comment.