dev
dart 280 lines 8.93 KB
Raw
1 import 'dart:async';
2 import 'dart:io';
3
4 import 'package:cake_wallet/core/reset_service.dart';
5 import 'package:cake_wallet/core/secure_storage.dart';
6 import 'package:cake_wallet/core/totp_request_details.dart';
7 import 'package:cake_wallet/main.dart';
8 import 'package:cake_wallet/routes.dart';
9 import 'package:cake_wallet/src/screens/auth/auth_page.dart';
10 import 'package:cake_wallet/store/app_store.dart' show AppStore;
11 import 'package:cake_wallet/store/authentication_store.dart';
12 import 'package:cw_core/db/sqlite.dart';
13 import 'package:cw_core/root_dir.dart';
14 import 'package:cw_core/utils/print_verbose.dart';
15 import 'package:cw_core/wallet_info.dart';
16 import 'package:flutter/material.dart';
17 import 'package:mobx/mobx.dart';
18 import 'package:shared_preferences/shared_preferences.dart';
19 import 'package:cake_wallet/entities/preferences_key.dart';
20 import 'package:cake_wallet/entities/secret_store_key.dart';
21 import 'package:cake_wallet/entities/encrypt.dart';
22 import 'package:cake_wallet/store/settings_store.dart';
23
24 import '../src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
25
26 class AuthService with Store {
27 AuthService({
28 required this.secureStorage,
29 required this.sharedPreferences,
30 required this.settingsStore,
31 required this.authenticationStore,
32 required this.appStore,
33 required this.resetService,
34 required this.walletList,
35 });
36
37 static const List<String> _alwaysAuthenticateRoutes = [
38 Routes.showKeys,
39 Routes.backup,
40 Routes.setupPin,
41 Routes.setup_2faPage,
42 Routes.modify2FAPage,
43 Routes.newWallet,
44 Routes.newWalletType,
45 Routes.addressBookAddContact,
46 Routes.restoreOptions,
47 Routes.securityBackupDuressPin,
48 ];
49
50 final SecureStorage secureStorage;
51 final SharedPreferences sharedPreferences;
52 final SettingsStore settingsStore;
53 final AuthenticationStore authenticationStore;
54 final AppStore appStore;
55 final ResetService resetService;
56 final List<WalletInfo> walletList;
57
58 Future<void> setPassword(String password) async {
59 final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
60 final encodedPassword = encodedPinCode(pin: password);
61 await secureStorage.write(key: key, value: encodedPassword);
62 }
63
64 Future<void> setDuressPin(String pin) async {
65 final key = generateStoreKeyFor(key: SecretStoreKey.duressPinCodePassword);
66 final encodedPin = encodedPinCode(pin: pin);
67 await secureStorage.write(key: key, value: encodedPin);
68 }
69
70 Future<void> clearDuressPin() async {
71 final key = generateStoreKeyFor(key: SecretStoreKey.duressPinCodePassword);
72 await secureStorage.delete(key: key);
73 }
74
75 Future<bool> canAuthenticate() async {
76 final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
77 final walletName = sharedPreferences.getString(PreferencesKey.currentWalletName) ?? '';
78 var password = '';
79
80 try {
81 password = await secureStorage.read(key: key) ?? '';
82 } catch (e) {
83 printV(e);
84 }
85
86 return walletName.isNotEmpty && password.isNotEmpty;
87 }
88
89 Future<bool> authenticate(String pin) async {
90 final regularKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
91 final encodedRegularPin = await secureStorage.read(key: regularKey);
92 final decodedRegularPin = decodedPinCode(pin: encodedRegularPin!);
93
94 if (decodedRegularPin == pin) {
95 return true;
96 }
97
98 // Check for duress pin
99 final duressKey = generateStoreKeyFor(key: SecretStoreKey.duressPinCodePassword);
100 final encodedDuressPin = await secureStorage.read(key: duressKey);
101
102 String? decodedDuressPin;
103 if (encodedDuressPin != null && encodedDuressPin.isNotEmpty) {
104 try {
105 decodedDuressPin = decodedPinCode(pin: encodedDuressPin);
106 } catch (e) {
107 printV("Failed to decode duress pin: $e");
108 }
109 }
110
111 if (decodedDuressPin == pin) {
112 await _handleDuressLogin(secureStorage, sharedPreferences, authenticationStore, appStore,
113 resetService, walletList);
114
115 navigatorKey.currentState?.pushNamedAndRemoveUntil(
116 Routes.welcome,
117 (route) => false,
118 );
119
120 return false;
121 }
122 return false;
123 }
124
125 void saveLastAuthTime() {
126 int timestamp = DateTime.now().millisecondsSinceEpoch;
127 secureStorage.write(key: SecureKey.lastAuthTimeMilliseconds, value: timestamp.toString());
128 }
129
130 Future<bool> requireAuth() async {
131 final timestamp =
132 int.tryParse(await secureStorage.read(key: SecureKey.lastAuthTimeMilliseconds) ?? '0');
133 final duration = _durationToRequireAuth(timestamp ?? 0);
134 final requiredPinInterval = settingsStore.pinTimeOutDuration;
135
136 return duration >= requiredPinInterval.value;
137 }
138
139 int _durationToRequireAuth(int timestamp) {
140 DateTime before = DateTime.fromMillisecondsSinceEpoch(timestamp);
141 DateTime now = DateTime.now();
142 Duration timeDifference = now.difference(before);
143
144 return timeDifference.inMinutes;
145 }
146
147 Future<void> authenticateAction(BuildContext context,
148 {Function(bool)? onAuthSuccess,
149 String? route,
150 Object? arguments,
151 required bool conditionToDetermineIfToUse2FA}) async {
152 assert(route != null || onAuthSuccess != null,
153 'Either route or onAuthSuccess param must be passed.');
154
155 if (!conditionToDetermineIfToUse2FA) {
156 if (!(await requireAuth()) && !_alwaysAuthenticateRoutes.contains(route)) {
157 if (onAuthSuccess != null) {
158 onAuthSuccess(true);
159 } else {
160 Navigator.of(context).pushNamed(
161 route ?? '',
162 arguments: arguments,
163 );
164 }
165 return;
166 }
167 }
168
169 if (context.mounted) {
170 Navigator.of(context).pushNamed(Routes.auth,
171 arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
172 if (!isAuthenticatedSuccessfully) {
173 onAuthSuccess?.call(false);
174 return;
175 } else {
176 if (settingsStore.useTOTP2FA && conditionToDetermineIfToUse2FA) {
177 auth.close(
178 route: Routes.totpAuthCodePage,
179 arguments: TotpAuthArgumentsModel(
180 isForSetup: !settingsStore.useTOTP2FA,
181 onTotpAuthenticationFinished:
182 (bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) async {
183 if (!isAuthenticatedSuccessfully) {
184 onAuthSuccess?.call(false);
185 return;
186 }
187 if (onAuthSuccess != null) {
188 totpAuth.close().then((value) => onAuthSuccess.call(true));
189 } else {
190 totpAuth.close(route: route, arguments: arguments);
191 }
192 },
193 ),
194 );
195 } else {
196 if (onAuthSuccess != null) {
197 auth.close().then((value) => onAuthSuccess.call(true));
198 } else {
199 auth.close(route: route, arguments: arguments);
200 }
201 }
202 }
203 });
204 }
205 }
206 }
207
208 Future<void> _handleDuressLogin(
209 SecureStorage secureStorage,
210 SharedPreferences sharedPreferences,
211 AuthenticationStore authenticationStore,
212 AppStore appStore,
213 ResetService resetService,
214 List<WalletInfo> wallets,
215 ) async {
216 printV('[DURESS] START FULL WIPE PROCESS');
217
218 // Close wallet instance if opened
219 try {
220 if (appStore.wallet != null) {
221 await appStore.wallet!.close();
222 }
223 appStore.wallet = null;
224 } catch (e) {
225 printV('[DURESS] Failed to close wallet instance: $e');
226 }
227
228 // Reset shared preference flag for new install
229 try {
230 await sharedPreferences.setBool(PreferencesKey.isNewInstall, true);
231 printV('[DURESS] isNewInstall flag set to true');
232 } catch (e) {
233 printV('[DURESS] Failed to set isNewInstall: $e');
234 }
235
236 // Reset auth data
237 await resetService.resetAuthDataOnNewInstall(sharedPreferences);
238 printV('[DURESS] Authentication data reset');
239
240 // wipe secure storage
241 try {
242 await secureStorage.deleteAll();
243 printV('[DURESS] SecureStorage wiped');
244 } catch (e) {
245 printV('[DURESS] SecureStorage wipe failed: $e');
246 }
247
248 // Delete wallet directories
249 try {
250 final appDir = await getAppDir();
251 final walletsDir = Directory('${appDir.path}/wallets');
252
253 if (walletsDir.existsSync()) {
254 walletsDir.deleteSync(recursive: true);
255 printV('[DURESS] Wallet directories deleted');
256 }
257 } catch (e) {
258 printV('[DURESS] Failed deleting wallet directories: $e');
259 }
260
261 // Wipe wallet-related database tables
262 try {
263 await db!.transaction((txn) async {
264 await txn.delete(WalletInfoAddressInfo.tableName);
265 await txn.delete(WalletInfoAddressMap.tableName);
266 await txn.delete(WalletInfoAddress.tableName);
267 await txn.delete(DerivationInfo.tableName);
268 await txn.delete(WalletInfo.tableName);
269 });
270 printV('[DURESS] SQLite wallet tables wiped');
271 } catch (e) {
272 printV('[DURESS] SQLite wipe failed: $e');
273 }
274
275 //Force app state to uninitialized
276 authenticationStore.state = AuthenticationState.uninitialized;
277 printV('[DURESS] Authentication state set to "uninitialized"');
278
279 printV('[DURESS] FULL WIPE COMPLETED');
280 }