mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-25 08:39:06 +00:00
feat: totp linux flow + fixes for wallet switching
This commit is contained in:
parent
2bce18d240
commit
feb385835a
22 changed files with 566 additions and 761 deletions
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/secure_storage.dart';
|
import 'package:cake_wallet/core/secure_storage.dart';
|
||||||
|
@ -10,8 +10,6 @@ import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||||
import 'package:cake_wallet/entities/encrypt.dart';
|
import 'package:cake_wallet/entities/encrypt.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
|
||||||
import '../src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
|
||||||
|
|
||||||
class AuthService with Store {
|
class AuthService with Store {
|
||||||
AuthService({
|
AuthService({
|
||||||
required this.secureStorage,
|
required this.secureStorage,
|
||||||
|
@ -86,62 +84,55 @@ class AuthService with Store {
|
||||||
|
|
||||||
Future<void> authenticateAction(BuildContext context,
|
Future<void> authenticateAction(BuildContext context,
|
||||||
{Function(bool)? onAuthSuccess,
|
{Function(bool)? onAuthSuccess,
|
||||||
|
String? authRoute,
|
||||||
|
Object? authArguments,
|
||||||
String? route,
|
String? route,
|
||||||
Object? arguments,
|
Object? arguments,
|
||||||
|
bool? alwaysRequireAuth,
|
||||||
required bool conditionToDetermineIfToUse2FA}) async {
|
required bool conditionToDetermineIfToUse2FA}) async {
|
||||||
assert(route != null || onAuthSuccess != null,
|
assert(route != null || onAuthSuccess != null,
|
||||||
'Either route or onAuthSuccess param must be passed.');
|
'Either route or onAuthSuccess param must be passed.');
|
||||||
|
|
||||||
if (!conditionToDetermineIfToUse2FA) {
|
if (!conditionToDetermineIfToUse2FA) {
|
||||||
if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) {
|
if (alwaysRequireAuth != true &&
|
||||||
|
!requireAuth() &&
|
||||||
|
!_alwaysAuthenticateRoutes.contains(route)) {
|
||||||
if (onAuthSuccess != null) {
|
if (onAuthSuccess != null) {
|
||||||
onAuthSuccess(true);
|
onAuthSuccess(true);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(route ?? '', arguments: arguments);
|
||||||
route ?? '',
|
|
||||||
arguments: arguments,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(Routes.auth,
|
|
||||||
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
|
||||||
if (!isAuthenticatedSuccessfully) {
|
|
||||||
onAuthSuccess?.call(false);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (settingsStore.useTOTP2FA && conditionToDetermineIfToUse2FA) {
|
|
||||||
auth.close(
|
|
||||||
route: Routes.totpAuthCodePage,
|
|
||||||
arguments: TotpAuthArgumentsModel(
|
|
||||||
isForSetup: !settingsStore.useTOTP2FA,
|
|
||||||
onTotpAuthenticationFinished:
|
|
||||||
(bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) async {
|
|
||||||
if (!isAuthenticatedSuccessfully) {
|
|
||||||
onAuthSuccess?.call(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (onAuthSuccess != null) {
|
|
||||||
totpAuth.close().then((value) => onAuthSuccess.call(true));
|
|
||||||
} else {
|
|
||||||
totpAuth.close(route: route, arguments: arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (onAuthSuccess != null) {
|
|
||||||
auth.close().then((value) => onAuthSuccess.call(true));
|
|
||||||
} else {
|
|
||||||
auth.close(route: route, arguments: arguments);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
Navigator.of(context).pushNamed(authRoute ?? Routes.auth,
|
||||||
|
arguments: authArguments ??
|
||||||
|
(SettingsStoreBase.walletPasswordDirectInput
|
||||||
|
? WalletUnlockArguments(
|
||||||
|
useTotp: conditionToDetermineIfToUse2FA,
|
||||||
|
callback: (AuthResponse auth) async {
|
||||||
|
if (!auth.success) {
|
||||||
|
onAuthSuccess?.call(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onAuthSuccess != null) {
|
||||||
|
auth.close().then((value) => onAuthSuccess.call(true));
|
||||||
|
} else {
|
||||||
|
auth.close(route: route, arguments: arguments);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: (AuthResponse auth) {
|
||||||
|
if (!auth.success) {
|
||||||
|
onAuthSuccess?.call(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onAuthSuccess != null) {
|
||||||
|
auth.close().then((value) => onAuthSuccess.call(true));
|
||||||
|
} else {
|
||||||
|
auth.close(route: route, arguments: arguments);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
lib/core/authentication_request_data.dart
Normal file
14
lib/core/authentication_request_data.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
typedef CloseAuth = Future<void> Function({String? route, dynamic arguments});
|
||||||
|
|
||||||
|
class AuthResponse {
|
||||||
|
AuthResponse({bool success = false, this.payload, required this.close, String? error})
|
||||||
|
: this.success = success,
|
||||||
|
this.error = success == false ? error ?? '' : null;
|
||||||
|
|
||||||
|
final bool success;
|
||||||
|
final dynamic payload;
|
||||||
|
final String? error;
|
||||||
|
final CloseAuth close;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef OnAuthenticationFinished = void Function(AuthResponse);
|
|
@ -1,13 +1,27 @@
|
||||||
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
|
|
||||||
|
class TotpResponse {
|
||||||
|
TotpResponse({bool success = false, required this.close, String? error})
|
||||||
|
: this.success = success,
|
||||||
|
this.error = success == false ? error ?? '' : null;
|
||||||
|
|
||||||
|
final bool success;
|
||||||
|
final String? error;
|
||||||
|
final CloseAuth close;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef OnTotpAuthenticationFinished = void Function(TotpResponse);
|
||||||
|
|
||||||
class TotpAuthArgumentsModel {
|
class TotpAuthArgumentsModel {
|
||||||
final bool? isForSetup;
|
final bool? isForSetup;
|
||||||
final bool? isClosable;
|
final bool? isClosable;
|
||||||
|
final bool? showPopup;
|
||||||
final OnTotpAuthenticationFinished? onTotpAuthenticationFinished;
|
final OnTotpAuthenticationFinished? onTotpAuthenticationFinished;
|
||||||
|
|
||||||
TotpAuthArgumentsModel({
|
TotpAuthArgumentsModel({
|
||||||
this.isForSetup,
|
this.isForSetup,
|
||||||
this.isClosable,
|
this.isClosable,
|
||||||
|
this.showPopup,
|
||||||
this.onTotpAuthenticationFinished,
|
this.onTotpAuthenticationFinished,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
159
lib/di.dart
159
lib/di.dart
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||||
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cake_wallet/core/yat_service.dart';
|
import 'package:cake_wallet/core/yat_service.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
@ -7,7 +7,6 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||||
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
|
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
|
||||||
import 'package:cake_wallet/core/yat_service.dart';
|
|
||||||
import 'package:cake_wallet/entities/background_tasks.dart';
|
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||||
|
@ -22,10 +21,8 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
||||||
|
@ -299,8 +296,7 @@ Future setup({
|
||||||
getIt.registerSingleton<ExchangeTemplateStore>(
|
getIt.registerSingleton<ExchangeTemplateStore>(
|
||||||
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
||||||
getIt.registerSingleton<YatStore>(
|
getIt.registerSingleton<YatStore>(
|
||||||
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())
|
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
|
||||||
..init());
|
|
||||||
getIt.registerSingleton<AnonpayTransactionsStore>(
|
getIt.registerSingleton<AnonpayTransactionsStore>(
|
||||||
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
|
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
|
||||||
|
|
||||||
|
@ -382,8 +378,8 @@ Future setup({
|
||||||
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(getIt.get<AuthService>(),
|
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(getIt.get<AuthService>(),
|
||||||
getIt.get<SharedPreferences>(), getIt.get<SettingsStore>(), BiometricAuth()));
|
getIt.get<SharedPreferences>(), getIt.get<SettingsStore>(), BiometricAuth()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<AuthPage, void Function(bool, AuthPageState), bool>(
|
getIt.registerFactoryParam<AuthPage, OnAuthenticationFinished, bool>((onAuthFinished, closable) =>
|
||||||
(onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
|
AuthPage(getIt.get<AuthViewModel>(),
|
||||||
onAuthenticationFinished: onAuthFinished, closable: closable));
|
onAuthenticationFinished: onAuthFinished, closable: closable));
|
||||||
|
|
||||||
getIt.registerFactory<Setup2FAViewModel>(
|
getIt.registerFactory<Setup2FAViewModel>(
|
||||||
|
@ -395,16 +391,12 @@ Future setup({
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<TotpAuthCodePage, TotpAuthArgumentsModel, void>(
|
getIt.registerFactoryParam<TotpAuthCodePage, TotpAuthArgumentsModel, void>(
|
||||||
(totpAuthPageArguments, _) => TotpAuthCodePage(
|
(totpAuthPageArguments, _) => TotpAuthCodePage(
|
||||||
getIt.get<Setup2FAViewModel>(),
|
setup2FAViewModel: getIt.get<Setup2FAViewModel>(), totpArguments: totpAuthPageArguments));
|
||||||
totpArguments: totpAuthPageArguments,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
getIt.registerFactory<AuthPage>(() {
|
getIt.registerFactory<AuthPage>(() {
|
||||||
return AuthPage(getIt.get<AuthViewModel>(),
|
return AuthPage(getIt.get<AuthViewModel>(), onAuthenticationFinished: (auth) {
|
||||||
onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) {
|
if (!auth.success) {
|
||||||
if (!isAuthenticated) {
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
final authStore = getIt.get<AuthenticationStore>();
|
final authStore = getIt.get<AuthenticationStore>();
|
||||||
|
@ -413,14 +405,13 @@ Future setup({
|
||||||
final shouldUseTotp2FAToAccessWallets =
|
final shouldUseTotp2FAToAccessWallets =
|
||||||
appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||||
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||||
authPageState.close(
|
auth.close(
|
||||||
route: Routes.totpAuthCodePage,
|
route: Routes.totpAuthCodePage,
|
||||||
arguments: TotpAuthArgumentsModel(
|
arguments: TotpAuthArgumentsModel(
|
||||||
isForSetup: false,
|
isForSetup: false,
|
||||||
isClosable: false,
|
isClosable: false,
|
||||||
onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully,
|
onTotpAuthenticationFinished: (totpAuth) async {
|
||||||
TotpAuthCodePageState totpAuthPageState) async {
|
if (!totpAuth.success) {
|
||||||
if (!isAuthenticatedSuccessfully) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (appStore.wallet != null) {
|
if (appStore.wallet != null) {
|
||||||
|
@ -428,12 +419,6 @@ Future setup({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
totpAuthPageState.changeProcessText('Loading the wallet');
|
|
||||||
|
|
||||||
if (loginError != null) {
|
|
||||||
totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}');
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactionDisposer? _reaction;
|
ReactionDisposer? _reaction;
|
||||||
_reaction = reaction((_) => appStore.wallet, (Object? _) {
|
_reaction = reaction((_) => appStore.wallet, (Object? _) {
|
||||||
_reaction?.reaction.dispose();
|
_reaction?.reaction.dispose();
|
||||||
|
@ -448,12 +433,6 @@ Future setup({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
authPageState.changeProcessText('Loading the wallet');
|
|
||||||
|
|
||||||
if (loginError != null) {
|
|
||||||
authPageState.changeProcessText('ERROR: ${loginError.toString()}');
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactionDisposer? _reaction;
|
ReactionDisposer? _reaction;
|
||||||
_reaction = reaction((_) => appStore.wallet, (Object? _) {
|
_reaction = reaction((_) => appStore.wallet, (Object? _) {
|
||||||
_reaction?.reaction.dispose();
|
_reaction?.reaction.dispose();
|
||||||
|
@ -760,17 +739,14 @@ Future setup({
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return monero!.createMoneroWalletService(_walletInfoSource);
|
return monero!.createMoneroWalletService(_walletInfoSource);
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return bitcoin!.createBitcoinWalletService(
|
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!,
|
||||||
_walletInfoSource, _unspentCoinsInfoSource!,
|
|
||||||
SettingsStoreBase.walletPasswordDirectInput);
|
SettingsStoreBase.walletPasswordDirectInput);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return bitcoin!.createLitecoinWalletService(
|
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!,
|
||||||
_walletInfoSource, _unspentCoinsInfoSource!,
|
|
||||||
SettingsStoreBase.walletPasswordDirectInput);
|
SettingsStoreBase.walletPasswordDirectInput);
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return ethereum!.createEthereumWalletService(
|
return ethereum!.createEthereumWalletService(
|
||||||
_walletInfoSource,
|
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||||
SettingsStoreBase.walletPasswordDirectInput);
|
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||||
}
|
}
|
||||||
|
@ -825,19 +801,16 @@ Future setup({
|
||||||
trades: _tradesSource,
|
trades: _tradesSource,
|
||||||
settingsStore: getIt.get<SettingsStore>()));
|
settingsStore: getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupService(
|
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||||
getIt.get<SecureStorage>(),
|
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||||
_walletInfoSource,
|
|
||||||
getIt.get<KeyService>(),
|
|
||||||
getIt.get<SharedPreferences>()));
|
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupViewModel(
|
getIt.registerFactory(() => BackupViewModel(
|
||||||
getIt.get<SecureStorage>(), getIt.get<SecretStore>(), getIt.get<BackupService>()));
|
getIt.get<SecureStorage>(), getIt.get<SecretStore>(), getIt.get<BackupService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
|
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
getIt.registerFactory(
|
||||||
EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
|
() => EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
|
getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
|
||||||
|
|
||||||
|
@ -1063,63 +1036,73 @@ Future setup({
|
||||||
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
||||||
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
String currentWalletName() {
|
||||||
final currentWalletName = getIt
|
return getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName) ?? '';
|
||||||
.get<SharedPreferences>()
|
}
|
||||||
.getString(PreferencesKey.currentWalletName) ?? '';
|
|
||||||
final currentWalletTypeRaw =
|
|
||||||
getIt.get<SharedPreferences>()
|
|
||||||
.getInt(PreferencesKey.currentWalletType) ?? 0;
|
|
||||||
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
|
||||||
|
|
||||||
return WalletUnlockLoadableViewModel(
|
WalletType currentWalletType() {
|
||||||
getIt.get<AppStore>(),
|
return deserializeFromInt(
|
||||||
getIt.get<WalletLoadingService>(),
|
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType) ?? 0);
|
||||||
walletName: args.walletName ?? currentWalletName,
|
}
|
||||||
walletType: args.walletType ?? currentWalletType);
|
|
||||||
});
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockVerifiableViewModel, WalletUnlockArguments, void>((args, _) {
|
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>(
|
||||||
final currentWalletName = getIt
|
(args, _) => WalletUnlockLoadableViewModel(
|
||||||
.get<SharedPreferences>()
|
getIt.get<AppStore>(), getIt.get<WalletLoadingService>(),
|
||||||
.getString(PreferencesKey.currentWalletName) ?? '';
|
useTotp: args.useTotp && getIt.get<Setup2FAViewModel>().useTOTP2FA,
|
||||||
final currentWalletTypeRaw =
|
walletName: args.walletName ?? currentWalletName(),
|
||||||
getIt.get<SharedPreferences>()
|
walletType: args.walletType ?? currentWalletType()));
|
||||||
.getInt(PreferencesKey.currentWalletType) ?? 0;
|
|
||||||
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
|
||||||
|
|
||||||
return WalletUnlockVerifiableViewModel(
|
getIt.registerFactoryParam<WalletUnlockVerifiableViewModel, WalletUnlockArguments, void>(
|
||||||
getIt.get<AppStore>(),
|
(args, _) => WalletUnlockVerifiableViewModel(getIt.get<AppStore>(),
|
||||||
walletName: args.walletName ?? currentWalletName,
|
useTotp: args.useTotp && getIt.get<Setup2FAViewModel>().useTOTP2FA,
|
||||||
walletType: args.walletType ?? currentWalletType);
|
walletName: currentWalletName(),
|
||||||
});
|
walletType: currentWalletType()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
||||||
|
final walletPasswordAuthViewModel = getIt.get<WalletUnlockLoadableViewModel>(param1: args);
|
||||||
return WalletUnlockPage(
|
return WalletUnlockPage(
|
||||||
getIt.get<WalletUnlockLoadableViewModel>(param1: args),
|
walletPasswordAuthViewModel: walletPasswordAuthViewModel,
|
||||||
args.callback,
|
onAuthenticationFinished: args.callback,
|
||||||
args.authPasswordHandler,
|
authPasswordHandler: (String walletName, WalletType walletType, String password) async {
|
||||||
closable: closable);
|
if (args.authPasswordHandler != null) {
|
||||||
|
// verify password matches (load wallet)
|
||||||
|
final wallet = await walletPasswordAuthViewModel.load();
|
||||||
|
// do custom password verification (eg. renaming wallet files)
|
||||||
|
await args.authPasswordHandler!(walletName, walletType, password);
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not closable -> login auth, unlocks wallet and wait for totp for authstore.allowed
|
||||||
|
if (!closable) {
|
||||||
|
return await walletPasswordAuthViewModel.unlock();
|
||||||
|
} else {
|
||||||
|
// closable -> only load to verify match
|
||||||
|
return await walletPasswordAuthViewModel.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closable: closable);
|
||||||
}, instanceName: 'wallet_unlock_loadable');
|
}, instanceName: 'wallet_unlock_loadable');
|
||||||
|
|
||||||
|
// Verifiable == simply uses the current loaded wallet and verifies the password matches - always used for current wallet auth methods after initial load
|
||||||
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
||||||
|
final walletPasswordAuthViewModel = getIt.get<WalletUnlockVerifiableViewModel>(param1: args);
|
||||||
return WalletUnlockPage(
|
return WalletUnlockPage(
|
||||||
getIt.get<WalletUnlockVerifiableViewModel>(param1: args),
|
walletPasswordAuthViewModel: walletPasswordAuthViewModel,
|
||||||
args.callback,
|
onAuthenticationFinished: args.callback,
|
||||||
args.authPasswordHandler,
|
authPasswordHandler: (_, __, ___) => walletPasswordAuthViewModel.verify(),
|
||||||
closable: closable);
|
closable: closable,
|
||||||
|
isVerifiable: true);
|
||||||
}, instanceName: 'wallet_unlock_verifiable');
|
}, instanceName: 'wallet_unlock_verifiable');
|
||||||
|
|
||||||
getIt.registerFactory<WalletUnlockPage>(
|
getIt.registerFactory<WalletUnlockPage>(
|
||||||
() => getIt.get<WalletUnlockPage>(
|
() => getIt.get<WalletUnlockPage>(
|
||||||
param1: WalletUnlockArguments(
|
param1: WalletUnlockArguments(
|
||||||
callback: (bool successful, _) {
|
useTotp: getIt.get<AppStore>().settingsStore.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
if (successful) {
|
callback: (AuthResponse auth) =>
|
||||||
final authStore = getIt.get<AuthenticationStore>();
|
auth.success ? getIt.get<AuthenticationStore>().allowed() : null),
|
||||||
authStore.allowed();
|
param2: false,
|
||||||
}}),
|
instanceName: 'wallet_unlock_loadable'),
|
||||||
param2: false,
|
|
||||||
instanceName: 'wallet_unlock_loadable'),
|
|
||||||
instanceName: 'wallet_password_login');
|
instanceName: 'wallet_password_login');
|
||||||
|
|
||||||
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Cake2FAPresetsOptions extends EnumerableItem<int> with Serializable<int> {
|
||||||
static const narrow = Cake2FAPresetsOptions(title: 'Narrow', raw: 0);
|
static const narrow = Cake2FAPresetsOptions(title: 'Narrow', raw: 0);
|
||||||
static const normal = Cake2FAPresetsOptions(title: 'Normal', raw: 1);
|
static const normal = Cake2FAPresetsOptions(title: 'Normal', raw: 1);
|
||||||
static const aggressive = Cake2FAPresetsOptions(title: 'Aggressive', raw: 2);
|
static const aggressive = Cake2FAPresetsOptions(title: 'Aggressive', raw: 2);
|
||||||
|
static const unselected = Cake2FAPresetsOptions(title: 'Unselected', raw: 3);
|
||||||
|
|
||||||
static Cake2FAPresetsOptions deserialize({required int raw}) {
|
static Cake2FAPresetsOptions deserialize({required int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
|
@ -15,6 +16,8 @@ class Cake2FAPresetsOptions extends EnumerableItem<int> with Serializable<int> {
|
||||||
return Cake2FAPresetsOptions.normal;
|
return Cake2FAPresetsOptions.normal;
|
||||||
case 2:
|
case 2:
|
||||||
return Cake2FAPresetsOptions.aggressive;
|
return Cake2FAPresetsOptions.aggressive;
|
||||||
|
case 3:
|
||||||
|
return Cake2FAPresetsOptions.unselected;
|
||||||
default:
|
default:
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Incorrect Cake 2FA Preset $raw for Cake2FAPresetOptions deserialize',
|
'Incorrect Cake 2FA Preset $raw for Cake2FAPresetOptions deserialize',
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||||
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
|
@ -289,25 +290,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
case Routes.auth:
|
case Routes.auth:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_)
|
builder: (_) => SettingsStoreBase.walletPasswordDirectInput
|
||||||
=> SettingsStoreBase.walletPasswordDirectInput
|
? getIt.get<WalletUnlockPage>(
|
||||||
? getIt.get<WalletUnlockPage>(
|
param1: settings.arguments as WalletUnlockArguments,
|
||||||
param1: WalletUnlockArguments(
|
instanceName: 'wallet_unlock_verifiable',
|
||||||
callback: settings.arguments as OnAuthenticationFinished),
|
param2: true)
|
||||||
instanceName: 'wallet_unlock_verifiable',
|
: getIt.get<AuthPage>(
|
||||||
param2: true)
|
param1: settings.arguments as OnAuthenticationFinished, param2: true));
|
||||||
: getIt.get<AuthPage>(
|
|
||||||
param1: settings.arguments as OnAuthenticationFinished,
|
|
||||||
param2: true));
|
|
||||||
|
|
||||||
case Routes.totpAuthCodePage:
|
case Routes.totpAuthCodePage:
|
||||||
final args = settings.arguments as TotpAuthArgumentsModel;
|
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_) => getIt.get<TotpAuthCodePage>(
|
builder: (_) =>
|
||||||
param1: args,
|
getIt.get<TotpAuthCodePage>(param1: settings.arguments as TotpAuthArgumentsModel));
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
case Routes.walletUnlockLoadable:
|
case Routes.walletUnlockLoadable:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
|
@ -321,19 +316,16 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
case Routes.unlock:
|
case Routes.unlock:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
builder: (_)
|
builder: (_) => SettingsStoreBase.walletPasswordDirectInput
|
||||||
=> SettingsStoreBase.walletPasswordDirectInput
|
? WillPopScope(
|
||||||
? WillPopScope(
|
child: getIt.get<WalletUnlockPage>(
|
||||||
child: getIt.get<WalletUnlockPage>(
|
param1: settings.arguments as WalletUnlockArguments,
|
||||||
param1: WalletUnlockArguments(
|
|
||||||
callback: settings.arguments as OnAuthenticationFinished),
|
|
||||||
param2: false,
|
param2: false,
|
||||||
instanceName: 'wallet_unlock_verifiable'),
|
instanceName: 'wallet_unlock_verifiable'),
|
||||||
onWillPop: () async => false)
|
onWillPop: () async => false)
|
||||||
: WillPopScope(
|
: WillPopScope(
|
||||||
child: getIt.get<AuthPage>(
|
child: getIt.get<AuthPage>(
|
||||||
param1: settings.arguments as OnAuthenticationFinished,
|
param1: settings.arguments as OnAuthenticationFinished, param2: false),
|
||||||
param2: false),
|
|
||||||
onWillPop: () async => false));
|
onWillPop: () async => false));
|
||||||
|
|
||||||
case Routes.connectionSync:
|
case Routes.connectionSync:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:another_flushbar/flushbar.dart';
|
import 'package:another_flushbar/flushbar.dart';
|
||||||
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -10,8 +11,6 @@ import 'package:cake_wallet/src/screens/pin_code/pin_code.dart';
|
||||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
|
||||||
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
|
|
||||||
|
|
||||||
abstract class AuthPageState<T extends StatefulWidget> extends State<T> {
|
abstract class AuthPageState<T extends StatefulWidget> extends State<T> {
|
||||||
void changeProcessText(String text);
|
void changeProcessText(String text);
|
||||||
void hideProgressText();
|
void hideProgressText();
|
||||||
|
@ -19,9 +18,7 @@ abstract class AuthPageState<T extends StatefulWidget> extends State<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthPage extends StatefulWidget {
|
class AuthPage extends StatefulWidget {
|
||||||
AuthPage(this.authViewModel,
|
AuthPage(this.authViewModel, {required this.onAuthenticationFinished, this.closable = true});
|
||||||
{required this.onAuthenticationFinished,
|
|
||||||
this.closable = true});
|
|
||||||
|
|
||||||
final AuthViewModel authViewModel;
|
final AuthViewModel authViewModel;
|
||||||
final OnAuthenticationFinished onAuthenticationFinished;
|
final OnAuthenticationFinished onAuthenticationFinished;
|
||||||
|
@ -34,19 +31,17 @@ class AuthPage extends StatefulWidget {
|
||||||
class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||||
final _key = GlobalKey<ScaffoldState>();
|
final _key = GlobalKey<ScaffoldState>();
|
||||||
final _pinCodeKey = GlobalKey<PinCodeState>();
|
final _pinCodeKey = GlobalKey<PinCodeState>();
|
||||||
final _backArrowImageDarkTheme =
|
final _backArrowImageDarkTheme = Image.asset('assets/images/close_button.png');
|
||||||
Image.asset('assets/images/close_button.png');
|
|
||||||
ReactionDisposer? _reaction;
|
ReactionDisposer? _reaction;
|
||||||
Flushbar<void>? _authBar;
|
Flushbar<void>? _authBar;
|
||||||
Flushbar<void>? _progressBar;
|
Flushbar<void>? _progressBar;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_reaction ??=
|
_reaction ??= reaction((_) => widget.authViewModel.state, (ExecutionState state) {
|
||||||
reaction((_) => widget.authViewModel.state, (ExecutionState state) {
|
|
||||||
if (state is ExecutedSuccessfullyState) {
|
if (state is ExecutedSuccessfullyState) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
widget.onAuthenticationFinished(true, this);
|
widget.onAuthenticationFinished(AuthResponse(success: true, close: close));
|
||||||
});
|
});
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
@ -54,9 +49,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||||
if (state is IsExecutingState) {
|
if (state is IsExecutingState) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// null duration to make it indefinite until its disposed
|
// null duration to make it indefinite until its disposed
|
||||||
_authBar =
|
_authBar = createBar<void>(S.of(context).authentication, duration: null)..show(context);
|
||||||
createBar<void>(S.of(context).authentication, duration: null)
|
|
||||||
..show(context);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,10 +57,10 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
_pinCodeKey.currentState?.clear();
|
_pinCodeKey.currentState?.clear();
|
||||||
dismissFlushBar(_authBar);
|
dismissFlushBar(_authBar);
|
||||||
showBar<void>(
|
showBar<void>(context, S.of(context).failed_authentication(state.error));
|
||||||
context, S.of(context).failed_authentication(state.error));
|
|
||||||
|
|
||||||
widget.onAuthenticationFinished(false, this);
|
widget.onAuthenticationFinished(
|
||||||
|
AuthResponse(error: S.of(context).failed_authentication(state.error), close: close));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +68,10 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
_pinCodeKey.currentState?.clear();
|
_pinCodeKey.currentState?.clear();
|
||||||
dismissFlushBar(_authBar);
|
dismissFlushBar(_authBar);
|
||||||
showBar<void>(
|
showBar<void>(context, S.of(context).failed_authentication(state.error));
|
||||||
context, S.of(context).failed_authentication(state.error));
|
|
||||||
|
|
||||||
widget.onAuthenticationFinished(false, this);
|
widget.onAuthenticationFinished(
|
||||||
|
AuthResponse(error: S.of(context).failed_authentication(state.error), close: close));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -102,8 +95,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||||
@override
|
@override
|
||||||
void changeProcessText(String text) {
|
void changeProcessText(String text) {
|
||||||
dismissFlushBar(_authBar);
|
dismissFlushBar(_authBar);
|
||||||
_progressBar = createBar<void>(text, duration: null)
|
_progressBar = createBar<void>(text, duration: null)..show(_key.currentContext!);
|
||||||
..show(_key.currentContext!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -144,15 +136,15 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||||
width: 37,
|
width: 37,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => Navigator.of(context).pop(),
|
onTap: () => Navigator.of(context).pop(),
|
||||||
child: _backArrowImageDarkTheme,
|
child: _backArrowImageDarkTheme,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
: Container(),
|
: Container(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
border: null),
|
border: null),
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin),
|
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin), (_) => null,
|
||||||
(_) => null, widget.authViewModel.pinLength, false, _pinCodeKey));
|
widget.authViewModel.pinLength, false, _pinCodeKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
void dismissFlushBar(Flushbar<dynamic>? bar) {
|
void dismissFlushBar(Flushbar<dynamic>? bar) {
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_navbar.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar/side_menu_item.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
@ -40,14 +41,11 @@ class DesktopSidebarWrapper extends BasePage {
|
||||||
),
|
),
|
||||||
trailing: InkWell(
|
trailing: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(Routes.unlock,
|
||||||
Routes.unlock,
|
arguments: WalletUnlockArguments(
|
||||||
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
|
useTotp: getIt.get<Setup2FAViewModel>().useTOTP2FA &&
|
||||||
if (isAuthenticatedSuccessfully) {
|
getIt.get<Setup2FAViewModel>().shouldRequireTOTP2FAForAccessingWallet,
|
||||||
auth.close();
|
callback: (auth) => auth.success ? auth.close() : null));
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Icon(Icons.lock_outline),
|
child: Icon(Icons.lock_outline),
|
||||||
),
|
),
|
||||||
|
|
|
@ -148,38 +148,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||||
if (SettingsStoreBase.walletPasswordDirectInput) {
|
await widget.walletListViewModel.authWallet(context, wallet);
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
Routes.walletUnlockLoadable,
|
|
||||||
arguments: WalletUnlockArguments(
|
|
||||||
callback: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
|
||||||
if (isAuthenticatedSuccessfully) {
|
|
||||||
auth.close();
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}, walletName: wallet.name,
|
|
||||||
walletType: wallet.type));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
widget._authService.authenticateAction(context,
|
|
||||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
|
||||||
if (!isAuthenticatedSuccessfully) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
|
||||||
await widget.walletListViewModel.loadWallet(wallet);
|
|
||||||
hideProgressText();
|
|
||||||
setState(() {});
|
|
||||||
} catch (e) {
|
|
||||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
conditionToDetermineIfToUse2FA:
|
|
||||||
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToCreateWallet() {
|
void _navigateToCreateWallet() {
|
||||||
|
@ -188,17 +157,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
context,
|
context,
|
||||||
route: Routes.newWallet,
|
route: Routes.newWallet,
|
||||||
arguments: widget.walletListViewModel.currentWalletType,
|
arguments: widget.walletListViewModel.currentWalletType,
|
||||||
conditionToDetermineIfToUse2FA: widget
|
conditionToDetermineIfToUse2FA:
|
||||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
widget._authService.authenticateAction(
|
widget._authService.authenticateAction(
|
||||||
context,
|
context,
|
||||||
route: Routes.newWalletType,
|
route: Routes.newWalletType,
|
||||||
conditionToDetermineIfToUse2FA: widget
|
conditionToDetermineIfToUse2FA:
|
||||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
@ -12,8 +11,6 @@ import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||||
import 'package:uni_links/uni_links.dart';
|
import 'package:uni_links/uni_links.dart';
|
||||||
|
|
||||||
import '../setup_2fa/setup_2fa_enter_code_page.dart';
|
|
||||||
|
|
||||||
class Root extends StatefulWidget {
|
class Root extends StatefulWidget {
|
||||||
Root({
|
Root({
|
||||||
required Key key,
|
required Key key,
|
||||||
|
@ -98,8 +95,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isInactive &&
|
if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||||
widget.authenticationStore.state == AuthenticationState.allowed) {
|
|
||||||
setState(() => _setInactive(true));
|
setState(() => _setInactive(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,17 +122,15 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
||||||
final shouldUseTotp2FAToAccessWallets = widget.appStore
|
final shouldUseTotp2FAToAccessWallets =
|
||||||
.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||||
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||||
_reset();
|
_reset();
|
||||||
auth.close(
|
auth.close(
|
||||||
route: Routes.totpAuthCodePage,
|
route: Routes.totpAuthCodePage,
|
||||||
arguments: TotpAuthArgumentsModel(
|
arguments: TotpAuthArgumentsModel(
|
||||||
onTotpAuthenticationFinished:
|
onTotpAuthenticationFinished: (totpAuth) {
|
||||||
(bool isAuthenticatedSuccessfully,
|
if (!totpAuth.success) {
|
||||||
TotpAuthCodePageState totpAuth) {
|
|
||||||
if (!isAuthenticatedSuccessfully) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_reset();
|
_reset();
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import 'package:another_flushbar/flushbar.dart';
|
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
|
||||||
import 'package:cake_wallet/view_model/auth_state.dart';
|
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
|
import 'package:cake_wallet/src/screens/setup_2fa/widgets/popup_cancellable_alert.dart';
|
||||||
|
@ -13,135 +10,49 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
|
|
||||||
import '../../../palette.dart';
|
import '../../../palette.dart';
|
||||||
import '../../../routes.dart';
|
import '../../../routes.dart';
|
||||||
|
|
||||||
typedef OnTotpAuthenticationFinished = void Function(
|
class TotpAuthCodePage extends BasePage {
|
||||||
bool, TotpAuthCodePageState);
|
TotpAuthCodePage({required this.setup2FAViewModel, required TotpAuthArgumentsModel totpArguments})
|
||||||
|
: isForSetup = totpArguments.isForSetup ?? false,
|
||||||
class TotpAuthCodePage extends StatefulWidget {
|
isClosable = totpArguments.isClosable ?? true,
|
||||||
TotpAuthCodePage(
|
showPopup = totpArguments.isForSetup ?? totpArguments.showPopup ?? false,
|
||||||
this.setup2FAViewModel, {
|
onTotpAuthenticationFinished = totpArguments.onTotpAuthenticationFinished,
|
||||||
required this.totpArguments,
|
totpController = TextEditingController() {
|
||||||
});
|
|
||||||
|
|
||||||
final Setup2FAViewModel setup2FAViewModel;
|
|
||||||
|
|
||||||
final TotpAuthArgumentsModel totpArguments;
|
|
||||||
|
|
||||||
@override
|
|
||||||
TotpAuthCodePageState createState() => TotpAuthCodePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TotpAuthCodePageState extends State<TotpAuthCodePage> {
|
|
||||||
final _key = GlobalKey<ScaffoldState>();
|
|
||||||
|
|
||||||
ReactionDisposer? _reaction;
|
|
||||||
Flushbar<void>? _authBar;
|
|
||||||
Flushbar<void>? _progressBar;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
if (widget.totpArguments.onTotpAuthenticationFinished != null) {
|
|
||||||
_reaction ??= reaction((_) => widget.setup2FAViewModel.state,
|
|
||||||
(ExecutionState state) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (state is ExecutedSuccessfullyState) {
|
|
||||||
widget.totpArguments.onTotpAuthenticationFinished!(true, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state is FailureState) {
|
|
||||||
print(state.error);
|
|
||||||
widget.totpArguments.onTotpAuthenticationFinished!(false, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state is AuthenticationBanned) {
|
|
||||||
widget.totpArguments.onTotpAuthenticationFinished!(false, this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_reaction?.reaction.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeProcessText(String text) {
|
|
||||||
dismissFlushBar(_authBar);
|
|
||||||
_progressBar = createBar<void>(text, duration: null)
|
|
||||||
..show(_key.currentContext!);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> close({String? route, dynamic arguments}) async {
|
|
||||||
if (_key.currentContext == null) {
|
|
||||||
throw Exception('Key context is null. Should be not happened');
|
|
||||||
}
|
|
||||||
await Future<void>.delayed(Duration(milliseconds: 50));
|
|
||||||
if (route != null) {
|
|
||||||
Navigator.of(_key.currentContext!)
|
|
||||||
.pushReplacementNamed(route, arguments: arguments);
|
|
||||||
} else {
|
|
||||||
Navigator.of(_key.currentContext!).pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
key: _key,
|
|
||||||
resizeToAvoidBottomInset: false,
|
|
||||||
body: TOTPEnterCode(
|
|
||||||
setup2FAViewModel: widget.setup2FAViewModel,
|
|
||||||
isForSetup: widget.totpArguments.isForSetup ?? false,
|
|
||||||
isClosable: widget.totpArguments.isClosable ?? true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dismissFlushBar(Flushbar<dynamic>? bar) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
await bar?.dismiss();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TOTPEnterCode extends BasePage {
|
|
||||||
TOTPEnterCode({
|
|
||||||
required this.setup2FAViewModel,
|
|
||||||
required this.isForSetup,
|
|
||||||
required this.isClosable,
|
|
||||||
}) : totpController = TextEditingController() {
|
|
||||||
totpController.addListener(() {
|
totpController.addListener(() {
|
||||||
setup2FAViewModel.enteredOTPCode = totpController.text;
|
setup2FAViewModel.enteredOTPCode = totpController.text;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String get title =>
|
|
||||||
isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa;
|
|
||||||
|
|
||||||
Widget? leading(BuildContext context) {
|
|
||||||
return isClosable ? super.leading(context) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final TextEditingController totpController;
|
final TextEditingController totpController;
|
||||||
final Setup2FAViewModel setup2FAViewModel;
|
final Setup2FAViewModel setup2FAViewModel;
|
||||||
final bool isForSetup;
|
final bool isForSetup;
|
||||||
final bool isClosable;
|
final bool isClosable;
|
||||||
|
final bool showPopup;
|
||||||
|
final Function(TotpResponse)? onTotpAuthenticationFinished;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? leading(BuildContext context) {
|
||||||
|
return isClosable ? super.leading(context) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
|
Future<void> close({String? route, dynamic arguments}) async {
|
||||||
|
if (route != null) {
|
||||||
|
Navigator.of(context).pushReplacementNamed(route, arguments: arguments);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
horizontal: 24,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
BaseTextFormField(
|
BaseTextFormField(
|
||||||
|
@ -167,40 +78,47 @@ class TOTPEnterCode extends BasePage {
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (context) {
|
builder: (_) {
|
||||||
return PrimaryButton(
|
return LoadingPrimaryButton(
|
||||||
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8,
|
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8 ||
|
||||||
|
setup2FAViewModel.state is IsExecutingState,
|
||||||
|
isLoading: setup2FAViewModel.state is IsExecutingState,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result = await setup2FAViewModel.totp2FAAuth(
|
final result =
|
||||||
totpController.text, isForSetup);
|
await setup2FAViewModel.totp2FAAuth(totpController.text, isForSetup);
|
||||||
final bannedState =
|
|
||||||
setup2FAViewModel.state is AuthenticationBanned;
|
|
||||||
|
|
||||||
await showPopUp<void>(
|
if (showPopup || result == false) {
|
||||||
context: context,
|
final bannedState = setup2FAViewModel.state is AuthenticationBanned;
|
||||||
builder: (BuildContext context) {
|
await showPopUp<void>(
|
||||||
return PopUpCancellableAlertDialog(
|
context: context,
|
||||||
contentText: _textDisplayedInPopupOnResult(
|
builder: (BuildContext context) {
|
||||||
result, bannedState, context),
|
return PopUpCancellableAlertDialog(
|
||||||
actionButtonText: S.of(context).ok,
|
contentText: _textDisplayedInPopupOnResult(result, bannedState, context),
|
||||||
buttonAction: () {
|
actionButtonText: S.of(context).ok,
|
||||||
result ? setup2FAViewModel.success() : null;
|
buttonAction: () {
|
||||||
if (isForSetup && result) {
|
result
|
||||||
Navigator.pop(context);
|
? onTotpAuthenticationFinished
|
||||||
// Navigator.of(context)
|
?.call(TotpResponse(success: true, close: close))
|
||||||
// .popAndPushNamed(Routes.modify2FAPage);
|
: null;
|
||||||
} else {
|
|
||||||
Navigator.of(context).pop(result);
|
if (isForSetup && result) {
|
||||||
}
|
Navigator.pushNamedAndRemoveUntil(
|
||||||
},
|
context, Routes.dashboard, (route) => false);
|
||||||
);
|
} else {
|
||||||
},
|
Navigator.of(context).pop(result);
|
||||||
);
|
}
|
||||||
if (isForSetup && result) {
|
},
|
||||||
Navigator.pushReplacementNamed(
|
);
|
||||||
context, Routes.modify2FAPage);
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showPopup == false) {
|
||||||
|
result
|
||||||
|
? onTotpAuthenticationFinished
|
||||||
|
?.call(TotpResponse(success: true, close: close))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
text: S.of(context).continue_text,
|
text: S.of(context).continue_text,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
|
@ -214,13 +132,10 @@ class TOTPEnterCode extends BasePage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _textDisplayedInPopupOnResult(
|
String _textDisplayedInPopupOnResult(bool result, bool bannedState, BuildContext context) {
|
||||||
bool result, bool bannedState, BuildContext context) {
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case true:
|
case true:
|
||||||
return isForSetup
|
return isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
|
||||||
? S.current.totp_2fa_success
|
|
||||||
: S.current.totp_verification_success;
|
|
||||||
case false:
|
case false:
|
||||||
if (bannedState) {
|
if (bannedState) {
|
||||||
final state = setup2FAViewModel.state as AuthenticationBanned;
|
final state = setup2FAViewModel.state as AuthenticationBanned;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:another_flushbar/flushbar.dart';
|
import 'package:another_flushbar/flushbar.dart';
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cake_wallet/core/wallet_name_validator.dart';
|
import 'package:cake_wallet/core/wallet_name_validator.dart';
|
||||||
import 'package:cake_wallet/palette.dart';
|
import 'package:cake_wallet/palette.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
|
@ -100,21 +100,17 @@ class WalletEditPage extends BasePage {
|
||||||
try {
|
try {
|
||||||
bool confirmed = false;
|
bool confirmed = false;
|
||||||
|
|
||||||
if (SettingsStoreBase
|
if (SettingsStoreBase.walletPasswordDirectInput) {
|
||||||
.walletPasswordDirectInput) {
|
|
||||||
await Navigator.of(context).pushNamed(
|
await Navigator.of(context).pushNamed(
|
||||||
Routes.walletUnlockLoadable,
|
Routes.walletUnlockLoadable,
|
||||||
arguments: WalletUnlockArguments(
|
arguments: WalletUnlockArguments(
|
||||||
authPasswordHandler:
|
useTotp: true,
|
||||||
(String password) async {
|
authPasswordHandler: (_, __, String password) async {
|
||||||
await walletEditViewModel
|
await walletEditViewModel.changeName(editingWallet,
|
||||||
.changeName(editingWallet,
|
password: password);
|
||||||
password: password);
|
|
||||||
},
|
},
|
||||||
callback: (bool
|
callback: (AuthResponse auth) async {
|
||||||
isAuthenticatedSuccessfully,
|
if (auth.success) {
|
||||||
AuthPageState auth) async {
|
|
||||||
if (isAuthenticatedSuccessfully) {
|
|
||||||
auth.close();
|
auth.close();
|
||||||
confirmed = true;
|
confirmed = true;
|
||||||
}
|
}
|
||||||
|
@ -122,8 +118,7 @@ class WalletEditPage extends BasePage {
|
||||||
walletName: editingWallet.name,
|
walletName: editingWallet.name,
|
||||||
walletType: editingWallet.type));
|
walletType: editingWallet.type));
|
||||||
} else {
|
} else {
|
||||||
await walletEditViewModel
|
await walletEditViewModel.changeName(editingWallet);
|
||||||
.changeName(editingWallet);
|
|
||||||
confirmed = true;
|
confirmed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,12 +145,14 @@ class WalletEditPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _removeWallet(BuildContext context) async {
|
Future<void> _removeWallet(BuildContext context) async {
|
||||||
authService.authenticateAction(context, onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
authService.authenticateAction(
|
||||||
if (!isAuthenticatedSuccessfully) {
|
context,
|
||||||
return;
|
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||||
}
|
if (!isAuthenticatedSuccessfully) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_onSuccessfulAuth(context);
|
_onSuccessfulAuth(context);
|
||||||
},
|
},
|
||||||
conditionToDetermineIfToUse2FA: false,
|
conditionToDetermineIfToUse2FA: false,
|
||||||
);
|
);
|
||||||
|
|
|
@ -252,45 +252,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||||
if (SettingsStoreBase.walletPasswordDirectInput) {
|
widget.walletListViewModel.authWallet(context, wallet);
|
||||||
Navigator.of(context).pushNamed(
|
|
||||||
Routes.walletUnlockLoadable,
|
|
||||||
arguments: WalletUnlockArguments(
|
|
||||||
callback: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
|
|
||||||
if (isAuthenticatedSuccessfully) {
|
|
||||||
auth.close();
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}, walletName: wallet.name,
|
|
||||||
walletType: wallet.type));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await widget.authService.authenticateAction(
|
|
||||||
context,
|
|
||||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
|
||||||
if (!isAuthenticatedSuccessfully) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
|
||||||
await widget.walletListViewModel.loadWallet(wallet);
|
|
||||||
await hideProgressText();
|
|
||||||
// only pop the wallets route in mobile as it will go back to dashboard page
|
|
||||||
// in desktop platforms the navigation tree is different
|
|
||||||
if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
conditionToDetermineIfToUse2FA:
|
|
||||||
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeProcessText(String text) {
|
void changeProcessText(String text) {
|
||||||
|
|
|
@ -1,17 +1,36 @@
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
|
||||||
typedef AuthPasswordHandler = Future<void> Function(String);
|
typedef AuthPasswordHandler = Future<dynamic> Function(
|
||||||
|
String walletName, WalletType walletType, String password);
|
||||||
|
|
||||||
class WalletUnlockArguments {
|
class WalletUnlockArguments {
|
||||||
WalletUnlockArguments(
|
WalletUnlockArguments(
|
||||||
{required this.callback,
|
{required this.callback,
|
||||||
|
required this.useTotp,
|
||||||
this.walletName,
|
this.walletName,
|
||||||
this.walletType,
|
this.walletType,
|
||||||
this.authPasswordHandler});
|
this.authPasswordHandler})
|
||||||
|
: state = InitialExecutionState();
|
||||||
|
|
||||||
final OnAuthenticationFinished callback;
|
final OnAuthenticationFinished callback;
|
||||||
final AuthPasswordHandler? authPasswordHandler;
|
final AuthPasswordHandler? authPasswordHandler;
|
||||||
final String? walletName;
|
final String? walletName;
|
||||||
final WalletType? walletType;
|
final WalletType? walletType;
|
||||||
|
final bool useTotp;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ExecutionState state;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void success() {
|
||||||
|
state = ExecutedSuccessfullyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void failure(e) {
|
||||||
|
state = FailureState(e.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,228 +1,174 @@
|
||||||
import 'package:another_flushbar/flushbar.dart';
|
import 'package:another_flushbar/flushbar.dart';
|
||||||
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
|
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_unlock_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_password_auth_view_model.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
|
|
||||||
|
class WalletUnlockPage extends BasePage {
|
||||||
class WalletUnlockPage extends StatefulWidget {
|
|
||||||
WalletUnlockPage(
|
WalletUnlockPage(
|
||||||
this.walletUnlockViewModel,
|
{required this.walletPasswordAuthViewModel,
|
||||||
this.onAuthenticationFinished,
|
required this.onAuthenticationFinished,
|
||||||
this.authPasswordHandler,
|
required this.authPasswordHandler,
|
||||||
{required this.closable});
|
bool closable = false,
|
||||||
|
bool isVerifiable = false})
|
||||||
|
: this.isClosable = closable,
|
||||||
|
this.isVerifiable = isVerifiable,
|
||||||
|
_passwordController = TextEditingController() {
|
||||||
|
_passwordController.text = walletPasswordAuthViewModel.password;
|
||||||
|
_passwordController
|
||||||
|
.addListener(() => walletPasswordAuthViewModel.password = _passwordController.text);
|
||||||
|
}
|
||||||
|
|
||||||
final WalletUnlockViewModel walletUnlockViewModel;
|
final WalletPasswordAuthViewModel walletPasswordAuthViewModel;
|
||||||
final OnAuthenticationFinished onAuthenticationFinished;
|
final OnAuthenticationFinished onAuthenticationFinished;
|
||||||
final AuthPasswordHandler? authPasswordHandler;
|
final AuthPasswordHandler authPasswordHandler;
|
||||||
final bool closable;
|
final bool isClosable;
|
||||||
|
final bool isVerifiable;
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => WalletUnlockPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class WalletUnlockPageState extends AuthPageState<WalletUnlockPage> {
|
|
||||||
WalletUnlockPageState()
|
|
||||||
: _passwordController = TextEditingController();
|
|
||||||
|
|
||||||
final TextEditingController _passwordController;
|
final TextEditingController _passwordController;
|
||||||
final _key = GlobalKey<ScaffoldState>();
|
|
||||||
final _backArrowImageDarkTheme =
|
|
||||||
Image.asset('assets/images/close_button.png');
|
|
||||||
ReactionDisposer? _reaction;
|
|
||||||
Flushbar<void>? _authBar;
|
|
||||||
Flushbar<void>? _progressBar;
|
|
||||||
void Function()? _passwordControllerListener;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
Widget? leading(BuildContext context) {
|
||||||
_reaction ??=
|
return isClosable ? super.leading(context) : null;
|
||||||
reaction((_) => widget.walletUnlockViewModel.state, (ExecutionState state) {
|
}
|
||||||
if (state is ExecutedSuccessfullyState) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
@override
|
||||||
widget.onAuthenticationFinished(true, this);
|
Widget body(BuildContext context) {
|
||||||
});
|
Future<void> close({String? route, arguments}) async {
|
||||||
setState(() {});
|
if (route != null) {
|
||||||
|
Navigator.of(context).pushReplacementNamed(route, arguments: arguments);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is IsExecutingState) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
// null duration to make it indefinite until its disposed
|
|
||||||
_authBar =
|
|
||||||
createBar<void>(S.of(context).authentication, duration: null)
|
|
||||||
..show(context);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state is FailureState) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
dismissFlushBar(_authBar);
|
|
||||||
showBar<void>(
|
|
||||||
context, S.of(context).failed_authentication(state.error));
|
|
||||||
|
|
||||||
widget.onAuthenticationFinished(false, this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_passwordControllerListener = () => widget.walletUnlockViewModel.setPassword(_passwordController.text);
|
|
||||||
|
|
||||||
if (_passwordControllerListener != null) {
|
|
||||||
_passwordController.addListener(_passwordControllerListener!);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_reaction?.reaction.dispose();
|
|
||||||
|
|
||||||
if (_passwordControllerListener != null) {
|
|
||||||
_passwordController.removeListener(_passwordControllerListener!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.dispose();
|
void onFailure(String error) {
|
||||||
}
|
walletPasswordAuthViewModel.state = FailureState(error);
|
||||||
|
showBar<void>(context, S.of(context).failed_authentication(error),
|
||||||
@override
|
duration: Duration(seconds: 3));
|
||||||
void changeProcessText(String text) {
|
onAuthenticationFinished(
|
||||||
dismissFlushBar(_authBar);
|
AuthResponse(error: S.of(context).failed_authentication(error), close: close));
|
||||||
_progressBar = createBar<void>(text, duration: null)
|
|
||||||
..show(_key.currentContext!);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void hideProgressText() {
|
|
||||||
dismissFlushBar(_progressBar);
|
|
||||||
_progressBar = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dismissFlushBar(Flushbar<dynamic>? bar) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
await bar?.dismiss();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close({String? route, arguments}) async {
|
|
||||||
if (_key.currentContext == null) {
|
|
||||||
throw Exception('Key context is null. Should be not happened');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// not the best scenario, but WidgetsBinding is not behaving correctly on Android
|
return Center(
|
||||||
await Future<void>.delayed(Duration(milliseconds: 50));
|
child: ConstrainedBox(
|
||||||
await _authBar?.dismiss();
|
|
||||||
await Future<void>.delayed(Duration(milliseconds: 50));
|
|
||||||
await _progressBar?.dismiss();
|
|
||||||
await Future<void>.delayed(Duration(milliseconds: 50));
|
|
||||||
if (route != null) {
|
|
||||||
Navigator.of(_key.currentContext!).pushReplacementNamed(route, arguments: arguments);
|
|
||||||
} else {
|
|
||||||
Navigator.of(_key.currentContext!).pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
key: _key,
|
|
||||||
appBar: CupertinoNavigationBar(
|
|
||||||
leading: widget.closable
|
|
||||||
? Container(
|
|
||||||
padding: EdgeInsets.only(top: 10),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 37,
|
|
||||||
width: 37,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => Navigator.of(context).pop(),
|
|
||||||
child: _backArrowImageDarkTheme,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
: Container(),
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
|
||||||
border: null),
|
|
||||||
resizeToAvoidBottomInset: false,
|
|
||||||
body: Center(
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint),
|
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Column(
|
Expanded(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
Text(widget.walletUnlockViewModel.walletName,
|
children: [
|
||||||
textAlign: TextAlign.center,
|
Text(walletPasswordAuthViewModel.walletName,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 24,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: 24,
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
fontWeight: FontWeight.w500,
|
||||||
SizedBox(height: 24),
|
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||||
Form(
|
SizedBox(height: 24),
|
||||||
child: TextFormField(
|
Form(
|
||||||
onChanged: (value) => null,
|
child: TextFormField(
|
||||||
controller: _passwordController,
|
onChanged: (value) => null,
|
||||||
textAlign: TextAlign.center,
|
controller: _passwordController,
|
||||||
obscureText: true,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
obscureText: true,
|
||||||
fontSize: 20.0,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 20.0,
|
||||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
fontWeight: FontWeight.w600,
|
||||||
decoration: InputDecoration(
|
color:
|
||||||
hintStyle: TextStyle(
|
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||||
fontSize: 18.0,
|
decoration: InputDecoration(
|
||||||
fontWeight: FontWeight.w500,
|
hintStyle: TextStyle(
|
||||||
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor),
|
fontSize: 18.0,
|
||||||
hintText: S.of(context).enter_wallet_password,
|
fontWeight: FontWeight.w500,
|
||||||
focusedBorder: UnderlineInputBorder(
|
color: Theme.of(context)
|
||||||
borderSide: BorderSide(
|
.extension<NewWalletTheme>()!
|
||||||
color: Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
|
.hintTextColor),
|
||||||
width: 1.0)),
|
hintText: S.of(context).enter_wallet_password,
|
||||||
enabledBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
|
color: Theme.of(context)
|
||||||
width: 1.0),
|
.extension<NewWalletTheme>()!
|
||||||
)
|
.underlineColor,
|
||||||
)))])),
|
width: 1.0)),
|
||||||
Padding(
|
enabledBorder: UnderlineInputBorder(
|
||||||
padding: EdgeInsets.only(bottom: 24),
|
borderSide: BorderSide(
|
||||||
child: Observer(
|
color: Theme.of(context)
|
||||||
builder: (_) =>
|
.extension<NewWalletTheme>()!
|
||||||
LoadingPrimaryButton(
|
.underlineColor,
|
||||||
onPressed: () async {
|
width: 1.0)))))
|
||||||
if (widget.authPasswordHandler != null) {
|
])),
|
||||||
try {
|
Observer(builder: (_) {
|
||||||
await widget.authPasswordHandler!(widget
|
return Padding(
|
||||||
.walletUnlockViewModel.password);
|
padding: EdgeInsets.only(bottom: 24),
|
||||||
widget.walletUnlockViewModel.success();
|
child: LoadingPrimaryButton(
|
||||||
} catch (e) {
|
onPressed: () async {
|
||||||
widget.walletUnlockViewModel.failure(e);
|
Flushbar<void>? loadingBar;
|
||||||
}
|
if (!isVerifiable)
|
||||||
return;
|
loadingBar = createBar<void>(S.of(context).loading_your_wallet)
|
||||||
}
|
..show(context);
|
||||||
|
|
||||||
widget.walletUnlockViewModel.unlock();
|
walletPasswordAuthViewModel.state = IsExecutingState();
|
||||||
},
|
dynamic payload;
|
||||||
text: S.of(context).unlock,
|
|
||||||
color: Colors.green,
|
try {
|
||||||
textColor: Colors.white,
|
payload = await authPasswordHandler(
|
||||||
isLoading: widget.walletUnlockViewModel.state is IsExecutingState,
|
walletPasswordAuthViewModel.walletName,
|
||||||
isDisabled: widget.walletUnlockViewModel.state is IsExecutingState)))
|
walletPasswordAuthViewModel.walletType,
|
||||||
]))
|
walletPasswordAuthViewModel.password);
|
||||||
));
|
} catch (err, stack) {
|
||||||
|
loadingBar?.dismiss();
|
||||||
|
onFailure(S.of(context).invalid_password);
|
||||||
|
ExceptionHandler.onError(
|
||||||
|
FlutterErrorDetails(exception: err, stack: stack));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingBar?.dismiss();
|
||||||
|
|
||||||
|
if (!walletPasswordAuthViewModel.useTotp) {
|
||||||
|
onAuthenticationFinished(
|
||||||
|
AuthResponse(success: true, close: close, payload: payload));
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushReplacementNamed(
|
||||||
|
Routes.totpAuthCodePage,
|
||||||
|
arguments: TotpAuthArgumentsModel(
|
||||||
|
isForSetup: false,
|
||||||
|
isClosable: isClosable,
|
||||||
|
onTotpAuthenticationFinished: (totpAuth) async {
|
||||||
|
if (!totpAuth.success) {
|
||||||
|
onFailure(totpAuth.error!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAuthenticationFinished(AuthResponse(
|
||||||
|
success: true, close: totpAuth.close, payload: payload));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text: S.of(context).unlock,
|
||||||
|
color: Colors.green,
|
||||||
|
textColor: Colors.white,
|
||||||
|
isLoading: walletPasswordAuthViewModel.state is IsExecutingState,
|
||||||
|
isDisabled: walletPasswordAuthViewModel.state is IsExecutingState ||
|
||||||
|
walletPasswordAuthViewModel.password.length == 0));
|
||||||
|
})
|
||||||
|
])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -532,7 +532,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false;
|
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false;
|
||||||
final selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
final selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||||
Cake2FAPresetsOptions.normal.raw);
|
Cake2FAPresetsOptions.unselected.raw);
|
||||||
final shouldRequireTOTP2FAForAccessingWallet =
|
final shouldRequireTOTP2FAForAccessingWallet =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
||||||
final shouldRequireTOTP2FAForSendsToContact =
|
final shouldRequireTOTP2FAForSendsToContact =
|
||||||
|
@ -736,7 +736,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
allowBiometricalAuthentication;
|
allowBiometricalAuthentication;
|
||||||
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||||
Cake2FAPresetsOptions.normal.raw);
|
Cake2FAPresetsOptions.unselected.raw);
|
||||||
shouldRequireTOTP2FAForAccessingWallet =
|
shouldRequireTOTP2FAForAccessingWallet =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
||||||
shouldRequireTOTP2FAForSendsToContact =
|
shouldRequireTOTP2FAForSendsToContact =
|
||||||
|
@ -762,7 +762,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
shouldShowMarketPlaceInDashboard;
|
shouldShowMarketPlaceInDashboard;
|
||||||
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||||
Cake2FAPresetsOptions.narrow.raw);
|
Cake2FAPresetsOptions.unselected.raw);
|
||||||
exchangeStatus = ExchangeApiMode.deserialize(
|
exchangeStatus = ExchangeApiMode.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
||||||
ExchangeApiMode.enabled.raw);
|
ExchangeApiMode.enabled.raw);
|
||||||
|
|
|
@ -28,7 +28,9 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
||||||
state = InitialExecutionState() {
|
state = InitialExecutionState() {
|
||||||
_getRandomBase32SecretKey();
|
_getRandomBase32SecretKey();
|
||||||
selectCakePreset(selectedCake2FAPreset);
|
if (selectedCake2FAPreset == Cake2FAPresetsOptions.unselected) {
|
||||||
|
selectCakePreset(Cake2FAPresetsOptions.normal);
|
||||||
|
}
|
||||||
reaction((_) => state, _saveLastAuthTime);
|
reaction((_) => state, _saveLastAuthTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +94,7 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void setUseTOTP2FA(bool value) {
|
void setUseTOTP2FA(bool value) {
|
||||||
_settingsStore.useTOTP2FA = value;
|
_settingsStore.useTOTP2FA = value;
|
||||||
|
if (!value) _settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.unselected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -134,7 +137,7 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<bool> totp2FAAuth(String otpText, bool isForSetup) async {
|
Future<bool> totp2FAAuth(String otpText, bool isForSetup) async {
|
||||||
state = InitialExecutionState();
|
state = IsExecutingState();
|
||||||
_failureCounter = _settingsStore.numberOfFailedTokenTrials;
|
_failureCounter = _settingsStore.numberOfFailedTokenTrials;
|
||||||
final _banDuration = banDuration();
|
final _banDuration = banDuration();
|
||||||
|
|
||||||
|
@ -154,11 +157,11 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
isForSetup ? setUseTOTP2FA(result) : null;
|
isForSetup ? setUseTOTP2FA(result) : null;
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
state = ExecutedSuccessfullyState();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
||||||
adjustTokenTrialNumber(value);
|
adjustTokenTrialNumber(value);
|
||||||
print(value);
|
|
||||||
if (_failureCounter >= maxFailedTrials) {
|
if (_failureCounter >= maxFailedTrials) {
|
||||||
final banDuration = await ban();
|
final banDuration = await ban();
|
||||||
state = AuthenticationBanned(
|
state = AuthenticationBanned(
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
|
import 'package:cake_wallet/core/authentication_request_data.dart';
|
||||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
|
@ -7,6 +12,8 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/wallet_types.g.dart';
|
import 'package:cake_wallet/wallet_types.g.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
part 'wallet_list_view_model.g.dart';
|
part 'wallet_list_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -43,8 +50,7 @@ abstract class WalletListViewModelBase with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> loadWallet(WalletListItem walletItem) async {
|
Future<void> loadWallet(WalletListItem walletItem) async {
|
||||||
final wallet =
|
final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name);
|
||||||
await _walletLoadingService.load(walletItem.type, walletItem.name);
|
|
||||||
_appStore.changeCurrentWallet(wallet);
|
_appStore.changeCurrentWallet(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,15 +63,53 @@ abstract class WalletListViewModelBase with Store {
|
||||||
name: info.name,
|
name: info.name,
|
||||||
type: info.type,
|
type: info.type,
|
||||||
key: info.key,
|
key: info.key,
|
||||||
isCurrent: info.name == _appStore.wallet?.name &&
|
isCurrent: info.name == _appStore.wallet!.name && info.type == _appStore.wallet!.type,
|
||||||
info.type == _appStore.wallet?.type,
|
|
||||||
isEnabled: availableWalletTypes.contains(info.type),
|
isEnabled: availableWalletTypes.contains(info.type),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkIfAuthRequired() {
|
@action
|
||||||
return _authService.requireAuth();
|
Future<void> authWallet(BuildContext context, WalletListItem wallet) async {
|
||||||
|
void onAuthAndTotpSuccess(bool isAuthenticatedSuccessfully, {AuthPageState? auth}) async {
|
||||||
|
if (isAuthenticatedSuccessfully) {
|
||||||
|
auth != null ? auth.close() : null;
|
||||||
|
await loadWallet(wallet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop/password auth -> walletUnlockLoadable Route with wallet values as params
|
||||||
|
if (SettingsStoreBase.walletPasswordDirectInput) {
|
||||||
|
_authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
// Desktop: every wallet file has a different password,
|
||||||
|
// so, when switching, always require auth
|
||||||
|
alwaysRequireAuth: true,
|
||||||
|
authRoute: Routes.walletUnlockLoadable,
|
||||||
|
authArguments: WalletUnlockArguments(
|
||||||
|
useTotp: shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
callback: (AuthResponse auth) async {
|
||||||
|
if (auth.success) {
|
||||||
|
auth.close();
|
||||||
|
_appStore.changeCurrentWallet(auth.payload as WalletBase);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
walletName: wallet.name,
|
||||||
|
walletType: wallet.type),
|
||||||
|
onAuthSuccess: onAuthAndTotpSuccess,
|
||||||
|
conditionToDetermineIfToUse2FA: shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile/PIN auth -> regular Auth Route
|
||||||
|
_authService.authenticateAction(context,
|
||||||
|
onAuthSuccess: onAuthAndTotpSuccess,
|
||||||
|
conditionToDetermineIfToUse2FA: shouldRequireTOTP2FAForAccessingWallet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
lib/view_model/wallet_password_auth_view_model.dart
Normal file
40
lib/view_model/wallet_password_auth_view_model.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'wallet_password_auth_view_model.g.dart';
|
||||||
|
|
||||||
|
class WalletPasswordAuthViewModel = WalletPasswordAuthViewModelBase
|
||||||
|
with _$WalletPasswordAuthViewModel;
|
||||||
|
|
||||||
|
abstract class WalletPasswordAuthViewModelBase with Store {
|
||||||
|
WalletPasswordAuthViewModelBase(
|
||||||
|
{required this.useTotp, required this.walletName, required this.walletType})
|
||||||
|
: password = '',
|
||||||
|
state = InitialExecutionState();
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String walletName;
|
||||||
|
@observable
|
||||||
|
WalletType walletType;
|
||||||
|
@observable
|
||||||
|
String password;
|
||||||
|
@observable
|
||||||
|
bool useTotp;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setPassword(String password) => this.password = password;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ExecutionState state;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void success() {
|
||||||
|
state = ExecutedSuccessfullyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void failure(e) {
|
||||||
|
state = FailureState(e.toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,65 +1,38 @@
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_unlock_view_model.dart';
|
import 'package:cake_wallet/view_model/wallet_password_auth_view_model.dart';
|
||||||
|
|
||||||
part 'wallet_unlock_loadable_view_model.g.dart';
|
part 'wallet_unlock_loadable_view_model.g.dart';
|
||||||
|
|
||||||
class WalletUnlockLoadableViewModel = WalletUnlockLoadableViewModelBase
|
class WalletUnlockLoadableViewModel = WalletUnlockLoadableViewModelBase
|
||||||
with _$WalletUnlockLoadableViewModel;
|
with _$WalletUnlockLoadableViewModel;
|
||||||
|
|
||||||
abstract class WalletUnlockLoadableViewModelBase extends WalletUnlockViewModel
|
abstract class WalletUnlockLoadableViewModelBase extends WalletPasswordAuthViewModel with Store {
|
||||||
with Store {
|
|
||||||
WalletUnlockLoadableViewModelBase(this._appStore, this._walletLoadingService,
|
WalletUnlockLoadableViewModelBase(this._appStore, this._walletLoadingService,
|
||||||
{required this.walletName, required this.walletType})
|
{required this.useTotp, required this.walletName, required this.walletType})
|
||||||
: password = '',
|
: super(useTotp: useTotp, walletName: walletName, walletType: walletType);
|
||||||
state = InitialExecutionState();
|
|
||||||
|
|
||||||
final String walletName;
|
final String walletName;
|
||||||
|
|
||||||
final WalletType walletType;
|
final WalletType walletType;
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
String password;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
ExecutionState state;
|
|
||||||
|
|
||||||
final WalletLoadingService _walletLoadingService;
|
final WalletLoadingService _walletLoadingService;
|
||||||
|
|
||||||
final AppStore _appStore;
|
final AppStore _appStore;
|
||||||
|
|
||||||
@override
|
@observable
|
||||||
@action
|
bool useTotp;
|
||||||
void setPassword(String password) => this.password = password;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@action
|
@action
|
||||||
Future<void> unlock() async {
|
Future<dynamic> load() async {
|
||||||
try {
|
final wallet = await _walletLoadingService.load(walletType, walletName, password: password);
|
||||||
state = InitialExecutionState();
|
return wallet;
|
||||||
final wallet = await _walletLoadingService.load(walletType, walletName,
|
|
||||||
password: password);
|
|
||||||
_appStore.changeCurrentWallet(wallet);
|
|
||||||
success();
|
|
||||||
} catch (e) {
|
|
||||||
failure(e.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
@action
|
@action
|
||||||
void success() {
|
Future<dynamic> unlock() async {
|
||||||
state = ExecutedSuccessfullyState();
|
final wallet = await load();
|
||||||
}
|
_appStore.changeCurrentWallet(wallet as WalletBase);
|
||||||
|
return wallet;
|
||||||
@override
|
|
||||||
@action
|
|
||||||
void failure(e) {
|
|
||||||
state = FailureState(e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,60 +2,28 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/view_model/wallet_password_auth_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_unlock_view_model.dart';
|
|
||||||
|
|
||||||
part 'wallet_unlock_verifiable_view_model.g.dart';
|
part 'wallet_unlock_verifiable_view_model.g.dart';
|
||||||
|
|
||||||
class WalletUnlockVerifiableViewModel = WalletUnlockVerifiableViewModelBase
|
class WalletUnlockVerifiableViewModel = WalletUnlockVerifiableViewModelBase
|
||||||
with _$WalletUnlockVerifiableViewModel;
|
with _$WalletUnlockVerifiableViewModel;
|
||||||
|
|
||||||
abstract class WalletUnlockVerifiableViewModelBase extends WalletUnlockViewModel
|
abstract class WalletUnlockVerifiableViewModelBase extends WalletPasswordAuthViewModel with Store {
|
||||||
with Store {
|
WalletUnlockVerifiableViewModelBase(this._appStore,
|
||||||
WalletUnlockVerifiableViewModelBase(this.appStore,
|
{required this.useTotp, required this.walletName, required this.walletType})
|
||||||
{required this.walletName, required this.walletType})
|
: super(useTotp: useTotp, walletName: walletName, walletType: walletType);
|
||||||
: password = '',
|
|
||||||
state = InitialExecutionState();
|
|
||||||
|
|
||||||
final String walletName;
|
final String walletName;
|
||||||
|
|
||||||
final WalletType walletType;
|
final WalletType walletType;
|
||||||
|
final AppStore _appStore;
|
||||||
|
|
||||||
final AppStore appStore;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
@observable
|
||||||
String password;
|
bool useTotp;
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
ExecutionState state;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@action
|
@action
|
||||||
void setPassword(String password) => this.password = password;
|
Future<void> verify() async {
|
||||||
|
final valid = _appStore.wallet!.password == password;
|
||||||
@override
|
if (!valid) throw Exception('${S.current.invalid_password}');
|
||||||
@action
|
|
||||||
Future<void> unlock() async {
|
|
||||||
try {
|
|
||||||
state = appStore.wallet!.password == password
|
|
||||||
? ExecutedSuccessfullyState()
|
|
||||||
: FailureState(S.current.invalid_password);
|
|
||||||
} catch (e) {
|
|
||||||
failure('${S.current.invalid_password}\n${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@action
|
|
||||||
void success() {
|
|
||||||
state = ExecutedSuccessfullyState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@action
|
|
||||||
void failure(e) {
|
|
||||||
state = FailureState(e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
|
||||||
|
|
||||||
abstract class WalletUnlockViewModel {
|
|
||||||
String get walletName;
|
|
||||||
String get password;
|
|
||||||
void setPassword(String password);
|
|
||||||
ExecutionState get state;
|
|
||||||
Future<void> unlock();
|
|
||||||
void success();
|
|
||||||
void failure(dynamic e);
|
|
||||||
}
|
|
Loading…
Reference in a new issue