import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import '../config/app_config.dart'; import '../services/melo_logger.dart'; /// Baka-Auth Service – JWT-basierte Authentifizierung (v3 — Persistent Login) /// Integriert mit https://baka-net.de/auth class AuthService { static final AuthService _instance = AuthService._(); factory AuthService() => _instance; AuthService._(); final FlutterSecureStorage _secure = const FlutterSecureStorage(); String? _token; String _user = ''; bool _initialisiert = false; bool get istEingeloggt => _token != null && _token!.isNotEmpty; String get benutzer => _user; String? get token => _token; /// Auth-Header für API-Requests Map get authHeader { final headers = { 'Content-Type': 'application/json', }; if (_token != null && _token!.isNotEmpty) { headers['Authorization'] = 'Bearer $_token'; } return headers; } /// Lädt gespeicherten Token beim App-Start und validiert ihn Future initialisieren() async { if (_initialisiert) return istEingeloggt; try { _token = await _secure.read(key: 'baka_token'); _user = await _secure.read(key: 'baka_user') ?? ''; if (_token != null && _token!.isNotEmpty) { // Token beim Server validieren (nicht blind vertrauen) final valid = await _tokenOnlinePruefen(); if (!valid) { // Token ist abgelaufen — nicht als eingeloggt behandeln MeloLogger().zustand('auth_token_expired', {'user': _user}); _token = null; _user = ''; await _secure.delete(key: 'baka_token'); await _secure.delete(key: 'baka_user'); } else { MeloLogger().zustand('auth_restored', {'user': _user}); } } } catch (e) { MeloLogger().fehler('auth_init', e); } _initialisiert = true; return istEingeloggt; } /// Prüft Token beim Baka-Auth-Server (online) Future _tokenOnlinePruefen() async { if (_token == null) return false; try { final response = await http .get( Uri.parse('${AppConfig.authUrl}/verify'), headers: authHeader, ) .timeout(const Duration(seconds: 5)); if (response.statusCode == 200) return true; // Nur bei explizitem 401 (abgelaufen/ungültig) den Token löschen. // Bei 5xx, anderen Fehlern oder temporären Server-Problemen Token behalten. if (response.statusCode == 401) return false; return true; } catch (_) { // Bei Netzwerkfehler: Token lokal akzeptieren (Offline-Modus) return true; } } /// Login über Baka-Auth-Server Future login(String user, String password) async { try { final response = await http .post( Uri.parse('${AppConfig.authUrl}/login'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'username': user, 'password': password, }), ) .timeout(const Duration(seconds: 10)); if (response.statusCode == 200) { final data = jsonDecode(response.body); _token = data['token'] as String?; _user = user; if (_token != null && _token!.isNotEmpty) { await _secure.write(key: 'baka_token', value: _token!); await _secure.write(key: 'baka_user', value: _user); MeloLogger().aktion('auth_login_ok', {'user': _user}); return AuthResult.ok; } final serverMsg = data['message'] as String? ?? data['error'] as String? ?? 'Unbekannt'; return AuthResult.fehlgeschlagen(serverMsg); } String fehler = 'Login fehlgeschlagen (${response.statusCode})'; MeloLogger().fehler('auth_login_fail', fehler); return AuthResult.fehlgeschlagen(fehler); } catch (e) { final msg = 'Keine Verbindung zum Auth-Server'; MeloLogger().fehler('auth_login_error', e); return AuthResult.fehlgeschlagen(msg); } } /// Registrierung über Baka-Auth-Server Future registrieren( String user, String password, String email) async { try { final response = await http .post( Uri.parse('${AppConfig.authUrl}/register'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'username': user, 'password': password, 'email': email, }), ) .timeout(const Duration(seconds: 10)); if (response.statusCode == 200 || response.statusCode == 201) { final data = jsonDecode(response.body); _token = data['token'] as String?; _user = user; if (_token != null && _token!.isNotEmpty) { await _secure.write(key: 'baka_token', value: _token!); await _secure.write(key: 'baka_user', value: _user); MeloLogger().aktion('auth_register_ok', {'user': _user}); return AuthResult.ok; } } String fehler = 'Registrierung fehlgeschlagen'; try { final data = jsonDecode(response.body); fehler = data['error'] as String? ?? fehler; } catch (_) {} return AuthResult.fehlgeschlagen(fehler); } catch (e) { return AuthResult.fehlgeschlagen('Keine Verbindung zum Server'); } } /// Token beim Server validieren (public) Future tokenPruefen() async { return _tokenOnlinePruefen(); } /// Ausloggen – Token löschen Future logout() async { _token = null; _user = ''; await _secure.delete(key: 'baka_token'); await _secure.delete(key: 'baka_user'); MeloLogger().aktion('auth_logout', {}); } } /// Ergebnis eines Auth-Versuchs class AuthResult { final bool erfolg; final String? fehler; const AuthResult._(this.erfolg, this.fehler); static const ok = AuthResult._(true, null); static AuthResult fehlgeschlagen(String msg) => AuthResult._(false, msg); }