enable and handle heartbeath

This commit is contained in:
otsmr 2026-06-21 02:07:09 +02:00
parent 6db45a6da8
commit 4d943eca1c
38 changed files with 19135 additions and 261 deletions

View file

@ -35,7 +35,6 @@ If you decide to give twonly a try, please keep in mind that it is still in its
- End-to-End encryption using the [Signal Protocol](https://de.wikipedia.org/wiki/Signal-Protokoll)
- Open Source and can be downloaded directly from GitHub
- No email or phone number required to register
- Privacy friendly - Everything is stored on the device
- The backend is hosted exclusively in Europe
## Roadmap
@ -43,7 +42,6 @@ If you decide to give twonly a try, please keep in mind that it is still in its
### Currently
- Focus on user-friendliness so that people enjoy using the app
- User discovery without a phone number
- Passwordless recovery without a phone number
- Implementation of features so that Snapchat can actually be replaced
- E2EE cloud backup of memories

View file

@ -0,0 +1,173 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.12.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../../bridge.dart';
import '../../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`
class UserDiscoveryStoreFlutter {
const UserDiscoveryStoreFlutter();
Future<AnnouncedUser?> getAnnouncedUserByPublicId({
required PlatformInt64 publicId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetAnnouncedUserByPublicId(
that: this,
publicId: publicId,
);
Future<String> getConfig() => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetConfig(
that: this,
);
Future<Uint8List?> getContactPromotion({
required PlatformInt64 contactId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetContactPromotion(
that: this,
contactId: contactId,
);
Future<Uint8List?> getContactVersion({
required PlatformInt64 contactId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetContactVersion(
that: this,
contactId: contactId,
);
Future<List<OtherPromotion>> getOtherPromotionsByPublicId({
required PlatformInt64 publicId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetOtherPromotionsByPublicId(
that: this,
publicId: publicId,
);
Future<List<Uint8List>> getOwnPromotionsAfterVersion({
required int version,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetOwnPromotionsAfterVersion(
that: this,
version: version,
);
Future<Uint8List> getShareForContact({
required PlatformInt64 contactId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetShareForContact(
that: this,
contactId: contactId,
);
Future<void> pushNewUserRelation({
required PlatformInt64 fromContactId,
required AnnouncedUser announcedUser,
PlatformInt64? publicKeyVerifiedTimestamp,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterPushNewUserRelation(
that: this,
fromContactId: fromContactId,
announcedUser: announcedUser,
publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp,
);
Future<void> pushOwnPromotionAndClearOldVersion({
required PlatformInt64 contactId,
required int version,
required List<int> promotion,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterPushOwnPromotionAndClearOldVersion(
that: this,
contactId: contactId,
version: version,
promotion: promotion,
);
Future<void> setContactVersion({
required PlatformInt64 contactId,
required List<int> update,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterSetContactVersion(
that: this,
contactId: contactId,
update: update,
);
Future<void> setShares({required List<Uint8List> shares}) => RustLib
.instance
.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterSetShares(
that: this,
shares: shares,
);
Future<void> storeOtherPromotion({
required OtherPromotion promotion,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterStoreOtherPromotion(
that: this,
promotion: promotion,
);
Future<void> updateConfig({required String update}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterUpdateConfig(
that: this,
update: update,
);
@override
int get hashCode => 0;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserDiscoveryStoreFlutter && runtimeType == other.runtimeType;
}
class UserDiscoveryUtilsFlutter {
const UserDiscoveryUtilsFlutter();
Future<Uint8List> signData({required List<int> inputData}) => RustLib
.instance
.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryUtilsFlutterSignData(
that: this,
inputData: inputData,
);
Future<bool> verifySignature({
required List<int> inputData,
required List<int> pubkey,
required List<int> signature,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryUtilsFlutterVerifySignature(
that: this,
inputData: inputData,
pubkey: pubkey,
signature: signature,
);
Future<bool> verifyStoredPubkey({
required PlatformInt64 fromContactId,
required List<int> pubkey,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryUtilsFlutterVerifyStoredPubkey(
that: this,
fromContactId: fromContactId,
pubkey: pubkey,
);
@override
int get hashCode => 0;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserDiscoveryUtilsFlutter && runtimeType == other.runtimeType;
}

View file

@ -0,0 +1,37 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.12.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
class RustUtils {
const RustUtils();
static Future<List<Uint8List>> generateShares({
required List<int> secret,
required int total,
required int threshold,
}) => RustLib.instance.api.crateBridgeWrapperRustUtilsGenerateShares(
secret: secret,
total: total,
threshold: threshold,
);
static Future<Uint8List> recoverSecret({
required List<Uint8List> shares,
required int threshold,
}) => RustLib.instance.api.crateBridgeWrapperRustUtilsRecoverSecret(
shares: shares,
threshold: threshold,
);
@override
int get hashCode => 0;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RustUtils && runtimeType == other.runtimeType;
}

View file

@ -53,6 +53,10 @@ class RustKeyManager {
signedPreKeyId: signedPreKeyId,
);
/// Serialize the key_manager. Needed for the passwordless_recovery feature.
static Future<Uint8List> serialize() => RustLib.instance.api
.crateBridgeWrapperKeyManagerRustKeyManagerSerialize();
static Future<void> setUserId({required PlatformInt64 userId}) => RustLib
.instance
.api

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,8 @@
import 'bridge.dart';
import 'bridge/callbacks.dart';
import 'bridge/callbacks/user_discovery.dart';
import 'bridge/wrapper.dart';
import 'bridge/wrapper/backup.dart';
import 'bridge/wrapper/key_manager.dart';
import 'bridge/wrapper/user_discovery.dart';
@ -134,6 +136,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
@protected
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_box_autoadd_user_discovery_utils_flutter(
dynamic raw,
);
@protected
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
@ -205,6 +220,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
@protected
RustUtils dco_decode_rust_utils(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@ -217,6 +235,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void dco_decode_unit(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_user_discovery_utils_flutter(
dynamic raw,
);
@protected
BigInt dco_decode_usize(dynamic raw);
@ -266,6 +294,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
@protected
OtherPromotion sse_decode_box_autoadd_other_promotion(
SseDeserializer deserializer,
);
@protected
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_box_autoadd_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
FlutterUserDiscovery sse_decode_flutter_user_discovery(
SseDeserializer deserializer,
@ -357,6 +400,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
@protected
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@ -369,6 +415,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
UserDiscoveryStoreFlutter sse_decode_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
BigInt sse_decode_usize(SseDeserializer deserializer);
@ -517,6 +573,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_other_promotion(
OtherPromotion self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_flutter_user_discovery(
FlutterUserDiscovery self,
@ -634,6 +708,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@ -646,6 +723,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_usize(BigInt self, SseSerializer serializer);

View file

@ -8,6 +8,8 @@
import 'bridge.dart';
import 'bridge/callbacks.dart';
import 'bridge/callbacks/user_discovery.dart';
import 'bridge/wrapper.dart';
import 'bridge/wrapper/backup.dart';
import 'bridge/wrapper/key_manager.dart';
import 'bridge/wrapper/user_discovery.dart';
@ -136,6 +138,19 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
@protected
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_box_autoadd_user_discovery_utils_flutter(
dynamic raw,
);
@protected
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
@ -207,6 +222,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
@protected
RustUtils dco_decode_rust_utils(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@ -219,6 +237,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void dco_decode_unit(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_user_discovery_utils_flutter(
dynamic raw,
);
@protected
BigInt dco_decode_usize(dynamic raw);
@ -268,6 +296,21 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
@protected
OtherPromotion sse_decode_box_autoadd_other_promotion(
SseDeserializer deserializer,
);
@protected
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_box_autoadd_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
FlutterUserDiscovery sse_decode_flutter_user_discovery(
SseDeserializer deserializer,
@ -359,6 +402,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
@protected
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@ -371,6 +417,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
UserDiscoveryStoreFlutter sse_decode_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
BigInt sse_decode_usize(SseDeserializer deserializer);
@ -519,6 +575,24 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_other_promotion(
OtherPromotion self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_flutter_user_discovery(
FlutterUserDiscovery self,
@ -636,6 +710,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@ -648,6 +725,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_usize(BigInt self, SseSerializer serializer);

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,10 @@ class Contacts extends Table {
DateTimeColumn get recoveryLastHeartbeat => dateTime().nullable()();
BlobColumn get recoverySecretShare => blob().nullable()();
// This is the share from the contact in case the contact has selected this user as his trusted friend.
BlobColumn get recoveryContactsSecretShare => blob().nullable()();
DateTimeColumn get recoveryContactsLastHeartbeat => dateTime().nullable()();
BoolColumn get askForFriendPromotions => boolean().nullable()();
IntColumn get mediaSendCounter => integer().withDefault(const Constant(0))();

View file

@ -82,7 +82,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection);
@override
int get schemaVersion => 20;
int get schemaVersion => 21;
static QueryExecutor _openConnection() {
final connection = driftDatabase(
@ -259,6 +259,16 @@ class TwonlyDB extends _$TwonlyDB {
schema.contacts.recoverySecretShare,
);
},
from20To21: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.recoveryContactsSecretShare,
);
await m.addColumn(
schema.contacts,
schema.contacts.recoveryContactsLastHeartbeat,
);
},
)(m, from, to);
},
);

View file

@ -252,6 +252,28 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
type: DriftSqlType.blob,
requiredDuringInsert: false,
);
static const VerificationMeta _recoveryContactsSecretShareMeta =
const VerificationMeta('recoveryContactsSecretShare');
@override
late final GeneratedColumn<Uint8List> recoveryContactsSecretShare =
GeneratedColumn<Uint8List>(
'recovery_contacts_secret_share',
aliasedName,
true,
type: DriftSqlType.blob,
requiredDuringInsert: false,
);
static const VerificationMeta _recoveryContactsLastHeartbeatMeta =
const VerificationMeta('recoveryContactsLastHeartbeat');
@override
late final GeneratedColumn<DateTime> recoveryContactsLastHeartbeat =
GeneratedColumn<DateTime>(
'recovery_contacts_last_heartbeat',
aliasedName,
true,
type: DriftSqlType.dateTime,
requiredDuringInsert: false,
);
static const VerificationMeta _askForFriendPromotionsMeta =
const VerificationMeta('askForFriendPromotions');
@override
@ -310,6 +332,8 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
recoveryIsTrustedFriend,
recoveryLastHeartbeat,
recoverySecretShare,
recoveryContactsSecretShare,
recoveryContactsLastHeartbeat,
askForFriendPromotions,
mediaSendCounter,
mediaReceivedCounter,
@ -475,6 +499,24 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
),
);
}
if (data.containsKey('recovery_contacts_secret_share')) {
context.handle(
_recoveryContactsSecretShareMeta,
recoveryContactsSecretShare.isAcceptableOrUnknown(
data['recovery_contacts_secret_share']!,
_recoveryContactsSecretShareMeta,
),
);
}
if (data.containsKey('recovery_contacts_last_heartbeat')) {
context.handle(
_recoveryContactsLastHeartbeatMeta,
recoveryContactsLastHeartbeat.isAcceptableOrUnknown(
data['recovery_contacts_last_heartbeat']!,
_recoveryContactsLastHeartbeatMeta,
),
);
}
if (data.containsKey('ask_for_friend_promotions')) {
context.handle(
_askForFriendPromotionsMeta,
@ -587,6 +629,14 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
DriftSqlType.blob,
data['${effectivePrefix}recovery_secret_share'],
),
recoveryContactsSecretShare: attachedDatabase.typeMapping.read(
DriftSqlType.blob,
data['${effectivePrefix}recovery_contacts_secret_share'],
),
recoveryContactsLastHeartbeat: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}recovery_contacts_last_heartbeat'],
),
askForFriendPromotions: attachedDatabase.typeMapping.read(
DriftSqlType.bool,
data['${effectivePrefix}ask_for_friend_promotions'],
@ -628,6 +678,8 @@ class Contact extends DataClass implements Insertable<Contact> {
final bool recoveryIsTrustedFriend;
final DateTime? recoveryLastHeartbeat;
final Uint8List? recoverySecretShare;
final Uint8List? recoveryContactsSecretShare;
final DateTime? recoveryContactsLastHeartbeat;
final bool? askForFriendPromotions;
final int mediaSendCounter;
final int mediaReceivedCounter;
@ -651,6 +703,8 @@ class Contact extends DataClass implements Insertable<Contact> {
required this.recoveryIsTrustedFriend,
this.recoveryLastHeartbeat,
this.recoverySecretShare,
this.recoveryContactsSecretShare,
this.recoveryContactsLastHeartbeat,
this.askForFriendPromotions,
required this.mediaSendCounter,
required this.mediaReceivedCounter,
@ -695,6 +749,16 @@ class Contact extends DataClass implements Insertable<Contact> {
if (!nullToAbsent || recoverySecretShare != null) {
map['recovery_secret_share'] = Variable<Uint8List>(recoverySecretShare);
}
if (!nullToAbsent || recoveryContactsSecretShare != null) {
map['recovery_contacts_secret_share'] = Variable<Uint8List>(
recoveryContactsSecretShare,
);
}
if (!nullToAbsent || recoveryContactsLastHeartbeat != null) {
map['recovery_contacts_last_heartbeat'] = Variable<DateTime>(
recoveryContactsLastHeartbeat,
);
}
if (!nullToAbsent || askForFriendPromotions != null) {
map['ask_for_friend_promotions'] = Variable<bool>(askForFriendPromotions);
}
@ -739,6 +803,14 @@ class Contact extends DataClass implements Insertable<Contact> {
recoverySecretShare: recoverySecretShare == null && nullToAbsent
? const Value.absent()
: Value(recoverySecretShare),
recoveryContactsSecretShare:
recoveryContactsSecretShare == null && nullToAbsent
? const Value.absent()
: Value(recoveryContactsSecretShare),
recoveryContactsLastHeartbeat:
recoveryContactsLastHeartbeat == null && nullToAbsent
? const Value.absent()
: Value(recoveryContactsLastHeartbeat),
askForFriendPromotions: askForFriendPromotions == null && nullToAbsent
? const Value.absent()
: Value(askForFriendPromotions),
@ -788,6 +860,12 @@ class Contact extends DataClass implements Insertable<Contact> {
recoverySecretShare: serializer.fromJson<Uint8List?>(
json['recoverySecretShare'],
),
recoveryContactsSecretShare: serializer.fromJson<Uint8List?>(
json['recoveryContactsSecretShare'],
),
recoveryContactsLastHeartbeat: serializer.fromJson<DateTime?>(
json['recoveryContactsLastHeartbeat'],
),
askForFriendPromotions: serializer.fromJson<bool?>(
json['askForFriendPromotions'],
),
@ -828,6 +906,12 @@ class Contact extends DataClass implements Insertable<Contact> {
recoveryLastHeartbeat,
),
'recoverySecretShare': serializer.toJson<Uint8List?>(recoverySecretShare),
'recoveryContactsSecretShare': serializer.toJson<Uint8List?>(
recoveryContactsSecretShare,
),
'recoveryContactsLastHeartbeat': serializer.toJson<DateTime?>(
recoveryContactsLastHeartbeat,
),
'askForFriendPromotions': serializer.toJson<bool?>(
askForFriendPromotions,
),
@ -856,6 +940,8 @@ class Contact extends DataClass implements Insertable<Contact> {
bool? recoveryIsTrustedFriend,
Value<DateTime?> recoveryLastHeartbeat = const Value.absent(),
Value<Uint8List?> recoverySecretShare = const Value.absent(),
Value<Uint8List?> recoveryContactsSecretShare = const Value.absent(),
Value<DateTime?> recoveryContactsLastHeartbeat = const Value.absent(),
Value<bool?> askForFriendPromotions = const Value.absent(),
int? mediaSendCounter,
int? mediaReceivedCounter,
@ -890,6 +976,12 @@ class Contact extends DataClass implements Insertable<Contact> {
recoverySecretShare: recoverySecretShare.present
? recoverySecretShare.value
: this.recoverySecretShare,
recoveryContactsSecretShare: recoveryContactsSecretShare.present
? recoveryContactsSecretShare.value
: this.recoveryContactsSecretShare,
recoveryContactsLastHeartbeat: recoveryContactsLastHeartbeat.present
? recoveryContactsLastHeartbeat.value
: this.recoveryContactsLastHeartbeat,
askForFriendPromotions: askForFriendPromotions.present
? askForFriendPromotions.value
: this.askForFriendPromotions,
@ -939,6 +1031,12 @@ class Contact extends DataClass implements Insertable<Contact> {
recoverySecretShare: data.recoverySecretShare.present
? data.recoverySecretShare.value
: this.recoverySecretShare,
recoveryContactsSecretShare: data.recoveryContactsSecretShare.present
? data.recoveryContactsSecretShare.value
: this.recoveryContactsSecretShare,
recoveryContactsLastHeartbeat: data.recoveryContactsLastHeartbeat.present
? data.recoveryContactsLastHeartbeat.value
: this.recoveryContactsLastHeartbeat,
askForFriendPromotions: data.askForFriendPromotions.present
? data.askForFriendPromotions.value
: this.askForFriendPromotions,
@ -973,6 +1071,10 @@ class Contact extends DataClass implements Insertable<Contact> {
..write('recoveryIsTrustedFriend: $recoveryIsTrustedFriend, ')
..write('recoveryLastHeartbeat: $recoveryLastHeartbeat, ')
..write('recoverySecretShare: $recoverySecretShare, ')
..write('recoveryContactsSecretShare: $recoveryContactsSecretShare, ')
..write(
'recoveryContactsLastHeartbeat: $recoveryContactsLastHeartbeat, ',
)
..write('askForFriendPromotions: $askForFriendPromotions, ')
..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter')
@ -1001,6 +1103,8 @@ class Contact extends DataClass implements Insertable<Contact> {
recoveryIsTrustedFriend,
recoveryLastHeartbeat,
$driftBlobEquality.hash(recoverySecretShare),
$driftBlobEquality.hash(recoveryContactsSecretShare),
recoveryContactsLastHeartbeat,
askForFriendPromotions,
mediaSendCounter,
mediaReceivedCounter,
@ -1038,6 +1142,12 @@ class Contact extends DataClass implements Insertable<Contact> {
other.recoverySecretShare,
this.recoverySecretShare,
) &&
$driftBlobEquality.equals(
other.recoveryContactsSecretShare,
this.recoveryContactsSecretShare,
) &&
other.recoveryContactsLastHeartbeat ==
this.recoveryContactsLastHeartbeat &&
other.askForFriendPromotions == this.askForFriendPromotions &&
other.mediaSendCounter == this.mediaSendCounter &&
other.mediaReceivedCounter == this.mediaReceivedCounter);
@ -1063,6 +1173,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
final Value<bool> recoveryIsTrustedFriend;
final Value<DateTime?> recoveryLastHeartbeat;
final Value<Uint8List?> recoverySecretShare;
final Value<Uint8List?> recoveryContactsSecretShare;
final Value<DateTime?> recoveryContactsLastHeartbeat;
final Value<bool?> askForFriendPromotions;
final Value<int> mediaSendCounter;
final Value<int> mediaReceivedCounter;
@ -1086,6 +1198,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
this.recoveryIsTrustedFriend = const Value.absent(),
this.recoveryLastHeartbeat = const Value.absent(),
this.recoverySecretShare = const Value.absent(),
this.recoveryContactsSecretShare = const Value.absent(),
this.recoveryContactsLastHeartbeat = const Value.absent(),
this.askForFriendPromotions = const Value.absent(),
this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(),
@ -1110,6 +1224,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
this.recoveryIsTrustedFriend = const Value.absent(),
this.recoveryLastHeartbeat = const Value.absent(),
this.recoverySecretShare = const Value.absent(),
this.recoveryContactsSecretShare = const Value.absent(),
this.recoveryContactsLastHeartbeat = const Value.absent(),
this.askForFriendPromotions = const Value.absent(),
this.mediaSendCounter = const Value.absent(),
this.mediaReceivedCounter = const Value.absent(),
@ -1134,6 +1250,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Expression<bool>? recoveryIsTrustedFriend,
Expression<DateTime>? recoveryLastHeartbeat,
Expression<Uint8List>? recoverySecretShare,
Expression<Uint8List>? recoveryContactsSecretShare,
Expression<DateTime>? recoveryContactsLastHeartbeat,
Expression<bool>? askForFriendPromotions,
Expression<int>? mediaSendCounter,
Expression<int>? mediaReceivedCounter,
@ -1166,6 +1284,10 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
'recovery_last_heartbeat': recoveryLastHeartbeat,
if (recoverySecretShare != null)
'recovery_secret_share': recoverySecretShare,
if (recoveryContactsSecretShare != null)
'recovery_contacts_secret_share': recoveryContactsSecretShare,
if (recoveryContactsLastHeartbeat != null)
'recovery_contacts_last_heartbeat': recoveryContactsLastHeartbeat,
if (askForFriendPromotions != null)
'ask_for_friend_promotions': askForFriendPromotions,
if (mediaSendCounter != null) 'media_send_counter': mediaSendCounter,
@ -1194,6 +1316,8 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
Value<bool>? recoveryIsTrustedFriend,
Value<DateTime?>? recoveryLastHeartbeat,
Value<Uint8List?>? recoverySecretShare,
Value<Uint8List?>? recoveryContactsSecretShare,
Value<DateTime?>? recoveryContactsLastHeartbeat,
Value<bool?>? askForFriendPromotions,
Value<int>? mediaSendCounter,
Value<int>? mediaReceivedCounter,
@ -1222,6 +1346,10 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
recoveryLastHeartbeat:
recoveryLastHeartbeat ?? this.recoveryLastHeartbeat,
recoverySecretShare: recoverySecretShare ?? this.recoverySecretShare,
recoveryContactsSecretShare:
recoveryContactsSecretShare ?? this.recoveryContactsSecretShare,
recoveryContactsLastHeartbeat:
recoveryContactsLastHeartbeat ?? this.recoveryContactsLastHeartbeat,
askForFriendPromotions:
askForFriendPromotions ?? this.askForFriendPromotions,
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
@ -1303,6 +1431,16 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
recoverySecretShare.value,
);
}
if (recoveryContactsSecretShare.present) {
map['recovery_contacts_secret_share'] = Variable<Uint8List>(
recoveryContactsSecretShare.value,
);
}
if (recoveryContactsLastHeartbeat.present) {
map['recovery_contacts_last_heartbeat'] = Variable<DateTime>(
recoveryContactsLastHeartbeat.value,
);
}
if (askForFriendPromotions.present) {
map['ask_for_friend_promotions'] = Variable<bool>(
askForFriendPromotions.value,
@ -1339,6 +1477,10 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
..write('recoveryIsTrustedFriend: $recoveryIsTrustedFriend, ')
..write('recoveryLastHeartbeat: $recoveryLastHeartbeat, ')
..write('recoverySecretShare: $recoverySecretShare, ')
..write('recoveryContactsSecretShare: $recoveryContactsSecretShare, ')
..write(
'recoveryContactsLastHeartbeat: $recoveryContactsLastHeartbeat, ',
)
..write('askForFriendPromotions: $askForFriendPromotions, ')
..write('mediaSendCounter: $mediaSendCounter, ')
..write('mediaReceivedCounter: $mediaReceivedCounter')
@ -13121,6 +13263,8 @@ typedef $$ContactsTableCreateCompanionBuilder =
Value<bool> recoveryIsTrustedFriend,
Value<DateTime?> recoveryLastHeartbeat,
Value<Uint8List?> recoverySecretShare,
Value<Uint8List?> recoveryContactsSecretShare,
Value<DateTime?> recoveryContactsLastHeartbeat,
Value<bool?> askForFriendPromotions,
Value<int> mediaSendCounter,
Value<int> mediaReceivedCounter,
@ -13146,6 +13290,8 @@ typedef $$ContactsTableUpdateCompanionBuilder =
Value<bool> recoveryIsTrustedFriend,
Value<DateTime?> recoveryLastHeartbeat,
Value<Uint8List?> recoverySecretShare,
Value<Uint8List?> recoveryContactsSecretShare,
Value<DateTime?> recoveryContactsLastHeartbeat,
Value<bool?> askForFriendPromotions,
Value<int> mediaSendCounter,
Value<int> mediaReceivedCounter,
@ -13525,6 +13671,18 @@ class $$ContactsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<Uint8List> get recoveryContactsSecretShare =>
$composableBuilder(
column: $table.recoveryContactsSecretShare,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get recoveryContactsLastHeartbeat =>
$composableBuilder(
column: $table.recoveryContactsLastHeartbeat,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<bool> get askForFriendPromotions => $composableBuilder(
column: $table.askForFriendPromotions,
builder: (column) => ColumnFilters(column),
@ -13928,6 +14086,18 @@ class $$ContactsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<Uint8List> get recoveryContactsSecretShare =>
$composableBuilder(
column: $table.recoveryContactsSecretShare,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get recoveryContactsLastHeartbeat =>
$composableBuilder(
column: $table.recoveryContactsLastHeartbeat,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<bool> get askForFriendPromotions => $composableBuilder(
column: $table.askForFriendPromotions,
builder: (column) => ColumnOrderings(column),
@ -14032,6 +14202,18 @@ class $$ContactsTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<Uint8List> get recoveryContactsSecretShare =>
$composableBuilder(
column: $table.recoveryContactsSecretShare,
builder: (column) => column,
);
GeneratedColumn<DateTime> get recoveryContactsLastHeartbeat =>
$composableBuilder(
column: $table.recoveryContactsLastHeartbeat,
builder: (column) => column,
);
GeneratedColumn<bool> get askForFriendPromotions => $composableBuilder(
column: $table.askForFriendPromotions,
builder: (column) => column,
@ -14395,6 +14577,10 @@ class $$ContactsTableTableManager
Value<bool> recoveryIsTrustedFriend = const Value.absent(),
Value<DateTime?> recoveryLastHeartbeat = const Value.absent(),
Value<Uint8List?> recoverySecretShare = const Value.absent(),
Value<Uint8List?> recoveryContactsSecretShare =
const Value.absent(),
Value<DateTime?> recoveryContactsLastHeartbeat =
const Value.absent(),
Value<bool?> askForFriendPromotions = const Value.absent(),
Value<int> mediaSendCounter = const Value.absent(),
Value<int> mediaReceivedCounter = const Value.absent(),
@ -14418,6 +14604,8 @@ class $$ContactsTableTableManager
recoveryIsTrustedFriend: recoveryIsTrustedFriend,
recoveryLastHeartbeat: recoveryLastHeartbeat,
recoverySecretShare: recoverySecretShare,
recoveryContactsSecretShare: recoveryContactsSecretShare,
recoveryContactsLastHeartbeat: recoveryContactsLastHeartbeat,
askForFriendPromotions: askForFriendPromotions,
mediaSendCounter: mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter,
@ -14443,6 +14631,10 @@ class $$ContactsTableTableManager
Value<bool> recoveryIsTrustedFriend = const Value.absent(),
Value<DateTime?> recoveryLastHeartbeat = const Value.absent(),
Value<Uint8List?> recoverySecretShare = const Value.absent(),
Value<Uint8List?> recoveryContactsSecretShare =
const Value.absent(),
Value<DateTime?> recoveryContactsLastHeartbeat =
const Value.absent(),
Value<bool?> askForFriendPromotions = const Value.absent(),
Value<int> mediaSendCounter = const Value.absent(),
Value<int> mediaReceivedCounter = const Value.absent(),
@ -14466,6 +14658,8 @@ class $$ContactsTableTableManager
recoveryIsTrustedFriend: recoveryIsTrustedFriend,
recoveryLastHeartbeat: recoveryLastHeartbeat,
recoverySecretShare: recoverySecretShare,
recoveryContactsSecretShare: recoveryContactsSecretShare,
recoveryContactsLastHeartbeat: recoveryContactsLastHeartbeat,
askForFriendPromotions: askForFriendPromotions,
mediaSendCounter: mediaSendCounter,
mediaReceivedCounter: mediaReceivedCounter,

View file

@ -10537,6 +10537,540 @@ i1.GeneratedColumn<i2.Uint8List> _column_251(String aliasedName) =>
type: i1.DriftSqlType.blob,
$customConstraints: 'NULL',
);
final class Schema21 extends i0.VersionedSchema {
Schema21({required super.database}) : super(version: 21);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
groups,
mediaFiles,
messages,
messageHistories,
reactions,
groupMembers,
receipts,
receivedReceipts,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
signalSignedPreKeyStores,
messageActions,
groupHistories,
keyVerifications,
verificationTokens,
userDiscoveryAnnouncedUsers,
userDiscoveryUserRelations,
userDiscoveryOtherPromotions,
userDiscoveryOwnPromotions,
userDiscoveryShares,
shortcuts,
shortcutMembers,
];
late final Shape56 contacts = Shape56(
source: i0.VersionedTable(
entityName: 'contacts',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(user_id)'],
columns: [
_column_106,
_column_107,
_column_108,
_column_109,
_column_110,
_column_111,
_column_112,
_column_113,
_column_114,
_column_115,
_column_116,
_column_117,
_column_118,
_column_211,
_column_212,
_column_213,
_column_249,
_column_250,
_column_251,
_column_252,
_column_253,
_column_247,
_column_214,
_column_215,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape23 groups = Shape23(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(group_id)'],
columns: [
_column_119,
_column_120,
_column_121,
_column_122,
_column_123,
_column_124,
_column_125,
_column_126,
_column_127,
_column_128,
_column_129,
_column_130,
_column_131,
_column_132,
_column_133,
_column_134,
_column_118,
_column_135,
_column_136,
_column_137,
_column_138,
_column_139,
_column_140,
_column_141,
_column_142,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape51 mediaFiles = Shape51(
source: i0.VersionedTable(
entityName: 'media_files',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(media_id)'],
columns: [
_column_143,
_column_144,
_column_145,
_column_146,
_column_147,
_column_148,
_column_149,
_column_239,
_column_240,
_column_207,
_column_150,
_column_151,
_column_152,
_column_153,
_column_154,
_column_155,
_column_156,
_column_157,
_column_244,
_column_245,
_column_118,
_column_241,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape25 messages = Shape25(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(message_id)'],
columns: [
_column_158,
_column_159,
_column_160,
_column_144,
_column_161,
_column_162,
_column_163,
_column_164,
_column_165,
_column_153,
_column_166,
_column_167,
_column_168,
_column_169,
_column_118,
_column_170,
_column_171,
_column_172,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape26 messageHistories = Shape26(
source: i0.VersionedTable(
entityName: 'message_histories',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_173,
_column_174,
_column_175,
_column_161,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape27 reactions = Shape27(
source: i0.VersionedTable(
entityName: 'reactions',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
columns: [_column_174, _column_176, _column_177, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape38 groupMembers = Shape38(
source: i0.VersionedTable(
entityName: 'group_members',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
columns: [
_column_158,
_column_178,
_column_179,
_column_180,
_column_209,
_column_210,
_column_181,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape37 receipts = Shape37(
source: i0.VersionedTable(
entityName: 'receipts',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(receipt_id)'],
columns: [
_column_182,
_column_183,
_column_184,
_column_185,
_column_186,
_column_208,
_column_187,
_column_188,
_column_189,
_column_190,
_column_191,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape30 receivedReceipts = Shape30(
source: i0.VersionedTable(
entityName: 'received_receipts',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(receipt_id)'],
columns: [_column_182, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape31 signalIdentityKeyStores = Shape31(
source: i0.VersionedTable(
entityName: 'signal_identity_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(device_id, name)'],
columns: [_column_192, _column_193, _column_194, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape32 signalPreKeyStores = Shape32(
source: i0.VersionedTable(
entityName: 'signal_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
columns: [_column_195, _column_196, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape11 signalSenderKeyStores = Shape11(
source: i0.VersionedTable(
entityName: 'signal_sender_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
columns: [_column_197, _column_198],
attachedDatabase: database,
),
alias: null,
);
late final Shape33 signalSessionStores = Shape33(
source: i0.VersionedTable(
entityName: 'signal_session_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(device_id, name)'],
columns: [_column_192, _column_193, _column_199, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape50 signalSignedPreKeyStores = Shape50(
source: i0.VersionedTable(
entityName: 'signal_signed_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(signed_pre_key_id)'],
columns: [_column_242, _column_243, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape34 messageActions = Shape34(
source: i0.VersionedTable(
entityName: 'message_actions',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
columns: [_column_174, _column_183, _column_144, _column_200],
attachedDatabase: database,
),
alias: null,
);
late final Shape35 groupHistories = Shape35(
source: i0.VersionedTable(
entityName: 'group_histories',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(group_history_id)'],
columns: [
_column_201,
_column_158,
_column_202,
_column_203,
_column_204,
_column_205,
_column_206,
_column_144,
_column_200,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape54 keyVerifications = Shape54(
source: i0.VersionedTable(
entityName: 'key_verifications',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_216,
_column_183,
_column_144,
_column_248,
_column_118,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape41 verificationTokens = Shape41(
source: i0.VersionedTable(
entityName: 'verification_tokens',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_217, _column_218, _column_118],
attachedDatabase: database,
),
alias: null,
);
late final Shape52 userDiscoveryAnnouncedUsers = Shape52(
source: i0.VersionedTable(
entityName: 'user_discovery_announced_users',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
columns: [
_column_219,
_column_220,
_column_221,
_column_222,
_column_223,
_column_224,
_column_246,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape43 userDiscoveryUserRelations = Shape43(
source: i0.VersionedTable(
entityName: 'user_discovery_user_relations',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
columns: [_column_225, _column_226, _column_227],
attachedDatabase: database,
),
alias: null,
);
late final Shape44 userDiscoveryOtherPromotions = Shape44(
source: i0.VersionedTable(
entityName: 'user_discovery_other_promotions',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(from_contact_id, public_id)'],
columns: [
_column_226,
_column_228,
_column_229,
_column_230,
_column_231,
_column_227,
],
attachedDatabase: database,
),
alias: null,
);
late final Shape45 userDiscoveryOwnPromotions = Shape45(
source: i0.VersionedTable(
entityName: 'user_discovery_own_promotions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_232, _column_183, _column_233],
attachedDatabase: database,
),
alias: null,
);
late final Shape46 userDiscoveryShares = Shape46(
source: i0.VersionedTable(
entityName: 'user_discovery_shares',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_234, _column_235, _column_175],
attachedDatabase: database,
),
alias: null,
);
late final Shape47 shortcuts = Shape47(
source: i0.VersionedTable(
entityName: 'shortcuts',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [_column_173, _column_236, _column_237],
attachedDatabase: database,
),
alias: null,
);
late final Shape48 shortcutMembers = Shape48(
source: i0.VersionedTable(
entityName: 'shortcut_members',
withoutRowId: false,
isStrict: false,
tableConstraints: ['PRIMARY KEY(shortcut_id, group_id)'],
columns: [_column_238, _column_158],
attachedDatabase: database,
),
alias: null,
);
}
class Shape56 extends i0.VersionedTable {
Shape56({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get userId =>
columnsByName['user_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get username =>
columnsByName['username']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get displayName =>
columnsByName['display_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get nickName =>
columnsByName['nick_name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<i2.Uint8List> get avatarSvgCompressed =>
columnsByName['avatar_svg_compressed']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get senderProfileCounter =>
columnsByName['sender_profile_counter']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get accepted =>
columnsByName['accepted']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get deletedByUser =>
columnsByName['deleted_by_user']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get requested =>
columnsByName['requested']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get blocked =>
columnsByName['blocked']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get verified =>
columnsByName['verified']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get accountDeleted =>
columnsByName['account_deleted']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get createdAt =>
columnsByName['created_at']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<i2.Uint8List> get userDiscoveryVersion =>
columnsByName['user_discovery_version']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get userDiscoveryExcluded =>
columnsByName['user_discovery_excluded']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get userDiscoveryManualApproved =>
columnsByName['user_discovery_manual_approved']!
as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get recoveryIsTrustedFriend =>
columnsByName['recovery_is_trusted_friend']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get recoveryLastHeartbeat =>
columnsByName['recovery_last_heartbeat']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<i2.Uint8List> get recoverySecretShare =>
columnsByName['recovery_secret_share']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<i2.Uint8List> get recoveryContactsSecretShare =>
columnsByName['recovery_contacts_secret_share']!
as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get recoveryContactsLastHeartbeat =>
columnsByName['recovery_contacts_last_heartbeat']!
as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get askForFriendPromotions =>
columnsByName['ask_for_friend_promotions']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get mediaSendCounter =>
columnsByName['media_send_counter']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get mediaReceivedCounter =>
columnsByName['media_received_counter']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<i2.Uint8List> _column_252(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>(
'recovery_contacts_secret_share',
aliasedName,
true,
type: i1.DriftSqlType.blob,
$customConstraints: 'NULL',
);
i1.GeneratedColumn<int> _column_253(String aliasedName) =>
i1.GeneratedColumn<int>(
'recovery_contacts_last_heartbeat',
aliasedName,
true,
type: i1.DriftSqlType.int,
$customConstraints: 'NULL',
);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -10557,6 +11091,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -10655,6 +11190,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from19To20(migrator, schema);
return 20;
case 20:
final schema = Schema21(database: database);
final migrator = i1.Migrator(database, schema);
await from20To21(migrator, schema);
return 21;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -10681,6 +11221,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
}) => i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
@ -10702,5 +11243,6 @@ i1.OnUpgrade stepByStep({
from17To18: from17To18,
from18To19: from18To19,
from19To20: from19To20,
from20To21: from20To21,
),
);

View file

@ -208,22 +208,29 @@ class TwonlySafeBackup {
@JsonSerializable()
class PasswordLessRecovery {
PasswordLessRecovery({
this.email,
this.pinSeed,
this.pinUnlockToken,
this.threshold,
this.lastHeartbeat,
});
PasswordLessRecovery();
factory PasswordLessRecovery.fromJson(Map<String, dynamic> json) =>
_$PasswordLessRecoveryFromJson(json);
// Only stored, so the user can see his deposit email address...
String? email;
String? pinSeed;
String? pinUnlockToken;
int? threshold;
DateTime? lastHeartbeat;
// <--
// Data shared with trusted friends
// Trusted friends are able to brute-force the pin -> Server delets after X tries
List<int>? pinSeed;
List<int>? pinUnlockToken;
// Stored not on the server, so the server is unable to link a email to a user until the actuall recovery or can
// brute-force the pin
List<int>? encryptedServerKeyNonce;
List<int>? encryptedServerKeyMac;
// --->
// Checking with the server that the server data is valid and not delted throug the pin protection for example.
DateTime? lastServerHeartbeat;
List<int>? encryptedServerKey;
Map<String, dynamic> toJson() => _$PasswordLessRecoveryToJson(this);
}

View file

@ -243,15 +243,27 @@ const _$LastBackupUploadStateEnumMap = {
PasswordLessRecovery _$PasswordLessRecoveryFromJson(
Map<String, dynamic> json,
) => PasswordLessRecovery(
email: json['email'] as String?,
pinSeed: json['pinSeed'] as String?,
pinUnlockToken: json['pinUnlockToken'] as String?,
threshold: (json['threshold'] as num?)?.toInt(),
lastHeartbeat: json['lastHeartbeat'] == null
) => PasswordLessRecovery()
..email = json['email'] as String?
..pinSeed = (json['pinSeed'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..pinUnlockToken = (json['pinUnlockToken'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..encryptedServerKeyNonce =
(json['encryptedServerKeyNonce'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..encryptedServerKeyMac = (json['encryptedServerKeyMac'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..lastServerHeartbeat = json['lastServerHeartbeat'] == null
? null
: DateTime.parse(json['lastHeartbeat'] as String),
);
: DateTime.parse(json['lastServerHeartbeat'] as String)
..encryptedServerKey = (json['encryptedServerKey'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList();
Map<String, dynamic> _$PasswordLessRecoveryToJson(
PasswordLessRecovery instance,
@ -259,6 +271,8 @@ Map<String, dynamic> _$PasswordLessRecoveryToJson(
'email': instance.email,
'pinSeed': instance.pinSeed,
'pinUnlockToken': instance.pinUnlockToken,
'threshold': instance.threshold,
'lastHeartbeat': instance.lastHeartbeat?.toIso8601String(),
'encryptedServerKeyNonce': instance.encryptedServerKeyNonce,
'encryptedServerKeyMac': instance.encryptedServerKeyMac,
'lastServerHeartbeat': instance.lastServerHeartbeat?.toIso8601String(),
'encryptedServerKey': instance.encryptedServerKey,
};

View file

@ -2182,6 +2182,87 @@ class ApplicationData_Deprecated extends $pb.GeneratedMessage {
static ApplicationData_Deprecated? _defaultInstance;
}
class ApplicationData_RegisterPasswordLessRecovery
extends $pb.GeneratedMessage {
factory ApplicationData_RegisterPasswordLessRecovery({
$core.List<$core.int>? pinUnlockToken,
$core.List<$core.int>? encryptedServerKey,
}) {
final result = create();
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
if (encryptedServerKey != null)
result.encryptedServerKey = encryptedServerKey;
return result;
}
ApplicationData_RegisterPasswordLessRecovery._();
factory ApplicationData_RegisterPasswordLessRecovery.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory ApplicationData_RegisterPasswordLessRecovery.fromJson(
$core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'ApplicationData.RegisterPasswordLessRecovery',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY,
protoName: 'pinUnlockToken')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'encryptedServerKey', $pb.PbFieldType.OY,
protoName: 'encryptedServerKey')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ApplicationData_RegisterPasswordLessRecovery clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ApplicationData_RegisterPasswordLessRecovery copyWith(
void Function(ApplicationData_RegisterPasswordLessRecovery)
updates) =>
super.copyWith((message) =>
updates(message as ApplicationData_RegisterPasswordLessRecovery))
as ApplicationData_RegisterPasswordLessRecovery;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ApplicationData_RegisterPasswordLessRecovery create() =>
ApplicationData_RegisterPasswordLessRecovery._();
@$core.override
ApplicationData_RegisterPasswordLessRecovery createEmptyInstance() =>
create();
@$core.pragma('dart2js:noInline')
static ApplicationData_RegisterPasswordLessRecovery getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
ApplicationData_RegisterPasswordLessRecovery>(create);
static ApplicationData_RegisterPasswordLessRecovery? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get pinUnlockToken => $_getN(0);
@$pb.TagNumber(1)
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasPinUnlockToken() => $_has(0);
@$pb.TagNumber(1)
void clearPinUnlockToken() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get encryptedServerKey => $_getN(1);
@$pb.TagNumber(2)
set encryptedServerKey($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasEncryptedServerKey() => $_has(1);
@$pb.TagNumber(2)
void clearEncryptedServerKey() => $_clearField(2);
}
enum ApplicationData_ApplicationData {
textMessage,
getUserByUsername,
@ -2209,6 +2290,7 @@ enum ApplicationData_ApplicationData {
ipaForceCheck,
addAdditionalUser,
setLoginToken,
registerPasswordlessRecovery,
notSet
}
@ -2240,6 +2322,7 @@ class ApplicationData extends $pb.GeneratedMessage {
ApplicationData_IPAForceCheck? ipaForceCheck,
ApplicationData_AddAdditionalUser? addAdditionalUser,
ApplicationData_SetLoginToken? setLoginToken,
ApplicationData_RegisterPasswordLessRecovery? registerPasswordlessRecovery,
}) {
final result = create();
if (textMessage != null) result.textMessage = textMessage;
@ -2274,6 +2357,8 @@ class ApplicationData extends $pb.GeneratedMessage {
if (ipaForceCheck != null) result.ipaForceCheck = ipaForceCheck;
if (addAdditionalUser != null) result.addAdditionalUser = addAdditionalUser;
if (setLoginToken != null) result.setLoginToken = setLoginToken;
if (registerPasswordlessRecovery != null)
result.registerPasswordlessRecovery = registerPasswordlessRecovery;
return result;
}
@ -2314,6 +2399,7 @@ class ApplicationData extends $pb.GeneratedMessage {
28: ApplicationData_ApplicationData.ipaForceCheck,
29: ApplicationData_ApplicationData.addAdditionalUser,
30: ApplicationData_ApplicationData.setLoginToken,
31: ApplicationData_ApplicationData.registerPasswordlessRecovery,
0: ApplicationData_ApplicationData.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
@ -2347,7 +2433,8 @@ class ApplicationData extends $pb.GeneratedMessage {
27,
28,
29,
30
30,
31
])
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
protoName: 'textMessage',
@ -2439,6 +2526,9 @@ class ApplicationData extends $pb.GeneratedMessage {
..aOM<ApplicationData_SetLoginToken>(
30, _omitFieldNames ? '' : 'setLoginToken',
subBuilder: ApplicationData_SetLoginToken.create)
..aOM<ApplicationData_RegisterPasswordLessRecovery>(
31, _omitFieldNames ? '' : 'registerPasswordlessRecovery',
subBuilder: ApplicationData_RegisterPasswordLessRecovery.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -2486,6 +2576,7 @@ class ApplicationData extends $pb.GeneratedMessage {
@$pb.TagNumber(28)
@$pb.TagNumber(29)
@$pb.TagNumber(30)
@$pb.TagNumber(31)
ApplicationData_ApplicationData whichApplicationData() =>
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
@$pb.TagNumber(1)
@ -2514,6 +2605,7 @@ class ApplicationData extends $pb.GeneratedMessage {
@$pb.TagNumber(28)
@$pb.TagNumber(29)
@$pb.TagNumber(30)
@$pb.TagNumber(31)
void clearApplicationData() => $_clearField($_whichOneof(0));
@$pb.TagNumber(1)
@ -2819,6 +2911,21 @@ class ApplicationData extends $pb.GeneratedMessage {
void clearSetLoginToken() => $_clearField(30);
@$pb.TagNumber(30)
ApplicationData_SetLoginToken ensureSetLoginToken() => $_ensure(25);
@$pb.TagNumber(31)
ApplicationData_RegisterPasswordLessRecovery
get registerPasswordlessRecovery => $_getN(26);
@$pb.TagNumber(31)
set registerPasswordlessRecovery(
ApplicationData_RegisterPasswordLessRecovery value) =>
$_setField(31, value);
@$pb.TagNumber(31)
$core.bool hasRegisterPasswordlessRecovery() => $_has(26);
@$pb.TagNumber(31)
void clearRegisterPasswordlessRecovery() => $_clearField(31);
@$pb.TagNumber(31)
ApplicationData_RegisterPasswordLessRecovery
ensureRegisterPasswordlessRecovery() => $_ensure(26);
}
class Response_PreKey extends $pb.GeneratedMessage {

View file

@ -575,6 +575,15 @@ const ApplicationData$json = {
'9': 0,
'10': 'setLoginToken'
},
{
'1': 'register_passwordless_recovery',
'3': 31,
'4': 1,
'5': 11,
'6': '.client_to_server.ApplicationData.RegisterPasswordLessRecovery',
'9': 0,
'10': 'registerPasswordlessRecovery'
},
],
'3': [
ApplicationData_TextMessage$json,
@ -596,7 +605,8 @@ const ApplicationData$json = {
ApplicationData_DeleteAccount$json,
ApplicationData_AddAdditionalUser$json,
ApplicationData_SetLoginToken$json,
ApplicationData_Deprecated$json
ApplicationData_Deprecated$json,
ApplicationData_RegisterPasswordLessRecovery$json
],
'8': [
{'1': 'ApplicationData'},
@ -775,6 +785,35 @@ const ApplicationData_Deprecated$json = {
'1': 'Deprecated',
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_RegisterPasswordLessRecovery$json = {
'1': 'RegisterPasswordLessRecovery',
'2': [
{
'1': 'pinUnlockToken',
'3': 1,
'4': 1,
'5': 12,
'9': 0,
'10': 'pinUnlockToken',
'17': true
},
{
'1': 'encryptedServerKey',
'3': 2,
'4': 1,
'5': 12,
'9': 1,
'10': 'encryptedServerKey',
'17': true
},
],
'8': [
{'1': '_pinUnlockToken'},
{'1': '_encryptedServerKey'},
],
};
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
@ -820,27 +859,32 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'dmVBZGRpdGlvbmFsVXNlchJjChFhZGRBZGRpdGlvbmFsVXNlchgdIAEoCzIzLmNsaWVudF90b1'
'9zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkFkZEFkZGl0aW9uYWxVc2VySABSEWFkZEFkZGl0aW9u'
'YWxVc2VyElkKD3NldF9sb2dpbl90b2tlbhgeIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbG'
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhpqCgtUZXh0TWVzc2Fn'
'ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRIgCglwdXNoX2'
'RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YRovChFHZXRVc2VyQnlVc2Vy'
'bmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbWUSGgoIdX'
'Nlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2ds'
'ZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUg'
'Z1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUK'
'E0dldEN1cnJlbnRQbGFuSW5mb3MaLwoUUmVtb3ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZB'
'gBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1'
'c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlck'
'lkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2ln'
'bmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ2'
'5lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93'
'bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG'
'9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVh'
'c29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcH'
'JvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMg'
'ASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudB'
'osChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMAoNU2V0TG9n'
'aW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhoMCgpEZXByZWNhdGVkQh'
'EKD0FwcGxpY2F0aW9uRGF0YQ==');
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhKGAQoecmVnaXN0ZXJf'
'cGFzc3dvcmRsZXNzX3JlY292ZXJ5GB8gASgLMj4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
'lvbkRhdGEuUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeUgAUhxyZWdpc3RlclBhc3N3b3Jk'
'bGVzc1JlY292ZXJ5GmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBISCg'
'Rib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNoRGF0YYgBAUIMCgpf'
'cHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGAEgASgJUgh1c2Vybm'
'FtZRosCg5DaGFuZ2VVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaNQoUVXBk'
'YXRlR29vZ2xlRmNtVG9rZW4SHQoKZ29vZ2xlX2ZjbRgBIAEoCVIJZ29vZ2xlRmNtGiYKC0dldF'
'VzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoTChFHZXRBdmFpbGFibGVQbGFucxoX'
'ChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5JbmZvcxovChRSZW1vdm'
'VBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2V0UHJla2V5c0J5'
'VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQcmVLZXlCeVVzZX'
'JJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZFByZUtleRIoChBz'
'aWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWduZWRfcHJla2V5GA'
'IgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cmUYAyABKAxSFXNp'
'Z25lZFByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRfdG9rZW4YAS'
'ABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRlZF91c2VyX2lkGAEg'
'ASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbhpxCgtJUEFQdXJjaG'
'FzZRIdCgpwcm9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNlGAIgASgJUgZzb3Vy'
'Y2USKwoRdmVyaWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbkRhdGEaDwoNSVBBRm'
'9yY2VDaGVjaxoPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxVc2VyEhcKB3VzZXJf'
'aWQYASABKANSBnVzZXJJZBowCg1TZXRMb2dpblRva2VuEh8KC2xvZ2luX3Rva2VuGAEgASgMUg'
'psb2dpblRva2VuGgwKCkRlcHJlY2F0ZWQaqgEKHFJlZ2lzdGVyUGFzc3dvcmRMZXNzUmVjb3Zl'
'cnkSKwoOcGluVW5sb2NrVG9rZW4YASABKAxIAFIOcGluVW5sb2NrVG9rZW6IAQESMwoSZW5jcn'
'lwdGVkU2VydmVyS2V5GAIgASgMSAFSEmVuY3J5cHRlZFNlcnZlcktleYgBAUIRCg9fcGluVW5s'
'b2NrVG9rZW5CFQoTX2VuY3J5cHRlZFNlcnZlcktleUIRCg9BcHBsaWNhdGlvbkRhdGE=');
@$core.Deprecated('Use responseDescriptor instead')
const Response$json = {

View file

@ -1839,6 +1839,142 @@ class EncryptedContent_KeyVerificationProof extends $pb.GeneratedMessage {
void clearCalculatedMac() => $_clearField(1);
}
class EncryptedContent_PasswordLessRecovery extends $pb.GeneratedMessage {
factory EncryptedContent_PasswordLessRecovery({
$core.List<$core.int>? recoverySecretShare,
$core.bool? delete,
}) {
final result = create();
if (recoverySecretShare != null)
result.recoverySecretShare = recoverySecretShare;
if (delete != null) result.delete = delete;
return result;
}
EncryptedContent_PasswordLessRecovery._();
factory EncryptedContent_PasswordLessRecovery.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedContent_PasswordLessRecovery.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedContent.PasswordLessRecovery',
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'recoverySecretShare', $pb.PbFieldType.OY,
protoName: 'recoverySecretShare')
..aOB(2, _omitFieldNames ? '' : 'delete')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecovery clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecovery copyWith(
void Function(EncryptedContent_PasswordLessRecovery) updates) =>
super.copyWith((message) =>
updates(message as EncryptedContent_PasswordLessRecovery))
as EncryptedContent_PasswordLessRecovery;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecovery create() =>
EncryptedContent_PasswordLessRecovery._();
@$core.override
EncryptedContent_PasswordLessRecovery createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecovery getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
EncryptedContent_PasswordLessRecovery>(create);
static EncryptedContent_PasswordLessRecovery? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get recoverySecretShare => $_getN(0);
@$pb.TagNumber(1)
set recoverySecretShare($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasRecoverySecretShare() => $_has(0);
@$pb.TagNumber(1)
void clearRecoverySecretShare() => $_clearField(1);
@$pb.TagNumber(2)
$core.bool get delete => $_getBF(1);
@$pb.TagNumber(2)
set delete($core.bool value) => $_setBool(1, value);
@$pb.TagNumber(2)
$core.bool hasDelete() => $_has(1);
@$pb.TagNumber(2)
void clearDelete() => $_clearField(2);
}
class EncryptedContent_PasswordLessRecoveryHeartbeat
extends $pb.GeneratedMessage {
factory EncryptedContent_PasswordLessRecoveryHeartbeat({
$core.List<$core.int>? hash,
}) {
final result = create();
if (hash != null) result.hash = hash;
return result;
}
EncryptedContent_PasswordLessRecoveryHeartbeat._();
factory EncryptedContent_PasswordLessRecoveryHeartbeat.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedContent_PasswordLessRecoveryHeartbeat.fromJson(
$core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedContent.PasswordLessRecoveryHeartbeat',
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'hash', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecoveryHeartbeat clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecoveryHeartbeat copyWith(
void Function(EncryptedContent_PasswordLessRecoveryHeartbeat)
updates) =>
super.copyWith((message) => updates(
message as EncryptedContent_PasswordLessRecoveryHeartbeat))
as EncryptedContent_PasswordLessRecoveryHeartbeat;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecoveryHeartbeat create() =>
EncryptedContent_PasswordLessRecoveryHeartbeat._();
@$core.override
EncryptedContent_PasswordLessRecoveryHeartbeat createEmptyInstance() =>
create();
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecoveryHeartbeat getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
EncryptedContent_PasswordLessRecoveryHeartbeat>(create);
static EncryptedContent_PasswordLessRecoveryHeartbeat? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get hash => $_getN(0);
@$pb.TagNumber(1)
set hash($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasHash() => $_has(0);
@$pb.TagNumber(1)
void clearHash() => $_clearField(1);
}
class EncryptedContent extends $pb.GeneratedMessage {
factory EncryptedContent({
$core.String? groupId,
@ -1865,6 +2001,9 @@ class EncryptedContent extends $pb.GeneratedMessage {
EncryptedContent_UserDiscoveryUpdate? userDiscoveryUpdate,
EncryptedContent_KeyVerificationProof? keyVerificationProof,
$core.bool? askForFriendPromotions,
EncryptedContent_PasswordLessRecovery? passwordlessRecovery,
EncryptedContent_PasswordLessRecoveryHeartbeat?
passwordlessRecoveryHeartbeat,
}) {
final result = create();
if (groupId != null) result.groupId = groupId;
@ -1899,6 +2038,10 @@ class EncryptedContent extends $pb.GeneratedMessage {
result.keyVerificationProof = keyVerificationProof;
if (askForFriendPromotions != null)
result.askForFriendPromotions = askForFriendPromotions;
if (passwordlessRecovery != null)
result.passwordlessRecovery = passwordlessRecovery;
if (passwordlessRecoveryHeartbeat != null)
result.passwordlessRecoveryHeartbeat = passwordlessRecoveryHeartbeat;
return result;
}
@ -1971,6 +2114,12 @@ class EncryptedContent extends $pb.GeneratedMessage {
24, _omitFieldNames ? '' : 'keyVerificationProof',
subBuilder: EncryptedContent_KeyVerificationProof.create)
..aOB(25, _omitFieldNames ? '' : 'askForFriendPromotions')
..aOM<EncryptedContent_PasswordLessRecovery>(
26, _omitFieldNames ? '' : 'passwordlessRecovery',
subBuilder: EncryptedContent_PasswordLessRecovery.create)
..aOM<EncryptedContent_PasswordLessRecoveryHeartbeat>(
27, _omitFieldNames ? '' : 'passwordlessRecoveryHeartbeat',
subBuilder: EncryptedContent_PasswordLessRecoveryHeartbeat.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -2263,6 +2412,34 @@ class EncryptedContent extends $pb.GeneratedMessage {
$core.bool hasAskForFriendPromotions() => $_has(23);
@$pb.TagNumber(25)
void clearAskForFriendPromotions() => $_clearField(25);
@$pb.TagNumber(26)
EncryptedContent_PasswordLessRecovery get passwordlessRecovery => $_getN(24);
@$pb.TagNumber(26)
set passwordlessRecovery(EncryptedContent_PasswordLessRecovery value) =>
$_setField(26, value);
@$pb.TagNumber(26)
$core.bool hasPasswordlessRecovery() => $_has(24);
@$pb.TagNumber(26)
void clearPasswordlessRecovery() => $_clearField(26);
@$pb.TagNumber(26)
EncryptedContent_PasswordLessRecovery ensurePasswordlessRecovery() =>
$_ensure(24);
@$pb.TagNumber(27)
EncryptedContent_PasswordLessRecoveryHeartbeat
get passwordlessRecoveryHeartbeat => $_getN(25);
@$pb.TagNumber(27)
set passwordlessRecoveryHeartbeat(
EncryptedContent_PasswordLessRecoveryHeartbeat value) =>
$_setField(27, value);
@$pb.TagNumber(27)
$core.bool hasPasswordlessRecoveryHeartbeat() => $_has(25);
@$pb.TagNumber(27)
void clearPasswordlessRecoveryHeartbeat() => $_clearField(27);
@$pb.TagNumber(27)
EncryptedContent_PasswordLessRecoveryHeartbeat
ensurePasswordlessRecoveryHeartbeat() => $_ensure(25);
}
const $core.bool _omitFieldNames =

View file

@ -385,6 +385,26 @@ const EncryptedContent$json = {
'10': 'keyVerificationProof',
'17': true
},
{
'1': 'passwordless_recovery',
'3': 26,
'4': 1,
'5': 11,
'6': '.EncryptedContent.PasswordLessRecovery',
'9': 24,
'10': 'passwordlessRecovery',
'17': true
},
{
'1': 'passwordless_recovery_heartbeat',
'3': 27,
'4': 1,
'5': 11,
'6': '.EncryptedContent.PasswordLessRecoveryHeartbeat',
'9': 25,
'10': 'passwordlessRecoveryHeartbeat',
'17': true
},
],
'3': [
EncryptedContent_ErrorMessages$json,
@ -405,7 +425,9 @@ const EncryptedContent$json = {
EncryptedContent_TypingIndicator$json,
EncryptedContent_UserDiscoveryRequest$json,
EncryptedContent_UserDiscoveryUpdate$json,
EncryptedContent_KeyVerificationProof$json
EncryptedContent_KeyVerificationProof$json,
EncryptedContent_PasswordLessRecovery$json,
EncryptedContent_PasswordLessRecoveryHeartbeat$json
],
'8': [
{'1': '_group_id'},
@ -432,6 +454,8 @@ const EncryptedContent$json = {
{'1': '_user_discovery_request'},
{'1': '_user_discovery_update'},
{'1': '_key_verification_proof'},
{'1': '_passwordless_recovery'},
{'1': '_passwordless_recovery_heartbeat'},
],
};
@ -955,6 +979,34 @@ const EncryptedContent_KeyVerificationProof$json = {
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_PasswordLessRecovery$json = {
'1': 'PasswordLessRecovery',
'2': [
{
'1': 'recoverySecretShare',
'3': 1,
'4': 1,
'5': 12,
'9': 0,
'10': 'recoverySecretShare',
'17': true
},
{'1': 'delete', '3': 2, '4': 1, '5': 8, '10': 'delete'},
],
'8': [
{'1': '_recoverySecretShare'},
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_PasswordLessRecoveryHeartbeat$json = {
'1': 'PasswordLessRecoveryHeartbeat',
'2': [
{'1': 'hash', '3': 1, '4': 1, '5': 12, '10': 'hash'},
],
};
/// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'ChBFbmNyeXB0ZWRDb250ZW50Eh4KCGdyb3VwX2lkGAIgASgJSABSB2dyb3VwSWSIAQESKQoOaX'
@ -988,79 +1040,88 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'aXNjb3ZlcnlSZXF1ZXN0iAEBEl4KFXVzZXJfZGlzY292ZXJ5X3VwZGF0ZRgXIAEoCzIlLkVuY3'
'J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVVwZGF0ZUgWUhN1c2VyRGlzY292ZXJ5VXBkYXRl'
'iAEBEmEKFmtleV92ZXJpZmljYXRpb25fcHJvb2YYGCABKAsyJi5FbmNyeXB0ZWRDb250ZW50Lk'
'tleVZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBGpYCCg1FcnJv'
'ck1lc3NhZ2VzEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZX'
'MuVHlwZVIEdHlwZRIsChJyZWxhdGVkX3JlY2VpcHRfaWQYAiABKAlSEHJlbGF0ZWRSZWNlaXB0'
'SWQinAEKBFR5cGUSPAo4RVJST1JfUFJPQ0VTU0lOR19NRVNTQUdFX0NSRUFURURfQUNDT1VOVF'
'9SRVFVRVNUX0lOU1RFQUQQABIYChRVTktOT1dOX01FU1NBR0VfVFlQRRACEhcKE1NFU1NJT05f'
'T1VUX09GX1NZTkMQAxIjCh9HUk9VUF9OT1RfRk9VTkRfT1JfTk9UX0FfTUVNQkVSEAQahwEKC0'
'dyb3VwQ3JlYXRlEhsKCXN0YXRlX2tleRgDIAEoDFIIc3RhdGVLZXkSKAoQZ3JvdXBfcHVibGlj'
'X2tleRgEIAEoDFIOZ3JvdXBQdWJsaWNLZXkSIgoKZ3JvdXBfbmFtZRgFIAEoCUgAUglncm91cE'
'5hbWWIAQFCDQoLX2dyb3VwX25hbWUaNQoJR3JvdXBKb2luEigKEGdyb3VwX3B1YmxpY19rZXkY'
'ASABKAxSDmdyb3VwUHVibGljS2V5GhYKFFJlc2VuZEdyb3VwUHVibGljS2V5GsgCCgtHcm91cF'
'VwZGF0ZRIqChFncm91cF9hY3Rpb25fdHlwZRgBIAEoCVIPZ3JvdXBBY3Rpb25UeXBlEjMKE2Fm'
'ZmVjdGVkX2NvbnRhY3RfaWQYAiABKANIAFIRYWZmZWN0ZWRDb250YWN0SWSIAQESKQoObmV3X2'
'dyb3VwX25hbWUYAyABKAlIAVIMbmV3R3JvdXBOYW1liAEBElcKJm5ld19kZWxldGVfbWVzc2Fn'
'ZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGAQgASgDSAJSIm5ld0RlbGV0ZU1lc3NhZ2VzQWZ0ZXJNaW'
'xsaXNlY29uZHOIAQFCFgoUX2FmZmVjdGVkX2NvbnRhY3RfaWRCEQoPX25ld19ncm91cF9uYW1l'
'QikKJ19uZXdfZGVsZXRlX21lc3NhZ2VzX2FmdGVyX21pbGxpc2Vjb25kcxqvAQoLVGV4dE1lc3'
'NhZ2USKgoRc2VuZGVyX21lc3NhZ2VfaWQYASABKAlSD3NlbmRlck1lc3NhZ2VJZBISCgR0ZXh0'
'GAIgASgJUgR0ZXh0EhwKCXRpbWVzdGFtcBgDIAEoA1IJdGltZXN0YW1wEi0KEHF1b3RlX21lc3'
'NhZ2VfaWQYBCABKAlIAFIOcXVvdGVNZXNzYWdlSWSIAQFCEwoRX3F1b3RlX21lc3NhZ2VfaWQa'
'zgEKFUFkZGl0aW9uYWxEYXRhTWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2'
'VuZGVyTWVzc2FnZUlkEhwKCXRpbWVzdGFtcBgCIAEoA1IJdGltZXN0YW1wEhIKBHR5cGUYAyAB'
'KAlSBHR5cGUSOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYBCABKAxIAFIVYWRkaXRpb25hbE'
'1lc3NhZ2VEYXRhiAEBQhoKGF9hZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRpkCghSZWFjdGlvbhIq'
'ChF0YXJnZXRfbWVzc2FnZV9pZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEhQKBWVtb2ppGAIgAS'
'gJUgVlbW9qaRIWCgZyZW1vdmUYAyABKAhSBnJlbW92ZRq+AgoNTWVzc2FnZVVwZGF0ZRI4CgR0'
'eXBlGAEgASgOMiQuRW5jcnlwdGVkQ29udGVudC5NZXNzYWdlVXBkYXRlLlR5cGVSBHR5cGUSLw'
'oRc2VuZGVyX21lc3NhZ2VfaWQYAiABKAlIAFIPc2VuZGVyTWVzc2FnZUlkiAEBEj0KG211bHRp'
'cGxlX3RhcmdldF9tZXNzYWdlX2lkcxgDIAMoCVIYbXVsdGlwbGVUYXJnZXRNZXNzYWdlSWRzEh'
'cKBHRleHQYBCABKAlIAVIEdGV4dIgBARIcCgl0aW1lc3RhbXAYBSABKANSCXRpbWVzdGFtcCIt'
'CgRUeXBlEgoKBkRFTEVURRAAEg0KCUVESVRfVEVYVBABEgoKBk9QRU5FRBACQhQKEl9zZW5kZX'
'JfbWVzc2FnZV9pZEIHCgVfdGV4dBqFBgoFTWVkaWESKgoRc2VuZGVyX21lc3NhZ2VfaWQYASAB'
'KAlSD3NlbmRlck1lc3NhZ2VJZBIwCgR0eXBlGAIgASgOMhwuRW5jcnlwdGVkQ29udGVudC5NZW'
'RpYS5UeXBlUgR0eXBlEkYKHWRpc3BsYXlfbGltaXRfaW5fbWlsbGlzZWNvbmRzGAMgASgDSABS'
'GmRpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRziAEBEjcKF3JlcXVpcmVzX2F1dGhlbnRpY2F0aW'
'9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGlt'
'ZXN0YW1wEi0KEHF1b3RlX21lc3NhZ2VfaWQYBiABKAlIAVIOcXVvdGVNZXNzYWdlSWSIAQESKg'
'oOZG93bmxvYWRfdG9rZW4YByABKAxIAlINZG93bmxvYWRUb2tlbogBARIqCg5lbmNyeXB0aW9u'
'X2tleRgIIAEoDEgDUg1lbmNyeXB0aW9uS2V5iAEBEioKDmVuY3J5cHRpb25fbWFjGAkgASgMSA'
'RSDWVuY3J5cHRpb25NYWOIAQESLgoQZW5jcnlwdGlvbl9ub25jZRgKIAEoDEgFUg9lbmNyeXB0'
'aW9uTm9uY2WIAQESOwoXYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEYCyABKAxIBlIVYWRkaXRpb2'
'5hbE1lc3NhZ2VEYXRhiAEBIj4KBFR5cGUSDAoIUkVVUExPQUQQABIJCgVJTUFHRRABEgkKBVZJ'
'REVPEAISBwoDR0lGEAMSCQoFQVVESU8QBEIgCh5fZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY2'
'9uZHNCEwoRX3F1b3RlX21lc3NhZ2VfaWRCEQoPX2Rvd25sb2FkX3Rva2VuQhEKD19lbmNyeXB0'
'aW9uX2tleUIRCg9fZW5jcnlwdGlvbl9tYWNCEwoRX2VuY3J5cHRpb25fbm9uY2VCGgoYX2FkZG'
'l0aW9uYWxfbWVzc2FnZV9kYXRhGqkBCgtNZWRpYVVwZGF0ZRI2CgR0eXBlGAEgASgOMiIuRW5j'
'cnlwdGVkQ29udGVudC5NZWRpYVVwZGF0ZS5UeXBlUgR0eXBlEioKEXRhcmdldF9tZXNzYWdlX2'
'lkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JF'
'RBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgAS'
'gOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUS'
'CwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0VQVBACGqQCCg1Db250YWN0VXBkYXRlEj'
'gKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlw'
'ZRI3ChVhdmF0YXJfc3ZnX2NvbXByZXNzZWQYAiABKAxIAFITYXZhdGFyU3ZnQ29tcHJlc3NlZI'
'gBARIfCgh1c2VybmFtZRgDIAEoCUgBUgh1c2VybmFtZYgBARImCgxkaXNwbGF5X25hbWUYBCAB'
'KAlIAlILZGlzcGxheU5hbWWIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCGA'
'oWX2F2YXRhcl9zdmdfY29tcHJlc3NlZEILCglfdXNlcm5hbWVCDwoNX2Rpc3BsYXlfbmFtZRrZ'
'AQoIUHVzaEtleXMSMwoEdHlwZRgBIAEoDjIfLkVuY3J5cHRlZENvbnRlbnQuUHVzaEtleXMuVH'
'lwZVIEdHlwZRIaCgZrZXlfaWQYAiABKANIAFIFa2V5SWSIAQESFQoDa2V5GAMgASgMSAFSA2tl'
'eYgBARIiCgpjcmVhdGVkX2F0GAQgASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUV'
'VFU1QQABIKCgZVUERBVEUQAUIJCgdfa2V5X2lkQgYKBF9rZXlCDQoLX2NyZWF0ZWRfYXQarwEK'
'CUZsYW1lU3luYxIjCg1mbGFtZV9jb3VudGVyGAEgASgDUgxmbGFtZUNvdW50ZXISOQoZbGFzdF'
'9mbGFtZV9jb3VudGVyX2NoYW5nZRgCIAEoA1IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIfCgti'
'ZXN0X2ZyaWVuZBgDIAEoCFIKYmVzdEZyaWVuZBIhCgxmb3JjZV91cGRhdGUYBCABKAhSC2Zvcm'
'NlVXBkYXRlGk0KD1R5cGluZ0luZGljYXRvchIbCglpc190eXBpbmcYASABKAhSCGlzVHlwaW5n'
'Eh0KCmNyZWF0ZWRfYXQYAiABKANSCWNyZWF0ZWRBdBo/ChRVc2VyRGlzY292ZXJ5UmVxdWVzdB'
'InCg9jdXJyZW50X3ZlcnNpb24YASABKAxSDmN1cnJlbnRWZXJzaW9uGjEKE1VzZXJEaXNjb3Zl'
'cnlVcGRhdGUSGgoIbWVzc2FnZXMYASADKAxSCG1lc3NhZ2VzGj0KFEtleVZlcmlmaWNhdGlvbl'
'Byb29mEiUKDmNhbGN1bGF0ZWRfbWFjGAEgASgMUg1jYWxjdWxhdGVkTWFjQgsKCV9ncm91cF9p'
'ZEIRCg9faXNfZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm9maWxlX2NvdW50ZXJCIAoeX3Nlbm'
'Rlcl91c2VyX2Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tfZm9yX2ZyaWVuZF9wcm9tb3Rpb25z'
'QhEKD19tZXNzYWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX21lZGlhX3VwZGF0ZUIRCg9fY29udG'
'FjdF91cGRhdGVCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtfZmxhbWVfc3luY0IMCgpfcHVzaF9r'
'ZXlzQgsKCV9yZWFjdGlvbkIPCg1fdGV4dF9tZXNzYWdlQg8KDV9ncm91cF9jcmVhdGVCDQoLX2'
'dyb3VwX2pvaW5CDwoNX2dyb3VwX3VwZGF0ZUIaChhfcmVzZW5kX2dyb3VwX3B1YmxpY19rZXlC'
'EQoPX2Vycm9yX21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFfbWVzc2FnZUITChFfdHlwaW'
'5nX2luZGljYXRvckIZChdfdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdEIYChZfdXNlcl9kaXNjb3Zl'
'cnlfdXBkYXRlQhkKF19rZXlfdmVyaWZpY2F0aW9uX3Byb29m');
'tleVZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBEmAKFXBhc3N3'
'b3JkbGVzc19yZWNvdmVyeRgaIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuUGFzc3dvcmRMZXNzUm'
'Vjb3ZlcnlIGFIUcGFzc3dvcmRsZXNzUmVjb3ZlcnmIAQESfAofcGFzc3dvcmRsZXNzX3JlY292'
'ZXJ5X2hlYXJ0YmVhdBgbIAEoCzIvLkVuY3J5cHRlZENvbnRlbnQuUGFzc3dvcmRMZXNzUmVjb3'
'ZlcnlIZWFydGJlYXRIGVIdcGFzc3dvcmRsZXNzUmVjb3ZlcnlIZWFydGJlYXSIAQEalgIKDUVy'
'cm9yTWVzc2FnZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYW'
'dlcy5UeXBlUgR0eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2Vp'
'cHRJZCKcAQoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU'
'5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVTU0FHRV9UWVBFEAISFwoTU0VTU0lP'
'Tl9PVVRfT0ZfU1lOQxADEiMKH0dST1VQX05PVF9GT1VORF9PUl9OT1RfQV9NRU1CRVIQBBqHAQ'
'oLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91cF9wdWJs'
'aWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRIiCgpncm91cF9uYW1lGAUgASgJSABSCWdyb3'
'VwTmFtZYgBAUINCgtfZ3JvdXBfbmFtZRo1CglHcm91cEpvaW4SKAoQZ3JvdXBfcHVibGljX2tl'
'eRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkayAIKC0dyb3'
'VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMwoT'
'YWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZIgBARIpCg5uZX'
'dfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2RlbGV0ZV9tZXNz'
'YWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZnRlck'
'1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbmV3X2dyb3VwX25h'
'bWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGq8BCgtUZXh0TW'
'Vzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRl'
'eHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbW'
'Vzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUITChFfcXVvdGVfbWVzc2FnZV9p'
'ZBrOAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEioKEXNlbmRlcl9tZXNzYWdlX2lkGAEgASgJUg'
'9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1wGAIgASgDUgl0aW1lc3RhbXASEgoEdHlwZRgD'
'IAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgEIAEoDEgAUhVhZGRpdGlvbm'
'FsTWVzc2FnZURhdGGIAQFCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGmQKCFJlYWN0aW9u'
'EioKEXRhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSFAoFZW1vamkYAi'
'ABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGr4CCg1NZXNzYWdlVXBkYXRlEjgK'
'BHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUuVHlwZVIEdHlwZR'
'IvChFzZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESPQobbXVs'
'dGlwbGVfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZH'
'MSFwoEdGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1w'
'Ii0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCFAoSX3Nlbm'
'Rlcl9tZXNzYWdlX2lkQgcKBV90ZXh0GoUGCgVNZWRpYRIqChFzZW5kZXJfbWVzc2FnZV9pZBgB'
'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk'
'1lZGlhLlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY29uZHMYAyABKANI'
'AFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNwoXcmVxdWlyZXNfYXV0aGVudGljYX'
'Rpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZXN0YW1wGAUgASgDUgl0'
'aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBAR'
'IqCg5kb3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEioKDmVuY3J5cHRp'
'b25fa2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQESKgoOZW5jcnlwdGlvbl9tYWMYCSABKA'
'xIBFINZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW9uX25vbmNlGAogASgMSAVSD2VuY3J5'
'cHRpb25Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgLIAEoDEgGUhVhZGRpdG'
'lvbmFsTWVzc2FnZURhdGGIAQEiPgoEVHlwZRIMCghSRVVQTE9BRBAAEgkKBUlNQUdFEAESCQoF'
'VklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQiAKHl9kaXNwbGF5X2xpbWl0X2luX21pbGxpc2'
'Vjb25kc0ITChFfcXVvdGVfbWVzc2FnZV9pZEIRCg9fZG93bmxvYWRfdG9rZW5CEQoPX2VuY3J5'
'cHRpb25fa2V5QhEKD19lbmNyeXB0aW9uX21hY0ITChFfZW5jcnlwdGlvbl9ub25jZUIaChhfYW'
'RkaXRpb25hbF9tZXNzYWdlX2RhdGEaqQEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5F'
'bmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKgoRdGFyZ2V0X21lc3NhZ2'
'VfaWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RP'
'UkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYAS'
'ABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlw'
'ZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIapAIKDUNvbnRhY3RVcGRhdG'
'USOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0'
'eXBlEjcKFWF2YXRhcl9zdmdfY29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJTdmdDb21wcmVzc2'
'VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiYKDGRpc3BsYXlfbmFtZRgE'
'IAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAU'
'IYChZfYXZhdGFyX3N2Z19jb21wcmVzc2VkQgsKCV91c2VybmFtZUIPCg1fZGlzcGxheV9uYW1l'
'GtkBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy'
'5UeXBlUgR0eXBlEhoKBmtleV9pZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZXkYAyABKAxIAVID'
'a2V5iAEBEiIKCmNyZWF0ZWRfYXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUSCwoHUk'
'VRVUVTVBAAEgoKBlVQREFURRABQgkKB19rZXlfaWRCBgoEX2tleUINCgtfY3JlYXRlZF9hdBqv'
'AQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDGZsYW1lQ291bnRlchI5ChlsYX'
'N0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh8K'
'C2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcmNlX3VwZGF0ZRgEIAEoCFILZm'
'9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5cGluZxgBIAEoCFIIaXNUeXBp'
'bmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFFVzZXJEaXNjb3ZlcnlSZXF1ZX'
'N0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZlcnNpb24aMQoTVXNlckRpc2Nv'
'dmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZXMaPQoUS2V5VmVyaWZpY2F0aW'
'9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1bGF0ZWRNYWMafQoUUGFzc3dv'
'cmRMZXNzUmVjb3ZlcnkSNQoTcmVjb3ZlcnlTZWNyZXRTaGFyZRgBIAEoDEgAUhNyZWNvdmVyeV'
'NlY3JldFNoYXJliAEBEhYKBmRlbGV0ZRgCIAEoCFIGZGVsZXRlQhYKFF9yZWNvdmVyeVNlY3Jl'
'dFNoYXJlGjMKHVBhc3N3b3JkTGVzc1JlY292ZXJ5SGVhcnRiZWF0EhIKBGhhc2gYASABKAxSBG'
'hhc2hCCwoJX2dyb3VwX2lkQhEKD19pc19kaXJlY3RfY2hhdEIZChdfc2VuZGVyX3Byb2ZpbGVf'
'Y291bnRlckIgCh5fc2VuZGVyX3VzZXJfZGlzY292ZXJ5X3ZlcnNpb25CHAoaX2Fza19mb3JfZn'
'JpZW5kX3Byb21vdGlvbnNCEQoPX21lc3NhZ2VfdXBkYXRlQggKBl9tZWRpYUIPCg1fbWVkaWFf'
'dXBkYXRlQhEKD19jb250YWN0X3VwZGF0ZUISChBfY29udGFjdF9yZXF1ZXN0Qg0KC19mbGFtZV'
'9zeW5jQgwKCl9wdXNoX2tleXNCCwoJX3JlYWN0aW9uQg8KDV90ZXh0X21lc3NhZ2VCDwoNX2dy'
'b3VwX2NyZWF0ZUINCgtfZ3JvdXBfam9pbkIPCg1fZ3JvdXBfdXBkYXRlQhoKGF9yZXNlbmRfZ3'
'JvdXBfcHVibGljX2tleUIRCg9fZXJyb3JfbWVzc2FnZXNCGgoYX2FkZGl0aW9uYWxfZGF0YV9t'
'ZXNzYWdlQhMKEV90eXBpbmdfaW5kaWNhdG9yQhkKF191c2VyX2Rpc2NvdmVyeV9yZXF1ZXN0Qh'
'gKFl91c2VyX2Rpc2NvdmVyeV91cGRhdGVCGQoXX2tleV92ZXJpZmljYXRpb25fcHJvb2ZCGAoW'
'X3Bhc3N3b3JkbGVzc19yZWNvdmVyeUIiCiBfcGFzc3dvcmRsZXNzX3JlY292ZXJ5X2hlYXJ0Ym'
'VhdA==');

View file

@ -0,0 +1,559 @@
// This is a generated file - do not edit.
//
// Generated from passwordless_recovery.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
/// Send from the person who tries to recover their account.
/// This can be done via a link, which will then be opend in the app of the contact.
/// The contact than has to manualy select from which user he got the request.
/// -> Using this phishing is harder, as the user has to manualy select the user to recovery
/// -> The user who wants to recover his account does not need to remember her old username
class RecoveryRequest extends $pb.GeneratedMessage {
factory RecoveryRequest({
$fixnum.Int64? tempId,
$core.List<$core.int>? publicKey,
}) {
final result = create();
if (tempId != null) result.tempId = tempId;
if (publicKey != null) result.publicKey = publicKey;
return result;
}
RecoveryRequest._();
factory RecoveryRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RecoveryRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RecoveryRequest',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'tempId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryRequest copyWith(void Function(RecoveryRequest) updates) =>
super.copyWith((message) => updates(message as RecoveryRequest))
as RecoveryRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecoveryRequest create() => RecoveryRequest._();
@$core.override
RecoveryRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RecoveryRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RecoveryRequest>(create);
static RecoveryRequest? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get tempId => $_getI64(0);
@$pb.TagNumber(1)
set tempId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasTempId() => $_has(0);
@$pb.TagNumber(1)
void clearTempId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get publicKey => $_getN(1);
@$pb.TagNumber(2)
set publicKey($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasPublicKey() => $_has(1);
@$pb.TagNumber(2)
void clearPublicKey() => $_clearField(2);
}
/// Used as envelope for TrustedFriendShare and RecoveryData
class EncryptedEnvelope extends $pb.GeneratedMessage {
factory EncryptedEnvelope({
$core.List<$core.int>? encryptedData,
$core.List<$core.int>? iv,
$core.List<$core.int>? mac,
}) {
final result = create();
if (encryptedData != null) result.encryptedData = encryptedData;
if (iv != null) result.iv = iv;
if (mac != null) result.mac = mac;
return result;
}
EncryptedEnvelope._();
factory EncryptedEnvelope.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedEnvelope.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedEnvelope',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'encryptedData', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'iv', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'mac', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedEnvelope clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedEnvelope copyWith(void Function(EncryptedEnvelope) updates) =>
super.copyWith((message) => updates(message as EncryptedEnvelope))
as EncryptedEnvelope;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedEnvelope create() => EncryptedEnvelope._();
@$core.override
EncryptedEnvelope createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static EncryptedEnvelope getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<EncryptedEnvelope>(create);
static EncryptedEnvelope? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get encryptedData => $_getN(0);
@$pb.TagNumber(1)
set encryptedData($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasEncryptedData() => $_has(0);
@$pb.TagNumber(1)
void clearEncryptedData() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get iv => $_getN(1);
@$pb.TagNumber(2)
set iv($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasIv() => $_has(1);
@$pb.TagNumber(2)
void clearIv() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get mac => $_getN(2);
@$pb.TagNumber(3)
set mac($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasMac() => $_has(2);
@$pb.TagNumber(3)
void clearMac() => $_clearField(3);
}
class TrustedFriendShare_User extends $pb.GeneratedMessage {
factory TrustedFriendShare_User({
$fixnum.Int64? userId,
$core.String? displayName,
$core.List<$core.int>? avatar,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (displayName != null) result.displayName = displayName;
if (avatar != null) result.avatar = avatar;
return result;
}
TrustedFriendShare_User._();
factory TrustedFriendShare_User.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory TrustedFriendShare_User.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'TrustedFriendShare.User',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..aOS(2, _omitFieldNames ? '' : 'displayName')
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'avatar', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare_User clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare_User copyWith(
void Function(TrustedFriendShare_User) updates) =>
super.copyWith((message) => updates(message as TrustedFriendShare_User))
as TrustedFriendShare_User;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TrustedFriendShare_User create() => TrustedFriendShare_User._();
@$core.override
TrustedFriendShare_User createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static TrustedFriendShare_User getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare_User>(create);
static TrustedFriendShare_User? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get displayName => $_getSZ(1);
@$pb.TagNumber(2)
set displayName($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasDisplayName() => $_has(1);
@$pb.TagNumber(2)
void clearDisplayName() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get avatar => $_getN(2);
@$pb.TagNumber(3)
set avatar($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasAvatar() => $_has(2);
@$pb.TagNumber(3)
void clearAvatar() => $_clearField(3);
}
/// Send from the trusted friend to
/// This is encrypted with the received public key.
class TrustedFriendShare extends $pb.GeneratedMessage {
factory TrustedFriendShare({
TrustedFriendShare_User? trustedFriend,
TrustedFriendShare_User? shareUser,
$core.int? threshold,
$core.List<$core.int>? sharedSecretData,
}) {
final result = create();
if (trustedFriend != null) result.trustedFriend = trustedFriend;
if (shareUser != null) result.shareUser = shareUser;
if (threshold != null) result.threshold = threshold;
if (sharedSecretData != null) result.sharedSecretData = sharedSecretData;
return result;
}
TrustedFriendShare._();
factory TrustedFriendShare.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory TrustedFriendShare.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'TrustedFriendShare',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aOM<TrustedFriendShare_User>(1, _omitFieldNames ? '' : 'trustedFriend',
subBuilder: TrustedFriendShare_User.create)
..aOM<TrustedFriendShare_User>(2, _omitFieldNames ? '' : 'shareUser',
subBuilder: TrustedFriendShare_User.create)
..aI(3, _omitFieldNames ? '' : 'threshold')
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'sharedSecretData', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare copyWith(void Function(TrustedFriendShare) updates) =>
super.copyWith((message) => updates(message as TrustedFriendShare))
as TrustedFriendShare;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TrustedFriendShare create() => TrustedFriendShare._();
@$core.override
TrustedFriendShare createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static TrustedFriendShare getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare>(create);
static TrustedFriendShare? _defaultInstance;
/// This allows to display the user which user has send him his recovery data.
@$pb.TagNumber(1)
TrustedFriendShare_User get trustedFriend => $_getN(0);
@$pb.TagNumber(1)
set trustedFriend(TrustedFriendShare_User value) => $_setField(1, value);
@$pb.TagNumber(1)
$core.bool hasTrustedFriend() => $_has(0);
@$pb.TagNumber(1)
void clearTrustedFriend() => $_clearField(1);
@$pb.TagNumber(1)
TrustedFriendShare_User ensureTrustedFriend() => $_ensure(0);
/// This allows to display the userdata, showing that he is recovering the correct person.
@$pb.TagNumber(2)
TrustedFriendShare_User get shareUser => $_getN(1);
@$pb.TagNumber(2)
set shareUser(TrustedFriendShare_User value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasShareUser() => $_has(1);
@$pb.TagNumber(2)
void clearShareUser() => $_clearField(2);
@$pb.TagNumber(2)
TrustedFriendShare_User ensureShareUser() => $_ensure(1);
/// The minimum threshold required to decrypte the shares.
@$pb.TagNumber(3)
$core.int get threshold => $_getIZ(2);
@$pb.TagNumber(3)
set threshold($core.int value) => $_setSignedInt32(2, value);
@$pb.TagNumber(3)
$core.bool hasThreshold() => $_has(2);
@$pb.TagNumber(3)
void clearThreshold() => $_clearField(3);
/// The actual share which will become: SharedSecretData
@$pb.TagNumber(4)
$core.List<$core.int> get sharedSecretData => $_getN(3);
@$pb.TagNumber(4)
set sharedSecretData($core.List<$core.int> value) => $_setBytes(3, value);
@$pb.TagNumber(4)
$core.bool hasSharedSecretData() => $_has(3);
@$pb.TagNumber(4)
void clearSharedSecretData() => $_clearField(4);
}
/// The user identity keys. Can be used to restore the data backup.
class RecoveryData extends $pb.GeneratedMessage {
factory RecoveryData({
$fixnum.Int64? userId,
$core.List<$core.int>? keyManager,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (keyManager != null) result.keyManager = keyManager;
return result;
}
RecoveryData._();
factory RecoveryData.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RecoveryData.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RecoveryData',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'keyManager', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryData clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryData copyWith(void Function(RecoveryData) updates) =>
super.copyWith((message) => updates(message as RecoveryData))
as RecoveryData;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecoveryData create() => RecoveryData._();
@$core.override
RecoveryData createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RecoveryData getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RecoveryData>(create);
static RecoveryData? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(3)
$core.List<$core.int> get keyManager => $_getN(1);
@$pb.TagNumber(3)
set keyManager($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(3)
$core.bool hasKeyManager() => $_has(1);
@$pb.TagNumber(3)
void clearKeyManager() => $_clearField(3);
}
/// After received all shares this is decrypted by the user restoring its own
class SharedSecretData extends $pb.GeneratedMessage {
factory SharedSecretData({
$core.List<$core.int>? recoveryData,
$core.List<$core.int>? pinSeed,
$core.List<$core.int>? pinUnlockToken,
$core.String? emailHint,
$core.List<$core.int>? encryptedServerKeyNonce,
$core.List<$core.int>? encryptedServerKeyMac,
}) {
final result = create();
if (recoveryData != null) result.recoveryData = recoveryData;
if (pinSeed != null) result.pinSeed = pinSeed;
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
if (emailHint != null) result.emailHint = emailHint;
if (encryptedServerKeyNonce != null)
result.encryptedServerKeyNonce = encryptedServerKeyNonce;
if (encryptedServerKeyMac != null)
result.encryptedServerKeyMac = encryptedServerKeyMac;
return result;
}
SharedSecretData._();
factory SharedSecretData.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory SharedSecretData.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'SharedSecretData',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'recoveryData', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'pinSeed', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY)
..aOS(4, _omitFieldNames ? '' : 'emailHint')
..a<$core.List<$core.int>>(
5, _omitFieldNames ? '' : 'encryptedServerKeyNonce', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
6, _omitFieldNames ? '' : 'encryptedServerKeyMac', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData copyWith(void Function(SharedSecretData) updates) =>
super.copyWith((message) => updates(message as SharedSecretData))
as SharedSecretData;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SharedSecretData create() => SharedSecretData._();
@$core.override
SharedSecretData createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static SharedSecretData getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<SharedSecretData>(create);
static SharedSecretData? _defaultInstance;
/// The recovery data is encrypted in case a second factor was chosen.
@$pb.TagNumber(1)
$core.List<$core.int> get recoveryData => $_getN(0);
@$pb.TagNumber(1)
set recoveryData($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasRecoveryData() => $_has(0);
@$pb.TagNumber(1)
void clearRecoveryData() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get pinSeed => $_getN(1);
@$pb.TagNumber(2)
set pinSeed($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasPinSeed() => $_has(1);
@$pb.TagNumber(2)
void clearPinSeed() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get pinUnlockToken => $_getN(2);
@$pb.TagNumber(3)
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasPinUnlockToken() => $_has(2);
@$pb.TagNumber(3)
void clearPinUnlockToken() => $_clearField(3);
@$pb.TagNumber(4)
$core.String get emailHint => $_getSZ(3);
@$pb.TagNumber(4)
set emailHint($core.String value) => $_setString(3, value);
@$pb.TagNumber(4)
$core.bool hasEmailHint() => $_has(3);
@$pb.TagNumber(4)
void clearEmailHint() => $_clearField(4);
@$pb.TagNumber(5)
$core.List<$core.int> get encryptedServerKeyNonce => $_getN(4);
@$pb.TagNumber(5)
set encryptedServerKeyNonce($core.List<$core.int> value) =>
$_setBytes(4, value);
@$pb.TagNumber(5)
$core.bool hasEncryptedServerKeyNonce() => $_has(4);
@$pb.TagNumber(5)
void clearEncryptedServerKeyNonce() => $_clearField(5);
@$pb.TagNumber(6)
$core.List<$core.int> get encryptedServerKeyMac => $_getN(5);
@$pb.TagNumber(6)
set encryptedServerKeyMac($core.List<$core.int> value) =>
$_setBytes(5, value);
@$pb.TagNumber(6)
$core.bool hasEncryptedServerKeyMac() => $_has(5);
@$pb.TagNumber(6)
void clearEncryptedServerKeyMac() => $_clearField(6);
}
const $core.bool _omitFieldNames =
$core.bool.fromEnvironment('protobuf.omit_field_names');
const $core.bool _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,11 @@
// This is a generated file - do not edit.
//
// Generated from passwordless_recovery.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports

View file

@ -0,0 +1,182 @@
// This is a generated file - do not edit.
//
// Generated from passwordless_recovery.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use recoveryRequestDescriptor instead')
const RecoveryRequest$json = {
'1': 'RecoveryRequest',
'2': [
{'1': 'temp_id', '3': 1, '4': 1, '5': 3, '10': 'tempId'},
{'1': 'public_key', '3': 2, '4': 1, '5': 12, '10': 'publicKey'},
],
};
/// Descriptor for `RecoveryRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recoveryRequestDescriptor = $convert.base64Decode(
'Cg9SZWNvdmVyeVJlcXVlc3QSFwoHdGVtcF9pZBgBIAEoA1IGdGVtcElkEh0KCnB1YmxpY19rZX'
'kYAiABKAxSCXB1YmxpY0tleQ==');
@$core.Deprecated('Use encryptedEnvelopeDescriptor instead')
const EncryptedEnvelope$json = {
'1': 'EncryptedEnvelope',
'2': [
{'1': 'encrypted_data', '3': 1, '4': 1, '5': 12, '10': 'encryptedData'},
{'1': 'iv', '3': 2, '4': 1, '5': 12, '10': 'iv'},
{'1': 'mac', '3': 3, '4': 1, '5': 12, '10': 'mac'},
],
};
/// Descriptor for `EncryptedEnvelope`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedEnvelopeDescriptor = $convert.base64Decode(
'ChFFbmNyeXB0ZWRFbnZlbG9wZRIlCg5lbmNyeXB0ZWRfZGF0YRgBIAEoDFINZW5jcnlwdGVkRG'
'F0YRIOCgJpdhgCIAEoDFICaXYSEAoDbWFjGAMgASgMUgNtYWM=');
@$core.Deprecated('Use trustedFriendShareDescriptor instead')
const TrustedFriendShare$json = {
'1': 'TrustedFriendShare',
'2': [
{
'1': 'trusted_friend',
'3': 1,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.TrustedFriendShare.User',
'10': 'trustedFriend'
},
{
'1': 'share_user',
'3': 2,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.TrustedFriendShare.User',
'10': 'shareUser'
},
{'1': 'threshold', '3': 3, '4': 1, '5': 5, '10': 'threshold'},
{
'1': 'shared_secret_data',
'3': 4,
'4': 1,
'5': 12,
'10': 'sharedSecretData'
},
],
'3': [TrustedFriendShare_User$json],
};
@$core.Deprecated('Use trustedFriendShareDescriptor instead')
const TrustedFriendShare_User$json = {
'1': 'User',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'display_name', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
{'1': 'avatar', '3': 3, '4': 1, '5': 12, '10': 'avatar'},
],
};
/// Descriptor for `TrustedFriendShare`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List trustedFriendShareDescriptor = $convert.base64Decode(
'ChJUcnVzdGVkRnJpZW5kU2hhcmUSVQoOdHJ1c3RlZF9mcmllbmQYASABKAsyLi5wYXNzd29yZG'
'xlc3NfcmVjb3ZlcnkuVHJ1c3RlZEZyaWVuZFNoYXJlLlVzZXJSDXRydXN0ZWRGcmllbmQSTQoK'
'c2hhcmVfdXNlchgCIAEoCzIuLnBhc3N3b3JkbGVzc19yZWNvdmVyeS5UcnVzdGVkRnJpZW5kU2'
'hhcmUuVXNlclIJc2hhcmVVc2VyEhwKCXRocmVzaG9sZBgDIAEoBVIJdGhyZXNob2xkEiwKEnNo'
'YXJlZF9zZWNyZXRfZGF0YRgEIAEoDFIQc2hhcmVkU2VjcmV0RGF0YRpaCgRVc2VyEhcKB3VzZX'
'JfaWQYASABKANSBnVzZXJJZBIhCgxkaXNwbGF5X25hbWUYAiABKAlSC2Rpc3BsYXlOYW1lEhYK'
'BmF2YXRhchgDIAEoDFIGYXZhdGFy');
@$core.Deprecated('Use recoveryDataDescriptor instead')
const RecoveryData$json = {
'1': 'RecoveryData',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'key_manager', '3': 3, '4': 1, '5': 12, '10': 'keyManager'},
],
};
/// Descriptor for `RecoveryData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recoveryDataDescriptor = $convert.base64Decode(
'CgxSZWNvdmVyeURhdGESFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEh8KC2tleV9tYW5hZ2VyGA'
'MgASgMUgprZXlNYW5hZ2Vy');
@$core.Deprecated('Use sharedSecretDataDescriptor instead')
const SharedSecretData$json = {
'1': 'SharedSecretData',
'2': [
{'1': 'recovery_data', '3': 1, '4': 1, '5': 12, '10': 'recoveryData'},
{
'1': 'pin_seed',
'3': 2,
'4': 1,
'5': 12,
'9': 0,
'10': 'pinSeed',
'17': true
},
{
'1': 'pin_unlock_token',
'3': 3,
'4': 1,
'5': 12,
'9': 1,
'10': 'pinUnlockToken',
'17': true
},
{
'1': 'email_hint',
'3': 4,
'4': 1,
'5': 9,
'9': 2,
'10': 'emailHint',
'17': true
},
{
'1': 'encrypted_server_key_nonce',
'3': 5,
'4': 1,
'5': 12,
'9': 3,
'10': 'encryptedServerKeyNonce',
'17': true
},
{
'1': 'encrypted_server_key_mac',
'3': 6,
'4': 1,
'5': 12,
'9': 4,
'10': 'encryptedServerKeyMac',
'17': true
},
],
'8': [
{'1': '_pin_seed'},
{'1': '_pin_unlock_token'},
{'1': '_email_hint'},
{'1': '_encrypted_server_key_nonce'},
{'1': '_encrypted_server_key_mac'},
],
};
/// Descriptor for `SharedSecretData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sharedSecretDataDescriptor = $convert.base64Decode(
'ChBTaGFyZWRTZWNyZXREYXRhEiMKDXJlY292ZXJ5X2RhdGEYASABKAxSDHJlY292ZXJ5RGF0YR'
'IeCghwaW5fc2VlZBgCIAEoDEgAUgdwaW5TZWVkiAEBEi0KEHBpbl91bmxvY2tfdG9rZW4YAyAB'
'KAxIAVIOcGluVW5sb2NrVG9rZW6IAQESIgoKZW1haWxfaGludBgEIAEoCUgCUgllbWFpbEhpbn'
'SIAQESQAoaZW5jcnlwdGVkX3NlcnZlcl9rZXlfbm9uY2UYBSABKAxIA1IXZW5jcnlwdGVkU2Vy'
'dmVyS2V5Tm9uY2WIAQESPAoYZW5jcnlwdGVkX3NlcnZlcl9rZXlfbWFjGAYgASgMSARSFWVuY3'
'J5cHRlZFNlcnZlcktleU1hY4gBAUILCglfcGluX3NlZWRCEwoRX3Bpbl91bmxvY2tfdG9rZW5C'
'DQoLX2VtYWlsX2hpbnRCHQobX2VuY3J5cHRlZF9zZXJ2ZXJfa2V5X25vbmNlQhsKGV9lbmNyeX'
'B0ZWRfc2VydmVyX2tleV9tYWM=');

View file

@ -58,6 +58,8 @@ message EncryptedContent {
optional UserDiscoveryRequest user_discovery_request = 22;
optional UserDiscoveryUpdate user_discovery_update = 23;
optional KeyVerificationProof key_verification_proof = 24;
optional PasswordLessRecovery passwordless_recovery = 26;
optional PasswordLessRecoveryHeartbeat passwordless_recovery_heartbeat = 27;
message ErrorMessages {
enum Type {
@ -82,9 +84,7 @@ message EncryptedContent {
bytes group_public_key = 1;
}
message ResendGroupPublicKey {
}
message ResendGroupPublicKey {}
message GroupUpdate {
string group_action_type = 1; // GroupActionType.name
@ -217,4 +217,13 @@ message EncryptedContent {
bytes calculated_mac = 1;
}
message PasswordLessRecovery {
optional bytes recoverySecretShare = 1;
bool delete = 2;
}
message PasswordLessRecoveryHeartbeat {
bytes hash = 1;
}
}

View file

@ -46,40 +46,24 @@ message TrustedFriendShare {
}
}
// After received all shares this is decrypted by the user restoring its own
message SharedSecretData {
// No second factor was selected
optional RecoveryData recovery_data = 1;
// Server has
optional SecondFactorMail second_factor_mail = 2;
optional SecondFactorPin second_factor_pin = 3;
// The recovery data in case a second factor was used
// The decryption key is loaded from the server either using the PIN or the MAIL
optional bytes recovery_data_encrypted = 4;
message SecondFactorPin {
// Required to try the PIN to get the share from the server.
// This prevents that someone else can lock the pin, as the server only
// allows 3 tries then after 1 day again 3 tries until the key is deleted.
bytes unlock_token = 1;
// This never is send to the server but used to hash the pin before sending it to the server.
// This prevents that the server every knows the shot 4-diget PIN.
bytes pin_seed = 2;
}
message SecondFactorMail {}
}
// The data which is recovered at the end.
// The backup_master_key allows to recover the actual backup uploaded in the background to the server.
// In case the backup is not available any more the user can use its user_id and his private_key to requister as a new user.
// The user identity keys. Can be used to restore the data backup.
message RecoveryData {
int64 user_id = 1;
bytes key_manager = 3;
}
// After received all shares this is decrypted by the user restoring its own
message SharedSecretData {
// The recovery data is encrypted in case a second factor was chosen.
bytes recovery_data = 1;
optional bytes pin_seed = 2;
optional bytes pin_unlock_token = 3;
optional string email_hint = 4;
optional bytes encrypted_server_key_nonce = 5;
optional bytes encrypted_server_key_mac = 6;
}

View file

@ -758,6 +758,22 @@ class ApiService {
return sendRequestSync(req, contactId: userId.toInt());
}
Future<Result> registerPasswordLessRecovery(
List<int> encryptedServerKey,
List<int>? pinUnlockToken,
) async {
final req = createClientToServerFromApplicationData(
ApplicationData(
registerPasswordlessRecovery:
ApplicationData_RegisterPasswordLessRecovery(
encryptedServerKey: encryptedServerKey,
pinUnlockToken: pinUnlockToken,
),
),
);
return sendRequestSync(req);
}
Future<Result> addAdditionalUser(Int64 userId) async {
final get = ApplicationData_AddAdditionalUser()..userId = userId;
final appData = ApplicationData()..addAdditionalUser = get;

View file

@ -33,6 +33,7 @@ import 'package:twonly/src/services/group.service.dart';
import 'package:twonly/src/services/key_verification.service.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart';
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
import 'package:twonly/src/services/passwordless_recovery.service.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart';
@ -370,6 +371,24 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
return (null, null);
}
if (content.hasPasswordlessRecovery()) {
await PasswordlessRecoveryService.handlePasswordlessRecovery(
fromUserId,
content.passwordlessRecovery,
receiptId,
);
return (null, null);
}
if (content.hasPasswordlessRecoveryHeartbeat()) {
await PasswordlessRecoveryService.handlePasswordlessRecoveryHeartbeat(
fromUserId,
content.passwordlessRecoveryHeartbeat,
receiptId,
);
return (null, null);
}
if (content.hasContactUpdate()) {
await handleContactUpdate(
fromUserId,

View file

@ -1,15 +1,25 @@
import 'dart:async';
import 'dart:convert' show base64Encode, utf8;
import 'dart:typed_data';
import 'dart:convert' show utf8;
import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart' hide Hmac;
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'
show FlutterChacha20;
import 'package:cryptography_plus/cryptography_plus.dart' show SecretKey;
import 'package:hashlib/hashlib.dart' show Scrypt;
import 'package:cryptography_plus/cryptography_plus.dart' show Hmac, SecretKey;
import 'package:drift/drift.dart';
import 'package:fixnum/fixnum.dart';
import 'package:twonly/core/bridge/wrapper.dart';
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/userdata.model.dart'
show PasswordLessRecovery;
import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery/types.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb;
import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -22,94 +32,352 @@ class PasswordlessRecoveryService {
required String secondFactorValue,
required int threshold,
}) async {
await twonlyDB.contactsDao.resetRecoveryDataForAllContacts();
// 1. Get all currently trusted friends contacts and send them delete messages
final oldTrustedFriends = await (twonlyDB.select(
twonlyDB.contacts,
)..where((t) => t.recoveryIsTrustedFriend.equals(true))).get();
// 2. Update the user model configuration
for (final contact in oldTrustedFriends) {
try {
await sendCipherText(
contact.userId,
pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
delete: true,
),
),
);
} catch (e) {
Log.error(
'Failed to send delete PasswordLessRecovery message to contact ${contact.userId}: $e',
);
}
}
// 1. Reset current recovery data to ensure a clean state
await twonlyDB.contactsDao.resetRecoveryDataForAllContacts();
await UserService.update((u) => u.passwordLessRecovery = null);
final config = PasswordLessRecovery();
final chacha20 = FlutterChacha20.poly1305Aead();
final serverKey = getRandomUint8List(32);
Uint8List? protectedEmailServerKey;
Uint8List? pinUnlockToken;
Uint8List? protectedPin;
// 2. If enabled, handle the second factor and create serverKey
Uint8List? serverKey;
Uint8List? encryptedServerKey;
SecretKey? secondFactorEncryptedServerKeyKey;
String? emailHint;
switch (secondFactorType) {
case SecondFactorType.email:
final emailSeed = getRandomUint8List(32);
// Store it, so the user can see it again.
config.email = secondFactorValue;
final chacha20 = FlutterChacha20.poly1305Aead();
final scrypt = Scrypt(
cost: 65536,
salt: emailSeed,
);
final protectedEmailKey = scrypt
.convert(utf8.encode(secondFactorValue))
.bytes;
// there must be a emailSeed like for the pin...
// The serverKey is encrypted with the email; this protects the server from seeing the user's email address, while
// also ensuring that the server can only send the real secret to the user's configured email, as a different
// email will result in a different secret key.
final secretBox = await chacha20.encrypt(
serverKey,
secretKey: SecretKey(
Uint8List.fromList(protectedEmailKey),
),
nonce: chacha20.newNonce(),
);
// This is only stored so the user can see there email, and verify that he has set the valid mail...
config.email = secondFactorValue;
emailHint = createEmailHint(secondFactorValue);
protectedEmailServerKey = EncryptedEnvelope(
encryptedData: secretBox.cipherText,
// iv: secretBox.nonce,
mac: secretBox.mac.bytes,
).writeToBuffer();
// E-Mail Protection:
// - Server can only learn the email during recovery. Ensured as the server gets the NONCE and MAC to decrpyt during recovery.
// - Trusted-friends: Server key is send to the mail, they whould need access to the user's mail account.
secondFactorEncryptedServerKeyKey = SecretKey(
Uint8List.fromList(utf8.encode(config.email!)),
);
case SecondFactorType.pin:
final pinSeed = getRandomUint8List(32);
pinUnlockToken = getRandomUint8List(32);
// The pin seed is to ensure that the server does never learns the real 4-digit pin. The seed is never send to
// the server. As the pin are heavily protected against brute-forcing and will be discared by the server after X
// tries, this prevents that a malicous user trigger this deletion...
config.pinSeed = base64Encode(pinSeed);
config.pinUnlockToken = base64Encode(pinUnlockToken);
// The pin seed - never shared with the server - ensures that the server is unable to brute-force real user's pin.
config.pinSeed = getRandomUint8List(32);
final scrypt = Scrypt(
cost: 65536,
salt: pinSeed,
// As the pin is heavily protected against brute-forcing e.g. will be deleted by the server after X tries, the
// unlock token is required to prevent a malicous user (except the trusted friends) to triger this deletion.
config.pinUnlockToken = getRandomUint8List(32);
// Brute-force protection for the user's pin:
// - Server: Does not know the seed.
// - Trusted friends: Can only check the result X times before the server deletes the key.
final pinProtectionKey = await Hmac.sha256().calculateMac(
Uint8List.fromList(utf8.encode(secondFactorValue)),
secretKey: SecretKey(config.pinSeed!),
);
final key = scrypt.convert(utf8.encode(secondFactorValue)).bytes;
protectedPin = key.sublist(0, 32);
// To restore the user has to provide the server with this encryption key. The server then can verify the
// correct pin was entered, when decrypting the server key as he also receives the mac and nonce from the user
// during recovery. Only when the mac is correct the server provides the user with the serverKey.
secondFactorEncryptedServerKeyKey = SecretKey(pinProtectionKey.bytes);
case SecondFactorType.none:
}
// 3. Use the second factor and generate the shares...
if (secondFactorEncryptedServerKeyKey != null) {
// The server key is used to encrypt the RecoveryData of the users. This ensures that when the trusted friends
// colaberate, they additional need the serverKey to decrypt the user's key.
serverKey = getRandomUint8List(32);
// Generate the shares, and store them in the contacrs.table.
final secretBox = await chacha20.encrypt(
serverKey,
secretKey: secondFactorEncryptedServerKeyKey,
nonce: chacha20.newNonce(),
);
Log.info('Enabling passwordless recovery with:');
Log.info(' - Trusted Friends: $trustedFriendIds');
Log.info(' - Second Factor Type: $secondFactorType');
Log.info(' - Second Factor Value: $secondFactorValue');
Log.info(' - Threshold: $threshold');
// This is send to the server and then deleted localy
encryptedServerKey = Uint8List.fromList(secretBox.cipherText);
// Use the serverKey to protect the recovery_data_encrypted
// Because the server does not know the nonce and the mac, he is unable to link a email to the user and also
// unable to brute-force the pin.
config
..encryptedServerKeyNonce = secretBox.nonce
..encryptedServerKey = encryptedServerKey
..encryptedServerKeyMac = secretBox.mac.bytes;
}
// 4. Send the data to the server:
// 3. Using shamir's secret to generate the shares for the users.
// to th server
// protectedPin, pinUnlockToken, protectedEmailServerKey;
// 3.1. Create the SharedSecretData
// 5. send to the contacts / implement the heartbeat check, and notify the user in case the hearbeath shows that to few users are active...
var recoveryData = RecoveryData(
userId: Int64(userService.currentUser.userId),
keyManager: await RustKeyManager.serialize(),
).writeToBuffer();
if (serverKey != null) {
// Second factor was enabled, so encrypt the recoveryData using the serverKey.
final secretBox = await chacha20.encrypt(
recoveryData,
secretKey: SecretKey(serverKey),
nonce: chacha20.newNonce(),
);
recoveryData = EncryptedEnvelope(
encryptedData: secretBox.cipherText,
iv: secretBox.nonce,
mac: secretBox.mac.bytes,
).writeToBuffer();
}
final sharedSecretData = SharedSecretData(
recoveryData: recoveryData,
pinSeed: config.pinSeed,
pinUnlockToken: config.pinUnlockToken,
emailHint: emailHint,
encryptedServerKeyMac: config.encryptedServerKeyMac,
encryptedServerKeyNonce: config.encryptedServerKeyNonce,
).writeToBuffer();
// 3.2. Use the amount of trusted friends to generate the shares
final List<Uint8List> shares;
try {
shares = await RustUtils.generateShares(
secret: sharedSecretData,
total: trustedFriendIds.length,
threshold: threshold,
);
if (shares.length != trustedFriendIds.length) {
Log.error('shares.length != trustedFriendIds.length');
return false;
}
} catch (e) {
Log.error('Failed to generate secret shares: $e');
return false;
}
// 3.4. Store the shares in the contact's rows
for (final contactId in trustedFriendIds) {
await twonlyDB.contactsDao.updateContact(
contactId,
ContactsCompanion(
recoveryIsTrustedFriend: const Value(true),
recoveryLastHeartbeat: const Value(null),
recoverySecretShare: Value(shares.removeLast()),
),
);
}
// The passwordless is configured sucessfully.
return true;
}
static Future<void> performHeartbeat() async {
final config = userService.currentUser.passwordLessRecovery;
if (config != null) {
final lastHeartbeat = config.lastServerHeartbeat;
final isOlderThanAMonth =
lastHeartbeat != null &&
clock.now().difference(lastHeartbeat).inDays > 30;
if ((lastHeartbeat == null || isOlderThanAMonth) &&
config.encryptedServerKey != null) {
final res = await apiService.registerPasswordLessRecovery(
config.encryptedServerKey!,
config.pinUnlockToken,
);
if (res.isSuccess) {
await UserService.update((u) {
u.passwordLessRecovery?.lastServerHeartbeat = clock.now();
});
}
}
// Get all contacts where recoveryLastHeartbeat is NULL. Then for each contacts send
final pendingShares =
await (twonlyDB.select(twonlyDB.contacts)..where(
(t) =>
t.recoveryIsTrustedFriend.equals(true) &
t.recoveryLastHeartbeat.isNull() &
t.recoverySecretShare.isNotNull(),
))
.get();
for (final contact in pendingShares) {
try {
await sendCipherText(
contact.userId,
pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
recoverySecretShare: contact.recoverySecretShare,
delete: false,
),
),
);
await twonlyDB.contactsDao.updateContact(
contact.userId,
ContactsCompanion(
recoveryLastHeartbeat: Value(clock.now()),
),
);
} catch (e) {
Log.error(
'Failed to send PasswordLessRecovery share to contact ${contact.userId}: $e',
);
}
}
}
// Send heartbeat to the friends I am a trusted friend.
final oneWeekAgo = clock.now().subtract(const Duration(days: 7));
final trustedFriendsToNotify =
await (twonlyDB.select(twonlyDB.contacts)..where(
(t) =>
t.recoveryContactsSecretShare.isNotNull() &
(t.recoveryContactsLastHeartbeat.isNull() |
t.recoveryContactsLastHeartbeat.isSmallerThanValue(
oneWeekAgo,
)),
))
.get();
for (final contact in trustedFriendsToNotify) {
try {
final share = contact.recoveryContactsSecretShare!;
final hash = sha256.convert(share).bytes;
await sendCipherText(
contact.userId,
pb.EncryptedContent(
passwordlessRecoveryHeartbeat:
pb.EncryptedContent_PasswordLessRecoveryHeartbeat(
hash: hash,
),
),
);
await twonlyDB.contactsDao.updateContact(
contact.userId,
ContactsCompanion(
recoveryContactsLastHeartbeat: Value(clock.now()),
),
);
} catch (e) {
Log.error(
'Failed to send PasswordLessRecoveryHeartbeat to contact ${contact.userId}: $e',
);
}
}
}
static Future<void> handlePasswordlessRecovery(
int fromUserId,
pb.EncryptedContent_PasswordLessRecovery msg,
String receiptId,
) async {
if (msg.delete) {
Log.info(
'[$receiptId] Received request to delete passwordless recovery share from contact $fromUserId',
);
await twonlyDB.contactsDao.updateContact(
fromUserId,
const ContactsCompanion(
recoveryContactsSecretShare: Value(null),
recoveryContactsLastHeartbeat: Value(null),
),
);
} else if (msg.hasRecoverySecretShare()) {
Log.info(
'[$receiptId] Received new passwordless recovery share from contact $fromUserId',
);
await twonlyDB.contactsDao.updateContact(
fromUserId,
ContactsCompanion(
recoveryContactsSecretShare: Value(
Uint8List.fromList(msg.recoverySecretShare),
),
recoveryContactsLastHeartbeat: const Value(
null, // this will trigger that a heartbeat will be send...
),
),
);
}
}
static Future<void> handlePasswordlessRecoveryHeartbeat(
int fromUserId,
pb.EncryptedContent_PasswordLessRecoveryHeartbeat msg,
String receiptId,
) async {
Log.info(
'[$receiptId] Received passwordless recovery heartbeat from contact $fromUserId',
);
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
final storedShare = contact?.recoverySecretShare;
if (storedShare == null) {
unawaited(
sendCipherText(
fromUserId,
pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
delete: true,
),
),
),
);
Log.warn(
'[$receiptId] Received passwordless recovery heartbeat from $fromUserId but we did not send him a secret share.',
);
return;
}
final computedHash = sha256.convert(storedShare).bytes;
final recoveryLastHeartbeat =
const ListEquality().equals(computedHash, msg.hash)
? clock.now()
: null; // The stored share not valid (maybe a old backup was restored). This will cause the performHeartbeat to resend him his share
Log.info(
'[$receiptId] Got heartbeat: ($recoveryLastHeartbeat)',
);
await twonlyDB.contactsDao.updateContact(
fromUserId,
ContactsCompanion(
recoveryLastHeartbeat: Value(recoveryLastHeartbeat),
),
);
}
}

View file

@ -405,3 +405,26 @@ String joinWithAnd(List<String> items, String andWord) {
if (items.length == 1) return items.first;
return '${items.sublist(0, items.length - 1).join(', ')} $andWord ${items.last}';
}
String createEmailHint(String email) {
final parts = email.split('@');
if (parts.length != 2) return email;
final local = parts[0];
final domain = parts[1];
final localMasked = local.length > 2
? '${local[0]}${'*' * (local.length - 2)}${local[local.length - 1]}'
: local;
final domainParts = domain.split('.');
if (domainParts.isEmpty) return '$localMasked@$domain';
final domainName = domainParts[0];
final domainNameMasked = domainName.length > 1
? '${domainName[0]}${'*' * (domainName.length - 1)}'
: domainName;
final restDomain = domainParts.skip(1).join('.');
return '$localMasked@$domainNameMasked${restDomain.isNotEmpty ? '.$restDomain' : ''}';
}

View file

@ -1,6 +1,5 @@
use std::io::Result;
fn main() -> Result<()> {
prost_build::compile_protos(&["src/user_discovery/types.proto"], &["src/"])?;
prost_build::compile_protos(&["src/passwordless_recovery/types.proto"], &["src/"])?;
Ok(())
}

View file

@ -108,4 +108,12 @@ impl RustKeyManager {
crate::keys::KeyManager::remove_from_keychain(&ctx.secure_storage)?;
Ok(())
}
/// Serialize the key_manager. Needed for the passwordless_recovery feature.
pub async fn serialize() -> Result<Vec<u8>> {
let ctx = get_twonly_flutter()?;
let key_manager = ctx.key_manager.lock().await;
let serialized_bytes = postcard::to_allocvec(&*key_manager)?;
Ok(serialized_bytes)
}
}

View file

@ -1,3 +1,29 @@
pub mod backup;
pub mod key_manager;
pub mod user_discovery;
use crate::error::Result;
use blahaj::{Share, Sharks};
pub struct RustUtils {}
impl RustUtils {
pub fn generate_shares(secret: Vec<u8>, total: u8, threshold: u8) -> Result<Vec<Vec<u8>>> {
let sharks = Sharks(threshold);
let dealer = sharks.dealer(&secret);
let shares = dealer.take(total as usize).map(|x| Vec::from(&x)).collect();
Ok(shares)
}
pub fn recover_secret(shares: Vec<Vec<u8>>, threshold: u8) -> Result<Vec<u8>> {
let sharks = Sharks(threshold);
let shares: Vec<Share> = shares
.iter()
.filter_map(|x| Share::try_from(x.as_slice()).ok())
.collect();
let secret = sharks
.recover(&shares)
.map_err(|e| crate::error::TwonlyError::Generic(e.to_string()))?;
Ok(secret)
}
}

View file

@ -26,6 +26,8 @@
// Section: imports
use crate::user_discovery::traits::UserDiscoveryStore;
use crate::user_discovery::traits::UserDiscoveryUtils;
use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable};
use flutter_rust_bridge::{Handler, IntoIntoDart};
@ -38,7 +40,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueMoi,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1867463121;
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -508562557;
// Section: executor
@ -544,6 +546,43 @@ fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_remove_signed_pre
})().await)
} })
}
fn wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_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_serialize",
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::serialize()
.await?;
Ok(output_ok)
})()
.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,
@ -600,6 +639,349 @@ let api_record = <Vec<u8>>::sse_decode(&mut deserializer);deserializer.end(); mo
})().await)
} })
}
fn wire__crate__bridge__wrapper__rust_utils_generate_shares_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_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "rust_utils_generate_shares",
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_secret = <Vec<u8>>::sse_decode(&mut deserializer);
let api_total = <u8>::sse_decode(&mut deserializer);
let api_threshold = <u8>::sse_decode(&mut deserializer);
deserializer.end();
move |context| {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok = crate::bridge::wrapper::RustUtils::generate_shares(
api_secret,
api_total,
api_threshold,
)?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__bridge__wrapper__rust_utils_recover_secret_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_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "rust_utils_recover_secret",
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_shares = <Vec<Vec<u8>>>::sse_decode(&mut deserializer);
let api_threshold = <u8>::sse_decode(&mut deserializer);
deserializer.end();
move |context| {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || {
let output_ok = crate::bridge::wrapper::RustUtils::recover_secret(
api_shares,
api_threshold,
)?;
Ok(output_ok)
})(),
)
}
},
)
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_announced_user_by_public_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: "user_discovery_store_flutter_get_announced_user_by_public_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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_public_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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_announced_user_by_public_id(&api_that, api_public_id).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_config_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: "user_discovery_store_flutter_get_config", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_config(&api_that).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_promotion_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: "user_discovery_store_flutter_get_contact_promotion", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_contact_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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_contact_promotion(&api_that, api_contact_id).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_version_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: "user_discovery_store_flutter_get_contact_version", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_contact_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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_contact_version(&api_that, api_contact_id).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_other_promotions_by_public_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: "user_discovery_store_flutter_get_other_promotions_by_public_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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_public_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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_other_promotions_by_public_id(&api_that, api_public_id).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_own_promotions_after_version_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: "user_discovery_store_flutter_get_own_promotions_after_version", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_version = <u32>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_own_promotions_after_version(&api_that, api_version).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_share_for_contact_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: "user_discovery_store_flutter_get_share_for_contact", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_contact_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::callbacks::user_discovery::UserDiscoveryStoreFlutter::get_share_for_contact(&api_that, api_contact_id).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_new_user_relation_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: "user_discovery_store_flutter_push_new_user_relation", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_from_contact_id = <i64>::sse_decode(&mut deserializer);
let api_announced_user = <crate::bridge::AnnouncedUser>::sse_decode(&mut deserializer);
let api_public_key_verified_timestamp = <Option<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::callbacks::user_discovery::UserDiscoveryStoreFlutter::push_new_user_relation(&api_that, api_from_contact_id, api_announced_user, api_public_key_verified_timestamp).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_own_promotion_and_clear_old_version_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: "user_discovery_store_flutter_push_own_promotion_and_clear_old_version", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_version = <u32>::sse_decode(&mut deserializer);
let api_promotion = <Vec<u8>>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::push_own_promotion_and_clear_old_version(&api_that, api_contact_id, api_version, api_promotion).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_contact_version_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: "user_discovery_store_flutter_set_contact_version", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_contact_id = <i64>::sse_decode(&mut deserializer);
let api_update = <Vec<u8>>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::set_contact_version(&api_that, api_contact_id, api_update).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_shares_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: "user_discovery_store_flutter_set_shares", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_shares = <Vec<Vec<u8>>>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::set_shares(&api_that, api_shares).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_store_other_promotion_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: "user_discovery_store_flutter_store_other_promotion", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_promotion = <crate::bridge::OtherPromotion>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::store_other_promotion(&api_that, api_promotion).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_update_config_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: "user_discovery_store_flutter_update_config", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter>::sse_decode(&mut deserializer);
let api_update = <String>::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::callbacks::user_discovery::UserDiscoveryStoreFlutter::update_config(&api_that, api_update).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_sign_data_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: "user_discovery_utils_flutter_sign_data", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter>::sse_decode(&mut deserializer);
let api_input_data = <Vec<u8>>::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::callbacks::user_discovery::UserDiscoveryUtilsFlutter::sign_data(&api_that, &api_input_data).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_signature_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: "user_discovery_utils_flutter_verify_signature", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter>::sse_decode(&mut deserializer);
let api_input_data = <Vec<u8>>::sse_decode(&mut deserializer);
let api_pubkey = <Vec<u8>>::sse_decode(&mut deserializer);
let api_signature = <Vec<u8>>::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::callbacks::user_discovery::UserDiscoveryUtilsFlutter::verify_signature(&api_that, &api_input_data, &api_pubkey, &api_signature).await?; Ok(output_ok)
})().await)
} })
}
fn wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_stored_pubkey_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: "user_discovery_utils_flutter_verify_stored_pubkey", 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_that = <crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter>::sse_decode(&mut deserializer);
let api_from_contact_id = <i64>::sse_decode(&mut deserializer);
let api_pubkey = <Vec<u8>>::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::callbacks::user_discovery::UserDiscoveryUtilsFlutter::verify_stored_pubkey(&api_that, api_from_contact_id, &api_pubkey).await?; Ok(output_ok)
})().await)
} })
}
// Section: static_checks
@ -1356,6 +1738,13 @@ impl SseDecode for crate::bridge::wrapper::key_manager::RustKeyManager {
}
}
impl SseDecode for crate::bridge::wrapper::RustUtils {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
return crate::bridge::wrapper::RustUtils {};
}
}
impl SseDecode for u32 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -1383,6 +1772,20 @@ impl SseDecode for () {
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {}
}
impl SseDecode for crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
return crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter {};
}
}
impl SseDecode for crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
return crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter {};
}
}
impl SseDecode for usize {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -1431,8 +1834,27 @@ fn pde_ffi_dispatcher_primary_impl(
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_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),
26 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_serialize_impl(port, ptr, rust_vec_len, data_len),
27 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_set_user_id_impl(port, ptr, rust_vec_len, data_len),
28 => wire__crate__bridge__wrapper__key_manager__rust_key_manager_store_signed_prekey_impl(port, ptr, rust_vec_len, data_len),
29 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
30 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
31 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_announced_user_by_public_id_impl(port, ptr, rust_vec_len, data_len),
32 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_config_impl(port, ptr, rust_vec_len, data_len),
33 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_promotion_impl(port, ptr, rust_vec_len, data_len),
34 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_version_impl(port, ptr, rust_vec_len, data_len),
35 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_other_promotions_by_public_id_impl(port, ptr, rust_vec_len, data_len),
36 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_own_promotions_after_version_impl(port, ptr, rust_vec_len, data_len),
37 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_share_for_contact_impl(port, ptr, rust_vec_len, data_len),
38 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_new_user_relation_impl(port, ptr, rust_vec_len, data_len),
39 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_own_promotion_and_clear_old_version_impl(port, ptr, rust_vec_len, data_len),
40 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_contact_version_impl(port, ptr, rust_vec_len, data_len),
41 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_shares_impl(port, ptr, rust_vec_len, data_len),
42 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_store_other_promotion_impl(port, ptr, rust_vec_len, data_len),
43 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_update_config_impl(port, ptr, rust_vec_len, data_len),
44 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_sign_data_impl(port, ptr, rust_vec_len, data_len),
45 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_signature_impl(port, ptr, rust_vec_len, data_len),
46 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_stored_pubkey_impl(port, ptr, rust_vec_len, data_len),
_ => unreachable!(),
}
}
@ -1608,6 +2030,65 @@ impl flutter_rust_bridge::IntoIntoDart<crate::bridge::wrapper::key_manager::Rust
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::bridge::wrapper::RustUtils {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
Vec::<u8>::new().into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::bridge::wrapper::RustUtils
{
}
impl flutter_rust_bridge::IntoIntoDart<crate::bridge::wrapper::RustUtils>
for crate::bridge::wrapper::RustUtils
{
fn into_into_dart(self) -> crate::bridge::wrapper::RustUtils {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart
for crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter
{
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
Vec::<u8>::new().into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter
{
}
impl
flutter_rust_bridge::IntoIntoDart<
crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter,
> for crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter
{
fn into_into_dart(self) -> crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart
for crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter
{
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
Vec::<u8>::new().into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter
{
}
impl
flutter_rust_bridge::IntoIntoDart<
crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter,
> for crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter
{
fn into_into_dart(self) -> crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter {
self
}
}
impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error {
// Codec=Sse (Serialization based), see doc to use other codecs
@ -1849,6 +2330,11 @@ impl SseEncode for crate::bridge::wrapper::key_manager::RustKeyManager {
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
}
impl SseEncode for crate::bridge::wrapper::RustUtils {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
}
impl SseEncode for u32 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -1881,6 +2367,16 @@ impl SseEncode for () {
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
}
impl SseEncode for crate::bridge::callbacks::user_discovery::UserDiscoveryStoreFlutter {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
}
impl SseEncode for crate::bridge::callbacks::user_discovery::UserDiscoveryUtilsFlutter {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
}
impl SseEncode for usize {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -1906,6 +2402,8 @@ mod io {
// Section: imports
use super::*;
use crate::user_discovery::traits::UserDiscoveryStore;
use crate::user_discovery::traits::UserDiscoveryUtils;
use flutter_rust_bridge::for_generated::byteorder::{
NativeEndian, ReadBytesExt, WriteBytesExt,
};
@ -1928,6 +2426,8 @@ mod web {
// Section: imports
use super::*;
use crate::user_discovery::traits::UserDiscoveryStore;
use crate::user_discovery::traits::UserDiscoveryUtils;
use flutter_rust_bridge::for_generated::byteorder::{
NativeEndian, ReadBytesExt, WriteBytesExt,
};

View file

@ -6,7 +6,6 @@ mod error;
mod frb_generated;
mod keys;
mod log;
mod passwordless_recovery;
mod secure_storage;
mod standalone;
mod user_discovery;

View file

@ -1,12 +0,0 @@
include!(concat!(env!("OUT_DIR"), "/passwordless_recovery.rs"));
struct PasswordLessRecovery {}
impl PasswordLessRecovery {
pub(crate) fn create_shared_secret_data(
total_shares: i64,
threshold: i64,
pin_unlock_token: Option<Vec<u8>>,
) {
}
}

View file

@ -16,13 +16,11 @@ protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "messages.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "groups.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "qr.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "data.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "passwordless_recovery.proto"
mkdir "$GENERATED_DIR/user_discovery/" &>/dev/null
protoc --proto_path="./rust/src/user_discovery/" --dart_out="$GENERATED_DIR/user_discovery/" "types.proto"
mkdir "$GENERATED_DIR/passwordless_recovery/" &>/dev/null
protoc --proto_path="./rust/src/passwordless_recovery/" --dart_out="$GENERATED_DIR/passwordless_recovery/" "types.proto"
protoc --proto_path="$CLIENT_DIR" --dart_out="$GENERATED_DIR" "push_notification.proto"
protoc --proto_path="$CLIENT_DIR" --swift_out="./ios/NotificationService/" "push_notification.proto"

View file

@ -24,6 +24,7 @@ import 'schema_v17.dart' as v17;
import 'schema_v18.dart' as v18;
import 'schema_v19.dart' as v19;
import 'schema_v20.dart' as v20;
import 'schema_v21.dart' as v21;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -69,6 +70,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v19.DatabaseAtV19(db);
case 20:
return v20.DatabaseAtV20(db);
case 21:
return v21.DatabaseAtV21(db);
default:
throw MissingSchemaException(version, versions);
}
@ -95,5 +98,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
18,
19,
20,
21,
];
}

File diff suppressed because it is too large Load diff

View file

@ -57,20 +57,6 @@ void main() {
shareAction: 70,
likeAction: 90,
),
LinkParserTest(
title: 'Kuketz-Blog 🛡 (@kuketzblog@social.tchncs.de)',
url: 'https://social.tchncs.de/@kuketzblog/115898752560771936',
siteName: 'Mastodon',
desc:
'AWS verspricht jetzt »Souveränität« mit einem »europäischen« Cloud-Angebot Standort Deutschland, großes Vertrauens-Theater.\n'
'\n'
'Nur: Souveränität ist keine Postleitzahl. Wenn der Anbieter Amazon heißt, bleibt es dasselbe Märchen mit neuem Umschlag: Der Cloud Act, FISA etc. gilt trotzdem. US-Recht schlägt Geografie. Das Gerede von »Souveränität« ist kein Konzept, sondern Marketing.\n'
'\n'
'https://www.heise.de/news/AWS-verspricht-Souveraenitaet-mit-europaeischem-Cloudangebot-11141800.html',
vendor: Vendor.mastodonSocialMediaPosting,
shareAction: 15,
likeAction: 190,
),
LinkParserTest(
title:
'David Kriesel: Traue keinem Scan, den du nicht selbst gefälscht hast',

View file

@ -0,0 +1,610 @@
// ignore_for_file: strict_raw_type
import 'dart:convert' show utf8;
import 'dart:io';
import 'package:clock/clock.dart';
import 'package:crypto/crypto.dart';
import 'package:drift/drift.dart' hide isNotNull, isNull;
import 'package:drift/native.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/core/bridge.dart' as bridge;
import 'package:twonly/core/frb_generated.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/callbacks/callbacks.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/userdata.model.dart';
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
as api_pb;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb;
import 'package:twonly/src/services/api.service.dart';
import 'package:twonly/src/services/api/utils.api.dart';
import 'package:twonly/src/services/passwordless_recovery.service.dart';
import 'package:twonly/src/services/signal/identity.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/log.dart';
import '../mocks/platform_channels.dart';
class MockApiService extends ApiService {
final List<(List<int>, List<int>?)> registeredPasswordLessRecoveries = [];
final List<(int, Uint8List, List<int>?)> sentTextMessages = [];
@override
Future<Result> registerPasswordLessRecovery(
List<int> encryptedServerKey,
List<int>? pinUnlockToken,
) async {
registeredPasswordLessRecoveries.add((encryptedServerKey, pinUnlockToken));
return Result.success(api_pb.Response_Ok());
}
@override
Future<Result> sendTextMessage(
int target,
Uint8List msg,
List<int>? pushData,
) async {
sentTextMessages.add((target, msg, pushData));
return Result.success(api_pb.Response_Ok());
}
@override
Future<Result> updateSignedPreKey(
int id,
List<int> key,
List<int> signature,
) async {
return Result.success(api_pb.Response_Ok());
}
}
void main() {
if (!Platform.isMacOS) {
return;
}
TestWidgetsFlutterBinding.ensureInitialized();
late Directory tempDir;
setUpAll(() async {
Log.init();
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
final dylibPath =
'${Directory.current.path}/rust/target/debug/librust_lib_twonly.dylib';
if (File(dylibPath).existsSync()) {
await RustLib.init(
externalLibrary: ExternalLibrary.open(dylibPath),
);
} else {
await RustLib.init();
}
await initFlutterCallbacksForRust();
tempDir = Directory.systemTemp.createTempSync('twonly_passwordless_test_');
AppEnvironment.initTesting(
customCacheDir: tempDir.path,
customSupportDir: tempDir.path,
);
await bridge.initializeTwonlyFlutter(
config: bridge.InitConfig(
databaseDir: tempDir.path,
dataDir: tempDir.path,
),
);
});
setUp(() async {
setupPlatformChannelMocks();
await locator.reset();
final dbFile = File('${tempDir.path}/twonly.sqlite');
if (dbFile.existsSync()) {
dbFile.deleteSync();
}
locator
..registerSingleton<TwonlyDB>(
TwonlyDB(NativeDatabase(dbFile)),
)
..registerSingleton<UserService>(UserService())
..registerSingleton<ApiService>(MockApiService());
userService.currentUser = UserData(
userId: 1,
username: 'me',
displayName: 'Me',
subscriptionPlan: 'Free',
currentSetupPage: null,
appVersion: 100,
);
userService.isUserCreated = true;
await UserService.save(userService.currentUser);
await createIfNotExistsSignalIdentity();
});
tearDown(() async {
try {
await twonlyDB.close();
} catch (_) {}
});
tearDownAll(() async {
if (tempDir.existsSync()) {
try {
tempDir.deleteSync(recursive: true);
} catch (_) {}
}
});
Future<void> setupSignalSession(int contactId) async {
final identityKeyPair = generateIdentityKeyPair();
final registrationId = generateRegistrationId(true);
final signedPreKey = generateSignedPreKey(identityKeyPair, 1);
final preKey = generatePreKeys(1, 1).first;
final responseUserData = api_pb.Response_UserData()
..userId = Int64(contactId)
..username = utf8.encode('user_$contactId')
..registrationId = Int64(registrationId)
..publicIdentityKey = identityKeyPair.getPublicKey().serialize()
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
..signedPrekeyId = Int64(signedPreKey.id)
..signedPrekeySignature = signedPreKey.signature;
responseUserData.prekeys.add(
api_pb.Response_PreKey()
..id = Int64(preKey.id)
..prekey = preKey.getKeyPair().publicKey.serialize(),
);
final success = await processSignalUserData(responseUserData);
expect(success, isTrue);
}
group('PasswordlessRecoveryService - enablePasswordlessRecovery', () {
test('works with SecondFactorType.none', () async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
);
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(userId: const Value(3), username: 'friend_3'),
);
final success =
await PasswordlessRecoveryService.enablePasswordlessRecovery(
trustedFriendIds: [2, 3],
secondFactorType: SecondFactorType.none,
secondFactorValue: '',
threshold: 2,
);
expect(success, isTrue);
final c2 = await twonlyDB.contactsDao.getContactById(2);
final c3 = await twonlyDB.contactsDao.getContactById(3);
expect(c2?.recoveryIsTrustedFriend, isTrue);
expect(c3?.recoveryIsTrustedFriend, isTrue);
expect(c2?.recoverySecretShare, isNotNull);
expect(c3?.recoverySecretShare, isNotNull);
expect(c2?.recoveryLastHeartbeat, isNull);
expect(c3?.recoveryLastHeartbeat, isNull);
});
test('works with SecondFactorType.email', () async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
);
final success =
await PasswordlessRecoveryService.enablePasswordlessRecovery(
trustedFriendIds: [2],
secondFactorType: SecondFactorType.email,
secondFactorValue: 'a_very_long_email_addr@gmail.com',
threshold: 1,
);
expect(success, isTrue);
final c2 = await twonlyDB.contactsDao.getContactById(2);
expect(c2?.recoveryIsTrustedFriend, isTrue);
expect(c2?.recoverySecretShare, isNotNull);
});
test('works with SecondFactorType.pin', () async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(userId: const Value(2), username: 'friend_2'),
);
final success =
await PasswordlessRecoveryService.enablePasswordlessRecovery(
trustedFriendIds: [2],
secondFactorType: SecondFactorType.pin,
secondFactorValue: '123456',
threshold: 1,
);
expect(success, isTrue);
final c2 = await twonlyDB.contactsDao.getContactById(2);
expect(c2?.recoveryIsTrustedFriend, isTrue);
expect(c2?.recoverySecretShare, isNotNull);
});
test('sends delete messages to old trusted friends', () async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(2),
username: 'friend_2',
recoveryIsTrustedFriend: const Value(true),
),
);
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(userId: const Value(3), username: 'friend_3'),
);
await setupSignalSession(2);
await setupSignalSession(3);
final mockApi = apiService as MockApiService;
mockApi.sentTextMessages.clear();
final success =
await PasswordlessRecoveryService.enablePasswordlessRecovery(
trustedFriendIds: [3],
secondFactorType: SecondFactorType.none,
secondFactorValue: '',
threshold: 1,
);
expect(success, isTrue);
// Verify old friend got a delete message
expect(mockApi.sentTextMessages.isNotEmpty, isTrue);
final deleteMsg = mockApi.sentTextMessages.firstWhere((t) => t.$1 == 2);
expect(deleteMsg, isNotNull);
final oldFriend = await twonlyDB.contactsDao.getContactById(2);
expect(oldFriend?.recoveryIsTrustedFriend, isFalse);
});
});
group('PasswordlessRecoveryService - performHeartbeat', () {
test(
'triggers server registration based on lastServerHeartbeat time',
() async {
final config = PasswordLessRecovery()
..encryptedServerKey = Uint8List.fromList([1, 2, 3])
..pinUnlockToken = Uint8List.fromList([4, 5, 6]);
await UserService.update((u) => u.passwordLessRecovery = config);
final mockApi = apiService as MockApiService;
mockApi.registeredPasswordLessRecoveries.clear();
// Case 1: lastServerHeartbeat is null -> should trigger registration
await PasswordlessRecoveryService.performHeartbeat();
expect(mockApi.registeredPasswordLessRecoveries.length, 1);
expect(
userService.currentUser.passwordLessRecovery?.lastServerHeartbeat,
isNotNull,
);
// Case 2: lastServerHeartbeat is < 30 days old -> should not trigger
mockApi.registeredPasswordLessRecoveries.clear();
final now = clock.now();
await UserService.update(
(u) => u.passwordLessRecovery?.lastServerHeartbeat = now.subtract(
const Duration(days: 15),
),
);
await PasswordlessRecoveryService.performHeartbeat();
expect(mockApi.registeredPasswordLessRecoveries.length, 0);
// Case 3: lastServerHeartbeat is > 30 days old -> should trigger
await withClock(Clock.fixed(now), () async {
await UserService.update(
(u) => u.passwordLessRecovery?.lastServerHeartbeat = now.subtract(
const Duration(days: 31),
),
);
await PasswordlessRecoveryService.performHeartbeat();
expect(mockApi.registeredPasswordLessRecoveries.length, 1);
expect(
userService.currentUser.passwordLessRecovery?.lastServerHeartbeat,
now,
);
});
},
);
test(
'sends secret shares to trusted friends with null recoveryLastHeartbeat',
() async {
final config = PasswordLessRecovery()
..encryptedServerKey = Uint8List.fromList([1, 2, 3])
..pinUnlockToken = Uint8List.fromList([4, 5, 6]);
await UserService.update((u) => u.passwordLessRecovery = config);
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(2),
username: 'friend_2',
recoveryIsTrustedFriend: const Value(true),
recoverySecretShare: Value(Uint8List.fromList([1, 2, 3])),
recoveryLastHeartbeat: const Value(null),
),
);
await setupSignalSession(2);
final mockApi = apiService as MockApiService;
mockApi.sentTextMessages.clear();
final baseTime = DateTime(2026, 6, 20, 12, 0);
await withClock(Clock.fixed(baseTime), () async {
await PasswordlessRecoveryService.performHeartbeat();
});
// Verify message sent
expect(mockApi.sentTextMessages.length, 1);
expect(mockApi.sentTextMessages.first.$1, 2);
// Verify db update
final c2 = await twonlyDB.contactsDao.getContactById(2);
expect(c2?.recoveryLastHeartbeat, baseTime);
// Verify calling again does not resend
mockApi.sentTextMessages.clear();
await PasswordlessRecoveryService.performHeartbeat();
expect(mockApi.sentTextMessages.length, 0);
},
);
test(
'sends heartbeat to friends who chose us as a trusted friend based on time',
() async {
final config = PasswordLessRecovery()
..encryptedServerKey = Uint8List.fromList([1, 2, 3])
..pinUnlockToken = Uint8List.fromList([4, 5, 6]);
await UserService.update((u) => u.passwordLessRecovery = config);
final shareBytes = Uint8List.fromList([10, 20, 30]);
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(3),
username: 'friend_3',
recoveryContactsSecretShare: Value(shareBytes),
recoveryContactsLastHeartbeat: const Value(null),
),
);
await setupSignalSession(3);
final mockApi = apiService as MockApiService;
mockApi.sentTextMessages.clear();
final baseTime = DateTime(2026, 6, 20, 12, 0);
// Case 1: recoveryContactsLastHeartbeat is null -> should send
await withClock(Clock.fixed(baseTime), () async {
await PasswordlessRecoveryService.performHeartbeat();
});
expect(mockApi.sentTextMessages.length, 1);
expect(mockApi.sentTextMessages.first.$1, 3);
final c3 = await twonlyDB.contactsDao.getContactById(3);
expect(c3?.recoveryContactsLastHeartbeat, baseTime);
// Case 2: last heartbeat is < 7 days -> should not send
mockApi.sentTextMessages.clear();
await withClock(
Clock.fixed(baseTime.add(const Duration(days: 5))),
() async {
await PasswordlessRecoveryService.performHeartbeat();
},
);
expect(mockApi.sentTextMessages.length, 0);
// Case 3: last heartbeat is > 7 days -> should send
mockApi.sentTextMessages.clear();
final targetTime = baseTime.add(const Duration(days: 8));
await withClock(Clock.fixed(targetTime), () async {
await PasswordlessRecoveryService.performHeartbeat();
});
expect(mockApi.sentTextMessages.length, 1);
final c3Updated = await twonlyDB.contactsDao.getContactById(3);
expect(c3Updated?.recoveryContactsLastHeartbeat, targetTime);
},
);
test(
'sends heartbeat to friends who chose us as a trusted friend even if our config is null',
() async {
await UserService.update((u) => u.passwordLessRecovery = null);
final shareBytes = Uint8List.fromList([10, 20, 30]);
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(4),
username: 'friend_4',
recoveryContactsSecretShare: Value(shareBytes),
recoveryContactsLastHeartbeat: const Value(null),
),
);
await setupSignalSession(4);
final mockApi = apiService as MockApiService;
mockApi.sentTextMessages.clear();
final baseTime = DateTime(2026, 6, 20, 12, 0);
await withClock(Clock.fixed(baseTime), () async {
await PasswordlessRecoveryService.performHeartbeat();
});
expect(mockApi.sentTextMessages.length, 1);
expect(mockApi.sentTextMessages.first.$1, 4);
},
);
});
group('PasswordlessRecoveryService - handlePasswordlessRecovery', () {
test(
'handlePasswordlessRecovery deletes share when delete is true',
() async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(4),
username: 'friend_4',
recoveryContactsSecretShare: Value(Uint8List.fromList([7, 8, 9])),
recoveryContactsLastHeartbeat: Value(DateTime.now()),
),
);
final msg = pb.EncryptedContent_PasswordLessRecovery()..delete = true;
await PasswordlessRecoveryService.handlePasswordlessRecovery(
4,
msg,
'receipt_id_1',
);
final c4 = await twonlyDB.contactsDao.getContactById(4);
expect(c4?.recoveryContactsSecretShare, isNull);
expect(c4?.recoveryContactsLastHeartbeat, isNull);
},
);
test(
'handlePasswordlessRecovery saves share when delete is false',
() async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(4),
username: 'friend_4',
),
);
final msg = pb.EncryptedContent_PasswordLessRecovery()
..delete = false
..recoverySecretShare = [11, 22, 33];
await PasswordlessRecoveryService.handlePasswordlessRecovery(
4,
msg,
'receipt_id_2',
);
final c4 = await twonlyDB.contactsDao.getContactById(4);
expect(
c4?.recoveryContactsSecretShare,
Uint8List.fromList([11, 22, 33]),
);
expect(c4?.recoveryContactsLastHeartbeat, isNull);
},
);
});
group('PasswordlessRecoveryService - handlePasswordlessRecoveryHeartbeat', () {
test('sends delete message back if stored share is null', () async {
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(userId: const Value(5), username: 'friend_5'),
);
await setupSignalSession(5);
final mockApi = apiService as MockApiService;
mockApi.sentTextMessages.clear();
final msg = pb.EncryptedContent_PasswordLessRecoveryHeartbeat()
..hash = [1, 2, 3];
await PasswordlessRecoveryService.handlePasswordlessRecoveryHeartbeat(
5,
msg,
'receipt_id_3',
);
// Await unawaited sendCipherText call
await Future.delayed(const Duration(milliseconds: 100));
// We expect a delete message sent back
expect(mockApi.sentTextMessages.isNotEmpty, isTrue);
expect(mockApi.sentTextMessages.first.$1, 5);
});
test(
'updates recoveryLastHeartbeat to clock.now() when hash matches',
() async {
final shareBytes = Uint8List.fromList([1, 2, 3]);
final hashBytes = sha256.convert(shareBytes).bytes;
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(5),
username: 'friend_5',
recoverySecretShare: Value(shareBytes),
),
);
final msg = pb.EncryptedContent_PasswordLessRecoveryHeartbeat()
..hash = hashBytes;
final now = DateTime(2026, 6, 20, 12, 0);
await withClock(Clock.fixed(now), () async {
await PasswordlessRecoveryService.handlePasswordlessRecoveryHeartbeat(
5,
msg,
'receipt_id_4',
);
});
final c5 = await twonlyDB.contactsDao.getContactById(5);
expect(c5?.recoveryLastHeartbeat, now);
},
);
test(
'sets recoveryLastHeartbeat to null when hash does not match',
() async {
final shareBytes = Uint8List.fromList([1, 2, 3]);
final wrongHashBytes = sha256.convert([4, 5, 6]).bytes;
await twonlyDB.contactsDao.insertContact(
ContactsCompanion.insert(
userId: const Value(5),
username: 'friend_5',
recoverySecretShare: Value(shareBytes),
recoveryLastHeartbeat: Value(DateTime.now()),
),
);
final msg = pb.EncryptedContent_PasswordLessRecoveryHeartbeat()
..hash = wrongHashBytes;
await PasswordlessRecoveryService.handlePasswordlessRecoveryHeartbeat(
5,
msg,
'receipt_id_5',
);
final c5 = await twonlyDB.contactsDao.getContactById(5);
expect(c5?.recoveryLastHeartbeat, isNull);
},
);
});
}