This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
melo-app/lib/services/auth_service.dart
T
Dustin d555a7f146 v2.36 — Sprint 1: Behebe Issues #1 (Zurück-Button), #5 (Ausgeloggt), #4 (Berechtigungen)
Issue #1: CloudScreen Zurück-Button — onZurueck Callback statt Navigator.pop()
- CloudScreen erhält VoidCallback? onZurueck Parameter
- Bei onZurueck != null → Callback (Tab-Wechsel in home_screen)
- Fallback: Navigator.pop(context) für Standalone-Nutzung

Issue #5: Token wird nur bei HTTP 401 gelöscht
- _tokenOnlinePruefen(): nur statusCode == 401 → false
- 5xx, andere Fehler, Netzwerkfehler → true (Token behalten)
- Verhindert Ausloggen bei temporären Server-Problemen

Issue #4: Android 13+ Berechtigungen beim App-Start
- main.dart: frageSpeicherZugriff() + POST_NOTIFICATIONS nach Auth-Init
- musik_scanner.dart: Permission-Check vor externen Ordnern in _sammleMusikPfade()

Build: ⏭️ (no APK)
2026-08-02 15:48:08 +02:00

192 lines
6.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:convert';
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 (v3 — Persistent Login)
/// 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 und validiert ihn
Future<bool> initialisieren() async {
if (_initialisiert) return istEingeloggt;
try {
final prefs = await SharedPreferences.getInstance();
_token = prefs.getString('baka_token');
_user = prefs.getString('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 prefs.remove('baka_token');
await prefs.remove('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<bool> _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<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);
_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_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<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 (public)
Future<bool> tokenPruefen() async {
return _tokenOnlinePruefen();
}
/// 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);
}