Files
pyramid/test/livekit_token_test.dart
T
Bernd SteckmeisterandClaude Opus 5.5 756486d1fe security: LiveKit-Token vom Server statt Secret in der App
Die App signierte LiveKit-Tokens selbst – dafür steckte das SFU-Secret im
App-Code (jeder mit der APK konnte sich Tokens für beliebige Räume und
Identitäten ausstellen). Jetzt holt die App das Token von der bereits
vorhandenen Server-Route /api/livekit-token (whoami-Prüfung, Identität =
geprüfte Matrix-ID). Secret und JWT-Signieren sind aus dem Client entfernt.

- Lesbare Fehlermeldungen (offline, 401, Serverfehler) statt stiller Fehler.
- Gruppenanruf aus dem Chat-Kopf übergibt jetzt den Matrix-Client.
- Tests: Unit-Test mit Mock-HTTP + echter LiveKit-Beitritt (Testkonto).
- Offen: Handy-Anruftest; danach Secret-Rotation durch Bernd (Doku).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 22:12:15 +02:00

88 lines
2.7 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:pyramid/core/livekit_token.dart';
/// Beitritts-Token kommt vom Server; das LiveKit-Secret steckt nicht mehr
/// in der App (docs/LIVEKIT_TOKEN_MIGRATION.md).
void main() {
test('schickt Raum + Matrix-Token und liefert Token und Adresse', () async {
late Map<String, dynamic> sent;
final client = MockClient((req) async {
expect(req.url.toString(), LiveKitTokenService.endpoint);
sent = jsonDecode(req.body) as Map<String, dynamic>;
return http.Response(
jsonEncode({'token': 'abc.def.ghi', 'url': 'wss://lk.example'}),
200,
);
});
final r = await LiveKitTokenService.fetch(
room: '!raum:server',
matrixToken: 'mt',
httpClient: client,
);
expect(sent, {'room': '!raum:server', 'matrix_token': 'mt'});
expect(r.token, 'abc.def.ghi');
expect(r.url, 'wss://lk.example');
});
test('ohne brauchbare Adresse: Standard-LiveKit', () async {
final client = MockClient(
(_) async => http.Response(jsonEncode({'token': 't'}), 200),
);
final r = await LiveKitTokenService.fetch(
room: 'r',
matrixToken: 'm',
httpClient: client,
);
expect(r.url, LiveKitTokenService.fallbackUrl);
});
Future<String> failureFor(MockClient client) async {
try {
await LiveKitTokenService.fetch(
room: 'r',
matrixToken: 'm',
httpClient: client,
);
} on LiveKitTokenException catch (e) {
return e.message;
}
fail('LiveKitTokenException erwartet');
}
test('Fehler werden verständlich gemeldet statt still geschluckt', () async {
expect(
await failureFor(MockClient((_) async => http.Response('{}', 401))),
contains('neu anmelden'),
);
expect(
await failureFor(MockClient((_) async => http.Response('kaputt', 500))),
contains('HTTP 500'),
);
expect(
await failureFor(MockClient((_) async => http.Response('{}', 200))),
contains('kein Zugangstoken'),
);
expect(
await failureFor(MockClient((_) async => http.Response('<html>', 200))),
contains('unlesbare'),
);
expect(
await failureFor(
MockClient((_) async => throw const SocketException('offline')),
),
contains('nicht erreichbar'),
);
});
test('App-Code enthält kein LiveKit-Secret und signiert nicht selbst', () {
final src = File('lib/core/livekit_token.dart').readAsStringSync();
expect(src, isNot(contains('apiSecret')));
expect(src, isNot(contains('jsonwebtoken')));
});
}