Skip to content

Commit

Permalink
Support web encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Jan 24, 2025
1 parent a7e0b43 commit ad65733
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 28 deletions.
2 changes: 2 additions & 0 deletions demos/supabase-todolist/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
Expand Down
20 changes: 10 additions & 10 deletions demos/supabase-todolist/macos/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- powersync-sqlite-core (0.3.4)
- powersync-sqlite-core (0.3.9)
- powersync_flutter_libs (0.0.1):
- FlutterMacOS
- powersync-sqlite-core (~> 0.3.4)
- powersync-sqlite-core (~> 0.3.8)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
Expand Down Expand Up @@ -64,16 +64,16 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos

SPEC CHECKSUMS:
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
powersync-sqlite-core: d029aa444d33acbb05b47f9f9757b2650578e2d3
powersync_flutter_libs: 44829eda70d4f87c9271e963a54126ce19408d7c
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
powersync-sqlite-core: 7515d321eb8e3c08b5259cdadb9d19b1876fe13a
powersync_flutter_libs: 330d8309223a121ec15a7334d9edc105053e5f82
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
sqlite3_flutter_libs: 03311aede9d32fb2d24e32bebb8cd01c3b2e6239
url_launcher_macos: de10e46d8d8b9e3a7b8a133e8de92b104379f05e

PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
6 changes: 5 additions & 1 deletion demos/supabase-todolist/macos/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Cocoa
import FlutterMacOS

@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}

override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
9 changes: 6 additions & 3 deletions packages/powersync_core/lib/src/open_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// To conditionally export an implementation for either web or "native" platforms
// The sqlite library uses dart:ffi which is not supported on web

export './open_factory/open_factory_stub.dart'
export 'open_factory/abstract_powersync_open_factory.dart'
show powerSyncDefaultSqliteOptions;

export 'open_factory/open_factory_stub.dart'
// ignore: uri_does_not_exist
if (dart.library.io) './open_factory/native/native_open_factory.dart'
if (dart.library.io) 'open_factory/native/native_open_factory.dart'
// ignore: uri_does_not_exist
if (dart.library.html) './open_factory/web/web_open_factory.dart';
if (dart.library.html) 'open_factory/web/web_open_factory.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import '../../web/worker_utils.dart';

/// Web implementation for [AbstractPowerSyncOpenFactory]
class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
implements WebSqliteOpenFactory {
with WebSqliteOpenFactory {
PowerSyncOpenFactory({
required super.path,
super.sqliteOptions,
Expand All @@ -26,6 +26,12 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
);
}

@override
Future<ConnectToRecommendedResult> connectToWorker(
WebSqlite sqlite, String name) {
return sqlite.connectToRecommended(name);
}

@override
void enableExtension() {
// No op for web
Expand All @@ -34,11 +40,11 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
@override
Future<SqliteConnection> openConnection(SqliteOpenOptions options) async {
var conn = await super.openConnection(options);
for (final statement in super.pragmaStatements(options)) {
for (final statement in pragmaStatements(options)) {
await conn.execute(statement);
}

return super.openConnection(options);
return conn;
}

@override
Expand Down
31 changes: 28 additions & 3 deletions packages/powersync_core/lib/src/web/worker_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,45 @@ import 'package:uuid/uuid.dart';

final class PowerSyncAsyncSqliteController extends AsyncSqliteController {
@override
Future<WorkerDatabase> openDatabase(
WasmSqlite3 sqlite3, String path, String vfs) async {
final asyncDb = await super.openDatabase(sqlite3, path, vfs);
Future<WorkerDatabase> openDatabase(WasmSqlite3 sqlite3, String path,
String vfs, JSAny? additionalData) async {
final asyncDb =
await super.openDatabase(sqlite3, path, vfs, additionalData);
setupPowerSyncDatabase(asyncDb.database);
return asyncDb;
}

@override
CommonDatabase openUnderlying(
WasmSqlite3 sqlite3, String path, String vfs, JSAny? additionalData) {
final options = additionalData == null
? null
: additionalData as PowerSyncAdditionalOpenOptions;
if (options != null && options.useMultipleCiphersVfs) {
vfs = 'multipleciphers-$vfs';
}

return sqlite3.open(path, vfs: vfs);
}

@override
Future<JSAny?> handleCustomRequest(
ClientConnection connection, JSAny? request) {
throw UnimplementedError();
}
}

@JS()
@anonymous
extension type PowerSyncAdditionalOpenOptions._(JSObject _)
implements JSObject {
external factory PowerSyncAdditionalOpenOptions({
required bool useMultipleCiphersVfs,
});

external bool get useMultipleCiphersVfs;
}

// Registers custom SQLite functions for the SQLite connection
void setupPowerSyncDatabase(CommonDatabase database) {
setupCommonDBFunctions(database);
Expand Down
6 changes: 6 additions & 0 deletions packages/powersync_core/lib/web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Internal options used to customize how PowerSync opens databases on the web.
library;

export 'src/web/worker_utils.dart' show PowerSyncAdditionalOpenOptions;
export 'package:sqlite_async/sqlite3_web.dart';
export 'package:sqlite_async/web.dart';
4 changes: 4 additions & 0 deletions packages/powersync_sqlcipher/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.1.4-dev

- Support encryption on the web (through SQLite3 Multiple Ciphers).

## 0.1.3

- Fix `statusStream` emitting the same sync status multiple times.
Expand Down
24 changes: 23 additions & 1 deletion packages/powersync_sqlcipher/lib/powersync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,27 @@
/// Use [PowerSyncSQLCipherOpenFactory] to open an encrypted database.
library;

export 'src/sqlcipher.dart';
import 'package:powersync_core/sqlite_async.dart';
import 'package:powersync_sqlcipher/powersync.dart';

export 'package:powersync_core/powersync_core.dart';

import 'src/stub.dart'
if (dart.library.js_interop) 'src/web_encryption.dart'
if (dart.library.ffi) 'src/sqlcipher.dart';

/// A factory for opening a database with SQLCipher encryption.
/// An encryption [key] is required to open the database.
abstract base class PowerSyncSQLCipherOpenFactory extends PowerSyncOpenFactory {
PowerSyncSQLCipherOpenFactory.internal(
{required super.path, required this.key, super.sqliteOptions});

factory PowerSyncSQLCipherOpenFactory(
{required String path,
required String key,
SqliteOptions sqliteOptions = powerSyncDefaultSqliteOptions}) {
return cipherFactory(path: path, key: key, options: sqliteOptions);
}

final String key;
}
22 changes: 15 additions & 7 deletions packages/powersync_sqlcipher/lib/src/sqlcipher.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import 'package:powersync_core/powersync_core.dart';
import 'package:powersync_core/sqlite3_common.dart';
import 'package:powersync_core/sqlite3_open.dart' as sqlite3_open;
import 'package:powersync_core/sqlite_async.dart';
import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart';

/// A factory for opening a database with SQLCipher encryption.
/// An encryption [key] is required to open the database.
class PowerSyncSQLCipherOpenFactory extends PowerSyncOpenFactory {
PowerSyncSQLCipherOpenFactory(
{required super.path, required this.key, super.sqliteOptions});
import '../powersync.dart';

final String key;
final class _NativeCipherOpenFactory extends PowerSyncSQLCipherOpenFactory {
_NativeCipherOpenFactory({
required super.path,
required super.key,
super.sqliteOptions,
}) : super.internal();

@override
List<String> pragmaStatements(SqliteOpenOptions options) {
Expand All @@ -37,3 +37,11 @@ class PowerSyncSQLCipherOpenFactory extends PowerSyncOpenFactory {
return db;
}
}

PowerSyncSQLCipherOpenFactory cipherFactory({
required String path,
required String key,
required SqliteOptions options,
}) {
return _NativeCipherOpenFactory(path: path, key: key, sqliteOptions: options);
}
10 changes: 10 additions & 0 deletions packages/powersync_sqlcipher/lib/src/stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import '../powersync.dart';
import '../sqlite_async.dart';

PowerSyncSQLCipherOpenFactory cipherFactory({
required String path,
required String key,
required SqliteOptions options,
}) {
throw UnsupportedError('Unsupported platform for powersync_sqlcipher');
}
43 changes: 43 additions & 0 deletions packages/powersync_sqlcipher/lib/src/web_encryption.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:powersync_core/sqlite_async.dart';
import 'package:powersync_core/web.dart';

import '../powersync.dart';

final class _WebEncryptionFactory extends PowerSyncSQLCipherOpenFactory
with WebSqliteOpenFactory {
_WebEncryptionFactory({
required super.path,
required super.key,
super.sqliteOptions,
}) : super.internal();

@override
List<String> pragmaStatements(SqliteOpenOptions options) {
final basePragmaStatements = super.pragmaStatements(options);
return [
// Set the encryption key as the first statement
"PRAGMA KEY = ${quoteString(key)}",
// Include the default statements afterwards
for (var statement in basePragmaStatements) statement
];
}

@override
Future<ConnectToRecommendedResult> connectToWorker(
WebSqlite sqlite, String name) async {
return sqlite.connectToRecommended(
name,
additionalOptions: PowerSyncAdditionalOpenOptions(
useMultipleCiphersVfs: true,
),
);
}
}

PowerSyncSQLCipherOpenFactory cipherFactory({
required String path,
required String key,
required SqliteOptions options,
}) {
return _WebEncryptionFactory(path: path, key: key, sqliteOptions: options);
}

0 comments on commit ad65733

Please sign in to comment.