185 lines
5.8 KiB
Dart
185 lines
5.8 KiB
Dart
import 'dart:convert';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:http/http.dart' as http;
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../config/app_config.dart';
|
||
import '../services/melo_logger.dart';
|
||
|
||
/// Baka-Auth Service – JWT-basierte Authentifizierung
|
||
/// Integriert mit https://baka-net.de/auth
|
||
class AuthService {
|
||
static final AuthService _instance = AuthService._();
|
||
factory AuthService() => _instance;
|
||
AuthService._();
|
||
|
||
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<String, String> get authHeader {
|
||
final headers = <String, String>{
|
||
'Content-Type': 'application/json',
|
||
};
|
||
if (_token != null && _token!.isNotEmpty) {
|
||
headers['Authorization'] = 'Bearer $_token';
|
||
}
|
||
return headers;
|
||
}
|
||
|
||
/// Lädt gespeicherten Token beim App-Start
|
||
Future<void> initialisieren() async {
|
||
if (_initialisiert) return;
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
_token = prefs.getString('baka_token');
|
||
_user = prefs.getString('baka_user') ?? '';
|
||
if (_token != null && _token!.isNotEmpty) {
|
||
MeloLogger().zustand('auth_restored', {'user': _user});
|
||
}
|
||
} catch (e) {
|
||
MeloLogger().fehler('auth_init', e);
|
||
}
|
||
_initialisiert = true;
|
||
}
|
||
|
||
/// Login über Baka-Auth-Server
|
||
/// Gibt true zurück bei Erfolg, false bei Fehler
|
||
Future<AuthResult> 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);
|
||
MeloLogger().zustand('auth_response', {'body': response.body.substring(0, response.body.length.clamp(0, 200))});
|
||
_token = data['token'] as String?;
|
||
_user = user;
|
||
|
||
if (_token != null && _token!.isNotEmpty) {
|
||
// Token speichern
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('baka_token', _token!);
|
||
await prefs.setString('baka_user', _user);
|
||
|
||
MeloLogger().aktion('auth_login_ok', {'user': _user});
|
||
return AuthResult.ok;
|
||
}
|
||
// 200 aber kein Token — zeige was der Server wirklich geschickt hat
|
||
final preview = response.body.length > 100
|
||
? '${response.body.substring(0, 100)}...'
|
||
: response.body;
|
||
MeloLogger().fehler('auth_login_no_token', preview);
|
||
return AuthResult.fehlgeschlagen('Server: $preview');
|
||
}
|
||
|
||
// Nicht-200: Fehler vom Server parsen
|
||
String fehler = 'Unbekannter Fehler';
|
||
try {
|
||
final data = jsonDecode(response.body);
|
||
fehler = data['error'] as String? ?? data['message'] as String? ?? 'Login fehlgeschlagen (${response.statusCode})';
|
||
} catch (_) {
|
||
fehler = 'Server nicht erreichbar (${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<AuthResult> 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) {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('baka_token', _token!);
|
||
await prefs.setString('baka_user', _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
|
||
Future<bool> tokenPruefen() async {
|
||
if (_token == null) return false;
|
||
try {
|
||
final response = await http
|
||
.get(
|
||
Uri.parse('${AppConfig.authUrl}/verify'),
|
||
headers: authHeader,
|
||
)
|
||
.timeout(const Duration(seconds: 5));
|
||
return response.statusCode == 200;
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// Ausloggen – Token löschen
|
||
Future<void> logout() async {
|
||
_token = null;
|
||
_user = '';
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.remove('baka_token');
|
||
await prefs.remove('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);
|
||
}
|