From 666875d82a0610f6d6ef98560d9a2bf448f21256 Mon Sep 17 00:00:00 2001 From: CJ Brewer Date: Wed, 9 Oct 2024 13:59:56 -0600 Subject: [PATCH] feat(js): add where examples --- cipherstash/dataset.yml | 21 +++++++++ cipherstash/start.sh | 2 +- javascript/apps/drizzle/src/insert.ts | 40 ++++++++-------- javascript/apps/drizzle/src/schema.ts | 30 ++++++------ javascript/apps/drizzle/src/select.ts | 48 ++++++++++---------- javascript/apps/prisma/src/db.ts | 38 +++++++++++++++- javascript/apps/prisma/src/insert.ts | 38 ++++++++-------- javascript/apps/prisma/src/select.ts | 41 ++++++++++++----- javascript/apps/prisma/types.d.ts | 8 ++-- javascript/packages/eql/src/drizzle/index.ts | 40 ++++++++-------- 10 files changed, 189 insertions(+), 117 deletions(-) diff --git a/cipherstash/dataset.yml b/cipherstash/dataset.yml index 8bd2e83c..4ff94d04 100644 --- a/cipherstash/dataset.yml +++ b/cipherstash/dataset.yml @@ -20,3 +20,24 @@ tables: kind: ore - version: 1 kind: unique + - path: User + fields: + - name: email_encrypted + in_place: false + mode: plaintext-duplicate + cast_type: utf8-str + indexes: + - version: 1 + kind: match + tokenizer: + kind: ngram + token_length: 3 + token_filters: + - kind: downcase + k: 6 + m: 2048 + include_original: true + - version: 1 + kind: ore + - version: 1 + kind: unique diff --git a/cipherstash/start.sh b/cipherstash/start.sh index 884307f8..b4764d84 100755 --- a/cipherstash/start.sh +++ b/cipherstash/start.sh @@ -1,3 +1,3 @@ #!/bin/bash -docker run -p 6432:6432 -e CS_STATEMENT_HANDLER=mylittleproxy -v $(pwd)/cipherstash-proxy.toml:/etc/cipherstash-proxy/cipherstash-proxy.toml cipherstash/cipherstash-proxy:cipherstash-proxy-v0.0.25 \ No newline at end of file +docker run -p 6432:6432 -e CS_STATEMENT_HANDLER=mylittleproxy -e LOG_LEVEL=debug -v $(pwd)/cipherstash-proxy.toml:/etc/cipherstash-proxy/cipherstash-proxy.toml cipherstash/cipherstash-proxy:cipherstash-proxy-v0.0.25 \ No newline at end of file diff --git a/javascript/apps/drizzle/src/insert.ts b/javascript/apps/drizzle/src/insert.ts index f99d2768..ee1f1fa0 100644 --- a/javascript/apps/drizzle/src/insert.ts +++ b/javascript/apps/drizzle/src/insert.ts @@ -1,28 +1,28 @@ -import { getEmailArg } from '@cipherstash/utils' -import { eqlPayload } from '@cipherstash/eql' -import { db } from './db' -import { users } from './schema' +import { getEmailArg } from "@cipherstash/utils"; +import { eqlPayload } from "@cipherstash/eql"; +import { db } from "./db"; +import { users } from "./schema"; const email = getEmailArg({ - required: true, -}) + required: true, +}); const sql = db.insert(users).values({ - email: email, - email_encrypted: eqlPayload({ - plaintext: email, - table: 'users', - column: 'email_encrypted', - }), -}) + email: email, + email_encrypted: eqlPayload({ + plaintext: email, + table: "users", + column: "email_encrypted", + }), +}); -const sqlResult = sql.toSQL() -console.log('[INFO] SQL statement:', sqlResult) +const sqlResult = sql.toSQL(); +console.log("[INFO] SQL statement:", sqlResult); -await sql.execute() +await sql.execute(); console.log( - "[INFO] You've inserted a new user with an encrypted email from the plaintext", - email, -) + "[INFO] You've inserted a new user with an encrypted email from the plaintext", + email, +); -process.exit(0) +process.exit(0); diff --git a/javascript/apps/drizzle/src/schema.ts b/javascript/apps/drizzle/src/schema.ts index 6ffb02ff..35598498 100644 --- a/javascript/apps/drizzle/src/schema.ts +++ b/javascript/apps/drizzle/src/schema.ts @@ -1,18 +1,18 @@ -import type { CsEncryptedV1Schema } from '@cipherstash/eql' -import { customType, pgTable, serial, varchar } from 'drizzle-orm/pg-core' +import type { CsEncryptedV1Schema } from "@cipherstash/eql"; +import { customType, pgTable, serial, varchar } from "drizzle-orm/pg-core"; const cs_encrypted_v1 = (name: string) => - customType<{ data: TData; driverData: string }>({ - dataType() { - return 'cs_encrypted_v1' - }, - toDriver(value: TData): string { - return JSON.stringify(value) - }, - })(name) + customType<{ data: TData; driverData: string }>({ + dataType() { + return "cs_encrypted_v1"; + }, + toDriver(value: TData): string { + return JSON.stringify(value); + }, + })(name); -export const users = pgTable('users', { - id: serial('id').primaryKey(), - email: varchar('email').unique(), - email_encrypted: cs_encrypted_v1('email_encrypted'), -}) +export const users = pgTable("users", { + id: serial("id").primaryKey(), + email: varchar("email").unique(), + email_encrypted: cs_encrypted_v1("email_encrypted"), +}); diff --git a/javascript/apps/drizzle/src/select.ts b/javascript/apps/drizzle/src/select.ts index f293ae8e..b5830735 100644 --- a/javascript/apps/drizzle/src/select.ts +++ b/javascript/apps/drizzle/src/select.ts @@ -1,35 +1,35 @@ -import { getEmailArg } from '@cipherstash/utils' -import { cs_match_v1 } from '@cipherstash/eql/drizzle' -import { db } from './db' -import { users } from './schema' +import { getEmailArg } from "@cipherstash/utils"; +import { cs_match_v1 } from "@cipherstash/eql/drizzle"; +import { getPlaintext } from "@cipherstash/eql"; +import { db } from "./db"; +import { users } from "./schema"; const email = getEmailArg({ - required: false, -}) + required: false, +}); const sql = db - .select({ - email: users.email_encrypted, - }) - .from(users) + .select({ + email: users.email_encrypted, + }) + .from(users); if (email) { - sql.where(cs_match_v1(users, users.email_encrypted, email)) + sql.where(cs_match_v1(users, users.email_encrypted, email)); } -const sqlResult = sql.toSQL() -console.log('[INFO] SQL statement:', sqlResult) +const sqlResult = sql.toSQL(); +console.log("[INFO] SQL statement:", sqlResult); -const data = await sql.execute() -console.log('[INFO] All emails have been decrypted by CipherStash Proxy') +const data = await sql.execute(); +console.log("[INFO] All emails have been decrypted by CipherStash Proxy"); console.log( - 'Emails:', - JSON.stringify( - // data.map((row) => getPlaintext(row.email_encrypted)), - data, - null, - 2, - ), -) + "Emails:", + JSON.stringify( + data.map((row) => getPlaintext(row.email)), + null, + 2, + ), +); -process.exit(0) +process.exit(0); diff --git a/javascript/apps/prisma/src/db.ts b/javascript/apps/prisma/src/db.ts index 152a8ba4..0931de88 100644 --- a/javascript/apps/prisma/src/db.ts +++ b/javascript/apps/prisma/src/db.ts @@ -1,2 +1,36 @@ -import { PrismaClient } from '@prisma/client' -export const prisma = new PrismaClient() +import { eqlPayload } from "@cipherstash/eql"; +import { PrismaClient, Prisma } from "@prisma/client"; + +// TODO: Fix dynamic type of the whereEncrypted method +export const prisma = new PrismaClient().$extends({ + model: { + $allModels: { + async whereEncrypted( + this: T, + column: string, + plaintext: string, + ): Promise { + const context = Prisma.getExtensionContext(this); + const tableName = context.$name ?? ""; + + const result = (await prisma.$queryRaw`SELECT current_schema()`) as [ + { current_schema: string }, + ]; + const schema = result[0].current_schema; + + const payload = JSON.stringify( + eqlPayload({ + plaintext, + table: tableName, + column, + }), + ); + + // TODO: Fix Prisma.raw to prevent SQL injection + return prisma.$queryRaw< + T[] + >`SELECT * FROM "${Prisma.raw(schema)}"."${Prisma.raw(tableName)}" WHERE cs_match_v1(${Prisma.raw(column)}) @> cs_match_v1('${Prisma.raw(payload)}')`; + }, + }, + }, +}); diff --git a/javascript/apps/prisma/src/insert.ts b/javascript/apps/prisma/src/insert.ts index 45728606..db9b06be 100644 --- a/javascript/apps/prisma/src/insert.ts +++ b/javascript/apps/prisma/src/insert.ts @@ -1,26 +1,26 @@ -import { getEmailArg } from '@cipherstash/utils' -import { eqlPayload } from '@cipherstash/eql' -import { prisma } from './db' +import { getEmailArg } from "@cipherstash/utils"; +import { eqlPayload } from "@cipherstash/eql"; +import { prisma } from "./db"; const email = getEmailArg({ - required: true, -}) + required: true, +}); await prisma.user.create({ - data: { - email: email ?? 'test@test.com', - email_encrypted: eqlPayload({ - plaintext: email, - table: 'users', - column: 'email_encrypted', - }), - }, -}) + data: { + email: email ?? "test@test.com", + 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, -) + "[INFO] You've inserted a new user with an encrypted email from the plaintext", + email, +); -await prisma.$disconnect() -process.exit(0) +await prisma.$disconnect(); +process.exit(0); diff --git a/javascript/apps/prisma/src/select.ts b/javascript/apps/prisma/src/select.ts index a4f43dff..ad45ec51 100644 --- a/javascript/apps/prisma/src/select.ts +++ b/javascript/apps/prisma/src/select.ts @@ -1,16 +1,33 @@ -import { prisma } from './db' +import { getPlaintext } from "@cipherstash/eql"; +import { prisma } from "./db"; +import { getEmailArg } from "@cipherstash/utils"; +import type { User } from "@prisma/client"; -const allUsers = await prisma.user.findMany() +const email = getEmailArg({ + required: false, +}); -console.log('[INFO] All emails have been decrypted by CipherStash Proxy') +let users: User[]; + +if (email) { + // TODO: Fix dynamic type of the whereEncrypted method + users = (await prisma.user.whereEncrypted( + "email_encrypted", + email, + )) as unknown as User[]; +} else { + users = 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, - ), -) + "Emails:", + JSON.stringify( + users.map((row) => getPlaintext(row.email_encrypted)), + null, + 2, + ), +); -await prisma.$disconnect() -process.exit(0) +await prisma.$disconnect(); +process.exit(0); diff --git a/javascript/apps/prisma/types.d.ts b/javascript/apps/prisma/types.d.ts index 7e43d89f..1fc5a7bd 100644 --- a/javascript/apps/prisma/types.d.ts +++ b/javascript/apps/prisma/types.d.ts @@ -1,7 +1,7 @@ -import type { CsEncryptedV1Schema } from '@cipherstash/eql' +import type { CsEncryptedV1Schema } from "@cipherstash/eql"; declare global { - namespace PrismaJson { - type CsEncryptedType = CsEncryptedV1Schema - } + namespace PrismaJson { + type CsEncryptedType = CsEncryptedV1Schema; + } } diff --git a/javascript/packages/eql/src/drizzle/index.ts b/javascript/packages/eql/src/drizzle/index.ts index 01e4fa8d..58faeedd 100644 --- a/javascript/packages/eql/src/drizzle/index.ts +++ b/javascript/packages/eql/src/drizzle/index.ts @@ -1,26 +1,26 @@ -import { sql } from 'drizzle-orm' +import { sql } from "drizzle-orm"; import { - type PgTable, - getTableConfig, - type PgColumn, -} from 'drizzle-orm/pg-core' -import { eqlPayload } from '../' + type PgTable, + getTableConfig, + type PgColumn, +} from "drizzle-orm/pg-core"; +import { eqlPayload } from "../"; export const cs_match_v1 = ( - table: PgTable, - column: PgColumn, - plaintext: string, + table: PgTable, + column: PgColumn, + plaintext: string, ) => { - const tableName = getTableConfig(table)?.name - const columnName = column.name + const tableName = getTableConfig(table)?.name; + const columnName = column.name; - const payload = JSON.stringify( - eqlPayload({ - plaintext, - table: tableName, - column: columnName, - }), - ) + const payload = JSON.stringify( + eqlPayload({ + plaintext, + table: tableName, + column: columnName, + }), + ); - return sql`cs_match_v1(${column}) @> cs_match_v1(${payload})` -} + return sql`cs_match_v1(${column}) @> cs_match_v1(${payload})`; +};