import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:veilid/veilid.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../tools/tools.dart'; import '../veilid_support/veilid_support.dart'; import '../entities/entities.dart'; import '../entities/proto.dart' as proto; import 'logins.dart'; part 'local_accounts.g.dart'; // Local account manager @riverpod class LocalAccounts extends _$LocalAccounts with AsyncTableDBBacked> { ////////////////////////////////////////////////////////////// /// AsyncTableDBBacked @override String tableName() => "local_account_manager"; @override String tableKeyName() => "local_accounts"; @override IList reviveJson(Object? obj) => obj != null ? IList.fromJson( obj, genericFromJson(LocalAccount.fromJson)) : IList(); /// Get all local account information @override FutureOr> build() async { return await load(); } ////////////////////////////////////////////////////////////// /// Mutators and Selectors /// Reorder accounts Future reorderAccount(int oldIndex, int newIndex) async { final localAccounts = state.requireValue; var removedItem = Output(); final updated = localAccounts .removeAt(oldIndex, removedItem) .insert(newIndex, removedItem.value!); await store(updated); state = AsyncValue.data(updated); } /// Creates a new account associated with master identity Future newAccount( IdentityMaster identityMaster, SecretKey identitySecret, EncryptionKeyType encryptionKeyType, String encryptionKey, proto.Account account) async { final veilid = await eventualVeilid.future; final localAccounts = state.requireValue; // Encrypt identitySecret with key late final Uint8List identitySecretBytes; late final Uint8List identitySecretSaltBytes; switch (encryptionKeyType) { case EncryptionKeyType.none: identitySecretBytes = identitySecret.decode(); identitySecretSaltBytes = Uint8List(0); case EncryptionKeyType.pin: case EncryptionKeyType.password: final cs = await veilid.getCryptoSystem(identityMaster.identityRecordKey.kind); final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey)); final nonce = await cs.randomNonce(); identitySecretSaltBytes = nonce.decode(); SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, identitySecretSaltBytes); identitySecretBytes = await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret); } // Create local account object final localAccount = LocalAccount( identityMaster: identityMaster, identitySecretKeyBytes: identitySecretBytes, identitySecretSaltBytes: identitySecretSaltBytes, encryptionKeyType: encryptionKeyType, biometricsEnabled: false, hiddenAccount: false, ); /////// Add account with profile to DHT // Create private routing context final dhtctx = (await veilid.routingContext()) .withPrivacy() .withSequencing(Sequencing.ensureOrdered); // Open identity key for writing (await DHTRecord.openWrite(dhtctx, identityMaster.identityRecordKey, identityMaster.identityWriter(identitySecret))) .scope((identityRec) async { // Create new account to insert into identity (await DHTRecord.create(dhtctx)).deleteScope((accountRec) async { // Write account key await accountRec.eventualWriteProtobuf(account); // Update identity key to include account final newAccountRecordInfo = AccountRecordInfo( key: accountRec.key(), owner: accountRec.ownerKeyPair()!); await identityRec.eventualUpdateJson(Identity.fromJson, (oldIdentity) async { final accountRecords = IMapOfSets.from(oldIdentity.accountRecords) .add("VeilidChat", newAccountRecordInfo) .asIMap(); return oldIdentity.copyWith(accountRecords: accountRecords); }); }); }); // Add local account object to internal store final newLocalAccounts = localAccounts.add(localAccount); await store(newLocalAccounts); state = AsyncValue.data(newLocalAccounts); // Return local account object return localAccount; } /// Remove an account and wipe the messages for this account from this device Future deleteAccount(TypedKey accountMasterRecordKey) async { final logins = ref.read(loginsProvider.notifier); await logins.logout(accountMasterRecordKey); final localAccounts = state.requireValue; final updated = localAccounts.removeWhere( (la) => la.identityMaster.masterRecordKey == accountMasterRecordKey); await store(updated); state = AsyncValue.data(updated); // xxx todo: wipe messages return true; } /// Import an account from another VeilidChat instance /// Recover an account with the master identity secret }