import 'dart:async'; import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Gère le mode installateur : déverrouillage par PIN, auto-lock, tentatives. /// /// Le PIN n'est jamais stocké en clair — SHA-256 uniquement. /// PIN par défaut au premier lancement : "1234". class InstallerModeProvider extends ChangeNotifier { static const _pinHashKey = 'installer_pin_hash'; static const _defaultPin = '1234'; static const _maxAttempts = 3; static const _lockDuration = Duration(seconds: 30); static const _autoLockDuration = Duration(minutes: 10); bool _isUnlocked = false; int _failedAttempts = 0; DateTime? _lockedUntil; Timer? _autoLockTimer; bool get isUnlocked => _isUnlocked; bool get isLocked => _lockedUntil != null && DateTime.now().isBefore(_lockedUntil!); int get failedAttempts => _failedAttempts; /// Durée restante avant déverrouillage (0 si non verrouillé). Duration get lockRemaining { if (!isLocked) return Duration.zero; return _lockedUntil!.difference(DateTime.now()); } // ── SHA-256 helper ────────────────────────────────────────────────────────── static String _hash(String pin) { final bytes = utf8.encode(pin); return sha256.convert(bytes).toString(); } // ── Initialisation ────────────────────────────────────────────────────────── Future init() async { final prefs = await SharedPreferences.getInstance(); // Crée le hash du PIN par défaut si aucun n'est encore défini if (!prefs.containsKey(_pinHashKey)) { await prefs.setString(_pinHashKey, _hash(_defaultPin)); } } // ── Déverrouillage ────────────────────────────────────────────────────────── Future unlock(String pin) async { if (isLocked) return UnlockResult.locked; final prefs = await SharedPreferences.getInstance(); final storedHash = prefs.getString(_pinHashKey) ?? _hash(_defaultPin); if (_hash(pin) == storedHash) { _isUnlocked = true; _failedAttempts = 0; _lockedUntil = null; _resetAutoLockTimer(); notifyListeners(); return UnlockResult.success; } else { _failedAttempts++; if (_failedAttempts >= _maxAttempts) { _lockedUntil = DateTime.now().add(_lockDuration); _failedAttempts = 0; // Déclenche rebuild après le délai pour effacer le verrou Timer(_lockDuration, () { _lockedUntil = null; notifyListeners(); }); } notifyListeners(); return UnlockResult.wrongPin; } } // ── Verrouillage ──────────────────────────────────────────────────────────── void lock() { _isUnlocked = false; _autoLockTimer?.cancel(); notifyListeners(); } // ── Changement de PIN ─────────────────────────────────────────────────────── Future changePin(String newPin) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_pinHashKey, _hash(newPin)); } // ── Auto-lock après inactivité ─────────────────────────────────────────────── void _resetAutoLockTimer() { _autoLockTimer?.cancel(); _autoLockTimer = Timer(_autoLockDuration, () { if (_isUnlocked) lock(); }); } /// Appeler lors d'une interaction en mode installateur pour réinitialiser /// le timer d'auto-lock. void onInstallerActivity() { if (_isUnlocked) _resetAutoLockTimer(); } @override void dispose() { _autoLockTimer?.cancel(); super.dispose(); } } enum UnlockResult { success, wrongPin, locked }