mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-07-23 14:40:58 -04:00
dht work
This commit is contained in:
parent
57c366ef91
commit
c35056f687
39 changed files with 1382 additions and 662 deletions
|
@ -1,8 +1,5 @@
|
|||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../entities/proto.dart' as proto;
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
|
||||
|
@ -30,11 +27,12 @@ class AccountInfo {
|
|||
proto.Account? account;
|
||||
}
|
||||
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
@riverpod
|
||||
Future<AccountInfo> fetchAccount(FetchAccountRef ref,
|
||||
{required TypedKey accountMasterRecordKey}) async {
|
||||
// Get which local account we want to fetch the profile for
|
||||
final veilid = await eventualVeilid.future;
|
||||
final localAccount = await ref.watch(
|
||||
fetchLocalAccountProvider(accountMasterRecordKey: accountMasterRecordKey)
|
||||
.future);
|
||||
|
@ -56,55 +54,17 @@ Future<AccountInfo> fetchAccount(FetchAccountRef ref,
|
|||
return AccountInfo(status: AccountInfoStatus.accountLocked, active: active);
|
||||
}
|
||||
|
||||
// Read the identity key to get the account keys
|
||||
final dhtctx = (await veilid.routingContext())
|
||||
.withPrivacy()
|
||||
.withSequencing(Sequencing.ensureOrdered);
|
||||
final identityRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
|
||||
localAccount.identityMaster.identityRecordKey.kind,
|
||||
login.identitySecret.value);
|
||||
|
||||
late final TypedKey accountRecordKey;
|
||||
late final KeyPair accountRecordOwner;
|
||||
|
||||
await (await DHTRecord.openRead(
|
||||
dhtctx, localAccount.identityMaster.identityRecordKey,
|
||||
crypto: identityRecordCrypto))
|
||||
.scope((identityRec) async {
|
||||
final identity = await identityRec.getJson(Identity.fromJson);
|
||||
if (identity == null) {
|
||||
// Identity could not be read or decrypted from DHT
|
||||
return AccountInfo(
|
||||
status: AccountInfoStatus.accountInvalid, active: active);
|
||||
}
|
||||
final accountRecords = IMapOfSets.from(identity.accountRecords);
|
||||
final vcAccounts = accountRecords.get(veilidChatAccountKey);
|
||||
if (vcAccounts.length != 1) {
|
||||
// No veilidchat account, or multiple accounts
|
||||
// somehow associated with identity
|
||||
return AccountInfo(
|
||||
status: AccountInfoStatus.accountInvalid, active: active);
|
||||
}
|
||||
final accountRecordInfo = vcAccounts.first;
|
||||
accountRecordKey = accountRecordInfo.key;
|
||||
accountRecordOwner = accountRecordInfo.owner;
|
||||
});
|
||||
|
||||
// Pull the account DHT key, decode it and return it
|
||||
final accountRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
|
||||
accountRecordKey.kind, accountRecordOwner.secret);
|
||||
late final proto.Account account;
|
||||
await (await DHTRecord.openRead(dhtctx, accountRecordKey,
|
||||
crypto: accountRecordCrypto))
|
||||
.scope((accountRec) async {
|
||||
final protoAccount = await accountRec.getProtobuf(proto.Account.fromBuffer);
|
||||
if (protoAccount == null) {
|
||||
// Account could not be read or decrypted from DHT
|
||||
return AccountInfo(
|
||||
status: AccountInfoStatus.accountInvalid, active: active);
|
||||
}
|
||||
account = protoAccount;
|
||||
});
|
||||
final pool = await DHTRecordPool.instance();
|
||||
final account = await (await pool.openOwned(
|
||||
login.accountRecordInfo.accountRecord,
|
||||
parent: localAccount.identityMaster.identityRecordKey))
|
||||
.scope((accountRec) => accountRec.getProtobuf(proto.Account.fromBuffer));
|
||||
if (account == null) {
|
||||
// Account could not be read or decrypted from DHT
|
||||
return AccountInfo(
|
||||
status: AccountInfoStatus.accountInvalid, active: active);
|
||||
}
|
||||
|
||||
// Got account, decrypted and decoded
|
||||
return AccountInfo(
|
||||
|
|
|
@ -6,7 +6,7 @@ part of 'account.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchAccountHash() => r'4d94703d07a21509650e19f60ea67ac96a39742e';
|
||||
String _$fetchAccountHash() => r'88dadc0d005cef8b3df1d03088c8a5da728c333c';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
@ -31,16 +31,28 @@ class _SystemHash {
|
|||
|
||||
typedef FetchAccountRef = AutoDisposeFutureProviderRef<AccountInfo>;
|
||||
|
||||
/// See also [fetchAccount].
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccount].
|
||||
@ProviderFor(fetchAccount)
|
||||
const fetchAccountProvider = FetchAccountFamily();
|
||||
|
||||
/// See also [fetchAccount].
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccount].
|
||||
class FetchAccountFamily extends Family<AsyncValue<AccountInfo>> {
|
||||
/// See also [fetchAccount].
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccount].
|
||||
const FetchAccountFamily();
|
||||
|
||||
/// See also [fetchAccount].
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccount].
|
||||
FetchAccountProvider call({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) {
|
||||
|
@ -73,9 +85,15 @@ class FetchAccountFamily extends Family<AsyncValue<AccountInfo>> {
|
|||
String? get name => r'fetchAccountProvider';
|
||||
}
|
||||
|
||||
/// See also [fetchAccount].
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccount].
|
||||
class FetchAccountProvider extends AutoDisposeFutureProvider<AccountInfo> {
|
||||
/// See also [fetchAccount].
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccount].
|
||||
FetchAccountProvider({
|
||||
required this.accountMasterRecordKey,
|
||||
}) : super.internal(
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../entities/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/dht_short_array.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'logins.dart';
|
||||
|
||||
part 'contact_request_records.g.dart';
|
||||
|
||||
// Contact invitation records stored in Account
|
||||
class ContactRequestRecords {
|
||||
DHTShortArray _backingArray;
|
||||
|
||||
Future<proto.ContactRequestRecord> newContactRequest(
|
||||
proto.EncryptionKind encryptionKind,
|
||||
String encryptionKey,
|
||||
) async {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
class ContactRequestRecordsParams {
|
||||
ContactRequestRecordsParams({required this.contactRequestsDHTListKey});
|
||||
TypedKey contactRequestsDHTListKey;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<ContactRequestRecords?> fetchContactRequestRecords(
|
||||
FetchContactRequestRecordsRef ref,
|
||||
{required ContactRequestRecordsParams params}) async {
|
||||
// final localAccounts = await ref.watch(localAccountsProvider.future);
|
||||
// try {
|
||||
// return localAccounts.firstWhere(
|
||||
// (e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
// } on Exception catch (e) {
|
||||
// if (e is StateError) {
|
||||
// return null;
|
||||
// }
|
||||
// rethrow;
|
||||
// }
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'contact_request_records.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchContactRequestRecordsHash() =>
|
||||
r'603c6d81b22d1cb4fd26cf32b98d3206ff6bc38c';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
typedef FetchContactRequestRecordsRef
|
||||
= AutoDisposeFutureProviderRef<ContactRequestRecords?>;
|
||||
|
||||
/// See also [fetchContactRequestRecords].
|
||||
@ProviderFor(fetchContactRequestRecords)
|
||||
const fetchContactRequestRecordsProvider = FetchContactRequestRecordsFamily();
|
||||
|
||||
/// See also [fetchContactRequestRecords].
|
||||
class FetchContactRequestRecordsFamily
|
||||
extends Family<AsyncValue<ContactRequestRecords?>> {
|
||||
/// See also [fetchContactRequestRecords].
|
||||
const FetchContactRequestRecordsFamily();
|
||||
|
||||
/// See also [fetchContactRequestRecords].
|
||||
FetchContactRequestRecordsProvider call({
|
||||
required ContactRequestRecordsParams params,
|
||||
}) {
|
||||
return FetchContactRequestRecordsProvider(
|
||||
params: params,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FetchContactRequestRecordsProvider getProviderOverride(
|
||||
covariant FetchContactRequestRecordsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
params: provider.params,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'fetchContactRequestRecordsProvider';
|
||||
}
|
||||
|
||||
/// See also [fetchContactRequestRecords].
|
||||
class FetchContactRequestRecordsProvider
|
||||
extends AutoDisposeFutureProvider<ContactRequestRecords?> {
|
||||
/// See also [fetchContactRequestRecords].
|
||||
FetchContactRequestRecordsProvider({
|
||||
required this.params,
|
||||
}) : super.internal(
|
||||
(ref) => fetchContactRequestRecords(
|
||||
ref,
|
||||
params: params,
|
||||
),
|
||||
from: fetchContactRequestRecordsProvider,
|
||||
name: r'fetchContactRequestRecordsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchContactRequestRecordsHash,
|
||||
dependencies: FetchContactRequestRecordsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
FetchContactRequestRecordsFamily._allTransitiveDependencies,
|
||||
);
|
||||
|
||||
final ContactRequestRecordsParams params;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchContactRequestRecordsProvider &&
|
||||
other.params == params;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, params.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions
|
|
@ -4,18 +4,16 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../entities/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
import 'logins.dart';
|
||||
|
||||
part 'local_accounts.g.dart';
|
||||
|
||||
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
||||
|
||||
// Local account manager
|
||||
@riverpod
|
||||
class LocalAccounts extends _$LocalAccounts
|
||||
|
@ -53,84 +51,71 @@ class LocalAccounts extends _$LocalAccounts
|
|||
state = AsyncValue.data(updated);
|
||||
}
|
||||
|
||||
/// Creates a new account associated with master identity
|
||||
Future<LocalAccount> newAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
required SecretKey identitySecret,
|
||||
required proto.Account account,
|
||||
/// Make encrypted identitySecret
|
||||
Future<Uint8List> _encryptIdentitySecret(
|
||||
{required SecretKey identitySecret,
|
||||
required CryptoKind cryptoKind,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) 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 cs = await veilid.getCryptoSystem(cryptoKind);
|
||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
final nonce = await cs.randomNonce();
|
||||
identitySecretSaltBytes = nonce.decode();
|
||||
final identitySecretSaltBytes = nonce.decode();
|
||||
final sharedSecret =
|
||||
await cs.deriveSharedSecret(ekbytes, identitySecretSaltBytes);
|
||||
identitySecretBytes =
|
||||
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
|
||||
identitySecretBytes = (await cs.cryptNoAuth(
|
||||
identitySecret.decode(), nonce, sharedSecret))
|
||||
..addAll(identitySecretSaltBytes);
|
||||
}
|
||||
return identitySecretBytes;
|
||||
}
|
||||
|
||||
/// Creates a new Account associated with master identity
|
||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||
Future<LocalAccount> newLocalAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
required SecretKey identitySecret,
|
||||
required String name,
|
||||
required String title,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final localAccounts = state.requireValue;
|
||||
|
||||
/////// Add account with profile to DHT
|
||||
await identityMaster.newAccount(
|
||||
identitySecret: identitySecret,
|
||||
name: name,
|
||||
title: title,
|
||||
);
|
||||
|
||||
// Encrypt identitySecret with key
|
||||
final identitySecretBytes = await _encryptIdentitySecret(
|
||||
identitySecret: identitySecret,
|
||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionKeyType: encryptionKeyType);
|
||||
|
||||
// Create local account object
|
||||
// Does not contain the account key or its secret
|
||||
// as that is not to be persisted, and only pulled from the identity key
|
||||
// and optionally decrypted with the unlock password
|
||||
final localAccount = LocalAccount(
|
||||
identityMaster: identityMaster,
|
||||
identitySecretKeyBytes: identitySecretBytes,
|
||||
identitySecretSaltBytes: identitySecretSaltBytes,
|
||||
identitySecretBytes: identitySecretBytes,
|
||||
encryptionKeyType: encryptionKeyType,
|
||||
biometricsEnabled: false,
|
||||
hiddenAccount: false,
|
||||
name: account.profile.name,
|
||||
name: name,
|
||||
);
|
||||
|
||||
/////// 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 (await DHTRecord.openWrite(dhtctx, identityMaster.identityRecordKey,
|
||||
identityMaster.identityWriter(identitySecret)))
|
||||
.scope((identityRec) async {
|
||||
// Create new account to insert into identity
|
||||
await (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 oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords);
|
||||
// Only allow one account per identity for veilidchat
|
||||
if (oldAccountRecords.get(veilidChatAccountKey).isNotEmpty) {
|
||||
throw StateError(
|
||||
'Only one account per identity allowed for VeilidChat');
|
||||
}
|
||||
final accountRecords = oldAccountRecords
|
||||
.add(veilidChatAccountKey, newAccountRecordInfo)
|
||||
.asIMap();
|
||||
return oldIdentity.copyWith(accountRecords: accountRecords);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Add local account object to internal store
|
||||
final newLocalAccounts = localAccounts.add(localAccount);
|
||||
await store(newLocalAccounts);
|
||||
|
@ -141,7 +126,7 @@ class LocalAccounts extends _$LocalAccounts
|
|||
}
|
||||
|
||||
/// Remove an account and wipe the messages for this account from this device
|
||||
Future<bool> deleteAccount(TypedKey accountMasterRecordKey) async {
|
||||
Future<bool> deleteLocalAccount(TypedKey accountMasterRecordKey) async {
|
||||
final logins = ref.read(loginsProvider.notifier);
|
||||
await logins.logout(accountMasterRecordKey);
|
||||
|
||||
|
@ -159,6 +144,8 @@ class LocalAccounts extends _$LocalAccounts
|
|||
/// Import an account from another VeilidChat instance
|
||||
|
||||
/// Recover an account with the master identity secret
|
||||
|
||||
/// Delete an account from all devices
|
||||
}
|
||||
|
||||
@riverpod
|
||||
|
|
|
@ -112,7 +112,7 @@ class FetchLocalAccountProvider
|
|||
}
|
||||
}
|
||||
|
||||
String _$localAccountsHash() => r'd6ced0ad7108c1111603235cf394faa5f6bcdae1';
|
||||
String _$localAccountsHash() => r'a9a1e1765188556858ec982c9e99f780756ade1e';
|
||||
|
||||
/// See also [LocalAccounts].
|
||||
@ProviderFor(LocalAccounts)
|
||||
|
|
|
@ -3,7 +3,6 @@ import 'dart:convert';
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
|
@ -46,8 +45,44 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
|||
state = AsyncValue.data(updated);
|
||||
}
|
||||
|
||||
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async {
|
||||
Future<bool> _loginCommon(
|
||||
IdentityMaster identityMaster, SecretKey identitySecret) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
final cs =
|
||||
await veilid.getCryptoSystem(identityMaster.identityRecordKey.kind);
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw Exception('Identity is corrupted');
|
||||
}
|
||||
|
||||
// Read the identity key to get the account keys
|
||||
final accountRecordInfo = await identityMaster.readAccountFromIdentity(
|
||||
identitySecret: identitySecret);
|
||||
|
||||
// Add to user logins and select it
|
||||
final current = state.requireValue;
|
||||
final now = veilid.now();
|
||||
final updated = current.copyWith(
|
||||
userLogins: current.userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == identityMaster.masterRecordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: identityMaster.masterRecordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
accountRecordInfo: accountRecordInfo,
|
||||
lastActive: now),
|
||||
addIfNotFound: true),
|
||||
activeUserLogin: identityMaster.masterRecordKey);
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async {
|
||||
final localAccounts = ref.read(localAccountsProvider).requireValue;
|
||||
|
||||
// Get account, throws if not found
|
||||
|
@ -62,36 +97,10 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
|||
}
|
||||
|
||||
final identitySecret =
|
||||
SecretKey.fromBytes(localAccount.identitySecretKeyBytes);
|
||||
SecretKey.fromBytes(localAccount.identitySecretBytes);
|
||||
|
||||
// Validate this secret with the identity public key
|
||||
final cs = await veilid
|
||||
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
localAccount.identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw Exception('Identity is corrupted');
|
||||
}
|
||||
|
||||
// Add to user logins and select it
|
||||
final current = state.requireValue;
|
||||
final now = veilid.now();
|
||||
final updated = current.copyWith(
|
||||
userLogins: current.userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
lastActive: now),
|
||||
addIfNotFound: true),
|
||||
activeUserLogin: accountMasterRecordKey);
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
|
||||
return true;
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _loginCommon(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<bool> loginWithPasswordOrPin(
|
||||
|
@ -112,39 +121,21 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
|||
}
|
||||
final cs = await veilid
|
||||
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
final eksalt = localAccount.identitySecretSaltBytes;
|
||||
final nonce = Nonce.fromBytes(eksalt);
|
||||
final sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
||||
final identitySecret = SecretKey.fromBytes(await cs.cryptNoAuth(
|
||||
localAccount.identitySecretKeyBytes, nonce, sharedSecret));
|
||||
final encryptionKeyBytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
|
||||
// Validate this secret with the identity public key
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
localAccount.identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
return false;
|
||||
}
|
||||
final identitySecretKeyBytes =
|
||||
localAccount.identitySecretBytes.sublist(0, SecretKey.decodedLength());
|
||||
final identitySecretSaltBytes =
|
||||
localAccount.identitySecretBytes.sublist(SecretKey.decodedLength());
|
||||
|
||||
// Add to user logins and select it
|
||||
final current = state.requireValue;
|
||||
final now = veilid.now();
|
||||
final updated = current.copyWith(
|
||||
userLogins: current.userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
lastActive: now),
|
||||
addIfNotFound: true),
|
||||
activeUserLogin: accountMasterRecordKey);
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
final nonce = Nonce.fromBytes(identitySecretSaltBytes);
|
||||
final sharedSecret = await cs.deriveSharedSecret(
|
||||
encryptionKeyBytes, identitySecretSaltBytes);
|
||||
final identitySecret = SecretKey.fromBytes(
|
||||
await cs.cryptNoAuth(identitySecretKeyBytes, nonce, sharedSecret));
|
||||
|
||||
return true;
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _loginCommon(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<void> logout(TypedKey? accountMasterRecordKey) async {
|
||||
|
|
|
@ -111,7 +111,7 @@ class FetchLoginProvider extends AutoDisposeFutureProvider<UserLogin?> {
|
|||
}
|
||||
}
|
||||
|
||||
String _$loginsHash() => r'ed9dbe91a248f662ccb0fac6edf5b1892cf2ef92';
|
||||
String _$loginsHash() => r'5720eaacf858b2e1d69ebf9d2a981173a30f8592';
|
||||
|
||||
/// See also [Logins].
|
||||
@ProviderFor(Logins)
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export 'account.dart';
|
||||
export 'connection_state.dart';
|
||||
export 'local_accounts.dart';
|
||||
export 'logins.dart';
|
||||
export 'window_control.dart';
|
9
lib/providers/veilid_instance.dart
Normal file
9
lib/providers/veilid_instance.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
|
||||
part 'veilid_instance.g.dart';
|
||||
|
||||
// Expose the Veilid instance as a FutureProvider
|
||||
@riverpod
|
||||
FutureOr<Veilid> veilidInstance(VeilidInstanceRef ref) async =>
|
||||
await eventualVeilid.future;
|
24
lib/providers/veilid_instance.g.dart
Normal file
24
lib/providers/veilid_instance.g.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'veilid_instance.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$veilidInstanceHash() => r'cca5cf288bafc4a051a1713e285f4c1d3ef4b680';
|
||||
|
||||
/// See also [veilidInstance].
|
||||
@ProviderFor(veilidInstance)
|
||||
final veilidInstanceProvider = AutoDisposeFutureProvider<Veilid>.internal(
|
||||
veilidInstance,
|
||||
name: r'veilidInstanceProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$veilidInstanceHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef VeilidInstanceRef = AutoDisposeFutureProviderRef<Veilid>;
|
||||
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions
|
Loading…
Add table
Add a link
Reference in a new issue