mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-10-30 08:57:38 +00:00
43e062d1ac
* CW-263-TOTP-2FA-in-security-settings WIP * Implement TOTP 2FA WIP * Implement TOTP 2FA Authentication * chore: Remove unneeded formatting * revert formatting * fixes * CW-263-TOTP-2FA-in-security-settings WIP * Setup TOTP Complete, left with Modify TOTF * CW-263-TOTP-2FA-in-security-settings * CW-263-TOTP-2FA-in-security-settings * CW-263-TOTP-2FA-in-security-settings * fix: Add copy-to-clipboard for qr secret key * fix: Translation * chore: Move strings into translation files * feat: End to end flow for TOTP * hotfix: Switch totp to use sha512 * Update strings; 8 digits and error explanation * fix: Totp 2fa implementation feedback * hotfix: same action for button and alert close * feat: App should show both normal and totp auths when totp is enabled * hotfix: prevent barrier from dismissing * fix: Changes requested during PR review * - Minor Enhancements - Minor UI fixes --------- Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com> Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
159 lines
4.3 KiB
Dart
159 lines
4.3 KiB
Dart
// ignore_for_file: prefer_final_fields
|
|
|
|
import 'package:cake_wallet/store/settings_store.dart';
|
|
import 'package:cake_wallet/utils/totp_utils.dart' as Utils;
|
|
import 'package:cake_wallet/view_model/auth_state.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:mobx/mobx.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../core/auth_service.dart';
|
|
import '../core/execution_state.dart';
|
|
import '../generated/i18n.dart';
|
|
|
|
part 'set_up_2fa_viewmodel.g.dart';
|
|
|
|
class Setup2FAViewModel = Setup2FAViewModelBase with _$Setup2FAViewModel;
|
|
|
|
abstract class Setup2FAViewModelBase with Store {
|
|
final SettingsStore _settingsStore;
|
|
final AuthService _authService;
|
|
final SharedPreferences _sharedPreferences;
|
|
|
|
Setup2FAViewModelBase(this._settingsStore, this._sharedPreferences, this._authService)
|
|
: _failureCounter = 0,
|
|
enteredOTPCode = '',
|
|
state = InitialExecutionState() {
|
|
_getRandomBase32SecretKey();
|
|
reaction((_) => state, _saveLastAuthTime);
|
|
}
|
|
|
|
static const maxFailedTrials = 3;
|
|
static const banTimeout = 180; // 3 minutes
|
|
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
|
|
|
String get secretKey => _settingsStore.totpSecretKey;
|
|
String get deviceName => _settingsStore.deviceName;
|
|
String get totpVersionOneLink => _settingsStore.totpVersionOneLink;
|
|
|
|
@observable
|
|
ExecutionState state;
|
|
|
|
@observable
|
|
int _failureCounter;
|
|
|
|
@observable
|
|
String enteredOTPCode;
|
|
|
|
@computed
|
|
bool get useTOTP2FA => _settingsStore.useTOTP2FA;
|
|
|
|
void _getRandomBase32SecretKey() {
|
|
final randomBase32Key = Utils.generateRandomBase32SecretKey(16);
|
|
_setBase32SecretKey(randomBase32Key);
|
|
}
|
|
|
|
@action
|
|
void setUseTOTP2FA(bool value) {
|
|
_settingsStore.useTOTP2FA = value;
|
|
}
|
|
|
|
@action
|
|
void _setBase32SecretKey(String value) {
|
|
if (_settingsStore.totpSecretKey == '') {
|
|
_settingsStore.totpSecretKey = value;
|
|
}
|
|
}
|
|
|
|
@action
|
|
void clearBase32SecretKey() {
|
|
_settingsStore.totpSecretKey = '';
|
|
}
|
|
|
|
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 - maxFailedTrials;
|
|
final timeout = (multiplier * banTimeout) * 1000;
|
|
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
|
await _sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
|
|
|
return Duration(milliseconds: timeout);
|
|
}
|
|
|
|
@action
|
|
Future<bool> totp2FAAuth(String otpText, bool isForSetup) async {
|
|
state = InitialExecutionState();
|
|
_failureCounter = _settingsStore.numberOfFailedTokenTrials;
|
|
final _banDuration = banDuration();
|
|
|
|
if (_banDuration != null) {
|
|
state = AuthenticationBanned(
|
|
error: S.current.auth_store_banned_for +
|
|
'${_banDuration.inMinutes}' +
|
|
S.current.auth_store_banned_minutes);
|
|
return false;
|
|
}
|
|
|
|
final result = Utils.verify(
|
|
secretKey: secretKey,
|
|
otp: otpText,
|
|
);
|
|
|
|
isForSetup ? setUseTOTP2FA(result) : null;
|
|
|
|
if (result) {
|
|
return true;
|
|
} else {
|
|
final value = _settingsStore.numberOfFailedTokenTrials + 1;
|
|
adjustTokenTrialNumber(value);
|
|
print(value);
|
|
if (_failureCounter >= maxFailedTrials) {
|
|
final banDuration = await ban();
|
|
state = AuthenticationBanned(
|
|
error: S.current.auth_store_banned_for +
|
|
'${banDuration.inMinutes}' +
|
|
S.current.auth_store_banned_minutes);
|
|
return false;
|
|
}
|
|
|
|
state = FailureState('Incorrect code');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
void success() {
|
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
|
state = ExecutedSuccessfullyState();
|
|
adjustTokenTrialNumber(0);
|
|
});
|
|
}
|
|
|
|
@action
|
|
void adjustTokenTrialNumber(int value) {
|
|
_failureCounter = value;
|
|
_settingsStore.numberOfFailedTokenTrials = value;
|
|
}
|
|
|
|
void _saveLastAuthTime(ExecutionState state) {
|
|
if (state is ExecutedSuccessfullyState) {
|
|
_authService.saveLastAuthTime();
|
|
}
|
|
}
|
|
}
|