account work

This commit is contained in:
Christien Rioux 2023-07-25 01:04:34 -04:00
parent b7236befd1
commit b502bc20a7
20 changed files with 168 additions and 95 deletions

View File

@ -5,15 +5,23 @@
"app_bar": { "app_bar": {
"settings_tooltip": "Settings" "settings_tooltip": "Settings"
}, },
"account": {
"form_name": "Name",
"form_title": "Title (optional)",
"form_lock_type": "Lock Type",
"lock_type_none": "none",
"lock_type_pin": "pin",
"lock_type_password": "password"
},
"new_account_page": { "new_account_page": {
"titlebar": "Create a new account", "titlebar": "Create a new account",
"header": "Account Profile", "header": "Account Profile",
"form_name": "Name",
"form_title": "Title (optional)",
"create": "Create", "create": "Create",
"instructions": "This information will be shared with the people you invite to connect with you on VeilidChat." "instructions": "This information will be shared with the people you invite to connect with you on VeilidChat.",
"error": "Account creation error"
}, },
"button": { "button": {
"ok": "Ok",
"cancel": "Cancel", "cancel": "Cancel",
"change_language": "Change Language" "change_language": "Change Language"
}, },

View File

@ -15,12 +15,12 @@ class AccountRecordInfo with _$AccountRecordInfo {
required KeyPair owner, required KeyPair owner,
}) = _AccountRecordInfo; }) = _AccountRecordInfo;
factory AccountRecordInfo.fromJson(Map<String, dynamic> json) => factory AccountRecordInfo.fromJson(dynamic json) =>
_$AccountRecordInfoFromJson(json); _$AccountRecordInfoFromJson(json as Map<String, dynamic>);
} }
// Identity Key points to accounts associated with this identity // Identity Key points to accounts associated with this identity
// accounts field has a map of service name or uuid to account key pairs // accounts field has a map of bundle id or uuid to account key pairs
// DHT Schema: DFLT(1) // DHT Schema: DFLT(1)
// DHT Key (Private): identityRecordKey // DHT Key (Private): identityRecordKey
// DHT Owner Key: identityPublicKey // DHT Owner Key: identityPublicKey
@ -32,8 +32,8 @@ class Identity with _$Identity {
required IMap<String, ISet<AccountRecordInfo>> accountRecords, required IMap<String, ISet<AccountRecordInfo>> accountRecords,
}) = _Identity; }) = _Identity;
factory Identity.fromJson(Map<String, dynamic> json) => factory Identity.fromJson(dynamic json) =>
_$IdentityFromJson(json); _$IdentityFromJson(json as Map<String, dynamic>);
} }
// Identity Master key structure for created account // Identity Master key structure for created account
@ -66,8 +66,8 @@ class IdentityMaster with _$IdentityMaster {
// Signature of masterRecordKey and masterPublicKey by identityPublicKey // Signature of masterRecordKey and masterPublicKey by identityPublicKey
required Signature masterSignature}) = _IdentityMaster; required Signature masterSignature}) = _IdentityMaster;
factory IdentityMaster.fromJson(Map<String, dynamic> json) => factory IdentityMaster.fromJson(dynamic json) =>
_$IdentityMasterFromJson(json); _$IdentityMasterFromJson(json as Map<String, dynamic>);
} }
extension IdentityMasterExtension on IdentityMaster { extension IdentityMasterExtension on IdentityMaster {
@ -79,15 +79,3 @@ extension IdentityMasterExtension on IdentityMaster {
return KeyPair(key: masterPublicKey, secret: secret); return KeyPair(key: masterPublicKey, secret: secret);
} }
} }
// Identity Master with secrets
// Not freezed because we never persist this class in its entirety
class IdentityMasterWithSecrets {
IdentityMaster identityMaster;
SecretKey masterSecret;
SecretKey identitySecret;
IdentityMasterWithSecrets(
{required this.identityMaster,
required this.masterSecret,
required this.identitySecret});
}

View File

@ -24,9 +24,7 @@ _$_Identity _$$_IdentityFromJson(Map<String, dynamic> json) => _$_Identity(
json['account_records'] as Map<String, dynamic>, json['account_records'] as Map<String, dynamic>,
(value) => value as String, (value) => value as String,
(value) => ISet<AccountRecordInfo>.fromJson( (value) => ISet<AccountRecordInfo>.fromJson(
value, value, (value) => AccountRecordInfo.fromJson(value))),
(value) =>
AccountRecordInfo.fromJson(value as Map<String, dynamic>))),
); );
Map<String, dynamic> _$$_IdentityToJson(_$_Identity instance) => Map<String, dynamic> _$$_IdentityToJson(_$_Identity instance) =>

View File

@ -20,8 +20,8 @@ enum EncryptionKeyType {
password; password;
String toJson() => name.toPascalCase(); String toJson() => name.toPascalCase();
factory EncryptionKeyType.fromJson(String j) => factory EncryptionKeyType.fromJson(dynamic j) =>
EncryptionKeyType.values.byName(j.toCamelCase()); EncryptionKeyType.values.byName((j as String).toCamelCase());
} }
// Local Accounts are stored in a table locally and not backed by a DHT key // Local Accounts are stored in a table locally and not backed by a DHT key
@ -49,6 +49,6 @@ class LocalAccount with _$LocalAccount {
required bool hiddenAccount, required bool hiddenAccount,
}) = _LocalAccount; }) = _LocalAccount;
factory LocalAccount.fromJson(Map<String, dynamic> json) => factory LocalAccount.fromJson(dynamic json) =>
_$LocalAccountFromJson(json); _$LocalAccountFromJson(json as Map<String, dynamic>);
} }

View File

@ -8,14 +8,13 @@ part of 'local_account.dart';
_$_LocalAccount _$$_LocalAccountFromJson(Map<String, dynamic> json) => _$_LocalAccount _$$_LocalAccountFromJson(Map<String, dynamic> json) =>
_$_LocalAccount( _$_LocalAccount(
identityMaster: IdentityMaster.fromJson( identityMaster: IdentityMaster.fromJson(json['identity_master']),
json['identity_master'] as Map<String, dynamic>),
identitySecretKeyBytes: const Uint8ListJsonConverter() identitySecretKeyBytes: const Uint8ListJsonConverter()
.fromJson(json['identity_secret_key_bytes'] as String), .fromJson(json['identity_secret_key_bytes'] as String),
identitySecretSaltBytes: const Uint8ListJsonConverter() identitySecretSaltBytes: const Uint8ListJsonConverter()
.fromJson(json['identity_secret_salt_bytes'] as String), .fromJson(json['identity_secret_salt_bytes'] as String),
encryptionKeyType: encryptionKeyType:
EncryptionKeyType.fromJson(json['encryption_key_type'] as String), EncryptionKeyType.fromJson(json['encryption_key_type']),
biometricsEnabled: json['biometrics_enabled'] as bool, biometricsEnabled: json['biometrics_enabled'] as bool,
hiddenAccount: json['hidden_account'] as bool, hiddenAccount: json['hidden_account'] as bool,
); );

View File

@ -12,8 +12,8 @@ enum DarkModePreference {
dark; dark;
String toJson() => name.toPascalCase(); String toJson() => name.toPascalCase();
factory DarkModePreference.fromJson(String j) => factory DarkModePreference.fromJson(dynamic j) =>
DarkModePreference.values.byName(j.toCamelCase()); DarkModePreference.values.byName((j as String).toCamelCase());
} }
// Lock preference changes how frequently the messenger locks its // Lock preference changes how frequently the messenger locks its
@ -26,8 +26,8 @@ class LockPreference with _$LockPreference {
required bool lockWithSystemLock, required bool lockWithSystemLock,
}) = _LockPreference; }) = _LockPreference;
factory LockPreference.fromJson(Map<String, dynamic> json) => factory LockPreference.fromJson(dynamic json) =>
_$LockPreferenceFromJson(json); _$LockPreferenceFromJson(json as Map<String, dynamic>);
} }
// Theme supports multiple color variants based on 'Radix' // Theme supports multiple color variants based on 'Radix'
@ -62,8 +62,8 @@ enum ColorPreference {
yellow; yellow;
String toJson() => name.toPascalCase(); String toJson() => name.toPascalCase();
factory ColorPreference.fromJson(String j) => factory ColorPreference.fromJson(dynamic j) =>
ColorPreference.values.byName(j.toCamelCase()); ColorPreference.values.byName((j as String).toCamelCase());
} }
// Theme supports multiple translations // Theme supports multiple translations
@ -71,8 +71,8 @@ enum LanguagePreference {
englishUS; englishUS;
String toJson() => name.toPascalCase(); String toJson() => name.toPascalCase();
factory LanguagePreference.fromJson(String j) => factory LanguagePreference.fromJson(dynamic j) =>
LanguagePreference.values.byName(j.toCamelCase()); LanguagePreference.values.byName((j as String).toCamelCase());
} }
// Preferences are stored in a table locally and globally affect all // Preferences are stored in a table locally and globally affect all
@ -87,6 +87,6 @@ class Preferences with _$Preferences {
required LockPreference locking, required LockPreference locking,
}) = _Preferences; }) = _Preferences;
factory Preferences.fromJson(Map<String, dynamic> json) => factory Preferences.fromJson(dynamic json) =>
_$PreferencesFromJson(json); _$PreferencesFromJson(json as Map<String, dynamic>);
} }

View File

@ -22,11 +22,11 @@ Map<String, dynamic> _$$_LockPreferenceToJson(_$_LockPreference instance) =>
_$_Preferences _$$_PreferencesFromJson(Map<String, dynamic> json) => _$_Preferences _$$_PreferencesFromJson(Map<String, dynamic> json) =>
_$_Preferences( _$_Preferences(
darkMode: DarkModePreference.fromJson(json['dark_mode'] as String), darkMode: DarkModePreference.fromJson(json['dark_mode']),
themeColor: ColorPreference.fromJson(json['theme_color'] as String), themeColor: ColorPreference.fromJson(json['theme_color']),
language: LanguagePreference.fromJson(json['language'] as String), language: LanguagePreference.fromJson(json['language']),
displayScale: json['display_scale'] as int, displayScale: json['display_scale'] as int,
locking: LockPreference.fromJson(json['locking'] as Map<String, dynamic>), locking: LockPreference.fromJson(json['locking']),
); );
Map<String, dynamic> _$$_PreferencesToJson(_$_Preferences instance) => Map<String, dynamic> _$$_PreferencesToJson(_$_Preferences instance) =>

View File

@ -1038,7 +1038,7 @@ class Profile extends $pb.GeneratedMessage {
..aOS(2, _omitFieldNames ? '' : 'title') ..aOS(2, _omitFieldNames ? '' : 'title')
..aOS(3, _omitFieldNames ? '' : 'status') ..aOS(3, _omitFieldNames ? '' : 'status')
..e<Availability>(4, _omitFieldNames ? '' : 'availability', $pb.PbFieldType.OE, defaultOrMaker: Availability.AVAILABILITY_UNSPECIFIED, valueOf: Availability.valueOf, enumValues: Availability.values) ..e<Availability>(4, _omitFieldNames ? '' : 'availability', $pb.PbFieldType.OE, defaultOrMaker: Availability.AVAILABILITY_UNSPECIFIED, valueOf: Availability.valueOf, enumValues: Availability.values)
..aOM<TypedKey>(5, _omitFieldNames ? '' : 'icon', subBuilder: TypedKey.create) ..aOM<TypedKey>(5, _omitFieldNames ? '' : 'avatar', subBuilder: TypedKey.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -1100,15 +1100,15 @@ class Profile extends $pb.GeneratedMessage {
void clearAvailability() => clearField(4); void clearAvailability() => clearField(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
TypedKey get icon => $_getN(4); TypedKey get avatar => $_getN(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
set icon(TypedKey v) { setField(5, v); } set avatar(TypedKey v) { setField(5, v); }
@$pb.TagNumber(5) @$pb.TagNumber(5)
$core.bool hasIcon() => $_has(4); $core.bool hasAvatar() => $_has(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
void clearIcon() => clearField(5); void clearAvatar() => clearField(5);
@$pb.TagNumber(5) @$pb.TagNumber(5)
TypedKey ensureIcon() => $_ensure(4); TypedKey ensureAvatar() => $_ensure(4);
} }
class Account extends $pb.GeneratedMessage { class Account extends $pb.GeneratedMessage {

View File

@ -279,7 +279,10 @@ const Profile$json = {
{'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'}, {'1': 'title', '3': 2, '4': 1, '5': 9, '10': 'title'},
{'1': 'status', '3': 3, '4': 1, '5': 9, '10': 'status'}, {'1': 'status', '3': 3, '4': 1, '5': 9, '10': 'status'},
{'1': 'availability', '3': 4, '4': 1, '5': 14, '6': '.Availability', '10': 'availability'}, {'1': 'availability', '3': 4, '4': 1, '5': 14, '6': '.Availability', '10': 'availability'},
{'1': 'icon', '3': 5, '4': 1, '5': 11, '6': '.TypedKey', '10': 'icon'}, {'1': 'avatar', '3': 5, '4': 1, '5': 11, '6': '.TypedKey', '9': 0, '10': 'avatar', '17': true},
],
'8': [
{'1': '_avatar'},
], ],
}; };
@ -287,7 +290,8 @@ const Profile$json = {
final $typed_data.Uint8List profileDescriptor = $convert.base64Decode( final $typed_data.Uint8List profileDescriptor = $convert.base64Decode(
'CgdQcm9maWxlEhIKBG5hbWUYASABKAlSBG5hbWUSFAoFdGl0bGUYAiABKAlSBXRpdGxlEhYKBn' 'CgdQcm9maWxlEhIKBG5hbWUYASABKAlSBG5hbWUSFAoFdGl0bGUYAiABKAlSBXRpdGxlEhYKBn'
'N0YXR1cxgDIAEoCVIGc3RhdHVzEjEKDGF2YWlsYWJpbGl0eRgEIAEoDjINLkF2YWlsYWJpbGl0' 'N0YXR1cxgDIAEoCVIGc3RhdHVzEjEKDGF2YWlsYWJpbGl0eRgEIAEoDjINLkF2YWlsYWJpbGl0'
'eVIMYXZhaWxhYmlsaXR5Eh0KBGljb24YBSABKAsyCS5UeXBlZEtleVIEaWNvbg=='); 'eVIMYXZhaWxhYmlsaXR5EiYKBmF2YXRhchgFIAEoCzIJLlR5cGVkS2V5SABSBmF2YXRhcogBAU'
'IJCgdfYXZhdGFy');
@$core.Deprecated('Use accountDescriptor instead') @$core.Deprecated('Use accountDescriptor instead')
const Account$json = { const Account$json = {

View File

@ -19,8 +19,8 @@ class UserLogin with _$UserLogin {
required Timestamp lastActive, required Timestamp lastActive,
}) = _UserLogin; }) = _UserLogin;
factory UserLogin.fromJson(Map<String, dynamic> json) => factory UserLogin.fromJson(dynamic json) =>
_$UserLoginFromJson(json); _$UserLoginFromJson(json as Map<String, dynamic>);
} }
// Represents a set of user logins // Represents a set of user logins
@ -37,6 +37,6 @@ class ActiveLogins with _$ActiveLogins {
factory ActiveLogins.empty() => factory ActiveLogins.empty() =>
const ActiveLogins(userLogins: IListConst([])); const ActiveLogins(userLogins: IListConst([]));
factory ActiveLogins.fromJson(Map<String, dynamic> json) => factory ActiveLogins.fromJson(dynamic json) =>
_$ActiveLoginsFromJson(json); _$ActiveLoginsFromJson(json as Map<String, dynamic>);
} }

View File

@ -23,8 +23,8 @@ Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) =>
_$_ActiveLogins _$$_ActiveLoginsFromJson(Map<String, dynamic> json) => _$_ActiveLogins _$$_ActiveLoginsFromJson(Map<String, dynamic> json) =>
_$_ActiveLogins( _$_ActiveLogins(
userLogins: IList<UserLogin>.fromJson(json['user_logins'], userLogins: IList<UserLogin>.fromJson(
(value) => UserLogin.fromJson(value as Map<String, dynamic>)), json['user_logins'], (value) => UserLogin.fromJson(value)),
activeUserLogin: json['active_user_login'] == null activeUserLogin: json['active_user_login'] == null
? null ? null
: Typed<FixedEncodedString43>.fromJson(json['active_user_login']), : Typed<FixedEncodedString43>.fromJson(json['active_user_login']),

View File

@ -196,7 +196,7 @@ message Contact {
bool show_availability = 6; bool show_availability = 6;
} }
// Contact availability as specified by the // Contact availability
enum Availability { enum Availability {
AVAILABILITY_UNSPECIFIED = 0; AVAILABILITY_UNSPECIFIED = 0;
AVAILABILITY_OFFLINE = 1; AVAILABILITY_OFFLINE = 1;
@ -222,8 +222,8 @@ message Profile {
string status = 3; string status = 3;
// Availability // Availability
Availability availability = 4; Availability availability = 4;
// Icon DHTData // Avatar DHTData
TypedKey icon = 5; optional TypedKey avatar = 5;
} }
// A record of an individual account // A record of an individual account

View File

@ -1,15 +1,17 @@
import 'dart:io';
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:quickalert/quickalert.dart';
import '../components/default_app_bar.dart'; import '../components/default_app_bar.dart';
import '../entities/proto.dart' as proto;
import '../providers/local_accounts.dart'; import '../providers/local_accounts.dart';
import '../providers/logins.dart';
import '../tools/tools.dart'; import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart';
class NewAccountPage extends ConsumerStatefulWidget { class NewAccountPage extends ConsumerStatefulWidget {
const NewAccountPage({super.key}); const NewAccountPage({super.key});
@ -24,6 +26,34 @@ class NewAccountPage extends ConsumerStatefulWidget {
class NewAccountPageState extends ConsumerState<NewAccountPage> { class NewAccountPageState extends ConsumerState<NewAccountPage> {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
late bool isInAsyncCall = false; late bool isInAsyncCall = false;
static const String formFieldName = "name";
static const String formFieldTitle = "title";
Future<void> createAccount() async {
final imws = await newIdentityMaster();
try {
final localAccounts = ref.read(localAccountsProvider.notifier);
final logins = ref.read(loginsProvider.notifier);
final profile = proto.Profile();
profile.name = _formKey.currentState!.fields[formFieldName]!.value;
profile.title = _formKey.currentState!.fields[formFieldTitle]!.value;
final account = proto.Account();
account.profile = profile;
final localAccount = await localAccounts.newAccount(
identityMaster: imws.identityMaster,
identitySecret: imws.identitySecret,
account: account);
// Log in the new account by default with no pin
final ok = await logins
.loginWithNone(localAccount.identityMaster.masterRecordKey);
assert(ok == true);
} catch (e) {
await imws.delete();
rethrow;
}
}
Widget _newAccountForm(BuildContext context, Widget _newAccountForm(BuildContext context,
{required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) { {required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) {
@ -36,9 +66,9 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
.paddingSymmetric(vertical: 16), .paddingSymmetric(vertical: 16),
FormBuilderTextField( FormBuilderTextField(
autofocus: true, autofocus: true,
name: 'name', name: formFieldName,
decoration: InputDecoration( decoration:
hintText: translate("new_account_page.form_name")), InputDecoration(hintText: translate("account.form_name")),
maxLength: 64, maxLength: 64,
// The validator receives the text that the user has entered. // The validator receives the text that the user has entered.
validator: FormBuilderValidators.compose([ validator: FormBuilderValidators.compose([
@ -46,10 +76,10 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
]), ]),
), ),
FormBuilderTextField( FormBuilderTextField(
name: 'title', name: formFieldTitle,
maxLength: 64, maxLength: 64,
decoration: InputDecoration( decoration:
hintText: translate("new_account_page.form_title")), InputDecoration(hintText: translate("account.form_title")),
), ),
Row(children: [ Row(children: [
const Spacer(), const Spacer(),
@ -85,7 +115,8 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
enableTitleBar(true); enableTitleBar(true);
portraitOnly(); portraitOnly();
final localAccounts = ref.watch(localAccountsProvider); // final localAccountsData = ref.watch(localAccountsProvider);
final displayModalHUD = isInAsyncCall; // || !localAccountsData.hasValue;
return Scaffold( return Scaffold(
// resizeToAvoidBottomInset: false, // resizeToAvoidBottomInset: false,
@ -96,9 +127,21 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
onSubmit: (formKey) async { onSubmit: (formKey) async {
debugPrint(_formKey.currentState?.value.toString()); debugPrint(_formKey.currentState?.value.toString());
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
await Future.delayed(Duration(seconds: 5)); try {
await createAccount();
} catch (e) {
QuickAlert.show(
context: context,
type: QuickAlertType.error,
title: translate("new_account_page.error"),
text: 'Exception: ${e.toString()}',
//backgroundColor: Colors.black,
//titleColor: Colors.white,
//textColor: Colors.white,
);
}
}, },
).paddingSymmetric(horizontal: 24, vertical: 8), ).paddingSymmetric(horizontal: 24, vertical: 8),
).withModalHUD(context, isInAsyncCall); ).withModalHUD(context, displayModalHUD);
} }
} }

View File

@ -52,11 +52,11 @@ class LocalAccounts extends _$LocalAccounts
/// Creates a new account associated with master identity /// Creates a new account associated with master identity
Future<LocalAccount> newAccount( Future<LocalAccount> newAccount(
IdentityMaster identityMaster, {required IdentityMaster identityMaster,
SecretKey identitySecret, required SecretKey identitySecret,
EncryptionKeyType encryptionKeyType, EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey, String encryptionKey = "",
proto.Account account) async { required proto.Account account}) async {
final veilid = await eventualVeilid.future; final veilid = await eventualVeilid.future;
final localAccounts = state.requireValue; final localAccounts = state.requireValue;
@ -114,7 +114,7 @@ class LocalAccounts extends _$LocalAccounts
await identityRec.eventualUpdateJson(Identity.fromJson, await identityRec.eventualUpdateJson(Identity.fromJson,
(oldIdentity) async { (oldIdentity) async {
final accountRecords = IMapOfSets.from(oldIdentity.accountRecords) final accountRecords = IMapOfSets.from(oldIdentity.accountRecords)
.add("VeilidChat", newAccountRecordInfo) .add("com.veilid.veilidchat", newAccountRecordInfo)
.asIMap(); .asIMap();
return oldIdentity.copyWith(accountRecords: accountRecords); return oldIdentity.copyWith(accountRecords: accountRecords);
}); });

View File

@ -6,7 +6,7 @@ part of 'local_accounts.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$localAccountsHash() => r'1faa6b22284a402e4f47b2629e54a39ffda9a4ad'; String _$localAccountsHash() => r'37ed2ab40b6ed9063c7d1d00f067b7006c9a7670';
/// See also [LocalAccounts]. /// See also [LocalAccounts].
@ProviderFor(LocalAccounts) @ProviderFor(LocalAccounts)

View File

@ -152,11 +152,6 @@ class DHTRecord {
// Get existing identity key // Get existing identity key
ValueData? valueData; ValueData? valueData;
do { do {
// Ensure it exists already
if (valueData == null) {
throw const FormatException("value does not exist");
}
// Set the new data // Set the new data
valueData = valueData =
await _dhtctx.setDHTValue(_recordDescriptor.key, subkey, newValue); await _dhtctx.setDHTValue(_recordDescriptor.key, subkey, newValue);

View File

@ -41,8 +41,10 @@ class DHTRecordCryptoPrivate implements DHTRecordCrypto {
// generate nonce // generate nonce
final nonce = await _cryptoSystem.randomNonce(); final nonce = await _cryptoSystem.randomNonce();
// crypt and append nonce // crypt and append nonce
return (await _cryptoSystem.cryptNoAuth(data, nonce, _secretKey)) var b = BytesBuilder();
..addAll(nonce.decode()); b.add(await _cryptoSystem.cryptNoAuth(data, nonce, _secretKey));
b.add(nonce.decode());
return b.toBytes();
} }
@override @override

View File

@ -1,13 +1,35 @@
import 'dart:typed_data';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import '../entities/identity.dart'; import '../entities/identity.dart';
import 'veilid_support.dart'; import 'veilid_support.dart';
// Identity Master with secrets
// Not freezed because we never persist this class in its entirety
class IdentityMasterWithSecrets {
IdentityMaster identityMaster;
SecretKey masterSecret;
SecretKey identitySecret;
IdentityMasterWithSecrets(
{required this.identityMaster,
required this.masterSecret,
required this.identitySecret});
Future<void> delete() async {
final veilid = await eventualVeilid.future;
final dhtctx = (await veilid.routingContext())
.withPrivacy()
.withSequencing(Sequencing.ensureOrdered);
await dhtctx.deleteDHTRecord(identityMaster.masterRecordKey);
await dhtctx.deleteDHTRecord(identityMaster.identityRecordKey);
}
}
/// 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 {
final veilid = await eventualVeilid.future; final veilid = await eventualVeilid.future;
final crypto = await veilid.bestCryptoSystem();
final dhtctx = (await veilid.routingContext()) final dhtctx = (await veilid.routingContext())
.withPrivacy() .withPrivacy()
.withSequencing(Sequencing.ensureOrdered); .withSequencing(Sequencing.ensureOrdered);
@ -20,18 +42,23 @@ Future<IdentityMasterWithSecrets> newIdentityMaster() async {
// Make IdentityMaster // Make IdentityMaster
final masterRecordKey = masterRec.key(); final masterRecordKey = masterRec.key();
final masterOwner = masterRec.ownerKeyPair()!; final masterOwner = masterRec.ownerKeyPair()!;
final masterSigBuf = masterRecordKey.decode() final masterSigBuf = BytesBuilder();
..addAll(masterOwner.key.decode()); masterSigBuf.add(masterRecordKey.decode());
masterSigBuf.add(masterOwner.key.decode());
final identityRecordKey = identityRec.key(); final identityRecordKey = identityRec.key();
final identityOwner = identityRec.ownerKeyPair()!; final identityOwner = identityRec.ownerKeyPair()!;
final identitySigBuf = identityRecordKey.decode() final identitySigBuf = BytesBuilder();
..addAll(identityOwner.key.decode()); identitySigBuf.add(identityRecordKey.decode());
identitySigBuf.add(identityOwner.key.decode());
assert(masterRecordKey.kind == identityRecordKey.kind);
final crypto = await veilid.getCryptoSystem(masterRecordKey.kind);
final identitySignature = final identitySignature =
await crypto.signWithKeyPair(masterOwner, identitySigBuf); await crypto.signWithKeyPair(masterOwner, identitySigBuf.toBytes());
final masterSignature = final masterSignature =
await crypto.signWithKeyPair(identityOwner, masterSigBuf); await crypto.signWithKeyPair(identityOwner, masterSigBuf.toBytes());
final identityMaster = IdentityMaster( final identityMaster = IdentityMaster(
identityRecordKey: identityRecordKey, identityRecordKey: identityRecordKey,

View File

@ -813,6 +813,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.3" version: "1.2.3"
quickalert:
dependency: "direct main"
description:
name: quickalert
sha256: "33a52870b2a87c55d0649d0cd228efaa2368d5df39231fdecebb71f349a9b221"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
radix_colors: radix_colors:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -49,6 +49,7 @@ dependencies:
form_builder_validators: ^9.0.0 form_builder_validators: ^9.0.0
blurry_modal_progress_hud: ^1.1.0 blurry_modal_progress_hud: ^1.1.0
flutter_spinkit: ^5.2.0 flutter_spinkit: ^5.2.0
quickalert: ^1.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: