From 10d8451acb5c4dc77a5d5e40a2f9e6bf1bb0e3a0 Mon Sep 17 00:00:00 2001 From: Mugi Khan Date: Tue, 26 Nov 2024 17:53:21 +0200 Subject: [PATCH] Setup encryption by passing key --- demos/react-supabase-todolist/package.json | 2 +- packages/web/package.json | 4 +- packages/web/src/db/PowerSyncDatabase.ts | 25 +++++++++--- .../adapters/wa-sqlite/WASQLiteDBAdapter.ts | 8 +++- .../adapters/wa-sqlite/WASQLiteOpenFactory.ts | 3 +- packages/web/src/db/adapters/web-sql-flags.ts | 6 +++ packages/web/src/shared/open-db.ts | 39 ++++++++++--------- pnpm-lock.yaml | 28 +++++++++---- 8 files changed, 80 insertions(+), 35 deletions(-) diff --git a/demos/react-supabase-todolist/package.json b/demos/react-supabase-todolist/package.json index 33342be5..976d380e 100644 --- a/demos/react-supabase-todolist/package.json +++ b/demos/react-supabase-todolist/package.json @@ -13,7 +13,7 @@ "@powersync/web": "workspace:*", "@emotion/react": "11.11.4", "@emotion/styled": "11.11.5", - "@journeyapps/wa-sqlite": "0.0.0-dev-20241126093237", + "@journeyapps/wa-sqlite": "0.0.0-dev-20241126145151", "@mui/icons-material": "^5.15.12", "@mui/material": "^5.15.12", "@mui/x-data-grid": "^6.19.6", diff --git a/packages/web/package.json b/packages/web/package.json index f615377f..33404b95 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -60,7 +60,7 @@ "author": "JOURNEYAPPS", "license": "Apache-2.0", "peerDependencies": { - "@journeyapps/wa-sqlite": "0.0.0-dev-20241126093237", + "@journeyapps/wa-sqlite": "0.0.0-dev-20241126145151", "@powersync/common": "workspace:^1.21.0" }, "dependencies": { @@ -72,7 +72,7 @@ "js-logger": "^1.6.1" }, "devDependencies": { - "@journeyapps/wa-sqlite": "0.0.0-dev-20241126093237", + "@journeyapps/wa-sqlite": "0.0.0-dev-20241126145151", "@types/uuid": "^9.0.6", "@vitest/browser": "^2.1.4", "crypto-browserify": "^3.12.0", diff --git a/packages/web/src/db/PowerSyncDatabase.ts b/packages/web/src/db/PowerSyncDatabase.ts index 56a3c6dd..24e61300 100644 --- a/packages/web/src/db/PowerSyncDatabase.ts +++ b/packages/web/src/db/PowerSyncDatabase.ts @@ -55,17 +55,31 @@ type WithWebSyncOptions = Base & { sync?: WebSyncOptions; }; +export interface WebEncryptionOptions { + /** + * Encryption key for the database. + * If set, the database will be encrypted using Multiple Ciphers. + */ + encryptionKey?: string; +} + +type WithWebEncryptionOptions = Base & { + encryptionKey?: string; +}; + export type WebPowerSyncDatabaseOptionsWithAdapter = WithWebSyncOptions< - WithWebFlags + WithWebFlags> >; export type WebPowerSyncDatabaseOptionsWithOpenFactory = WithWebSyncOptions< - WithWebFlags + WithWebFlags> >; export type WebPowerSyncDatabaseOptionsWithSettings = WithWebSyncOptions< - WithWebFlags + WithWebFlags> >; -export type WebPowerSyncDatabaseOptions = WithWebSyncOptions>; +export type WebPowerSyncDatabaseOptions = WithWebSyncOptions< + WithWebFlags> +>; export const DEFAULT_POWERSYNC_FLAGS: Required = { ...DEFAULT_WEB_SQL_FLAGS, @@ -120,7 +134,8 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase { protected openDBAdapter(options: WebPowerSyncDatabaseOptionsWithSettings): DBAdapter { const defaultFactory = new WASQLiteOpenFactory({ ...options.database, - flags: resolveWebPowerSyncFlags(options.flags) + flags: resolveWebPowerSyncFlags(options.flags), + encryptionKey: options.encryptionKey }); return defaultFactory.openDB(); } diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts index d2d39e0b..b7104ea2 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts @@ -32,6 +32,12 @@ export interface WASQLiteDBAdapterOptions extends Omit Worker | SharedWorker); + + /** + * Encryption key for the database. + * If set, the database will be encrypted using SQLCipher. + */ + encryptionKey?: string; } /** @@ -111,7 +117,7 @@ export class WASQLiteDBAdapter extends BaseObserver implement return; } - this.methods = await _openDB(this.options.dbFilename, { useWebWorker: false }); + this.methods = await _openDB(this.options.dbFilename, this.options.encryptionKey, { useWebWorker: false }); this.methods.registerOnTableChange((event) => { this.iterateListeners((cb) => cb.tablesUpdated?.(event)); }); diff --git a/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts b/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts index 039e0f83..98b8eadf 100644 --- a/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +++ b/packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts @@ -9,7 +9,8 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory { protected openAdapter(): DBAdapter { return new WASQLiteDBAdapter({ ...this.options, - flags: this.resolvedFlags + flags: this.resolvedFlags, + encryptionKey: this.options.encryptionKey }); } } diff --git a/packages/web/src/db/adapters/web-sql-flags.ts b/packages/web/src/db/adapters/web-sql-flags.ts index dcc62c6b..8b36dae7 100644 --- a/packages/web/src/db/adapters/web-sql-flags.ts +++ b/packages/web/src/db/adapters/web-sql-flags.ts @@ -55,6 +55,12 @@ export interface WebSQLOpenFactoryOptions extends SQLOpenOptions { * or a factory method that returns a worker. */ worker?: string | URL | ((options: ResolvedWebSQLOpenOptions) => Worker | SharedWorker); + + /** + * Encryption key for the database. + * If set, the database will be encrypted using Multiple Ciphers. + */ + encryptionKey?: string; } export function isServerSide() { diff --git a/packages/web/src/shared/open-db.ts b/packages/web/src/shared/open-db.ts index efd31c16..0e494834 100644 --- a/packages/web/src/shared/open-db.ts +++ b/packages/web/src/shared/open-db.ts @@ -9,45 +9,48 @@ let nextId = 1; export async function _openDB( dbFileName: string, + encryptionKey?: string, options: { useWebWorker: boolean } = { useWebWorker: true } ): Promise { - const { default: moduleFactory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs'); + let moduleFactory; + if (encryptionKey) { + console.log('Using encrypted database'); + moduleFactory = (await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs')).default; + } else { + console.log('Using unencrypted database'); + moduleFactory = (await import('@journeyapps/wa-sqlite/dist/wa-sqlite-async.mjs')).default; + } + // const { default: moduleFactory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs'); const module = await moduleFactory(); const sqlite3 = SQLite.Factory(module); - console.log('sqlite3', sqlite3); /** * Register the PowerSync core SQLite extension */ module.ccall('powersync_init_static', 'int', []); - console.log('powerync init'); - const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js'); - console.log('DbFileName', dbFileName); // @ts-expect-error The types for this static method are missing upstream const vfs = await IDBBatchAtomicVFS.create(dbFileName, module, { lockPolicy: 'exclusive' }); - - console.log('vfs before', vfs); sqlite3.vfs_register(vfs, true); - const createResult = module.ccall('sqlite3mc_vfs_create', 'int', ['string', 'int'], [dbFileName, 1]); - console.log('result from creation', createResult); - if (createResult !== 0) { - throw new Error('Failed to create multiple cipher vfs'); + if (encryptionKey) { + const createResult = module.ccall('sqlite3mc_vfs_create', 'int', ['string', 'int'], [dbFileName, 1]); + if (createResult !== 0) { + throw new Error('Failed to create multiple cipher vfs'); + } } - console.log('vfs after', vfs); const db = await sqlite3.open_v2(dbFileName); - console.log('db', db); - // const pragma = await sqlite3.exec(db, 'PRAGMA key = "key"'); - // console.log('pragma', pragma); - // if (pragma !== SQLite.SQLITE_OK) { - // throw new Error('Failed to set key'); - // } + if (encryptionKey) { + const pragma = await sqlite3.exec(db, `PRAGMA key = "${encryptionKey}"`); + if (pragma !== SQLite.SQLITE_OK) { + throw new Error('Failed to set encryption key'); + } + } const statementMutex = new Mutex(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 748f8ad4..83b9e175 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1067,8 +1067,8 @@ importers: specifier: 11.11.5 version: 11.11.5(@emotion/react@11.11.4)(@types/react@18.3.11)(react@18.2.0) '@journeyapps/wa-sqlite': - specifier: 0.0.0-dev-20241126093237 - version: 0.0.0-dev-20241126093237 + specifier: 0.0.0-dev-20241126145151 + version: 0.0.0-dev-20241126145151 '@mui/icons-material': specifier: ^5.15.12 version: 5.16.7(@mui/material@5.16.7)(@types/react@18.3.11)(react@18.2.0) @@ -1900,8 +1900,8 @@ importers: version: 1.6.1 devDependencies: '@journeyapps/wa-sqlite': - specifier: 0.0.0-dev-20241126093237 - version: 0.0.0-dev-20241126093237 + specifier: 0.0.0-dev-20241126145151 + version: 0.0.0-dev-20241126145151 '@types/uuid': specifier: ^9.0.6 version: 9.0.8 @@ -10697,8 +10697,8 @@ packages: react-native: 0.74.5(@babel/core@7.24.5)(@babel/preset-env@7.25.7)(@types/react@18.2.79)(react@18.2.0) dev: false - /@journeyapps/wa-sqlite@0.0.0-dev-20241126093237: - resolution: {integrity: sha512-hLAEGlzXmfapXxnWUDTT0ClHuz+oxmAoR5pBESplB4EEAUMnyNeV595/IufvFQq3WqNZ+P/RXsaJYZqY0Ji2HA==} + /@journeyapps/wa-sqlite@0.0.0-dev-20241126145151: + resolution: {integrity: sha512-FwlF1Q/pMIHUXKjcDBkO3MGSimCIA21PwFmz29xII61+Ga0sRpLEx28WQYD7YTG6/8r68f5vLgB0SkD8nXAIag==} requiresBuild: true /@journeyapps/wa-sqlite@0.4.2: @@ -15367,6 +15367,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@swc/core-darwin-arm64@1.7.26: @@ -15384,6 +15385,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@swc/core-darwin-x64@1.7.26: @@ -15401,6 +15403,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-arm-gnueabihf@1.7.26: @@ -15418,6 +15421,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-arm64-gnu@1.7.26: @@ -15435,6 +15439,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-arm64-musl@1.7.26: @@ -15452,6 +15457,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-x64-gnu@1.7.26: @@ -15469,6 +15475,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@swc/core-linux-x64-musl@1.7.26: @@ -15486,6 +15493,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@swc/core-win32-arm64-msvc@1.7.26: @@ -15503,6 +15511,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@swc/core-win32-ia32-msvc@1.7.26: @@ -15520,6 +15529,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@swc/core-win32-x64-msvc@1.7.26: @@ -15554,6 +15564,7 @@ packages: '@swc/core-win32-arm64-msvc': 1.6.13 '@swc/core-win32-ia32-msvc': 1.6.13 '@swc/core-win32-x64-msvc': 1.6.13 + dev: true /@swc/core@1.7.26: resolution: {integrity: sha512-f5uYFf+TmMQyYIoxkn/evWhNGuUzC730dFwAKGwBVHHVoPyak1/GvJUm6i1SKl+2Hrj9oN0i3WSoWWZ4pgI8lw==} @@ -15594,6 +15605,7 @@ packages: resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} dependencies: '@swc/counter': 0.1.3 + dev: true /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} @@ -19745,7 +19757,7 @@ packages: '@babel/core': 7.24.5 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.95.0(@swc/core@1.6.13) + webpack: 5.95.0(webpack-cli@5.1.4) /babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} @@ -36166,6 +36178,7 @@ packages: serialize-javascript: 6.0.2 terser: 5.34.1 webpack: 5.95.0(@swc/core@1.6.13) + dev: true /terser-webpack-plugin@5.3.10(esbuild@0.23.0)(webpack@5.94.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -38737,6 +38750,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: true /webpack@5.95.0(webpack-cli@5.1.4): resolution: {integrity: sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==}