veilidchat/lib/providers/local_accounts.dart

153 lines
5.3 KiB
Dart
Raw Normal View History

2023-07-20 15:48:55 -04:00
import 'dart:async';
2023-07-19 00:09:57 -04:00
import 'dart:convert';
import 'dart:typed_data';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
2023-07-20 15:48:55 -04:00
import 'package:riverpod_annotation/riverpod_annotation.dart';
2023-07-26 14:20:29 -04:00
import 'package:veilid/veilid.dart';
2023-07-19 00:09:57 -04:00
import '../entities/entities.dart';
import '../entities/proto.dart' as proto;
2023-07-26 14:20:29 -04:00
import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart';
2023-07-22 23:29:10 -04:00
import 'logins.dart';
2023-07-19 00:09:57 -04:00
2023-07-20 15:48:55 -04:00
part 'local_accounts.g.dart';
2023-07-19 00:09:57 -04:00
// Local account manager
2023-07-20 15:48:55 -04:00
@riverpod
2023-07-21 21:25:27 -04:00
class LocalAccounts extends _$LocalAccounts
with AsyncTableDBBacked<IList<LocalAccount>> {
//////////////////////////////////////////////////////////////
/// AsyncTableDBBacked
@override
2023-07-26 14:20:29 -04:00
String tableName() => 'local_account_manager';
2023-07-21 21:25:27 -04:00
@override
2023-07-26 14:20:29 -04:00
String tableKeyName() => 'local_accounts';
2023-07-21 21:25:27 -04:00
@override
2023-07-26 10:06:54 -04:00
IList<LocalAccount> valueFromJson(Object? obj) => obj != null
2023-07-21 21:25:27 -04:00
? IList<LocalAccount>.fromJson(
obj, genericFromJson(LocalAccount.fromJson))
: IList<LocalAccount>();
2023-07-26 10:06:54 -04:00
@override
Object? valueToJson(IList<LocalAccount> val) =>
val.toJson((la) => la.toJson());
2023-07-19 00:09:57 -04:00
2023-07-20 15:48:55 -04:00
/// Get all local account information
@override
2023-07-26 14:20:29 -04:00
FutureOr<IList<LocalAccount>> build() async => await load();
2023-07-19 00:09:57 -04:00
2023-07-21 21:25:27 -04:00
//////////////////////////////////////////////////////////////
/// Mutators and Selectors
2023-07-19 00:09:57 -04:00
2023-07-22 23:29:10 -04:00
/// Reorder accounts
Future<void> reorderAccount(int oldIndex, int newIndex) async {
final localAccounts = state.requireValue;
2023-07-26 14:20:29 -04:00
final removedItem = Output<LocalAccount>();
2023-07-22 23:29:10 -04:00
final updated = localAccounts
.removeAt(oldIndex, removedItem)
.insert(newIndex, removedItem.value!);
await store(updated);
state = AsyncValue.data(updated);
2023-07-19 00:09:57 -04:00
}
/// Creates a new account associated with master identity
Future<LocalAccount> newAccount(
2023-07-25 01:04:34 -04:00
{required IdentityMaster identityMaster,
required SecretKey identitySecret,
2023-07-26 14:20:29 -04:00
required proto.Account account, EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey = ''}) async {
2023-07-22 23:29:10 -04:00
final veilid = await eventualVeilid.future;
2023-07-20 15:48:55 -04:00
final localAccounts = state.requireValue;
2023-07-19 00:09:57 -04:00
// Encrypt identitySecret with key
2023-07-21 21:25:27 -04:00
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:
2023-07-22 23:29:10 -04:00
final cs =
await veilid.getCryptoSystem(identityMaster.identityRecordKey.kind);
2023-07-21 21:25:27 -04:00
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
final nonce = await cs.randomNonce();
identitySecretSaltBytes = nonce.decode();
2023-07-26 14:20:29 -04:00
final sharedSecret =
2023-07-21 21:25:27 -04:00
await cs.deriveSharedSecret(ekbytes, identitySecretSaltBytes);
identitySecretBytes =
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
}
2023-07-19 00:09:57 -04:00
// Create local account object
final localAccount = LocalAccount(
identityMaster: identityMaster,
identitySecretKeyBytes: identitySecretBytes,
2023-07-21 21:25:27 -04:00
identitySecretSaltBytes: identitySecretSaltBytes,
2023-07-19 00:09:57 -04:00
encryptionKeyType: encryptionKeyType,
biometricsEnabled: false,
hiddenAccount: false,
);
/////// Add account with profile to DHT
// Create private routing context
2023-07-22 23:29:10 -04:00
final dhtctx = (await veilid.routingContext())
2023-07-19 00:09:57 -04:00
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
// Open identity key for writing
2023-07-26 14:20:29 -04:00
await (await DHTRecord.openWrite(dhtctx, identityMaster.identityRecordKey,
2023-07-19 00:09:57 -04:00
identityMaster.identityWriter(identitySecret)))
.scope((identityRec) async {
// Create new account to insert into identity
2023-07-26 14:20:29 -04:00
await (await DHTRecord.create(dhtctx)).deleteScope((accountRec) async {
2023-07-19 00:09:57 -04:00
// Write account key
await accountRec.eventualWriteProtobuf(account);
// Update identity key to include account
final newAccountRecordInfo = AccountRecordInfo(
2023-07-21 21:25:27 -04:00
key: accountRec.key(), owner: accountRec.ownerKeyPair()!);
2023-07-19 00:09:57 -04:00
await identityRec.eventualUpdateJson(Identity.fromJson,
(oldIdentity) async {
final accountRecords = IMapOfSets.from(oldIdentity.accountRecords)
2023-07-26 14:20:29 -04:00
.add('com.veilid.veilidchat', newAccountRecordInfo)
2023-07-19 00:09:57 -04:00
.asIMap();
return oldIdentity.copyWith(accountRecords: accountRecords);
});
});
});
// Add local account object to internal store
2023-07-20 15:48:55 -04:00
final newLocalAccounts = localAccounts.add(localAccount);
2023-07-21 21:25:27 -04:00
await store(newLocalAccounts);
2023-07-20 15:48:55 -04:00
state = AsyncValue.data(newLocalAccounts);
2023-07-19 00:09:57 -04:00
// Return local account object
return localAccount;
}
2023-07-20 15:48:55 -04:00
2023-07-22 23:29:10 -04:00
/// Remove an account and wipe the messages for this account from this device
Future<bool> 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;
}
2023-07-20 15:48:55 -04:00
/// Import an account from another VeilidChat instance
/// Recover an account with the master identity secret
2023-07-19 00:09:57 -04:00
}