mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 03:42:13 +00:00
Seamless recovery for iOS reinstallations
This commit is contained in:
parent
7634177191
commit
4d39eb0bf4
16 changed files with 594 additions and 48 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## 0.2.11
|
## 0.2.11
|
||||||
|
|
||||||
|
- New: Seamless recovery for iOS reinstallations
|
||||||
- Improved: Redesigned snackbar notifications
|
- Improved: Redesigned snackbar notifications
|
||||||
- Improved: New backup mechanism to allow larger backup files
|
- Improved: New backup mechanism to allow larger backup files
|
||||||
- Improved: Move keys into a centralized Rust-owned structure stored in secure storage
|
- Improved: Move keys into a centralized Rust-owned structure stored in secure storage
|
||||||
|
|
|
||||||
21
lib/app.dart
21
lib/app.dart
|
|
@ -15,14 +15,20 @@ import 'package:twonly/src/visual/themes/dark.dart';
|
||||||
import 'package:twonly/src/visual/themes/light.dart';
|
import 'package:twonly/src/visual/themes/light.dart';
|
||||||
import 'package:twonly/src/visual/views/critical_error.view.dart';
|
import 'package:twonly/src/visual/views/critical_error.view.dart';
|
||||||
import 'package:twonly/src/visual/views/home.view.dart';
|
import 'package:twonly/src/visual/views/home.view.dart';
|
||||||
|
import 'package:twonly/src/visual/views/recovery.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
|
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
||||||
import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
|
import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
|
||||||
|
|
||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
const App({required this.storageError, super.key});
|
const App({
|
||||||
|
required this.storageError,
|
||||||
|
required this.recoveryPossible,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
final bool storageError;
|
final bool storageError;
|
||||||
|
final bool recoveryPossible;
|
||||||
@override
|
@override
|
||||||
State<App> createState() => _AppState();
|
State<App> createState() => _AppState();
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +94,19 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.recoveryPossible) {
|
||||||
|
return MaterialApp(
|
||||||
|
localizationsDelegates: localizationsDelegates,
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
supportedLocales: supportedLocales,
|
||||||
|
title: 'twonly',
|
||||||
|
theme: lightTheme,
|
||||||
|
darkTheme: darkTheme,
|
||||||
|
themeMode: context.read<SettingsChangeProvider>().themeMode,
|
||||||
|
home: const RecoveryView(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
routerConfig: routerProvider,
|
routerConfig: routerProvider,
|
||||||
localizationsDelegates: localizationsDelegates,
|
localizationsDelegates: localizationsDelegates,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ class RustKeyManager {
|
||||||
.api
|
.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity();
|
.crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity();
|
||||||
|
|
||||||
|
static Future<PlatformInt64?> getUserId() => RustLib.instance.api
|
||||||
|
.crateBridgeWrapperKeyManagerRustKeyManagerGetUserId();
|
||||||
|
|
||||||
static Future<void> importSignalIdentity({
|
static Future<void> importSignalIdentity({
|
||||||
required List<int> identityKeyPairStructure,
|
required List<int> identityKeyPairStructure,
|
||||||
required PlatformInt64 registrationId,
|
required PlatformInt64 registrationId,
|
||||||
|
|
@ -40,6 +43,9 @@ class RustKeyManager {
|
||||||
.api
|
.api
|
||||||
.crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys();
|
.crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys();
|
||||||
|
|
||||||
|
static Future<void> removeKeyManager() => RustLib.instance.api
|
||||||
|
.crateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManager();
|
||||||
|
|
||||||
static Future<void> removeSignedPrekey({
|
static Future<void> removeSignedPrekey({
|
||||||
required PlatformInt64 signedPreKeyId,
|
required PlatformInt64 signedPreKeyId,
|
||||||
}) => RustLib.instance.api
|
}) => RustLib.instance.api
|
||||||
|
|
@ -47,6 +53,11 @@ class RustKeyManager {
|
||||||
signedPreKeyId: signedPreKeyId,
|
signedPreKeyId: signedPreKeyId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static Future<void> setUserId({required PlatformInt64 userId}) => RustLib
|
||||||
|
.instance
|
||||||
|
.api
|
||||||
|
.crateBridgeWrapperKeyManagerRustKeyManagerSetUserId(userId: userId);
|
||||||
|
|
||||||
static Future<void> storeSignedPrekey({
|
static Future<void> storeSignedPrekey({
|
||||||
required PlatformInt64 signedPreKeyId,
|
required PlatformInt64 signedPreKeyId,
|
||||||
required List<int> record,
|
required List<int> record,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||||
String get codegenVersion => '2.12.0';
|
String get codegenVersion => '2.12.0';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get rustContentHash => 1215442517;
|
int get rustContentHash => -1867463121;
|
||||||
|
|
||||||
static const kDefaultExternalLibraryLoaderConfig =
|
static const kDefaultExternalLibraryLoaderConfig =
|
||||||
ExternalLibraryLoaderConfig(
|
ExternalLibraryLoaderConfig(
|
||||||
|
|
@ -200,6 +200,8 @@ abstract class RustLibApi extends BaseApi {
|
||||||
Future<(Uint8List, PlatformInt64)>
|
Future<(Uint8List, PlatformInt64)>
|
||||||
crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity();
|
crateBridgeWrapperKeyManagerRustKeyManagerGetSignalIdentity();
|
||||||
|
|
||||||
|
Future<PlatformInt64?> crateBridgeWrapperKeyManagerRustKeyManagerGetUserId();
|
||||||
|
|
||||||
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity({
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity({
|
||||||
required List<int> identityKeyPairStructure,
|
required List<int> identityKeyPairStructure,
|
||||||
required PlatformInt64 registrationId,
|
required PlatformInt64 registrationId,
|
||||||
|
|
@ -214,10 +216,16 @@ abstract class RustLibApi extends BaseApi {
|
||||||
Future<Map<PlatformInt64, Uint8List>>
|
Future<Map<PlatformInt64, Uint8List>>
|
||||||
crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys();
|
crateBridgeWrapperKeyManagerRustKeyManagerLoadSignedPrekeys();
|
||||||
|
|
||||||
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManager();
|
||||||
|
|
||||||
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey({
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey({
|
||||||
required PlatformInt64 signedPreKeyId,
|
required PlatformInt64 signedPreKeyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerSetUserId({
|
||||||
|
required PlatformInt64 userId,
|
||||||
|
});
|
||||||
|
|
||||||
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey({
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey({
|
||||||
required PlatformInt64 signedPreKeyId,
|
required PlatformInt64 signedPreKeyId,
|
||||||
required List<int> record,
|
required List<int> record,
|
||||||
|
|
@ -1035,6 +1043,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
argNames: [],
|
argNames: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PlatformInt64?> crateBridgeWrapperKeyManagerRustKeyManagerGetUserId() {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 20,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_opt_box_autoadd_i_64,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
|
constMeta:
|
||||||
|
kCrateBridgeWrapperKeyManagerRustKeyManagerGetUserIdConstMeta,
|
||||||
|
argValues: [],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta
|
||||||
|
get kCrateBridgeWrapperKeyManagerRustKeyManagerGetUserIdConstMeta =>
|
||||||
|
const TaskConstMeta(
|
||||||
|
debugName: "rust_key_manager_get_user_id",
|
||||||
|
argNames: [],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity({
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerImportSignalIdentity({
|
||||||
required List<int> identityKeyPairStructure,
|
required List<int> identityKeyPairStructure,
|
||||||
|
|
@ -1054,7 +1094,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 20,
|
funcId: 21,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1098,7 +1138,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 21,
|
funcId: 22,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1131,7 +1171,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 22,
|
funcId: 23,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1154,6 +1194,38 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
argNames: [],
|
argNames: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManager() {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 24,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
|
constMeta:
|
||||||
|
kCrateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManagerConstMeta,
|
||||||
|
argValues: [],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta
|
||||||
|
get kCrateBridgeWrapperKeyManagerRustKeyManagerRemoveKeyManagerConstMeta =>
|
||||||
|
const TaskConstMeta(
|
||||||
|
debugName: "rust_key_manager_remove_key_manager",
|
||||||
|
argNames: [],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey({
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerRemoveSignedPrekey({
|
||||||
required PlatformInt64 signedPreKeyId,
|
required PlatformInt64 signedPreKeyId,
|
||||||
|
|
@ -1166,7 +1238,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 23,
|
funcId: 25,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -1189,6 +1261,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
argNames: ["signedPreKeyId"],
|
argNames: ["signedPreKeyId"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerSetUserId({
|
||||||
|
required PlatformInt64 userId,
|
||||||
|
}) {
|
||||||
|
return handler.executeNormal(
|
||||||
|
NormalTask(
|
||||||
|
callFfi: (port_) {
|
||||||
|
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||||
|
sse_encode_i_64(userId, serializer);
|
||||||
|
pdeCallFfi(
|
||||||
|
generalizedFrbRustBinding,
|
||||||
|
serializer,
|
||||||
|
funcId: 26,
|
||||||
|
port: port_,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
codec: SseCodec(
|
||||||
|
decodeSuccessData: sse_decode_unit,
|
||||||
|
decodeErrorData: sse_decode_AnyhowException,
|
||||||
|
),
|
||||||
|
constMeta:
|
||||||
|
kCrateBridgeWrapperKeyManagerRustKeyManagerSetUserIdConstMeta,
|
||||||
|
argValues: [userId],
|
||||||
|
apiImpl: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskConstMeta
|
||||||
|
get kCrateBridgeWrapperKeyManagerRustKeyManagerSetUserIdConstMeta =>
|
||||||
|
const TaskConstMeta(
|
||||||
|
debugName: "rust_key_manager_set_user_id",
|
||||||
|
argNames: ["userId"],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey({
|
Future<void> crateBridgeWrapperKeyManagerRustKeyManagerStoreSignedPrekey({
|
||||||
required PlatformInt64 signedPreKeyId,
|
required PlatformInt64 signedPreKeyId,
|
||||||
|
|
@ -1203,7 +1310,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
pdeCallFfi(
|
pdeCallFfi(
|
||||||
generalizedFrbRustBinding,
|
generalizedFrbRustBinding,
|
||||||
serializer,
|
serializer,
|
||||||
funcId: 24,
|
funcId: 27,
|
||||||
port: port_,
|
port: port_,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
@ -90,6 +88,8 @@ void main() async {
|
||||||
|
|
||||||
var userExists = false;
|
var userExists = false;
|
||||||
|
|
||||||
|
var recoveryPossible = false;
|
||||||
|
|
||||||
if (!storageError) {
|
if (!storageError) {
|
||||||
try {
|
try {
|
||||||
userExists = await userService.tryInit();
|
userExists = await userService.tryInit();
|
||||||
|
|
@ -99,12 +99,14 @@ void main() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isIOS && userExists) {
|
if (!userExists && !storageError) {
|
||||||
final dbFile = File('${AppEnvironment.supportDir}/twonly.sqlite');
|
try {
|
||||||
if (!dbFile.existsSync()) {
|
final userId = await RustKeyManager.getUserId();
|
||||||
Log.error('[twonly] IOS: App was removed and then reinstalled again...');
|
if (userId != null) {
|
||||||
await SecureStorage.instance.deleteAll();
|
recoveryPossible = true;
|
||||||
userExists = false;
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Could not check KeyManager userId for iOS recovery: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,7 +154,10 @@ void main() async {
|
||||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => PurchasesProvider()),
|
ChangeNotifierProvider(create: (_) => PurchasesProvider()),
|
||||||
],
|
],
|
||||||
child: App(storageError: storageError),
|
child: App(
|
||||||
|
storageError: storageError,
|
||||||
|
recoveryPossible: recoveryPossible,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3079,6 +3079,42 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Click here to open the app again'**
|
/// **'Click here to open the app again'**
|
||||||
String get recoverSuccessBody;
|
String get recoverSuccessBody;
|
||||||
|
|
||||||
|
/// No description provided for @iosRecoveryWelcomeBack.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Welcome Back'**
|
||||||
|
String get iosRecoveryWelcomeBack;
|
||||||
|
|
||||||
|
/// No description provided for @iosRecoveryPrompt.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'We detected a previously secured twonly identity on this device. Would you like to automatically download and restore your contacts, messages, and settings from your cloud archive?'**
|
||||||
|
String get iosRecoveryPrompt;
|
||||||
|
|
||||||
|
/// No description provided for @iosRecoveryNoBackupFound.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No backup archive could be retrieved from the server for this device.\n\nError: {error}\n\nPlease proceed to register a new twonly account.'**
|
||||||
|
String iosRecoveryNoBackupFound(Object error);
|
||||||
|
|
||||||
|
/// No description provided for @registerNewAccount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Register New Account'**
|
||||||
|
String get registerNewAccount;
|
||||||
|
|
||||||
|
/// No description provided for @tryRestoreAgain.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Try Restore Again'**
|
||||||
|
String get tryRestoreAgain;
|
||||||
|
|
||||||
|
/// No description provided for @registeringNewAccount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Registering new account'**
|
||||||
|
String get registeringNewAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1736,4 +1736,25 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get recoverSuccessBody => 'Klicke hier, um die App wieder zu öffnen';
|
String get recoverSuccessBody => 'Klicke hier, um die App wieder zu öffnen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iosRecoveryWelcomeBack => 'Willkommen zurück';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iosRecoveryPrompt =>
|
||||||
|
'Wir haben eine zuvor gesicherte twonly-Identität auf diesem Gerät erkannt. Möchtest du deine Kontakte, Nachrichten und Einstellungen automatisch aus deinem Cloud-Archiv herunterladen und wiederherstellen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String iosRecoveryNoBackupFound(Object error) {
|
||||||
|
return 'Für dieses Gerät konnte kein Backup-Archiv vom Server abgerufen werden.\n\nFehler: $error\n\nBitte fahre mit der Registrierung eines neuen twonly-Kontos fort.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerNewAccount => 'Neues Konto registrieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tryRestoreAgain => 'Wiederherstellung erneut versuchen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registeringNewAccount => 'Neues Konto wird registriert';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1721,4 +1721,25 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get recoverSuccessBody => 'Click here to open the app again';
|
String get recoverSuccessBody => 'Click here to open the app again';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iosRecoveryWelcomeBack => 'Welcome Back';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iosRecoveryPrompt =>
|
||||||
|
'We detected a previously secured twonly identity on this device. Would you like to automatically download and restore your contacts, messages, and settings from your cloud archive?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String iosRecoveryNoBackupFound(Object error) {
|
||||||
|
return 'No backup archive could be retrieved from the server for this device.\n\nError: $error\n\nPlease proceed to register a new twonly account.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registerNewAccount => 'Register New Account';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tryRestoreAgain => 'Try Restore Again';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get registeringNewAccount => 'Registering new account';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 65bf6a4d161bfa0cd2db698446c58b4cd03db92c
|
Subproject commit 75b97e912f2e72a8e2a5da65e8ad12f0d1091855
|
||||||
|
|
@ -7,6 +7,7 @@ import 'package:clock/clock.dart' as clock;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:twonly/core/bridge/wrapper/backup.dart';
|
import 'package:twonly/core/bridge/wrapper/backup.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||||
|
|
@ -102,23 +103,23 @@ class BackupService {
|
||||||
backup.identityLastSuccessFull!.isBefore(
|
backup.identityLastSuccessFull!.isBefore(
|
||||||
lastWeek.subtract(const Duration(days: 1)),
|
lastWeek.subtract(const Duration(days: 1)),
|
||||||
))) {
|
))) {
|
||||||
Log.info('Performing a identity backup.');
|
|
||||||
final encryptedBackup =
|
|
||||||
await RustBackupIdentity.getIdentityBackupBytes();
|
|
||||||
|
|
||||||
final backupTempFile = File(
|
|
||||||
'${AppEnvironment.cacheDir}/identity_backup.bin',
|
|
||||||
)..writeAsBytesSync(encryptedBackup);
|
|
||||||
|
|
||||||
Log.info(
|
|
||||||
'Identity backup has a size of ${backupTempFile.statSync().size}.',
|
|
||||||
);
|
|
||||||
|
|
||||||
final backupId = await RustBackupIdentity.getBackupId();
|
final backupId = await RustBackupIdentity.getBackupId();
|
||||||
if (backupId == null) {
|
if (backupId == null) {
|
||||||
Log.error('Got empty backup id.');
|
Log.error('No backup password was set by the user.');
|
||||||
backup.identityState = LastBackupUploadState.failed;
|
backup.identityState = LastBackupUploadState.failed;
|
||||||
} else {
|
} else {
|
||||||
|
Log.info('Performing a identity backup.');
|
||||||
|
final encryptedBackup =
|
||||||
|
await RustBackupIdentity.getIdentityBackupBytes();
|
||||||
|
|
||||||
|
final backupTempFile = File(
|
||||||
|
'${AppEnvironment.cacheDir}/identity_backup.bin',
|
||||||
|
)..writeAsBytesSync(encryptedBackup);
|
||||||
|
|
||||||
|
Log.info(
|
||||||
|
'Identity backup has a size of ${backupTempFile.statSync().size}.',
|
||||||
|
);
|
||||||
|
|
||||||
final task = UploadTask.fromFile(
|
final task = UploadTask.fromFile(
|
||||||
taskId: 'backup_identity',
|
taskId: 'backup_identity',
|
||||||
httpRequestMethod: 'PUT',
|
httpRequestMethod: 'PUT',
|
||||||
|
|
@ -290,6 +291,19 @@ class BackupService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<RecoveryError?> tryToReinstallTheArchive() async {
|
||||||
|
final userId = await RustKeyManager.getUserId();
|
||||||
|
if (userId == null) return null;
|
||||||
|
|
||||||
|
final state = BackupRecovery(
|
||||||
|
username: '',
|
||||||
|
userId: userId,
|
||||||
|
password: '',
|
||||||
|
)..state = BackupRecoveryState.archiveBackupStarted;
|
||||||
|
await KeyValueStore.put(KeyValueKeys.backupRecoveryState, state.toJson());
|
||||||
|
return _nextBackupStage();
|
||||||
|
}
|
||||||
|
|
||||||
static Future<RecoveryError?> startFullBackupRecovery(
|
static Future<RecoveryError?> startFullBackupRecovery(
|
||||||
String username,
|
String username,
|
||||||
String password,
|
String password,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
import 'package:twonly/src/constants/secure_storage.keys.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.model.dart';
|
import 'package:twonly/src/model/json/userdata.model.dart';
|
||||||
|
|
@ -30,7 +31,9 @@ class UserService {
|
||||||
// 1. Try to load from KeyValueStore (user.json)
|
// 1. Try to load from KeyValueStore (user.json)
|
||||||
final userDataMap = await KeyValueStore.get('user');
|
final userDataMap = await KeyValueStore.get('user');
|
||||||
if (userDataMap != null) {
|
if (userDataMap != null) {
|
||||||
return UserData.fromJson(userDataMap);
|
final userData = UserData.fromJson(userDataMap);
|
||||||
|
await RustKeyManager.setUserId(userId: userData.userId);
|
||||||
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. If not found, try to load from SecureStorage (Migration path)
|
// 2. If not found, try to load from SecureStorage (Migration path)
|
||||||
|
|
@ -58,6 +61,11 @@ class UserService {
|
||||||
static Future<void> _migrateFromSecureStorage(UserData userData) async {
|
static Future<void> _migrateFromSecureStorage(UserData userData) async {
|
||||||
// Currently empty migration logic as requested, but we MUST store the data
|
// Currently empty migration logic as requested, but we MUST store the data
|
||||||
await KeyValueStore.put('user', userData.toJson());
|
await KeyValueStore.put('user', userData.toJson());
|
||||||
|
try {
|
||||||
|
await RustKeyManager.setUserId(userId: userData.userId);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Could not set userId in RustKeyManager during migration: $e');
|
||||||
|
}
|
||||||
|
|
||||||
// Optional: Log migration
|
// Optional: Log migration
|
||||||
Log.info('Migrated user data from SecureStorage to KeyValueStore');
|
Log.info('Migrated user data from SecureStorage to KeyValueStore');
|
||||||
|
|
@ -87,6 +95,11 @@ class UserService {
|
||||||
|
|
||||||
static Future<void> save(UserData user) async {
|
static Future<void> save(UserData user) async {
|
||||||
await KeyValueStore.put('user', user.toJson());
|
await KeyValueStore.put('user', user.toJson());
|
||||||
|
try {
|
||||||
|
await RustKeyManager.setUserId(userId: user.userId);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Could not set userId in RustKeyManager during save: $e');
|
||||||
|
}
|
||||||
await userService.tryInit();
|
await userService.tryInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
178
lib/src/visual/views/recovery.view.dart
Normal file
178
lib/src/visual/views/recovery.view.dart
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:restart_app/restart_app.dart';
|
||||||
|
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/services/backup.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
class RecoveryView extends StatefulWidget {
|
||||||
|
const RecoveryView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RecoveryView> createState() => _RecoveryViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecoveryViewState extends State<RecoveryView> {
|
||||||
|
bool _isLoading = false;
|
||||||
|
String? _errorMessage;
|
||||||
|
bool _showRegisterNewPrompt = false;
|
||||||
|
|
||||||
|
Future<void> _startRestore() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
final error = await BackupService.tryToReinstallTheArchive();
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
String msg;
|
||||||
|
switch (error) {
|
||||||
|
case RecoveryError.noInternet:
|
||||||
|
msg = context.lang.recoverErrorNoInternet;
|
||||||
|
case RecoveryError.usernameNotValid:
|
||||||
|
msg = context.lang.recoverErrorUsernameNotValid;
|
||||||
|
case RecoveryError.passwordInvalid:
|
||||||
|
msg = context.lang.recoverErrorPasswordInvalid;
|
||||||
|
case RecoveryError.tryAgainLater:
|
||||||
|
msg = context.lang.recoverErrorTryAgainLater;
|
||||||
|
case RecoveryError.unkownError:
|
||||||
|
msg = context.lang.recoverErrorUnknown;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_errorMessage = msg;
|
||||||
|
_showRegisterNewPrompt = true;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final userExists = await userService.tryInit();
|
||||||
|
if (userExists && mounted) {
|
||||||
|
await Restart.restartApp(
|
||||||
|
notificationTitle: context.lang.recoverSuccessTitle,
|
||||||
|
notificationBody: context.lang.recoverSuccessBody,
|
||||||
|
forceKill: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
_errorMessage = context.lang.recoverErrorUnknown;
|
||||||
|
_showRegisterNewPrompt = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _registerNewAccount() async {
|
||||||
|
try {
|
||||||
|
await RustKeyManager.removeKeyManager();
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('Could not remove KeyManager during account reset: $e');
|
||||||
|
}
|
||||||
|
await deleteLocalUserData();
|
||||||
|
if (!mounted) return;
|
||||||
|
await Restart.restartApp(
|
||||||
|
notificationTitle: 'twonly',
|
||||||
|
notificationBody: context.lang.registeringNewAccount,
|
||||||
|
forceKill: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.lang.twonlySafeRecoverTitle),
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 100),
|
||||||
|
Center(
|
||||||
|
child: FaIcon(
|
||||||
|
FontAwesomeIcons.cloudArrowDown,
|
||||||
|
size: 80,
|
||||||
|
color: context.color.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
context.lang.iosRecoveryWelcomeBack,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
_showRegisterNewPrompt
|
||||||
|
? context.lang.iosRecoveryNoBackupFound(
|
||||||
|
_errorMessage ?? '',
|
||||||
|
)
|
||||||
|
: context.lang.iosRecoveryPrompt,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
if (!_showRegisterNewPrompt) ...[
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _isLoading ? null : _startRestore,
|
||||||
|
icon: _isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
width: 16,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
|
)
|
||||||
|
: const Icon(Icons.restore_rounded),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
context.lang.twonlySafeRecoverBtn,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isLoading ? null : _registerNewAccount,
|
||||||
|
child: Text(context.lang.registerNewAccount),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _registerNewAccount,
|
||||||
|
icon: const Icon(Icons.person_add_rounded),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 32,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
context.lang.registerNewAccount,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: _startRestore,
|
||||||
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
|
label: Text(context.lang.tryRestoreAgain),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,19 @@ impl RustKeyManager {
|
||||||
Ok(key_manager.main_key.get_login_token().to_vec())
|
Ok(key_manager.main_key.get_login_token().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_id() -> Result<Option<i64>> {
|
||||||
|
let key_manager = get_twonly_flutter()?.key_manager.lock().await;
|
||||||
|
Ok(key_manager.user_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_user_id(user_id: i64) -> Result<()> {
|
||||||
|
let ctx = get_twonly_flutter()?;
|
||||||
|
let mut key_manager = ctx.key_manager.lock().await;
|
||||||
|
key_manager.user_id = Some(user_id);
|
||||||
|
key_manager.store_to_keychain(&ctx.secure_storage)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn import_signal_identity(
|
pub async fn import_signal_identity(
|
||||||
identity_key_pair_structure: Vec<u8>,
|
identity_key_pair_structure: Vec<u8>,
|
||||||
registration_id: i64,
|
registration_id: i64,
|
||||||
|
|
@ -89,4 +102,10 @@ impl RustKeyManager {
|
||||||
Err(TwonlyError::SignalIdentityNotFound)
|
Err(TwonlyError::SignalIdentityNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn remove_key_manager() -> Result<()> {
|
||||||
|
let ctx = get_twonly_flutter()?;
|
||||||
|
crate::keys::KeyManager::remove_from_keychain(&ctx.secure_storage)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
||||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||||
);
|
);
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1215442517;
|
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1867463121;
|
||||||
|
|
||||||
// Section: executor
|
// Section: executor
|
||||||
|
|
||||||
|
|
@ -424,6 +424,43 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identi
|
||||||
})().await)
|
})().await)
|
||||||
} })
|
} })
|
||||||
}
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "rust_key_manager_get_user_id",
|
||||||
|
port: Some(port_),
|
||||||
|
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||||
|
},
|
||||||
|
move || {
|
||||||
|
let message = unsafe {
|
||||||
|
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||||
|
ptr_,
|
||||||
|
rust_vec_len_,
|
||||||
|
data_len_,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut deserializer =
|
||||||
|
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| async move {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
|
(move || async move {
|
||||||
|
let output_ok =
|
||||||
|
crate::bridge::wrapper::key_manager::RustKeyManager::get_user_id()
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -471,6 +508,21 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_preke
|
||||||
})().await)
|
})().await)
|
||||||
} })
|
} })
|
||||||
}
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec,_,_,_>(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "rust_key_manager_remove_key_manager", port: Some(port_), mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal }, move || {
|
||||||
|
let message = unsafe { flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(ptr_, rust_vec_len_, data_len_) };
|
||||||
|
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
deserializer.end(); move |context| async move {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move {
|
||||||
|
let output_ok = crate::bridge::wrapper::key_manager::RustKeyManager::remove_key_manager().await?; Ok(output_ok)
|
||||||
|
})().await)
|
||||||
|
} })
|
||||||
|
}
|
||||||
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -486,6 +538,46 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_pre
|
||||||
})().await)
|
})().await)
|
||||||
} })
|
} })
|
||||||
}
|
}
|
||||||
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(
|
||||||
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
rust_vec_len_: i32,
|
||||||
|
data_len_: i32,
|
||||||
|
) {
|
||||||
|
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||||
|
flutter_rust_bridge::for_generated::TaskInfo {
|
||||||
|
debug_name: "rust_key_manager_set_user_id",
|
||||||
|
port: Some(port_),
|
||||||
|
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||||
|
},
|
||||||
|
move || {
|
||||||
|
let message = unsafe {
|
||||||
|
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||||
|
ptr_,
|
||||||
|
rust_vec_len_,
|
||||||
|
data_len_,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut deserializer =
|
||||||
|
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||||
|
let api_user_id = <i64>::sse_decode(&mut deserializer);
|
||||||
|
deserializer.end();
|
||||||
|
move |context| async move {
|
||||||
|
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||||
|
(move || async move {
|
||||||
|
let output_ok =
|
||||||
|
crate::bridge::wrapper::key_manager::RustKeyManager::set_user_id(
|
||||||
|
api_user_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(output_ok)
|
||||||
|
})()
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(
|
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(
|
||||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||||
|
|
@ -1327,11 +1419,14 @@ fn pde_ffi_dispatcher_primary_impl(
|
||||||
17 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
17 => wire__crate__bridge__wrapper__backup__rust_backup_identity_set_backup_password_keys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
18 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len),
|
18 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_login_token_impl(port, ptr, rust_vec_len, data_len),
|
||||||
19 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
19 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
||||||
20 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
20 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_get_user_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
21 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
21 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_import_signal_identity_impl(port, ptr, rust_vec_len, data_len),
|
||||||
22 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
22 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
23 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
23 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_load_signed_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
||||||
24 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
24 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_key_manager_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
25 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
26 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len),
|
||||||
|
27 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,4 +53,10 @@ impl KeyManager {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the KeyManager from the secure keychain/local storage.
|
||||||
|
pub fn remove_from_keychain(storage: &SecureStorage) -> Result<()> {
|
||||||
|
storage.delete(KEY_MANAGER_ID)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,15 +92,15 @@ impl SecureStorage {
|
||||||
/// Deletes the secret associated with the given key from the secure keyring.
|
/// Deletes the secret associated with the given key from the secure keyring.
|
||||||
///
|
///
|
||||||
/// If the key does not exist, this function returns `Ok(())` (idempotent).
|
/// If the key does not exist, this function returns `Ok(())` (idempotent).
|
||||||
// pub fn delete(&self, key: &str) -> Result<(), String> {
|
pub fn delete(&self, key: &str) -> Result<(), String> {
|
||||||
// let entry = self.get_entry(key)?;
|
let entry = self.get_entry(key)?;
|
||||||
|
|
||||||
// match entry.delete_credential() {
|
match entry.delete_credential() {
|
||||||
// Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
// Err(KeyringError::NoEntry) => Ok(()),
|
Err(KeyringError::NoEntry) => Ok(()),
|
||||||
// Err(e) => Err(format!("Failed to delete secret from keyring: {}", e)),
|
Err(e) => Err(format!("Failed to delete secret from keyring: {}", e)),
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
/// Helper to create a keyring entry with the appropriate platform modifiers.
|
/// Helper to create a keyring entry with the appropriate platform modifiers.
|
||||||
fn get_entry(&self, key: &str) -> Result<Entry, String> {
|
fn get_entry(&self, key: &str) -> Result<Entry, String> {
|
||||||
|
|
@ -142,10 +142,10 @@ mod tests {
|
||||||
assert_eq!(read_val, Some(secret.to_string()));
|
assert_eq!(read_val, Some(secret.to_string()));
|
||||||
|
|
||||||
// 3. Delete the secret
|
// 3. Delete the secret
|
||||||
// storage.delete(key).expect("Failed to delete secret");
|
storage.delete(key).expect("Failed to delete secret");
|
||||||
|
|
||||||
// 4. Verify the secret is gone
|
// 4. Verify the secret is gone
|
||||||
// let after_delete = storage.read(key).expect("Failed to read after delete");
|
let after_delete = storage.read(key).expect("Failed to read after delete");
|
||||||
// assert_eq!(after_delete, None);
|
assert_eq!(after_delete, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue