inou-mobile/lib/services/auth_service.dart

193 lines
5.2 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'biometric_service.dart';
import 'inou_api.dart';
/// Authentication state
enum AuthState {
unknown, // Initial state, checking stored credentials
loggedOut, // No valid session
loggedIn, // Authenticated
}
/// Authentication service - manages login state with email + OTP code
class AuthService extends ChangeNotifier {
static final AuthService _instance = AuthService._internal();
factory AuthService() => _instance;
AuthService._internal();
final BiometricService _biometricService = BiometricService();
final InouApi _api = InouApi();
AuthState _state = AuthState.unknown;
String? _userEmail;
String? _error;
// Preference keys
static const String _prefKeyLoggedIn = 'auth_logged_in';
static const String _prefKeyEmail = 'auth_email';
static const String _prefKeyBiometricSetup = 'auth_biometric_setup';
/// Current auth state
AuthState get state => _state;
/// Current user email (if logged in)
String? get userEmail => _userEmail;
/// Last error message
String? get error => _error;
/// Whether user has completed biometric setup prompt
Future<bool> get hasBiometricSetupCompleted async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_prefKeyBiometricSetup) ?? false;
}
/// Initialize - check stored login state
Future<void> initialize() async {
await _api.initialize();
final prefs = await SharedPreferences.getInstance();
final isLoggedIn = prefs.getBool(_prefKeyLoggedIn) ?? false;
final email = prefs.getString(_prefKeyEmail);
if (isLoggedIn && email != null && _api.isAuthenticated) {
_userEmail = email;
_state = AuthState.loggedIn;
} else {
_state = AuthState.loggedOut;
}
notifyListeners();
}
/// Request login code to be sent to email
/// Returns true if code was sent successfully
Future<bool> requestLoginCode(String email) async {
_error = null;
if (email.isEmpty) {
_error = 'Please enter your email';
notifyListeners();
return false;
}
if (!_isValidEmail(email)) {
_error = 'Please enter a valid email address';
notifyListeners();
return false;
}
// Call the real API
final apiError = await _api.sendLoginCode(email);
if (apiError != null) {
_error = apiError;
notifyListeners();
return false;
}
notifyListeners();
return true;
}
/// Verify the login code
/// Returns true if code is valid and user is now logged in
Future<bool> verifyLoginCode(String email, String code) async {
_error = null;
if (code.isEmpty) {
_error = 'Please enter the verification code';
notifyListeners();
return false;
}
if (code.length != 6) {
_error = 'Code must be 6 digits';
notifyListeners();
return false;
}
// Call the real API
final apiError = await _api.verifyCode(email, code);
if (apiError != null) {
_error = apiError;
notifyListeners();
return false;
}
// Store login state
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefKeyLoggedIn, true);
await prefs.setString(_prefKeyEmail, email);
_userEmail = email;
_state = AuthState.loggedIn;
notifyListeners();
return true;
}
/// Logout - clear all auth state
Future<void> logout() async {
await _api.logout();
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_prefKeyLoggedIn);
await prefs.remove(_prefKeyEmail);
await prefs.remove(_prefKeyBiometricSetup);
// Also disable biometric
await _biometricService.setBiometricEnabled(false);
_biometricService.resetAuthState();
_userEmail = null;
_state = AuthState.loggedOut;
_error = null;
notifyListeners();
}
/// Mark biometric setup as completed (user saw the prompt)
Future<void> completeBiometricSetup() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefKeyBiometricSetup, true);
}
/// Enable biometric authentication for future logins
Future<void> enableBiometric() async {
// User just logged in - no need to verify again, just enable
await _biometricService.setBiometricEnabled(true);
await completeBiometricSetup();
}
/// Skip biometric setup
Future<void> skipBiometricSetup() async {
await completeBiometricSetup();
}
/// Check if biometric login is available and enabled
Future<bool> canUseBiometricLogin() async {
final available = await _biometricService.isBiometricsAvailable();
final enabled = await _biometricService.isBiometricEnabled();
return available && enabled;
}
/// Attempt biometric login (for returning users)
Future<bool> biometricLogin() async {
final canUse = await canUseBiometricLogin();
if (!canUse) return false;
final result = await _biometricService.authenticate(
reason: 'Login to inou',
);
return result == BiometricResult.success;
}
bool _isValidEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}
}