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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ae6f651f4c
commit
756486d1fe
@@ -0,0 +1,67 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:pyramid/core/matrix_client.dart';
|
||||
import 'package:pyramid/features/voice_channel/voice_channel_service.dart';
|
||||
import 'package:pyramid/main.dart' as app;
|
||||
|
||||
/// Echter Beitritt zu LiveKit mit einem vom SERVER ausgestellten Token
|
||||
/// (docs/LIVEKIT_TOKEN_MIGRATION.md) – über denselben Weg wie die App
|
||||
/// (voiceChannelProvider.startCall), nur mit dem TEST-Konto.
|
||||
///
|
||||
/// Läuft nur mit gesetztem PYRAMID_PROFILE_DIR (eingeloggtes Testprofil,
|
||||
/// z. B. pyramidtest1) – nie gegen Bernds echtes Profil:
|
||||
///
|
||||
/// $env:PYRAMID_PROFILE_DIR="$env:USERPROFILE\.pyramid-autopilot\profile1"
|
||||
/// flutter test integration_test/livekit_token_connect_test.dart -d windows
|
||||
///
|
||||
/// Betritt einen eigenen Wegwerf-Raum („pyramid-tokentest“), nur Ton, und
|
||||
/// legt sofort wieder auf. Ersetzt NICHT den Anruftest auf dem Handy.
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
final profile = Platform.environment['PYRAMID_PROFILE_DIR'];
|
||||
|
||||
testWidgets(
|
||||
'LiveKit-Beitritt mit Server-Token: Identität = eigene Matrix-ID',
|
||||
(tester) async {
|
||||
app.main();
|
||||
final end = DateTime.now().add(const Duration(seconds: 90));
|
||||
while (find.byType(MaterialApp).evaluate().isEmpty &&
|
||||
DateTime.now().isBefore(end)) {
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
}
|
||||
final container = ProviderScope.containerOf(
|
||||
tester.element(find.byType(MaterialApp).first),
|
||||
);
|
||||
final client = await tester.runAsync(
|
||||
() => container.read(matrixClientProvider.future),
|
||||
);
|
||||
expect(client!.isLogged(), isTrue, reason: 'Testprofil nicht angemeldet');
|
||||
|
||||
final call = container.read(voiceChannelProvider);
|
||||
await tester.runAsync(() => call.startCall(
|
||||
roomName: 'pyramid-tokentest',
|
||||
roomDisplayName: 'Token-Test',
|
||||
identity: 'wird-vom-server-ignoriert',
|
||||
audioOnly: true,
|
||||
matrixClient: client,
|
||||
));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
|
||||
expect(call.error, isNull, reason: 'Beitritt fehlgeschlagen');
|
||||
expect(call.room, isNotNull, reason: 'keine LiveKit-Verbindung');
|
||||
// Der Server bestimmt die Identität aus dem geprüften Matrix-Login –
|
||||
// der vom Client übergebene Wert spielt keine Rolle mehr.
|
||||
expect(call.room!.localParticipant?.identity, client.userID);
|
||||
|
||||
await tester.runAsync(() => call.hangUp());
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(call.room, isNull);
|
||||
},
|
||||
skip: profile == null,
|
||||
timeout: const Timeout(Duration(minutes: 3)),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user