import 'dart:io'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:local_auth/local_auth.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:login_vault_app/constants.dart'; /// Handles app-level authentication, session, and local identity. class AuthService { final FlutterSecureStorage _storage = const FlutterSecureStorage(); final LocalAuthentication _localAuth = LocalAuthentication(); // ---------- Identity (email + app password) ---------- Future getEmail() async => await _storage.read(key: AppConstants.secureKeyEmail); Future getAppPassword() async => await _storage.read(key: AppConstants.secureKeyAppPassword); Future isRegistered() async => (await getEmail()) != null; Future registerWithEmail(String email, String appPassword) async { await _storage.write(key: AppConstants.secureKeyEmail, value: email); await _storage.write( key: AppConstants.secureKeyAppPassword, value: appPassword); } /// Verify the entered app password against the stored one. Future verifyPassword(String password) async { final stored = await getAppPassword(); if (stored == null) return false; return stored == password; } Future changePassword(String newPassword) async { await _storage.write( key: AppConstants.secureKeyAppPassword, value: newPassword); } // ---------- Biometric (fingerprint) ---------- /// Can the device authenticate with biometrics (fingerprint etc.)? Future canUseBiometrics() async { try { final supported = await _localAuth.isDeviceSupported(); if (!supported) return false; final available = await _localAuth.getAvailableBiometrics(); return available.isNotEmpty; } catch (_) { return false; } } /// Prompt the OS biometric dialog. /// Returns (success, errorMessage). errorMessage is null on success. Future<(bool, String?)> authenticateWithBiometrics() async { String? lastErr; // Prefer strict biometric (fingerprint). If that fails, fall back to // device credentials (PIN/pattern) so the user isn't hard-blocked. for (final opt in [ const AuthenticationOptions( biometricOnly: true, stickyAuth: true, sensitiveTransaction: true), const AuthenticationOptions( biometricOnly: false, stickyAuth: true, sensitiveTransaction: true), ]) { try { final ok = await _localAuth.authenticate( localizedReason: 'Gunakan biometrik untuk membuka Login Vault', options: opt, ); if (ok) return (true, null); } on PlatformException catch (e) { lastErr = '${e.code}: ${e.message}'; // user cancel / negative button -> stop retrying if (e.code == 'PasscodeNotSet' || e.code == 'NotAvailable' || e.code == 'NotEnrolled' || e.code == 'LockedOut' || e.code == 'UserCancel' || e.code == 'Canceled' || e.code == 'UserFallback') { return (false, lastErr); } } catch (e) { lastErr = e.toString(); } } return (false, lastErr); } // ---------- Session ---------- /// Persist that a session was just opened (with timestamp). Future openSession() async { await _storage.write( key: AppConstants.secureKeySession, value: DateTime.now().toIso8601String()); } Future closeSession() async { await _storage.delete(key: AppConstants.secureKeySession); } /// Returns true if a valid, non-expired session exists. /// [timeoutMinutes] = 0 means "stay logged in until manual logout". Future hasValidSession(int timeoutMinutes) async { if (timeoutMinutes <= 0) { // never auto-expire; just check a session was opened final v = await _storage.read(key: AppConstants.secureKeySession); return v != null; } final v = await _storage.read(key: AppConstants.secureKeySession); if (v == null) return false; final opened = DateTime.tryParse(v); if (opened == null) return false; final diff = DateTime.now().difference(opened).inMinutes; return diff < timeoutMinutes; } // ---------- Timeout setting ---------- /// Get configured session timeout (minutes). Default 3. Future getTimeoutMinutes() async { final sp = await _prefs(); return sp.getInt(AppConstants.prefTimeout) ?? 3; } Future setTimeoutMinutes(int minutes) async { final sp = await _prefs(); await sp.setInt(AppConstants.prefTimeout, minutes); } // ---------- Biometric toggle setting ---------- Future isBiometricEnabled() async { final sp = await _prefs(); return sp.getBool(AppConstants.prefBiometric) ?? false; } Future setBiometricEnabled(bool on) async { final sp = await _prefs(); await sp.setBool(AppConstants.prefBiometric, on); } Future _prefs() async => await SharedPreferences.getInstance(); Future reset() async { await _storage.delete(key: AppConstants.secureKeyEmail); await _storage.delete(key: AppConstants.secureKeyAppPassword); await _storage.delete(key: AppConstants.secureKeySession); } }