fix: честные ошибки при вводе пароля и дубликат в Keychain

This commit is contained in:
klockky
2026-08-22 13:52:09 +03:00
parent 04235e0972
commit d9e29f8651
6 changed files with 79 additions and 18 deletions
+1 -1
View File
@@ -488,7 +488,7 @@ class AccountModule {
final data = _requireMapPayload(packet, 'checkPassword');
if (data['error'] != null) {
throw Exception('checkPassword: неверный пароль');
throw const WrongPasswordException();
}
final tokenAttrs = data['tokenAttrs'];
@@ -290,6 +290,12 @@ class WrongDeviceTokenException implements Exception {
String toString() => 'WrongDeviceTokenException';
}
class WrongPasswordException implements Exception {
const WrongPasswordException();
@override
String toString() => 'WrongPasswordException';
}
class RequestCodeResult {
final String token;
@@ -124,7 +124,7 @@ class TwoFactorModule extends AccountApiBase {
checkPacketError(packet, 'check2faPassword');
final data = packet.payload;
if (data is Map && data['error'] != null) {
throw Exception('Неверный пароль');
throw const WrongPasswordException();
}
}
+21 -6
View File
@@ -1,3 +1,4 @@
import 'package:flutter/services.dart' show PlatformException;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -14,10 +15,25 @@ class TokenStorage {
mOptions: MacOsOptions(usesDataProtectionKeychain: false),
);
static Future<void> writeSecure(String key, String value) async {
await _secure.write(key: key, value: value);
static const int _duplicateKeychainItem = -25299;
static bool _isDuplicateItem(PlatformException error) =>
error.details == _duplicateKeychainItem ||
(error.message?.contains('$_duplicateKeychainItem') ?? false);
static Future<void> _write(String key, String value) async {
try {
await _secure.write(key: key, value: value);
} on PlatformException catch (e) {
if (!_isDuplicateItem(e)) rethrow;
await _secure.delete(key: key);
await _secure.write(key: key, value: value);
}
}
static Future<void> writeSecure(String key, String value) =>
_write(key, value);
static Future<String?> readSecure(String key) async {
return _secure.read(key: key);
}
@@ -31,9 +47,8 @@ class TokenStorage {
return all.keys.where((key) => key.startsWith(prefix)).toList();
}
static Future<void> saveToken(String token, int accountId) async {
await _secure.write(key: '$_tokenPrefix$accountId', value: token);
}
static Future<void> saveToken(String token, int accountId) =>
_write('$_tokenPrefix$accountId', token);
static Future<String?> readToken(int accountId) async {
final key = '$_tokenPrefix$accountId';
@@ -43,7 +58,7 @@ class TokenStorage {
final prefs = await SharedPreferences.getInstance();
final legacy = prefs.getString(key);
if (legacy != null) {
await _secure.write(key: key, value: legacy);
await _write(key, legacy);
await prefs.remove(key);
return legacy;
}
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../backend/modules/account/account_models.dart';
import '../../../core/protocol/packet.dart';
import '../../../l10n/app_localizations.dart';
import '../../../main.dart';
import '../../widgets/animated_slash_icon.dart';
import '../../widgets/custom_notification.dart';
@@ -101,10 +103,13 @@ class _Password2FAScreenState extends State<Password2FAScreen>
_isLoading = false;
});
final l10n = AppLocalizations.of(context)!;
if (!passed && (isSessionStateError(e) || sessionStale)) {
recoverStaleSession();
} else if (e is WrongPasswordException) {
showCustomNotification(context, l10n.passwordEntryWrongPassword);
} else {
showCustomNotification(context, 'Неверный пароль: $e');
showCustomNotification(context, l10n.devicesGenericError('$e'));
}
}
}
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../main.dart' show accountModule;
import '../../../backend/modules/account.dart' show TwoFactorDetails;
import '../../../core/storage/app_database.dart';
import '../../../l10n/app_localizations.dart';
import '../../widgets/animated_slash_icon.dart';
@@ -11,6 +10,13 @@ import '../../widgets/primary_loading_button.dart';
import '../../widgets/small_spinner.dart';
import '../../../core/config/app_fonts.dart';
import '../../../core/config/app_shape.dart';
import '../../../backend/modules/account/account_models.dart';
String _passwordErrorText(Object error, AppLocalizations l10n) =>
error is WrongPasswordException
? l10n.passwordEntryWrongPassword
: l10n.devicesGenericError('$error');
class PasswordEntryScreen extends StatefulWidget {
const PasswordEntryScreen({super.key});
@@ -56,12 +62,13 @@ class _PasswordEntryScreenState extends State<PasswordEntryScreen> {
_details = details;
});
_passwordController.clear();
} catch (_) {
} catch (e) {
if (mounted) {
setState(
() => _errorMessage = AppLocalizations.of(
context,
)!.passwordEntryWrongPassword,
() => _errorMessage = _passwordErrorText(
e,
AppLocalizations.of(context)!,
),
);
}
} finally {
@@ -621,7 +628,14 @@ class _TwoFactorSetupScreenState extends State<TwoFactorSetupScreen> {
break;
}
} catch (e) {
if (mounted) setState(() => _errorMessage = e.toString());
if (mounted) {
setState(
() => _errorMessage = _passwordErrorText(
e,
AppLocalizations.of(context)!,
),
);
}
} finally {
if (mounted) {
_isLoading.value = false;
@@ -1011,7 +1025,14 @@ class _TwoFactorPasswordChangeScreenState
Navigator.popUntil(context, ModalRoute.withName('SecurityScreen'));
}
} catch (e) {
if (mounted) setState(() => _errorMessage = e.toString());
if (mounted) {
setState(
() => _errorMessage = _passwordErrorText(
e,
AppLocalizations.of(context)!,
),
);
}
} finally {
if (mounted) _isLoading.value = false;
}
@@ -1182,7 +1203,14 @@ class _TwoFactorEmailChangeScreenState
break;
}
} catch (e) {
if (mounted) setState(() => _errorMessage = e.toString());
if (mounted) {
setState(
() => _errorMessage = _passwordErrorText(
e,
AppLocalizations.of(context)!,
),
);
}
} finally {
if (mounted) _isLoading.value = false;
}
@@ -1335,7 +1363,14 @@ class _TwoFactorRemoveScreenState extends State<TwoFactorRemoveScreen> {
Navigator.popUntil(context, ModalRoute.withName('SecurityScreen'));
}
} catch (e) {
if (mounted) setState(() => _errorMessage = e.toString());
if (mounted) {
setState(
() => _errorMessage = _passwordErrorText(
e,
AppLocalizations.of(context)!,
),
);
}
} finally {
if (mounted) _isLoading.value = false;
}