mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +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/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:mobx/mobx.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/store/settings_store.dart';
|
||||
|
||||
import '../src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
|
||||
class AuthService with Store {
|
||||
AuthService({
|
||||
required this.secureStorage,
|
||||
|
@ -86,62 +84,55 @@ class AuthService with Store {
|
|||
|
||||
Future<void> authenticateAction(BuildContext context,
|
||||
{Function(bool)? onAuthSuccess,
|
||||
String? authRoute,
|
||||
Object? authArguments,
|
||||
String? route,
|
||||
Object? arguments,
|
||||
bool? alwaysRequireAuth,
|
||||
required bool conditionToDetermineIfToUse2FA}) async {
|
||||
assert(route != null || onAuthSuccess != null,
|
||||
'Either route or onAuthSuccess param must be passed.');
|
||||
|
||||
if (!conditionToDetermineIfToUse2FA) {
|
||||
if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) {
|
||||
if (alwaysRequireAuth != true &&
|
||||
!requireAuth() &&
|
||||
!_alwaysAuthenticateRoutes.contains(route)) {
|
||||
if (onAuthSuccess != null) {
|
||||
onAuthSuccess(true);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(
|
||||
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(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 {
|
||||
final bool? isForSetup;
|
||||
final bool? isClosable;
|
||||
final bool? showPopup;
|
||||
final OnTotpAuthenticationFinished? onTotpAuthenticationFinished;
|
||||
|
||||
TotpAuthArgumentsModel({
|
||||
this.isForSetup,
|
||||
this.isClosable,
|
||||
this.showPopup,
|
||||
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/core/authentication_request_data.dart';
|
||||
import 'package:cake_wallet/core/yat_service.dart';
|
||||
import 'package:cake_wallet/entities/parse_address_from_domain.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_invoice_info.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/exchange_api_mode.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/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_wallet_selection_dropdown.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/widgets/transactions_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/settings/display_settings_page.dart';
|
||||
|
@ -299,8 +296,7 @@ Future setup({
|
|||
getIt.registerSingleton<ExchangeTemplateStore>(
|
||||
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
||||
getIt.registerSingleton<YatStore>(
|
||||
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())
|
||||
..init());
|
||||
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
|
||||
getIt.registerSingleton<AnonpayTransactionsStore>(
|
||||
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
|
||||
|
||||
|
@ -382,8 +378,8 @@ Future setup({
|
|||
getIt.registerFactory<AuthViewModel>(() => AuthViewModel(getIt.get<AuthService>(),
|
||||
getIt.get<SharedPreferences>(), getIt.get<SettingsStore>(), BiometricAuth()));
|
||||
|
||||
getIt.registerFactoryParam<AuthPage, void Function(bool, AuthPageState), bool>(
|
||||
(onAuthFinished, closable) => AuthPage(getIt.get<AuthViewModel>(),
|
||||
getIt.registerFactoryParam<AuthPage, OnAuthenticationFinished, bool>((onAuthFinished, closable) =>
|
||||
AuthPage(getIt.get<AuthViewModel>(),
|
||||
onAuthenticationFinished: onAuthFinished, closable: closable));
|
||||
|
||||
getIt.registerFactory<Setup2FAViewModel>(
|
||||
|
@ -395,16 +391,12 @@ Future setup({
|
|||
);
|
||||
|
||||
getIt.registerFactoryParam<TotpAuthCodePage, TotpAuthArgumentsModel, void>(
|
||||
(totpAuthPageArguments, _) => TotpAuthCodePage(
|
||||
getIt.get<Setup2FAViewModel>(),
|
||||
totpArguments: totpAuthPageArguments,
|
||||
),
|
||||
);
|
||||
(totpAuthPageArguments, _) => TotpAuthCodePage(
|
||||
setup2FAViewModel: getIt.get<Setup2FAViewModel>(), totpArguments: totpAuthPageArguments));
|
||||
|
||||
getIt.registerFactory<AuthPage>(() {
|
||||
return AuthPage(getIt.get<AuthViewModel>(),
|
||||
onAuthenticationFinished: (isAuthenticated, AuthPageState authPageState) {
|
||||
if (!isAuthenticated) {
|
||||
return AuthPage(getIt.get<AuthViewModel>(), onAuthenticationFinished: (auth) {
|
||||
if (!auth.success) {
|
||||
return;
|
||||
} else {
|
||||
final authStore = getIt.get<AuthenticationStore>();
|
||||
|
@ -413,14 +405,13 @@ Future setup({
|
|||
final shouldUseTotp2FAToAccessWallets =
|
||||
appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||
authPageState.close(
|
||||
auth.close(
|
||||
route: Routes.totpAuthCodePage,
|
||||
arguments: TotpAuthArgumentsModel(
|
||||
isForSetup: false,
|
||||
isClosable: false,
|
||||
onTotpAuthenticationFinished: (bool isAuthenticatedSuccessfully,
|
||||
TotpAuthCodePageState totpAuthPageState) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
onTotpAuthenticationFinished: (totpAuth) async {
|
||||
if (!totpAuth.success) {
|
||||
return;
|
||||
}
|
||||
if (appStore.wallet != null) {
|
||||
|
@ -428,12 +419,6 @@ Future setup({
|
|||
return;
|
||||
}
|
||||
|
||||
totpAuthPageState.changeProcessText('Loading the wallet');
|
||||
|
||||
if (loginError != null) {
|
||||
totpAuthPageState.changeProcessText('ERROR: ${loginError.toString()}');
|
||||
}
|
||||
|
||||
ReactionDisposer? _reaction;
|
||||
_reaction = reaction((_) => appStore.wallet, (Object? _) {
|
||||
_reaction?.reaction.dispose();
|
||||
|
@ -448,12 +433,6 @@ Future setup({
|
|||
return;
|
||||
}
|
||||
|
||||
authPageState.changeProcessText('Loading the wallet');
|
||||
|
||||
if (loginError != null) {
|
||||
authPageState.changeProcessText('ERROR: ${loginError.toString()}');
|
||||
}
|
||||
|
||||
ReactionDisposer? _reaction;
|
||||
_reaction = reaction((_) => appStore.wallet, (Object? _) {
|
||||
_reaction?.reaction.dispose();
|
||||
|
@ -760,17 +739,14 @@ Future setup({
|
|||
case WalletType.monero:
|
||||
return monero!.createMoneroWalletService(_walletInfoSource);
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.createBitcoinWalletService(
|
||||
_walletInfoSource, _unspentCoinsInfoSource!,
|
||||
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!,
|
||||
SettingsStoreBase.walletPasswordDirectInput);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createLitecoinWalletService(
|
||||
_walletInfoSource, _unspentCoinsInfoSource!,
|
||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!,
|
||||
SettingsStoreBase.walletPasswordDirectInput);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumWalletService(
|
||||
_walletInfoSource,
|
||||
SettingsStoreBase.walletPasswordDirectInput);
|
||||
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||
default:
|
||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||
}
|
||||
|
@ -825,19 +801,16 @@ Future setup({
|
|||
trades: _tradesSource,
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactory(() => BackupService(
|
||||
getIt.get<SecureStorage>(),
|
||||
_walletInfoSource,
|
||||
getIt.get<KeyService>(),
|
||||
getIt.get<SharedPreferences>()));
|
||||
getIt.registerFactory(() => BackupService(getIt.get<SecureStorage>(), _walletInfoSource,
|
||||
getIt.get<KeyService>(), getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory(() => BackupViewModel(
|
||||
getIt.get<SecureStorage>(), getIt.get<SecretStore>(), getIt.get<BackupService>()));
|
||||
|
||||
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
|
||||
getIt.registerFactory(
|
||||
() => EditBackupPasswordViewModel(getIt.get<SecureStorage>(), getIt.get<SecretStore>()));
|
||||
|
||||
getIt.registerFactory(() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
|
||||
|
||||
|
@ -1063,63 +1036,73 @@ Future setup({
|
|||
getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
|
||||
(type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>((args, _) {
|
||||
final currentWalletName = getIt
|
||||
.get<SharedPreferences>()
|
||||
.getString(PreferencesKey.currentWalletName) ?? '';
|
||||
final currentWalletTypeRaw =
|
||||
getIt.get<SharedPreferences>()
|
||||
.getInt(PreferencesKey.currentWalletType) ?? 0;
|
||||
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
||||
String currentWalletName() {
|
||||
return getIt.get<SharedPreferences>().getString(PreferencesKey.currentWalletName) ?? '';
|
||||
}
|
||||
|
||||
return WalletUnlockLoadableViewModel(
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<WalletLoadingService>(),
|
||||
walletName: args.walletName ?? currentWalletName,
|
||||
walletType: args.walletType ?? currentWalletType);
|
||||
});
|
||||
WalletType currentWalletType() {
|
||||
return deserializeFromInt(
|
||||
getIt.get<SharedPreferences>().getInt(PreferencesKey.currentWalletType) ?? 0);
|
||||
}
|
||||
|
||||
getIt.registerFactoryParam<WalletUnlockVerifiableViewModel, WalletUnlockArguments, void>((args, _) {
|
||||
final currentWalletName = getIt
|
||||
.get<SharedPreferences>()
|
||||
.getString(PreferencesKey.currentWalletName) ?? '';
|
||||
final currentWalletTypeRaw =
|
||||
getIt.get<SharedPreferences>()
|
||||
.getInt(PreferencesKey.currentWalletType) ?? 0;
|
||||
final currentWalletType = deserializeFromInt(currentWalletTypeRaw);
|
||||
getIt.registerFactoryParam<WalletUnlockLoadableViewModel, WalletUnlockArguments, void>(
|
||||
(args, _) => WalletUnlockLoadableViewModel(
|
||||
getIt.get<AppStore>(), getIt.get<WalletLoadingService>(),
|
||||
useTotp: args.useTotp && getIt.get<Setup2FAViewModel>().useTOTP2FA,
|
||||
walletName: args.walletName ?? currentWalletName(),
|
||||
walletType: args.walletType ?? currentWalletType()));
|
||||
|
||||
return WalletUnlockVerifiableViewModel(
|
||||
getIt.get<AppStore>(),
|
||||
walletName: args.walletName ?? currentWalletName,
|
||||
walletType: args.walletType ?? currentWalletType);
|
||||
});
|
||||
getIt.registerFactoryParam<WalletUnlockVerifiableViewModel, WalletUnlockArguments, void>(
|
||||
(args, _) => WalletUnlockVerifiableViewModel(getIt.get<AppStore>(),
|
||||
useTotp: args.useTotp && getIt.get<Setup2FAViewModel>().useTOTP2FA,
|
||||
walletName: currentWalletName(),
|
||||
walletType: currentWalletType()));
|
||||
|
||||
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
|
||||
final walletPasswordAuthViewModel = getIt.get<WalletUnlockLoadableViewModel>(param1: args);
|
||||
return WalletUnlockPage(
|
||||
getIt.get<WalletUnlockLoadableViewModel>(param1: args),
|
||||
args.callback,
|
||||
args.authPasswordHandler,
|
||||
closable: closable);
|
||||
walletPasswordAuthViewModel: walletPasswordAuthViewModel,
|
||||
onAuthenticationFinished: args.callback,
|
||||
authPasswordHandler: (String walletName, WalletType walletType, String password) async {
|
||||
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');
|
||||
|
||||
// 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) {
|
||||
final walletPasswordAuthViewModel = getIt.get<WalletUnlockVerifiableViewModel>(param1: args);
|
||||
return WalletUnlockPage(
|
||||
getIt.get<WalletUnlockVerifiableViewModel>(param1: args),
|
||||
args.callback,
|
||||
args.authPasswordHandler,
|
||||
closable: closable);
|
||||
walletPasswordAuthViewModel: walletPasswordAuthViewModel,
|
||||
onAuthenticationFinished: args.callback,
|
||||
authPasswordHandler: (_, __, ___) => walletPasswordAuthViewModel.verify(),
|
||||
closable: closable,
|
||||
isVerifiable: true);
|
||||
}, instanceName: 'wallet_unlock_verifiable');
|
||||
|
||||
getIt.registerFactory<WalletUnlockPage>(
|
||||
() => getIt.get<WalletUnlockPage>(
|
||||
param1: WalletUnlockArguments(
|
||||
callback: (bool successful, _) {
|
||||
if (successful) {
|
||||
final authStore = getIt.get<AuthenticationStore>();
|
||||
authStore.allowed();
|
||||
}}),
|
||||
param2: false,
|
||||
instanceName: 'wallet_unlock_loadable'),
|
||||
param1: WalletUnlockArguments(
|
||||
useTotp: getIt.get<AppStore>().settingsStore.shouldRequireTOTP2FAForAccessingWallet,
|
||||
callback: (AuthResponse auth) =>
|
||||
auth.success ? getIt.get<AuthenticationStore>().allowed() : null),
|
||||
param2: false,
|
||||
instanceName: 'wallet_unlock_loadable'),
|
||||
instanceName: 'wallet_password_login');
|
||||
|
||||
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 normal = Cake2FAPresetsOptions(title: 'Normal', raw: 1);
|
||||
static const aggressive = Cake2FAPresetsOptions(title: 'Aggressive', raw: 2);
|
||||
static const unselected = Cake2FAPresetsOptions(title: 'Unselected', raw: 3);
|
||||
|
||||
static Cake2FAPresetsOptions deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
|
@ -15,6 +16,8 @@ class Cake2FAPresetsOptions extends EnumerableItem<int> with Serializable<int> {
|
|||
return Cake2FAPresetsOptions.normal;
|
||||
case 2:
|
||||
return Cake2FAPresetsOptions.aggressive;
|
||||
case 3:
|
||||
return Cake2FAPresetsOptions.unselected;
|
||||
default:
|
||||
throw Exception(
|
||||
'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_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/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
|
@ -289,25 +290,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
case Routes.auth:
|
||||
return MaterialPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_)
|
||||
=> SettingsStoreBase.walletPasswordDirectInput
|
||||
? getIt.get<WalletUnlockPage>(
|
||||
param1: WalletUnlockArguments(
|
||||
callback: settings.arguments as OnAuthenticationFinished),
|
||||
instanceName: 'wallet_unlock_verifiable',
|
||||
param2: true)
|
||||
: getIt.get<AuthPage>(
|
||||
param1: settings.arguments as OnAuthenticationFinished,
|
||||
param2: true));
|
||||
builder: (_) => SettingsStoreBase.walletPasswordDirectInput
|
||||
? getIt.get<WalletUnlockPage>(
|
||||
param1: settings.arguments as WalletUnlockArguments,
|
||||
instanceName: 'wallet_unlock_verifiable',
|
||||
param2: true)
|
||||
: getIt.get<AuthPage>(
|
||||
param1: settings.arguments as OnAuthenticationFinished, param2: true));
|
||||
|
||||
case Routes.totpAuthCodePage:
|
||||
final args = settings.arguments as TotpAuthArgumentsModel;
|
||||
return MaterialPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<TotpAuthCodePage>(
|
||||
param1: args,
|
||||
),
|
||||
);
|
||||
fullscreenDialog: true,
|
||||
builder: (_) =>
|
||||
getIt.get<TotpAuthCodePage>(param1: settings.arguments as TotpAuthArgumentsModel));
|
||||
|
||||
case Routes.walletUnlockLoadable:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -321,19 +316,16 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
case Routes.unlock:
|
||||
return MaterialPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_)
|
||||
=> SettingsStoreBase.walletPasswordDirectInput
|
||||
? WillPopScope(
|
||||
child: getIt.get<WalletUnlockPage>(
|
||||
param1: WalletUnlockArguments(
|
||||
callback: settings.arguments as OnAuthenticationFinished),
|
||||
builder: (_) => SettingsStoreBase.walletPasswordDirectInput
|
||||
? WillPopScope(
|
||||
child: getIt.get<WalletUnlockPage>(
|
||||
param1: settings.arguments as WalletUnlockArguments,
|
||||
param2: false,
|
||||
instanceName: 'wallet_unlock_verifiable'),
|
||||
onWillPop: () async => false)
|
||||
: WillPopScope(
|
||||
child: getIt.get<AuthPage>(
|
||||
param1: settings.arguments as OnAuthenticationFinished,
|
||||
param2: false),
|
||||
onWillPop: () async => false)
|
||||
: WillPopScope(
|
||||
child: getIt.get<AuthPage>(
|
||||
param1: settings.arguments as OnAuthenticationFinished, param2: false),
|
||||
onWillPop: () async => false));
|
||||
|
||||
case Routes.connectionSync:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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:mobx/mobx.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/core/execution_state.dart';
|
||||
|
||||
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
|
||||
|
||||
abstract class AuthPageState<T extends StatefulWidget> extends State<T> {
|
||||
void changeProcessText(String text);
|
||||
void hideProgressText();
|
||||
|
@ -19,9 +18,7 @@ abstract class AuthPageState<T extends StatefulWidget> extends State<T> {
|
|||
}
|
||||
|
||||
class AuthPage extends StatefulWidget {
|
||||
AuthPage(this.authViewModel,
|
||||
{required this.onAuthenticationFinished,
|
||||
this.closable = true});
|
||||
AuthPage(this.authViewModel, {required this.onAuthenticationFinished, this.closable = true});
|
||||
|
||||
final AuthViewModel authViewModel;
|
||||
final OnAuthenticationFinished onAuthenticationFinished;
|
||||
|
@ -34,19 +31,17 @@ class AuthPage extends StatefulWidget {
|
|||
class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
||||
final _key = GlobalKey<ScaffoldState>();
|
||||
final _pinCodeKey = GlobalKey<PinCodeState>();
|
||||
final _backArrowImageDarkTheme =
|
||||
Image.asset('assets/images/close_button.png');
|
||||
final _backArrowImageDarkTheme = Image.asset('assets/images/close_button.png');
|
||||
ReactionDisposer? _reaction;
|
||||
Flushbar<void>? _authBar;
|
||||
Flushbar<void>? _progressBar;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_reaction ??=
|
||||
reaction((_) => widget.authViewModel.state, (ExecutionState state) {
|
||||
_reaction ??= reaction((_) => widget.authViewModel.state, (ExecutionState state) {
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onAuthenticationFinished(true, this);
|
||||
widget.onAuthenticationFinished(AuthResponse(success: true, close: close));
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
|
@ -54,9 +49,7 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
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);
|
||||
_authBar = createBar<void>(S.of(context).authentication, duration: null)..show(context);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,10 +57,10 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
_pinCodeKey.currentState?.clear();
|
||||
dismissFlushBar(_authBar);
|
||||
showBar<void>(
|
||||
context, S.of(context).failed_authentication(state.error));
|
||||
showBar<void>(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 {
|
||||
_pinCodeKey.currentState?.clear();
|
||||
dismissFlushBar(_authBar);
|
||||
showBar<void>(
|
||||
context, S.of(context).failed_authentication(state.error));
|
||||
showBar<void>(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
|
||||
void changeProcessText(String text) {
|
||||
dismissFlushBar(_authBar);
|
||||
_progressBar = createBar<void>(text, duration: null)
|
||||
..show(_key.currentContext!);
|
||||
_progressBar = createBar<void>(text, duration: null)..show(_key.currentContext!);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -144,15 +136,15 @@ class AuthPagePinCodeStateImpl extends AuthPageState<AuthPage> {
|
|||
width: 37,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: _backArrowImageDarkTheme,
|
||||
child: _backArrowImageDarkTheme,
|
||||
),
|
||||
))
|
||||
: Container(),
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
border: null),
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin),
|
||||
(_) => null, widget.authViewModel.pinLength, false, _pinCodeKey));
|
||||
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin), (_) => null,
|
||||
widget.authViewModel.pinLength, false, _pinCodeKey));
|
||||
}
|
||||
|
||||
void dismissFlushBar(Flushbar<dynamic>? bar) {
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import 'package:cake_wallet/di.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/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_item.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/wallet_unlock/wallet_unlock_arguments.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/set_up_2fa_viewmodel.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -40,14 +41,11 @@ class DesktopSidebarWrapper extends BasePage {
|
|||
),
|
||||
trailing: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.unlock,
|
||||
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
|
||||
if (isAuthenticatedSuccessfully) {
|
||||
auth.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
Navigator.of(context).pushNamed(Routes.unlock,
|
||||
arguments: WalletUnlockArguments(
|
||||
useTotp: getIt.get<Setup2FAViewModel>().useTOTP2FA &&
|
||||
getIt.get<Setup2FAViewModel>().shouldRequireTOTP2FAForAccessingWallet,
|
||||
callback: (auth) => auth.success ? auth.close() : null));
|
||||
},
|
||||
child: Icon(Icons.lock_outline),
|
||||
),
|
||||
|
|
|
@ -148,38 +148,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
}
|
||||
|
||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||
if (SettingsStoreBase.walletPasswordDirectInput) {
|
||||
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,
|
||||
);
|
||||
await widget.walletListViewModel.authWallet(context, wallet);
|
||||
}
|
||||
|
||||
void _navigateToCreateWallet() {
|
||||
|
@ -188,17 +157,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
context,
|
||||
route: Routes.newWallet,
|
||||
arguments: widget.walletListViewModel.currentWalletType,
|
||||
conditionToDetermineIfToUse2FA: widget
|
||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
} else {
|
||||
widget._authService.authenticateAction(
|
||||
context,
|
||||
route: Routes.newWalletType,
|
||||
conditionToDetermineIfToUse2FA: widget
|
||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.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:uni_links/uni_links.dart';
|
||||
|
||||
import '../setup_2fa/setup_2fa_enter_code_page.dart';
|
||||
|
||||
class Root extends StatefulWidget {
|
||||
Root({
|
||||
required Key key,
|
||||
|
@ -98,8 +95,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_isInactive &&
|
||||
widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||
setState(() => _setInactive(true));
|
||||
}
|
||||
|
||||
|
@ -126,17 +122,15 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
return;
|
||||
} else {
|
||||
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
||||
final shouldUseTotp2FAToAccessWallets = widget.appStore
|
||||
.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
final shouldUseTotp2FAToAccessWallets =
|
||||
widget.appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||
_reset();
|
||||
auth.close(
|
||||
route: Routes.totpAuthCodePage,
|
||||
arguments: TotpAuthArgumentsModel(
|
||||
onTotpAuthenticationFinished:
|
||||
(bool isAuthenticatedSuccessfully,
|
||||
TotpAuthCodePageState totpAuth) {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
onTotpAuthenticationFinished: (totpAuth) {
|
||||
if (!totpAuth.success) {
|
||||
return;
|
||||
}
|
||||
_reset();
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import 'package:another_flushbar/flushbar.dart';
|
||||
import 'package:cake_wallet/core/execution_state.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:flutter/material.dart';
|
||||
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.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/view_model/set_up_2fa_viewmodel.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import '../../../palette.dart';
|
||||
import '../../../routes.dart';
|
||||
|
||||
typedef OnTotpAuthenticationFinished = void Function(
|
||||
bool, TotpAuthCodePageState);
|
||||
|
||||
class TotpAuthCodePage extends StatefulWidget {
|
||||
TotpAuthCodePage(
|
||||
this.setup2FAViewModel, {
|
||||
required this.totpArguments,
|
||||
});
|
||||
|
||||
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() {
|
||||
class TotpAuthCodePage extends BasePage {
|
||||
TotpAuthCodePage({required this.setup2FAViewModel, required TotpAuthArgumentsModel totpArguments})
|
||||
: isForSetup = totpArguments.isForSetup ?? false,
|
||||
isClosable = totpArguments.isClosable ?? true,
|
||||
showPopup = totpArguments.isForSetup ?? totpArguments.showPopup ?? false,
|
||||
onTotpAuthenticationFinished = totpArguments.onTotpAuthenticationFinished,
|
||||
totpController = TextEditingController() {
|
||||
totpController.addListener(() {
|
||||
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 Setup2FAViewModel setup2FAViewModel;
|
||||
final bool isForSetup;
|
||||
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
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
|
@ -167,40 +78,47 @@ class TOTPEnterCode extends BasePage {
|
|||
),
|
||||
Spacer(),
|
||||
Observer(
|
||||
builder: (context) {
|
||||
return PrimaryButton(
|
||||
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8,
|
||||
builder: (_) {
|
||||
return LoadingPrimaryButton(
|
||||
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8 ||
|
||||
setup2FAViewModel.state is IsExecutingState,
|
||||
isLoading: setup2FAViewModel.state is IsExecutingState,
|
||||
onPressed: () async {
|
||||
final result = await setup2FAViewModel.totp2FAAuth(
|
||||
totpController.text, isForSetup);
|
||||
final bannedState =
|
||||
setup2FAViewModel.state is AuthenticationBanned;
|
||||
final result =
|
||||
await setup2FAViewModel.totp2FAAuth(totpController.text, isForSetup);
|
||||
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return PopUpCancellableAlertDialog(
|
||||
contentText: _textDisplayedInPopupOnResult(
|
||||
result, bannedState, context),
|
||||
actionButtonText: S.of(context).ok,
|
||||
buttonAction: () {
|
||||
result ? setup2FAViewModel.success() : null;
|
||||
if (isForSetup && result) {
|
||||
Navigator.pop(context);
|
||||
// Navigator.of(context)
|
||||
// .popAndPushNamed(Routes.modify2FAPage);
|
||||
} else {
|
||||
Navigator.of(context).pop(result);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (isForSetup && result) {
|
||||
Navigator.pushReplacementNamed(
|
||||
context, Routes.modify2FAPage);
|
||||
if (showPopup || result == false) {
|
||||
final bannedState = setup2FAViewModel.state is AuthenticationBanned;
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return PopUpCancellableAlertDialog(
|
||||
contentText: _textDisplayedInPopupOnResult(result, bannedState, context),
|
||||
actionButtonText: S.of(context).ok,
|
||||
buttonAction: () {
|
||||
result
|
||||
? onTotpAuthenticationFinished
|
||||
?.call(TotpResponse(success: true, close: close))
|
||||
: null;
|
||||
|
||||
if (isForSetup && result) {
|
||||
Navigator.pushNamedAndRemoveUntil(
|
||||
context, Routes.dashboard, (route) => false);
|
||||
} else {
|
||||
Navigator.of(context).pop(result);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (showPopup == false) {
|
||||
result
|
||||
? onTotpAuthenticationFinished
|
||||
?.call(TotpResponse(success: true, close: close))
|
||||
: null;
|
||||
}
|
||||
},
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).primaryColor,
|
||||
|
@ -214,13 +132,10 @@ class TOTPEnterCode extends BasePage {
|
|||
);
|
||||
}
|
||||
|
||||
String _textDisplayedInPopupOnResult(
|
||||
bool result, bool bannedState, BuildContext context) {
|
||||
String _textDisplayedInPopupOnResult(bool result, bool bannedState, BuildContext context) {
|
||||
switch (result) {
|
||||
case true:
|
||||
return isForSetup
|
||||
? S.current.totp_2fa_success
|
||||
: S.current.totp_verification_success;
|
||||
return isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
|
||||
case false:
|
||||
if (bannedState) {
|
||||
final state = setup2FAViewModel.state as AuthenticationBanned;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:another_flushbar/flushbar.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/palette.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/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
|
@ -100,21 +100,17 @@ class WalletEditPage extends BasePage {
|
|||
try {
|
||||
bool confirmed = false;
|
||||
|
||||
if (SettingsStoreBase
|
||||
.walletPasswordDirectInput) {
|
||||
if (SettingsStoreBase.walletPasswordDirectInput) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.walletUnlockLoadable,
|
||||
arguments: WalletUnlockArguments(
|
||||
authPasswordHandler:
|
||||
(String password) async {
|
||||
await walletEditViewModel
|
||||
.changeName(editingWallet,
|
||||
password: password);
|
||||
useTotp: true,
|
||||
authPasswordHandler: (_, __, String password) async {
|
||||
await walletEditViewModel.changeName(editingWallet,
|
||||
password: password);
|
||||
},
|
||||
callback: (bool
|
||||
isAuthenticatedSuccessfully,
|
||||
AuthPageState auth) async {
|
||||
if (isAuthenticatedSuccessfully) {
|
||||
callback: (AuthResponse auth) async {
|
||||
if (auth.success) {
|
||||
auth.close();
|
||||
confirmed = true;
|
||||
}
|
||||
|
@ -122,8 +118,7 @@ class WalletEditPage extends BasePage {
|
|||
walletName: editingWallet.name,
|
||||
walletType: editingWallet.type));
|
||||
} else {
|
||||
await walletEditViewModel
|
||||
.changeName(editingWallet);
|
||||
await walletEditViewModel.changeName(editingWallet);
|
||||
confirmed = true;
|
||||
}
|
||||
|
||||
|
@ -150,12 +145,14 @@ class WalletEditPage extends BasePage {
|
|||
}
|
||||
|
||||
Future<void> _removeWallet(BuildContext context) async {
|
||||
authService.authenticateAction(context, onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
authService.authenticateAction(
|
||||
context,
|
||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
|
||||
_onSuccessfulAuth(context);
|
||||
_onSuccessfulAuth(context);
|
||||
},
|
||||
conditionToDetermineIfToUse2FA: false,
|
||||
);
|
||||
|
|
|
@ -252,45 +252,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
}
|
||||
|
||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||
if (SettingsStoreBase.walletPasswordDirectInput) {
|
||||
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,
|
||||
);
|
||||
widget.walletListViewModel.authWallet(context, wallet);
|
||||
}
|
||||
|
||||
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: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 {
|
||||
WalletUnlockArguments(
|
||||
{required this.callback,
|
||||
required this.useTotp,
|
||||
this.walletName,
|
||||
this.walletType,
|
||||
this.authPasswordHandler});
|
||||
this.authPasswordHandler})
|
||||
: state = InitialExecutionState();
|
||||
|
||||
final OnAuthenticationFinished callback;
|
||||
final AuthPasswordHandler? authPasswordHandler;
|
||||
final String? walletName;
|
||||
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:cake_wallet/core/authentication_request_data.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/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/widgets/primary_button.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/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/show_bar.dart';
|
||||
import 'package:flutter/cupertino.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:mobx/mobx.dart';
|
||||
|
||||
|
||||
class WalletUnlockPage extends StatefulWidget {
|
||||
class WalletUnlockPage extends BasePage {
|
||||
WalletUnlockPage(
|
||||
this.walletUnlockViewModel,
|
||||
this.onAuthenticationFinished,
|
||||
this.authPasswordHandler,
|
||||
{required this.closable});
|
||||
{required this.walletPasswordAuthViewModel,
|
||||
required this.onAuthenticationFinished,
|
||||
required this.authPasswordHandler,
|
||||
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 AuthPasswordHandler? authPasswordHandler;
|
||||
final bool closable;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => WalletUnlockPageState();
|
||||
}
|
||||
|
||||
class WalletUnlockPageState extends AuthPageState<WalletUnlockPage> {
|
||||
WalletUnlockPageState()
|
||||
: _passwordController = TextEditingController();
|
||||
|
||||
final AuthPasswordHandler authPasswordHandler;
|
||||
final bool isClosable;
|
||||
final bool isVerifiable;
|
||||
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
|
||||
void initState() {
|
||||
_reaction ??=
|
||||
reaction((_) => widget.walletUnlockViewModel.state, (ExecutionState state) {
|
||||
if (state is ExecutedSuccessfullyState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onAuthenticationFinished(true, this);
|
||||
});
|
||||
setState(() {});
|
||||
Widget? leading(BuildContext context) {
|
||||
return isClosable ? super.leading(context) : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
Future<void> close({String? route, arguments}) async {
|
||||
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();
|
||||
}
|
||||
|
||||
@override
|
||||
void changeProcessText(String text) {
|
||||
dismissFlushBar(_authBar);
|
||||
_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');
|
||||
void onFailure(String error) {
|
||||
walletPasswordAuthViewModel.state = FailureState(error);
|
||||
showBar<void>(context, S.of(context).failed_authentication(error),
|
||||
duration: Duration(seconds: 3));
|
||||
onAuthenticationFinished(
|
||||
AuthResponse(error: S.of(context).failed_authentication(error), close: close));
|
||||
}
|
||||
|
||||
/// not the best scenario, but WidgetsBinding is not behaving correctly on Android
|
||||
await Future<void>.delayed(Duration(milliseconds: 50));
|
||||
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(
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtil.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(widget.walletUnlockViewModel.walletName,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
SizedBox(height: 24),
|
||||
Form(
|
||||
child: TextFormField(
|
||||
onChanged: (value) => null,
|
||||
controller: _passwordController,
|
||||
textAlign: TextAlign.center,
|
||||
obscureText: true,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<NewWalletTheme>()!.hintTextColor),
|
||||
hintText: S.of(context).enter_wallet_password,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).extension<NewWalletTheme>()!.underlineColor,
|
||||
width: 1.0),
|
||||
)
|
||||
)))])),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 24),
|
||||
child: Observer(
|
||||
builder: (_) =>
|
||||
LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
if (widget.authPasswordHandler != null) {
|
||||
try {
|
||||
await widget.authPasswordHandler!(widget
|
||||
.walletUnlockViewModel.password);
|
||||
widget.walletUnlockViewModel.success();
|
||||
} catch (e) {
|
||||
widget.walletUnlockViewModel.failure(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(walletPasswordAuthViewModel.walletName,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
|
||||
SizedBox(height: 24),
|
||||
Form(
|
||||
child: TextFormField(
|
||||
onChanged: (value) => null,
|
||||
controller: _passwordController,
|
||||
textAlign: TextAlign.center,
|
||||
obscureText: true,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.extension<NewWalletTheme>()!
|
||||
.hintTextColor),
|
||||
hintText: S.of(context).enter_wallet_password,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.extension<NewWalletTheme>()!
|
||||
.underlineColor,
|
||||
width: 1.0)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.extension<NewWalletTheme>()!
|
||||
.underlineColor,
|
||||
width: 1.0)))))
|
||||
])),
|
||||
Observer(builder: (_) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 24),
|
||||
child: LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
Flushbar<void>? loadingBar;
|
||||
if (!isVerifiable)
|
||||
loadingBar = createBar<void>(S.of(context).loading_your_wallet)
|
||||
..show(context);
|
||||
|
||||
widget.walletUnlockViewModel.unlock();
|
||||
},
|
||||
text: S.of(context).unlock,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
isLoading: widget.walletUnlockViewModel.state is IsExecutingState,
|
||||
isDisabled: widget.walletUnlockViewModel.state is IsExecutingState)))
|
||||
]))
|
||||
));
|
||||
walletPasswordAuthViewModel.state = IsExecutingState();
|
||||
dynamic payload;
|
||||
|
||||
try {
|
||||
payload = await authPasswordHandler(
|
||||
walletPasswordAuthViewModel.walletName,
|
||||
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;
|
||||
final selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||
Cake2FAPresetsOptions.normal.raw);
|
||||
Cake2FAPresetsOptions.unselected.raw);
|
||||
final shouldRequireTOTP2FAForAccessingWallet =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
||||
final shouldRequireTOTP2FAForSendsToContact =
|
||||
|
@ -736,7 +736,7 @@ abstract class SettingsStoreBase with Store {
|
|||
allowBiometricalAuthentication;
|
||||
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||
Cake2FAPresetsOptions.normal.raw);
|
||||
Cake2FAPresetsOptions.unselected.raw);
|
||||
shouldRequireTOTP2FAForAccessingWallet =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
||||
shouldRequireTOTP2FAForSendsToContact =
|
||||
|
@ -762,7 +762,7 @@ abstract class SettingsStoreBase with Store {
|
|||
shouldShowMarketPlaceInDashboard;
|
||||
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||
Cake2FAPresetsOptions.narrow.raw);
|
||||
Cake2FAPresetsOptions.unselected.raw);
|
||||
exchangeStatus = ExchangeApiMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
||||
ExchangeApiMode.enabled.raw);
|
||||
|
|
|
@ -28,7 +28,9 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
||||
state = InitialExecutionState() {
|
||||
_getRandomBase32SecretKey();
|
||||
selectCakePreset(selectedCake2FAPreset);
|
||||
if (selectedCake2FAPreset == Cake2FAPresetsOptions.unselected) {
|
||||
selectCakePreset(Cake2FAPresetsOptions.normal);
|
||||
}
|
||||
reaction((_) => state, _saveLastAuthTime);
|
||||
}
|
||||
|
||||
|
@ -92,6 +94,7 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
@action
|
||||
void setUseTOTP2FA(bool value) {
|
||||
_settingsStore.useTOTP2FA = value;
|
||||
if (!value) _settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.unselected;
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -134,7 +137,7 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
|
||||
@action
|
||||
Future<bool> totp2FAAuth(String otpText, bool isForSetup) async {
|
||||
state = InitialExecutionState();
|
||||
state = IsExecutingState();
|
||||
_failureCounter = _settingsStore.numberOfFailedTokenTrials;
|
||||
final _banDuration = banDuration();
|
||||
|
||||
|
@ -154,11 +157,11 @@ abstract class Setup2FAViewModelBase with Store {
|
|||
isForSetup ? setUseTOTP2FA(result) : null;
|
||||
|
||||
if (result) {
|
||||
state = ExecutedSuccessfullyState();
|
||||
return true;
|
||||
} else {
|
||||
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
||||
adjustTokenTrialNumber(value);
|
||||
print(value);
|
||||
if (_failureCounter >= maxFailedTrials) {
|
||||
final banDuration = await ban();
|
||||
state = AuthenticationBanned(
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
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/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:mobx/mobx.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_type.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';
|
||||
|
||||
|
@ -43,8 +50,7 @@ abstract class WalletListViewModelBase with Store {
|
|||
|
||||
@action
|
||||
Future<void> loadWallet(WalletListItem walletItem) async {
|
||||
final wallet =
|
||||
await _walletLoadingService.load(walletItem.type, walletItem.name);
|
||||
final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name);
|
||||
_appStore.changeCurrentWallet(wallet);
|
||||
}
|
||||
|
||||
|
@ -57,15 +63,53 @@ abstract class WalletListViewModelBase with Store {
|
|||
name: info.name,
|
||||
type: info.type,
|
||||
key: info.key,
|
||||
isCurrent: info.name == _appStore.wallet?.name &&
|
||||
info.type == _appStore.wallet?.type,
|
||||
isCurrent: info.name == _appStore.wallet!.name && info.type == _appStore.wallet!.type,
|
||||
isEnabled: availableWalletTypes.contains(info.type),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool checkIfAuthRequired() {
|
||||
return _authService.requireAuth();
|
||||
@action
|
||||
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:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/core/wallet_loading_service.dart';
|
||||
import 'package:cake_wallet/store/app_store.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';
|
||||
|
||||
class WalletUnlockLoadableViewModel = WalletUnlockLoadableViewModelBase
|
||||
with _$WalletUnlockLoadableViewModel;
|
||||
|
||||
abstract class WalletUnlockLoadableViewModelBase extends WalletUnlockViewModel
|
||||
with Store {
|
||||
abstract class WalletUnlockLoadableViewModelBase extends WalletPasswordAuthViewModel with Store {
|
||||
WalletUnlockLoadableViewModelBase(this._appStore, this._walletLoadingService,
|
||||
{required this.walletName, required this.walletType})
|
||||
: password = '',
|
||||
state = InitialExecutionState();
|
||||
{required this.useTotp, required this.walletName, required this.walletType})
|
||||
: super(useTotp: useTotp, walletName: walletName, walletType: walletType);
|
||||
|
||||
final String walletName;
|
||||
|
||||
final WalletType walletType;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String password;
|
||||
|
||||
@override
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
||||
final WalletLoadingService _walletLoadingService;
|
||||
|
||||
final AppStore _appStore;
|
||||
|
||||
@override
|
||||
@action
|
||||
void setPassword(String password) => this.password = password;
|
||||
@observable
|
||||
bool useTotp;
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<void> unlock() async {
|
||||
try {
|
||||
state = InitialExecutionState();
|
||||
final wallet = await _walletLoadingService.load(walletType, walletName,
|
||||
password: password);
|
||||
_appStore.changeCurrentWallet(wallet);
|
||||
success();
|
||||
} catch (e) {
|
||||
failure(e.toString());
|
||||
}
|
||||
Future<dynamic> load() async {
|
||||
final wallet = await _walletLoadingService.load(walletType, walletName, password: password);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
@action
|
||||
void success() {
|
||||
state = ExecutedSuccessfullyState();
|
||||
}
|
||||
|
||||
@override
|
||||
@action
|
||||
void failure(e) {
|
||||
state = FailureState(e.toString());
|
||||
Future<dynamic> unlock() async {
|
||||
final wallet = await load();
|
||||
_appStore.changeCurrentWallet(wallet as WalletBase);
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,60 +2,28 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/core/execution_state.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_verifiable_view_model.g.dart';
|
||||
|
||||
class WalletUnlockVerifiableViewModel = WalletUnlockVerifiableViewModelBase
|
||||
with _$WalletUnlockVerifiableViewModel;
|
||||
|
||||
abstract class WalletUnlockVerifiableViewModelBase extends WalletUnlockViewModel
|
||||
with Store {
|
||||
WalletUnlockVerifiableViewModelBase(this.appStore,
|
||||
{required this.walletName, required this.walletType})
|
||||
: password = '',
|
||||
state = InitialExecutionState();
|
||||
abstract class WalletUnlockVerifiableViewModelBase extends WalletPasswordAuthViewModel with Store {
|
||||
WalletUnlockVerifiableViewModelBase(this._appStore,
|
||||
{required this.useTotp, required this.walletName, required this.walletType})
|
||||
: super(useTotp: useTotp, walletName: walletName, walletType: walletType);
|
||||
|
||||
final String walletName;
|
||||
|
||||
final WalletType walletType;
|
||||
final AppStore _appStore;
|
||||
|
||||
final AppStore appStore;
|
||||
|
||||
@override
|
||||
@observable
|
||||
String password;
|
||||
bool useTotp;
|
||||
|
||||
@override
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
||||
@override
|
||||
@action
|
||||
void setPassword(String password) => this.password = password;
|
||||
|
||||
@override
|
||||
@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());
|
||||
Future<void> verify() async {
|
||||
final valid = _appStore.wallet!.password == password;
|
||||
if (!valid) throw Exception('${S.current.invalid_password}');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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