Skip to content

Commit

Permalink
Merge pull request #649 from cake-tech/CW-225-pin-timeout
Browse files Browse the repository at this point in the history
[CW-225] pin timeout
  • Loading branch information
OmarHatem28 authored Dec 13, 2022
2 parents b21bcf3 + d1832aa commit 4337af0
Show file tree
Hide file tree
Showing 27 changed files with 330 additions and 116 deletions.
33 changes: 30 additions & 3 deletions lib/core/auth_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ 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 {
AuthService({required this.secureStorage, required this.sharedPreferences});
AuthService({
required this.secureStorage,
required this.sharedPreferences,
required this.settingsStore,
});

final FlutterSecureStorage secureStorage;
final SharedPreferences sharedPreferences;
final SettingsStore settingsStore;

Future<void> setPassword(String password) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
Expand All @@ -19,8 +26,7 @@ class AuthService with Store {

Future<bool> canAuthenticate() async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final walletName =
sharedPreferences.getString(PreferencesKey.currentWalletName) ?? '';
final walletName = sharedPreferences.getString(PreferencesKey.currentWalletName) ?? '';
var password = '';

try {
Expand All @@ -39,4 +45,25 @@ class AuthService with Store {

return decodedPin == pin;
}

void saveLastAuthTime() {
int timestamp = DateTime.now().millisecondsSinceEpoch;
sharedPreferences.setInt(PreferencesKey.lastAuthTimeMilliseconds, timestamp);
}

bool requireAuth() {
final timestamp = sharedPreferences.getInt(PreferencesKey.lastAuthTimeMilliseconds);
final duration = _durationToRequireAuth(timestamp ?? 0);
final requiredPinInterval = settingsStore.pinTimeOutDuration;

return duration >= requiredPinInterval.value;
}

int _durationToRequireAuth(int timestamp) {
DateTime before = DateTime.fromMillisecondsSinceEpoch(timestamp);
DateTime now = DateTime.now();
Duration timeDifference = now.difference(before);

return timeDifference.inMinutes;
}
}
14 changes: 10 additions & 4 deletions lib/di.dart
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ Future setup(

getIt.registerFactory<AuthService>(() => AuthService(
secureStorage: getIt.get<FlutterSecureStorage>(),
sharedPreferences: getIt.get<SharedPreferences>()));
sharedPreferences: getIt.get<SharedPreferences>(),
settingsStore: getIt.get<SettingsStore>(),
),
);

getIt.registerFactory<AuthViewModel>(() => AuthViewModel(
getIt.get<AuthService>(),
Expand Down Expand Up @@ -397,7 +400,10 @@ Future setup(
getIt.registerFactory(() => WalletListViewModel(
_walletInfoSource,
getIt.get<AppStore>(),
getIt.get<WalletLoadingService>()));
getIt.get<WalletLoadingService>(),
getIt.get<AuthService>(),
),
);

getIt.registerFactory(() =>
WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>()));
Expand Down Expand Up @@ -457,7 +463,7 @@ Future setup(
});

getIt.registerFactory(() {
return SecuritySettingsViewModel(getIt.get<SettingsStore>());
return SecuritySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AuthService>());
});

getIt
Expand Down Expand Up @@ -828,4 +834,4 @@ Future setup(
AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));

_isSetupFinished = true;
}
}
32 changes: 32 additions & 0 deletions lib/entities/pin_code_required_duration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:cake_wallet/generated/i18n.dart';

enum PinCodeRequiredDuration {
always(0),
tenminutes(10),
onehour(60);

const PinCodeRequiredDuration(this.value);
final int value;

static PinCodeRequiredDuration deserialize({required int raw}) =>
PinCodeRequiredDuration.values.firstWhere((e) => e.value == raw);

@override
String toString(){
String label = '';
switch (this) {
case PinCodeRequiredDuration.always:
label = S.current.always;
break;
case PinCodeRequiredDuration.tenminutes:
label = S.current.minutes_to_pin_code('10');
break;
case PinCodeRequiredDuration.onehour:
label = S.current.minutes_to_pin_code('60');
break;
}
return label;

}

}
3 changes: 3 additions & 0 deletions lib/entities/preferences_key.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class PreferencesKey {
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
static const pinTimeOutDuration = 'pin_timeout_duration';
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';


static String moneroWalletUpdateV1Key(String name)
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
Expand Down
3 changes: 3 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/ionia/ionia_category.dart';
Expand Down Expand Up @@ -251,6 +252,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
Widget build(BuildContext context) {
return Observer(builder: (BuildContext context) {
final appStore = getIt.get<AppStore>();
final authService = getIt.get<AuthService>();
final settingsStore = appStore.settingsStore;
final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>();
Expand All @@ -275,6 +277,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
appStore: appStore,
authenticationStore: authenticationStore,
navigatorKey: navigatorKey,
authService: authService,
child: MaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
Expand Down
35 changes: 22 additions & 13 deletions lib/src/screens/root/root.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
Expand All @@ -9,17 +10,19 @@ import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:uni_links/uni_links.dart';

class Root extends StatefulWidget {
Root(
{required Key key,
required this.authenticationStore,
required this.appStore,
required this.child,
required this.navigatorKey})
: super(key: key);
Root({
required Key key,
required this.authenticationStore,
required this.appStore,
required this.child,
required this.navigatorKey,
required this.authService,
}) : super(key: key);

final AuthenticationStore authenticationStore;
final AppStore appStore;
final GlobalKey<NavigatorState> navigatorKey;
final AuthService authService;
final Widget child;

@override
Expand All @@ -28,20 +31,23 @@ class Root extends StatefulWidget {

class RootState extends State<Root> with WidgetsBindingObserver {
RootState()
: _isInactiveController = StreamController<bool>.broadcast(),
_isInactive = false,
_postFrameCallback = false;
: _isInactiveController = StreamController<bool>.broadcast(),
_isInactive = false,
_requestAuth = true,
_postFrameCallback = false;

Stream<bool> get isInactive => _isInactiveController.stream;
StreamController<bool> _isInactiveController;
bool _isInactive;
bool _postFrameCallback;
bool _requestAuth;

StreamSubscription<Uri?>? stream;
Uri? launchUri;

@override
void initState() {
_requestAuth = widget.authService.requireAuth();
_isInactiveController = StreamController<bool>.broadcast();
_isInactive = false;
_postFrameCallback = false;
Expand Down Expand Up @@ -85,8 +91,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
return;
}

if (!_isInactive &&
widget.authenticationStore.state == AuthenticationState.allowed) {
setState(() {
_requestAuth = widget.authService.requireAuth();
});

if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
setState(() => _setInactive(true));
}

Expand All @@ -98,7 +107,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {

@override
Widget build(BuildContext context) {
if (_isInactive && !_postFrameCallback) {
if (_isInactive && !_postFrameCallback && _requestAuth) {
_postFrameCallback = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.navigatorKey.currentState?.pushNamed(Routes.unlock,
Expand Down
46 changes: 32 additions & 14 deletions lib/src/screens/settings/security_backup_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
Expand All @@ -25,22 +27,26 @@ 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: (_) => _securitySettingsViewModel.checkPinCodeRiquired()
? Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.showKeys);
}
})
: Navigator.of(context).pushNamed(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: (_) => _securitySettingsViewModel.checkPinCodeRiquired()
? Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route: Routes.backup);
}
})
: Navigator.of(context).pushNamed(Routes.backup),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(
Expand All @@ -65,10 +71,12 @@ class SecurityBackupPage extends BasePage {
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (isAuthenticatedSuccessfully) {
if (await _securitySettingsViewModel.biometricAuthenticated()) {
_securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}
} else {
_securitySettingsViewModel.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
_securitySettingsViewModel
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
}

auth.close();
Expand All @@ -78,6 +86,16 @@ class SecurityBackupPage extends BasePage {
}
});
}),
Observer(builder: (_) {
return SettingsPickerCell<PinCodeRequiredDuration>(
title: S.current.require_pin_after,
items: PinCodeRequiredDuration.values,
selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration,
onItemSelected: (PinCodeRequiredDuration code) {
_securitySettingsViewModel.setPinCodeRequiredDuration(code);
},
);
}),
]),
);
}
Expand Down
Loading

0 comments on commit 4337af0

Please sign in to comment.