mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-09 22:52:29 -04:00
big identity refactor
This commit is contained in:
parent
bae58d5f5c
commit
ddc02f6771
46 changed files with 2143 additions and 1300 deletions
|
@ -14,14 +14,18 @@ class ActiveAccountInfo {
|
|||
});
|
||||
//
|
||||
|
||||
TypedKey get superIdentityRecordKey => localAccount.superIdentity.recordKey;
|
||||
TypedKey get accountRecordKey =>
|
||||
userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
KeyPair get conversationWriter {
|
||||
final identityKey = localAccount.identityMaster.identityPublicKey;
|
||||
final identitySecret = userLogin.identitySecret;
|
||||
return KeyPair(key: identityKey, secret: identitySecret.value);
|
||||
}
|
||||
TypedKey get identityTypedPublicKey =>
|
||||
localAccount.superIdentity.currentInstance.typedPublicKey;
|
||||
PublicKey get identityPublicKey =>
|
||||
localAccount.superIdentity.currentInstance.publicKey;
|
||||
SecretKey get identitySecretKey => userLogin.identitySecret.value;
|
||||
KeyPair get identityWriter =>
|
||||
KeyPair(key: identityPublicKey, secret: identitySecretKey);
|
||||
Future<VeilidCryptoSystem> get identityCryptoSystem =>
|
||||
localAccount.superIdentity.currentInstance.cryptoSystem;
|
||||
|
||||
Future<VeilidCrypto> makeConversationCrypto(
|
||||
TypedKey remoteIdentityPublicKey) async {
|
||||
|
|
|
@ -11,25 +11,31 @@ part 'local_account.freezed.dart';
|
|||
// Local Accounts are stored in a table locally and not backed by a DHT key
|
||||
// and represents the accounts that have been added/imported
|
||||
// on the current device.
|
||||
// Stores a copy of the IdentityMaster associated with the account
|
||||
// Stores a copy of the most recent SuperIdentity associated with the account
|
||||
// and the identitySecretKey optionally encrypted by an unlock code
|
||||
// This is the root of the account information tree for VeilidChat
|
||||
//
|
||||
@freezed
|
||||
class LocalAccount with _$LocalAccount {
|
||||
const factory LocalAccount({
|
||||
// The master key record for the account, containing the identityPublicKey
|
||||
required IdentityMaster identityMaster,
|
||||
// The encrypted identity secret that goes with
|
||||
// The super identity key record for the account,
|
||||
// containing the publicKey in the currentIdentity
|
||||
required SuperIdentity superIdentity,
|
||||
|
||||
// The encrypted currentIdentity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter() required Uint8List identitySecretBytes,
|
||||
|
||||
// The kind of encryption input used on the account
|
||||
required EncryptionKeyType encryptionKeyType,
|
||||
|
||||
// If account is not hidden, password can be retrieved via
|
||||
required bool biometricsEnabled,
|
||||
|
||||
// Keep account hidden unless account password is entered
|
||||
// (tries all hidden accounts with auth method (no biometrics))
|
||||
required bool hiddenAccount,
|
||||
|
||||
// Display name for account until it is unlocked
|
||||
required String name,
|
||||
}) = _LocalAccount;
|
||||
|
|
|
@ -20,9 +20,10 @@ LocalAccount _$LocalAccountFromJson(Map<String, dynamic> json) {
|
|||
|
||||
/// @nodoc
|
||||
mixin _$LocalAccount {
|
||||
// The master key record for the account, containing the identityPublicKey
|
||||
IdentityMaster get identityMaster =>
|
||||
throw _privateConstructorUsedError; // The encrypted identity secret that goes with
|
||||
// The super identity key record for the account,
|
||||
// containing the publicKey in the currentIdentity
|
||||
SuperIdentity get superIdentity =>
|
||||
throw _privateConstructorUsedError; // The encrypted currentIdentity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter()
|
||||
Uint8List get identitySecretBytes =>
|
||||
|
@ -49,14 +50,14 @@ abstract class $LocalAccountCopyWith<$Res> {
|
|||
_$LocalAccountCopyWithImpl<$Res, LocalAccount>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{IdentityMaster identityMaster,
|
||||
{SuperIdentity superIdentity,
|
||||
@Uint8ListJsonConverter() Uint8List identitySecretBytes,
|
||||
EncryptionKeyType encryptionKeyType,
|
||||
bool biometricsEnabled,
|
||||
bool hiddenAccount,
|
||||
String name});
|
||||
|
||||
$IdentityMasterCopyWith<$Res> get identityMaster;
|
||||
$SuperIdentityCopyWith<$Res> get superIdentity;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
@ -72,7 +73,7 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
|
|||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? identityMaster = null,
|
||||
Object? superIdentity = null,
|
||||
Object? identitySecretBytes = null,
|
||||
Object? encryptionKeyType = null,
|
||||
Object? biometricsEnabled = null,
|
||||
|
@ -80,10 +81,10 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
|
|||
Object? name = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
identityMaster: null == identityMaster
|
||||
? _value.identityMaster
|
||||
: identityMaster // ignore: cast_nullable_to_non_nullable
|
||||
as IdentityMaster,
|
||||
superIdentity: null == superIdentity
|
||||
? _value.superIdentity
|
||||
: superIdentity // ignore: cast_nullable_to_non_nullable
|
||||
as SuperIdentity,
|
||||
identitySecretBytes: null == identitySecretBytes
|
||||
? _value.identitySecretBytes
|
||||
: identitySecretBytes // ignore: cast_nullable_to_non_nullable
|
||||
|
@ -109,9 +110,9 @@ class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
|
|||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$IdentityMasterCopyWith<$Res> get identityMaster {
|
||||
return $IdentityMasterCopyWith<$Res>(_value.identityMaster, (value) {
|
||||
return _then(_value.copyWith(identityMaster: value) as $Val);
|
||||
$SuperIdentityCopyWith<$Res> get superIdentity {
|
||||
return $SuperIdentityCopyWith<$Res>(_value.superIdentity, (value) {
|
||||
return _then(_value.copyWith(superIdentity: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +126,7 @@ abstract class _$$LocalAccountImplCopyWith<$Res>
|
|||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{IdentityMaster identityMaster,
|
||||
{SuperIdentity superIdentity,
|
||||
@Uint8ListJsonConverter() Uint8List identitySecretBytes,
|
||||
EncryptionKeyType encryptionKeyType,
|
||||
bool biometricsEnabled,
|
||||
|
@ -133,7 +134,7 @@ abstract class _$$LocalAccountImplCopyWith<$Res>
|
|||
String name});
|
||||
|
||||
@override
|
||||
$IdentityMasterCopyWith<$Res> get identityMaster;
|
||||
$SuperIdentityCopyWith<$Res> get superIdentity;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
@ -147,7 +148,7 @@ class __$$LocalAccountImplCopyWithImpl<$Res>
|
|||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? identityMaster = null,
|
||||
Object? superIdentity = null,
|
||||
Object? identitySecretBytes = null,
|
||||
Object? encryptionKeyType = null,
|
||||
Object? biometricsEnabled = null,
|
||||
|
@ -155,10 +156,10 @@ class __$$LocalAccountImplCopyWithImpl<$Res>
|
|||
Object? name = null,
|
||||
}) {
|
||||
return _then(_$LocalAccountImpl(
|
||||
identityMaster: null == identityMaster
|
||||
? _value.identityMaster
|
||||
: identityMaster // ignore: cast_nullable_to_non_nullable
|
||||
as IdentityMaster,
|
||||
superIdentity: null == superIdentity
|
||||
? _value.superIdentity
|
||||
: superIdentity // ignore: cast_nullable_to_non_nullable
|
||||
as SuperIdentity,
|
||||
identitySecretBytes: null == identitySecretBytes
|
||||
? _value.identitySecretBytes
|
||||
: identitySecretBytes // ignore: cast_nullable_to_non_nullable
|
||||
|
@ -187,7 +188,7 @@ class __$$LocalAccountImplCopyWithImpl<$Res>
|
|||
@JsonSerializable()
|
||||
class _$LocalAccountImpl implements _LocalAccount {
|
||||
const _$LocalAccountImpl(
|
||||
{required this.identityMaster,
|
||||
{required this.superIdentity,
|
||||
@Uint8ListJsonConverter() required this.identitySecretBytes,
|
||||
required this.encryptionKeyType,
|
||||
required this.biometricsEnabled,
|
||||
|
@ -197,10 +198,11 @@ class _$LocalAccountImpl implements _LocalAccount {
|
|||
factory _$LocalAccountImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$LocalAccountImplFromJson(json);
|
||||
|
||||
// The master key record for the account, containing the identityPublicKey
|
||||
// The super identity key record for the account,
|
||||
// containing the publicKey in the currentIdentity
|
||||
@override
|
||||
final IdentityMaster identityMaster;
|
||||
// The encrypted identity secret that goes with
|
||||
final SuperIdentity superIdentity;
|
||||
// The encrypted currentIdentity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@override
|
||||
@Uint8ListJsonConverter()
|
||||
|
@ -221,7 +223,7 @@ class _$LocalAccountImpl implements _LocalAccount {
|
|||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LocalAccount(identityMaster: $identityMaster, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
|
||||
return 'LocalAccount(superIdentity: $superIdentity, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -229,8 +231,8 @@ class _$LocalAccountImpl implements _LocalAccount {
|
|||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LocalAccountImpl &&
|
||||
(identical(other.identityMaster, identityMaster) ||
|
||||
other.identityMaster == identityMaster) &&
|
||||
(identical(other.superIdentity, superIdentity) ||
|
||||
other.superIdentity == superIdentity) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.identitySecretBytes, identitySecretBytes) &&
|
||||
(identical(other.encryptionKeyType, encryptionKeyType) ||
|
||||
|
@ -246,7 +248,7 @@ class _$LocalAccountImpl implements _LocalAccount {
|
|||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
identityMaster,
|
||||
superIdentity,
|
||||
const DeepCollectionEquality().hash(identitySecretBytes),
|
||||
encryptionKeyType,
|
||||
biometricsEnabled,
|
||||
|
@ -269,7 +271,7 @@ class _$LocalAccountImpl implements _LocalAccount {
|
|||
|
||||
abstract class _LocalAccount implements LocalAccount {
|
||||
const factory _LocalAccount(
|
||||
{required final IdentityMaster identityMaster,
|
||||
{required final SuperIdentity superIdentity,
|
||||
@Uint8ListJsonConverter() required final Uint8List identitySecretBytes,
|
||||
required final EncryptionKeyType encryptionKeyType,
|
||||
required final bool biometricsEnabled,
|
||||
|
@ -279,9 +281,10 @@ abstract class _LocalAccount implements LocalAccount {
|
|||
factory _LocalAccount.fromJson(Map<String, dynamic> json) =
|
||||
_$LocalAccountImpl.fromJson;
|
||||
|
||||
@override // The master key record for the account, containing the identityPublicKey
|
||||
IdentityMaster get identityMaster;
|
||||
@override // The encrypted identity secret that goes with
|
||||
@override // The super identity key record for the account,
|
||||
// containing the publicKey in the currentIdentity
|
||||
SuperIdentity get superIdentity;
|
||||
@override // The encrypted currentIdentity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter()
|
||||
Uint8List get identitySecretBytes;
|
||||
|
|
|
@ -8,7 +8,7 @@ part of 'local_account.dart';
|
|||
|
||||
_$LocalAccountImpl _$$LocalAccountImplFromJson(Map<String, dynamic> json) =>
|
||||
_$LocalAccountImpl(
|
||||
identityMaster: IdentityMaster.fromJson(json['identity_master']),
|
||||
superIdentity: SuperIdentity.fromJson(json['super_identity']),
|
||||
identitySecretBytes: const Uint8ListJsonConverter()
|
||||
.fromJson(json['identity_secret_bytes']),
|
||||
encryptionKeyType:
|
||||
|
@ -20,7 +20,7 @@ _$LocalAccountImpl _$$LocalAccountImplFromJson(Map<String, dynamic> json) =>
|
|||
|
||||
Map<String, dynamic> _$$LocalAccountImplToJson(_$LocalAccountImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'identity_master': instance.identityMaster.toJson(),
|
||||
'super_identity': instance.superIdentity.toJson(),
|
||||
'identity_secret_bytes':
|
||||
const Uint8ListJsonConverter().toJson(instance.identitySecretBytes),
|
||||
'encryption_key_type': instance.encryptionKeyType.toJson(),
|
||||
|
|
|
@ -7,12 +7,13 @@ part 'user_login.g.dart';
|
|||
|
||||
// Represents a currently logged in account
|
||||
// User logins are stored in the user_logins tablestore table
|
||||
// indexed by the accountMasterKey
|
||||
// indexed by the accountSuperIdentityRecordKey
|
||||
@freezed
|
||||
class UserLogin with _$UserLogin {
|
||||
const factory UserLogin({
|
||||
// Master record key for the user used to index the local accounts table
|
||||
required TypedKey accountMasterRecordKey,
|
||||
// SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
required TypedKey superIdentityRecordKey,
|
||||
// The identity secret as unlocked from the local accounts table
|
||||
required TypedSecret identitySecret,
|
||||
// The account record key, owner key and secret pulled from the identity
|
||||
|
|
|
@ -20,8 +20,9 @@ UserLogin _$UserLoginFromJson(Map<String, dynamic> json) {
|
|||
|
||||
/// @nodoc
|
||||
mixin _$UserLogin {
|
||||
// Master record key for the user used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||
// SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get superIdentityRecordKey =>
|
||||
throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table
|
||||
Typed<FixedEncodedString43> get identitySecret =>
|
||||
throw _privateConstructorUsedError; // The account record key, owner key and secret pulled from the identity
|
||||
|
@ -41,7 +42,7 @@ abstract class $UserLoginCopyWith<$Res> {
|
|||
_$UserLoginCopyWithImpl<$Res, UserLogin>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
{Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
Typed<FixedEncodedString43> identitySecret,
|
||||
AccountRecordInfo accountRecordInfo,
|
||||
Timestamp lastActive});
|
||||
|
@ -62,15 +63,15 @@ class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin>
|
|||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? accountMasterRecordKey = null,
|
||||
Object? superIdentityRecordKey = null,
|
||||
Object? identitySecret = null,
|
||||
Object? accountRecordInfo = null,
|
||||
Object? lastActive = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
accountMasterRecordKey: null == accountMasterRecordKey
|
||||
? _value.accountMasterRecordKey
|
||||
: accountMasterRecordKey // ignore: cast_nullable_to_non_nullable
|
||||
superIdentityRecordKey: null == superIdentityRecordKey
|
||||
? _value.superIdentityRecordKey
|
||||
: superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
identitySecret: null == identitySecret
|
||||
? _value.identitySecret
|
||||
|
@ -105,7 +106,7 @@ abstract class _$$UserLoginImplCopyWith<$Res>
|
|||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
{Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
Typed<FixedEncodedString43> identitySecret,
|
||||
AccountRecordInfo accountRecordInfo,
|
||||
Timestamp lastActive});
|
||||
|
@ -125,15 +126,15 @@ class __$$UserLoginImplCopyWithImpl<$Res>
|
|||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? accountMasterRecordKey = null,
|
||||
Object? superIdentityRecordKey = null,
|
||||
Object? identitySecret = null,
|
||||
Object? accountRecordInfo = null,
|
||||
Object? lastActive = null,
|
||||
}) {
|
||||
return _then(_$UserLoginImpl(
|
||||
accountMasterRecordKey: null == accountMasterRecordKey
|
||||
? _value.accountMasterRecordKey
|
||||
: accountMasterRecordKey // ignore: cast_nullable_to_non_nullable
|
||||
superIdentityRecordKey: null == superIdentityRecordKey
|
||||
? _value.superIdentityRecordKey
|
||||
: superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
identitySecret: null == identitySecret
|
||||
? _value.identitySecret
|
||||
|
@ -155,7 +156,7 @@ class __$$UserLoginImplCopyWithImpl<$Res>
|
|||
@JsonSerializable()
|
||||
class _$UserLoginImpl implements _UserLogin {
|
||||
const _$UserLoginImpl(
|
||||
{required this.accountMasterRecordKey,
|
||||
{required this.superIdentityRecordKey,
|
||||
required this.identitySecret,
|
||||
required this.accountRecordInfo,
|
||||
required this.lastActive});
|
||||
|
@ -163,9 +164,10 @@ class _$UserLoginImpl implements _UserLogin {
|
|||
factory _$UserLoginImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserLoginImplFromJson(json);
|
||||
|
||||
// Master record key for the user used to index the local accounts table
|
||||
// SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> accountMasterRecordKey;
|
||||
final Typed<FixedEncodedString43> superIdentityRecordKey;
|
||||
// The identity secret as unlocked from the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> identitySecret;
|
||||
|
@ -178,7 +180,7 @@ class _$UserLoginImpl implements _UserLogin {
|
|||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserLogin(accountMasterRecordKey: $accountMasterRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
|
||||
return 'UserLogin(superIdentityRecordKey: $superIdentityRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -186,8 +188,8 @@ class _$UserLoginImpl implements _UserLogin {
|
|||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserLoginImpl &&
|
||||
(identical(other.accountMasterRecordKey, accountMasterRecordKey) ||
|
||||
other.accountMasterRecordKey == accountMasterRecordKey) &&
|
||||
(identical(other.superIdentityRecordKey, superIdentityRecordKey) ||
|
||||
other.superIdentityRecordKey == superIdentityRecordKey) &&
|
||||
(identical(other.identitySecret, identitySecret) ||
|
||||
other.identitySecret == identitySecret) &&
|
||||
(identical(other.accountRecordInfo, accountRecordInfo) ||
|
||||
|
@ -198,7 +200,7 @@ class _$UserLoginImpl implements _UserLogin {
|
|||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, accountMasterRecordKey,
|
||||
int get hashCode => Object.hash(runtimeType, superIdentityRecordKey,
|
||||
identitySecret, accountRecordInfo, lastActive);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
|
@ -217,7 +219,7 @@ class _$UserLoginImpl implements _UserLogin {
|
|||
|
||||
abstract class _UserLogin implements UserLogin {
|
||||
const factory _UserLogin(
|
||||
{required final Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
{required final Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
required final Typed<FixedEncodedString43> identitySecret,
|
||||
required final AccountRecordInfo accountRecordInfo,
|
||||
required final Timestamp lastActive}) = _$UserLoginImpl;
|
||||
|
@ -225,8 +227,9 @@ abstract class _UserLogin implements UserLogin {
|
|||
factory _UserLogin.fromJson(Map<String, dynamic> json) =
|
||||
_$UserLoginImpl.fromJson;
|
||||
|
||||
@override // Master record key for the user used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey;
|
||||
@override // SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get superIdentityRecordKey;
|
||||
@override // The identity secret as unlocked from the local accounts table
|
||||
Typed<FixedEncodedString43> get identitySecret;
|
||||
@override // The account record key, owner key and secret pulled from the identity
|
||||
|
|
|
@ -8,8 +8,8 @@ part of 'user_login.dart';
|
|||
|
||||
_$UserLoginImpl _$$UserLoginImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserLoginImpl(
|
||||
accountMasterRecordKey: Typed<FixedEncodedString43>.fromJson(
|
||||
json['account_master_record_key']),
|
||||
superIdentityRecordKey: Typed<FixedEncodedString43>.fromJson(
|
||||
json['super_identity_record_key']),
|
||||
identitySecret:
|
||||
Typed<FixedEncodedString43>.fromJson(json['identity_secret']),
|
||||
accountRecordInfo:
|
||||
|
@ -19,7 +19,7 @@ _$UserLoginImpl _$$UserLoginImplFromJson(Map<String, dynamic> json) =>
|
|||
|
||||
Map<String, dynamic> _$$UserLoginImplToJson(_$UserLoginImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'account_master_record_key': instance.accountMasterRecordKey.toJson(),
|
||||
'super_identity_record_key': instance.superIdentityRecordKey.toJson(),
|
||||
'identity_secret': instance.identitySecret.toJson(),
|
||||
'account_record_info': instance.accountRecordInfo.toJson(),
|
||||
'last_active': instance.lastActive.toJson(),
|
||||
|
|
|
@ -87,30 +87,30 @@ class AccountRepository {
|
|||
: fetchUserLogin(activeLocalAccount);
|
||||
}
|
||||
|
||||
LocalAccount? fetchLocalAccount(TypedKey accountMasterRecordKey) {
|
||||
LocalAccount? fetchLocalAccount(TypedKey accountSuperIdentityRecordKey) {
|
||||
final localAccounts = _localAccounts.value;
|
||||
final idx = localAccounts.indexWhere(
|
||||
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
(e) => e.superIdentity.recordKey == accountSuperIdentityRecordKey);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
return localAccounts[idx];
|
||||
}
|
||||
|
||||
UserLogin? fetchUserLogin(TypedKey accountMasterRecordKey) {
|
||||
UserLogin? fetchUserLogin(TypedKey superIdentityRecordKey) {
|
||||
final userLogins = _userLogins.value;
|
||||
final idx = userLogins
|
||||
.indexWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);
|
||||
.indexWhere((e) => e.superIdentityRecordKey == superIdentityRecordKey);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
return userLogins[idx];
|
||||
}
|
||||
|
||||
AccountInfo getAccountInfo(TypedKey? accountMasterRecordKey) {
|
||||
AccountInfo getAccountInfo(TypedKey? superIdentityRecordKey) {
|
||||
// Get active account if we have one
|
||||
final activeLocalAccount = getActiveLocalAccount();
|
||||
if (accountMasterRecordKey == null) {
|
||||
if (superIdentityRecordKey == null) {
|
||||
if (activeLocalAccount == null) {
|
||||
// No user logged in
|
||||
return const AccountInfo(
|
||||
|
@ -118,12 +118,12 @@ class AccountRepository {
|
|||
active: false,
|
||||
activeAccountInfo: null);
|
||||
}
|
||||
accountMasterRecordKey = activeLocalAccount;
|
||||
superIdentityRecordKey = activeLocalAccount;
|
||||
}
|
||||
final active = accountMasterRecordKey == activeLocalAccount;
|
||||
final active = superIdentityRecordKey == activeLocalAccount;
|
||||
|
||||
// Get which local account we want to fetch the profile for
|
||||
final localAccount = fetchLocalAccount(accountMasterRecordKey);
|
||||
final localAccount = fetchLocalAccount(superIdentityRecordKey);
|
||||
if (localAccount == null) {
|
||||
// account does not exist
|
||||
return AccountInfo(
|
||||
|
@ -133,7 +133,7 @@ class AccountRepository {
|
|||
}
|
||||
|
||||
// See if we've logged into this account or if it is locked
|
||||
final userLogin = fetchUserLogin(accountMasterRecordKey);
|
||||
final userLogin = fetchUserLogin(superIdentityRecordKey);
|
||||
if (userLogin == null) {
|
||||
// Account was locked
|
||||
return AccountInfo(
|
||||
|
@ -165,33 +165,32 @@ class AccountRepository {
|
|||
_streamController.add(AccountRepositoryChange.localAccounts);
|
||||
}
|
||||
|
||||
/// Creates a new master identity, an account associated with the master
|
||||
/// identity, stores the account in the identity key and then logs into
|
||||
/// that account with no password set at this time
|
||||
Future<void> createWithNewMasterIdentity(
|
||||
NewProfileSpec newProfileSpec) async {
|
||||
log.debug('Creating master identity');
|
||||
final imws = await IdentityMasterWithSecrets.create();
|
||||
/// Creates a new super identity, an identity instance, an account associated
|
||||
/// with the identity instance, stores the account in the identity key and
|
||||
/// then logs into that account with no password set at this time
|
||||
Future<void> createWithNewSuperIdentity(NewProfileSpec newProfileSpec) async {
|
||||
log.debug('Creating super identity');
|
||||
final wsi = await WritableSuperIdentity.create();
|
||||
try {
|
||||
final localAccount = await _newLocalAccount(
|
||||
identityMaster: imws.identityMaster,
|
||||
identitySecret: imws.identitySecret,
|
||||
superIdentity: wsi.superIdentity,
|
||||
identitySecret: wsi.identitySecret,
|
||||
newProfileSpec: newProfileSpec);
|
||||
|
||||
// Log in the new account by default with no pin
|
||||
final ok = await login(localAccount.identityMaster.masterRecordKey,
|
||||
EncryptionKeyType.none, '');
|
||||
final ok = await login(
|
||||
localAccount.superIdentity.recordKey, EncryptionKeyType.none, '');
|
||||
assert(ok, 'login with none should never fail');
|
||||
} on Exception catch (_) {
|
||||
await imws.delete();
|
||||
await wsi.delete();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Account associated with master identity
|
||||
/// Creates a new Account associated with the current instance of the identity
|
||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||
Future<LocalAccount> _newLocalAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
{required SuperIdentity superIdentity,
|
||||
required SecretKey identitySecret,
|
||||
required NewProfileSpec newProfileSpec,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
|
@ -201,8 +200,9 @@ class AccountRepository {
|
|||
final localAccounts = await _localAccounts.get();
|
||||
|
||||
// Add account with profile to DHT
|
||||
await identityMaster.addAccountToIdentity(
|
||||
identitySecret: identitySecret,
|
||||
await superIdentity.currentInstance.addAccount(
|
||||
superRecordKey: superIdentity.recordKey,
|
||||
secretKey: identitySecret,
|
||||
accountKey: veilidChatAccountKey,
|
||||
createAccountCallback: (parent) async {
|
||||
// Make empty contact list
|
||||
|
@ -235,13 +235,13 @@ class AccountRepository {
|
|||
..contactList = contactList.toProto()
|
||||
..contactInvitationRecords = contactInvitationRecords.toProto()
|
||||
..chatList = chatRecords.toProto();
|
||||
return account;
|
||||
return account.writeToBuffer();
|
||||
});
|
||||
|
||||
// Encrypt identitySecret with key
|
||||
final identitySecretBytes = await encryptionKeyType.encryptSecretToBytes(
|
||||
secret: identitySecret,
|
||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||
cryptoKind: superIdentity.currentInstance.recordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
|
@ -250,7 +250,7 @@ class AccountRepository {
|
|||
// 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,
|
||||
superIdentity: superIdentity,
|
||||
identitySecretBytes: identitySecretBytes,
|
||||
encryptionKeyType: encryptionKeyType,
|
||||
biometricsEnabled: false,
|
||||
|
@ -269,12 +269,12 @@ class AccountRepository {
|
|||
}
|
||||
|
||||
/// Remove an account and wipe the messages for this account from this device
|
||||
Future<bool> deleteLocalAccount(TypedKey accountMasterRecordKey) async {
|
||||
await logout(accountMasterRecordKey);
|
||||
Future<bool> deleteLocalAccount(TypedKey superIdentityRecordKey) async {
|
||||
await logout(superIdentityRecordKey);
|
||||
|
||||
final localAccounts = await _localAccounts.get();
|
||||
final newLocalAccounts = localAccounts.removeWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
(la) => la.superIdentity.recordKey == superIdentityRecordKey);
|
||||
|
||||
await _localAccounts.set(newLocalAccounts);
|
||||
_streamController.add(AccountRepositoryChange.localAccounts);
|
||||
|
@ -290,31 +290,35 @@ class AccountRepository {
|
|||
|
||||
/// Delete an account from all devices
|
||||
|
||||
Future<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
|
||||
Future<void> switchToAccount(TypedKey? superIdentityRecordKey) async {
|
||||
final activeLocalAccount = await _activeLocalAccount.get();
|
||||
|
||||
if (activeLocalAccount == accountMasterRecordKey) {
|
||||
if (activeLocalAccount == superIdentityRecordKey) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountMasterRecordKey != null) {
|
||||
if (superIdentityRecordKey != null) {
|
||||
// Assert the specified record key can be found, will throw if not
|
||||
final _ = _userLogins.value.firstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
|
||||
(ul) => ul.superIdentityRecordKey == superIdentityRecordKey);
|
||||
}
|
||||
await _activeLocalAccount.set(accountMasterRecordKey);
|
||||
await _activeLocalAccount.set(superIdentityRecordKey);
|
||||
_streamController.add(AccountRepositoryChange.activeLocalAccount);
|
||||
}
|
||||
|
||||
Future<bool> _decryptedLogin(
|
||||
IdentityMaster identityMaster, SecretKey identitySecret) async {
|
||||
SuperIdentity superIdentity, SecretKey identitySecret) async {
|
||||
// Verify identity secret works and return the valid cryptosystem
|
||||
final cs = await identityMaster.validateIdentitySecret(identitySecret);
|
||||
final cs = await superIdentity.currentInstance
|
||||
.validateIdentitySecret(identitySecret);
|
||||
|
||||
// Read the identity key to get the account keys
|
||||
final accountRecordInfoList = await identityMaster.readAccountsFromIdentity(
|
||||
identitySecret: identitySecret, accountKey: veilidChatAccountKey);
|
||||
final accountRecordInfoList = await superIdentity.currentInstance
|
||||
.readAccount(
|
||||
superRecordKey: superIdentity.recordKey,
|
||||
secretKey: identitySecret,
|
||||
accountKey: veilidChatAccountKey);
|
||||
if (accountRecordInfoList.length > 1) {
|
||||
throw IdentityException.limitExceeded;
|
||||
} else if (accountRecordInfoList.isEmpty) {
|
||||
|
@ -326,11 +330,11 @@ class AccountRepository {
|
|||
final userLogins = await _userLogins.get();
|
||||
final now = Veilid.instance.now();
|
||||
final newUserLogins = userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == identityMaster.masterRecordKey,
|
||||
(ul) => ul.superIdentityRecordKey == superIdentity.recordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: identityMaster.masterRecordKey,
|
||||
superIdentityRecordKey: superIdentity.recordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
accountRecordInfo: accountRecordInfo,
|
||||
|
@ -338,7 +342,7 @@ class AccountRepository {
|
|||
addIfNotFound: true);
|
||||
|
||||
await _userLogins.set(newUserLogins);
|
||||
await _activeLocalAccount.set(identityMaster.masterRecordKey);
|
||||
await _activeLocalAccount.set(superIdentity.recordKey);
|
||||
|
||||
_streamController
|
||||
..add(AccountRepositoryChange.userLogins)
|
||||
|
@ -347,13 +351,13 @@ class AccountRepository {
|
|||
return true;
|
||||
}
|
||||
|
||||
Future<bool> login(TypedKey accountMasterRecordKey,
|
||||
Future<bool> login(TypedKey accountSuperRecordKey,
|
||||
EncryptionKeyType encryptionKeyType, String encryptionKey) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
|
||||
// Get account, throws if not found
|
||||
final localAccount = localAccounts.firstWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
(la) => la.superIdentity.recordKey == accountSuperRecordKey);
|
||||
|
||||
// Log in with this local account
|
||||
|
||||
|
@ -365,12 +369,12 @@ class AccountRepository {
|
|||
final identitySecret =
|
||||
await localAccount.encryptionKeyType.decryptSecretFromBytes(
|
||||
secretBytes: localAccount.identitySecretBytes,
|
||||
cryptoKind: localAccount.identityMaster.identityRecordKey.kind,
|
||||
cryptoKind: localAccount.superIdentity.currentInstance.recordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _decryptedLogin(localAccount.identityMaster, identitySecret);
|
||||
return _decryptedLogin(localAccount.superIdentity, identitySecret);
|
||||
}
|
||||
|
||||
Future<void> logout(TypedKey? accountMasterRecordKey) async {
|
||||
|
@ -390,20 +394,20 @@ class AccountRepository {
|
|||
|
||||
// Remove user from active logins list
|
||||
final newUserLogins = (await _userLogins.get())
|
||||
.removeWhere((ul) => ul.accountMasterRecordKey == logoutUser);
|
||||
.removeWhere((ul) => ul.superIdentityRecordKey == logoutUser);
|
||||
await _userLogins.set(newUserLogins);
|
||||
_streamController.add(AccountRepositoryChange.userLogins);
|
||||
}
|
||||
|
||||
Future<DHTRecord> openAccountRecord(UserLogin userLogin) async {
|
||||
final localAccount = fetchLocalAccount(userLogin.accountMasterRecordKey)!;
|
||||
final localAccount = fetchLocalAccount(userLogin.superIdentityRecordKey)!;
|
||||
|
||||
// Record not yet open, do it
|
||||
final pool = DHTRecordPool.instance;
|
||||
final record = await pool.openRecordOwned(
|
||||
userLogin.accountRecordInfo.accountRecord,
|
||||
debugName: 'AccountRepository::openAccountRecord::AccountRecord',
|
||||
parent: localAccount.identityMaster.identityRecordKey);
|
||||
parent: localAccount.superIdentity.currentInstance.recordKey);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../layout/default_app_bar.dart';
|
||||
import '../../../theme/theme.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../veilid_processor/veilid_processor.dart';
|
||||
import '../../account_manager.dart';
|
||||
import '../../layout/default_app_bar.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_processor/veilid_processor.dart';
|
||||
import '../account_manager.dart';
|
||||
|
||||
class NewAccountPage extends StatefulWidget {
|
||||
const NewAccountPage({super.key});
|
||||
|
@ -36,63 +37,76 @@ class NewAccountPageState extends State<NewAccountPage> {
|
|||
}
|
||||
|
||||
Widget _newAccountForm(BuildContext context,
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>)
|
||||
onSubmit}) =>
|
||||
FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(translate('new_account_page.header'))
|
||||
.textStyle(context.headlineSmall)
|
||||
.paddingSymmetric(vertical: 16),
|
||||
FormBuilderTextField(
|
||||
autofocus: true,
|
||||
name: formFieldName,
|
||||
decoration:
|
||||
InputDecoration(labelText: translate('account.form_name')),
|
||||
maxLength: 64,
|
||||
// The validator receives the text that the user has entered.
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(),
|
||||
]),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: formFieldPronouns,
|
||||
maxLength: 64,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_pronouns')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
Text(translate('new_account_page.instructions'))
|
||||
.toCenter()
|
||||
.flexible(flex: 6),
|
||||
const Spacer(),
|
||||
]).paddingSymmetric(vertical: 4),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
setState(() {
|
||||
isInAsyncCall = true;
|
||||
});
|
||||
try {
|
||||
await onSubmit(_formKey);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) {
|
||||
final networkReady = context
|
||||
.watch<ConnectionStateCubit>()
|
||||
.state
|
||||
.asData
|
||||
?.value
|
||||
.isPublicInternetReady ??
|
||||
false;
|
||||
final canSubmit = networkReady;
|
||||
|
||||
return FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(translate('new_account_page.header'))
|
||||
.textStyle(context.headlineSmall)
|
||||
.paddingSymmetric(vertical: 16),
|
||||
FormBuilderTextField(
|
||||
autofocus: true,
|
||||
name: formFieldName,
|
||||
decoration:
|
||||
InputDecoration(labelText: translate('account.form_name')),
|
||||
maxLength: 64,
|
||||
// The validator receives the text that the user has entered.
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(),
|
||||
]),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: formFieldPronouns,
|
||||
maxLength: 64,
|
||||
decoration:
|
||||
InputDecoration(labelText: translate('account.form_pronouns')),
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
Text(translate('new_account_page.instructions'))
|
||||
.toCenter()
|
||||
.flexible(flex: 6),
|
||||
const Spacer(),
|
||||
]).paddingSymmetric(vertical: 4),
|
||||
ElevatedButton(
|
||||
onPressed: !canSubmit
|
||||
? null
|
||||
: () async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
setState(() {
|
||||
isInAsyncCall = false;
|
||||
isInAsyncCall = true;
|
||||
});
|
||||
try {
|
||||
await onSubmit(_formKey);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isInAsyncCall = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(translate('new_account_page.create')),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(translate(!networkReady
|
||||
? 'button.waiting_for_network'
|
||||
: 'new_account_page.create')),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -127,7 +141,7 @@ class NewAccountPageState extends State<NewAccountPage> {
|
|||
NewProfileSpec(name: name, pronouns: pronouns);
|
||||
|
||||
await AccountRepository.instance
|
||||
.createWithNewMasterIdentity(newProfileSpec);
|
||||
.createWithNewSuperIdentity(newProfileSpec);
|
||||
} on Exception catch (e) {
|
||||
if (context.mounted) {
|
||||
await showErrorModal(context, translate('new_account_page.error'),
|
|
@ -1,2 +1,2 @@
|
|||
export 'new_account_page/new_account_page.dart';
|
||||
export 'new_account_page.dart';
|
||||
export 'profile_widget.dart';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue