mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-12 12:50:58 +01:00
crdt_sync_flutter 0.6.1: keystore wrapper over dart open
Todo: fda71ed1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eebeb0b510
commit
33791da6ff
@ -2,14 +2,12 @@
|
||||
/// crdt-sync passphrase kept in the OS keystore (prompt-free after setup).
|
||||
library;
|
||||
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:crdt_sync/crdt_sync.dart';
|
||||
import 'package:crdt_sync_flutter/src/keystore.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
/// Keystore key holding the shared crdt-sync passphrase.
|
||||
const String kE2ePassphraseKey = 'crdt_sync_e2e_passphrase';
|
||||
const String kE2ePassphraseKey = kE2ePassphraseStorageKey;
|
||||
|
||||
/// Stores the passphrase this device derives its key from.
|
||||
Future<void> saveE2ePassphrase(
|
||||
@ -22,55 +20,15 @@ Future<void> clearE2ePassphrase({
|
||||
FlutterSecureStorage storage = kSecureStorage,
|
||||
}) => storage.delete(key: kE2ePassphraseKey);
|
||||
|
||||
/// Opens the encrypted store for [oldPrefix] (the app's former plaintext
|
||||
/// prefix; data lives on `E2eLayout.of(oldPrefix)`).
|
||||
///
|
||||
/// Fail closed: no passphrase, a wrong one, or an unreadable header returns
|
||||
/// [E2eLocked] and the caller must not sync (show an icon-only lock state).
|
||||
/// On success the old plaintext prefix is copied over once per call, which is
|
||||
/// idempotent and picks up writes from builds not yet migrated; the old
|
||||
/// prefix is never deleted here.
|
||||
Future<E2eState> openEncryptedStore({
|
||||
/// [openEncryptedStore] reading the passphrase from the OS keystore.
|
||||
Future<E2eState> openEncryptedStoreFromKeystore({
|
||||
required RemoteStore store,
|
||||
required String oldPrefix,
|
||||
FlutterSecureStorage storage = kSecureStorage,
|
||||
Argon2Params params = Argon2Params.recommended,
|
||||
}) async {
|
||||
final passphrase = await storage.read(key: kE2ePassphraseKey);
|
||||
if (passphrase == null || passphrase.isEmpty) {
|
||||
return E2eLocked(E2eLockReason.needsPassphrase);
|
||||
}
|
||||
final layout = E2eLayout.of(oldPrefix);
|
||||
final E2eKey key;
|
||||
try {
|
||||
key = await unlockE2e(
|
||||
store: store,
|
||||
pathPrefix: layout.prefix,
|
||||
passphrase: passphrase,
|
||||
params: params,
|
||||
e2ePath: layout.e2ePath,
|
||||
);
|
||||
} on E2eWrongPassphraseError {
|
||||
return E2eLocked(E2eLockReason.wrongKey);
|
||||
} on E2eHeaderError {
|
||||
return E2eLocked(E2eLockReason.badHeader);
|
||||
}
|
||||
try {
|
||||
await copyToEncrypted(
|
||||
store: store,
|
||||
key: key,
|
||||
fromPrefix: oldPrefix,
|
||||
layout: layout,
|
||||
);
|
||||
} on Object catch (error, stackTrace) {
|
||||
// The copy is retried next open; sync itself must not stop for it.
|
||||
log('e2e copy failed', level: 900, error: error, stackTrace: stackTrace);
|
||||
}
|
||||
return await openE2e(
|
||||
store: store,
|
||||
pathPrefix: layout.prefix,
|
||||
key: key,
|
||||
e2ePath: layout.e2ePath,
|
||||
revsPath: layout.revsPath,
|
||||
);
|
||||
}
|
||||
}) => openEncryptedStore(
|
||||
store: store,
|
||||
oldPrefix: oldPrefix,
|
||||
readPassphrase: () => storage.read(key: kE2ePassphraseKey),
|
||||
params: params,
|
||||
);
|
||||
|
||||
@ -61,11 +61,11 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: crdt_sync_dart
|
||||
ref: "crdt_sync_dart-v0.13.0"
|
||||
resolved-ref: "51e562a76460eb25202a92644b2f1d202dba8dc5"
|
||||
ref: "crdt_sync_dart-v0.13.1"
|
||||
resolved-ref: eebeb0b51053f160f163140141aba10a447ba1e8
|
||||
url: "https://github.com/kuhyx/utils"
|
||||
source: git
|
||||
version: "0.13.0"
|
||||
version: "0.13.1"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -4,7 +4,7 @@ description: >-
|
||||
persisted per-install device id, and a one-call bootstrap. Removes the
|
||||
~150 lines of firebase_backend.dart every consuming app was copying.
|
||||
publish_to: 'none'
|
||||
version: 0.6.0
|
||||
version: 0.6.1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
@ -13,7 +13,7 @@ dependencies:
|
||||
crdt_sync:
|
||||
git:
|
||||
url: https://github.com/kuhyx/utils
|
||||
ref: crdt_sync_dart-v0.13.0
|
||||
ref: crdt_sync_dart-v0.13.1
|
||||
path: crdt_sync_dart
|
||||
crypto: 3.0.8
|
||||
flutter:
|
||||
|
||||
@ -52,7 +52,7 @@ void main() {
|
||||
setUp(installFakeSecureStorage);
|
||||
|
||||
test('no passphrase on the device is locked, never plaintext', () async {
|
||||
final state = await openEncryptedStore(
|
||||
final state = await openEncryptedStoreFromKeystore(
|
||||
store: _Remote(),
|
||||
oldPrefix: _old,
|
||||
params: _cheap,
|
||||
@ -64,7 +64,7 @@ void main() {
|
||||
test('right passphrase unlocks and copies old plaintext once', () async {
|
||||
final remote = _Remote()..files['$_old/phone/log.json'] = _log('milk');
|
||||
await saveE2ePassphrase('pw', storage: storage);
|
||||
final state = await openEncryptedStore(
|
||||
final state = await openEncryptedStoreFromKeystore(
|
||||
store: remote,
|
||||
oldPrefix: _old,
|
||||
params: _cheap,
|
||||
@ -76,7 +76,7 @@ void main() {
|
||||
isNot(contains('milk')),
|
||||
);
|
||||
expect(remote.files['$_old/phone/log.json'], contains('milk'));
|
||||
final again = await openEncryptedStore(
|
||||
final again = await openEncryptedStoreFromKeystore(
|
||||
store: remote,
|
||||
oldPrefix: _old,
|
||||
params: _cheap,
|
||||
@ -88,7 +88,7 @@ void main() {
|
||||
final remote = _Remote();
|
||||
await saveE2ePassphrase('pw', storage: storage);
|
||||
final first =
|
||||
await openEncryptedStore(store: remote, oldPrefix: _old, params: _cheap)
|
||||
await openEncryptedStoreFromKeystore(store: remote, oldPrefix: _old, params: _cheap)
|
||||
as E2eReady;
|
||||
final layout = E2eLayout.of(_old);
|
||||
await first.store.putFileText(
|
||||
@ -97,7 +97,7 @@ void main() {
|
||||
message: 'm',
|
||||
);
|
||||
final second =
|
||||
await openEncryptedStore(store: remote, oldPrefix: _old, params: _cheap)
|
||||
await openEncryptedStoreFromKeystore(store: remote, oldPrefix: _old, params: _cheap)
|
||||
as E2eReady;
|
||||
final text = await second.store.getFileText(
|
||||
'${layout.prefix}/phone/log.json',
|
||||
@ -108,9 +108,9 @@ void main() {
|
||||
test('wrong passphrase is locked and merges nothing', () async {
|
||||
final remote = _Remote();
|
||||
await saveE2ePassphrase('pw', storage: storage);
|
||||
await openEncryptedStore(store: remote, oldPrefix: _old, params: _cheap);
|
||||
await openEncryptedStoreFromKeystore(store: remote, oldPrefix: _old, params: _cheap);
|
||||
await saveE2ePassphrase('nope', storage: storage);
|
||||
final state = await openEncryptedStore(
|
||||
final state = await openEncryptedStoreFromKeystore(
|
||||
store: remote,
|
||||
oldPrefix: _old,
|
||||
params: _cheap,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user