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

Godwin Asuquo committed Apr 20, 2023 at 03:54 UTC efef30f8eba78ebd8ab5933098701bb2e3138dc1
5 files changed +103 -112
lib/core/auth_service.dart
+38 -1
@@ -1,10 +1,12 @@
1 +import 'package:cake_wallet/routes.dart';
2 +import 'package:cake_wallet/src/screens/auth/auth_page.dart';
3 +import 'package:flutter/material.dart';
4 import 'package:mobx/mobx.dart';
5 import 'package:flutter_secure_storage/flutter_secure_storage.dart';
6 import 'package:shared_preferences/shared_preferences.dart';
7 import 'package:cake_wallet/entities/preferences_key.dart';
8 import 'package:cake_wallet/entities/secret_store_key.dart';
9 import 'package:cake_wallet/entities/encrypt.dart';
7 -import 'package:cake_wallet/di.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
11
12 class AuthService with Store {
@@ -14,6 +16,12 @@ class AuthService with Store {
16 required this.settingsStore,
17 });
18
19 + static const List<String> _alwaysAuthenticateRoutes = [
20 + Routes.showKeys,
21 + Routes.backup,
22 + Routes.setupPin,
23 + ];
24 +
25 final FlutterSecureStorage secureStorage;
26 final SharedPreferences sharedPreferences;
27 final SettingsStore settingsStore;
@@ -66,4 +74,33 @@ class AuthService with Store {
74
75 return timeDifference.inMinutes;
76 }
77 +
78 + Future<void> authenticateAction(BuildContext context,
79 + {Function(bool)? onAuthSuccess, String? route, Object? arguments}) async {
80 + assert(route != null || onAuthSuccess != null,
81 + 'Either route or onAuthSuccess param must be passed.');
82 + if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) {
83 + if (onAuthSuccess != null) {
84 + onAuthSuccess(true);
85 + } else {
86 + Navigator.of(context).pushNamed(
87 + route ?? '',
88 + arguments: arguments,
89 + );
90 + }
91 + return;
92 + }
93 + Navigator.of(context).pushNamed(Routes.auth,
94 + arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
95 + if (!isAuthenticatedSuccessfully) {
96 + onAuthSuccess?.call(false);
97 + return;
98 + }
99 + if (onAuthSuccess != null) {
100 + auth.close().then((value) => onAuthSuccess.call(true));
101 + } else {
102 + auth.close(route: route, arguments: arguments);
103 + }
104 + });
105 + }
106 }
lib/di.dart
+3 -3
@@ -499,7 +499,7 @@ Future setup(
499 }
500
501 getIt.registerFactory(() =>
502 - WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>()));
502 + WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>(), authService: getIt.get<AuthService>(),));
503
504 getIt.registerFactory(() {
505 final wallet = getIt.get<AppStore>().wallet!;
@@ -593,7 +593,7 @@ Future setup(
593
594 getIt.registerFactory(() => ConnectionSyncPage(getIt.get<NodeListViewModel>(), getIt.get<DashboardViewModel>()));
595
596 - getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>()));
596 + getIt.registerFactory(() => SecurityBackupPage(getIt.get<SecuritySettingsViewModel>(), getIt.get<AuthService>()));
597
598 getIt.registerFactory(() => PrivacyPage(getIt.get<PrivacySettingsViewModel>()));
599
@@ -926,7 +926,7 @@ Future setup(
926 wallet: getIt.get<AppStore>().wallet!)
927 );
928
929 - getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>()));
929 + getIt.registerFactory(() => DesktopWalletSelectionDropDown(getIt.get<WalletListViewModel>(), getIt.get<AuthService>()));
930
931 getIt.registerFactory(() => DesktopSidebarViewModel());
932
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+12 -22
@@ -1,8 +1,9 @@
1 import 'package:another_flushbar/flushbar.dart';
2 +import 'package:cake_wallet/core/auth_service.dart';
3 +import 'package:cake_wallet/di.dart';
4 import 'package:cake_wallet/entities/desktop_dropdown_item.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/routes.dart';
5 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
7 import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart';
8 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
9 import 'package:cake_wallet/utils/show_bar.dart';
@@ -16,8 +17,10 @@ import 'package:flutter_mobx/flutter_mobx.dart';
17
18 class DesktopWalletSelectionDropDown extends StatefulWidget {
19 final WalletListViewModel walletListViewModel;
20 + final AuthService _authService;
21
20 - DesktopWalletSelectionDropDown(this.walletListViewModel, {Key? key}) : super(key: key);
22 + DesktopWalletSelectionDropDown(this.walletListViewModel, this._authService, {Key? key})
23 + : super(key: key);
24
25 @override
26 State<DesktopWalletSelectionDropDown> createState() => _DesktopWalletSelectionDropDownState();
@@ -140,25 +143,12 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
143 }
144
145 Future<void> _loadWallet(WalletListItem wallet) async {
143 - if (await widget.walletListViewModel.checkIfAuthRequired()) {
144 - await Navigator.of(context).pushNamed(Routes.auth,
145 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
146 - if (!isAuthenticatedSuccessfully) {
147 - return;
148 - }
149 -
150 - try {
151 - auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
152 - await widget.walletListViewModel.loadWallet(wallet);
153 - auth.hideProgressText();
154 - auth.close();
155 - setState(() {});
156 - } catch (e) {
157 - auth.changeProcessText(
158 - S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
159 - }
160 - });
161 - } else {
146 + widget._authService.authenticateAction(context,
147 + onAuthSuccess: (isAuthenticatedSuccessfully) async {
148 + if (!isAuthenticatedSuccessfully) {
149 + return;
150 + }
151 +
152 try {
153 changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
154 await widget.walletListViewModel.loadWallet(wallet);
@@ -167,7 +157,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
157 } catch (e) {
158 changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
159 }
170 - }
160 + });
161 }
162
163 void _navigateToCreateWallet() {
lib/src/screens/settings/security_backup_page.dart
+17 -28
@@ -1,6 +1,6 @@
1 +import 'package:cake_wallet/core/auth_service.dart';
2 import 'package:cake_wallet/entities/pin_code_required_duration.dart';
3 import 'package:cake_wallet/routes.dart';
3 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
4 import 'package:cake_wallet/src/screens/base_page.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
@@ -13,7 +13,9 @@ import 'package:flutter/material.dart';
13 import 'package:flutter_mobx/flutter_mobx.dart';
14
15 class SecurityBackupPage extends BasePage {
16 - SecurityBackupPage(this._securitySettingsViewModel);
16 + SecurityBackupPage(this._securitySettingsViewModel, this._authService);
17 +
18 + final AuthService _authService;
19
20 @override
21 String get title => S.current.security_and_backup;
@@ -27,35 +29,24 @@ class SecurityBackupPage extends BasePage {
29 child: Column(mainAxisSize: MainAxisSize.min, children: [
30 SettingsCellWithArrow(
31 title: S.current.show_keys,
30 - handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
31 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
32 - if (isAuthenticatedSuccessfully) {
33 - auth.close(route: Routes.showKeys);
34 - }
35 - }),
32 + handler: (_) => _authService.authenticateAction(context, route: Routes.showKeys),
33 ),
34 StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
35 SettingsCellWithArrow(
36 title: S.current.create_backup,
40 - handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
41 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
42 - if (isAuthenticatedSuccessfully) {
43 - auth.close(route: Routes.backup);
44 - }
45 - }),
37 + handler: (_) => _authService.authenticateAction(context, route: Routes.backup),
38 ),
39 StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
40 SettingsCellWithArrow(
49 - title: S.current.settings_change_pin,
50 - handler: (_) => Navigator.of(context).pushNamed(Routes.auth,
51 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
52 - auth.close(
53 - route: isAuthenticatedSuccessfully ? Routes.setupPin : null,
54 - arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
55 - setupPinContext.close();
56 - },
57 - );
58 - })),
41 + title: S.current.settings_change_pin,
42 + handler: (_) => _authService.authenticateAction(
43 + context,
44 + route: Routes.setupPin,
45 + arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
46 + setupPinContext.close();
47 + },
48 + ),
49 + ),
50 StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
51 Observer(builder: (_) {
52 return SettingsSwitcherCell(
@@ -63,8 +54,8 @@ class SecurityBackupPage extends BasePage {
54 value: _securitySettingsViewModel.allowBiometricalAuthentication,
55 onValueChange: (BuildContext context, bool value) {
56 if (value) {
66 - Navigator.of(context).pushNamed(Routes.auth,
67 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
57 + _authService.authenticateAction(context,
58 + onAuthSuccess: (isAuthenticatedSuccessfully) async {
59 if (isAuthenticatedSuccessfully) {
60 if (await _securitySettingsViewModel.biometricAuthenticated()) {
61 _securitySettingsViewModel
@@ -74,8 +65,6 @@ class SecurityBackupPage extends BasePage {
65 _securitySettingsViewModel
66 .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
67 }
77 -
78 - auth.close();
68 });
69 } else {
70 _securitySettingsViewModel.setAllowBiometricalAuthentication(value);
lib/src/screens/wallet_list/wallet_list_page.dart
+33 -58
@@ -1,4 +1,4 @@
1 -import 'package:cake_wallet/src/screens/auth/auth_page.dart';
1 +import 'package:cake_wallet/core/auth_service.dart';
2 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
3 import 'package:cake_wallet/utils/device_info.dart';
4 import 'package:cake_wallet/utils/show_bar.dart';
@@ -19,18 +19,21 @@ import 'package:flutter_slidable/flutter_slidable.dart';
19 import 'package:cake_wallet/wallet_type_utils.dart';
20
21 class WalletListPage extends BasePage {
22 - WalletListPage({required this.walletListViewModel});
22 + WalletListPage({required this.walletListViewModel, required this.authService});
23
24 final WalletListViewModel walletListViewModel;
25 + final AuthService authService;
26
27 @override
27 - Widget body(BuildContext context) => WalletListBody(walletListViewModel: walletListViewModel);
28 + Widget body(BuildContext context) =>
29 + WalletListBody(walletListViewModel: walletListViewModel, authService: authService);
30 }
31
32 class WalletListBody extends StatefulWidget {
31 - WalletListBody({required this.walletListViewModel});
33 + WalletListBody({required this.walletListViewModel, required this.authService});
34
35 final WalletListViewModel walletListViewModel;
36 + final AuthService authService;
37
38 @override
39 WalletListBodyState createState() => WalletListBodyState();
@@ -129,7 +132,8 @@ class WalletListBodyState extends State<WalletListBody> {
132 fontSize: 22,
133 fontWeight: FontWeight.w500,
134 color: Theme.of(context)
132 - .primaryTextTheme.headline6!
135 + .primaryTextTheme
136 + .headline6!
137 .color!),
138 )
139 ],
@@ -201,61 +205,40 @@ class WalletListBodyState extends State<WalletListBody> {
205 }
206
207 Future<void> _loadWallet(WalletListItem wallet) async {
204 - if (await widget.walletListViewModel.checkIfAuthRequired()) {
205 - await Navigator.of(context).pushNamed(Routes.auth,
206 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
207 - if (!isAuthenticatedSuccessfully) {
208 - return;
209 - }
208 + await widget.authService.authenticateAction(context,
209 + onAuthSuccess: (isAuthenticatedSuccessfully) async {
210 + if (!isAuthenticatedSuccessfully) {
211 + return;
212 + }
213
211 - try {
212 - auth.changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
213 - await widget.walletListViewModel.loadWallet(wallet);
214 - auth.hideProgressText();
215 - auth.close();
216 - // only pop the wallets route in mobile as it will go back to dashboard page
217 - // in desktop platforms the navigation tree is different
218 - if (DeviceInfo.instance.isMobile) {
219 - WidgetsBinding.instance.addPostFrameCallback((_) {
220 - Navigator.of(context).pop();
221 - });
222 - }
223 - } catch (e) {
224 - auth.changeProcessText(
225 - S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
226 - }
227 - });
228 - } else {
214 try {
215 changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
216 await widget.walletListViewModel.loadWallet(wallet);
232 - hideProgressText();
217 + await hideProgressText();
218 // only pop the wallets route in mobile as it will go back to dashboard page
219 // in desktop platforms the navigation tree is different
220 if (DeviceInfo.instance.isMobile) {
236 - Navigator.of(context).pop();
221 + WidgetsBinding.instance.addPostFrameCallback((_) {
222 + Navigator.of(context).pop();
223 + });
224 }
225 } catch (e) {
226 changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
227 }
241 - }
228 + });
229 }
230
231 Future<void> _removeWallet(WalletListItem wallet) async {
245 - if (widget.walletListViewModel.checkIfAuthRequired()) {
246 - await Navigator.of(context).pushNamed(Routes.auth,
247 - arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
248 - if (!isAuthenticatedSuccessfully) {
249 - return;
250 - }
251 - _onSuccessfulAuth(wallet, auth);
252 - });
253 - } else {
254 - _onSuccessfulAuth(wallet, null);
255 - }
232 + widget.authService.authenticateAction(context,
233 + onAuthSuccess: (isAuthenticatedSuccessfully) async {
234 + if (!isAuthenticatedSuccessfully) {
235 + return;
236 + }
237 + _onSuccessfulAuth(wallet);
238 + });
239 }
240
258 - void _onSuccessfulAuth(WalletListItem wallet, AuthPageState? auth) async {
241 + void _onSuccessfulAuth(WalletListItem wallet) async {
242 bool confirmed = false;
243 await showPopUp<void>(
244 context: context,
@@ -275,31 +258,23 @@ class WalletListBodyState extends State<WalletListBody> {
258
259 if (confirmed) {
260 try {
278 - auth != null
279 - ? auth.changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name))
280 - : changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name));
261 + changeProcessText(S.of(context).wallet_list_removing_wallet(wallet.name));
262 await widget.walletListViewModel.remove(wallet);
263 hideProgressText();
264 } catch (e) {
284 - auth != null
285 - ? auth.changeProcessText(
286 - S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()),
287 - )
288 - : changeProcessText(
289 - S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()),
290 - );
265 + changeProcessText(
266 + S.of(context).wallet_list_failed_to_remove(wallet.name, e.toString()),
267 + );
268 }
269 }
293 -
294 - auth?.close();
270 }
271
272 void changeProcessText(String text) {
273 _progressBar = createBar<void>(text, duration: null)..show(context);
274 }
275
301 - void hideProgressText() {
302 - Future.delayed(Duration(milliseconds: 50), () {
276 + Future<void> hideProgressText() async {
277 + await Future.delayed(Duration(milliseconds: 50), () {
278 _progressBar?.dismiss();
279 _progressBar = null;
280 });