Saba-dart/lib/utils/app_lock_service.dart
2026-04-13 23:41:27 +03:30

159 lines
4.3 KiB
Dart

import 'dart:convert';
import 'dart:math';
import 'package:cryptography/cryptography.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class AppLockService extends ChangeNotifier {
AppLockService._();
static final AppLockService instance = AppLockService._();
static const _enabledKey = 'app_lock_enabled_v1';
static const _saltKey = 'app_lock_salt_v1';
static const _hashKey = 'app_lock_hash_v1';
static const _pbkdf2Iterations = 120000;
final FlutterSecureStorage _storage = const FlutterSecureStorage();
final Pbkdf2 _kdf = Pbkdf2(
macAlgorithm: Hmac.sha256(),
iterations: _pbkdf2Iterations,
bits: 256,
);
bool _initialized = false;
bool _enabled = false;
bool _locked = false;
bool get initialized => _initialized;
bool get isEnabled => _enabled;
bool get isLocked => _enabled && _locked;
Future<void> init() async {
if (_initialized) return;
await _reloadFromStorage(lockIfEnabled: true);
}
Future<void> refresh() async {
await _reloadFromStorage(lockIfEnabled: false);
}
Future<void> setPasscode(String passcode) async {
final normalized = _normalizePasscode(passcode);
final salt = _generateSalt();
final hash = await _derivePasscodeHash(normalized, salt);
await _storage.write(key: _saltKey, value: salt);
await _storage.write(key: _hashKey, value: hash);
await _storage.write(key: _enabledKey, value: '1');
_enabled = true;
_locked = false;
_initialized = true;
notifyListeners();
}
Future<bool> verifyPasscode(String passcode) async {
final isValid = await _matchesStoredPasscode(passcode);
if (!isValid) return false;
_enabled = true;
_locked = false;
_initialized = true;
notifyListeners();
return true;
}
Future<bool> changePasscode({
required String currentPasscode,
required String newPasscode,
}) async {
final isCurrentValid = await _matchesStoredPasscode(currentPasscode);
if (!isCurrentValid) return false;
await setPasscode(newPasscode);
return true;
}
Future<bool> disablePasscode(String currentPasscode) async {
final isCurrentValid = await _matchesStoredPasscode(currentPasscode);
if (!isCurrentValid) return false;
await clear();
return true;
}
Future<void> clear() async {
await _storage.delete(key: _enabledKey);
await _storage.delete(key: _saltKey);
await _storage.delete(key: _hashKey);
_enabled = false;
_locked = false;
_initialized = true;
notifyListeners();
}
void lock() {
if (!_enabled || _locked) return;
_locked = true;
notifyListeners();
}
Future<void> _reloadFromStorage({required bool lockIfEnabled}) async {
try {
final enabledValue = await _storage.read(key: _enabledKey);
_enabled = enabledValue == '1';
_locked = _enabled && lockIfEnabled ? true : (_enabled && _locked);
} catch (e) {
debugPrint('[APP_LOCK] Failed to load state: $e');
_enabled = false;
_locked = false;
}
if (!_enabled) {
_locked = false;
}
_initialized = true;
notifyListeners();
}
Future<bool> _matchesStoredPasscode(String passcode) async {
try {
final normalized = _normalizePasscode(passcode);
final salt = await _storage.read(key: _saltKey);
final storedHash = await _storage.read(key: _hashKey);
final enabledValue = await _storage.read(key: _enabledKey);
if (enabledValue != '1' || salt == null || storedHash == null) {
return false;
}
final computedHash = await _derivePasscodeHash(normalized, salt);
return computedHash == storedHash;
} catch (e) {
debugPrint('[APP_LOCK] Failed to verify passcode: $e');
return false;
}
}
Future<String> _derivePasscodeHash(String passcode, String salt) async {
final secretKey = await _kdf.deriveKey(
secretKey: SecretKey(utf8.encode(passcode)),
nonce: utf8.encode(salt),
);
final bytes = await secretKey.extractBytes();
return base64UrlEncode(bytes);
}
String _generateSalt() {
final random = Random.secure();
final bytes = List<int>.generate(16, (_) => random.nextInt(256));
return base64UrlEncode(bytes);
}
String _normalizePasscode(String input) => input.trim();
}