persist login securely

This commit is contained in:
Henry Hiles 2025-11-18 14:24:09 -05:00
commit 111a875529
No known key found for this signature in database
6 changed files with 394 additions and 15 deletions

View file

@ -1,24 +1,50 @@
import "dart:convert";
import "package:flutter/foundation.dart";
import "package:matrix/matrix.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/secure_storage_controller.dart";
import "package:nexus/models/session_backup.dart";
import "package:path/path.dart";
import "package:path_provider/path_provider.dart";
import "package:sqflite_common_ffi/sqflite_ffi.dart";
class ClientController extends AsyncNotifier<Client> {
static const sessionBackupKey = "sessionBackup";
@override
Future<Client> build() async => Client(
"nexus",
logLevel: kReleaseMode ? Level.warning : Level.verbose,
importantStateEvents: {"im.ponies.room_emotes"},
supportedLoginTypes: {AuthenticationTypes.password},
database: await MatrixSdkDatabase.init(
Future<Client> build() async {
final client = Client(
"nexus",
database: await databaseFactoryFfi.openDatabase(
join((await getApplicationSupportDirectory()).path, "database.db"),
logLevel: kReleaseMode ? Level.warning : Level.verbose,
importantStateEvents: {"im.ponies.room_emotes"},
supportedLoginTypes: {AuthenticationTypes.password},
database: await MatrixSdkDatabase.init(
"nexus",
database: await databaseFactoryFfi.openDatabase(
join((await getApplicationSupportDirectory()).path, "database.db"),
),
),
),
);
);
final backupJson = await ref
.watch(SecureStorageController.provider.notifier)
.get(sessionBackupKey);
if (backupJson != null) {
final backup = SessionBackup.fromJson(json.decode(backupJson));
await client.init(
waitForFirstSync: false,
newToken: backup.accessToken,
newHomeserver: backup.homeserver,
newUserID: backup.userID,
newDeviceID: backup.deviceID,
newDeviceName: backup.deviceName,
);
}
return client;
}
Future<bool> setHomeserver(Uri homeserverUrl) async {
final client = await future;
@ -33,13 +59,28 @@ class ClientController extends AsyncNotifier<Client> {
Future<bool> login(String username, String password) async {
final client = await future;
try {
await client.login(
final deviceName =
"Nexus Client login at ${DateTime.now().toIso8601String()}";
final details = await MatrixApi(homeserver: client.homeserver).login(
LoginType.mLoginPassword,
initialDeviceDisplayName:
"Nexus Client login at ${DateTime.now().toIso8601String()}",
initialDeviceDisplayName: deviceName,
identifier: AuthenticationUserIdentifier(user: username),
password: password,
);
await ref
.watch(SecureStorageController.provider.notifier)
.set(
sessionBackupKey,
json.encode(
SessionBackup(
accessToken: details.accessToken,
homeserver: client.homeserver!,
userID: details.userId,
deviceID: details.deviceId,
deviceName: deviceName,
).toJson(),
),
);
ref.invalidateSelf();
return true;
} catch (_) {

View file

@ -1,4 +1,3 @@
import "package:matrix/matrix.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:simple_secure_storage/simple_secure_storage.dart";
@ -6,6 +5,21 @@ class SecureStorageController extends AsyncNotifier<void> {
@override
Future<void> build() => SimpleSecureStorage.initialize();
Future<String?> get(String key) async {
await future;
return SimpleSecureStorage.read(key);
}
Future<void> set(String key, String value) async {
await future;
return SimpleSecureStorage.write(key, value);
}
Future<void> clear() async {
await future;
return SimpleSecureStorage.clear();
}
static final provider = AsyncNotifierProvider<SecureStorageController, void>(
SecureStorageController.new,
);