Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cat-voices): caching user #1391

Merged
merged 11 commits into from
Dec 16, 2024
16 changes: 9 additions & 7 deletions catalyst_voices/apps/voices/lib/dependency/dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ final class Dependencies extends DependencyProvider {
() {
return SessionCubit(
get<UserService>(),
get<DummyUserFactory>(),
get<RegistrationService>(),
get<RegistrationProgressNotifier>(),
get<AccessControl>(),
Expand Down Expand Up @@ -95,7 +94,13 @@ final class Dependencies extends DependencyProvider {
)
..registerLazySingleton<ProposalRepository>(ProposalRepository.new)
..registerLazySingleton<CampaignRepository>(CampaignRepository.new)
..registerLazySingleton<ConfigRepository>(ConfigRepository.new);
..registerLazySingleton<ConfigRepository>(ConfigRepository.new)
..registerLazySingleton<UserRepository>(() {
return UserRepository(
get<UserStorage>(),
get<KeychainProvider>(),
);
});
}

void _registerServices() {
Expand All @@ -111,7 +116,6 @@ final class Dependencies extends DependencyProvider {
});
registerLazySingleton<Downloader>(Downloader.new);
registerLazySingleton<CatalystCardano>(() => CatalystCardano.instance);
registerLazySingleton<UserStorage>(SecureUserStorage.new);
registerLazySingleton<RegistrationProgressNotifier>(
RegistrationProgressNotifier.new,
);
Expand All @@ -126,14 +130,11 @@ final class Dependencies extends DependencyProvider {
registerLazySingleton<UserService>(
() {
return UserService(
keychainProvider: get<KeychainProvider>(),
userStorage: get<UserStorage>(),
dummyUserFactory: get<DummyUserFactory>(),
userRepository: get<UserRepository>(),
);
},
dispose: (service) => unawaited(service.dispose()),
);
registerLazySingleton<DummyUserFactory>(DummyUserFactory.new);
registerLazySingleton<AccessControl>(AccessControl.new);
registerLazySingleton<CampaignService>(() {
return CampaignService(
Expand All @@ -155,5 +156,6 @@ final class Dependencies extends DependencyProvider {
void _registerStorages() {
registerLazySingleton<FlutterSecureStorage>(FlutterSecureStorage.new);
registerLazySingleton<SharedPreferencesAsync>(SharedPreferencesAsync.new);
registerLazySingleton<UserStorage>(SecureUserStorage.new);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_services/catalyst_voices_services.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Expand Down Expand Up @@ -42,7 +42,7 @@ class _ToggleStateTextState extends State<ToggleStateText> {

await sessionBloc
.switchToDummyAccount()
.then((_) => sessionBloc.unlock(DummyUserFactory.dummyUnlockFactor));
.then((_) => sessionBloc.unlock(Account.dummyUnlockFactor));
};
}

Expand Down
10 changes: 10 additions & 0 deletions catalyst_voices/melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ scripts:
The catalyst_voices_repositories is skipped because to run a build_runner there you
must generate first swagger docs (see related Earthfile).

build_runner_repository:
run: |
melos exec -c 1 \
--depends-on="build_runner" \
--scope="catalyst_voices_repositories" -- \
dart run build_runner build --delete-conflicting-outputs \
--build-filter="lib/src/dto/*"
description: |
Run `build_runner` in catalyst_voices_repositories package only in selected folders

metrics:
run: |
melos exec -c 1 -- \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,15 @@ final class RecoverCubit extends Cubit<RecoverStateData>
}

final lockFactor = PasswordLockFactor(password.value);

await _registrationService.createKeychainFor(
account: account,
final masterKey = await _registrationService.deriveMasterKey(
seedPhrase: seedPhrase,
lockFactor: lockFactor,
);

final keychain = account.keychain;
await keychain.setLock(lockFactor);
await keychain.unlock(lockFactor);
await keychain.setMasterKey(masterKey);

await _userService.useAccount(account);

return true;
Expand All @@ -162,7 +164,7 @@ final class RecoverCubit extends Cubit<RecoverStateData>
Future<void> reset() async {
final recoveredAccount = _recoveredAccount;
if (recoveredAccount != null) {
await _userService.removeKeychain(recoveredAccount.keychainId);
await _userService.removeAccount(recoveredAccount);
}

_recoveredAccount = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,30 @@ import 'package:flutter_bloc/flutter_bloc.dart';
final class SessionCubit extends Cubit<SessionState>
with BlocErrorEmitterMixin {
final UserService _userService;
final DummyUserFactory _dummyUserFactory;
final RegistrationService _registrationService;
final RegistrationProgressNotifier _registrationProgressNotifier;
final AccessControl _accessControl;
final AdminTools _adminTools;

final _logger = Logger('SessionCubit');

bool _hasKeychain = false;
bool _isUnlocked = false;
Account? _account;
AdminToolsState _adminToolsState;

StreamSubscription<bool>? _keychainSub;
StreamSubscription<bool>? _keychainUnlockedSub;
StreamSubscription<Account?>? _accountSub;
StreamSubscription<AdminToolsState>? _adminToolsSub;

SessionCubit(
this._userService,
this._dummyUserFactory,
this._registrationService,
this._registrationProgressNotifier,
this._accessControl,
this._adminTools,
) : _adminToolsState = _adminTools.state,
super(const VisitorSessionState(isRegistrationInProgress: false)) {
_keychainSub = _userService.watchKeychain
.map((keychain) => keychain != null)
.distinct()
.listen(_onHasKeychainChanged);

_keychainUnlockedSub = _userService.watchKeychain
.transform(KeychainToUnlockTransformer())
_keychainUnlockedSub = _userService.watchAccount
.transform(AccountToKeychainUnlockTransformer())
.distinct()
.listen(_onActiveKeychainUnlockChanged);

Expand All @@ -56,43 +46,39 @@ final class SessionCubit extends Cubit<SessionState>
_adminToolsSub = _adminTools.stream.listen(_onAdminToolsChanged);
}

Future<bool> unlock(LockFactor lockFactor) {
return _userService.keychain!.unlock(lockFactor);
Future<bool> unlock(LockFactor lockFactor) async {
final keychain = _userService.account?.keychain;
if (keychain == null) {
return false;
}

return keychain.unlock(lockFactor);
}

Future<void> lock() async {
await _userService.keychain!.lock();
await _userService.account?.keychain.lock();
}

Future<void> removeKeychain() {
return _userService.removeCurrentKeychain();
Future<void> removeKeychain() async {
final account = _userService.account;
if (account != null) {
await _userService.removeAccount(account);
}
}

Future<void> switchToDummyAccount() async {
final keychains = await _userService.keychains;
final dummyKeychain = keychains.firstWhereOrNull(
(keychain) => keychain.id == DummyUserFactory.dummyKeychainId,
);

if (dummyKeychain != null) {
await _userService.useKeychain(dummyKeychain.id);
final account = _userService.account;
if (account?.isDummy ?? false) {
return;
}

final account = await _registrationService.registerTestAccount(
keychainId: DummyUserFactory.dummyKeychainId,
seedPhrase: DummyUserFactory.dummySeedPhrase,
lockFactor: DummyUserFactory.dummyUnlockFactor,
);
final dummyAccount = await _getDummyAccount();

await _userService.useAccount(account);
await _userService.useAccount(dummyAccount);
}

@override
Future<void> close() async {
await _keychainSub?.cancel();
_keychainSub = null;

await _keychainUnlockedSub?.cancel();
_keychainUnlockedSub = null;

Expand All @@ -108,24 +94,17 @@ final class SessionCubit extends Cubit<SessionState>
return super.close();
}

void _onHasKeychainChanged(bool hasKeychain) {
_logger.fine('Has keychain changed [$hasKeychain]');
void _onActiveAccountChanged(Account? account) {
_logger.fine('Active account changed [$account]');

_account = account;

_hasKeychain = hasKeychain;
_updateState();
}

void _onActiveKeychainUnlockChanged(bool isUnlocked) {
_logger.fine('Keychain unlock changed [$isUnlocked]');

_isUnlocked = isUnlocked;
_updateState();
}

void _onActiveAccountChanged(Account? account) {
_logger.fine('Active account changed [$account]');

_account = account;
_updateState();
}

Expand All @@ -142,18 +121,23 @@ final class SessionCubit extends Cubit<SessionState>

void _updateState() {
if (_adminToolsState.enabled) {
emit(_createMockedSessionState());
unawaited(
_createMockedSessionState().then((value) {
if (!isClosed) {
emit(value);
}
}),
);
} else {
emit(_createSessionState());
}
}

SessionState _createSessionState() {
final hasKeychain = _hasKeychain;
final isUnlocked = _isUnlocked;
final account = _account;
final isUnlocked = _account?.keychain.lastIsUnlocked ?? false;

if (!hasKeychain) {
if (account == null) {
final isEmpty = _registrationProgressNotifier.value.isEmpty;
return VisitorSessionState(isRegistrationInProgress: !isEmpty);
}
Expand All @@ -174,11 +158,14 @@ final class SessionCubit extends Cubit<SessionState>
);
}

SessionState _createMockedSessionState() {
Future<SessionState> _createMockedSessionState() async {
dtscalac marked this conversation as resolved.
Show resolved Hide resolved
switch (_adminToolsState.sessionStatus) {
case SessionStatus.actor:
// TODO(damian-molinski): Limiting exposed Account so its not future.
final dummyAccount = await _getDummyAccount();

return ActiveAccountSessionState(
account: _dummyUserFactory.buildDummyAccount(),
account: dummyAccount,
spaces: Space.values,
overallSpaces: Space.values,
spacesShortcuts: AccessControl.allSpacesShortcutsActivators,
Expand All @@ -189,4 +176,16 @@ final class SessionCubit extends Cubit<SessionState>
return const VisitorSessionState(isRegistrationInProgress: false);
}
}

Future<Account> _getDummyAccount() async {
final dummyAccount =
_userService.accounts.firstWhereOrNull((e) => e.isDummy);

return dummyAccount ??
await _registrationService.registerTestAccount(
keychainId: Account.dummyKeychainId,
seedPhrase: Account.dummySeedPhrase,
lockFactor: Account.dummyUnlockFactor,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ final class GuestSessionState extends SessionState {

/// The user has registered and unlocked the keychain.
final class ActiveAccountSessionState extends SessionState {
// TODO(damian-molinski): Try limiting exposed Account to something smaller.
final Account? account;
@override
final List<Space> spaces;
Expand Down
Loading
Loading