mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-05-02 14:26:12 -04:00
refactor some more
This commit is contained in:
parent
2adc958128
commit
c516323e7d
91 changed files with 1237 additions and 748 deletions
3
lib/account_manager/account_manager.dart
Normal file
3
lib/account_manager/account_manager.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
export 'cubit/cubit.dart';
|
||||
export 'repository/repository.dart';
|
||||
export 'view/view.dart';
|
|
@ -0,0 +1,42 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../repository/account_repository/account_repository.dart';
|
||||
|
||||
part 'active_user_login_state.dart';
|
||||
|
||||
class ActiveUserLoginCubit extends Cubit<ActiveUserLoginState> {
|
||||
ActiveUserLoginCubit(AccountRepository accountRepository)
|
||||
: _accountRepository = accountRepository,
|
||||
super(null) {
|
||||
// Subscribe to streams
|
||||
_initAccountRepositorySubscription();
|
||||
}
|
||||
|
||||
void _initAccountRepositorySubscription() {
|
||||
_accountRepositorySubscription =
|
||||
_accountRepository.changes().listen((change) {
|
||||
switch (change) {
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
emit(_accountRepository.getActiveUserLogin());
|
||||
break;
|
||||
// Ignore these
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
case AccountRepositoryChange.userLogins:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
part of 'active_user_login_cubit.dart';
|
||||
|
||||
typedef ActiveUserLoginState = TypedKey?;
|
3
lib/account_manager/cubit/cubit.dart
Normal file
3
lib/account_manager/cubit/cubit.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
export 'active_user_login_cubit/active_user_login_cubit.dart';
|
||||
export 'local_accounts_cubit/local_accounts_cubit.dart';
|
||||
export 'user_logins_cubit/user_logins_cubit.dart';
|
|
@ -0,0 +1,42 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
import '../../repository/account_repository/account_repository.dart';
|
||||
|
||||
part 'local_accounts_state.dart';
|
||||
|
||||
class LocalAccountsCubit extends Cubit<LocalAccountsState> {
|
||||
LocalAccountsCubit(AccountRepository accountRepository)
|
||||
: _accountRepository = accountRepository,
|
||||
super(LocalAccountsState()) {
|
||||
// Subscribe to streams
|
||||
_initAccountRepositorySubscription();
|
||||
}
|
||||
|
||||
void _initAccountRepositorySubscription() {
|
||||
_accountRepositorySubscription =
|
||||
_accountRepository.changes().listen((change) {
|
||||
switch (change) {
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
emit(_accountRepository.getLocalAccounts());
|
||||
break;
|
||||
// Ignore these
|
||||
case AccountRepositoryChange.userLogins:
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
part of 'local_accounts_cubit.dart';
|
||||
|
||||
typedef LocalAccountsState = IList<LocalAccount>;
|
|
@ -0,0 +1,42 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
import '../../repository/account_repository/account_repository.dart';
|
||||
|
||||
part 'user_logins_state.dart';
|
||||
|
||||
class UserLoginsCubit extends Cubit<UserLoginsState> {
|
||||
UserLoginsCubit(AccountRepository accountRepository)
|
||||
: _accountRepository = accountRepository,
|
||||
super(UserLoginsState()) {
|
||||
// Subscribe to streams
|
||||
_initAccountRepositorySubscription();
|
||||
}
|
||||
|
||||
void _initAccountRepositorySubscription() {
|
||||
_accountRepositorySubscription =
|
||||
_accountRepository.changes().listen((change) {
|
||||
switch (change) {
|
||||
case AccountRepositoryChange.userLogins:
|
||||
emit(_accountRepository.getUserLogins());
|
||||
break;
|
||||
// Ignore these
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
part of 'user_logins_cubit.dart';
|
||||
|
||||
typedef UserLoginsState = IList<UserLogin>;
|
|
@ -0,0 +1,297 @@
|
|||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../../proto/proto.dart' as proto;
|
||||
import 'active_logins.dart';
|
||||
import 'encryption_key_type.dart';
|
||||
import 'local_account.dart';
|
||||
import 'new_profile_spec.dart';
|
||||
import 'user_login.dart';
|
||||
|
||||
export 'active_logins.dart';
|
||||
export 'encryption_key_type.dart';
|
||||
export 'local_account.dart';
|
||||
export 'user_login.dart';
|
||||
|
||||
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
||||
|
||||
enum AccountRepositoryChange { localAccounts, userLogins, activeUserLogin }
|
||||
|
||||
class AccountRepository {
|
||||
AccountRepository._()
|
||||
: _localAccounts = _initLocalAccounts(),
|
||||
_activeLogins = _initActiveLogins();
|
||||
|
||||
static TableDBValue<IList<LocalAccount>> _initLocalAccounts() => TableDBValue(
|
||||
tableName: 'local_account_manager',
|
||||
tableKeyName: 'local_accounts',
|
||||
valueFromJson: (obj) => obj != null
|
||||
? IList<LocalAccount>.fromJson(
|
||||
obj, genericFromJson(LocalAccount.fromJson))
|
||||
: IList<LocalAccount>(),
|
||||
valueToJson: (val) => val.toJson((la) => la.toJson()));
|
||||
|
||||
static TableDBValue<ActiveLogins> _initActiveLogins() => TableDBValue(
|
||||
tableName: 'local_account_manager',
|
||||
tableKeyName: 'active_logins',
|
||||
valueFromJson: (obj) => obj != null
|
||||
? ActiveLogins.fromJson(obj as Map<String, dynamic>)
|
||||
: ActiveLogins.empty(),
|
||||
valueToJson: (val) => val.toJson());
|
||||
|
||||
final TableDBValue<IList<LocalAccount>> _localAccounts;
|
||||
final TableDBValue<ActiveLogins> _activeLogins;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Singleton initialization
|
||||
|
||||
static AccountRepository instance = AccountRepository._();
|
||||
|
||||
Future<void> init() async {
|
||||
await _localAccounts.load();
|
||||
await _activeLogins.load();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Streams
|
||||
|
||||
Stream<AccountRepositoryChange> changes() async* {}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Selectors
|
||||
IList<LocalAccount> getLocalAccounts() => _localAccounts.requireValue;
|
||||
IList<UserLogin> getUserLogins() => _activeLogins.requireValue.userLogins;
|
||||
TypedKey? getActiveUserLogin() => _activeLogins.requireValue.activeUserLogin;
|
||||
|
||||
LocalAccount? fetchLocalAccount({required TypedKey accountMasterRecordKey}) {
|
||||
final localAccounts = _localAccounts.requireValue;
|
||||
final idx = localAccounts.indexWhere(
|
||||
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
return localAccounts[idx];
|
||||
}
|
||||
|
||||
UserLogin? fetchLogin({required TypedKey accountMasterRecordKey}) {
|
||||
final userLogins = _activeLogins.requireValue.userLogins;
|
||||
final idx = userLogins
|
||||
.indexWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
return userLogins[idx];
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Mutators
|
||||
|
||||
/// Reorder accounts
|
||||
Future<void> reorderAccount(int oldIndex, int newIndex) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
final removedItem = Output<LocalAccount>();
|
||||
final updated = localAccounts
|
||||
.removeAt(oldIndex, removedItem)
|
||||
.insert(newIndex, removedItem.value!);
|
||||
await _localAccounts.set(updated);
|
||||
}
|
||||
|
||||
/// 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> createMasterIdentity(NewProfileSpec newProfileSpec) async {
|
||||
final imws = await IdentityMasterWithSecrets.create();
|
||||
try {
|
||||
final localAccount = await _newLocalAccount(
|
||||
identityMaster: imws.identityMaster,
|
||||
identitySecret: imws.identitySecret,
|
||||
newProfileSpec: newProfileSpec);
|
||||
|
||||
// Log in the new account by default with no pin
|
||||
final ok = await login(localAccount.identityMaster.masterRecordKey,
|
||||
EncryptionKeyType.none, '');
|
||||
assert(ok, 'login with none should never fail');
|
||||
} on Exception catch (_) {
|
||||
await imws.delete();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Account associated with master identity
|
||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||
Future<LocalAccount> _newLocalAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
required SecretKey identitySecret,
|
||||
required NewProfileSpec newProfileSpec,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
|
||||
// Add account with profile to DHT
|
||||
await identityMaster.addAccountToIdentity(
|
||||
identitySecret: identitySecret,
|
||||
accountKey: veilidChatAccountKey,
|
||||
createAccountCallback: (parent) async {
|
||||
// Make empty contact list
|
||||
final contactList = await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make empty contact invitation record list
|
||||
final contactInvitationRecords =
|
||||
await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make empty chat record list
|
||||
final chatRecords = await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make account object
|
||||
final account = proto.Account()
|
||||
..profile = (proto.Profile()
|
||||
..name = newProfileSpec.name
|
||||
..pronouns = newProfileSpec.pronouns)
|
||||
..contactList = contactList.toProto()
|
||||
..contactInvitationRecords = contactInvitationRecords.toProto()
|
||||
..chatList = chatRecords.toProto();
|
||||
return account;
|
||||
});
|
||||
|
||||
// Encrypt identitySecret with key
|
||||
final identitySecretBytes = await encryptionKeyType.encryptSecretToBytes(
|
||||
secret: identitySecret,
|
||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Create local account object
|
||||
// Does not contain the account key or its secret
|
||||
// as that is not to be persisted, and only pulled from the identity key
|
||||
// and optionally decrypted with the unlock password
|
||||
final localAccount = LocalAccount(
|
||||
identityMaster: identityMaster,
|
||||
identitySecretBytes: identitySecretBytes,
|
||||
encryptionKeyType: encryptionKeyType,
|
||||
biometricsEnabled: false,
|
||||
hiddenAccount: false,
|
||||
name: newProfileSpec.name,
|
||||
);
|
||||
|
||||
// Add local account object to internal store
|
||||
final newLocalAccounts = localAccounts.add(localAccount);
|
||||
|
||||
await _localAccounts.set(newLocalAccounts);
|
||||
|
||||
// Return local account object
|
||||
return localAccount;
|
||||
}
|
||||
|
||||
/// Remove an account and wipe the messages for this account from this device
|
||||
Future<bool> deleteLocalAccount(TypedKey accountMasterRecordKey) async {
|
||||
await logout(accountMasterRecordKey);
|
||||
|
||||
final localAccounts = await _localAccounts.get();
|
||||
final newLocalAccounts = localAccounts.removeWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
|
||||
await _localAccounts.set(newLocalAccounts);
|
||||
|
||||
// TO DO: wipe messages
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Import an account from another VeilidChat instance
|
||||
|
||||
/// Recover an account with the master identity secret
|
||||
|
||||
/// Delete an account from all devices
|
||||
|
||||
Future<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
|
||||
final activeLogins = await _activeLogins.get();
|
||||
|
||||
if (accountMasterRecordKey != null) {
|
||||
// Assert the specified record key can be found, will throw if not
|
||||
final _ = activeLogins.userLogins.firstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
|
||||
}
|
||||
final newActiveLogins =
|
||||
activeLogins.copyWith(activeUserLogin: accountMasterRecordKey);
|
||||
await _activeLogins.set(newActiveLogins);
|
||||
}
|
||||
|
||||
Future<bool> _decryptedLogin(
|
||||
IdentityMaster identityMaster, SecretKey identitySecret) async {
|
||||
final cs = await Veilid.instance
|
||||
.getCryptoSystem(identityMaster.identityRecordKey.kind);
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw Exception('Identity is corrupted');
|
||||
}
|
||||
|
||||
// Read the identity key to get the account keys
|
||||
final accountRecordInfo = await identityMaster.readAccountFromIdentity(
|
||||
identitySecret: identitySecret, accountKey: veilidChatAccountKey);
|
||||
|
||||
// Add to user logins and select it
|
||||
final activeLogins = await _activeLogins.get();
|
||||
final now = Veilid.instance.now();
|
||||
final newActiveLogins = activeLogins.copyWith(
|
||||
userLogins: activeLogins.userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == identityMaster.masterRecordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: identityMaster.masterRecordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
accountRecordInfo: accountRecordInfo,
|
||||
lastActive: now),
|
||||
addIfNotFound: true),
|
||||
activeUserLogin: identityMaster.masterRecordKey);
|
||||
await _activeLogins.set(newActiveLogins);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> login(TypedKey accountMasterRecordKey,
|
||||
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);
|
||||
|
||||
// Log in with this local account
|
||||
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != encryptionKeyType) {
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
|
||||
final identitySecret =
|
||||
await localAccount.encryptionKeyType.decryptSecretFromBytes(
|
||||
secretBytes: localAccount.identitySecretBytes,
|
||||
cryptoKind: localAccount.identityMaster.identityRecordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _decryptedLogin(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<void> logout(TypedKey? accountMasterRecordKey) async {
|
||||
final activeLogins = await _activeLogins.get();
|
||||
final logoutUser = accountMasterRecordKey ?? activeLogins.activeUserLogin;
|
||||
if (logoutUser == null) {
|
||||
return;
|
||||
}
|
||||
final newActiveLogins = activeLogins.copyWith(
|
||||
activeUserLogin: activeLogins.activeUserLogin == logoutUser
|
||||
? null
|
||||
: activeLogins.activeUserLogin,
|
||||
userLogins: activeLogins.userLogins
|
||||
.removeWhere((ul) => ul.accountMasterRecordKey == logoutUser));
|
||||
await _activeLogins.set(newActiveLogins);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Represents a set of user logins and the currently selected account
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import 'user_login.dart';
|
||||
|
||||
part 'active_logins.g.dart';
|
||||
part 'active_logins.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ActiveLogins with _$ActiveLogins {
|
||||
const factory ActiveLogins({
|
||||
// The list of current logged in accounts
|
||||
required IList<UserLogin> userLogins,
|
||||
// The current selected account indexed by master record key
|
||||
TypedKey? activeUserLogin,
|
||||
}) = _ActiveLogins;
|
||||
|
||||
factory ActiveLogins.empty() =>
|
||||
const ActiveLogins(userLogins: IListConst([]));
|
||||
|
||||
factory ActiveLogins.fromJson(dynamic json) =>
|
||||
_ActiveLogins.fromJson(json as Map<String, dynamic>);
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'active_logins.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
ActiveLogins _$ActiveLoginsFromJson(Map<String, dynamic> json) {
|
||||
return _ActiveLogins.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ActiveLogins {
|
||||
// The list of current logged in accounts
|
||||
IList<UserLogin> get userLogins =>
|
||||
throw _privateConstructorUsedError; // The current selected account indexed by master record key
|
||||
Typed<FixedEncodedString43>? get activeUserLogin =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ActiveLoginsCopyWith<ActiveLogins> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ActiveLoginsCopyWith<$Res> {
|
||||
factory $ActiveLoginsCopyWith(
|
||||
ActiveLogins value, $Res Function(ActiveLogins) then) =
|
||||
_$ActiveLoginsCopyWithImpl<$Res, ActiveLogins>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{IList<UserLogin> userLogins,
|
||||
Typed<FixedEncodedString43>? activeUserLogin});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ActiveLoginsCopyWithImpl<$Res, $Val extends ActiveLogins>
|
||||
implements $ActiveLoginsCopyWith<$Res> {
|
||||
_$ActiveLoginsCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? userLogins = null,
|
||||
Object? activeUserLogin = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
userLogins: null == userLogins
|
||||
? _value.userLogins
|
||||
: userLogins // ignore: cast_nullable_to_non_nullable
|
||||
as IList<UserLogin>,
|
||||
activeUserLogin: freezed == activeUserLogin
|
||||
? _value.activeUserLogin
|
||||
: activeUserLogin // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ActiveLoginsImplCopyWith<$Res>
|
||||
implements $ActiveLoginsCopyWith<$Res> {
|
||||
factory _$$ActiveLoginsImplCopyWith(
|
||||
_$ActiveLoginsImpl value, $Res Function(_$ActiveLoginsImpl) then) =
|
||||
__$$ActiveLoginsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{IList<UserLogin> userLogins,
|
||||
Typed<FixedEncodedString43>? activeUserLogin});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ActiveLoginsImplCopyWithImpl<$Res>
|
||||
extends _$ActiveLoginsCopyWithImpl<$Res, _$ActiveLoginsImpl>
|
||||
implements _$$ActiveLoginsImplCopyWith<$Res> {
|
||||
__$$ActiveLoginsImplCopyWithImpl(
|
||||
_$ActiveLoginsImpl _value, $Res Function(_$ActiveLoginsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? userLogins = null,
|
||||
Object? activeUserLogin = freezed,
|
||||
}) {
|
||||
return _then(_$ActiveLoginsImpl(
|
||||
userLogins: null == userLogins
|
||||
? _value.userLogins
|
||||
: userLogins // ignore: cast_nullable_to_non_nullable
|
||||
as IList<UserLogin>,
|
||||
activeUserLogin: freezed == activeUserLogin
|
||||
? _value.activeUserLogin
|
||||
: activeUserLogin // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ActiveLoginsImpl implements _ActiveLogins {
|
||||
const _$ActiveLoginsImpl({required this.userLogins, this.activeUserLogin});
|
||||
|
||||
factory _$ActiveLoginsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ActiveLoginsImplFromJson(json);
|
||||
|
||||
// The list of current logged in accounts
|
||||
@override
|
||||
final IList<UserLogin> userLogins;
|
||||
// The current selected account indexed by master record key
|
||||
@override
|
||||
final Typed<FixedEncodedString43>? activeUserLogin;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActiveLogins(userLogins: $userLogins, activeUserLogin: $activeUserLogin)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ActiveLoginsImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.userLogins, userLogins) &&
|
||||
(identical(other.activeUserLogin, activeUserLogin) ||
|
||||
other.activeUserLogin == activeUserLogin));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,
|
||||
const DeepCollectionEquality().hash(userLogins), activeUserLogin);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ActiveLoginsImplCopyWith<_$ActiveLoginsImpl> get copyWith =>
|
||||
__$$ActiveLoginsImplCopyWithImpl<_$ActiveLoginsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ActiveLoginsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ActiveLogins implements ActiveLogins {
|
||||
const factory _ActiveLogins(
|
||||
{required final IList<UserLogin> userLogins,
|
||||
final Typed<FixedEncodedString43>? activeUserLogin}) = _$ActiveLoginsImpl;
|
||||
|
||||
factory _ActiveLogins.fromJson(Map<String, dynamic> json) =
|
||||
_$ActiveLoginsImpl.fromJson;
|
||||
|
||||
@override // The list of current logged in accounts
|
||||
IList<UserLogin> get userLogins;
|
||||
@override // The current selected account indexed by master record key
|
||||
Typed<FixedEncodedString43>? get activeUserLogin;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ActiveLoginsImplCopyWith<_$ActiveLoginsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'active_logins.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ActiveLoginsImpl _$$ActiveLoginsImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ActiveLoginsImpl(
|
||||
userLogins: IList<UserLogin>.fromJson(
|
||||
json['user_logins'], (value) => UserLogin.fromJson(value)),
|
||||
activeUserLogin: json['active_user_login'] == null
|
||||
? null
|
||||
: Typed<FixedEncodedString43>.fromJson(json['active_user_login']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ActiveLoginsImplToJson(_$ActiveLoginsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'user_logins': instance.userLogins.toJson(
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
'active_user_login': instance.activeUserLogin?.toJson(),
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
// Local account identitySecretKey is potentially encrypted with a key
|
||||
// using the following mechanisms
|
||||
// * None : no key, bytes are unencrypted
|
||||
// * Pin : Code is a numeric pin (4-256 numeric digits) hashed with Argon2
|
||||
// * Password: Code is a UTF-8 string that is hashed with Argon2
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:change_case/change_case.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../../proto/proto.dart' as proto;
|
||||
|
||||
enum EncryptionKeyType {
|
||||
none,
|
||||
pin,
|
||||
password;
|
||||
|
||||
factory EncryptionKeyType.fromJson(dynamic j) =>
|
||||
EncryptionKeyType.values.byName((j as String).toCamelCase());
|
||||
|
||||
factory EncryptionKeyType.fromProto(proto.EncryptionKeyType p) {
|
||||
// ignore: exhaustive_cases
|
||||
switch (p) {
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE:
|
||||
return EncryptionKeyType.none;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN:
|
||||
return EncryptionKeyType.pin;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD:
|
||||
return EncryptionKeyType.password;
|
||||
}
|
||||
throw StateError('unknown EncryptionKeyType enum value');
|
||||
}
|
||||
String toJson() => name.toPascalCase();
|
||||
proto.EncryptionKeyType toProto() => switch (this) {
|
||||
EncryptionKeyType.none =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE,
|
||||
EncryptionKeyType.pin =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN,
|
||||
EncryptionKeyType.password =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD,
|
||||
};
|
||||
|
||||
Future<Uint8List> encryptSecretToBytes(
|
||||
{required SecretKey secret,
|
||||
required CryptoKind cryptoKind,
|
||||
String encryptionKey = ''}) async {
|
||||
late final Uint8List secretBytes;
|
||||
switch (this) {
|
||||
case EncryptionKeyType.none:
|
||||
secretBytes = secret.decode();
|
||||
case EncryptionKeyType.pin:
|
||||
case EncryptionKeyType.password:
|
||||
final cs = await Veilid.instance.getCryptoSystem(cryptoKind);
|
||||
|
||||
secretBytes =
|
||||
await cs.encryptAeadWithPassword(secret.decode(), encryptionKey);
|
||||
}
|
||||
return secretBytes;
|
||||
}
|
||||
|
||||
Future<SecretKey> decryptSecretFromBytes(
|
||||
{required Uint8List secretBytes,
|
||||
required CryptoKind cryptoKind,
|
||||
String encryptionKey = ''}) async {
|
||||
late final SecretKey secret;
|
||||
switch (this) {
|
||||
case EncryptionKeyType.none:
|
||||
secret = SecretKey.fromBytes(secretBytes);
|
||||
case EncryptionKeyType.pin:
|
||||
case EncryptionKeyType.password:
|
||||
final cs = await Veilid.instance.getCryptoSystem(cryptoKind);
|
||||
|
||||
secret = SecretKey.fromBytes(
|
||||
await cs.decryptAeadWithPassword(secretBytes, encryptionKey));
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import 'encryption_key_type.dart';
|
||||
|
||||
part 'local_account.g.dart';
|
||||
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
|
||||
// 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 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;
|
||||
|
||||
factory LocalAccount.fromJson(dynamic json) =>
|
||||
_$LocalAccountFromJson(json as Map<String, dynamic>);
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'local_account.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
LocalAccount _$LocalAccountFromJson(Map<String, dynamic> json) {
|
||||
return _LocalAccount.fromJson(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 identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter()
|
||||
Uint8List get identitySecretBytes =>
|
||||
throw _privateConstructorUsedError; // The kind of encryption input used on the account
|
||||
EncryptionKeyType get encryptionKeyType =>
|
||||
throw _privateConstructorUsedError; // If account is not hidden, password can be retrieved via
|
||||
bool get biometricsEnabled =>
|
||||
throw _privateConstructorUsedError; // Keep account hidden unless account password is entered
|
||||
// (tries all hidden accounts with auth method (no biometrics))
|
||||
bool get hiddenAccount =>
|
||||
throw _privateConstructorUsedError; // Display name for account until it is unlocked
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$LocalAccountCopyWith<LocalAccount> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LocalAccountCopyWith<$Res> {
|
||||
factory $LocalAccountCopyWith(
|
||||
LocalAccount value, $Res Function(LocalAccount) then) =
|
||||
_$LocalAccountCopyWithImpl<$Res, LocalAccount>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{IdentityMaster identityMaster,
|
||||
@Uint8ListJsonConverter() Uint8List identitySecretBytes,
|
||||
EncryptionKeyType encryptionKeyType,
|
||||
bool biometricsEnabled,
|
||||
bool hiddenAccount,
|
||||
String name});
|
||||
|
||||
$IdentityMasterCopyWith<$Res> get identityMaster;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LocalAccountCopyWithImpl<$Res, $Val extends LocalAccount>
|
||||
implements $LocalAccountCopyWith<$Res> {
|
||||
_$LocalAccountCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? identityMaster = null,
|
||||
Object? identitySecretBytes = null,
|
||||
Object? encryptionKeyType = null,
|
||||
Object? biometricsEnabled = null,
|
||||
Object? hiddenAccount = null,
|
||||
Object? name = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
identityMaster: null == identityMaster
|
||||
? _value.identityMaster
|
||||
: identityMaster // ignore: cast_nullable_to_non_nullable
|
||||
as IdentityMaster,
|
||||
identitySecretBytes: null == identitySecretBytes
|
||||
? _value.identitySecretBytes
|
||||
: identitySecretBytes // ignore: cast_nullable_to_non_nullable
|
||||
as Uint8List,
|
||||
encryptionKeyType: null == encryptionKeyType
|
||||
? _value.encryptionKeyType
|
||||
: encryptionKeyType // ignore: cast_nullable_to_non_nullable
|
||||
as EncryptionKeyType,
|
||||
biometricsEnabled: null == biometricsEnabled
|
||||
? _value.biometricsEnabled
|
||||
: biometricsEnabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hiddenAccount: null == hiddenAccount
|
||||
? _value.hiddenAccount
|
||||
: hiddenAccount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$IdentityMasterCopyWith<$Res> get identityMaster {
|
||||
return $IdentityMasterCopyWith<$Res>(_value.identityMaster, (value) {
|
||||
return _then(_value.copyWith(identityMaster: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$LocalAccountImplCopyWith<$Res>
|
||||
implements $LocalAccountCopyWith<$Res> {
|
||||
factory _$$LocalAccountImplCopyWith(
|
||||
_$LocalAccountImpl value, $Res Function(_$LocalAccountImpl) then) =
|
||||
__$$LocalAccountImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{IdentityMaster identityMaster,
|
||||
@Uint8ListJsonConverter() Uint8List identitySecretBytes,
|
||||
EncryptionKeyType encryptionKeyType,
|
||||
bool biometricsEnabled,
|
||||
bool hiddenAccount,
|
||||
String name});
|
||||
|
||||
@override
|
||||
$IdentityMasterCopyWith<$Res> get identityMaster;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$LocalAccountImplCopyWithImpl<$Res>
|
||||
extends _$LocalAccountCopyWithImpl<$Res, _$LocalAccountImpl>
|
||||
implements _$$LocalAccountImplCopyWith<$Res> {
|
||||
__$$LocalAccountImplCopyWithImpl(
|
||||
_$LocalAccountImpl _value, $Res Function(_$LocalAccountImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? identityMaster = null,
|
||||
Object? identitySecretBytes = null,
|
||||
Object? encryptionKeyType = null,
|
||||
Object? biometricsEnabled = null,
|
||||
Object? hiddenAccount = null,
|
||||
Object? name = null,
|
||||
}) {
|
||||
return _then(_$LocalAccountImpl(
|
||||
identityMaster: null == identityMaster
|
||||
? _value.identityMaster
|
||||
: identityMaster // ignore: cast_nullable_to_non_nullable
|
||||
as IdentityMaster,
|
||||
identitySecretBytes: null == identitySecretBytes
|
||||
? _value.identitySecretBytes
|
||||
: identitySecretBytes // ignore: cast_nullable_to_non_nullable
|
||||
as Uint8List,
|
||||
encryptionKeyType: null == encryptionKeyType
|
||||
? _value.encryptionKeyType
|
||||
: encryptionKeyType // ignore: cast_nullable_to_non_nullable
|
||||
as EncryptionKeyType,
|
||||
biometricsEnabled: null == biometricsEnabled
|
||||
? _value.biometricsEnabled
|
||||
: biometricsEnabled // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hiddenAccount: null == hiddenAccount
|
||||
? _value.hiddenAccount
|
||||
: hiddenAccount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$LocalAccountImpl implements _LocalAccount {
|
||||
const _$LocalAccountImpl(
|
||||
{required this.identityMaster,
|
||||
@Uint8ListJsonConverter() required this.identitySecretBytes,
|
||||
required this.encryptionKeyType,
|
||||
required this.biometricsEnabled,
|
||||
required this.hiddenAccount,
|
||||
required this.name});
|
||||
|
||||
factory _$LocalAccountImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$LocalAccountImplFromJson(json);
|
||||
|
||||
// The master key record for the account, containing the identityPublicKey
|
||||
@override
|
||||
final IdentityMaster identityMaster;
|
||||
// The encrypted identity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@override
|
||||
@Uint8ListJsonConverter()
|
||||
final Uint8List identitySecretBytes;
|
||||
// The kind of encryption input used on the account
|
||||
@override
|
||||
final EncryptionKeyType encryptionKeyType;
|
||||
// If account is not hidden, password can be retrieved via
|
||||
@override
|
||||
final bool biometricsEnabled;
|
||||
// Keep account hidden unless account password is entered
|
||||
// (tries all hidden accounts with auth method (no biometrics))
|
||||
@override
|
||||
final bool hiddenAccount;
|
||||
// Display name for account until it is unlocked
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LocalAccount(identityMaster: $identityMaster, identitySecretBytes: $identitySecretBytes, encryptionKeyType: $encryptionKeyType, biometricsEnabled: $biometricsEnabled, hiddenAccount: $hiddenAccount, name: $name)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$LocalAccountImpl &&
|
||||
(identical(other.identityMaster, identityMaster) ||
|
||||
other.identityMaster == identityMaster) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.identitySecretBytes, identitySecretBytes) &&
|
||||
(identical(other.encryptionKeyType, encryptionKeyType) ||
|
||||
other.encryptionKeyType == encryptionKeyType) &&
|
||||
(identical(other.biometricsEnabled, biometricsEnabled) ||
|
||||
other.biometricsEnabled == biometricsEnabled) &&
|
||||
(identical(other.hiddenAccount, hiddenAccount) ||
|
||||
other.hiddenAccount == hiddenAccount) &&
|
||||
(identical(other.name, name) || other.name == name));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
identityMaster,
|
||||
const DeepCollectionEquality().hash(identitySecretBytes),
|
||||
encryptionKeyType,
|
||||
biometricsEnabled,
|
||||
hiddenAccount,
|
||||
name);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$LocalAccountImplCopyWith<_$LocalAccountImpl> get copyWith =>
|
||||
__$$LocalAccountImplCopyWithImpl<_$LocalAccountImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$LocalAccountImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _LocalAccount implements LocalAccount {
|
||||
const factory _LocalAccount(
|
||||
{required final IdentityMaster identityMaster,
|
||||
@Uint8ListJsonConverter() required final Uint8List identitySecretBytes,
|
||||
required final EncryptionKeyType encryptionKeyType,
|
||||
required final bool biometricsEnabled,
|
||||
required final bool hiddenAccount,
|
||||
required final String name}) = _$LocalAccountImpl;
|
||||
|
||||
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
|
||||
// the identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter()
|
||||
Uint8List get identitySecretBytes;
|
||||
@override // The kind of encryption input used on the account
|
||||
EncryptionKeyType get encryptionKeyType;
|
||||
@override // If account is not hidden, password can be retrieved via
|
||||
bool get biometricsEnabled;
|
||||
@override // Keep account hidden unless account password is entered
|
||||
// (tries all hidden accounts with auth method (no biometrics))
|
||||
bool get hiddenAccount;
|
||||
@override // Display name for account until it is unlocked
|
||||
String get name;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$LocalAccountImplCopyWith<_$LocalAccountImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'local_account.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$LocalAccountImpl _$$LocalAccountImplFromJson(Map<String, dynamic> json) =>
|
||||
_$LocalAccountImpl(
|
||||
identityMaster: IdentityMaster.fromJson(json['identity_master']),
|
||||
identitySecretBytes: const Uint8ListJsonConverter()
|
||||
.fromJson(json['identity_secret_bytes']),
|
||||
encryptionKeyType:
|
||||
EncryptionKeyType.fromJson(json['encryption_key_type']),
|
||||
biometricsEnabled: json['biometrics_enabled'] as bool,
|
||||
hiddenAccount: json['hidden_account'] as bool,
|
||||
name: json['name'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$LocalAccountImplToJson(_$LocalAccountImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'identity_master': instance.identityMaster.toJson(),
|
||||
'identity_secret_bytes':
|
||||
const Uint8ListJsonConverter().toJson(instance.identitySecretBytes),
|
||||
'encryption_key_type': instance.encryptionKeyType.toJson(),
|
||||
'biometrics_enabled': instance.biometricsEnabled,
|
||||
'hidden_account': instance.hiddenAccount,
|
||||
'name': instance.name,
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
class NewProfileSpec {
|
||||
NewProfileSpec({required this.name, required this.pronouns});
|
||||
String name;
|
||||
String pronouns;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
part 'user_login.freezed.dart';
|
||||
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
|
||||
@freezed
|
||||
class UserLogin with _$UserLogin {
|
||||
const factory UserLogin({
|
||||
// Master record key for the user used to index the local accounts table
|
||||
required TypedKey accountMasterRecordKey,
|
||||
// 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
|
||||
required AccountRecordInfo accountRecordInfo,
|
||||
|
||||
// The time this login was most recently used
|
||||
required Timestamp lastActive,
|
||||
}) = _UserLogin;
|
||||
|
||||
factory UserLogin.fromJson(dynamic json) =>
|
||||
_$UserLoginFromJson(json as Map<String, dynamic>);
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'user_login.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
UserLogin _$UserLoginFromJson(Map<String, dynamic> json) {
|
||||
return _UserLogin.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$UserLogin {
|
||||
// Master record key for the user used to index the local accounts table
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||
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
|
||||
AccountRecordInfo get accountRecordInfo =>
|
||||
throw _privateConstructorUsedError; // The time this login was most recently used
|
||||
Timestamp get lastActive => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$UserLoginCopyWith<UserLogin> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $UserLoginCopyWith<$Res> {
|
||||
factory $UserLoginCopyWith(UserLogin value, $Res Function(UserLogin) then) =
|
||||
_$UserLoginCopyWithImpl<$Res, UserLogin>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
Typed<FixedEncodedString43> identitySecret,
|
||||
AccountRecordInfo accountRecordInfo,
|
||||
Timestamp lastActive});
|
||||
|
||||
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin>
|
||||
implements $UserLoginCopyWith<$Res> {
|
||||
_$UserLoginCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? accountMasterRecordKey = 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
|
||||
as Typed<FixedEncodedString43>,
|
||||
identitySecret: null == identitySecret
|
||||
? _value.identitySecret
|
||||
: identitySecret // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
accountRecordInfo: null == accountRecordInfo
|
||||
? _value.accountRecordInfo
|
||||
: accountRecordInfo // ignore: cast_nullable_to_non_nullable
|
||||
as AccountRecordInfo,
|
||||
lastActive: null == lastActive
|
||||
? _value.lastActive
|
||||
: lastActive // ignore: cast_nullable_to_non_nullable
|
||||
as Timestamp,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo {
|
||||
return $AccountRecordInfoCopyWith<$Res>(_value.accountRecordInfo, (value) {
|
||||
return _then(_value.copyWith(accountRecordInfo: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$UserLoginImplCopyWith<$Res>
|
||||
implements $UserLoginCopyWith<$Res> {
|
||||
factory _$$UserLoginImplCopyWith(
|
||||
_$UserLoginImpl value, $Res Function(_$UserLoginImpl) then) =
|
||||
__$$UserLoginImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
Typed<FixedEncodedString43> identitySecret,
|
||||
AccountRecordInfo accountRecordInfo,
|
||||
Timestamp lastActive});
|
||||
|
||||
@override
|
||||
$AccountRecordInfoCopyWith<$Res> get accountRecordInfo;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$UserLoginImplCopyWithImpl<$Res>
|
||||
extends _$UserLoginCopyWithImpl<$Res, _$UserLoginImpl>
|
||||
implements _$$UserLoginImplCopyWith<$Res> {
|
||||
__$$UserLoginImplCopyWithImpl(
|
||||
_$UserLoginImpl _value, $Res Function(_$UserLoginImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? accountMasterRecordKey = 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
|
||||
as Typed<FixedEncodedString43>,
|
||||
identitySecret: null == identitySecret
|
||||
? _value.identitySecret
|
||||
: identitySecret // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
accountRecordInfo: null == accountRecordInfo
|
||||
? _value.accountRecordInfo
|
||||
: accountRecordInfo // ignore: cast_nullable_to_non_nullable
|
||||
as AccountRecordInfo,
|
||||
lastActive: null == lastActive
|
||||
? _value.lastActive
|
||||
: lastActive // ignore: cast_nullable_to_non_nullable
|
||||
as Timestamp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$UserLoginImpl implements _UserLogin {
|
||||
const _$UserLoginImpl(
|
||||
{required this.accountMasterRecordKey,
|
||||
required this.identitySecret,
|
||||
required this.accountRecordInfo,
|
||||
required this.lastActive});
|
||||
|
||||
factory _$UserLoginImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$UserLoginImplFromJson(json);
|
||||
|
||||
// Master record key for the user used to index the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> accountMasterRecordKey;
|
||||
// The identity secret as unlocked from the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> identitySecret;
|
||||
// The account record key, owner key and secret pulled from the identity
|
||||
@override
|
||||
final AccountRecordInfo accountRecordInfo;
|
||||
// The time this login was most recently used
|
||||
@override
|
||||
final Timestamp lastActive;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserLogin(accountMasterRecordKey: $accountMasterRecordKey, identitySecret: $identitySecret, accountRecordInfo: $accountRecordInfo, lastActive: $lastActive)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$UserLoginImpl &&
|
||||
(identical(other.accountMasterRecordKey, accountMasterRecordKey) ||
|
||||
other.accountMasterRecordKey == accountMasterRecordKey) &&
|
||||
(identical(other.identitySecret, identitySecret) ||
|
||||
other.identitySecret == identitySecret) &&
|
||||
(identical(other.accountRecordInfo, accountRecordInfo) ||
|
||||
other.accountRecordInfo == accountRecordInfo) &&
|
||||
(identical(other.lastActive, lastActive) ||
|
||||
other.lastActive == lastActive));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, accountMasterRecordKey,
|
||||
identitySecret, accountRecordInfo, lastActive);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith =>
|
||||
__$$UserLoginImplCopyWithImpl<_$UserLoginImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$UserLoginImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _UserLogin implements UserLogin {
|
||||
const factory _UserLogin(
|
||||
{required final Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
required final Typed<FixedEncodedString43> identitySecret,
|
||||
required final AccountRecordInfo accountRecordInfo,
|
||||
required final Timestamp lastActive}) = _$UserLoginImpl;
|
||||
|
||||
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 // 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
|
||||
AccountRecordInfo get accountRecordInfo;
|
||||
@override // The time this login was most recently used
|
||||
Timestamp get lastActive;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_login.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$UserLoginImpl _$$UserLoginImplFromJson(Map<String, dynamic> json) =>
|
||||
_$UserLoginImpl(
|
||||
accountMasterRecordKey: Typed<FixedEncodedString43>.fromJson(
|
||||
json['account_master_record_key']),
|
||||
identitySecret:
|
||||
Typed<FixedEncodedString43>.fromJson(json['identity_secret']),
|
||||
accountRecordInfo:
|
||||
AccountRecordInfo.fromJson(json['account_record_info']),
|
||||
lastActive: Timestamp.fromJson(json['last_active']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$UserLoginImplToJson(_$UserLoginImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'account_master_record_key': instance.accountMasterRecordKey.toJson(),
|
||||
'identity_secret': instance.identitySecret.toJson(),
|
||||
'account_record_info': instance.accountRecordInfo.toJson(),
|
||||
'last_active': instance.lastActive.toJson(),
|
||||
};
|
1
lib/account_manager/repository/repository.dart
Normal file
1
lib/account_manager/repository/repository.dart
Normal file
|
@ -0,0 +1 @@
|
|||
export 'account_repository/account_repository.dart';
|
136
lib/account_manager/view/new_account_page/new_account_page.dart
Normal file
136
lib/account_manager/view/new_account_page/new_account_page.dart
Normal file
|
@ -0,0 +1,136 @@
|
|||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.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 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../components/default_app_bar.dart';
|
||||
import '../../../components/signal_strength_meter.dart';
|
||||
import '../../../entities/entities.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
|
||||
class NewAccountPage extends StatefulWidget {
|
||||
const NewAccountPage({super.key});
|
||||
|
||||
@override
|
||||
NewAccountPageState createState() => NewAccountPageState();
|
||||
}
|
||||
|
||||
class NewAccountPageState extends State<NewAccountPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
late bool isInAsyncCall = false;
|
||||
static const String formFieldName = 'name';
|
||||
static const String formFieldPronouns = 'pronouns';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
setState(() {});
|
||||
await changeWindowSetup(
|
||||
TitleBarStyle.normal, OrientationCapability.portraitOnly);
|
||||
});
|
||||
}
|
||||
|
||||
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(),
|
||||
]),
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: formFieldPronouns,
|
||||
maxLength: 64,
|
||||
decoration: InputDecoration(
|
||||
labelText: translate('account.form_pronouns')),
|
||||
),
|
||||
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) {
|
||||
setState(() {
|
||||
isInAsyncCall = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(translate('new_account_page.create')),
|
||||
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final displayModalHUD =
|
||||
isInAsyncCall || !localAccounts.hasValue || !logins.hasValue;
|
||||
|
||||
return Scaffold(
|
||||
// resizeToAvoidBottomInset: false,
|
||||
appBar: DefaultAppBar(
|
||||
title: Text(translate('new_account_page.titlebar')),
|
||||
actions: [
|
||||
const SignalStrengthMeterWidget(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
tooltip: translate('app_bar.settings_tooltip'),
|
||||
onPressed: () async {
|
||||
context.go('/new_account/settings');
|
||||
})
|
||||
]),
|
||||
body: _newAccountForm(
|
||||
context,
|
||||
onSubmit: (formKey) async {
|
||||
FocusScope.of(context).unfocus();
|
||||
try {
|
||||
await createAccount();
|
||||
} on Exception catch (e) {
|
||||
if (context.mounted) {
|
||||
await showErrorModal(context, translate('new_account_page.error'),
|
||||
'Exception: $e');
|
||||
}
|
||||
}
|
||||
},
|
||||
).paddingSymmetric(horizontal: 24, vertical: 8),
|
||||
).withModalHUD(context, displayModalHUD);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<bool>('isInAsyncCall', isInAsyncCall));
|
||||
}
|
||||
}
|
1
lib/account_manager/view/view.dart
Normal file
1
lib/account_manager/view/view.dart
Normal file
|
@ -0,0 +1 @@
|
|||
export 'new_account_page/new_account_page.dart';
|
Loading…
Add table
Add a link
Reference in a new issue