dev
dart 124 lines 3.66 KB
Raw
1 import 'dart:async';
2 import 'package:flutter/material.dart';
3 import 'package:shared_preferences/shared_preferences.dart';
4 import 'package:mobx/mobx.dart';
5 import 'package:cake_wallet/view_model/auth_state.dart';
6 import 'package:cake_wallet/core/auth_service.dart';
7 import 'package:cake_wallet/generated/i18n.dart';
8 import 'package:cake_wallet/core/execution_state.dart';
9 import 'package:cake_wallet/entities/biometric_auth.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
11
12 part 'auth_view_model.g.dart';
13
14 class AuthViewModel = AuthViewModelBase with _$AuthViewModel;
15
16 abstract class AuthViewModelBase with Store {
17 AuthViewModelBase(
18 this._authService, this._sharedPreferences, this._settingsStore, this._biometricAuth)
19 : _failureCounter = 0,
20 state = InitialExecutionState() {
21 reaction((_) => state, _saveLastAuthTime);
22 }
23
24 static const maxFailedLogins = 3;
25 static const banTimeout = 180; // 3 minutes
26 final banTimeoutKey = S.current.auth_store_ban_timeout;
27
28 @observable
29 ExecutionState state;
30
31 int get pinLength => _settingsStore.pinCodeLength;
32
33 bool get isBiometricalAuthenticationAllowed => _settingsStore.allowBiometricalAuthentication;
34
35 @observable
36 int _failureCounter;
37
38 final AuthService _authService;
39 final BiometricAuth _biometricAuth;
40 final SharedPreferences _sharedPreferences;
41 final SettingsStore _settingsStore;
42
43 @action
44 Future<void> auth({required String password}) async {
45 state = InitialExecutionState();
46 final _banDuration = banDuration();
47
48 if (_banDuration != null) {
49 state = AuthenticationBanned(
50 error: S.current.auth_store_banned_for +
51 '${_banDuration.inMinutes}' +
52 S.current.auth_store_banned_minutes);
53 return;
54 }
55
56 state = IsExecutingState();
57 final isSuccessfulAuthenticated = await _authService.authenticate(password);
58
59 if (isSuccessfulAuthenticated) {
60 WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
61 state = ExecutedSuccessfullyState();
62 _failureCounter = 0;
63 });
64 } else {
65 _failureCounter += 1;
66
67 if (_failureCounter >= maxFailedLogins) {
68 final banDuration = await ban();
69 state = AuthenticationBanned(
70 error: S.current.auth_store_banned_for +
71 '${banDuration.inMinutes}' +
72 S.current.auth_store_banned_minutes);
73 return;
74 }
75
76 state = FailureState(S.current.auth_store_incorrect_password);
77 }
78 }
79
80 Duration? banDuration() {
81 final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey);
82
83 if (unbanTimestamp == null) {
84 return null;
85 }
86
87 final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
88 final now = DateTime.now();
89
90 if (now.isAfter(unbanTime)) {
91 return null;
92 }
93
94 return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
95 }
96
97 Future<Duration> ban() async {
98 final multiplier = _failureCounter - maxFailedLogins + 1;
99 final timeout = (multiplier * banTimeout) * 1000;
100 final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
101 await _sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
102
103 return Duration(milliseconds: timeout);
104 }
105
106 @action
107 Future<void> biometricAuth() async {
108 try {
109 if (await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated()) {
110 state = ExecutedSuccessfullyState();
111 } else {
112 throw Exception('Biometric authentication failed');
113 }
114 } catch (e) {
115 state = FailureState(e.toString());
116 }
117 }
118
119 void _saveLastAuthTime(ExecutionState state) {
120 if (state is ExecutedSuccessfullyState) {
121 _authService.saveLastAuthTime();
122 }
123 }
124 }