Skip to content

Commit a12bdeb

Browse files
committed
- Add encryption key parameter for web
- Dynamically load multiple cipher module factory
1 parent a2b02df commit a12bdeb

File tree

7 files changed

+18518
-23184
lines changed

7 files changed

+18518
-23184
lines changed

demos/react-supabase-todolist/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@powersync/web": "workspace:*",
1414
"@emotion/react": "11.11.4",
1515
"@emotion/styled": "11.11.5",
16-
"@journeyapps/wa-sqlite": "^1.0.0",
16+
"@journeyapps/wa-sqlite": "^1.1.1",
1717
"@mui/icons-material": "^5.15.12",
1818
"@mui/material": "^5.15.12",
1919
"@mui/x-data-grid": "^6.19.6",

packages/web/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"author": "JOURNEYAPPS",
6161
"license": "Apache-2.0",
6262
"peerDependencies": {
63-
"@journeyapps/wa-sqlite": "^1.0.0",
63+
"@journeyapps/wa-sqlite": "^1.1.1",
6464
"@powersync/common": "workspace:^1.22.0"
6565
},
6666
"dependencies": {
@@ -72,7 +72,7 @@
7272
"js-logger": "^1.6.1"
7373
},
7474
"devDependencies": {
75-
"@journeyapps/wa-sqlite": "^1.0.0",
75+
"@journeyapps/wa-sqlite": "^1.1.1",
7676
"@types/uuid": "^9.0.6",
7777
"@vitest/browser": "^2.1.4",
7878
"crypto-browserify": "^3.12.0",

packages/web/src/db/PowerSyncDatabase.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,35 @@ type WithWebSyncOptions<Base> = Base & {
5656
sync?: WebSyncOptions;
5757
};
5858

59+
export interface WebEncryptionOptions {
60+
/**
61+
* Encryption key for the database.
62+
* If set, the database will be encrypted using Multiple Ciphers.
63+
*/
64+
encryptionKey?: string;
65+
}
66+
67+
type WithWebEncryptionOptions<Base> = Base & {
68+
/**
69+
* Encryption key for the database.
70+
* If set, the database will be encrypted using Multiple Ciphers.
71+
*/
72+
encryptionKey?: string;
73+
};
74+
5975
export type WebPowerSyncDatabaseOptionsWithAdapter = WithWebSyncOptions<
60-
WithWebFlags<PowerSyncDatabaseOptionsWithDBAdapter>
76+
WithWebFlags<WithWebEncryptionOptions<PowerSyncDatabaseOptionsWithDBAdapter>>
6177
>;
6278
export type WebPowerSyncDatabaseOptionsWithOpenFactory = WithWebSyncOptions<
63-
WithWebFlags<PowerSyncDatabaseOptionsWithOpenFactory>
79+
WithWebFlags<WithWebEncryptionOptions<PowerSyncDatabaseOptionsWithOpenFactory>>
6480
>;
6581
export type WebPowerSyncDatabaseOptionsWithSettings = WithWebSyncOptions<
66-
WithWebFlags<PowerSyncDatabaseOptionsWithSettings>
82+
WithWebFlags<WithWebEncryptionOptions<PowerSyncDatabaseOptionsWithSettings>>
6783
>;
6884

69-
export type WebPowerSyncDatabaseOptions = WithWebSyncOptions<WithWebFlags<PowerSyncDatabaseOptions>>;
85+
export type WebPowerSyncDatabaseOptions = WithWebSyncOptions<
86+
WithWebFlags<WithWebEncryptionOptions<PowerSyncDatabaseOptions>>
87+
>;
7088

7189
export const DEFAULT_POWERSYNC_FLAGS: Required<WebPowerSyncFlags> = {
7290
...DEFAULT_WEB_SQL_FLAGS,
@@ -121,7 +139,8 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
121139
protected openDBAdapter(options: WebPowerSyncDatabaseOptionsWithSettings): DBAdapter {
122140
const defaultFactory = new WASQLiteOpenFactory({
123141
...options.database,
124-
flags: resolveWebPowerSyncFlags(options.flags)
142+
flags: resolveWebPowerSyncFlags(options.flags),
143+
encryptionKey: options.encryptionKey
125144
});
126145
return defaultFactory.openDB();
127146
}

packages/web/src/db/adapters/wa-sqlite/WASQLiteConnection.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type SQLiteModule = Parameters<typeof SQLite.Factory>[0];
3636
/**
3737
* @internal
3838
*/
39-
export type WASQLiteModuleFactoryOptions = { dbFileName: string };
39+
export type WASQLiteModuleFactoryOptions = { dbFileName: string; encryptionKey?: string };
4040

4141
/**
4242
* @internal
@@ -53,6 +53,14 @@ export const AsyncWASQLiteModuleFactory = async () => {
5353
return factory();
5454
};
5555

56+
/**
57+
* @internal
58+
*/
59+
export const MultiCipherAsyncWASQLiteModuleFactory = async () => {
60+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite-async.mjs');
61+
return factory();
62+
};
63+
5664
/**
5765
* @internal
5866
*/
@@ -61,12 +69,25 @@ export const SyncWASQLiteModuleFactory = async () => {
6169
return factory();
6270
};
6371

72+
/**
73+
* @internal
74+
*/
75+
export const MultiCipherSyncWASQLiteModuleFactory = async () => {
76+
const { default: factory } = await import('@journeyapps/wa-sqlite/dist/mc-wa-sqlite.mjs');
77+
return factory();
78+
};
79+
6480
/**
6581
* @internal
6682
*/
6783
export const DEFAULT_MODULE_FACTORIES = {
6884
[WASQLiteVFS.IDBBatchAtomicVFS]: async (options: WASQLiteModuleFactoryOptions) => {
69-
const module = await AsyncWASQLiteModuleFactory();
85+
let module;
86+
if (options.encryptionKey) {
87+
module = await MultiCipherAsyncWASQLiteModuleFactory();
88+
} else {
89+
module = await AsyncWASQLiteModuleFactory();
90+
}
7091
const { IDBBatchAtomicVFS } = await import('@journeyapps/wa-sqlite/src/examples/IDBBatchAtomicVFS.js');
7192
return {
7293
module,
@@ -75,7 +96,12 @@ export const DEFAULT_MODULE_FACTORIES = {
7596
};
7697
},
7798
[WASQLiteVFS.AccessHandlePoolVFS]: async (options: WASQLiteModuleFactoryOptions) => {
78-
const module = await SyncWASQLiteModuleFactory();
99+
let module;
100+
if (options.encryptionKey) {
101+
module = await MultiCipherSyncWASQLiteModuleFactory();
102+
} else {
103+
module = await SyncWASQLiteModuleFactory();
104+
}
79105
// @ts-expect-error The types for this static method are missing upstream
80106
const { AccessHandlePoolVFS } = await import('@journeyapps/wa-sqlite/src/examples/AccessHandlePoolVFS.js');
81107
return {
@@ -84,7 +110,12 @@ export const DEFAULT_MODULE_FACTORIES = {
84110
};
85111
},
86112
[WASQLiteVFS.OPFSCoopSyncVFS]: async (options: WASQLiteModuleFactoryOptions) => {
87-
const module = await SyncWASQLiteModuleFactory();
113+
let module;
114+
if (options.encryptionKey) {
115+
module = await MultiCipherSyncWASQLiteModuleFactory();
116+
} else {
117+
module = await SyncWASQLiteModuleFactory();
118+
}
88119
// @ts-expect-error The types for this static method are missing upstream
89120
const { OPFSCoopSyncVFS } = await import('@journeyapps/wa-sqlite/src/examples/OPFSCoopSyncVFS.js');
90121
return {
@@ -146,15 +177,35 @@ export class WASqliteConnection
146177
return this._dbP;
147178
}
148179

180+
protected async executeEncryptionPragma(): Promise<void> {
181+
if (this.options.encryptionKey && this._dbP) {
182+
await this.executeSingleStatement(`PRAGMA key = "${this.options.encryptionKey}"`);
183+
}
184+
return;
185+
}
186+
149187
protected async openSQLiteAPI(): Promise<SQLiteAPI> {
150-
const { module, vfs } = await this._moduleFactory({ dbFileName: this.options.dbFilename });
188+
const { module, vfs } = await this._moduleFactory({
189+
dbFileName: this.options.dbFilename,
190+
encryptionKey: this.options.encryptionKey
191+
});
151192
const sqlite3 = SQLite.Factory(module);
152193
sqlite3.vfs_register(vfs, true);
153194
/**
154195
* Register the PowerSync core SQLite extension
155196
*/
156197
module.ccall('powersync_init_static', 'int', []);
157198

199+
/**
200+
* Create the multiple cipher vfs if an encryption key is provided
201+
*/
202+
if (this.options.encryptionKey) {
203+
const createResult = module.ccall('sqlite3mc_vfs_create', 'int', ['string', 'int'], [this.options.dbFilename, 1]);
204+
if (createResult !== 0) {
205+
throw new Error('Failed to create multiple cipher vfs, Database encryption will not work');
206+
}
207+
}
208+
158209
return sqlite3;
159210
}
160211

@@ -182,6 +233,7 @@ export class WASqliteConnection
182233
await this.openDB();
183234
this.registerBroadcastListeners();
184235
await this.executeSingleStatement(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
236+
await this.executeEncryptionPragma();
185237

186238
this.sqliteAPI.update_hook(this.dbP, (updateType: number, dbName: string | null, tableName: string | null) => {
187239
if (!tableName) {

packages/web/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export interface WASQLiteDBAdapterOptions extends Omit<PowerSyncOpenFactoryOptio
2727

2828
vfs?: WASQLiteVFS;
2929
temporaryStorage?: TemporaryStorageOption;
30+
31+
/**
32+
* Encryption key for the database.
33+
* If set, the database will be encrypted using multiple-ciphers.
34+
*/
35+
encryptionKey?: string;
3036
}
3137

3238
/**

packages/web/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import { WASqliteConnection, WASQLiteVFS } from './WASQLiteConnection';
1010

1111
export interface WASQLiteOpenFactoryOptions extends WebSQLOpenFactoryOptions {
1212
vfs?: WASQLiteVFS;
13+
encryptionKey?: string;
1314
}
1415

1516
export interface ResolvedWASQLiteOpenFactoryOptions extends ResolvedWebSQLOpenOptions {
1617
vfs: WASQLiteVFS;
18+
encryptionKey?: string;
1719
}
1820
/**
1921
* Opens a SQLite connection using WA-SQLite.
@@ -39,7 +41,11 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
3941

4042
async openConnection(): Promise<AsyncDatabaseConnection> {
4143
const { enableMultiTabs, useWebWorker } = this.resolvedFlags;
42-
const { vfs = WASQLiteVFS.IDBBatchAtomicVFS, temporaryStorage = TemporaryStorageOption.MEMORY } = this.waOptions;
44+
const {
45+
vfs = WASQLiteVFS.IDBBatchAtomicVFS,
46+
temporaryStorage = TemporaryStorageOption.MEMORY,
47+
encryptionKey
48+
} = this.waOptions;
4349

4450
if (!enableMultiTabs) {
4551
this.logger.warn('Multiple tabs are not enabled in this browser');
@@ -67,7 +73,8 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
6773
dbFilename: this.options.dbFilename,
6874
vfs,
6975
temporaryStorage,
70-
flags: this.resolvedFlags
76+
flags: this.resolvedFlags,
77+
encryptionKey: encryptionKey
7178
}),
7279
identifier: this.options.dbFilename
7380
});
@@ -79,7 +86,8 @@ export class WASQLiteOpenFactory extends AbstractWebSQLOpenFactory {
7986
debugMode: this.options.debugMode,
8087
vfs,
8188
temporaryStorage,
82-
flags: this.resolvedFlags
89+
flags: this.resolvedFlags,
90+
encryptionKey: encryptionKey
8391
});
8492
}
8593
}

0 commit comments

Comments
 (0)