119 lines
3.3 KiB
Dart
119 lines
3.3 KiB
Dart
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<bool> isPinEnabled() async {
|
|
final enabled = await _store.getSetting(_pinEnabledKey);
|
|
final hash = await _store.getSetting(_pinHashKey);
|
|
return enabled == 'true' && hash != null && hash.isNotEmpty;
|
|
}
|
|
|
|
Future<void> setPin(String pin) async {
|
|
_validatePin(pin);
|
|
await _store.setSetting(_pinHashKey, _hashPin(pin));
|
|
await _store.setSetting(_pinEnabledKey, 'true');
|
|
}
|
|
|
|
Future<bool> 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<void> disablePin() async {
|
|
await _store.setSetting(_pinHashKey, '');
|
|
await _store.setSetting(_pinEnabledKey, 'false');
|
|
await setBiometricEnabled(false);
|
|
}
|
|
|
|
Future<bool> 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<List<BiometricType>> getAvailableBiometrics() async {
|
|
try {
|
|
return await _localAuth.getAvailableBiometrics();
|
|
} catch (_) {
|
|
return const [];
|
|
}
|
|
}
|
|
|
|
Future<bool> isBiometricEnabled() async {
|
|
final enabled = await _store.getSetting(_biometricEnabledKey);
|
|
final pinEnabled = await isPinEnabled();
|
|
return pinEnabled && enabled == 'true';
|
|
}
|
|
|
|
Future<void> 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<bool> 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);
|
|
}
|
|
}
|
|
|
|
|