This commit is contained in:
Christien Rioux 2023-07-22 23:29:10 -04:00
parent 08dab73757
commit c08878b35a
39 changed files with 771 additions and 218 deletions

View file

@ -10,6 +10,7 @@ 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';
@ -38,58 +39,15 @@ class LocalAccounts extends _$LocalAccounts
//////////////////////////////////////////////////////////////
/// Mutators and Selectors
/// 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);
// IdentityMaster DHT record is public/unencrypted
return (await DHTRecord.create(dhtctx,
crypto: const DHTRecordCryptoPublic()))
.deleteScope((masterRec) async {
// Identity record is private
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);
});
});
/// Reorder accounts
Future<void> reorderAccount(int oldIndex, int newIndex) async {
final localAccounts = state.requireValue;
var removedItem = Output<LocalAccount>();
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
@ -99,6 +57,7 @@ class LocalAccounts extends _$LocalAccounts
EncryptionKeyType encryptionKeyType,
String encryptionKey,
proto.Account account) async {
final veilid = await eventualVeilid.future;
final localAccounts = state.requireValue;
// Encrypt identitySecret with key
@ -111,8 +70,8 @@ class LocalAccounts extends _$LocalAccounts
identitySecretSaltBytes = Uint8List(0);
case EncryptionKeyType.pin:
case EncryptionKeyType.password:
final cs = await Veilid.instance
.getCryptoSystem(identityMaster.identityRecordKey.kind);
final cs =
await veilid.getCryptoSystem(identityMaster.identityRecordKey.kind);
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
final nonce = await cs.randomNonce();
identitySecretSaltBytes = nonce.decode();
@ -135,7 +94,7 @@ class LocalAccounts extends _$LocalAccounts
/////// Add account with profile to DHT
// Create private routing context
final dhtctx = (await Veilid.instance.routingContext())
final dhtctx = (await veilid.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
@ -171,6 +130,22 @@ class LocalAccounts extends _$LocalAccounts
return localAccount;
}
/// 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;
}
/// Import an account from another VeilidChat instance
/// Recover an account with the master identity secret

View file

@ -6,7 +6,7 @@ part of 'local_accounts.dart';
// RiverpodGenerator
// **************************************************************************
String _$localAccountsHash() => r'41f70b78e71c8b3faa2cd1f2a96084fbb373324c';
String _$localAccountsHash() => r'694236fa91156c00b3f7d6985fbc55b8871646ab';
/// See also [LocalAccounts].
@ProviderFor(LocalAccounts)

View file

@ -4,10 +4,10 @@ import 'dart:typed_data';
import 'package:veilid/veilid.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilidchat/providers/repositories.dart';
import '../veilid_support/veilid_support.dart';
import '../entities/entities.dart';
import 'local_accounts.dart';
part 'logins.g.dart';
@ -34,19 +34,20 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
//////////////////////////////////////////////////////////////
/// Mutators and Selectors
Future<void> setActiveUserLogin(TypedKey accountMasterKey) async {
Future<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
final current = state.requireValue;
for (final userLogin in current.userLogins) {
if (userLogin.accountMasterRecordKey == accountMasterKey) {
state = AsyncValue.data(
current.copyWith(activeUserLogin: accountMasterKey));
return;
}
if (accountMasterRecordKey != null) {
// Assert the specified record key can be found, will throw if not
final _ = current.userLogins.firstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
}
throw Exception("User not found");
final updated = current.copyWith(activeUserLogin: accountMasterRecordKey);
await store(updated);
state = AsyncValue.data(updated);
}
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async {
final veilid = await eventualVeilid.future;
final localAccounts = ref.read(localAccountsProvider).requireValue;
// Get account, throws if not found
@ -64,7 +65,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
SecretKey.fromBytes(localAccount.identitySecretKeyBytes);
// Validate this secret with the identity public key
final cs = await Veilid.instance
final cs = await veilid
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
final keyOk = await cs.validateKeyPair(
localAccount.identityMaster.identityPublicKey, identitySecret);
@ -74,15 +75,15 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
// Add to user logins and select it
final current = state.requireValue;
final now = Veilid.instance.now();
final now = veilid.now();
final updated = current.copyWith(
userLogins: current.userLogins.replaceFirstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
(ul) => ul != null
? ul.copyWith(lastActive: now)
: UserLogin(
accountMasterKey: accountMasterRecordKey,
secretKey:
accountMasterRecordKey: accountMasterRecordKey,
identitySecret:
TypedSecret(kind: cs.kind(), value: identitySecret),
lastActive: now),
addIfNotFound: true),
@ -95,6 +96,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
Future<bool> loginWithPasswordOrPin(
TypedKey accountMasterRecordKey, String encryptionKey) async {
final veilid = await eventualVeilid.future;
final localAccounts = ref.read(localAccountsProvider).requireValue;
// Get account, throws if not found
@ -108,7 +110,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
localAccount.encryptionKeyType != EncryptionKeyType.pin) {
throw Exception("Wrong authentication type");
}
final cs = await Veilid.instance
final cs = await veilid
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
final eksalt = localAccount.identitySecretSaltBytes;
@ -126,15 +128,15 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
// Add to user logins and select it
final current = state.requireValue;
final now = Veilid.instance.now();
final now = veilid.now();
final updated = current.copyWith(
userLogins: current.userLogins.replaceFirstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
(ul) => ul != null
? ul.copyWith(lastActive: now)
: UserLogin(
accountMasterKey: accountMasterRecordKey,
secretKey:
accountMasterRecordKey: accountMasterRecordKey,
identitySecret:
TypedSecret(kind: cs.kind(), value: identitySecret),
lastActive: now),
addIfNotFound: true),
@ -145,25 +147,18 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
return true;
}
Future<void> logout() async {
Future<void> logout(TypedKey? accountMasterRecordKey) async {
final current = state.requireValue;
if (current.activeUserLogin == null) {
final logoutUser = accountMasterRecordKey ?? current.activeUserLogin;
if (logoutUser == null) {
return;
}
final updated = current.copyWith(
activeUserLogin: null,
userLogins: current.userLogins.removeWhere(
(ul) => ul.accountMasterRecordKey == current.activeUserLogin));
await store(updated);
state = AsyncValue.data(updated);
}
Future<void> switchToAccount(TypedKey accountMasterRecordKey) async {
final current = state.requireValue;
final userLogin = current.userLogins.firstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
final updated =
current.copyWith(activeUserLogin: userLogin.accountMasterRecordKey);
activeUserLogin: current.activeUserLogin == logoutUser
? null
: current.activeUserLogin,
userLogins: current.userLogins
.removeWhere((ul) => ul.accountMasterRecordKey == logoutUser));
await store(updated);
state = AsyncValue.data(updated);
}

View file

@ -6,7 +6,7 @@ part of 'logins.dart';
// RiverpodGenerator
// **************************************************************************
String _$loginsHash() => r'379514b8b6623d59cb89b4f128be5953d5ea62a6';
String _$loginsHash() => r'cf7f1a2343340a4dc4ebfa4009e846d0a39c0167';
/// See also [Logins].
@ProviderFor(Logins)