diff --git a/javascript/apps/prisma/.env.example b/javascript/apps/prisma/.env.example new file mode 100644 index 00000000..4e875d66 --- /dev/null +++ b/javascript/apps/prisma/.env.example @@ -0,0 +1,8 @@ +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema + +# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +DATABASE_URL="postgresql://johndoe:randompassword@localhost:6432/mydb?schema=public" +DIRECT_URL="postgresql://johndoe:randompassword@my.db.host:5432/mydb?schema=public" \ No newline at end of file diff --git a/javascript/apps/prisma/README.md b/javascript/apps/prisma/README.md new file mode 100644 index 00000000..bcc8749b --- /dev/null +++ b/javascript/apps/prisma/README.md @@ -0,0 +1,5 @@ +# prisma-eql + +Init migrations: + +bun prisma migrate dev --name init diff --git a/javascript/apps/prisma/package.json b/javascript/apps/prisma/package.json new file mode 100644 index 00000000..92d4440e --- /dev/null +++ b/javascript/apps/prisma/package.json @@ -0,0 +1,22 @@ +{ + "name": "prisma-eql", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "prisma": "^5.20.0", + "prisma-json-types-generator": "^3.1.1" + }, + "scripts": { + "insert": "bun run src/insert.ts", + "select": "bun run src/select.ts" + }, + "peerDependencies": { + "typescript": "^5.0.0", + "@cipherstash/eql": "workspace:*" + }, + "dependencies": { + "@prisma/client": "^5.20.0", + "postgres": "^3.4.4" + } +} diff --git a/javascript/apps/prisma/prisma/migrations/20241003015025_init/migration.sql b/javascript/apps/prisma/prisma/migrations/20241003015025_init/migration.sql new file mode 100644 index 00000000..6406f58f --- /dev/null +++ b/javascript/apps/prisma/prisma/migrations/20241003015025_init/migration.sql @@ -0,0 +1,11 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "email_encrypted" JSONB NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/javascript/apps/prisma/prisma/migrations/migration_lock.toml b/javascript/apps/prisma/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..99e4f200 --- /dev/null +++ b/javascript/apps/prisma/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" diff --git a/javascript/apps/prisma/prisma/schema.prisma b/javascript/apps/prisma/prisma/schema.prisma new file mode 100644 index 00000000..f173dec0 --- /dev/null +++ b/javascript/apps/prisma/prisma/schema.prisma @@ -0,0 +1,28 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +/// Always after the prisma-client-js generator +generator json { + provider = "prisma-json-types-generator" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + + /// [CsEncryptedType] + email_encrypted Json +} diff --git a/javascript/apps/prisma/src/db.ts b/javascript/apps/prisma/src/db.ts new file mode 100644 index 00000000..152a8ba4 --- /dev/null +++ b/javascript/apps/prisma/src/db.ts @@ -0,0 +1,2 @@ +import { PrismaClient } from '@prisma/client' +export const prisma = new PrismaClient() diff --git a/javascript/apps/prisma/src/insert.ts b/javascript/apps/prisma/src/insert.ts new file mode 100644 index 00000000..e9a09177 --- /dev/null +++ b/javascript/apps/prisma/src/insert.ts @@ -0,0 +1,38 @@ +import { parseArgs } from 'node:util' +import { eqlPayload } from '@cipherstash/eql' +import type { InputJsonValue } from '@prisma/client/runtime/library' +import { prisma } from './db' + +const { values, positionals } = parseArgs({ + args: Bun.argv, + options: { + email: { + type: 'string', + }, + }, + strict: true, + allowPositionals: true, +}) + +const email = values.email + +if (!email) { + throw new Error('[ERROR] the email command line argument is required') +} + +await prisma.user.create({ + data: { + email, + email_encrypted: eqlPayload({ + plaintext: email, + table: 'users', + column: 'email_encrypted', + }), + }, +}) + +console.log( + "[INFO] You've inserted a new user with an encrypted email from the plaintext", + email, +) +process.exit(0) diff --git a/javascript/apps/prisma/src/select.ts b/javascript/apps/prisma/src/select.ts new file mode 100644 index 00000000..a4f43dff --- /dev/null +++ b/javascript/apps/prisma/src/select.ts @@ -0,0 +1,16 @@ +import { prisma } from './db' + +const allUsers = await prisma.user.findMany() + +console.log('[INFO] All emails have been decrypted by CipherStash Proxy') +console.log( + 'Emails:', + JSON.stringify( + allUsers.map((row) => row.email_encrypted?.p), + null, + 2, + ), +) + +await prisma.$disconnect() +process.exit(0) diff --git a/javascript/apps/prisma/tsconfig.json b/javascript/apps/prisma/tsconfig.json new file mode 100644 index 00000000..4e39a25d --- /dev/null +++ b/javascript/apps/prisma/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + + // For Prisma custom types on JSON payloads + "typeRoots": ["../../node_modules/@types/", "./types"] + } +} diff --git a/javascript/apps/prisma/types.d.ts b/javascript/apps/prisma/types.d.ts new file mode 100644 index 00000000..7e43d89f --- /dev/null +++ b/javascript/apps/prisma/types.d.ts @@ -0,0 +1,7 @@ +import type { CsEncryptedV1Schema } from '@cipherstash/eql' + +declare global { + namespace PrismaJson { + type CsEncryptedType = CsEncryptedV1Schema + } +} diff --git a/javascript/bun.lockb b/javascript/bun.lockb index eb19c740..955cddb0 100755 Binary files a/javascript/bun.lockb and b/javascript/bun.lockb differ diff --git a/src/cs_encrypted_v1.schema.json b/src/cs_encrypted_v1.schema.json index 53b586b9..955e5744 100644 --- a/src/cs_encrypted_v1.schema.json +++ b/src/cs_encrypted_v1.schema.json @@ -1,93 +1,93 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "s": { - "title": "Schema version", - "description": "The schema version of this json document", - "type": "integer", - "enum": [1] - }, - "v": { - "title": "Configuration version", - "type": "integer" - }, - "k": { - "title": "kind", - "type": "string", - "enum": ["pt", "ct", "en"] - }, - "i": { - "title": "ident", - "type": "object", - "properties": { - "t": { - "title": "table", - "type": "string", - "pattern": "^[a-zA-Z_]{1}[0-9a-zA-Z_]*$" - }, - "c": { - "title": "column", - "type": "string", - "pattern": "^[a-zA-Z_]{1}[0-9a-zA-Z_]*$" - } - }, - "required": ["t", "c"] - }, - "p": { - "title": "plaintext", - "type": "string" - }, - "c": { - "title": "ciphertext", - "type": "string" - }, - "u.1": { - "title": "unique index", - "type": "string" - }, - "o.1": { - "title": "ore index", - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - }, - "m.1": { - "title": "match index", - "type": "array", - "minItems": 1, - "items": { - "type": "number" - } - } - }, - "anyOf": [ - { - "properties": { - "k": { - "const": "pt" - } - }, - "required": ["p"] - }, - { - "properties": { - "k": { - "const": "ct" - } - }, - "required": ["c"] - }, - { - "properties": { - "k": { - "const": "en" - } - }, - "required": ["c", "p"] - } - ], - "required": ["v", "k", "e"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "s": { + "title": "Schema version", + "description": "The schema version of this json document", + "type": "integer", + "enum": [1] + }, + "v": { + "title": "Configuration version", + "type": "integer" + }, + "k": { + "title": "kind", + "type": "string", + "enum": ["pt", "ct", "en"] + }, + "i": { + "title": "ident", + "type": "object", + "properties": { + "t": { + "title": "table", + "type": "string", + "pattern": "^[a-zA-Z_]{1}[0-9a-zA-Z_]*$" + }, + "c": { + "title": "column", + "type": "string", + "pattern": "^[a-zA-Z_]{1}[0-9a-zA-Z_]*$" + } + }, + "required": ["t", "c"] + }, + "p": { + "title": "plaintext", + "type": "string" + }, + "c": { + "title": "ciphertext", + "type": "string" + }, + "u.1": { + "title": "unique index", + "type": "string" + }, + "o.1": { + "title": "ore index", + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "m.1": { + "title": "match index", + "type": "array", + "minItems": 1, + "items": { + "type": "number" + } + } + }, + "anyOf": [ + { + "properties": { + "k": { + "const": "pt" + } + }, + "required": ["p"] + }, + { + "properties": { + "k": { + "const": "ct" + } + }, + "required": ["c"] + }, + { + "properties": { + "k": { + "const": "en" + } + }, + "required": ["c", "p"] + } + ], + "required": ["v", "k", "e"] }