mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-29 06:38:45 +01:00
Upstream reformatted its whole tree in 16ddc0837. Matching it removes
formatting from the conflict surface of this and every later sync.
125 lines
3.7 KiB
Dart
125 lines
3.7 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
import 'package:hash_wallet/view_model/auth_state.dart';
|
|
import 'package:hash_wallet/core/auth_service.dart';
|
|
import 'package:hash_wallet/generated/i18n.dart';
|
|
import 'package:hash_wallet/core/execution_state.dart';
|
|
import 'package:hash_wallet/entities/biometric_auth.dart';
|
|
import 'package:hash_wallet/store/settings_store.dart';
|
|
|
|
part 'auth_view_model.g.dart';
|
|
|
|
class AuthViewModel = AuthViewModelBase with _$AuthViewModel;
|
|
|
|
abstract class AuthViewModelBase with Store {
|
|
AuthViewModelBase(
|
|
this._authService, this._sharedPreferences, this._settingsStore, this._biometricAuth)
|
|
: _failureCounter = 0,
|
|
state = InitialExecutionState() {
|
|
reaction((_) => state, _saveLastAuthTime);
|
|
}
|
|
|
|
static const maxFailedLogins = 3;
|
|
static const banTimeout = 180; // 3 minutes
|
|
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
|
|
|
@observable
|
|
ExecutionState state;
|
|
|
|
int get pinLength => _settingsStore.pinCodeLength;
|
|
|
|
bool get isBiometricalAuthenticationAllowed => _settingsStore.allowBiometricalAuthentication;
|
|
|
|
@observable
|
|
int _failureCounter;
|
|
|
|
final AuthService _authService;
|
|
final BiometricAuth _biometricAuth;
|
|
final SharedPreferences _sharedPreferences;
|
|
final SettingsStore _settingsStore;
|
|
|
|
@action
|
|
Future<void> auth({required String password}) async {
|
|
state = InitialExecutionState();
|
|
final _banDuration = banDuration();
|
|
|
|
if (_banDuration != null) {
|
|
state = AuthenticationBanned(
|
|
error: S.current.auth_store_banned_for +
|
|
'${_banDuration.inMinutes}' +
|
|
S.current.auth_store_banned_minutes);
|
|
return;
|
|
}
|
|
|
|
state = IsExecutingState();
|
|
final isSuccessfulAuthenticated = await _authService.authenticate(password);
|
|
|
|
if (isSuccessfulAuthenticated) {
|
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
state = ExecutedSuccessfullyState();
|
|
_failureCounter = 0;
|
|
});
|
|
} else {
|
|
_failureCounter += 1;
|
|
|
|
if (_failureCounter >= maxFailedLogins) {
|
|
final banDuration = await ban();
|
|
state = AuthenticationBanned(
|
|
error: S.current.auth_store_banned_for +
|
|
'${banDuration.inMinutes}' +
|
|
S.current.auth_store_banned_minutes);
|
|
return;
|
|
}
|
|
|
|
state = FailureState(S.current.auth_store_incorrect_password);
|
|
}
|
|
}
|
|
|
|
Duration? banDuration() {
|
|
final unbanTimestamp = _sharedPreferences.getInt(banTimeoutKey);
|
|
|
|
if (unbanTimestamp == null) {
|
|
return null;
|
|
}
|
|
|
|
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
|
final now = DateTime.now();
|
|
|
|
if (now.isAfter(unbanTime)) {
|
|
return null;
|
|
}
|
|
|
|
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
|
}
|
|
|
|
Future<Duration> ban() async {
|
|
final multiplier = _failureCounter - maxFailedLogins + 1;
|
|
final timeout = (multiplier * banTimeout) * 1000;
|
|
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
|
await _sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
|
|
|
return Duration(milliseconds: timeout);
|
|
}
|
|
|
|
@action
|
|
Future<void> biometricAuth() async {
|
|
try {
|
|
if (await _biometricAuth.canCheckBiometrics() && await _biometricAuth.isAuthenticated()) {
|
|
state = ExecutedSuccessfullyState();
|
|
} else {
|
|
throw Exception('Biometric authentication failed');
|
|
}
|
|
} catch (e) {
|
|
state = FailureState(e.toString());
|
|
}
|
|
}
|
|
|
|
void _saveLastAuthTime(ExecutionState state) {
|
|
if (state is ExecutedSuccessfullyState) {
|
|
_authService.saveLastAuthTime();
|
|
}
|
|
}
|
|
}
|