import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:local_auth/local_auth.dart'; import 'offline_store.dart'; class SecurityService { SecurityService({ OfflineStore? store, LocalAuthentication? localAuth, }) : _store = store ?? OfflineStore.instance, _localAuth = localAuth ?? LocalAuthentication(); final OfflineStore _store; final LocalAuthentication _localAuth; static const String _pinEnabledKey = 'security_pin_enabled'; static const String _pinHashKey = 'security_pin_hash'; static const String _biometricEnabledKey = 'security_biometric_enabled'; static const String _salt = 'FIN65_LOCAL_PIN_V1'; Future isPinEnabled() async { final enabled = await _store.getSetting(_pinEnabledKey); final hash = await _store.getSetting(_pinHashKey); return enabled == 'true' && hash != null && hash.isNotEmpty; } Future setPin(String pin) async { _validatePin(pin); await _store.setSetting(_pinHashKey, _hashPin(pin)); await _store.setSetting(_pinEnabledKey, 'true'); } Future verifyPin(String pin) async { if (!_isValidPinShape(pin)) return false; final storedHash = await _store.getSetting(_pinHashKey); if (storedHash == null || storedHash.isEmpty) return false; return storedHash == _hashPin(pin); } Future disablePin() async { await _store.setSetting(_pinHashKey, ''); await _store.setSetting(_pinEnabledKey, 'false'); await setBiometricEnabled(false); } Future isBiometricAvailable() async { try { final supported = await _localAuth.isDeviceSupported(); final canCheck = await _localAuth.canCheckBiometrics; final biometrics = await _localAuth.getAvailableBiometrics(); return supported && canCheck && biometrics.isNotEmpty; } catch (_) { return false; } } Future> getAvailableBiometrics() async { try { return await _localAuth.getAvailableBiometrics(); } catch (_) { return const []; } } Future isBiometricEnabled() async { final enabled = await _store.getSetting(_biometricEnabledKey); final pinEnabled = await isPinEnabled(); return pinEnabled && enabled == 'true'; } Future setBiometricEnabled(bool enabled) async { if (enabled) { final pinEnabled = await isPinEnabled(); if (!pinEnabled) { throw Exception('Active d’abord un PIN avant la biométrie.'); } final available = await isBiometricAvailable(); if (!available) { throw Exception('Aucune empreinte/biométrie disponible sur cet appareil.'); } } await _store.setSetting(_biometricEnabledKey, enabled ? 'true' : 'false'); } Future authenticateWithBiometrics() async { final enabled = await isBiometricEnabled(); if (!enabled) return false; try { return await _localAuth.authenticate( localizedReason: 'Déverrouiller FIN65', biometricOnly: true, ); } catch (_) { return false; } } String _hashPin(String pin) { return sha256.convert(utf8.encode('$_salt::$pin')).toString(); } void _validatePin(String pin) { if (!_isValidPinShape(pin)) { throw Exception('Le PIN doit contenir 4 à 8 chiffres.'); } } bool _isValidPinShape(String pin) { return RegExp(r'^\d{4,8}$').hasMatch(pin); } }