Files
pyramid/lib/features/auth/login_notifier.dart
T
Bernd Steckmeister 9dc83f7bcb fix: Refresh-Token beim Login anfordern (Uta-Random-Logout, M1)
Ursache gefunden: Ohne Refresh-Token eskaliert das matrix-SDK jeden
Soft-Logout (z. B. abgelaufenes Access-Token, M_UNKNOWN_TOKEN mit
soft_logout=true) automatisch zu einem echten, destruktiven Logout —
_handleSoftLogout() versucht refreshAccessToken(), das ohne
gespeicherten Refresh-Token sofort mit "No refresh token available"
scheitert, und ruft danach client.logout() auf (client.dart:2374-2384
im matrix-Paket, Version 6.2.0).

Mit refreshToken: true beim Login bekommt der Client vom Server einen
Refresh-Token und kann einen Soft-Logout künftig durch stillen
Token-Refresh überstehen, statt die Sitzung zu zerstören. Falls der
Server keine Refresh-Tokens unterstützt, bleibt das Verhalten
unverändert (response.refreshToken ist dann null).

Wichtig: gilt nur für neue Logins. Bereits eingeloggte Geräte (z. B.
Uta) profitieren erst nach einmaligem Neu-Login. Zur Diagnose siehe
den mit 63e4919 eingeführten AuthLog.

UNGETESTET (Pi, kein GUI/Login hier möglich) – nächster PC-Lauf muss
den vollen Kreislauf prüfen: Login → Nachrichten → Logout → erneuter
Login → alte verschlüsselte Nachrichten noch lesbar?
2026-07-03 09:51:40 +02:00

116 lines
3.5 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:matrix/matrix.dart' as mx;
import 'package:pyramid/core/fcm_push_service.dart';
import 'package:pyramid/core/matrix_client.dart';
import 'package:shared_preferences/shared_preferences.dart';
const _prefHomeserver = 'homeserver_url';
final homeserverProvider = StateProvider<String?>((ref) => null);
Future<void> initHomeserver(WidgetRef ref) async {
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString(_prefHomeserver);
ref.read(homeserverProvider.notifier).state = saved;
}
Future<void> saveHomeserver(WidgetRef ref, String url) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_prefHomeserver, url);
ref.read(homeserverProvider.notifier).state = url;
}
enum LoginStatus { idle, loading, error }
class LoginFormState {
final LoginStatus status;
final String? errorMessage;
const LoginFormState({this.status = LoginStatus.idle, this.errorMessage});
LoginFormState copyWith({LoginStatus? status, String? errorMessage}) =>
LoginFormState(
status: status ?? this.status,
errorMessage: errorMessage,
);
}
class LoginNotifier extends StateNotifier<LoginFormState> {
final Ref _ref;
LoginNotifier(this._ref) : super(const LoginFormState());
Future<void> login({
required String username,
required String password,
required String homeserver,
String? deviceName,
}) async {
state = state.copyWith(status: LoginStatus.loading, errorMessage: null);
final client = _ref.read(matrixClientProvider).valueOrNull;
if (client == null) {
state = state.copyWith(
status: LoginStatus.error,
errorMessage: 'Client nicht initialisiert',
);
return;
}
if (client.isLogged()) {
state = state.copyWith(status: LoginStatus.idle);
return;
}
try {
final serverUri = Uri.https(
homeserver.replaceAll(RegExp(r'^https?://'), ''),
'',
);
await client.checkHomeserver(serverUri);
await client.login(
mx.LoginType.mLoginPassword,
identifier: mx.AuthenticationUserIdentifier(user: username),
password: password,
initialDeviceDisplayName: deviceName ?? 'Pyramid',
// Ohne Refresh-Token eskaliert das SDK jeden Soft-Logout (z. B. nach
// Ablauf des Access-Tokens) sofort zu einem echten, destruktiven
// Logout, weil `refreshAccessToken()` sonst mangels Refresh-Token
// fehlschlägt (siehe matrix-Paket Client._handleSoftLogout). Das ist
// der wahrscheinlichste Auslöser des Uta-Random-Logout-Bugs.
refreshToken: true,
);
if (Platform.isAndroid) unawaited(initFcm(client));
state = state.copyWith(status: LoginStatus.idle);
} on mx.MatrixException catch (e) {
state = state.copyWith(
status: LoginStatus.error,
errorMessage: e.errorMessage,
);
} catch (e) {
state = state.copyWith(
status: LoginStatus.error,
errorMessage: e.toString(),
);
}
}
}
final loginNotifierProvider =
StateNotifierProvider<LoginNotifier, LoginFormState>((ref) {
return LoginNotifier(ref);
});
final isLoggedInProvider = StreamProvider<bool>((ref) async* {
final client = await ref.watch(matrixClientProvider.future);
yield client.isLogged();
await for (final loginState in client.onLoginStateChanged.stream) {
yield loginState == mx.LoginState.loggedIn;
}
});