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 e46ffa76a3 fix: 6 Claude-Audit-Fixes (3 CRIT + 3 HIGH)
CRITICAL:
- Logout: LoginScreen statt MeloHome pushen (home_screen.dart)
- Path Traversal: sanitizeDateiname() zentral + sync-loop abgesichert
- SecureStorage: flutter_secure_storage für Token+Passwort (auth_service, navidrome)

HIGH:
- Seek: PlayerService.seek() reload-frei (audio_handler)
- Play/Pause: Zielzustand statt Toggle für System-Controls
- X-User: Header entfernt, Server verlässt sich auf JWT
2026-08-03 01:41:04 +02:00

190 lines
6.0 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: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<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 {
_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<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) {
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<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) {
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<bool> tokenPruefen() async {
return _tokenOnlinePruefen();
}
/// Ausloggen Token löschen
Future<void> 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);
}