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
});