- LiveKit call E2EE: CallE2EEManager exchanges encryption keys via Matrix to-device events (m.rtc.encryption_keys) for interop with Element X - Olm bootstrapped in index.html before Flutter init; main.dart logs result - Encrypted messages shown with lock icon and informative fallback text - Profile screen: key restore dialog + security setup (cross-signing/backup) - Jitsi feature: welcome screen (public, no login), conference tab, full-screen embed via JitsiMeetExternalAPI, JitsiLink parser for all common link formats - Help tab: expandable cards for encryption, video calls, account management - Web security model: no session persistence — device ID only across visits - Media auth: MSC3916 authenticated endpoint for avatars (Synapse 1.120+) - Router: welcome route as public landing page; jitsi route as public - Manifest/index.html: M8Chat branding, dark theme colours Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.9 KiB
Dart
84 lines
2.9 KiB
Dart
// Version: 2.0.0 | Created: 2026-04-01 | Updated: 2026-04-11
|
|
// Auth repository: handles all Matrix login/logout API interactions.
|
|
// Uses the Matrix Dart SDK — no raw HTTP calls for auth.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:matrix/matrix.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
import '../../../core/config/app_config.dart';
|
|
import '../../../core/network/matrix_client.dart';
|
|
import '../domain/auth_failure.dart';
|
|
|
|
part 'auth_repository.g.dart';
|
|
|
|
@Riverpod(keepAlive: true)
|
|
AuthRepository authRepository(Ref ref) {
|
|
return AuthRepository(client: ref.watch(matrixClientProvider));
|
|
}
|
|
|
|
/// Handles authentication interactions with the Matrix homeserver.
|
|
class AuthRepository {
|
|
AuthRepository({required Client client}) : _client = client;
|
|
|
|
final Client _client;
|
|
|
|
/// Attempts password login. Returns [LoginResponse] on success,
|
|
/// throws [AuthFailure] on failure.
|
|
///
|
|
/// Matrix error codes mapped:
|
|
/// M_FORBIDDEN → [InvalidCredentials]
|
|
/// M_USER_DEACTIVATED → [AccountDisabled]
|
|
/// Network error → [NetworkError]
|
|
Future<LoginResponse> login({
|
|
required String username,
|
|
required String password,
|
|
}) async {
|
|
try {
|
|
_client.homeserver = Uri.parse(AppConfig.matrixBaseUrl);
|
|
final response = await _client.login(
|
|
LoginType.mLoginPassword,
|
|
identifier: AuthenticationUserIdentifier(user: username),
|
|
password: password,
|
|
initialDeviceDisplayName: AppConfig.appName,
|
|
);
|
|
debugPrint('[M8Chat] Login complete. encryptionEnabled=${_client.encryptionEnabled}');
|
|
debugPrint('[M8Chat] encryption=${_client.encryption}');
|
|
return response;
|
|
} on MatrixException catch (e) {
|
|
throw switch (e.errcode) {
|
|
'M_FORBIDDEN' => const AuthFailure.invalidCredentials(),
|
|
'M_USER_DEACTIVATED' => const AuthFailure.accountDisabled(),
|
|
_ => AuthFailure.serverError(
|
|
statusCode: e.response?.statusCode,
|
|
message: e.errorMessage,
|
|
),
|
|
};
|
|
} on Exception catch (e) {
|
|
// Covers SocketException, TimeoutException, etc.
|
|
final msg = e.toString().toLowerCase();
|
|
if (msg.contains('socket') ||
|
|
msg.contains('connection') ||
|
|
msg.contains('host lookup') ||
|
|
msg.contains('timeout')) {
|
|
throw AuthFailure.networkError(message: e.toString());
|
|
}
|
|
throw AuthFailure.unknown(message: e.toString());
|
|
}
|
|
}
|
|
|
|
/// Logs out the current session on the homeserver.
|
|
/// Silently succeeds if the token is already invalid (network-first logout).
|
|
Future<void> logout() async {
|
|
try {
|
|
await _client.logout();
|
|
} on MatrixException {
|
|
// Token already invalid — treat as successful logout.
|
|
} on Exception {
|
|
// Network offline — proceed with local cleanup regardless.
|
|
}
|
|
}
|
|
|
|
// restoreSession() removed — web app requires fresh login every visit.
|
|
}
|