veilidchat/lib/repositories/local_accounts.dart

165 lines
5.9 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';
import 'package:veilid/veilid.dart';
2023-07-20 15:48:55 -04:00
import 'package:riverpod_annotation/riverpod_annotation.dart';
2023-07-19 00:09:57 -04:00
import '../tools/tools.dart';
import '../entities/entities.dart';
import '../entities/proto.dart' as proto;
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
abstract class LocalAccounts extends _$LocalAccounts {
2023-07-19 00:09:57 -04:00
static const localAccountManagerTable = "local_account_manager";
static const localAccountsKey = "local_accounts";
2023-07-20 15:48:55 -04:00
/// Get all local account information
@override
FutureOr<IList<LocalAccount>> build() async {
2023-07-19 00:09:57 -04:00
// Load accounts from tabledb
final localAccounts =
await tableScope(localAccountManagerTable, (tdb) async {
final localAccountsJson = await tdb.loadStringJson(0, localAccountsKey);
return localAccountsJson != null
? IList<LocalAccount>.fromJson(
localAccountsJson, genericFromJson(LocalAccount.fromJson))
: IList<LocalAccount>();
});
2023-07-20 15:48:55 -04:00
return localAccounts;
2023-07-19 00:09:57 -04:00
}
/// Store things back to storage
2023-07-20 15:48:55 -04:00
Future<void> flush(IList<LocalAccount> localAccounts) async {
2023-07-19 00:09:57 -04:00
await tableScope(localAccountManagerTable, (tdb) async {
2023-07-20 15:48:55 -04:00
await tdb.storeStringJson(0, localAccountsKey, localAccounts);
2023-07-19 00:09:57 -04:00
});
}
/// Creates a new master identity and returns it with its secrets
Future<IdentityMasterWithSecrets> newIdentityMaster() async {
final crypto = await Veilid.instance.bestCryptoSystem();
final dhtctx = (await Veilid.instance.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
return (await DHTRecord.create(dhtctx)).deleteScope((masterRec) async {
return (await DHTRecord.create(dhtctx)).deleteScope((identityRec) async {
// Make IdentityMaster
final masterRecordKey = masterRec.key();
final masterOwner = masterRec.ownerKeyPair()!;
final masterSigBuf = masterRecordKey.decode()
..addAll(masterOwner.key.decode());
final identityRecordKey = identityRec.key();
final identityOwner = identityRec.ownerKeyPair()!;
final identitySigBuf = identityRecordKey.decode()
..addAll(identityOwner.key.decode());
final identitySignature =
await crypto.signWithKeyPair(masterOwner, identitySigBuf);
final masterSignature =
await crypto.signWithKeyPair(identityOwner, masterSigBuf);
final identityMaster = IdentityMaster(
identityRecordKey: identityRecordKey,
identityPublicKey: identityOwner.key,
masterRecordKey: masterRecordKey,
masterPublicKey: masterOwner.key,
identitySignature: identitySignature,
masterSignature: masterSignature);
// Write identity master to master dht key
await masterRec.eventualWriteJson(identityMaster);
// Make empty identity
const identity = Identity(accountRecords: IMapConst({}));
// Write empty identity to identity dht key
await identityRec.eventualWriteJson(identity);
return IdentityMasterWithSecrets(
identityMaster: identityMaster,
masterSecret: masterOwner.secret,
identitySecret: identityOwner.secret);
});
});
}
/// Creates a new account associated with master identity
Future<LocalAccount> newAccount(
IdentityMaster identityMaster,
SecretKey identitySecret,
EncryptionKeyType encryptionKeyType,
String encryptionKey,
proto.Account account) async {
2023-07-20 15:48:55 -04:00
final localAccounts = state.requireValue;
2023-07-19 00:09:57 -04:00
// Encrypt identitySecret with key
final cs = await Veilid.instance.bestCryptoSystem();
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
final nonce = await cs.randomNonce();
final eksalt = nonce.decode();
SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
final identitySecretBytes =
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
// Create local account object
final localAccount = LocalAccount(
identityMaster: identityMaster,
identitySecretKeyBytes: identitySecretBytes,
identitySecretSaltBytes: eksalt,
encryptionKeyType: encryptionKeyType,
biometricsEnabled: false,
hiddenAccount: false,
);
/////// Add account with profile to DHT
// Create private routing context
final dhtctx = (await Veilid.instance.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
// Open identity key for writing
(await DHTRecord.open(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 newAccountRecordOwner = accountRec.ownerKeyPair()!;
final newAccountRecordInfo = AccountRecordInfo(
key: accountRec.key(), owner: newAccountRecordOwner);
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
2023-07-20 15:48:55 -04:00
final newLocalAccounts = localAccounts.add(localAccount);
await flush(newLocalAccounts);
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
/// Import an account from another VeilidChat instance
/// Recover an account with the master identity secret
2023-07-19 00:09:57 -04:00
}