import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; /// Ablage für den Anmelde-Token. Als Schnittstelle, damit Tests ohne die /// Plattform-Kanäle von FlutterSecureStorage auskommen. abstract class TokenSpeicher { Future lesen(String key); Future schreiben(String key, String wert); Future loeschen(String key); } /// Produktions-Ablage: verschlüsselter Gerätespeicher. class SicherenSpeicher implements TokenSpeicher { const SicherenSpeicher(); static const _secure = FlutterSecureStorage(); @override Future lesen(String key) => _secure.read(key: key); @override Future schreiben(String key, String wert) => _secure.write(key: key, value: wert); @override Future loeschen(String key) => _secure.delete(key: key); } /// Anmeldung am Baka-Konto. Der YouTube-Proxy (yt.baka-net.de) akzeptiert /// nur Anfragen mit dem hier erzeugten Bearer-Token. class BakaAuth extends ChangeNotifier { BakaAuth({http.Client? client, TokenSpeicher? speicher}) : _client = client ?? http.Client(), _speicher = speicher ?? const SicherenSpeicher(); static const basisUrl = 'https://baka-net.de/auth'; static const _tokenKey = 'baka_token'; static const _userKey = 'baka_user'; final http.Client _client; final TokenSpeicher _speicher; String? _token; String _benutzer = ''; bool get istAngemeldet => _token != null && _token!.isNotEmpty; String get benutzer => _benutzer; /// Header für Anfragen an den Proxy. Ohne Anmeldung ohne `Authorization`. Map get authHeader => { if (istAngemeldet) 'Authorization': 'Bearer $_token', }; /// Stellt eine frühere Anmeldung wieder her (App-Start). Future laden() async { _token = await _speicher.lesen(_tokenKey); _benutzer = await _speicher.lesen(_userKey) ?? ''; notifyListeners(); } /// Meldet an. Gibt `null` bei Erfolg zurück, sonst den Fehlertext. Future anmelden(String benutzer, String passwort) async { final http.Response antwort; try { antwort = await _client .post( Uri.parse('$basisUrl/login'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'username': benutzer, 'password': passwort}), ) .timeout(const Duration(seconds: 15)); } catch (e) { debugPrint('Baka-Anmeldung fehlgeschlagen: $e'); return 'Keine Verbindung zum Anmelde-Server'; } Map daten; try { daten = jsonDecode(antwort.body) as Map; } catch (_) { return 'Anmelde-Server antwortet unverständlich (${antwort.statusCode})'; } final token = daten['token'] as String?; if (token != null && token.isNotEmpty) { _token = token; _benutzer = benutzer; await _speicher.schreiben(_tokenKey, token); await _speicher.schreiben(_userKey, benutzer); notifyListeners(); return null; } // Der Server meldet Fehler mit Status 200 und einem Text im Rumpf. return daten['message'] as String? ?? daten['error'] as String? ?? 'Anmeldung fehlgeschlagen (${antwort.statusCode})'; } Future abmelden() async { _token = null; _benutzer = ''; await _speicher.loeschen(_tokenKey); await _speicher.loeschen(_userKey); notifyListeners(); } }