mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
xfer
This commit is contained in:
parent
9d8b609844
commit
bc3ed79cc2
@ -11,7 +11,7 @@ part 'user_login.g.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class UserLogin with _$UserLogin {
|
class UserLogin with _$UserLogin {
|
||||||
const factory UserLogin({
|
const factory UserLogin({
|
||||||
// Master public key for the user used to index the local accounts table
|
// Master record key for the user used to index the local accounts table
|
||||||
required TypedKey accountMasterKey,
|
required TypedKey accountMasterKey,
|
||||||
// The identity secret as unlocked from the local accounts table
|
// The identity secret as unlocked from the local accounts table
|
||||||
required TypedSecret secretKey,
|
required TypedSecret secretKey,
|
||||||
@ -30,7 +30,7 @@ class ActiveLogins with _$ActiveLogins {
|
|||||||
const factory ActiveLogins({
|
const factory ActiveLogins({
|
||||||
// The list of current logged in accounts
|
// The list of current logged in accounts
|
||||||
required IList<UserLogin> userLogins,
|
required IList<UserLogin> userLogins,
|
||||||
// The current selected account indexed by master key
|
// The current selected account indexed by master record key
|
||||||
TypedKey? activeUserLogin,
|
TypedKey? activeUserLogin,
|
||||||
}) = _ActiveLogins;
|
}) = _ActiveLogins;
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ UserLogin _$UserLoginFromJson(Map<String, dynamic> json) {
|
|||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$UserLogin {
|
mixin _$UserLogin {
|
||||||
// Master public key for the user used to index the local accounts table
|
// Master record key for the user used to index the local accounts table
|
||||||
Typed<FixedEncodedString43> get accountMasterKey =>
|
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||||
throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table
|
throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table
|
||||||
Typed<FixedEncodedString43> get secretKey =>
|
Typed<FixedEncodedString43> get secretKey =>
|
||||||
throw _privateConstructorUsedError; // The time this login was most recently used
|
throw _privateConstructorUsedError; // The time this login was most recently used
|
||||||
@ -63,7 +63,7 @@ class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin>
|
|||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
accountMasterKey: null == accountMasterKey
|
accountMasterKey: null == accountMasterKey
|
||||||
? _value.accountMasterKey
|
? _value.accountMasterRecordKey
|
||||||
: accountMasterKey // ignore: cast_nullable_to_non_nullable
|
: accountMasterKey // ignore: cast_nullable_to_non_nullable
|
||||||
as Typed<FixedEncodedString43>,
|
as Typed<FixedEncodedString43>,
|
||||||
secretKey: null == secretKey
|
secretKey: null == secretKey
|
||||||
@ -107,8 +107,8 @@ class __$$_UserLoginCopyWithImpl<$Res>
|
|||||||
Object? lastActive = null,
|
Object? lastActive = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$_UserLogin(
|
return _then(_$_UserLogin(
|
||||||
accountMasterKey: null == accountMasterKey
|
accountMasterRecordKey: null == accountMasterKey
|
||||||
? _value.accountMasterKey
|
? _value.accountMasterRecordKey
|
||||||
: accountMasterKey // ignore: cast_nullable_to_non_nullable
|
: accountMasterKey // ignore: cast_nullable_to_non_nullable
|
||||||
as Typed<FixedEncodedString43>,
|
as Typed<FixedEncodedString43>,
|
||||||
secretKey: null == secretKey
|
secretKey: null == secretKey
|
||||||
@ -127,16 +127,16 @@ class __$$_UserLoginCopyWithImpl<$Res>
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$_UserLogin implements _UserLogin {
|
class _$_UserLogin implements _UserLogin {
|
||||||
const _$_UserLogin(
|
const _$_UserLogin(
|
||||||
{required this.accountMasterKey,
|
{required this.accountMasterRecordKey,
|
||||||
required this.secretKey,
|
required this.secretKey,
|
||||||
required this.lastActive});
|
required this.lastActive});
|
||||||
|
|
||||||
factory _$_UserLogin.fromJson(Map<String, dynamic> json) =>
|
factory _$_UserLogin.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$_UserLoginFromJson(json);
|
_$$_UserLoginFromJson(json);
|
||||||
|
|
||||||
// Master public key for the user used to index the local accounts table
|
// Master record key for the user used to index the local accounts table
|
||||||
@override
|
@override
|
||||||
final Typed<FixedEncodedString43> accountMasterKey;
|
final Typed<FixedEncodedString43> accountMasterRecordKey;
|
||||||
// The identity secret as unlocked from the local accounts table
|
// The identity secret as unlocked from the local accounts table
|
||||||
@override
|
@override
|
||||||
final Typed<FixedEncodedString43> secretKey;
|
final Typed<FixedEncodedString43> secretKey;
|
||||||
@ -146,7 +146,7 @@ class _$_UserLogin implements _UserLogin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'UserLogin(accountMasterKey: $accountMasterKey, secretKey: $secretKey, lastActive: $lastActive)';
|
return 'UserLogin(accountMasterKey: $accountMasterRecordKey, secretKey: $secretKey, lastActive: $lastActive)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -154,8 +154,8 @@ class _$_UserLogin implements _UserLogin {
|
|||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$_UserLogin &&
|
other is _$_UserLogin &&
|
||||||
(identical(other.accountMasterKey, accountMasterKey) ||
|
(identical(other.accountMasterRecordKey, accountMasterRecordKey) ||
|
||||||
other.accountMasterKey == accountMasterKey) &&
|
other.accountMasterRecordKey == accountMasterRecordKey) &&
|
||||||
(identical(other.secretKey, secretKey) ||
|
(identical(other.secretKey, secretKey) ||
|
||||||
other.secretKey == secretKey) &&
|
other.secretKey == secretKey) &&
|
||||||
(identical(other.lastActive, lastActive) ||
|
(identical(other.lastActive, lastActive) ||
|
||||||
@ -165,7 +165,7 @@ class _$_UserLogin implements _UserLogin {
|
|||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
Object.hash(runtimeType, accountMasterKey, secretKey, lastActive);
|
Object.hash(runtimeType, accountMasterRecordKey, secretKey, lastActive);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -190,8 +190,8 @@ abstract class _UserLogin implements UserLogin {
|
|||||||
factory _UserLogin.fromJson(Map<String, dynamic> json) =
|
factory _UserLogin.fromJson(Map<String, dynamic> json) =
|
||||||
_$_UserLogin.fromJson;
|
_$_UserLogin.fromJson;
|
||||||
|
|
||||||
@override // Master public key for the user used to index the local accounts table
|
@override // Master record key for the user used to index the local accounts table
|
||||||
Typed<FixedEncodedString43> get accountMasterKey;
|
Typed<FixedEncodedString43> get accountMasterRecordKey;
|
||||||
@override // The identity secret as unlocked from the local accounts table
|
@override // The identity secret as unlocked from the local accounts table
|
||||||
Typed<FixedEncodedString43> get secretKey;
|
Typed<FixedEncodedString43> get secretKey;
|
||||||
@override // The time this login was most recently used
|
@override // The time this login was most recently used
|
||||||
@ -210,7 +210,7 @@ ActiveLogins _$ActiveLoginsFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$ActiveLogins {
|
mixin _$ActiveLogins {
|
||||||
// The list of current logged in accounts
|
// The list of current logged in accounts
|
||||||
IList<UserLogin> get userLogins =>
|
IList<UserLogin> get userLogins =>
|
||||||
throw _privateConstructorUsedError; // The current selected account indexed by master key
|
throw _privateConstructorUsedError; // The current selected account indexed by master record key
|
||||||
Typed<FixedEncodedString43>? get activeUserLogin =>
|
Typed<FixedEncodedString43>? get activeUserLogin =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
@ -311,7 +311,7 @@ class _$_ActiveLogins implements _ActiveLogins {
|
|||||||
// The list of current logged in accounts
|
// The list of current logged in accounts
|
||||||
@override
|
@override
|
||||||
final IList<UserLogin> userLogins;
|
final IList<UserLogin> userLogins;
|
||||||
// The current selected account indexed by master key
|
// The current selected account indexed by master record key
|
||||||
@override
|
@override
|
||||||
final Typed<FixedEncodedString43>? activeUserLogin;
|
final Typed<FixedEncodedString43>? activeUserLogin;
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ abstract class _ActiveLogins implements ActiveLogins {
|
|||||||
|
|
||||||
@override // The list of current logged in accounts
|
@override // The list of current logged in accounts
|
||||||
IList<UserLogin> get userLogins;
|
IList<UserLogin> get userLogins;
|
||||||
@override // The current selected account indexed by master key
|
@override // The current selected account indexed by master record key
|
||||||
Typed<FixedEncodedString43>? get activeUserLogin;
|
Typed<FixedEncodedString43>? get activeUserLogin;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
@ -7,7 +7,7 @@ part of 'user_login.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
_$_UserLogin _$$_UserLoginFromJson(Map<String, dynamic> json) => _$_UserLogin(
|
_$_UserLogin _$$_UserLoginFromJson(Map<String, dynamic> json) => _$_UserLogin(
|
||||||
accountMasterKey:
|
accountMasterRecordKey:
|
||||||
Typed<FixedEncodedString43>.fromJson(json['account_master_key']),
|
Typed<FixedEncodedString43>.fromJson(json['account_master_key']),
|
||||||
secretKey: Typed<FixedEncodedString43>.fromJson(json['secret_key']),
|
secretKey: Typed<FixedEncodedString43>.fromJson(json['secret_key']),
|
||||||
lastActive: Timestamp.fromJson(json['last_active']),
|
lastActive: Timestamp.fromJson(json['last_active']),
|
||||||
@ -15,7 +15,7 @@ _$_UserLogin _$$_UserLoginFromJson(Map<String, dynamic> json) => _$_UserLogin(
|
|||||||
|
|
||||||
Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) =>
|
Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'account_master_key': instance.accountMasterKey.toJson(),
|
'account_master_key': instance.accountMasterRecordKey.toJson(),
|
||||||
'secret_key': instance.secretKey.toJson(),
|
'secret_key': instance.secretKey.toJson(),
|
||||||
'last_active': instance.lastActive.toJson(),
|
'last_active': instance.lastActive.toJson(),
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,7 @@ import 'package:veilid/veilid.dart';
|
|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
|
import '../veilid_support/veilid_support.dart';
|
||||||
import '../entities/entities.dart';
|
import '../entities/entities.dart';
|
||||||
import '../entities/proto.dart' as proto;
|
import '../entities/proto.dart' as proto;
|
||||||
|
|
||||||
@ -14,31 +15,28 @@ part 'local_accounts.g.dart';
|
|||||||
|
|
||||||
// Local account manager
|
// Local account manager
|
||||||
@riverpod
|
@riverpod
|
||||||
abstract class LocalAccounts extends _$LocalAccounts {
|
class LocalAccounts extends _$LocalAccounts
|
||||||
static const localAccountManagerTable = "local_account_manager";
|
with AsyncTableDBBacked<IList<LocalAccount>> {
|
||||||
static const localAccountsKey = "local_accounts";
|
//////////////////////////////////////////////////////////////
|
||||||
|
/// AsyncTableDBBacked
|
||||||
|
@override
|
||||||
|
String tableName() => "local_account_manager";
|
||||||
|
@override
|
||||||
|
String tableKeyName() => "local_accounts";
|
||||||
|
@override
|
||||||
|
IList<LocalAccount> reviveJson(Object? obj) => obj != null
|
||||||
|
? IList<LocalAccount>.fromJson(
|
||||||
|
obj, genericFromJson(LocalAccount.fromJson))
|
||||||
|
: IList<LocalAccount>();
|
||||||
|
|
||||||
/// Get all local account information
|
/// Get all local account information
|
||||||
@override
|
@override
|
||||||
FutureOr<IList<LocalAccount>> build() async {
|
FutureOr<IList<LocalAccount>> build() async {
|
||||||
// Load accounts from tabledb
|
return await load();
|
||||||
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>();
|
|
||||||
});
|
|
||||||
return localAccounts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store things back to storage
|
//////////////////////////////////////////////////////////////
|
||||||
Future<void> flush(IList<LocalAccount> localAccounts) async {
|
/// Mutators and Selectors
|
||||||
await tableScope(localAccountManagerTable, (tdb) async {
|
|
||||||
await tdb.storeStringJson(0, localAccountsKey, localAccounts);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new master identity and returns it with its secrets
|
/// Creates a new master identity and returns it with its secrets
|
||||||
Future<IdentityMasterWithSecrets> newIdentityMaster() async {
|
Future<IdentityMasterWithSecrets> newIdentityMaster() async {
|
||||||
@ -47,7 +45,11 @@ abstract class LocalAccounts extends _$LocalAccounts {
|
|||||||
.withPrivacy()
|
.withPrivacy()
|
||||||
.withSequencing(Sequencing.ensureOrdered);
|
.withSequencing(Sequencing.ensureOrdered);
|
||||||
|
|
||||||
return (await DHTRecord.create(dhtctx)).deleteScope((masterRec) async {
|
// 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 {
|
return (await DHTRecord.create(dhtctx)).deleteScope((identityRec) async {
|
||||||
// Make IdentityMaster
|
// Make IdentityMaster
|
||||||
final masterRecordKey = masterRec.key();
|
final masterRecordKey = masterRec.key();
|
||||||
@ -100,19 +102,31 @@ abstract class LocalAccounts extends _$LocalAccounts {
|
|||||||
final localAccounts = state.requireValue;
|
final localAccounts = state.requireValue;
|
||||||
|
|
||||||
// Encrypt identitySecret with key
|
// Encrypt identitySecret with key
|
||||||
final cs = await Veilid.instance.bestCryptoSystem();
|
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.instance
|
||||||
|
.getCryptoSystem(identityMaster.identityRecordKey.kind);
|
||||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||||
final nonce = await cs.randomNonce();
|
final nonce = await cs.randomNonce();
|
||||||
final eksalt = nonce.decode();
|
identitySecretSaltBytes = nonce.decode();
|
||||||
SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
SharedSecret sharedSecret =
|
||||||
final identitySecretBytes =
|
await cs.deriveSharedSecret(ekbytes, identitySecretSaltBytes);
|
||||||
|
identitySecretBytes =
|
||||||
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
|
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
|
||||||
|
}
|
||||||
|
|
||||||
// Create local account object
|
// Create local account object
|
||||||
final localAccount = LocalAccount(
|
final localAccount = LocalAccount(
|
||||||
identityMaster: identityMaster,
|
identityMaster: identityMaster,
|
||||||
identitySecretKeyBytes: identitySecretBytes,
|
identitySecretKeyBytes: identitySecretBytes,
|
||||||
identitySecretSaltBytes: eksalt,
|
identitySecretSaltBytes: identitySecretSaltBytes,
|
||||||
encryptionKeyType: encryptionKeyType,
|
encryptionKeyType: encryptionKeyType,
|
||||||
biometricsEnabled: false,
|
biometricsEnabled: false,
|
||||||
hiddenAccount: false,
|
hiddenAccount: false,
|
||||||
@ -126,7 +140,7 @@ abstract class LocalAccounts extends _$LocalAccounts {
|
|||||||
.withSequencing(Sequencing.ensureOrdered);
|
.withSequencing(Sequencing.ensureOrdered);
|
||||||
|
|
||||||
// Open identity key for writing
|
// Open identity key for writing
|
||||||
(await DHTRecord.open(dhtctx, identityMaster.identityRecordKey,
|
(await DHTRecord.openWrite(dhtctx, identityMaster.identityRecordKey,
|
||||||
identityMaster.identityWriter(identitySecret)))
|
identityMaster.identityWriter(identitySecret)))
|
||||||
.scope((identityRec) async {
|
.scope((identityRec) async {
|
||||||
// Create new account to insert into identity
|
// Create new account to insert into identity
|
||||||
@ -135,9 +149,8 @@ abstract class LocalAccounts extends _$LocalAccounts {
|
|||||||
await accountRec.eventualWriteProtobuf(account);
|
await accountRec.eventualWriteProtobuf(account);
|
||||||
|
|
||||||
// Update identity key to include account
|
// Update identity key to include account
|
||||||
final newAccountRecordOwner = accountRec.ownerKeyPair()!;
|
|
||||||
final newAccountRecordInfo = AccountRecordInfo(
|
final newAccountRecordInfo = AccountRecordInfo(
|
||||||
key: accountRec.key(), owner: newAccountRecordOwner);
|
key: accountRec.key(), owner: accountRec.ownerKeyPair()!);
|
||||||
|
|
||||||
await identityRec.eventualUpdateJson(Identity.fromJson,
|
await identityRec.eventualUpdateJson(Identity.fromJson,
|
||||||
(oldIdentity) async {
|
(oldIdentity) async {
|
||||||
@ -151,7 +164,7 @@ abstract class LocalAccounts extends _$LocalAccounts {
|
|||||||
|
|
||||||
// Add local account object to internal store
|
// Add local account object to internal store
|
||||||
final newLocalAccounts = localAccounts.add(localAccount);
|
final newLocalAccounts = localAccounts.add(localAccount);
|
||||||
await flush(newLocalAccounts);
|
await store(newLocalAccounts);
|
||||||
state = AsyncValue.data(newLocalAccounts);
|
state = AsyncValue.data(newLocalAccounts);
|
||||||
|
|
||||||
// Return local account object
|
// Return local account object
|
@ -6,7 +6,7 @@ part of 'local_accounts.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$localAccountsHash() => r'27e90f03be60aa9f3d534456b5b697de669a20fe';
|
String _$localAccountsHash() => r'41f70b78e71c8b3faa2cd1f2a96084fbb373324c';
|
||||||
|
|
||||||
/// See also [LocalAccounts].
|
/// See also [LocalAccounts].
|
||||||
@ProviderFor(LocalAccounts)
|
@ProviderFor(LocalAccounts)
|
170
lib/providers/logins.dart
Normal file
170
lib/providers/logins.dart
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
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';
|
||||||
|
|
||||||
|
part 'logins.g.dart';
|
||||||
|
|
||||||
|
// Local account manager
|
||||||
|
@riverpod
|
||||||
|
class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
/// AsyncTableDBBacked
|
||||||
|
@override
|
||||||
|
String tableName() => "local_account_manager";
|
||||||
|
@override
|
||||||
|
String tableKeyName() => "active_logins";
|
||||||
|
@override
|
||||||
|
ActiveLogins reviveJson(Object? obj) => obj != null
|
||||||
|
? ActiveLogins.fromJson(obj as Map<String, dynamic>)
|
||||||
|
: ActiveLogins.empty();
|
||||||
|
|
||||||
|
/// Get all local account information
|
||||||
|
@override
|
||||||
|
FutureOr<ActiveLogins> build() async {
|
||||||
|
return await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
/// Mutators and Selectors
|
||||||
|
|
||||||
|
Future<void> setActiveUserLogin(TypedKey accountMasterKey) async {
|
||||||
|
final current = state.requireValue;
|
||||||
|
for (final userLogin in current.userLogins) {
|
||||||
|
if (userLogin.accountMasterRecordKey == accountMasterKey) {
|
||||||
|
state = AsyncValue.data(
|
||||||
|
current.copyWith(activeUserLogin: accountMasterKey));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async {
|
||||||
|
final localAccounts = ref.read(localAccountsProvider).requireValue;
|
||||||
|
|
||||||
|
// Get account, throws if not found
|
||||||
|
final localAccount = localAccounts.firstWhere(
|
||||||
|
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||||
|
|
||||||
|
// Log in with this local account
|
||||||
|
|
||||||
|
// Derive key from password
|
||||||
|
if (localAccount.encryptionKeyType != EncryptionKeyType.none) {
|
||||||
|
throw Exception("Wrong authentication type");
|
||||||
|
}
|
||||||
|
|
||||||
|
final identitySecret =
|
||||||
|
SecretKey.fromBytes(localAccount.identitySecretKeyBytes);
|
||||||
|
|
||||||
|
// Validate this secret with the identity public key
|
||||||
|
final cs = await Veilid.instance
|
||||||
|
.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.instance.now();
|
||||||
|
final updated = current.copyWith(
|
||||||
|
userLogins: current.userLogins.replaceFirstWhere(
|
||||||
|
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
|
||||||
|
(ul) => ul != null
|
||||||
|
? ul.copyWith(lastActive: now)
|
||||||
|
: UserLogin(
|
||||||
|
accountMasterKey: accountMasterRecordKey,
|
||||||
|
secretKey:
|
||||||
|
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||||
|
lastActive: now),
|
||||||
|
addIfNotFound: true),
|
||||||
|
activeUserLogin: accountMasterRecordKey);
|
||||||
|
await store(updated);
|
||||||
|
state = AsyncValue.data(updated);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> loginWithPasswordOrPin(
|
||||||
|
TypedKey accountMasterRecordKey, String encryptionKey) async {
|
||||||
|
final localAccounts = ref.read(localAccountsProvider).requireValue;
|
||||||
|
|
||||||
|
// Get account, throws if not found
|
||||||
|
final localAccount = localAccounts.firstWhere(
|
||||||
|
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||||
|
|
||||||
|
// Log in with this local account
|
||||||
|
|
||||||
|
// Derive key from password
|
||||||
|
if (localAccount.encryptionKeyType != EncryptionKeyType.password ||
|
||||||
|
localAccount.encryptionKeyType != EncryptionKeyType.pin) {
|
||||||
|
throw Exception("Wrong authentication type");
|
||||||
|
}
|
||||||
|
final cs = await Veilid.instance
|
||||||
|
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
||||||
|
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||||
|
final eksalt = localAccount.identitySecretSaltBytes;
|
||||||
|
final nonce = Nonce.fromBytes(eksalt);
|
||||||
|
SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
||||||
|
final identitySecret = SecretKey.fromBytes(await cs.cryptNoAuth(
|
||||||
|
localAccount.identitySecretKeyBytes, nonce, sharedSecret));
|
||||||
|
|
||||||
|
// Validate this secret with the identity public key
|
||||||
|
final keyOk = await cs.validateKeyPair(
|
||||||
|
localAccount.identityMaster.identityPublicKey, identitySecret);
|
||||||
|
if (!keyOk) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to user logins and select it
|
||||||
|
final current = state.requireValue;
|
||||||
|
final now = Veilid.instance.now();
|
||||||
|
final updated = current.copyWith(
|
||||||
|
userLogins: current.userLogins.replaceFirstWhere(
|
||||||
|
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
|
||||||
|
(ul) => ul != null
|
||||||
|
? ul.copyWith(lastActive: now)
|
||||||
|
: UserLogin(
|
||||||
|
accountMasterKey: accountMasterRecordKey,
|
||||||
|
secretKey:
|
||||||
|
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||||
|
lastActive: now),
|
||||||
|
addIfNotFound: true),
|
||||||
|
activeUserLogin: accountMasterRecordKey);
|
||||||
|
await store(updated);
|
||||||
|
state = AsyncValue.data(updated);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logout() async {
|
||||||
|
final current = state.requireValue;
|
||||||
|
if (current.activeUserLogin == 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);
|
||||||
|
await store(updated);
|
||||||
|
state = AsyncValue.data(updated);
|
||||||
|
}
|
||||||
|
}
|
24
lib/providers/logins.g.dart
Normal file
24
lib/providers/logins.g.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'logins.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$loginsHash() => r'379514b8b6623d59cb89b4f128be5953d5ea62a6';
|
||||||
|
|
||||||
|
/// See also [Logins].
|
||||||
|
@ProviderFor(Logins)
|
||||||
|
final loginsProvider =
|
||||||
|
AutoDisposeAsyncNotifierProvider<Logins, ActiveLogins>.internal(
|
||||||
|
Logins.new,
|
||||||
|
name: r'loginsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$loginsHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef _$Logins = AutoDisposeAsyncNotifier<ActiveLogins>;
|
||||||
|
// 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
|
3
lib/providers/providers.dart
Normal file
3
lib/providers/providers.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export 'local_accounts.dart';
|
||||||
|
export 'connection_state.dart';
|
||||||
|
export 'logins.dart';
|
@ -1,3 +0,0 @@
|
|||||||
export 'local_accounts.dart';
|
|
||||||
|
|
||||||
// xxx rename this probably
|
|
@ -1,107 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:veilid/veilid.dart';
|
|
||||||
import '../entities/entities.dart';
|
|
||||||
|
|
||||||
part 'active_logins_state.g.dart';
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
class ActiveLoginsState extends _$ActiveLoginsState {
|
|
||||||
VeilidTableDB? _userLoginsTable;
|
|
||||||
ActiveLogins _activeLogins;
|
|
||||||
|
|
||||||
ActiveLoginsState() : _activeLogins = ActiveLogins.empty();
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<ActiveLogins> build() async {
|
|
||||||
_userLoginsTable ??= await Veilid.instance.openTableDB("login_state", 1);
|
|
||||||
_activeLogins =
|
|
||||||
(await _userLoginsTable!.loadStringJson(0, "active_logins") ??
|
|
||||||
ActiveLogins.empty()) as ActiveLogins;
|
|
||||||
_persistenceRefreshLogic();
|
|
||||||
return _activeLogins;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Log out of active user
|
|
||||||
Future<void> logout() async {
|
|
||||||
// If no user is active, then logout does nothing
|
|
||||||
if (_activeLogins.activeUserLogin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove userlogin and set the active user to logged out
|
|
||||||
final newUserLogins = _activeLogins.userLogins.removeWhere(
|
|
||||||
(ul) => _activeLogins.activeUserLogin == ul.accountMasterKey);
|
|
||||||
_activeLogins = _activeLogins.copyWith(
|
|
||||||
activeUserLogin: null, userLogins: newUserLogins);
|
|
||||||
|
|
||||||
// Report changed state
|
|
||||||
state = AsyncValue.data(_activeLogins);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Log all users
|
|
||||||
Future<void> logoutAll() async {
|
|
||||||
// If no user is active, then logout does nothing
|
|
||||||
if (_activeLogins.activeUserLogin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all userlogins and set the active user to logged out
|
|
||||||
_activeLogins = ActiveLogins.empty();
|
|
||||||
|
|
||||||
// Report changed state
|
|
||||||
state = AsyncValue.data(_activeLogins);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Log out specific user identified by its master public key
|
|
||||||
Future<void> logoutUser(TypedKey user) async {
|
|
||||||
// Remove userlogin and set the active user to logged out
|
|
||||||
final newUserLogins = _activeLogins.userLogins
|
|
||||||
.removeWhere((ul) => user == ul.accountMasterKey);
|
|
||||||
final newActiveUserLogin = _activeLogins.activeUserLogin == user
|
|
||||||
? null
|
|
||||||
: _activeLogins.activeUserLogin;
|
|
||||||
_activeLogins = ActiveLogins(
|
|
||||||
userLogins: newUserLogins, activeUserLogin: newActiveUserLogin);
|
|
||||||
|
|
||||||
// Report changed state
|
|
||||||
state = AsyncValue.data(_activeLogins);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt a login and if successful make that user active
|
|
||||||
Future<void> login(String publicKey, String password) async {
|
|
||||||
state = await AsyncValue.guard<User?>(() async {
|
|
||||||
return Future.delayed(
|
|
||||||
networkRoundTripTime,
|
|
||||||
() => _dummyUser,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal method used to listen authentication state changes.
|
|
||||||
/// When the auth object is in a loading state, nothing happens.
|
|
||||||
/// When the auth object is in a error state, we choose to remove the token
|
|
||||||
/// Otherwise, we expect the current auth value to be reflected in our persistence API
|
|
||||||
void _persistenceRefreshLogic() {
|
|
||||||
ref.listenSelf((_, next) {
|
|
||||||
if (next.isLoading) return;
|
|
||||||
if (next.hasError) {
|
|
||||||
sharedPreferences.remove(_sharedPrefsKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final val = next.requireValue;
|
|
||||||
final isAuthenticated = val == null;
|
|
||||||
|
|
||||||
isAuthenticated
|
|
||||||
? sharedPreferences.remove(_sharedPrefsKey)
|
|
||||||
: sharedPreferences.setString(_sharedPrefsKey, val.publicKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UnauthorizedException implements Exception {
|
|
||||||
final String message;
|
|
||||||
const UnauthorizedException(this.message);
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'active_logins_state.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$activeLoginsStateHash() => r'9b8795055e21f15f8fbf13534365725591311cf4';
|
|
||||||
|
|
||||||
/// See also [ActiveLoginsState].
|
|
||||||
@ProviderFor(ActiveLoginsState)
|
|
||||||
final activeLoginsStateProvider =
|
|
||||||
AutoDisposeAsyncNotifierProvider<ActiveLoginsState, ActiveLogins>.internal(
|
|
||||||
ActiveLoginsState.new,
|
|
||||||
name: r'activeLoginsStateProvider',
|
|
||||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$activeLoginsStateHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$ActiveLoginsState = AutoDisposeAsyncNotifier<ActiveLogins>;
|
|
||||||
// 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
|
|
@ -1,2 +0,0 @@
|
|||||||
export 'connection_state.dart';
|
|
||||||
export 'active_logins_state.dart';
|
|
@ -1,53 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:veilid/veilid.dart';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'tools.dart';
|
|
||||||
|
|
||||||
typedef DHTRecordEncryptionFactory = DHTRecordEncryption Function(DHTRecord);
|
|
||||||
|
|
||||||
abstract class DHTRecordEncryption {
|
|
||||||
factory DHTRecordEncryption.private(DHTRecord record) {
|
|
||||||
return _DHTRecordEncryptionPrivate(record);
|
|
||||||
}
|
|
||||||
factory DHTRecordEncryption.public(DHTRecord record) {
|
|
||||||
return _DHTRecordEncryptionPublic(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey);
|
|
||||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey);
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////
|
|
||||||
/// Private DHT Record: Encrypted with the owner's secret key
|
|
||||||
class _DHTRecordEncryptionPrivate implements DHTRecordEncryption {
|
|
||||||
_DHTRecordEncryptionPrivate(DHTRecord record) {
|
|
||||||
// xxx derive key from record
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////
|
|
||||||
/// Public DHT Record: No encryption
|
|
||||||
class _DHTRecordEncryptionPublic implements DHTRecordEncryption {
|
|
||||||
_DHTRecordEncryptionPublic(DHTRecord record) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:veilid/veilid.dart';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
export 'external_stream_state.dart';
|
export 'external_stream_state.dart';
|
||||||
export 'dht_record.dart';
|
|
||||||
export 'dht_record_encryption.dart';
|
|
||||||
export 'json_tools.dart';
|
export 'json_tools.dart';
|
||||||
export 'phono_byte.dart';
|
export 'phono_byte.dart';
|
||||||
export 'protobuf_tools.dart';
|
export 'protobuf_tools.dart';
|
||||||
export 'table_db.dart';
|
|
||||||
|
@ -1,60 +1,68 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart';
|
import 'package:protobuf/protobuf.dart';
|
||||||
import 'package:veilid/veilid.dart';
|
import 'package:veilid/veilid.dart';
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'tools.dart';
|
import 'veilid_support.dart';
|
||||||
|
import '../tools/tools.dart';
|
||||||
|
|
||||||
class DHTRecord {
|
class DHTRecord {
|
||||||
final VeilidRoutingContext _dhtctx;
|
final VeilidRoutingContext _dhtctx;
|
||||||
final DHTRecordDescriptor _recordDescriptor;
|
final DHTRecordDescriptor _recordDescriptor;
|
||||||
final int _defaultSubkey;
|
final int _defaultSubkey;
|
||||||
final KeyPair? _writer;
|
final KeyPair? _writer;
|
||||||
late final DHTRecordEncryption _encryption;
|
DHTRecordCrypto _crypto;
|
||||||
|
|
||||||
static Future<DHTRecord> create(VeilidRoutingContext dhtctx,
|
static Future<DHTRecord> create(VeilidRoutingContext dhtctx,
|
||||||
{DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
|
{DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
|
||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
DHTRecordEncryptionFactory crypto = DHTRecordEncryption.private}) async {
|
DHTRecordCrypto? crypto}) async {
|
||||||
DHTRecordDescriptor recordDescriptor = await dhtctx.createDHTRecord(schema);
|
DHTRecordDescriptor recordDescriptor = await dhtctx.createDHTRecord(schema);
|
||||||
|
|
||||||
final rec = DHTRecord(
|
final rec = DHTRecord(
|
||||||
dhtctx: dhtctx,
|
dhtctx: dhtctx,
|
||||||
recordDescriptor: recordDescriptor,
|
recordDescriptor: recordDescriptor,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
writer: recordDescriptor.ownerKeyPair());
|
writer: recordDescriptor.ownerKeyPair(),
|
||||||
|
crypto: crypto ??
|
||||||
rec._encryption = crypto(rec);
|
await DHTRecordCryptoPrivate.fromTypedKeyPair(
|
||||||
|
recordDescriptor.ownerTypedKeyPair()!));
|
||||||
|
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<DHTRecord> openRead(
|
static Future<DHTRecord> openRead(
|
||||||
VeilidRoutingContext dhtctx, TypedKey recordKey,
|
VeilidRoutingContext dhtctx, TypedKey recordKey,
|
||||||
{int defaultSubkey = 0,
|
{int defaultSubkey = 0, DHTRecordCrypto? crypto}) async {
|
||||||
DHTRecordEncryptionFactory crypto = DHTRecordEncryption.private}) async {
|
|
||||||
DHTRecordDescriptor recordDescriptor =
|
DHTRecordDescriptor recordDescriptor =
|
||||||
await dhtctx.openDHTRecord(recordKey, null);
|
await dhtctx.openDHTRecord(recordKey, null);
|
||||||
final rec = DHTRecord(
|
final rec = DHTRecord(
|
||||||
dhtctx: dhtctx,
|
dhtctx: dhtctx,
|
||||||
recordDescriptor: recordDescriptor,
|
recordDescriptor: recordDescriptor,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
writer: null);
|
writer: null,
|
||||||
|
crypto: crypto ?? const DHTRecordCryptoPublic());
|
||||||
|
|
||||||
rec._encryption = crypto(rec);
|
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<DHTRecord> openWrite(
|
static Future<DHTRecord> openWrite(
|
||||||
VeilidRoutingContext dhtctx, TypedKey recordKey, KeyPair writer,
|
VeilidRoutingContext dhtctx,
|
||||||
{int defaultSubkey = 0,
|
TypedKey recordKey,
|
||||||
DHTRecordEncryptionFactory crypto = DHTRecordEncryption.private}) async {
|
KeyPair writer, {
|
||||||
|
int defaultSubkey = 0,
|
||||||
|
DHTRecordCrypto? crypto,
|
||||||
|
}) async {
|
||||||
DHTRecordDescriptor recordDescriptor =
|
DHTRecordDescriptor recordDescriptor =
|
||||||
await dhtctx.openDHTRecord(recordKey, writer);
|
await dhtctx.openDHTRecord(recordKey, writer);
|
||||||
final rec = DHTRecord(
|
final rec = DHTRecord(
|
||||||
dhtctx: dhtctx,
|
dhtctx: dhtctx,
|
||||||
recordDescriptor: recordDescriptor,
|
recordDescriptor: recordDescriptor,
|
||||||
defaultSubkey: defaultSubkey,
|
defaultSubkey: defaultSubkey,
|
||||||
writer: writer);
|
writer: writer,
|
||||||
rec._encryption = crypto(rec);
|
crypto: crypto ??
|
||||||
|
await DHTRecordCryptoPrivate.fromTypedKeyPair(
|
||||||
|
TypedKeyPair.fromKeyPair(recordKey.kind, writer)));
|
||||||
return rec;
|
return rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,11 +70,13 @@ class DHTRecord {
|
|||||||
{required VeilidRoutingContext dhtctx,
|
{required VeilidRoutingContext dhtctx,
|
||||||
required DHTRecordDescriptor recordDescriptor,
|
required DHTRecordDescriptor recordDescriptor,
|
||||||
int defaultSubkey = 0,
|
int defaultSubkey = 0,
|
||||||
KeyPair? writer})
|
KeyPair? writer,
|
||||||
|
DHTRecordCrypto crypto = const DHTRecordCryptoPublic()})
|
||||||
: _dhtctx = dhtctx,
|
: _dhtctx = dhtctx,
|
||||||
_recordDescriptor = recordDescriptor,
|
_recordDescriptor = recordDescriptor,
|
||||||
_defaultSubkey = defaultSubkey,
|
_defaultSubkey = defaultSubkey,
|
||||||
_writer = writer;
|
_writer = writer,
|
||||||
|
_crypto = crypto;
|
||||||
|
|
||||||
int subkeyOrDefault(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
|
int subkeyOrDefault(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
|
||||||
|
|
||||||
@ -86,6 +96,10 @@ class DHTRecord {
|
|||||||
return _writer;
|
return _writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCrypto(DHTRecordCrypto crypto) {
|
||||||
|
_crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _dhtctx.closeDHTRecord(_recordDescriptor.key);
|
await _dhtctx.closeDHTRecord(_recordDescriptor.key);
|
||||||
}
|
}
|
||||||
@ -120,7 +134,7 @@ class DHTRecord {
|
|||||||
if (valueData == null) {
|
if (valueData == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return _encryption.decrypt(valueData.data, subkey);
|
return _crypto.decrypt(valueData.data, subkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> getJson<T>(T Function(Map<String, dynamic>) fromJson,
|
Future<T?> getJson<T>(T Function(Map<String, dynamic>) fromJson,
|
||||||
@ -134,7 +148,7 @@ class DHTRecord {
|
|||||||
|
|
||||||
Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
|
Future<void> eventualWriteBytes(Uint8List newValue, {int subkey = -1}) async {
|
||||||
subkey = subkeyOrDefault(subkey);
|
subkey = subkeyOrDefault(subkey);
|
||||||
newValue = await _encryption.encrypt(newValue, subkey);
|
newValue = await _crypto.encrypt(newValue, subkey);
|
||||||
// Get existing identity key
|
// Get existing identity key
|
||||||
ValueData? valueData;
|
ValueData? valueData;
|
||||||
do {
|
do {
|
||||||
@ -165,9 +179,9 @@ class DHTRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the data
|
// Update the data
|
||||||
final oldData = await _encryption.decrypt(valueData.data, subkey);
|
final oldData = await _crypto.decrypt(valueData.data, subkey);
|
||||||
final updatedData = await update(oldData);
|
final updatedData = await update(oldData);
|
||||||
final newData = await _encryption.encrypt(updatedData, subkey);
|
final newData = await _crypto.encrypt(updatedData, subkey);
|
||||||
|
|
||||||
// Set it back
|
// Set it back
|
||||||
valueData =
|
valueData =
|
73
lib/veilid_support/dht_record_crypto.dart
Normal file
73
lib/veilid_support/dht_record_crypto.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
abstract class DHTRecordCrypto {
|
||||||
|
FutureOr<Uint8List> encrypt(Uint8List data, int subkey);
|
||||||
|
FutureOr<Uint8List> decrypt(Uint8List data, int subkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
/// Private DHT Record: Encrypted for a specific symmetric key
|
||||||
|
class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||||
|
final VeilidCryptoSystem _cryptoSystem;
|
||||||
|
final SharedSecret _secretKey;
|
||||||
|
|
||||||
|
DHTRecordCryptoPrivate._(
|
||||||
|
VeilidCryptoSystem cryptoSystem, SharedSecret secretKey)
|
||||||
|
: _cryptoSystem = cryptoSystem,
|
||||||
|
_secretKey = secretKey;
|
||||||
|
|
||||||
|
static Future<DHTRecordCryptoPrivate> fromTypedKeyPair(
|
||||||
|
TypedKeyPair typedKeyPair) async {
|
||||||
|
final cryptoSystem =
|
||||||
|
await Veilid.instance.getCryptoSystem(typedKeyPair.kind);
|
||||||
|
final secretKey = typedKeyPair.secret;
|
||||||
|
return DHTRecordCryptoPrivate._(cryptoSystem, secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<DHTRecordCryptoPrivate> fromSecret(
|
||||||
|
CryptoKind kind, SharedSecret secretKey) async {
|
||||||
|
final cryptoSystem = await Veilid.instance.getCryptoSystem(kind);
|
||||||
|
return DHTRecordCryptoPrivate._(cryptoSystem, secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) async {
|
||||||
|
// generate nonce
|
||||||
|
final nonce = await _cryptoSystem.randomNonce();
|
||||||
|
// crypt and append nonce
|
||||||
|
return (await _cryptoSystem.cryptNoAuth(data, nonce, _secretKey))
|
||||||
|
..addAll(nonce.decode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) async {
|
||||||
|
// split off nonce from end
|
||||||
|
if (data.length <= Nonce.decodedLength()) {
|
||||||
|
throw const FormatException("not enough data to decrypt");
|
||||||
|
}
|
||||||
|
final nonce =
|
||||||
|
Nonce.fromBytes(data.sublist(data.length - Nonce.decodedLength()));
|
||||||
|
final encryptedData = data.sublist(0, data.length - Nonce.decodedLength());
|
||||||
|
// decrypt
|
||||||
|
return await _cryptoSystem.cryptNoAuth(encryptedData, nonce, _secretKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
/// Public DHT Record: No encryption
|
||||||
|
class DHTRecordCryptoPublic implements DHTRecordCrypto {
|
||||||
|
const DHTRecordCryptoPublic();
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
58
lib/veilid_support/identity_master.dart
Normal file
58
lib/veilid_support/identity_master.dart
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
|
||||||
|
import '../entities/identity.dart';
|
||||||
|
import 'veilid_support.dart';
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -3,7 +3,7 @@ import 'package:veilid/veilid.dart';
|
|||||||
import 'config.dart';
|
import 'config.dart';
|
||||||
import 'veilid_log.dart';
|
import 'veilid_log.dart';
|
||||||
import '../log/log.dart';
|
import '../log/log.dart';
|
||||||
import '../state/state.dart';
|
import '../providers/providers.dart';
|
||||||
|
|
||||||
class Processor {
|
class Processor {
|
||||||
String _veilidVersion = "";
|
String _veilidVersion = "";
|
||||||
|
@ -28,3 +28,25 @@ Future<T> transactionScope<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract mixin class AsyncTableDBBacked<T> {
|
||||||
|
String tableName();
|
||||||
|
String tableKeyName();
|
||||||
|
T reviveJson(Object? obj);
|
||||||
|
|
||||||
|
/// Load things from storage
|
||||||
|
Future<T> load() async {
|
||||||
|
final obj = await tableScope(tableName(), (tdb) async {
|
||||||
|
final objJson = await tdb.loadStringJson(0, tableKeyName());
|
||||||
|
return reviveJson(objJson);
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store things to storage
|
||||||
|
Future<void> store(T obj) async {
|
||||||
|
await tableScope(tableName(), (tdb) async {
|
||||||
|
await tdb.storeStringJson(0, tableKeyName(), obj);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
import 'package:veilid/veilid_encoding.dart';
|
|
||||||
|
|
||||||
//xxx
|
|
@ -1,5 +1,8 @@
|
|||||||
export 'config.dart';
|
export 'config.dart';
|
||||||
export 'processor.dart';
|
export 'processor.dart';
|
||||||
export 'tools.dart';
|
|
||||||
export 'veilid_log.dart';
|
export 'veilid_log.dart';
|
||||||
export 'init.dart';
|
export 'init.dart';
|
||||||
|
export 'dht_record.dart';
|
||||||
|
export 'dht_record_crypto.dart';
|
||||||
|
export 'table_db.dart';
|
||||||
|
export 'identity_master.dart';
|
||||||
|
Loading…
Reference in New Issue
Block a user