bio auth on mac + package updates for 3.19.3/5 (#1398)

* bio auth mac fix

* remove comment and change duration from 2 to 0

* cherry pick previous changes

* workaround for secure storage bug on mac

* bump version to 3.19.5 (because breez will need this version anyways)

* some code cleanup

* some changess didn't get saved

* just documenting the issue [skip ci]

* undo accidental removal + minor code cleanup

* merge conflicts

* Minor UI change [skip ci]

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Matthew Fosse
2024-05-06 22:55:05 +03:00
committed by GitHub
co-authored by Omar Hatem
parent 3f3cd10158
commit e5be737236
32 changed files with 730 additions and 555 deletions
+6 -7
View File
@@ -42,12 +42,7 @@ class AuthService with Store {
Future<void> setPassword(String password) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: password);
// secure storage has a weird bug on macOS, where overwriting a key doesn't work, unless
// we delete what's there first:
if (Platform.isMacOS) {
await secureStorage.delete(key: key);
}
await secureStorage.write(key: key, value: encodedPassword);
await writeSecureStorage(secureStorage, key: key, value: encodedPassword);
}
Future<bool> canAuthenticate() async {
@@ -74,7 +69,11 @@ class AuthService with Store {
void saveLastAuthTime() {
int timestamp = DateTime.now().millisecondsSinceEpoch;
secureStorage.write(key: SecureKey.lastAuthTimeMilliseconds, value: timestamp.toString());
writeSecureStorage(
secureStorage,
key: SecureKey.lastAuthTimeMilliseconds,
value: timestamp.toString(),
);
}
Future<bool> requireAuth() async {
+8 -9
View File
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -275,7 +276,7 @@ class BackupService {
if (currentTransactionPriorityKeyLegacy != null)
await _sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy);
if (currentBitcoinElectrumSererId != null)
await _sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId);
@@ -373,16 +374,15 @@ class BackupService {
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.delete(key: backupPasswordKey);
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
await writeSecureStorage(_flutterSecureStorage, key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.delete(key: pinCodeKey);
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
await writeSecureStorage(_flutterSecureStorage,
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
}
@@ -401,16 +401,15 @@ class BackupService {
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.delete(key: backupPasswordKey);
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
await writeSecureStorage(_flutterSecureStorage, key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.delete(key: pinCodeKey);
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
await writeSecureStorage(_flutterSecureStorage,
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
}
+1 -2
View File
@@ -20,8 +20,7 @@ class KeyService {
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = encodeWalletPassword(password: password);
await _secureStorage.delete(key: key);
await _secureStorage.write(key: key, value: encodedPassword);
await writeSecureStorage(_secureStorage, key: key, value: encodedPassword);
}
Future<void> deleteWalletPassword({required String walletName}) async {
+11
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
// For now, we can create a utility function to handle this.
//
@@ -25,3 +26,13 @@ Future<String?> readSecureStorage(FlutterSecureStorage secureStorage, String key
return result;
}
Future<void> writeSecureStorage(FlutterSecureStorage secureStorage,
{required String key, required String value}) async {
// delete the value before writing on macOS because of a weird bug
// https://github.com/mogol/flutter_secure_storage/issues/581
if (Platform.isMacOS) {
await secureStorage.delete(key: key);
}
await secureStorage.write(key: key, value: value);
}
+12 -15
View File
@@ -1,32 +1,29 @@
import 'package:local_auth/local_auth.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_local_authentication/flutter_local_authentication.dart';
class BiometricAuth {
final _localAuth = LocalAuthentication();
final _flutterLocalAuthenticationPlugin = FlutterLocalAuthentication();
Future<bool> isAuthenticated() async {
try {
return await _localAuth.authenticate(
localizedReason: S.current.biometric_auth_reason,
options: AuthenticationOptions(
biometricOnly: true,
useErrorDialogs: true,
stickyAuth: false));
} on PlatformException catch (e) {
final authenticated = await _flutterLocalAuthenticationPlugin.authenticate();
return authenticated;
} catch (e) {
print(e);
}
return false;
}
Future<bool> canCheckBiometrics() async {
bool canAuthenticate;
try {
return await _localAuth.canCheckBiometrics;
} on PlatformException catch (e) {
print(e);
canAuthenticate = await _flutterLocalAuthenticationPlugin.canAuthenticate();
await _flutterLocalAuthenticationPlugin.setTouchIDAuthenticationAllowableReuseDuration(0);
} catch (error) {
print("Exception checking support. $error");
canAuthenticate = false;
}
return false;
return canAuthenticate;
}
}
+3 -2
View File
@@ -1,5 +1,6 @@
import 'dart:io';
import 'dart:convert';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:collection/collection.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -147,8 +148,8 @@ Future<void> ios_migrate_pin() async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: pinPassword);
await flutterSecureStorage.delete(key: key);
await flutterSecureStorage.write(key: key, value: encodedPassword);
await writeSecureStorage(flutterSecureStorage, key: key, value: encodedPassword);
await prefs.setBool('ios_migration_pin_completed', true);
}
+2 -2
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cw_core/cake_hive.dart';
@@ -10,8 +11,7 @@ Future<List<int>> getEncryptionKey(
key = CakeHive.generateSecureKey();
final keyStringified = key.join(',');
String storageKey = 'transactionDescriptionsBoxKey';
await secureStorage.delete(key: storageKey);
await secureStorage.write(key: storageKey, value: keyStringified);
await writeSecureStorage(secureStorage, key: storageKey, value: keyStringified);
} else {
key = stringifiedKey.split(',').map((i) => int.parse(i)).toList();
}
+64
View File
@@ -751,6 +751,50 @@ class HaMaterialLocalizations extends GlobalMaterialLocalizations {
@override
String get scrimOnTapHintRaw => "Scrip on Tap";
@override
// TODO: implement collapsedHint
String get collapsedHint => "collapsedHint";
@override
// TODO: implement expandedHint
String get expandedHint => "expandedHint";
@override
// TODO: implement expansionTileCollapsedHint
String get expansionTileCollapsedHint => "expansionTileCollapsedHint";
@override
// TODO: implement expansionTileCollapsedTapHint
String get expansionTileCollapsedTapHint => "expansionTileCollapsedTapHint";
@override
// TODO: implement expansionTileExpandedHint
String get expansionTileExpandedHint => "expansionTileExpandedHint";
@override
// TODO: implement expansionTileExpandedTapHint
String get expansionTileExpandedTapHint => "expansionTileExpandedTapHint";
@override
// TODO: implement scanTextButtonLabel
String get scanTextButtonLabel => "scanTextButtonLabel";
@override
// TODO: implement lookUpButtonLabel
String get lookUpButtonLabel => "lookUpButtonLabel";
@override
// TODO: implement menuDismissLabel
String get menuDismissLabel => "menuDismissLabel";
@override
// TODO: implement searchWebButtonLabel
String get searchWebButtonLabel => "searchWebButtonLabel";
@override
// TODO: implement shareButtonLabel
String get shareButtonLabel => "shareButtonLabel";
}
/// Cupertino Support
@@ -955,4 +999,24 @@ class HaCupertinoLocalizations extends GlobalCupertinoLocalizations {
@override
String get noSpellCheckReplacementsLabel => "";
@override
// TODO: implement clearButtonLabel
String get clearButtonLabel => "clearButtonLabel";
@override
// TODO: implement lookUpButtonLabel
String get lookUpButtonLabel => "lookUpButtonLabel";
@override
// TODO: implement menuDismissLabel
String get menuDismissLabel => "menuDismissLabel";
@override
// TODO: implement searchWebButtonLabel
String get searchWebButtonLabel => "searchWebButtonLabel";
@override
// TODO: implement shareButtonLabel
String get shareButtonLabel => "shareButtonLabel";
}
File diff suppressed because it is too large Load Diff
@@ -12,7 +12,6 @@ final _settingsNavigatorKey = GlobalKey<NavigatorState>();
class DesktopSettingsPage extends StatefulWidget {
const DesktopSettingsPage({super.key});
@override
State<DesktopSettingsPage> createState() => _DesktopSettingsPageState();
}
@@ -33,22 +32,21 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
child: Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 4),
child: Text(
S.current.settings,
style: textXLarge(),
),
),
Expanded(
child: Row(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 24, 24, 4),
child: Text(
S.current.settings,
style: textXLarge(),
),
),
Expanded(
flex: 1,
child: ListView.separated(
padding: EdgeInsets.only(top: 0),
itemBuilder: (_, index) {
@@ -78,27 +76,27 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
itemCount: itemCount,
),
),
Flexible(
flex: 2,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: _settingsNavigatorKey,
initialRoute: Routes.empty_no_route,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes:
(NavigatorState navigator, String initialRouteName) {
return [
navigator
.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
)
],
),
),
Flexible(
flex: 2,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Navigator(
key: _settingsNavigatorKey,
initialRoute: Routes.empty_no_route,
onGenerateRoute: (settings) => Router.createRoute(settings),
onGenerateInitialRoutes:
(NavigatorState navigator, String initialRouteName) {
return [
navigator
.widget.onGenerateRoute!(RouteSettings(name: initialRouteName))!
];
},
),
),
)
],
),
),
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/routes.dart';
@@ -58,7 +60,7 @@ class SecurityBackupPage extends BasePage {
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
),
),
if (DeviceInfo.instance.isMobile)
if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux)
Observer(builder: (_) {
return SettingsSwitcherCell(
title: S.current.settings_allow_biometrical_authentication,
+2 -2
View File
@@ -146,7 +146,7 @@ class ConnectScreen extends StatelessWidget {
ElevatedButton(
onPressed: connect,
style: ElevatedButton.styleFrom(
primary: Colors.blue,
// primary: Colors.blue,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
@@ -211,7 +211,7 @@ class DisconnectScreen extends StatelessWidget {
ElevatedButton(
onPressed: disconnect,
style: ElevatedButton.styleFrom(
primary: Colors.red,
// primary: Colors.red,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@@ -58,7 +59,6 @@ class ChatwootWidgetState extends State<ChatwootWidget> {
}
Future<void> storeCookie(String value) async {
await widget.secureStorage.delete(key: COOKIE_KEY);
await widget.secureStorage.write(key: COOKIE_KEY, value: value);
await writeSecureStorage(widget.secureStorage, key: COOKIE_KEY, value: value);
}
}
@@ -1,8 +1,14 @@
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
class Annotation extends Comparable<Annotation> {
extension Compare<T> on Comparable<T> {
bool operator <=(T other) => compareTo(other) <= 0;
bool operator >=(T other) => compareTo(other) >= 0;
bool operator <(T other) => compareTo(other) < 0;
bool operator >(T other) => compareTo(other) > 0;
}
class Annotation implements Comparable<Annotation> {
Annotation({required this.range, required this.style});
final TextRange range;
@@ -12,7 +18,7 @@ class Annotation extends Comparable<Annotation> {
int compareTo(Annotation other) => range.start.compareTo(other.range.start);
}
class TextAnnotation extends Comparable<TextAnnotation> {
class TextAnnotation implements Comparable<TextAnnotation> {
TextAnnotation({required this.text, required this.style});
final TextStyle style;
+21 -16
View File
@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
@@ -434,79 +435,83 @@ abstract class SettingsStoreBase with Store {
// secure storage keys:
reaction(
(_) => allowBiometricalAuthentication,
(bool biometricalAuthentication) => secureStorage.write(
(bool biometricalAuthentication) => writeSecureStorage(secureStorage,
key: SecureKey.allowBiometricalAuthenticationKey,
value: biometricalAuthentication.toString()));
reaction(
(_) => selectedCake2FAPreset,
(Cake2FAPresetsOptions selectedCake2FAPreset) => secureStorage.write(
(Cake2FAPresetsOptions selectedCake2FAPreset) => writeSecureStorage(secureStorage,
key: SecureKey.selectedCake2FAPreset,
value: selectedCake2FAPreset.serialize().toString()));
reaction(
(_) => shouldRequireTOTP2FAForAccessingWallet,
(bool requireTOTP2FAForAccessingWallet) => secureStorage.write(
(bool requireTOTP2FAForAccessingWallet) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForAccessingWallet,
value: requireTOTP2FAForAccessingWallet.toString()));
reaction(
(_) => shouldRequireTOTP2FAForSendsToContact,
(bool requireTOTP2FAForSendsToContact) => secureStorage.write(
(bool requireTOTP2FAForSendsToContact) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForSendsToContact,
value: requireTOTP2FAForSendsToContact.toString()));
reaction(
(_) => shouldRequireTOTP2FAForSendsToNonContact,
(bool requireTOTP2FAForSendsToNonContact) => secureStorage.write(
(bool requireTOTP2FAForSendsToNonContact) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForSendsToNonContact,
value: requireTOTP2FAForSendsToNonContact.toString()));
reaction(
(_) => shouldRequireTOTP2FAForSendsToInternalWallets,
(bool requireTOTP2FAForSendsToInternalWallets) => secureStorage.write(
(bool requireTOTP2FAForSendsToInternalWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForSendsToInternalWallets,
value: requireTOTP2FAForSendsToInternalWallets.toString()));
reaction(
(_) => shouldRequireTOTP2FAForExchangesToInternalWallets,
(bool requireTOTP2FAForExchangesToInternalWallets) => secureStorage.write(
(bool requireTOTP2FAForExchangesToInternalWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForExchangesToInternalWallets,
value: requireTOTP2FAForExchangesToInternalWallets.toString()));
reaction(
(_) => shouldRequireTOTP2FAForExchangesToExternalWallets,
(bool requireTOTP2FAForExchangesToExternalWallets) => secureStorage.write(
(bool requireTOTP2FAForExchangesToExternalWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForExchangesToExternalWallets,
value: requireTOTP2FAForExchangesToExternalWallets.toString()));
reaction(
(_) => shouldRequireTOTP2FAForAddingContacts,
(bool requireTOTP2FAForAddingContacts) => secureStorage.write(
(bool requireTOTP2FAForAddingContacts) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForAddingContacts,
value: requireTOTP2FAForAddingContacts.toString()));
reaction(
(_) => shouldRequireTOTP2FAForCreatingNewWallets,
(bool requireTOTP2FAForCreatingNewWallets) => secureStorage.write(
(bool requireTOTP2FAForCreatingNewWallets) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForCreatingNewWallets,
value: requireTOTP2FAForCreatingNewWallets.toString()));
reaction(
(_) => shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
(bool requireTOTP2FAForAllSecurityAndBackupSettings) => secureStorage.write(
(bool requireTOTP2FAForAllSecurityAndBackupSettings) => writeSecureStorage(secureStorage,
key: SecureKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
value: requireTOTP2FAForAllSecurityAndBackupSettings.toString()));
reaction((_) => useTOTP2FA,
(bool use) => secureStorage.write(key: SecureKey.useTOTP2FA, value: use.toString()));
reaction(
(_) => useTOTP2FA,
(bool use) =>
writeSecureStorage(secureStorage, key: SecureKey.useTOTP2FA, value: use.toString()));
reaction((_) => totpSecretKey,
(String totpKey) => secureStorage.write(key: SecureKey.totpSecretKey, value: totpKey));
reaction(
(_) => totpSecretKey,
(String totpKey) =>
writeSecureStorage(secureStorage, key: SecureKey.totpSecretKey, value: totpKey));
reaction(
(_) => pinTimeOutDuration,
(PinCodeRequiredDuration pinCodeInterval) => secureStorage.write(
(PinCodeRequiredDuration pinCodeInterval) => writeSecureStorage(secureStorage,
key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString()));
reaction(
+4 -8
View File
@@ -106,14 +106,10 @@ abstract class AuthViewModelBase with Store {
@action
Future<void> biometricAuth() async {
try {
final canBiometricAuth = await _biometricAuth.canCheckBiometrics();
if (canBiometricAuth) {
final isAuthenticated = await _biometricAuth.isAuthenticated();
if (isAuthenticated) {
state = ExecutedSuccessfullyState();
}
if (await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated()) {
state = ExecutedSuccessfullyState();
} else {
throw Exception('Biometric authentication failed');
}
} catch (e) {
state = FailureState(e.toString());
@@ -1,3 +1,4 @@
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
@@ -37,8 +38,7 @@ abstract class EditBackupPasswordViewModelBase with Store {
@action
Future<void> save() async {
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
await secureStorage.delete(key: key);
await secureStorage.write(key: key, value: backupPassword);
await writeSecureStorage(secureStorage, key: key, value: backupPassword);
secretStore.write(key: key, value: backupPassword);
}
}