Files

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);
}
}