Security: Cloud-Auth auf Bearer-JWT umgestellt (IDOR-Luecke geschlossen)

- cloud_service: echter Login gegen baka-auth, Token statt X-API-Key/X-User
- app_config: hartcodierten API-Key-Default entfernt
- download_service + melo_logger: Bearer-Token statt X-API-Key
- navidrome: Passwort in flutter_secure_storage (Keychain/Keystore)
- song: token-haltige stream_url wird nicht mehr in SQLite persistiert
- cloud_screen: Pfad-Traversal beim Download-Dateinamen gefixt (p.basename)
- home_screen: Login-Dialog mit Passwort-Feld, Auto-Sync nutzt restoreLogin
This commit is contained in:
Hermes (Server)
2026-07-31 14:31:12 +02:00
parent 3e9389ab59
commit 9a880b0f21
17 changed files with 1552 additions and 181 deletions
+160
View File
@@ -0,0 +1,160 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/farb_theme.dart';
import '../services/melo_logger.dart';
/// Animierte Ladeanzeige mit Easter Eggs für Melo
class MeloLoader extends StatefulWidget {
final String titel;
final String? subtitle;
const MeloLoader({super.key, required this.titel, this.subtitle});
@override
State<MeloLoader> createState() => _MeloLoaderState();
}
class _MeloLoaderState extends State<MeloLoader>
with TickerProviderStateMixin {
late AnimationController _pulsCtrl;
late AnimationController _equalCtrl;
late Animation<double> _pulsAnim;
int _downloadCount = 0;
String? _easterEgg;
static const _easterEggs = [
'🎵 Baka hat dich programmiert!',
'🔥 Melo > Spotify. Change my mind.',
'💀 Dieser Download kostet dich 5 Jahre Lebenszeit.',
'🎸 Fun Fact: Dieser Song wurde von einem Server-Proxy gerettet!',
'🐧 Linux > Windows. (Server-Fakt)',
'🍎 SideStore > iLoader. Immer.',
'🛡️ Geschützt von Caddy + Basic Auth.',
'🤫 Melo ist besser als YouTube Music.',
'⚡ Server: Aingrad, Netcup RS 1000.',
'💾 yt-dlp 2026.03.17 Updates sind wichtig.',
'🎯 Der Proxy läuft auf Systemd stabil.',
'🧠 Dustin hat das selbst gecoded!',
'📱 iOS + Android + Linux = schon 3/5!',
'🐛 Crash-Logs auf crash.baka-net.de',
'🎵 Navidrome: musik.baka-net.de',
'🗿 Du hast den Stein der Weisen gefunden.',
'🧙 Die Antwort ist 42.',
'⚔️ SDS Origin Guide im Vault.',
];
@override
void initState() {
super.initState();
_pulsCtrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)..repeat(reverse: true);
_pulsAnim = Tween(begin: 0.6, end: 1.0).animate(
CurvedAnimation(parent: _pulsCtrl, curve: Curves.easeInOut),
);
_equalCtrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
)..repeat();
_ladeCount();
}
Future<void> _ladeCount() async {
try {
final prefs = await SharedPreferences.getInstance();
_downloadCount = prefs.getInt('dl_count') ?? 0;
_downloadCount++;
await prefs.setInt('dl_count', _downloadCount);
MeloLogger().aktion('dl_count', {'c': _downloadCount});
if (_downloadCount % 10 == 0 && mounted) {
final rng = Random(_downloadCount * 7);
final msg = _easterEggs[rng.nextInt(_easterEggs.length)];
setState(() => _easterEgg = msg);
}
} catch (_) {}
}
@override
void dispose() {
_pulsCtrl.dispose();
_equalCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 40,
child: AnimatedBuilder(
animation: _equalCtrl,
builder: (_, __) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (i) {
final phase = (i * 0.4 + _equalCtrl.value * 2 * pi);
final h = (sin(phase).abs() * 24 + 8);
return Container(
width: 4,
height: h,
margin: const EdgeInsets.symmetric(horizontal: 3),
decoration: BoxDecoration(
color: MeloTheme.rot.withValues(alpha: _pulsAnim.value),
borderRadius: BorderRadius.circular(2),
),
);
}),
),
),
),
const SizedBox(height: 12),
FadeTransition(
opacity: _pulsAnim,
child: Text(
widget.titel,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 15),
),
),
if (widget.subtitle != null) ...[
const SizedBox(height: 4),
Text(widget.subtitle!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.grey, fontSize: 12)),
],
if (_easterEgg != null) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: MeloTheme.dunkel1,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: MeloTheme.rot.withValues(alpha: 0.3)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('🥚', style: TextStyle(fontSize: 16)),
const SizedBox(width: 8),
Flexible(
child: Text(
_easterEgg!,
style: const TextStyle(color: MeloTheme.rot, fontSize: 12),
),
),
],
),
),
],
],
);
}
}