CW-278 Enhance PIN timeout feature code (#886)

* CW-278 enhance pin timeout feature

* CW-278 enhance pin timeout feature

* Update flow to remove extension

* Replace pin request on other instances
This commit is contained in:
Godwin Asuquo 2023-04-20 03:54:25 +03:00 committed by GitHub
parent 27961f2f25
commit efef30f8eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 111 deletions

View file

@ -1,10 +1,12 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/entities/encrypt.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/store/settings_store.dart';
class AuthService with Store {
@ -14,6 +16,12 @@ class AuthService with Store {
required this.settingsStore,
});
static const List<String> _alwaysAuthenticateRoutes = [
Routes.showKeys,
Routes.backup,
Routes.setupPin,
];
final FlutterSecureStorage secureStorage;
final SharedPreferences sharedPreferences;
final SettingsStore settingsStore;
@ -66,4 +74,33 @@ class AuthService with Store {
return timeDifference.inMinutes;
}
Future<void> authenticateAction(BuildContext context,
{Function(bool)? onAuthSuccess, String? route, Object? arguments}) async {
assert(route != null || onAuthSuccess != null,
'Either route or onAuthSuccess param must be passed.');
if (!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;
}
if (onAuthSuccess != null) {
auth.close().then((value) => onAuthSuccess.call(true));
} else {
auth.close(route: route, arguments: arguments);
}
});
}
}

View file

@ -499,7 +499,7 @@ Future setup(
}
getIt.registerFactory(() =>
WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>()));
WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>(), authService: getIt.get<AuthService>(),));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet!;
@ -593,7 +593,7 @@ Future setup(
getIt.registerFactory(() => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>()));
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>()));
getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
@ -926,7 +926,7 @@ Future setup(
wallet: getIt.get<AppStore>().wallet!)
);
getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>()));
getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>(), getIt.get<AuthService>()));
getIt.registerFactory(() => DesktopSidebarViewModel());

View file

@ -1,8 +1,9 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/desktop_dropdown_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_bar.dart';
@ -16,8 +17,10 @@ import 'package:flutter_mobx/flutter_mobx.dart';
class DesktopWalletSelectionDropDown extends StatefulWidget {
final WalletListViewModel walletListViewModel;
final AuthService _authService;
DesktopWalletSelectionDropDown(this.walletListViewModel, {Key? key}) : super(key: key);
DesktopWalletSelectionDropDown(this.walletListViewModel, this._authService, {Key? key})
: super(key: key);
@override
State<DesktopWalletSelectionDropDown> createState() => _DesktopWalletSelectionDropDownState();
@ -140,25 +143,12 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
}
Future<void> _loadWallet(WalletListItem wallet) async {
if (await widget.walletListViewModel.checkIfAuthRequired()) {
await Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (!isAuthenticatedSuccessfully) {
return;
}
widget._authService.authenticateAction(context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
try {
auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
auth.hideProgressText();
auth.close();
setState(() {});
} catch (e) {
auth.changeProcessText(
S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
});
} else {
try {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
@ -167,7 +157,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
} catch (e) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
});
}
void _navigateToCreateWallet() {

View file

@ -1,6 +1,6 @@
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.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/generated/i18n.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
@ -13,7 +13,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class SecurityBackupPage extends BasePage {
SecurityBackupPage(this._securitySettingsViewModel);
SecurityBackupPage(this._securitySettingsViewModel, this._authService);
final AuthService _authService;
@override
String get title => S.current.security_and_backup;
@ -27,35 +29,24 @@ class SecurityBackupPage extends BasePage {
child: Column(mainAxisSize: MainAxisSize.min, children: [
SettingsCellWithArrow(
title: S.current.show_keys,
handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.showKeys);
}
}),
handler: (_) => _authService.authenticateAction(context, route: Routes.showKeys),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(
title: S.current.create_backup,
handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.backup);
}
}),
handler: (_) => _authService.authenticateAction(context, route: Routes.backup),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(
title: S.current.settings_change_pin,
handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
auth.close(
route: isAuthenticatedSuccessfully ? Routes.setupPin : null,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
},
);
})),
title: S.current.settings_change_pin,
handler: (_) => _authService.authenticateAction(
context,
route: Routes.setupPin,
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
setupPinContext.close();
},
),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Observer(builder: (_) {
return SettingsSwitcherCell(
@ -63,8 +54,8 @@ class SecurityBackupPage extends BasePage {
value: _securitySettingsViewModel.allowBiometricalAuthentication,
onValueChange: (BuildContext context, bool value) {
if (value) {
Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
_authService.authenticateAction(context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel
@ -74,8 +65,6 @@ class SecurityBackupPage extends BasePage {
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
auth.close();
});
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);

View file

@ -1,4 +1,4 @@
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_bar.dart';
@ -19,18 +19,21 @@ import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
class WalletListPage extends BasePage {
WalletListPage({required this.walletListViewModel});
WalletListPage({required this.walletListViewModel, required this.authService});
final WalletListViewModel walletListViewModel;
final AuthService authService;
@override
Widget body(BuildContext context) => WalletListBody(walletListViewModel: walletListViewModel);
Widget body(BuildContext context) =>
WalletListBody(walletListViewModel: walletListViewModel, authService: authService);
}
class WalletListBody extends StatefulWidget {
WalletListBody({required this.walletListViewModel});
WalletListBody({required this.walletListViewModel, required this.authService});
final WalletListViewModel walletListViewModel;
final AuthService authService;
@override
WalletListBodyState createState() => WalletListBodyState();
@ -129,7 +132,8 @@ class WalletListBodyState extends State<WalletListBody> {
fontSize: 22,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.headline6!
.primaryTextTheme
.headline6!
.color!),
)
],
@ -201,61 +205,40 @@ class WalletListBodyState extends State<WalletListBody> {
}
Future<void> _loadWallet(WalletListItem wallet) async {
if (await widget.walletListViewModel.checkIfAuthRequired()) {
await Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (!isAuthenticatedSuccessfully) {
return;
}
await widget.authService.authenticateAction(context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
try {
auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
auth.hideProgressText();
auth.close();
// only pop the wallets route in mobile as it will go back to dashboard page
// in desktop platforms the navigation tree is different
if (DeviceInfo.instance.isMobile) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
});
}
} catch (e) {
auth.changeProcessText(
S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
});
} else {
try {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet);
hideProgressText();
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 (DeviceInfo.instance.isMobile) {
Navigator.of(context).pop();
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
});
}
} catch (e) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
});
}
Future<void> _removeWallet(WalletListItem wallet) async {
if (widget.walletListViewModel.checkIfAuthRequired()) {
await Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (!isAuthenticatedSuccessfully) {
return;
}
_onSuccessfulAuth(wallet, auth);
});
} else {
_onSuccessfulAuth(wallet, null);
}
widget.authService.authenticateAction(context,
onAuthSuccess: (isAuthenticatedSuccessfully) async {
if (!isAuthenticatedSuccessfully) {
return;
}
_onSuccessfulAuth(wallet);
});
}
void _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth) async {
void _onSuccessfulAuth(WalletListItem wallet) async {
bool confirmed = false;
await showPopUp<void>(
context: context,
@ -275,31 +258,23 @@ class WalletListBodyState extends State<WalletListBody> {
if (confirmed) {
try {
auth != null
? auth.changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name))
: changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name));
changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name));
await widget.walletListViewModel.remove(wallet);
hideProgressText();
} catch (e) {
auth != null
? auth.changeProcessText(
S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()),
)
: changeProcessText(
S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()),
);
changeProcessText(
S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()),
);
}
}
auth?.close();
}
void changeProcessText(String text) {
_progressBar = createBar<void>(text, duration: null)..show(context);
}
void hideProgressText() {
Future.delayed(Duration(milliseconds: 50), () {
Future<void> hideProgressText() async {
await Future.delayed(Duration(milliseconds: 50), () {
_progressBar?.dismiss();
_progressBar = null;
});