Add fingerprint (local_auth) session + 3-min inactivity timeout (default), activity resets timer, settings: biometric toggle + timeout picker
This commit is contained in:
@@ -1,35 +1,31 @@
|
||||
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 and persistence of the local identity.
|
||||
///
|
||||
/// On first launch the user registers (email+password OR Google). The chosen
|
||||
/// email is stored locally as the key ([AppConstants.secureKeyEmail]) and the
|
||||
/// app password (used to lock the dashboard) is stored in secure storage.
|
||||
/// Handles app-level authentication, session, and local identity.
|
||||
class AuthService {
|
||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||
final LocalAuthentication _localAuth = LocalAuthentication();
|
||||
|
||||
// ---------- Identity (email + app password) ----------
|
||||
|
||||
/// Returns the stored email, or null if not registered yet.
|
||||
Future<String?> getEmail() async =>
|
||||
await _storage.read(key: AppConstants.secureKeyEmail);
|
||||
|
||||
Future<String?> getAppPassword() async =>
|
||||
await _storage.read(key: AppConstants.secureKeyAppPassword);
|
||||
|
||||
/// True if a user identity already exists locally.
|
||||
Future<bool> isRegistered() async => (await getEmail()) != null;
|
||||
|
||||
/// Register with email + app password.
|
||||
Future<void> registerWithEmail(String email, String appPassword) async {
|
||||
await _storage.write(key: AppConstants.secureKeyEmail, value: email);
|
||||
await _storage.write(
|
||||
key: AppConstants.secureKeyAppPassword, value: appPassword);
|
||||
}
|
||||
|
||||
/// Register with Google (email supplied by Google sign-in).
|
||||
Future<void> registerWithGoogle(String email) async {
|
||||
await _storage.write(key: AppConstants.secureKeyEmail, value: email);
|
||||
// App password defaults to a marker; user can change it later in settings.
|
||||
final existing = await getAppPassword();
|
||||
if (existing == null) {
|
||||
await _storage.write(
|
||||
@@ -41,27 +37,103 @@ class AuthService {
|
||||
Future<bool> verifyPassword(String password) async {
|
||||
final stored = await getAppPassword();
|
||||
if (stored == null) return false;
|
||||
if (stored == '__google__') {
|
||||
// Google users have no password by default; let any entry pass OR force set.
|
||||
return true;
|
||||
}
|
||||
if (stored == '__google__') return true; // google users: any pass ok
|
||||
return stored == password;
|
||||
}
|
||||
|
||||
/// Change the app password (used in settings).
|
||||
Future<void> changePassword(String newPassword) async {
|
||||
await _storage.write(
|
||||
key: AppConstants.secureKeyAppPassword, value: newPassword);
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
// Keep email (identity) but we treat logout as returning to auth screen
|
||||
// with the email still known. To fully reset, call [reset].
|
||||
// ---------- Biometric (fingerprint) ----------
|
||||
|
||||
/// Can the device authenticate with biometrics (fingerprint etc.)?
|
||||
Future<bool> canUseBiometrics() async {
|
||||
try {
|
||||
final supported = await _localAuth.isDeviceSupported();
|
||||
if (!supported) return false;
|
||||
final available = await _localAuth.getAvailableBiometrics();
|
||||
return available.isNotEmpty;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Wipe local identity (for completeness / debugging).
|
||||
/// Prompt the OS biometric dialog. Returns true on success.
|
||||
Future<bool> authenticateWithBiometrics() async {
|
||||
try {
|
||||
return await _localAuth.authenticate(
|
||||
localizedReason: 'Gunakan sidik jari untuk membuka Login Vault',
|
||||
options: const AuthenticationOptions(
|
||||
biometricOnly: true,
|
||||
stickyAuth: true,
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Session ----------
|
||||
|
||||
/// Persist that a session was just opened (with timestamp).
|
||||
Future<void> openSession() async {
|
||||
await _storage.write(
|
||||
key: AppConstants.secureKeySession, value: DateTime.now().toIso8601String());
|
||||
}
|
||||
|
||||
Future<void> 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<bool> 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<int> getTimeoutMinutes() async {
|
||||
final sp = await _prefs();
|
||||
return sp.getInt(AppConstants.prefTimeout) ?? 3;
|
||||
}
|
||||
|
||||
Future<void> setTimeoutMinutes(int minutes) async {
|
||||
final sp = await _prefs();
|
||||
await sp.setInt(AppConstants.prefTimeout, minutes);
|
||||
}
|
||||
|
||||
// ---------- Biometric toggle setting ----------
|
||||
|
||||
Future<bool> isBiometricEnabled() async {
|
||||
final sp = await _prefs();
|
||||
return sp.getBool(AppConstants.prefBiometric) ?? false;
|
||||
}
|
||||
|
||||
Future<void> setBiometricEnabled(bool on) async {
|
||||
final sp = await _prefs();
|
||||
await sp.setBool(AppConstants.prefBiometric, on);
|
||||
}
|
||||
|
||||
Future<SharedPreferences> _prefs() async =>
|
||||
await SharedPreferences.getInstance();
|
||||
|
||||
Future<void> reset() async {
|
||||
await _storage.delete(key: AppConstants.secureKeyEmail);
|
||||
await _storage.delete(key: AppConstants.secureKeyAppPassword);
|
||||
await _storage.delete(key: AppConstants.secureKeySession);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user