From 45b265c15536925b5cb297968dfd1c58eb4384fb Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 8 Jan 2025 18:59:02 +0530 Subject: [PATCH 01/19] Add user's public and private key --- app/ios/Podfile.lock | 6 + .../detail/user_journey_detail_screen.dart | 2 +- .../timeline/journey_timeline_screen.dart | 10 +- .../timeline/journey_timeline_view_model.dart | 8 +- app/pubspec.lock | 84 +- data/lib/api/auth/api_user_service.dart | 48 +- data/lib/api/auth/auth_models.dart | 6 + data/lib/api/auth/auth_models.freezed.dart | 146 +++- data/lib/api/auth/auth_models.g.dart | 19 + data/lib/api/location/journey/journey.dart | 63 +- .../api/location/journey/journey.freezed.dart | 741 ++++++++++++++++-- data/lib/api/location/journey/journey.g.dart | 74 +- data/lib/api/location/location.dart | 38 +- data/lib/api/location/location.freezed.dart | 322 +++++++- data/lib/api/location/location.g.dart | 26 +- .../api/message/message_models.freezed.dart | 65 +- data/lib/api/place/api_place.freezed.dart | 63 +- data/lib/api/space/api_group_key_model.dart | 73 ++ .../space/api_group_key_model.freezed.dart | 641 +++++++++++++++ data/lib/api/space/api_group_key_model.g.dart | 63 ++ data/lib/api/space/api_sender_key_record.dart | 24 + .../space/api_sender_key_record.freezed.dart | 281 +++++++ .../api/space/api_sender_key_record.g.dart | 30 + data/lib/api/space/space_models.freezed.dart | 86 +- .../subscription_models.freezed.dart | 17 +- data/lib/converter/blob_converter.dart | 26 + .../lib/domain/journey_lat_lng_entension.dart | 9 - data/lib/domain/location_data_extension.dart | 17 - data/lib/repository/journey_generator.dart | 30 +- data/lib/service/auth_service.dart | 63 +- data/lib/storage/app_preferences.dart | 7 +- data/lib/utils/private_key_helper.dart | 59 ++ data/pubspec.yaml | 2 + 33 files changed, 2892 insertions(+), 257 deletions(-) create mode 100644 data/lib/api/space/api_group_key_model.dart create mode 100644 data/lib/api/space/api_group_key_model.freezed.dart create mode 100644 data/lib/api/space/api_group_key_model.g.dart create mode 100644 data/lib/api/space/api_sender_key_record.dart create mode 100644 data/lib/api/space/api_sender_key_record.freezed.dart create mode 100644 data/lib/api/space/api_sender_key_record.g.dart create mode 100644 data/lib/converter/blob_converter.dart delete mode 100644 data/lib/domain/journey_lat_lng_entension.dart delete mode 100644 data/lib/domain/location_data_extension.dart create mode 100644 data/lib/utils/private_key_helper.dart diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index fd501f66..8e69c5e4 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -18,6 +18,8 @@ PODS: - connectivity_plus (0.0.1): - Flutter - FlutterMacOS + - cryptography_flutter (0.2.0): + - Flutter - device_info_plus (0.0.1): - Flutter - Firebase/Auth (11.4.0): @@ -261,6 +263,7 @@ DEPENDENCIES: - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) - cloud_functions (from `.symlinks/plugins/cloud_functions/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) + - cryptography_flutter (from `.symlinks/plugins/cryptography_flutter/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) @@ -337,6 +340,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cloud_functions/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/darwin" + cryptography_flutter: + :path: ".symlinks/plugins/cryptography_flutter/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" firebase_auth: @@ -402,6 +407,7 @@ SPEC CHECKSUMS: cloud_firestore: 8794ea2885e367f0a2096338964ddfd49739b2c7 cloud_functions: d1dc9179c2db1729cb9bd0c9683909216a0f2230 connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563 + cryptography_flutter: 381bdacc984abcfbe3ca45ef7c76566ff061614c device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99 firebase_auth: 42718683069d35d73af7a986b55b194589039e5e diff --git a/app/lib/ui/flow/journey/detail/user_journey_detail_screen.dart b/app/lib/ui/flow/journey/detail/user_journey_detail_screen.dart index 94873a20..7e487d8b 100644 --- a/app/lib/ui/flow/journey/detail/user_journey_detail_screen.dart +++ b/app/lib/ui/flow/journey/detail/user_journey_detail_screen.dart @@ -113,7 +113,7 @@ class _UserJourneyDetailScreenState state.journey!.isSteady(), ), if (state.journey!.isMoving()) - _journeyPlacesInfo(state.addressTo, state.journey!.update_at, true), + _journeyPlacesInfo(state.addressTo, state.journey!.updated_at, true), ], ), ); diff --git a/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart b/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart index 058d53ae..1460349f 100644 --- a/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart +++ b/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart @@ -223,9 +223,9 @@ class _JourneyTimelineScreenState extends ConsumerState { ) { final location = LatLng(journey.from_latitude, journey.from_longitude); final steadyDuration = - notifier.getSteadyDuration(journey.created_at!, journey.update_at!); - final formattedTime = _getFormattedJourneyTime( - journey.created_at ?? 0, journey.update_at ?? 0); + notifier.getSteadyDuration(journey.created_at, journey.updated_at); + final formattedTime = + _getFormattedJourneyTime(journey.created_at, journey.updated_at); return Padding( padding: EdgeInsets.only(top: isFirstItem ? 16 : 0), @@ -273,8 +273,8 @@ class _JourneyTimelineScreenState extends ConsumerState { bool isLastItem, String mapType, ) { - final time = _getFormattedJourneyTime( - journey.created_at ?? 0, journey.update_at ?? 0); + final time = + _getFormattedJourneyTime(journey.created_at, journey.updated_at); final distance = notifier.getDistanceString(journey.route_distance ?? 0); final fromLatLng = LatLng(journey.from_latitude, journey.from_longitude); final toLatLng = diff --git a/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart b/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart index 3a6cef69..79e6a46e 100644 --- a/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart +++ b/app/lib/ui/flow/journey/timeline/journey_timeline_view_model.dart @@ -79,8 +79,8 @@ class JourneyTimelineViewModel extends StateNotifier { // Filter by date range final filteredJourneys = journeys.where((journey) { - return journey.created_at! >= from && journey.created_at! <= to || - (journey.update_at! >= from && journey.update_at! <= to); + return journey.created_at >= from && journey.created_at <= to || + (journey.updated_at >= from && journey.updated_at <= to); }).toList(); // Combine all journeys and sort @@ -108,13 +108,13 @@ class JourneyTimelineViewModel extends StateNotifier { if (allJourneys.isEmpty) return null; int earliestTimestamp = allJourneys .map((journey) => journey.created_at) - .fold(allJourneys.first.created_at!, (a, b) => a < b! ? a : b); + .fold(allJourneys.first.created_at, (a, b) => a < b ? a : b); return earliestTimestamp; } List _sortJourneysByUpdateAt( List journeys) { - journeys.sort((a, b) => (b.update_at ?? 0).compareTo(a.update_at ?? 0)); + journeys.sort((a, b) => b.updated_at.compareTo(a.updated_at)); return journeys; } diff --git a/app/pubspec.lock b/app/pubspec.lock index 84ccc971..23e0a0e1 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.46" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" analyzer: dependency: transitive description: @@ -321,6 +329,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + cryptography: + dependency: transitive + description: + name: cryptography + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + cryptography_flutter: + dependency: transitive + description: + name: cryptography_flutter + sha256: a7fc3f0de42fb0947cbf213257aa3a69c89df561d104723ede8050658621f292 + url: "https://pub.dev" + source: hosted + version: "2.3.2" csslib: dependency: transitive description: @@ -408,6 +432,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" fake_async: dependency: transitive description: @@ -1153,10 +1185,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.6.7" json_annotation: dependency: "direct main" description: @@ -1197,6 +1229,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + libsignal_protocol_dart: + dependency: transitive + description: + name: libsignal_protocol_dart + sha256: "2a8d54cddb89eab0301d333c4346d7edc920da0a0c0daa5e324878de2841dce6" + url: "https://pub.dev" + source: hosted + version: "0.7.1" lints: dependency: transitive description: @@ -1269,6 +1309,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + optional: + dependency: transitive + description: + name: optional + sha256: f80327d7a3335a0be68418072668043c7ab291df575c21aa42e0c5633641da39 + url: "https://pub.dev" + source: hosted + version: "6.1.0+1" package_config: dependency: transitive description: @@ -1429,6 +1477,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" pool: dependency: transitive description: @@ -1437,6 +1493,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" + url: "https://pub.dev" + source: hosted + version: "2.1.0" pub_semver: dependency: transitive description: @@ -1881,6 +1945,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + very_good_analysis: + dependency: transitive + description: + name: very_good_analysis + sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" + url: "https://pub.dev" + source: hosted + version: "5.1.0" visibility_detector: dependency: "direct main" description: @@ -1945,6 +2017,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.5" + x25519: + dependency: transitive + description: + name: x25519 + sha256: cec3c125f0d934dccba6c4cab48f3fbf866dc78895dcc5a1584d35b0a845005b + url: "https://pub.dev" + source: hosted + version: "0.1.1" xdg_directories: dependency: transitive description: diff --git a/data/lib/api/auth/api_user_service.dart b/data/lib/api/auth/api_user_service.dart index c41f13c9..4349671f 100644 --- a/data/lib/api/auth/api_user_service.dart +++ b/data/lib/api/auth/api_user_service.dart @@ -12,15 +12,15 @@ import '../../storage/app_preferences.dart'; import 'auth_models.dart'; final apiUserServiceProvider = StateProvider((ref) => ApiUserService( - ref.read(firestoreProvider), - ref.read(deviceServiceProvider), - ref.read(currentUserJsonPod.notifier), - ref.read(currentSpaceId.notifier), - ref.read(currentUserSessionJsonPod.notifier), - ref.read(isOnboardingShownPod.notifier), - ref.read(currentUserPod), - ref.read(locationManagerProvider), - )); + ref.read(firestoreProvider), + ref.read(deviceServiceProvider), + ref.read(currentUserJsonPod.notifier), + ref.read(currentSpaceId.notifier), + ref.read(currentUserSessionJsonPod.notifier), + ref.read(isOnboardingShownPod.notifier), + ref.read(currentUserPod), + ref.read(locationManagerProvider), + ref.read(userPassKeyPod.notifier))); class ApiUserService { final FirebaseFirestore _db; @@ -29,19 +29,20 @@ class ApiUserService { final StateController currentUserSpaceId; final StateController userSessionJsonNotifier; final StateController onBoardNotifier; + final StateController userPassKeyNotifier; ApiUser? currentUser; final LocationManager locationManager; ApiUserService( - this._db, - this._device, - this.userJsonNotifier, - this.currentUserSpaceId, - this.userSessionJsonNotifier, - this.onBoardNotifier, - this.currentUser, - this.locationManager, - ); + this._db, + this._device, + this.userJsonNotifier, + this.currentUserSpaceId, + this.userSessionJsonNotifier, + this.onBoardNotifier, + this.currentUser, + this.locationManager, + this.userPassKeyNotifier); CollectionReference get _userRef => _db.collection("users").withConverter( @@ -284,5 +285,16 @@ class ApiUserService { userSessionJsonNotifier.state = null; onBoardNotifier.state = false; currentUserSpaceId.state = null; + userPassKeyNotifier.state = null; + } + + Future updateKeys( + String id, Blob publicKey, Blob privateKey, Blob saltBlob) async { + + await _userRef.doc(id).update({ + "identity_key_public": publicKey, + "identity_key_private": privateKey, + "identity_key_salt": saltBlob, + }); } } diff --git a/data/lib/api/auth/auth_models.dart b/data/lib/api/auth/auth_models.dart index bf6ed4e2..5195ef7d 100644 --- a/data/lib/api/auth/auth_models.dart +++ b/data/lib/api/auth/auth_models.dart @@ -6,7 +6,10 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../converter/blob_converter.dart'; + part 'auth_models.freezed.dart'; + part 'auth_models.g.dart'; const LOGIN_TYPE_GOOGLE = 1; @@ -31,6 +34,9 @@ class ApiUser with _$ApiUser { @Default([]) List? space_ids, int? battery_pct, @Default("") String? fcm_token, + @BlobConverter() Blob? identity_key_public, + @BlobConverter() Blob? identity_key_private, + @BlobConverter() Blob? identity_key_salt, int? state, int? created_at, int? updated_at, diff --git a/data/lib/api/auth/auth_models.freezed.dart b/data/lib/api/auth/auth_models.freezed.dart index 3680f418..3d3923a4 100644 --- a/data/lib/api/auth/auth_models.freezed.dart +++ b/data/lib/api/auth/auth_models.freezed.dart @@ -31,12 +31,22 @@ mixin _$ApiUser { List? get space_ids => throw _privateConstructorUsedError; int? get battery_pct => throw _privateConstructorUsedError; String? get fcm_token => throw _privateConstructorUsedError; + @BlobConverter() + Blob? get identity_key_public => throw _privateConstructorUsedError; + @BlobConverter() + Blob? get identity_key_private => throw _privateConstructorUsedError; + @BlobConverter() + Blob? get identity_key_salt => throw _privateConstructorUsedError; int? get state => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; int? get updated_at => throw _privateConstructorUsedError; + /// Serializes this ApiUser to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiUser + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiUserCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -57,6 +67,9 @@ abstract class $ApiUserCopyWith<$Res> { List? space_ids, int? battery_pct, String? fcm_token, + @BlobConverter() Blob? identity_key_public, + @BlobConverter() Blob? identity_key_private, + @BlobConverter() Blob? identity_key_salt, int? state, int? created_at, int? updated_at}); @@ -72,6 +85,8 @@ class _$ApiUserCopyWithImpl<$Res, $Val extends ApiUser> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiUser + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -86,6 +101,9 @@ class _$ApiUserCopyWithImpl<$Res, $Val extends ApiUser> Object? space_ids = freezed, Object? battery_pct = freezed, Object? fcm_token = freezed, + Object? identity_key_public = freezed, + Object? identity_key_private = freezed, + Object? identity_key_salt = freezed, Object? state = freezed, Object? created_at = freezed, Object? updated_at = freezed, @@ -135,6 +153,18 @@ class _$ApiUserCopyWithImpl<$Res, $Val extends ApiUser> ? _value.fcm_token : fcm_token // ignore: cast_nullable_to_non_nullable as String?, + identity_key_public: freezed == identity_key_public + ? _value.identity_key_public + : identity_key_public // ignore: cast_nullable_to_non_nullable + as Blob?, + identity_key_private: freezed == identity_key_private + ? _value.identity_key_private + : identity_key_private // ignore: cast_nullable_to_non_nullable + as Blob?, + identity_key_salt: freezed == identity_key_salt + ? _value.identity_key_salt + : identity_key_salt // ignore: cast_nullable_to_non_nullable + as Blob?, state: freezed == state ? _value.state : state // ignore: cast_nullable_to_non_nullable @@ -170,6 +200,9 @@ abstract class _$$ApiUserImplCopyWith<$Res> implements $ApiUserCopyWith<$Res> { List? space_ids, int? battery_pct, String? fcm_token, + @BlobConverter() Blob? identity_key_public, + @BlobConverter() Blob? identity_key_private, + @BlobConverter() Blob? identity_key_salt, int? state, int? created_at, int? updated_at}); @@ -183,6 +216,8 @@ class __$$ApiUserImplCopyWithImpl<$Res> _$ApiUserImpl _value, $Res Function(_$ApiUserImpl) _then) : super(_value, _then); + /// Create a copy of ApiUser + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -197,6 +232,9 @@ class __$$ApiUserImplCopyWithImpl<$Res> Object? space_ids = freezed, Object? battery_pct = freezed, Object? fcm_token = freezed, + Object? identity_key_public = freezed, + Object? identity_key_private = freezed, + Object? identity_key_salt = freezed, Object? state = freezed, Object? created_at = freezed, Object? updated_at = freezed, @@ -246,6 +284,18 @@ class __$$ApiUserImplCopyWithImpl<$Res> ? _value.fcm_token : fcm_token // ignore: cast_nullable_to_non_nullable as String?, + identity_key_public: freezed == identity_key_public + ? _value.identity_key_public + : identity_key_public // ignore: cast_nullable_to_non_nullable + as Blob?, + identity_key_private: freezed == identity_key_private + ? _value.identity_key_private + : identity_key_private // ignore: cast_nullable_to_non_nullable + as Blob?, + identity_key_salt: freezed == identity_key_salt + ? _value.identity_key_salt + : identity_key_salt // ignore: cast_nullable_to_non_nullable + as Blob?, state: freezed == state ? _value.state : state // ignore: cast_nullable_to_non_nullable @@ -277,6 +327,9 @@ class _$ApiUserImpl extends _ApiUser { final List? space_ids = const [], this.battery_pct, this.fcm_token = "", + @BlobConverter() this.identity_key_public, + @BlobConverter() this.identity_key_private, + @BlobConverter() this.identity_key_salt, this.state, this.created_at, this.updated_at}) @@ -320,6 +373,15 @@ class _$ApiUserImpl extends _ApiUser { @JsonKey() final String? fcm_token; @override + @BlobConverter() + final Blob? identity_key_public; + @override + @BlobConverter() + final Blob? identity_key_private; + @override + @BlobConverter() + final Blob? identity_key_salt; + @override final int? state; @override final int? created_at; @@ -328,7 +390,7 @@ class _$ApiUserImpl extends _ApiUser { @override String toString() { - return 'ApiUser(id: $id, first_name: $first_name, last_name: $last_name, email: $email, provider_firebase_id_token: $provider_firebase_id_token, auth_type: $auth_type, profile_image: $profile_image, location_enabled: $location_enabled, space_ids: $space_ids, battery_pct: $battery_pct, fcm_token: $fcm_token, state: $state, created_at: $created_at, updated_at: $updated_at)'; + return 'ApiUser(id: $id, first_name: $first_name, last_name: $last_name, email: $email, provider_firebase_id_token: $provider_firebase_id_token, auth_type: $auth_type, profile_image: $profile_image, location_enabled: $location_enabled, space_ids: $space_ids, battery_pct: $battery_pct, fcm_token: $fcm_token, identity_key_public: $identity_key_public, identity_key_private: $identity_key_private, identity_key_salt: $identity_key_salt, state: $state, created_at: $created_at, updated_at: $updated_at)'; } @override @@ -358,6 +420,12 @@ class _$ApiUserImpl extends _ApiUser { other.battery_pct == battery_pct) && (identical(other.fcm_token, fcm_token) || other.fcm_token == fcm_token) && + (identical(other.identity_key_public, identity_key_public) || + other.identity_key_public == identity_key_public) && + (identical(other.identity_key_private, identity_key_private) || + other.identity_key_private == identity_key_private) && + (identical(other.identity_key_salt, identity_key_salt) || + other.identity_key_salt == identity_key_salt) && (identical(other.state, state) || other.state == state) && (identical(other.created_at, created_at) || other.created_at == created_at) && @@ -365,7 +433,7 @@ class _$ApiUserImpl extends _ApiUser { other.updated_at == updated_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -380,11 +448,16 @@ class _$ApiUserImpl extends _ApiUser { const DeepCollectionEquality().hash(_space_ids), battery_pct, fcm_token, + identity_key_public, + identity_key_private, + identity_key_salt, state, created_at, updated_at); - @JsonKey(ignore: true) + /// Create a copy of ApiUser + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiUserImplCopyWith<_$ApiUserImpl> get copyWith => @@ -411,6 +484,9 @@ abstract class _ApiUser extends ApiUser { final List? space_ids, final int? battery_pct, final String? fcm_token, + @BlobConverter() final Blob? identity_key_public, + @BlobConverter() final Blob? identity_key_private, + @BlobConverter() final Blob? identity_key_salt, final int? state, final int? created_at, final int? updated_at}) = _$ApiUserImpl; @@ -441,13 +517,25 @@ abstract class _ApiUser extends ApiUser { @override String? get fcm_token; @override + @BlobConverter() + Blob? get identity_key_public; + @override + @BlobConverter() + Blob? get identity_key_private; + @override + @BlobConverter() + Blob? get identity_key_salt; + @override int? get state; @override int? get created_at; @override int? get updated_at; + + /// Create a copy of ApiUser + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiUserImplCopyWith<_$ApiUserImpl> get copyWith => throw _privateConstructorUsedError; } @@ -467,8 +555,12 @@ mixin _$ApiSession { int? get created_at => throw _privateConstructorUsedError; int? get app_version => throw _privateConstructorUsedError; + /// Serializes this ApiSession to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiSession + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiSessionCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -500,6 +592,8 @@ class _$ApiSessionCopyWithImpl<$Res, $Val extends ApiSession> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiSession + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -576,6 +670,8 @@ class __$$ApiSessionImplCopyWithImpl<$Res> _$ApiSessionImpl _value, $Res Function(_$ApiSessionImpl) _then) : super(_value, _then); + /// Create a copy of ApiSession + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -686,12 +782,14 @@ class _$ApiSessionImpl extends _ApiSession { other.app_version == app_version)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, user_id, platform, session_active, device_name, device_id, created_at, app_version); - @JsonKey(ignore: true) + /// Create a copy of ApiSession + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiSessionImplCopyWith<_$ApiSessionImpl> get copyWith => @@ -736,8 +834,11 @@ abstract class _ApiSession extends ApiSession { int? get created_at; @override int? get app_version; + + /// Create a copy of ApiSession + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiSessionImplCopyWith<_$ApiSessionImpl> get copyWith => throw _privateConstructorUsedError; } @@ -753,8 +854,12 @@ mixin _$ApiUserInfo { bool get isLocationEnabled => throw _privateConstructorUsedError; ApiSession? get session => throw _privateConstructorUsedError; + /// Serializes this ApiUserInfo to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiUserInfoCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -786,6 +891,8 @@ class _$ApiUserInfoCopyWithImpl<$Res, $Val extends ApiUserInfo> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -814,6 +921,8 @@ class _$ApiUserInfoCopyWithImpl<$Res, $Val extends ApiUserInfo> ) as $Val); } + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ApiUserCopyWith<$Res> get user { @@ -822,6 +931,8 @@ class _$ApiUserInfoCopyWithImpl<$Res, $Val extends ApiUserInfo> }); } + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ApiLocationCopyWith<$Res>? get location { @@ -834,6 +945,8 @@ class _$ApiUserInfoCopyWithImpl<$Res, $Val extends ApiUserInfo> }); } + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ApiSessionCopyWith<$Res>? get session { @@ -877,6 +990,8 @@ class __$$ApiUserInfoImplCopyWithImpl<$Res> _$ApiUserInfoImpl _value, $Res Function(_$ApiUserInfoImpl) _then) : super(_value, _then); + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -946,12 +1061,14 @@ class _$ApiUserInfoImpl extends _ApiUserInfo { (identical(other.session, session) || other.session == session)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, user, location, isLocationEnabled, session); - @JsonKey(ignore: true) + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiUserInfoImplCopyWith<_$ApiUserInfoImpl> get copyWith => @@ -984,8 +1101,11 @@ abstract class _ApiUserInfo extends ApiUserInfo { bool get isLocationEnabled; @override ApiSession? get session; + + /// Create a copy of ApiUserInfo + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiUserInfoImplCopyWith<_$ApiUserInfoImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/api/auth/auth_models.g.dart b/data/lib/api/auth/auth_models.g.dart index 435e6543..2076ff0b 100644 --- a/data/lib/api/auth/auth_models.g.dart +++ b/data/lib/api/auth/auth_models.g.dart @@ -22,6 +22,12 @@ _$ApiUserImpl _$$ApiUserImplFromJson(Map json) => const [], battery_pct: (json['battery_pct'] as num?)?.toInt(), fcm_token: json['fcm_token'] as String? ?? "", + identity_key_public: const BlobConverter() + .fromJson(json['identity_key_public'] as Map?), + identity_key_private: const BlobConverter() + .fromJson(json['identity_key_private'] as Map?), + identity_key_salt: const BlobConverter() + .fromJson(json['identity_key_salt'] as Map?), state: (json['state'] as num?)?.toInt(), created_at: (json['created_at'] as num?)?.toInt(), updated_at: (json['updated_at'] as num?)?.toInt(), @@ -40,11 +46,24 @@ Map _$$ApiUserImplToJson(_$ApiUserImpl instance) => 'space_ids': instance.space_ids, 'battery_pct': instance.battery_pct, 'fcm_token': instance.fcm_token, + 'identity_key_public': _$JsonConverterToJson?, Blob>( + instance.identity_key_public, const BlobConverter().toJson), + 'identity_key_private': + _$JsonConverterToJson?, Blob>( + instance.identity_key_private, const BlobConverter().toJson), + 'identity_key_salt': _$JsonConverterToJson?, Blob>( + instance.identity_key_salt, const BlobConverter().toJson), 'state': instance.state, 'created_at': instance.created_at, 'updated_at': instance.updated_at, }; +Json? _$JsonConverterToJson( + Value? value, + Json? Function(Value value) toJson, +) => + value == null ? null : toJson(value); + _$ApiSessionImpl _$$ApiSessionImplFromJson(Map json) => _$ApiSessionImpl( id: json['id'] as String, diff --git a/data/lib/api/location/journey/journey.dart b/data/lib/api/location/journey/journey.dart index fd6b0435..6472632a 100644 --- a/data/lib/api/location/journey/journey.dart +++ b/data/lib/api/location/journey/journey.dart @@ -2,10 +2,11 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; -import 'package:data/domain/journey_lat_lng_entension.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import '../../../converter/blob_converter.dart'; + part 'journey.freezed.dart'; part 'journey.g.dart'; @@ -27,9 +28,9 @@ class ApiLocationJourney with _$ApiLocationJourney { @Default([]) List routes, double? route_distance, int? route_duration, - int? created_at, - int? update_at, - String? type, + required int created_at, + required int updated_at, + required String type, }) = _LocationJourney; factory ApiLocationJourney.fromJson(Map json) => @@ -43,17 +44,11 @@ class ApiLocationJourney with _$ApiLocationJourney { } bool isSteady() { - if (type != null) { - return type == JOURNEY_TYPE_STEADY; - } - return to_latitude == null || to_longitude == null; + return type == JOURNEY_TYPE_STEADY; } bool isMoving() { - if (type != null) { - return type == JOURNEY_TYPE_MOVING; - } - return to_latitude != null && to_longitude != null; + return type == JOURNEY_TYPE_MOVING; } List toRoute() { @@ -61,7 +56,8 @@ class ApiLocationJourney with _$ApiLocationJourney { return []; } else if (isMoving()) { List result = [LatLng(from_latitude, from_longitude)]; - result.addAll(routes.map((route) => route.toLatLng())); + result.addAll( + routes.map((route) => LatLng(route.latitude, route.longitude))); if (to_latitude != null && to_longitude != null) { result.add(LatLng(to_latitude!, to_longitude!)); } @@ -95,3 +91,44 @@ class JourneyRoute with _$JourneyRoute { factory JourneyRoute.fromJson(Map json) => _$JourneyRouteFromJson(json); } + +@freezed +class EncryptedLocationJourney with _$EncryptedLocationJourney { + const EncryptedLocationJourney._(); + + const factory EncryptedLocationJourney({ + String? id, + required String user_id, + @BlobConverter() required Blob from_latitude, + @BlobConverter() required Blob from_longitude, + @BlobConverter() Blob? to_latitude, + @BlobConverter() Blob? to_longitude, + @Default([]) List routes, + double? route_distance, + int? route_duration, + required int created_at, + required int updated_at, + required String type, + }) = _EncryptedLocationJourney; + + factory EncryptedLocationJourney.fromJson(Map json) => + _$EncryptedLocationJourneyFromJson(json); + + factory EncryptedLocationJourney.fromFireStore( + DocumentSnapshot> snapshot, + SnapshotOptions? options) { + Map? data = snapshot.data(); + return EncryptedLocationJourney.fromJson(data!); + } +} + +@freezed +class EncryptedJourneyRoute with _$EncryptedJourneyRoute { + const factory EncryptedJourneyRoute({ + @BlobConverter() required Blob latitude, + @BlobConverter() required Blob longitude, + }) = _EncryptedJourneyRoute; + + factory EncryptedJourneyRoute.fromJson(Map json) => + _$EncryptedJourneyRouteFromJson(json); +} diff --git a/data/lib/api/location/journey/journey.freezed.dart b/data/lib/api/location/journey/journey.freezed.dart index daf77fda..71b1dbc5 100644 --- a/data/lib/api/location/journey/journey.freezed.dart +++ b/data/lib/api/location/journey/journey.freezed.dart @@ -29,12 +29,16 @@ mixin _$ApiLocationJourney { List get routes => throw _privateConstructorUsedError; double? get route_distance => throw _privateConstructorUsedError; int? get route_duration => throw _privateConstructorUsedError; - int? get created_at => throw _privateConstructorUsedError; - int? get update_at => throw _privateConstructorUsedError; - String? get type => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; + int get updated_at => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + /// Serializes this ApiLocationJourney to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiLocationJourney + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiLocationJourneyCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -55,9 +59,9 @@ abstract class $ApiLocationJourneyCopyWith<$Res> { List routes, double? route_distance, int? route_duration, - int? created_at, - int? update_at, - String? type}); + int created_at, + int updated_at, + String type}); } /// @nodoc @@ -70,6 +74,8 @@ class _$ApiLocationJourneyCopyWithImpl<$Res, $Val extends ApiLocationJourney> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiLocationJourney + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -82,9 +88,9 @@ class _$ApiLocationJourneyCopyWithImpl<$Res, $Val extends ApiLocationJourney> Object? routes = null, Object? route_distance = freezed, Object? route_duration = freezed, - Object? created_at = freezed, - Object? update_at = freezed, - Object? type = freezed, + Object? created_at = null, + Object? updated_at = null, + Object? type = null, }) { return _then(_value.copyWith( id: freezed == id @@ -123,18 +129,18 @@ class _$ApiLocationJourneyCopyWithImpl<$Res, $Val extends ApiLocationJourney> ? _value.route_duration : route_duration // ignore: cast_nullable_to_non_nullable as int?, - created_at: freezed == created_at + created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable - as int?, - update_at: freezed == update_at - ? _value.update_at - : update_at // ignore: cast_nullable_to_non_nullable - as int?, - type: freezed == type + as int, + updated_at: null == updated_at + ? _value.updated_at + : updated_at // ignore: cast_nullable_to_non_nullable + as int, + type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable - as String?, + as String, ) as $Val); } } @@ -157,9 +163,9 @@ abstract class _$$LocationJourneyImplCopyWith<$Res> List routes, double? route_distance, int? route_duration, - int? created_at, - int? update_at, - String? type}); + int created_at, + int updated_at, + String type}); } /// @nodoc @@ -170,6 +176,8 @@ class __$$LocationJourneyImplCopyWithImpl<$Res> _$LocationJourneyImpl _value, $Res Function(_$LocationJourneyImpl) _then) : super(_value, _then); + /// Create a copy of ApiLocationJourney + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -182,9 +190,9 @@ class __$$LocationJourneyImplCopyWithImpl<$Res> Object? routes = null, Object? route_distance = freezed, Object? route_duration = freezed, - Object? created_at = freezed, - Object? update_at = freezed, - Object? type = freezed, + Object? created_at = null, + Object? updated_at = null, + Object? type = null, }) { return _then(_$LocationJourneyImpl( id: freezed == id @@ -223,18 +231,18 @@ class __$$LocationJourneyImplCopyWithImpl<$Res> ? _value.route_duration : route_duration // ignore: cast_nullable_to_non_nullable as int?, - created_at: freezed == created_at + created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable - as int?, - update_at: freezed == update_at - ? _value.update_at - : update_at // ignore: cast_nullable_to_non_nullable - as int?, - type: freezed == type + as int, + updated_at: null == updated_at + ? _value.updated_at + : updated_at // ignore: cast_nullable_to_non_nullable + as int, + type: null == type ? _value.type : type // ignore: cast_nullable_to_non_nullable - as String?, + as String, )); } } @@ -252,9 +260,9 @@ class _$LocationJourneyImpl extends _LocationJourney { final List routes = const [], this.route_distance, this.route_duration, - this.created_at, - this.update_at, - this.type}) + required this.created_at, + required this.updated_at, + required this.type}) : _routes = routes, super._(); @@ -287,15 +295,15 @@ class _$LocationJourneyImpl extends _LocationJourney { @override final int? route_duration; @override - final int? created_at; + final int created_at; @override - final int? update_at; + final int updated_at; @override - final String? type; + final String type; @override String toString() { - return 'ApiLocationJourney(id: $id, user_id: $user_id, from_latitude: $from_latitude, from_longitude: $from_longitude, to_latitude: $to_latitude, to_longitude: $to_longitude, routes: $routes, route_distance: $route_distance, route_duration: $route_duration, created_at: $created_at, update_at: $update_at, type: $type)'; + return 'ApiLocationJourney(id: $id, user_id: $user_id, from_latitude: $from_latitude, from_longitude: $from_longitude, to_latitude: $to_latitude, to_longitude: $to_longitude, routes: $routes, route_distance: $route_distance, route_duration: $route_duration, created_at: $created_at, updated_at: $updated_at, type: $type)'; } @override @@ -320,12 +328,12 @@ class _$LocationJourneyImpl extends _LocationJourney { other.route_duration == route_duration) && (identical(other.created_at, created_at) || other.created_at == created_at) && - (identical(other.update_at, update_at) || - other.update_at == update_at) && + (identical(other.updated_at, updated_at) || + other.updated_at == updated_at) && (identical(other.type, type) || other.type == type)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -339,10 +347,12 @@ class _$LocationJourneyImpl extends _LocationJourney { route_distance, route_duration, created_at, - update_at, + updated_at, type); - @JsonKey(ignore: true) + /// Create a copy of ApiLocationJourney + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LocationJourneyImplCopyWith<_$LocationJourneyImpl> get copyWith => @@ -368,9 +378,9 @@ abstract class _LocationJourney extends ApiLocationJourney { final List routes, final double? route_distance, final int? route_duration, - final int? created_at, - final int? update_at, - final String? type}) = _$LocationJourneyImpl; + required final int created_at, + required final int updated_at, + required final String type}) = _$LocationJourneyImpl; const _LocationJourney._() : super._(); factory _LocationJourney.fromJson(Map json) = @@ -395,13 +405,16 @@ abstract class _LocationJourney extends ApiLocationJourney { @override int? get route_duration; @override - int? get created_at; + int get created_at; @override - int? get update_at; + int get updated_at; @override - String? get type; + String get type; + + /// Create a copy of ApiLocationJourney + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$LocationJourneyImplCopyWith<_$LocationJourneyImpl> get copyWith => throw _privateConstructorUsedError; } @@ -415,8 +428,12 @@ mixin _$JourneyRoute { double get latitude => throw _privateConstructorUsedError; double get longitude => throw _privateConstructorUsedError; + /// Serializes this JourneyRoute to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of JourneyRoute + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $JourneyRouteCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -440,6 +457,8 @@ class _$JourneyRouteCopyWithImpl<$Res, $Val extends JourneyRoute> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of JourneyRoute + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -478,6 +497,8 @@ class __$$JourneyRouteImplCopyWithImpl<$Res> _$JourneyRouteImpl _value, $Res Function(_$JourneyRouteImpl) _then) : super(_value, _then); + /// Create a copy of JourneyRoute + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -526,11 +547,13 @@ class _$JourneyRouteImpl implements _JourneyRoute { other.longitude == longitude)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, latitude, longitude); - @JsonKey(ignore: true) + /// Create a copy of JourneyRoute + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$JourneyRouteImplCopyWith<_$JourneyRouteImpl> get copyWith => @@ -556,8 +579,616 @@ abstract class _JourneyRoute implements JourneyRoute { double get latitude; @override double get longitude; + + /// Create a copy of JourneyRoute + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$JourneyRouteImplCopyWith<_$JourneyRouteImpl> get copyWith => throw _privateConstructorUsedError; } + +EncryptedLocationJourney _$EncryptedLocationJourneyFromJson( + Map json) { + return _EncryptedLocationJourney.fromJson(json); +} + +/// @nodoc +mixin _$EncryptedLocationJourney { + String? get id => throw _privateConstructorUsedError; + String get user_id => throw _privateConstructorUsedError; + @BlobConverter() + Blob get from_latitude => throw _privateConstructorUsedError; + @BlobConverter() + Blob get from_longitude => throw _privateConstructorUsedError; + @BlobConverter() + Blob? get to_latitude => throw _privateConstructorUsedError; + @BlobConverter() + Blob? get to_longitude => throw _privateConstructorUsedError; + List get routes => throw _privateConstructorUsedError; + double? get route_distance => throw _privateConstructorUsedError; + int? get route_duration => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; + int get updated_at => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; + + /// Serializes this EncryptedLocationJourney to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of EncryptedLocationJourney + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EncryptedLocationJourneyCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EncryptedLocationJourneyCopyWith<$Res> { + factory $EncryptedLocationJourneyCopyWith(EncryptedLocationJourney value, + $Res Function(EncryptedLocationJourney) then) = + _$EncryptedLocationJourneyCopyWithImpl<$Res, EncryptedLocationJourney>; + @useResult + $Res call( + {String? id, + String user_id, + @BlobConverter() Blob from_latitude, + @BlobConverter() Blob from_longitude, + @BlobConverter() Blob? to_latitude, + @BlobConverter() Blob? to_longitude, + List routes, + double? route_distance, + int? route_duration, + int created_at, + int updated_at, + String type}); +} + +/// @nodoc +class _$EncryptedLocationJourneyCopyWithImpl<$Res, + $Val extends EncryptedLocationJourney> + implements $EncryptedLocationJourneyCopyWith<$Res> { + _$EncryptedLocationJourneyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of EncryptedLocationJourney + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? user_id = null, + Object? from_latitude = null, + Object? from_longitude = null, + Object? to_latitude = freezed, + Object? to_longitude = freezed, + Object? routes = null, + Object? route_distance = freezed, + Object? route_duration = freezed, + Object? created_at = null, + Object? updated_at = null, + Object? type = null, + }) { + return _then(_value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + user_id: null == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as String, + from_latitude: null == from_latitude + ? _value.from_latitude + : from_latitude // ignore: cast_nullable_to_non_nullable + as Blob, + from_longitude: null == from_longitude + ? _value.from_longitude + : from_longitude // ignore: cast_nullable_to_non_nullable + as Blob, + to_latitude: freezed == to_latitude + ? _value.to_latitude + : to_latitude // ignore: cast_nullable_to_non_nullable + as Blob?, + to_longitude: freezed == to_longitude + ? _value.to_longitude + : to_longitude // ignore: cast_nullable_to_non_nullable + as Blob?, + routes: null == routes + ? _value.routes + : routes // ignore: cast_nullable_to_non_nullable + as List, + route_distance: freezed == route_distance + ? _value.route_distance + : route_distance // ignore: cast_nullable_to_non_nullable + as double?, + route_duration: freezed == route_duration + ? _value.route_duration + : route_duration // ignore: cast_nullable_to_non_nullable + as int?, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + updated_at: null == updated_at + ? _value.updated_at + : updated_at // ignore: cast_nullable_to_non_nullable + as int, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$EncryptedLocationJourneyImplCopyWith<$Res> + implements $EncryptedLocationJourneyCopyWith<$Res> { + factory _$$EncryptedLocationJourneyImplCopyWith( + _$EncryptedLocationJourneyImpl value, + $Res Function(_$EncryptedLocationJourneyImpl) then) = + __$$EncryptedLocationJourneyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? id, + String user_id, + @BlobConverter() Blob from_latitude, + @BlobConverter() Blob from_longitude, + @BlobConverter() Blob? to_latitude, + @BlobConverter() Blob? to_longitude, + List routes, + double? route_distance, + int? route_duration, + int created_at, + int updated_at, + String type}); +} + +/// @nodoc +class __$$EncryptedLocationJourneyImplCopyWithImpl<$Res> + extends _$EncryptedLocationJourneyCopyWithImpl<$Res, + _$EncryptedLocationJourneyImpl> + implements _$$EncryptedLocationJourneyImplCopyWith<$Res> { + __$$EncryptedLocationJourneyImplCopyWithImpl( + _$EncryptedLocationJourneyImpl _value, + $Res Function(_$EncryptedLocationJourneyImpl) _then) + : super(_value, _then); + + /// Create a copy of EncryptedLocationJourney + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? user_id = null, + Object? from_latitude = null, + Object? from_longitude = null, + Object? to_latitude = freezed, + Object? to_longitude = freezed, + Object? routes = null, + Object? route_distance = freezed, + Object? route_duration = freezed, + Object? created_at = null, + Object? updated_at = null, + Object? type = null, + }) { + return _then(_$EncryptedLocationJourneyImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + user_id: null == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as String, + from_latitude: null == from_latitude + ? _value.from_latitude + : from_latitude // ignore: cast_nullable_to_non_nullable + as Blob, + from_longitude: null == from_longitude + ? _value.from_longitude + : from_longitude // ignore: cast_nullable_to_non_nullable + as Blob, + to_latitude: freezed == to_latitude + ? _value.to_latitude + : to_latitude // ignore: cast_nullable_to_non_nullable + as Blob?, + to_longitude: freezed == to_longitude + ? _value.to_longitude + : to_longitude // ignore: cast_nullable_to_non_nullable + as Blob?, + routes: null == routes + ? _value._routes + : routes // ignore: cast_nullable_to_non_nullable + as List, + route_distance: freezed == route_distance + ? _value.route_distance + : route_distance // ignore: cast_nullable_to_non_nullable + as double?, + route_duration: freezed == route_duration + ? _value.route_duration + : route_duration // ignore: cast_nullable_to_non_nullable + as int?, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + updated_at: null == updated_at + ? _value.updated_at + : updated_at // ignore: cast_nullable_to_non_nullable + as int, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$EncryptedLocationJourneyImpl extends _EncryptedLocationJourney { + const _$EncryptedLocationJourneyImpl( + {this.id, + required this.user_id, + @BlobConverter() required this.from_latitude, + @BlobConverter() required this.from_longitude, + @BlobConverter() this.to_latitude, + @BlobConverter() this.to_longitude, + final List routes = const [], + this.route_distance, + this.route_duration, + required this.created_at, + required this.updated_at, + required this.type}) + : _routes = routes, + super._(); + + factory _$EncryptedLocationJourneyImpl.fromJson(Map json) => + _$$EncryptedLocationJourneyImplFromJson(json); + + @override + final String? id; + @override + final String user_id; + @override + @BlobConverter() + final Blob from_latitude; + @override + @BlobConverter() + final Blob from_longitude; + @override + @BlobConverter() + final Blob? to_latitude; + @override + @BlobConverter() + final Blob? to_longitude; + final List _routes; + @override + @JsonKey() + List get routes { + if (_routes is EqualUnmodifiableListView) return _routes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_routes); + } + + @override + final double? route_distance; + @override + final int? route_duration; + @override + final int created_at; + @override + final int updated_at; + @override + final String type; + + @override + String toString() { + return 'EncryptedLocationJourney(id: $id, user_id: $user_id, from_latitude: $from_latitude, from_longitude: $from_longitude, to_latitude: $to_latitude, to_longitude: $to_longitude, routes: $routes, route_distance: $route_distance, route_duration: $route_duration, created_at: $created_at, updated_at: $updated_at, type: $type)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EncryptedLocationJourneyImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.user_id, user_id) || other.user_id == user_id) && + (identical(other.from_latitude, from_latitude) || + other.from_latitude == from_latitude) && + (identical(other.from_longitude, from_longitude) || + other.from_longitude == from_longitude) && + (identical(other.to_latitude, to_latitude) || + other.to_latitude == to_latitude) && + (identical(other.to_longitude, to_longitude) || + other.to_longitude == to_longitude) && + const DeepCollectionEquality().equals(other._routes, _routes) && + (identical(other.route_distance, route_distance) || + other.route_distance == route_distance) && + (identical(other.route_duration, route_duration) || + other.route_duration == route_duration) && + (identical(other.created_at, created_at) || + other.created_at == created_at) && + (identical(other.updated_at, updated_at) || + other.updated_at == updated_at) && + (identical(other.type, type) || other.type == type)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + user_id, + from_latitude, + from_longitude, + to_latitude, + to_longitude, + const DeepCollectionEquality().hash(_routes), + route_distance, + route_duration, + created_at, + updated_at, + type); + + /// Create a copy of EncryptedLocationJourney + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EncryptedLocationJourneyImplCopyWith<_$EncryptedLocationJourneyImpl> + get copyWith => __$$EncryptedLocationJourneyImplCopyWithImpl< + _$EncryptedLocationJourneyImpl>(this, _$identity); + + @override + Map toJson() { + return _$$EncryptedLocationJourneyImplToJson( + this, + ); + } +} + +abstract class _EncryptedLocationJourney extends EncryptedLocationJourney { + const factory _EncryptedLocationJourney( + {final String? id, + required final String user_id, + @BlobConverter() required final Blob from_latitude, + @BlobConverter() required final Blob from_longitude, + @BlobConverter() final Blob? to_latitude, + @BlobConverter() final Blob? to_longitude, + final List routes, + final double? route_distance, + final int? route_duration, + required final int created_at, + required final int updated_at, + required final String type}) = _$EncryptedLocationJourneyImpl; + const _EncryptedLocationJourney._() : super._(); + + factory _EncryptedLocationJourney.fromJson(Map json) = + _$EncryptedLocationJourneyImpl.fromJson; + + @override + String? get id; + @override + String get user_id; + @override + @BlobConverter() + Blob get from_latitude; + @override + @BlobConverter() + Blob get from_longitude; + @override + @BlobConverter() + Blob? get to_latitude; + @override + @BlobConverter() + Blob? get to_longitude; + @override + List get routes; + @override + double? get route_distance; + @override + int? get route_duration; + @override + int get created_at; + @override + int get updated_at; + @override + String get type; + + /// Create a copy of EncryptedLocationJourney + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EncryptedLocationJourneyImplCopyWith<_$EncryptedLocationJourneyImpl> + get copyWith => throw _privateConstructorUsedError; +} + +EncryptedJourneyRoute _$EncryptedJourneyRouteFromJson( + Map json) { + return _EncryptedJourneyRoute.fromJson(json); +} + +/// @nodoc +mixin _$EncryptedJourneyRoute { + @BlobConverter() + Blob get latitude => throw _privateConstructorUsedError; + @BlobConverter() + Blob get longitude => throw _privateConstructorUsedError; + + /// Serializes this EncryptedJourneyRoute to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of EncryptedJourneyRoute + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EncryptedJourneyRouteCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EncryptedJourneyRouteCopyWith<$Res> { + factory $EncryptedJourneyRouteCopyWith(EncryptedJourneyRoute value, + $Res Function(EncryptedJourneyRoute) then) = + _$EncryptedJourneyRouteCopyWithImpl<$Res, EncryptedJourneyRoute>; + @useResult + $Res call({@BlobConverter() Blob latitude, @BlobConverter() Blob longitude}); +} + +/// @nodoc +class _$EncryptedJourneyRouteCopyWithImpl<$Res, + $Val extends EncryptedJourneyRoute> + implements $EncryptedJourneyRouteCopyWith<$Res> { + _$EncryptedJourneyRouteCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of EncryptedJourneyRoute + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? latitude = null, + Object? longitude = null, + }) { + return _then(_value.copyWith( + latitude: null == latitude + ? _value.latitude + : latitude // ignore: cast_nullable_to_non_nullable + as Blob, + longitude: null == longitude + ? _value.longitude + : longitude // ignore: cast_nullable_to_non_nullable + as Blob, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$EncryptedJourneyRouteImplCopyWith<$Res> + implements $EncryptedJourneyRouteCopyWith<$Res> { + factory _$$EncryptedJourneyRouteImplCopyWith( + _$EncryptedJourneyRouteImpl value, + $Res Function(_$EncryptedJourneyRouteImpl) then) = + __$$EncryptedJourneyRouteImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({@BlobConverter() Blob latitude, @BlobConverter() Blob longitude}); +} + +/// @nodoc +class __$$EncryptedJourneyRouteImplCopyWithImpl<$Res> + extends _$EncryptedJourneyRouteCopyWithImpl<$Res, + _$EncryptedJourneyRouteImpl> + implements _$$EncryptedJourneyRouteImplCopyWith<$Res> { + __$$EncryptedJourneyRouteImplCopyWithImpl(_$EncryptedJourneyRouteImpl _value, + $Res Function(_$EncryptedJourneyRouteImpl) _then) + : super(_value, _then); + + /// Create a copy of EncryptedJourneyRoute + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? latitude = null, + Object? longitude = null, + }) { + return _then(_$EncryptedJourneyRouteImpl( + latitude: null == latitude + ? _value.latitude + : latitude // ignore: cast_nullable_to_non_nullable + as Blob, + longitude: null == longitude + ? _value.longitude + : longitude // ignore: cast_nullable_to_non_nullable + as Blob, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$EncryptedJourneyRouteImpl implements _EncryptedJourneyRoute { + const _$EncryptedJourneyRouteImpl( + {@BlobConverter() required this.latitude, + @BlobConverter() required this.longitude}); + + factory _$EncryptedJourneyRouteImpl.fromJson(Map json) => + _$$EncryptedJourneyRouteImplFromJson(json); + + @override + @BlobConverter() + final Blob latitude; + @override + @BlobConverter() + final Blob longitude; + + @override + String toString() { + return 'EncryptedJourneyRoute(latitude: $latitude, longitude: $longitude)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EncryptedJourneyRouteImpl && + (identical(other.latitude, latitude) || + other.latitude == latitude) && + (identical(other.longitude, longitude) || + other.longitude == longitude)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, latitude, longitude); + + /// Create a copy of EncryptedJourneyRoute + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EncryptedJourneyRouteImplCopyWith<_$EncryptedJourneyRouteImpl> + get copyWith => __$$EncryptedJourneyRouteImplCopyWithImpl< + _$EncryptedJourneyRouteImpl>(this, _$identity); + + @override + Map toJson() { + return _$$EncryptedJourneyRouteImplToJson( + this, + ); + } +} + +abstract class _EncryptedJourneyRoute implements EncryptedJourneyRoute { + const factory _EncryptedJourneyRoute( + {@BlobConverter() required final Blob latitude, + @BlobConverter() required final Blob longitude}) = + _$EncryptedJourneyRouteImpl; + + factory _EncryptedJourneyRoute.fromJson(Map json) = + _$EncryptedJourneyRouteImpl.fromJson; + + @override + @BlobConverter() + Blob get latitude; + @override + @BlobConverter() + Blob get longitude; + + /// Create a copy of EncryptedJourneyRoute + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EncryptedJourneyRouteImplCopyWith<_$EncryptedJourneyRouteImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/data/lib/api/location/journey/journey.g.dart b/data/lib/api/location/journey/journey.g.dart index 7efeefe8..8121e147 100644 --- a/data/lib/api/location/journey/journey.g.dart +++ b/data/lib/api/location/journey/journey.g.dart @@ -21,9 +21,9 @@ _$LocationJourneyImpl _$$LocationJourneyImplFromJson( const [], route_distance: (json['route_distance'] as num?)?.toDouble(), route_duration: (json['route_duration'] as num?)?.toInt(), - created_at: (json['created_at'] as num?)?.toInt(), - update_at: (json['update_at'] as num?)?.toInt(), - type: json['type'] as String?, + created_at: (json['created_at'] as num).toInt(), + updated_at: (json['updated_at'] as num).toInt(), + type: json['type'] as String, ); Map _$$LocationJourneyImplToJson( @@ -39,7 +39,7 @@ Map _$$LocationJourneyImplToJson( 'route_distance': instance.route_distance, 'route_duration': instance.route_duration, 'created_at': instance.created_at, - 'update_at': instance.update_at, + 'updated_at': instance.updated_at, 'type': instance.type, }; @@ -54,3 +54,69 @@ Map _$$JourneyRouteImplToJson(_$JourneyRouteImpl instance) => 'latitude': instance.latitude, 'longitude': instance.longitude, }; + +_$EncryptedLocationJourneyImpl _$$EncryptedLocationJourneyImplFromJson( + Map json) => + _$EncryptedLocationJourneyImpl( + id: json['id'] as String?, + user_id: json['user_id'] as String, + from_latitude: const BlobConverter() + .fromJson(json['from_latitude'] as Map?), + from_longitude: const BlobConverter() + .fromJson(json['from_longitude'] as Map?), + to_latitude: const BlobConverter() + .fromJson(json['to_latitude'] as Map?), + to_longitude: const BlobConverter() + .fromJson(json['to_longitude'] as Map?), + routes: (json['routes'] as List?) + ?.map((e) => + EncryptedJourneyRoute.fromJson(e as Map)) + .toList() ?? + const [], + route_distance: (json['route_distance'] as num?)?.toDouble(), + route_duration: (json['route_duration'] as num?)?.toInt(), + created_at: (json['created_at'] as num).toInt(), + updated_at: (json['updated_at'] as num).toInt(), + type: json['type'] as String, + ); + +Map _$$EncryptedLocationJourneyImplToJson( + _$EncryptedLocationJourneyImpl instance) => + { + 'id': instance.id, + 'user_id': instance.user_id, + 'from_latitude': const BlobConverter().toJson(instance.from_latitude), + 'from_longitude': const BlobConverter().toJson(instance.from_longitude), + 'to_latitude': _$JsonConverterToJson?, Blob>( + instance.to_latitude, const BlobConverter().toJson), + 'to_longitude': _$JsonConverterToJson?, Blob>( + instance.to_longitude, const BlobConverter().toJson), + 'routes': instance.routes.map((e) => e.toJson()).toList(), + 'route_distance': instance.route_distance, + 'route_duration': instance.route_duration, + 'created_at': instance.created_at, + 'updated_at': instance.updated_at, + 'type': instance.type, + }; + +Json? _$JsonConverterToJson( + Value? value, + Json? Function(Value value) toJson, +) => + value == null ? null : toJson(value); + +_$EncryptedJourneyRouteImpl _$$EncryptedJourneyRouteImplFromJson( + Map json) => + _$EncryptedJourneyRouteImpl( + latitude: const BlobConverter() + .fromJson(json['latitude'] as Map?), + longitude: const BlobConverter() + .fromJson(json['longitude'] as Map?), + ); + +Map _$$EncryptedJourneyRouteImplToJson( + _$EncryptedJourneyRouteImpl instance) => + { + 'latitude': const BlobConverter().toJson(instance.latitude), + 'longitude': const BlobConverter().toJson(instance.longitude), + }; diff --git a/data/lib/api/location/location.dart b/data/lib/api/location/location.dart index 28d8564e..a009c502 100644 --- a/data/lib/api/location/location.dart +++ b/data/lib/api/location/location.dart @@ -1,15 +1,14 @@ //ignore_for_file: constant_identifier_names import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:data/converter/blob_converter.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; part 'location.freezed.dart'; -part 'location.g.dart'; -const USER_STATE_STEADY = 0; -const USER_STATE_MOVING = 1; +part 'location.g.dart'; @freezed class ApiLocation with _$ApiLocation { @@ -20,8 +19,7 @@ class ApiLocation with _$ApiLocation { required String user_id, required double latitude, required double longitude, - int? user_state, - int? created_at, + required int created_at, }) = _ApiLocation; factory ApiLocation.fromJson(Map data) => @@ -37,6 +35,31 @@ class ApiLocation with _$ApiLocation { Map toFireStore(ApiLocation space) => space.toJson(); } +@freezed +class EncryptedApiLocation with _$EncryptedApiLocation { + const EncryptedApiLocation._(); + + const factory EncryptedApiLocation({ + required String id, + required String user_id, + @BlobConverter() required Blob latitude, + @BlobConverter() required Blob longitude, + required int created_at, + }) = _EncryptedApiLocation; + + factory EncryptedApiLocation.fromJson(Map data) => + _$EncryptedApiLocationFromJson(data); + + factory EncryptedApiLocation.fromFireStore( + DocumentSnapshot> snapshot, + SnapshotOptions? options) { + Map? data = snapshot.data(); + return EncryptedApiLocation.fromJson(data!); + } + + Map toFireStore(ApiLocation space) => space.toJson(); +} + class LocationData { final double latitude; final double longitude; @@ -49,7 +72,8 @@ class LocationData { }); double distanceTo(LocationData other) { - return Geolocator.distanceBetween(latitude, longitude, other.latitude, other.longitude); + return Geolocator.distanceBetween( + latitude, longitude, other.latitude, other.longitude); } } @@ -58,4 +82,4 @@ class MapTypeInfo { final int index; MapTypeInfo(this.mapType, this.index); -} \ No newline at end of file +} diff --git a/data/lib/api/location/location.freezed.dart b/data/lib/api/location/location.freezed.dart index e7093584..4841fcd7 100644 --- a/data/lib/api/location/location.freezed.dart +++ b/data/lib/api/location/location.freezed.dart @@ -24,11 +24,14 @@ mixin _$ApiLocation { String get user_id => throw _privateConstructorUsedError; double get latitude => throw _privateConstructorUsedError; double get longitude => throw _privateConstructorUsedError; - int? get user_state => throw _privateConstructorUsedError; - int? get created_at => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; + /// Serializes this ApiLocation to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiLocation + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiLocationCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -44,8 +47,7 @@ abstract class $ApiLocationCopyWith<$Res> { String user_id, double latitude, double longitude, - int? user_state, - int? created_at}); + int created_at}); } /// @nodoc @@ -58,6 +60,8 @@ class _$ApiLocationCopyWithImpl<$Res, $Val extends ApiLocation> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiLocation + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -65,8 +69,7 @@ class _$ApiLocationCopyWithImpl<$Res, $Val extends ApiLocation> Object? user_id = null, Object? latitude = null, Object? longitude = null, - Object? user_state = freezed, - Object? created_at = freezed, + Object? created_at = null, }) { return _then(_value.copyWith( id: null == id @@ -85,14 +88,10 @@ class _$ApiLocationCopyWithImpl<$Res, $Val extends ApiLocation> ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable as double, - user_state: freezed == user_state - ? _value.user_state - : user_state // ignore: cast_nullable_to_non_nullable - as int?, - created_at: freezed == created_at + created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable - as int?, + as int, ) as $Val); } } @@ -110,8 +109,7 @@ abstract class _$$ApiLocationImplCopyWith<$Res> String user_id, double latitude, double longitude, - int? user_state, - int? created_at}); + int created_at}); } /// @nodoc @@ -122,6 +120,8 @@ class __$$ApiLocationImplCopyWithImpl<$Res> _$ApiLocationImpl _value, $Res Function(_$ApiLocationImpl) _then) : super(_value, _then); + /// Create a copy of ApiLocation + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -129,8 +129,7 @@ class __$$ApiLocationImplCopyWithImpl<$Res> Object? user_id = null, Object? latitude = null, Object? longitude = null, - Object? user_state = freezed, - Object? created_at = freezed, + Object? created_at = null, }) { return _then(_$ApiLocationImpl( id: null == id @@ -149,14 +148,10 @@ class __$$ApiLocationImplCopyWithImpl<$Res> ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable as double, - user_state: freezed == user_state - ? _value.user_state - : user_state // ignore: cast_nullable_to_non_nullable - as int?, - created_at: freezed == created_at + created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable - as int?, + as int, )); } } @@ -169,8 +164,7 @@ class _$ApiLocationImpl extends _ApiLocation { required this.user_id, required this.latitude, required this.longitude, - this.user_state, - this.created_at}) + required this.created_at}) : super._(); factory _$ApiLocationImpl.fromJson(Map json) => @@ -185,13 +179,11 @@ class _$ApiLocationImpl extends _ApiLocation { @override final double longitude; @override - final int? user_state; - @override - final int? created_at; + final int created_at; @override String toString() { - return 'ApiLocation(id: $id, user_id: $user_id, latitude: $latitude, longitude: $longitude, user_state: $user_state, created_at: $created_at)'; + return 'ApiLocation(id: $id, user_id: $user_id, latitude: $latitude, longitude: $longitude, created_at: $created_at)'; } @override @@ -205,18 +197,18 @@ class _$ApiLocationImpl extends _ApiLocation { other.latitude == latitude) && (identical(other.longitude, longitude) || other.longitude == longitude) && - (identical(other.user_state, user_state) || - other.user_state == user_state) && (identical(other.created_at, created_at) || other.created_at == created_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, id, user_id, latitude, longitude, user_state, created_at); + int get hashCode => + Object.hash(runtimeType, id, user_id, latitude, longitude, created_at); - @JsonKey(ignore: true) + /// Create a copy of ApiLocation + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiLocationImplCopyWith<_$ApiLocationImpl> get copyWith => @@ -236,8 +228,7 @@ abstract class _ApiLocation extends ApiLocation { required final String user_id, required final double latitude, required final double longitude, - final int? user_state, - final int? created_at}) = _$ApiLocationImpl; + required final int created_at}) = _$ApiLocationImpl; const _ApiLocation._() : super._(); factory _ApiLocation.fromJson(Map json) = @@ -252,11 +243,260 @@ abstract class _ApiLocation extends ApiLocation { @override double get longitude; @override - int? get user_state; - @override - int? get created_at; + int get created_at; + + /// Create a copy of ApiLocation + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiLocationImplCopyWith<_$ApiLocationImpl> get copyWith => throw _privateConstructorUsedError; } + +EncryptedApiLocation _$EncryptedApiLocationFromJson(Map json) { + return _EncryptedApiLocation.fromJson(json); +} + +/// @nodoc +mixin _$EncryptedApiLocation { + String get id => throw _privateConstructorUsedError; + String get user_id => throw _privateConstructorUsedError; + @BlobConverter() + Blob get latitude => throw _privateConstructorUsedError; + @BlobConverter() + Blob get longitude => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; + + /// Serializes this EncryptedApiLocation to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of EncryptedApiLocation + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EncryptedApiLocationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EncryptedApiLocationCopyWith<$Res> { + factory $EncryptedApiLocationCopyWith(EncryptedApiLocation value, + $Res Function(EncryptedApiLocation) then) = + _$EncryptedApiLocationCopyWithImpl<$Res, EncryptedApiLocation>; + @useResult + $Res call( + {String id, + String user_id, + @BlobConverter() Blob latitude, + @BlobConverter() Blob longitude, + int created_at}); +} + +/// @nodoc +class _$EncryptedApiLocationCopyWithImpl<$Res, + $Val extends EncryptedApiLocation> + implements $EncryptedApiLocationCopyWith<$Res> { + _$EncryptedApiLocationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of EncryptedApiLocation + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? user_id = null, + Object? latitude = null, + Object? longitude = null, + Object? created_at = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + user_id: null == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as String, + latitude: null == latitude + ? _value.latitude + : latitude // ignore: cast_nullable_to_non_nullable + as Blob, + longitude: null == longitude + ? _value.longitude + : longitude // ignore: cast_nullable_to_non_nullable + as Blob, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$EncryptedApiLocationImplCopyWith<$Res> + implements $EncryptedApiLocationCopyWith<$Res> { + factory _$$EncryptedApiLocationImplCopyWith(_$EncryptedApiLocationImpl value, + $Res Function(_$EncryptedApiLocationImpl) then) = + __$$EncryptedApiLocationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String user_id, + @BlobConverter() Blob latitude, + @BlobConverter() Blob longitude, + int created_at}); +} + +/// @nodoc +class __$$EncryptedApiLocationImplCopyWithImpl<$Res> + extends _$EncryptedApiLocationCopyWithImpl<$Res, _$EncryptedApiLocationImpl> + implements _$$EncryptedApiLocationImplCopyWith<$Res> { + __$$EncryptedApiLocationImplCopyWithImpl(_$EncryptedApiLocationImpl _value, + $Res Function(_$EncryptedApiLocationImpl) _then) + : super(_value, _then); + + /// Create a copy of EncryptedApiLocation + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? user_id = null, + Object? latitude = null, + Object? longitude = null, + Object? created_at = null, + }) { + return _then(_$EncryptedApiLocationImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + user_id: null == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as String, + latitude: null == latitude + ? _value.latitude + : latitude // ignore: cast_nullable_to_non_nullable + as Blob, + longitude: null == longitude + ? _value.longitude + : longitude // ignore: cast_nullable_to_non_nullable + as Blob, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$EncryptedApiLocationImpl extends _EncryptedApiLocation { + const _$EncryptedApiLocationImpl( + {required this.id, + required this.user_id, + @BlobConverter() required this.latitude, + @BlobConverter() required this.longitude, + required this.created_at}) + : super._(); + + factory _$EncryptedApiLocationImpl.fromJson(Map json) => + _$$EncryptedApiLocationImplFromJson(json); + + @override + final String id; + @override + final String user_id; + @override + @BlobConverter() + final Blob latitude; + @override + @BlobConverter() + final Blob longitude; + @override + final int created_at; + + @override + String toString() { + return 'EncryptedApiLocation(id: $id, user_id: $user_id, latitude: $latitude, longitude: $longitude, created_at: $created_at)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EncryptedApiLocationImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.user_id, user_id) || other.user_id == user_id) && + (identical(other.latitude, latitude) || + other.latitude == latitude) && + (identical(other.longitude, longitude) || + other.longitude == longitude) && + (identical(other.created_at, created_at) || + other.created_at == created_at)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, id, user_id, latitude, longitude, created_at); + + /// Create a copy of EncryptedApiLocation + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EncryptedApiLocationImplCopyWith<_$EncryptedApiLocationImpl> + get copyWith => + __$$EncryptedApiLocationImplCopyWithImpl<_$EncryptedApiLocationImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$EncryptedApiLocationImplToJson( + this, + ); + } +} + +abstract class _EncryptedApiLocation extends EncryptedApiLocation { + const factory _EncryptedApiLocation( + {required final String id, + required final String user_id, + @BlobConverter() required final Blob latitude, + @BlobConverter() required final Blob longitude, + required final int created_at}) = _$EncryptedApiLocationImpl; + const _EncryptedApiLocation._() : super._(); + + factory _EncryptedApiLocation.fromJson(Map json) = + _$EncryptedApiLocationImpl.fromJson; + + @override + String get id; + @override + String get user_id; + @override + @BlobConverter() + Blob get latitude; + @override + @BlobConverter() + Blob get longitude; + @override + int get created_at; + + /// Create a copy of EncryptedApiLocation + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EncryptedApiLocationImplCopyWith<_$EncryptedApiLocationImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/data/lib/api/location/location.g.dart b/data/lib/api/location/location.g.dart index c0321b29..b682a01c 100644 --- a/data/lib/api/location/location.g.dart +++ b/data/lib/api/location/location.g.dart @@ -12,8 +12,7 @@ _$ApiLocationImpl _$$ApiLocationImplFromJson(Map json) => user_id: json['user_id'] as String, latitude: (json['latitude'] as num).toDouble(), longitude: (json['longitude'] as num).toDouble(), - user_state: (json['user_state'] as num?)?.toInt(), - created_at: (json['created_at'] as num?)?.toInt(), + created_at: (json['created_at'] as num).toInt(), ); Map _$$ApiLocationImplToJson(_$ApiLocationImpl instance) => @@ -22,6 +21,27 @@ Map _$$ApiLocationImplToJson(_$ApiLocationImpl instance) => 'user_id': instance.user_id, 'latitude': instance.latitude, 'longitude': instance.longitude, - 'user_state': instance.user_state, + 'created_at': instance.created_at, + }; + +_$EncryptedApiLocationImpl _$$EncryptedApiLocationImplFromJson( + Map json) => + _$EncryptedApiLocationImpl( + id: json['id'] as String, + user_id: json['user_id'] as String, + latitude: const BlobConverter() + .fromJson(json['latitude'] as Map?), + longitude: const BlobConverter() + .fromJson(json['longitude'] as Map?), + created_at: (json['created_at'] as num).toInt(), + ); + +Map _$$EncryptedApiLocationImplToJson( + _$EncryptedApiLocationImpl instance) => + { + 'id': instance.id, + 'user_id': instance.user_id, + 'latitude': const BlobConverter().toJson(instance.latitude), + 'longitude': const BlobConverter().toJson(instance.longitude), 'created_at': instance.created_at, }; diff --git a/data/lib/api/message/message_models.freezed.dart b/data/lib/api/message/message_models.freezed.dart index 700ab58d..9506f92f 100644 --- a/data/lib/api/message/message_models.freezed.dart +++ b/data/lib/api/message/message_models.freezed.dart @@ -31,8 +31,12 @@ mixin _$ApiThread { @ServerTimestampConverter() DateTime? get last_message_at => throw _privateConstructorUsedError; + /// Serializes this ApiThread to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiThread + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiThreadCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -64,6 +68,8 @@ class _$ApiThreadCopyWithImpl<$Res, $Val extends ApiThread> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiThread + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -146,6 +152,8 @@ class __$$ApiThreadImplCopyWithImpl<$Res> _$ApiThreadImpl _value, $Res Function(_$ApiThreadImpl) _then) : super(_value, _then); + /// Create a copy of ApiThread + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -291,7 +299,7 @@ class _$ApiThreadImpl extends _ApiThread { other.last_message_at == last_message_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -305,7 +313,9 @@ class _$ApiThreadImpl extends _ApiThread { last_message, last_message_at); - @JsonKey(ignore: true) + /// Create a copy of ApiThread + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiThreadImplCopyWith<_$ApiThreadImpl> get copyWith => @@ -355,8 +365,11 @@ abstract class _ApiThread extends ApiThread { @override @ServerTimestampConverter() DateTime? get last_message_at; + + /// Create a copy of ApiThread + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiThreadImplCopyWith<_$ApiThreadImpl> get copyWith => throw _privateConstructorUsedError; } @@ -376,8 +389,12 @@ mixin _$ApiThreadMessage { @ServerTimestampConverter() DateTime? get created_at => throw _privateConstructorUsedError; + /// Serializes this ApiThreadMessage to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiThreadMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiThreadMessageCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -408,6 +425,8 @@ class _$ApiThreadMessageCopyWithImpl<$Res, $Val extends ApiThreadMessage> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiThreadMessage + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -478,6 +497,8 @@ class __$$ApiThreadMessageImplCopyWithImpl<$Res> $Res Function(_$ApiThreadMessageImpl) _then) : super(_value, _then); + /// Create a copy of ApiThreadMessage + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -593,7 +614,7 @@ class _$ApiThreadMessageImpl extends _ApiThreadMessage { other.created_at == created_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -605,7 +626,9 @@ class _$ApiThreadMessageImpl extends _ApiThreadMessage { const DeepCollectionEquality().hash(_archived_for), created_at); - @JsonKey(ignore: true) + /// Create a copy of ApiThreadMessage + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiThreadMessageImplCopyWith<_$ApiThreadMessageImpl> get copyWith => @@ -650,8 +673,11 @@ abstract class _ApiThreadMessage extends ApiThreadMessage { @override @ServerTimestampConverter() DateTime? get created_at; + + /// Create a copy of ApiThreadMessage + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiThreadMessageImplCopyWith<_$ApiThreadMessageImpl> get copyWith => throw _privateConstructorUsedError; } @@ -667,8 +693,12 @@ mixin _$ThreadInfo { throw _privateConstructorUsedError; List get members => throw _privateConstructorUsedError; + /// Serializes this ThreadInfo to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ThreadInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ThreadInfoCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -697,6 +727,8 @@ class _$ThreadInfoCopyWithImpl<$Res, $Val extends ThreadInfo> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ThreadInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -720,6 +752,8 @@ class _$ThreadInfoCopyWithImpl<$Res, $Val extends ThreadInfo> ) as $Val); } + /// Create a copy of ThreadInfo + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ApiThreadCopyWith<$Res> get thread { @@ -754,6 +788,8 @@ class __$$ThreadInfoImplCopyWithImpl<$Res> _$ThreadInfoImpl _value, $Res Function(_$ThreadInfoImpl) _then) : super(_value, _then); + /// Create a copy of ThreadInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -826,7 +862,7 @@ class _$ThreadInfoImpl extends _ThreadInfo { const DeepCollectionEquality().equals(other._members, _members)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -834,7 +870,9 @@ class _$ThreadInfoImpl extends _ThreadInfo { const DeepCollectionEquality().hash(_threadMessage), const DeepCollectionEquality().hash(_members)); - @JsonKey(ignore: true) + /// Create a copy of ThreadInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ThreadInfoImplCopyWith<_$ThreadInfoImpl> get copyWith => @@ -864,8 +902,11 @@ abstract class _ThreadInfo extends ThreadInfo { List get threadMessage; @override List get members; + + /// Create a copy of ThreadInfo + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ThreadInfoImplCopyWith<_$ThreadInfoImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/api/place/api_place.freezed.dart b/data/lib/api/place/api_place.freezed.dart index a492dca8..149e2b73 100644 --- a/data/lib/api/place/api_place.freezed.dart +++ b/data/lib/api/place/api_place.freezed.dart @@ -31,8 +31,12 @@ mixin _$ApiPlace { DateTime? get created_at => throw _privateConstructorUsedError; List get space_member_ids => throw _privateConstructorUsedError; + /// Serializes this ApiPlace to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiPlace + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiPlaceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -64,6 +68,8 @@ class _$ApiPlaceCopyWithImpl<$Res, $Val extends ApiPlace> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiPlace + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -146,6 +152,8 @@ class __$$ApiPlaceImplCopyWithImpl<$Res> _$ApiPlaceImpl _value, $Res Function(_$ApiPlaceImpl) _then) : super(_value, _then); + /// Create a copy of ApiPlace + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -274,7 +282,7 @@ class _$ApiPlaceImpl extends _ApiPlace { .equals(other._space_member_ids, _space_member_ids)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -288,7 +296,9 @@ class _$ApiPlaceImpl extends _ApiPlace { created_at, const DeepCollectionEquality().hash(_space_member_ids)); - @JsonKey(ignore: true) + /// Create a copy of ApiPlace + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiPlaceImplCopyWith<_$ApiPlaceImpl> get copyWith => @@ -337,8 +347,11 @@ abstract class _ApiPlace extends ApiPlace { DateTime? get created_at; @override List get space_member_ids; + + /// Create a copy of ApiPlace + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiPlaceImplCopyWith<_$ApiPlaceImpl> get copyWith => throw _privateConstructorUsedError; } @@ -356,8 +369,12 @@ mixin _$ApiPlaceMemberSetting { List get arrival_alert_for => throw _privateConstructorUsedError; List get leave_alert_for => throw _privateConstructorUsedError; + /// Serializes this ApiPlaceMemberSetting to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiPlaceMemberSetting + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiPlaceMemberSettingCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -387,6 +404,8 @@ class _$ApiPlaceMemberSettingCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiPlaceMemberSetting + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -447,6 +466,8 @@ class __$$ApiPlaceMemberSettingImplCopyWithImpl<$Res> $Res Function(_$ApiPlaceMemberSettingImpl) _then) : super(_value, _then); + /// Create a copy of ApiPlaceMemberSetting + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -544,7 +565,7 @@ class _$ApiPlaceMemberSettingImpl extends _ApiPlaceMemberSetting { .equals(other._leave_alert_for, _leave_alert_for)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -554,7 +575,9 @@ class _$ApiPlaceMemberSettingImpl extends _ApiPlaceMemberSetting { const DeepCollectionEquality().hash(_arrival_alert_for), const DeepCollectionEquality().hash(_leave_alert_for)); - @JsonKey(ignore: true) + /// Create a copy of ApiPlaceMemberSetting + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiPlaceMemberSettingImplCopyWith<_$ApiPlaceMemberSettingImpl> @@ -591,8 +614,11 @@ abstract class _ApiPlaceMemberSetting extends ApiPlaceMemberSetting { List get arrival_alert_for; @override List get leave_alert_for; + + /// Create a copy of ApiPlaceMemberSetting + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiPlaceMemberSettingImplCopyWith<_$ApiPlaceMemberSettingImpl> get copyWith => throw _privateConstructorUsedError; } @@ -609,8 +635,12 @@ mixin _$ApiNearbyPlace { double get lat => throw _privateConstructorUsedError; double get lng => throw _privateConstructorUsedError; + /// Serializes this ApiNearbyPlace to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiNearbyPlace + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiNearbyPlaceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -639,6 +669,8 @@ class _$ApiNearbyPlaceCopyWithImpl<$Res, $Val extends ApiNearbyPlace> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiNearbyPlace + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -697,6 +729,8 @@ class __$$ApiNearbyPlaceImplCopyWithImpl<$Res> _$ApiNearbyPlaceImpl _value, $Res Function(_$ApiNearbyPlaceImpl) _then) : super(_value, _then); + /// Create a copy of ApiNearbyPlace + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -774,12 +808,14 @@ class _$ApiNearbyPlaceImpl extends _ApiNearbyPlace { (identical(other.lng, lng) || other.lng == lng)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, name, formatted_address, lat, lng); - @JsonKey(ignore: true) + /// Create a copy of ApiNearbyPlace + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiNearbyPlaceImplCopyWith<_$ApiNearbyPlaceImpl> get copyWith => @@ -816,8 +852,11 @@ abstract class _ApiNearbyPlace extends ApiNearbyPlace { double get lat; @override double get lng; + + /// Create a copy of ApiNearbyPlace + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiNearbyPlaceImplCopyWith<_$ApiNearbyPlaceImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/api/space/api_group_key_model.dart b/data/lib/api/space/api_group_key_model.dart new file mode 100644 index 00000000..576a1e01 --- /dev/null +++ b/data/lib/api/space/api_group_key_model.dart @@ -0,0 +1,73 @@ + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:data/converter/blob_converter.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'api_group_key_model.freezed.dart'; +part 'api_group_key_model.g.dart'; + +@freezed +class ApiGroupKey with _$ApiGroupKey { + const ApiGroupKey._(); + + const factory ApiGroupKey({ + required int docUpdatedAt, + @Default({}) Map memberKeys, + }) = _ApiGroupKey; + + factory ApiGroupKey.fromJson(Map data) => + _$ApiGroupKeyFromJson(data); + + factory ApiGroupKey.fromFireStore( + DocumentSnapshot> snapshot, + SnapshotOptions? options) { + Map? data = snapshot.data(); + return ApiGroupKey.fromJson(data!); + } + + Map toFireStore(ApiGroupKey key) => key.toJson(); + +} + +@freezed +class ApiMemberKeyData with _$ApiMemberKeyData { + const factory ApiMemberKeyData({ + @Default(0)int memberDeviceId, + @Default(0) int dataUpdatedAt, + @Default([]) List distributions, + }) = _ApiMemberKeyData; + + factory ApiMemberKeyData.fromJson(Map data) => + _$ApiMemberKeyDataFromJson(data); + +} + +@freezed +class EncryptedDistribution with _$EncryptedDistribution { + const EncryptedDistribution._(); + + const factory EncryptedDistribution({ + @Default("") String recipientId, + @BlobConverter() required Blob ephemeralPub, + @BlobConverter() required Blob iv, + @BlobConverter() required Blob ciphertext, + }) = _EncryptedDistribution; + + factory EncryptedDistribution.fromJson(Map data) => + _$EncryptedDistributionFromJson(data); + + void validateFieldSizes() { + if (ephemeralPub.bytes.length != 33 && ephemeralPub.bytes.isNotEmpty) { + throw ArgumentError( + "Invalid size for ephemeralPub: expected 33 bytes, got ${ephemeralPub.bytes.length} bytes."); + } + if (iv.bytes.length != 16 && iv.bytes.isNotEmpty) { + throw ArgumentError( + "Invalid size for iv: expected 16 bytes, got ${iv.bytes.length} bytes."); + } + if (ciphertext.bytes.length > 64 * 1024 && ciphertext.bytes.isNotEmpty) { + throw ArgumentError( + "Invalid size for ciphertext: maximum allowed size is 64 KB, got ${ciphertext.bytes.length} bytes."); + } + } +} \ No newline at end of file diff --git a/data/lib/api/space/api_group_key_model.freezed.dart b/data/lib/api/space/api_group_key_model.freezed.dart new file mode 100644 index 00000000..8651fb26 --- /dev/null +++ b/data/lib/api/space/api_group_key_model.freezed.dart @@ -0,0 +1,641 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'api_group_key_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ApiGroupKey _$ApiGroupKeyFromJson(Map json) { + return _ApiGroupKey.fromJson(json); +} + +/// @nodoc +mixin _$ApiGroupKey { + int get docUpdatedAt => throw _privateConstructorUsedError; + Map get memberKeys => + throw _privateConstructorUsedError; + + /// Serializes this ApiGroupKey to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ApiGroupKey + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ApiGroupKeyCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApiGroupKeyCopyWith<$Res> { + factory $ApiGroupKeyCopyWith( + ApiGroupKey value, $Res Function(ApiGroupKey) then) = + _$ApiGroupKeyCopyWithImpl<$Res, ApiGroupKey>; + @useResult + $Res call({int docUpdatedAt, Map memberKeys}); +} + +/// @nodoc +class _$ApiGroupKeyCopyWithImpl<$Res, $Val extends ApiGroupKey> + implements $ApiGroupKeyCopyWith<$Res> { + _$ApiGroupKeyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ApiGroupKey + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? docUpdatedAt = null, + Object? memberKeys = null, + }) { + return _then(_value.copyWith( + docUpdatedAt: null == docUpdatedAt + ? _value.docUpdatedAt + : docUpdatedAt // ignore: cast_nullable_to_non_nullable + as int, + memberKeys: null == memberKeys + ? _value.memberKeys + : memberKeys // ignore: cast_nullable_to_non_nullable + as Map, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ApiGroupKeyImplCopyWith<$Res> + implements $ApiGroupKeyCopyWith<$Res> { + factory _$$ApiGroupKeyImplCopyWith( + _$ApiGroupKeyImpl value, $Res Function(_$ApiGroupKeyImpl) then) = + __$$ApiGroupKeyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int docUpdatedAt, Map memberKeys}); +} + +/// @nodoc +class __$$ApiGroupKeyImplCopyWithImpl<$Res> + extends _$ApiGroupKeyCopyWithImpl<$Res, _$ApiGroupKeyImpl> + implements _$$ApiGroupKeyImplCopyWith<$Res> { + __$$ApiGroupKeyImplCopyWithImpl( + _$ApiGroupKeyImpl _value, $Res Function(_$ApiGroupKeyImpl) _then) + : super(_value, _then); + + /// Create a copy of ApiGroupKey + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? docUpdatedAt = null, + Object? memberKeys = null, + }) { + return _then(_$ApiGroupKeyImpl( + docUpdatedAt: null == docUpdatedAt + ? _value.docUpdatedAt + : docUpdatedAt // ignore: cast_nullable_to_non_nullable + as int, + memberKeys: null == memberKeys + ? _value._memberKeys + : memberKeys // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ApiGroupKeyImpl extends _ApiGroupKey { + const _$ApiGroupKeyImpl( + {required this.docUpdatedAt, + final Map memberKeys = const {}}) + : _memberKeys = memberKeys, + super._(); + + factory _$ApiGroupKeyImpl.fromJson(Map json) => + _$$ApiGroupKeyImplFromJson(json); + + @override + final int docUpdatedAt; + final Map _memberKeys; + @override + @JsonKey() + Map get memberKeys { + if (_memberKeys is EqualUnmodifiableMapView) return _memberKeys; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_memberKeys); + } + + @override + String toString() { + return 'ApiGroupKey(docUpdatedAt: $docUpdatedAt, memberKeys: $memberKeys)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ApiGroupKeyImpl && + (identical(other.docUpdatedAt, docUpdatedAt) || + other.docUpdatedAt == docUpdatedAt) && + const DeepCollectionEquality() + .equals(other._memberKeys, _memberKeys)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, docUpdatedAt, + const DeepCollectionEquality().hash(_memberKeys)); + + /// Create a copy of ApiGroupKey + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ApiGroupKeyImplCopyWith<_$ApiGroupKeyImpl> get copyWith => + __$$ApiGroupKeyImplCopyWithImpl<_$ApiGroupKeyImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ApiGroupKeyImplToJson( + this, + ); + } +} + +abstract class _ApiGroupKey extends ApiGroupKey { + const factory _ApiGroupKey( + {required final int docUpdatedAt, + final Map memberKeys}) = _$ApiGroupKeyImpl; + const _ApiGroupKey._() : super._(); + + factory _ApiGroupKey.fromJson(Map json) = + _$ApiGroupKeyImpl.fromJson; + + @override + int get docUpdatedAt; + @override + Map get memberKeys; + + /// Create a copy of ApiGroupKey + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ApiGroupKeyImplCopyWith<_$ApiGroupKeyImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ApiMemberKeyData _$ApiMemberKeyDataFromJson(Map json) { + return _ApiMemberKeyData.fromJson(json); +} + +/// @nodoc +mixin _$ApiMemberKeyData { + int get memberDeviceId => throw _privateConstructorUsedError; + int get dataUpdatedAt => throw _privateConstructorUsedError; + List get distributions => + throw _privateConstructorUsedError; + + /// Serializes this ApiMemberKeyData to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ApiMemberKeyData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ApiMemberKeyDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApiMemberKeyDataCopyWith<$Res> { + factory $ApiMemberKeyDataCopyWith( + ApiMemberKeyData value, $Res Function(ApiMemberKeyData) then) = + _$ApiMemberKeyDataCopyWithImpl<$Res, ApiMemberKeyData>; + @useResult + $Res call( + {int memberDeviceId, + int dataUpdatedAt, + List distributions}); +} + +/// @nodoc +class _$ApiMemberKeyDataCopyWithImpl<$Res, $Val extends ApiMemberKeyData> + implements $ApiMemberKeyDataCopyWith<$Res> { + _$ApiMemberKeyDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ApiMemberKeyData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? memberDeviceId = null, + Object? dataUpdatedAt = null, + Object? distributions = null, + }) { + return _then(_value.copyWith( + memberDeviceId: null == memberDeviceId + ? _value.memberDeviceId + : memberDeviceId // ignore: cast_nullable_to_non_nullable + as int, + dataUpdatedAt: null == dataUpdatedAt + ? _value.dataUpdatedAt + : dataUpdatedAt // ignore: cast_nullable_to_non_nullable + as int, + distributions: null == distributions + ? _value.distributions + : distributions // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ApiMemberKeyDataImplCopyWith<$Res> + implements $ApiMemberKeyDataCopyWith<$Res> { + factory _$$ApiMemberKeyDataImplCopyWith(_$ApiMemberKeyDataImpl value, + $Res Function(_$ApiMemberKeyDataImpl) then) = + __$$ApiMemberKeyDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int memberDeviceId, + int dataUpdatedAt, + List distributions}); +} + +/// @nodoc +class __$$ApiMemberKeyDataImplCopyWithImpl<$Res> + extends _$ApiMemberKeyDataCopyWithImpl<$Res, _$ApiMemberKeyDataImpl> + implements _$$ApiMemberKeyDataImplCopyWith<$Res> { + __$$ApiMemberKeyDataImplCopyWithImpl(_$ApiMemberKeyDataImpl _value, + $Res Function(_$ApiMemberKeyDataImpl) _then) + : super(_value, _then); + + /// Create a copy of ApiMemberKeyData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? memberDeviceId = null, + Object? dataUpdatedAt = null, + Object? distributions = null, + }) { + return _then(_$ApiMemberKeyDataImpl( + memberDeviceId: null == memberDeviceId + ? _value.memberDeviceId + : memberDeviceId // ignore: cast_nullable_to_non_nullable + as int, + dataUpdatedAt: null == dataUpdatedAt + ? _value.dataUpdatedAt + : dataUpdatedAt // ignore: cast_nullable_to_non_nullable + as int, + distributions: null == distributions + ? _value._distributions + : distributions // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ApiMemberKeyDataImpl implements _ApiMemberKeyData { + const _$ApiMemberKeyDataImpl( + {this.memberDeviceId = 0, + this.dataUpdatedAt = 0, + final List distributions = const []}) + : _distributions = distributions; + + factory _$ApiMemberKeyDataImpl.fromJson(Map json) => + _$$ApiMemberKeyDataImplFromJson(json); + + @override + @JsonKey() + final int memberDeviceId; + @override + @JsonKey() + final int dataUpdatedAt; + final List _distributions; + @override + @JsonKey() + List get distributions { + if (_distributions is EqualUnmodifiableListView) return _distributions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_distributions); + } + + @override + String toString() { + return 'ApiMemberKeyData(memberDeviceId: $memberDeviceId, dataUpdatedAt: $dataUpdatedAt, distributions: $distributions)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ApiMemberKeyDataImpl && + (identical(other.memberDeviceId, memberDeviceId) || + other.memberDeviceId == memberDeviceId) && + (identical(other.dataUpdatedAt, dataUpdatedAt) || + other.dataUpdatedAt == dataUpdatedAt) && + const DeepCollectionEquality() + .equals(other._distributions, _distributions)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, memberDeviceId, dataUpdatedAt, + const DeepCollectionEquality().hash(_distributions)); + + /// Create a copy of ApiMemberKeyData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ApiMemberKeyDataImplCopyWith<_$ApiMemberKeyDataImpl> get copyWith => + __$$ApiMemberKeyDataImplCopyWithImpl<_$ApiMemberKeyDataImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ApiMemberKeyDataImplToJson( + this, + ); + } +} + +abstract class _ApiMemberKeyData implements ApiMemberKeyData { + const factory _ApiMemberKeyData( + {final int memberDeviceId, + final int dataUpdatedAt, + final List distributions}) = + _$ApiMemberKeyDataImpl; + + factory _ApiMemberKeyData.fromJson(Map json) = + _$ApiMemberKeyDataImpl.fromJson; + + @override + int get memberDeviceId; + @override + int get dataUpdatedAt; + @override + List get distributions; + + /// Create a copy of ApiMemberKeyData + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ApiMemberKeyDataImplCopyWith<_$ApiMemberKeyDataImpl> get copyWith => + throw _privateConstructorUsedError; +} + +EncryptedDistribution _$EncryptedDistributionFromJson( + Map json) { + return _EncryptedDistribution.fromJson(json); +} + +/// @nodoc +mixin _$EncryptedDistribution { + String get recipientId => throw _privateConstructorUsedError; + @BlobConverter() + Blob get ephemeralPub => throw _privateConstructorUsedError; + @BlobConverter() + Blob get iv => throw _privateConstructorUsedError; + @BlobConverter() + Blob get ciphertext => throw _privateConstructorUsedError; + + /// Serializes this EncryptedDistribution to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of EncryptedDistribution + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $EncryptedDistributionCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $EncryptedDistributionCopyWith<$Res> { + factory $EncryptedDistributionCopyWith(EncryptedDistribution value, + $Res Function(EncryptedDistribution) then) = + _$EncryptedDistributionCopyWithImpl<$Res, EncryptedDistribution>; + @useResult + $Res call( + {String recipientId, + @BlobConverter() Blob ephemeralPub, + @BlobConverter() Blob iv, + @BlobConverter() Blob ciphertext}); +} + +/// @nodoc +class _$EncryptedDistributionCopyWithImpl<$Res, + $Val extends EncryptedDistribution> + implements $EncryptedDistributionCopyWith<$Res> { + _$EncryptedDistributionCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of EncryptedDistribution + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recipientId = null, + Object? ephemeralPub = null, + Object? iv = null, + Object? ciphertext = null, + }) { + return _then(_value.copyWith( + recipientId: null == recipientId + ? _value.recipientId + : recipientId // ignore: cast_nullable_to_non_nullable + as String, + ephemeralPub: null == ephemeralPub + ? _value.ephemeralPub + : ephemeralPub // ignore: cast_nullable_to_non_nullable + as Blob, + iv: null == iv + ? _value.iv + : iv // ignore: cast_nullable_to_non_nullable + as Blob, + ciphertext: null == ciphertext + ? _value.ciphertext + : ciphertext // ignore: cast_nullable_to_non_nullable + as Blob, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$EncryptedDistributionImplCopyWith<$Res> + implements $EncryptedDistributionCopyWith<$Res> { + factory _$$EncryptedDistributionImplCopyWith( + _$EncryptedDistributionImpl value, + $Res Function(_$EncryptedDistributionImpl) then) = + __$$EncryptedDistributionImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String recipientId, + @BlobConverter() Blob ephemeralPub, + @BlobConverter() Blob iv, + @BlobConverter() Blob ciphertext}); +} + +/// @nodoc +class __$$EncryptedDistributionImplCopyWithImpl<$Res> + extends _$EncryptedDistributionCopyWithImpl<$Res, + _$EncryptedDistributionImpl> + implements _$$EncryptedDistributionImplCopyWith<$Res> { + __$$EncryptedDistributionImplCopyWithImpl(_$EncryptedDistributionImpl _value, + $Res Function(_$EncryptedDistributionImpl) _then) + : super(_value, _then); + + /// Create a copy of EncryptedDistribution + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? recipientId = null, + Object? ephemeralPub = null, + Object? iv = null, + Object? ciphertext = null, + }) { + return _then(_$EncryptedDistributionImpl( + recipientId: null == recipientId + ? _value.recipientId + : recipientId // ignore: cast_nullable_to_non_nullable + as String, + ephemeralPub: null == ephemeralPub + ? _value.ephemeralPub + : ephemeralPub // ignore: cast_nullable_to_non_nullable + as Blob, + iv: null == iv + ? _value.iv + : iv // ignore: cast_nullable_to_non_nullable + as Blob, + ciphertext: null == ciphertext + ? _value.ciphertext + : ciphertext // ignore: cast_nullable_to_non_nullable + as Blob, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$EncryptedDistributionImpl extends _EncryptedDistribution { + const _$EncryptedDistributionImpl( + {this.recipientId = "", + @BlobConverter() required this.ephemeralPub, + @BlobConverter() required this.iv, + @BlobConverter() required this.ciphertext}) + : super._(); + + factory _$EncryptedDistributionImpl.fromJson(Map json) => + _$$EncryptedDistributionImplFromJson(json); + + @override + @JsonKey() + final String recipientId; + @override + @BlobConverter() + final Blob ephemeralPub; + @override + @BlobConverter() + final Blob iv; + @override + @BlobConverter() + final Blob ciphertext; + + @override + String toString() { + return 'EncryptedDistribution(recipientId: $recipientId, ephemeralPub: $ephemeralPub, iv: $iv, ciphertext: $ciphertext)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$EncryptedDistributionImpl && + (identical(other.recipientId, recipientId) || + other.recipientId == recipientId) && + (identical(other.ephemeralPub, ephemeralPub) || + other.ephemeralPub == ephemeralPub) && + (identical(other.iv, iv) || other.iv == iv) && + (identical(other.ciphertext, ciphertext) || + other.ciphertext == ciphertext)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, recipientId, ephemeralPub, iv, ciphertext); + + /// Create a copy of EncryptedDistribution + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$EncryptedDistributionImplCopyWith<_$EncryptedDistributionImpl> + get copyWith => __$$EncryptedDistributionImplCopyWithImpl< + _$EncryptedDistributionImpl>(this, _$identity); + + @override + Map toJson() { + return _$$EncryptedDistributionImplToJson( + this, + ); + } +} + +abstract class _EncryptedDistribution extends EncryptedDistribution { + const factory _EncryptedDistribution( + {final String recipientId, + @BlobConverter() required final Blob ephemeralPub, + @BlobConverter() required final Blob iv, + @BlobConverter() required final Blob ciphertext}) = + _$EncryptedDistributionImpl; + const _EncryptedDistribution._() : super._(); + + factory _EncryptedDistribution.fromJson(Map json) = + _$EncryptedDistributionImpl.fromJson; + + @override + String get recipientId; + @override + @BlobConverter() + Blob get ephemeralPub; + @override + @BlobConverter() + Blob get iv; + @override + @BlobConverter() + Blob get ciphertext; + + /// Create a copy of EncryptedDistribution + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$EncryptedDistributionImplCopyWith<_$EncryptedDistributionImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/data/lib/api/space/api_group_key_model.g.dart b/data/lib/api/space/api_group_key_model.g.dart new file mode 100644 index 00000000..2c8d7e99 --- /dev/null +++ b/data/lib/api/space/api_group_key_model.g.dart @@ -0,0 +1,63 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_group_key_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ApiGroupKeyImpl _$$ApiGroupKeyImplFromJson(Map json) => + _$ApiGroupKeyImpl( + docUpdatedAt: (json['docUpdatedAt'] as num).toInt(), + memberKeys: (json['memberKeys'] as Map?)?.map( + (k, e) => MapEntry( + k, ApiMemberKeyData.fromJson(e as Map)), + ) ?? + const {}, + ); + +Map _$$ApiGroupKeyImplToJson(_$ApiGroupKeyImpl instance) => + { + 'docUpdatedAt': instance.docUpdatedAt, + 'memberKeys': instance.memberKeys.map((k, e) => MapEntry(k, e.toJson())), + }; + +_$ApiMemberKeyDataImpl _$$ApiMemberKeyDataImplFromJson( + Map json) => + _$ApiMemberKeyDataImpl( + memberDeviceId: (json['memberDeviceId'] as num?)?.toInt() ?? 0, + dataUpdatedAt: (json['dataUpdatedAt'] as num?)?.toInt() ?? 0, + distributions: (json['distributions'] as List?) + ?.map((e) => + EncryptedDistribution.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$$ApiMemberKeyDataImplToJson( + _$ApiMemberKeyDataImpl instance) => + { + 'memberDeviceId': instance.memberDeviceId, + 'dataUpdatedAt': instance.dataUpdatedAt, + 'distributions': instance.distributions.map((e) => e.toJson()).toList(), + }; + +_$EncryptedDistributionImpl _$$EncryptedDistributionImplFromJson( + Map json) => + _$EncryptedDistributionImpl( + recipientId: json['recipientId'] as String? ?? "", + ephemeralPub: const BlobConverter() + .fromJson(json['ephemeralPub'] as Map?), + iv: const BlobConverter().fromJson(json['iv'] as Map?), + ciphertext: const BlobConverter() + .fromJson(json['ciphertext'] as Map?), + ); + +Map _$$EncryptedDistributionImplToJson( + _$EncryptedDistributionImpl instance) => + { + 'recipientId': instance.recipientId, + 'ephemeralPub': const BlobConverter().toJson(instance.ephemeralPub), + 'iv': const BlobConverter().toJson(instance.iv), + 'ciphertext': const BlobConverter().toJson(instance.ciphertext), + }; diff --git a/data/lib/api/space/api_sender_key_record.dart b/data/lib/api/space/api_sender_key_record.dart new file mode 100644 index 00000000..8c5cda1e --- /dev/null +++ b/data/lib/api/space/api_sender_key_record.dart @@ -0,0 +1,24 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../converter/blob_converter.dart'; + +part 'api_sender_key_record.freezed.dart'; +part 'api_sender_key_record.g.dart'; + +@freezed +class ApiSenderKeyRecord with _$ApiSenderKeyRecord { + const ApiSenderKeyRecord._(); + + const factory ApiSenderKeyRecord({ + required String id, + @Default(0) int deviceId, + @Default('') String address, + @Default('') String distributionId, + required int created_at, + @BlobConverter() required Blob record, + }) = _ApiSenderKeyRecord; + + factory ApiSenderKeyRecord.fromJson(Map data) => + _$ApiSenderKeyRecordFromJson(data); +} diff --git a/data/lib/api/space/api_sender_key_record.freezed.dart b/data/lib/api/space/api_sender_key_record.freezed.dart new file mode 100644 index 00000000..9bd534e5 --- /dev/null +++ b/data/lib/api/space/api_sender_key_record.freezed.dart @@ -0,0 +1,281 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'api_sender_key_record.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ApiSenderKeyRecord _$ApiSenderKeyRecordFromJson(Map json) { + return _ApiSenderKeyRecord.fromJson(json); +} + +/// @nodoc +mixin _$ApiSenderKeyRecord { + String get id => throw _privateConstructorUsedError; + int get deviceId => throw _privateConstructorUsedError; + String get address => throw _privateConstructorUsedError; + String get distributionId => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; + @BlobConverter() + Blob get record => throw _privateConstructorUsedError; + + /// Serializes this ApiSenderKeyRecord to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ApiSenderKeyRecordCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApiSenderKeyRecordCopyWith<$Res> { + factory $ApiSenderKeyRecordCopyWith( + ApiSenderKeyRecord value, $Res Function(ApiSenderKeyRecord) then) = + _$ApiSenderKeyRecordCopyWithImpl<$Res, ApiSenderKeyRecord>; + @useResult + $Res call( + {String id, + int deviceId, + String address, + String distributionId, + int created_at, + @BlobConverter() Blob record}); +} + +/// @nodoc +class _$ApiSenderKeyRecordCopyWithImpl<$Res, $Val extends ApiSenderKeyRecord> + implements $ApiSenderKeyRecordCopyWith<$Res> { + _$ApiSenderKeyRecordCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? deviceId = null, + Object? address = null, + Object? distributionId = null, + Object? created_at = null, + Object? record = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + deviceId: null == deviceId + ? _value.deviceId + : deviceId // ignore: cast_nullable_to_non_nullable + as int, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + distributionId: null == distributionId + ? _value.distributionId + : distributionId // ignore: cast_nullable_to_non_nullable + as String, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + record: null == record + ? _value.record + : record // ignore: cast_nullable_to_non_nullable + as Blob, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ApiSenderKeyRecordImplCopyWith<$Res> + implements $ApiSenderKeyRecordCopyWith<$Res> { + factory _$$ApiSenderKeyRecordImplCopyWith(_$ApiSenderKeyRecordImpl value, + $Res Function(_$ApiSenderKeyRecordImpl) then) = + __$$ApiSenderKeyRecordImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + int deviceId, + String address, + String distributionId, + int created_at, + @BlobConverter() Blob record}); +} + +/// @nodoc +class __$$ApiSenderKeyRecordImplCopyWithImpl<$Res> + extends _$ApiSenderKeyRecordCopyWithImpl<$Res, _$ApiSenderKeyRecordImpl> + implements _$$ApiSenderKeyRecordImplCopyWith<$Res> { + __$$ApiSenderKeyRecordImplCopyWithImpl(_$ApiSenderKeyRecordImpl _value, + $Res Function(_$ApiSenderKeyRecordImpl) _then) + : super(_value, _then); + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? deviceId = null, + Object? address = null, + Object? distributionId = null, + Object? created_at = null, + Object? record = null, + }) { + return _then(_$ApiSenderKeyRecordImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + deviceId: null == deviceId + ? _value.deviceId + : deviceId // ignore: cast_nullable_to_non_nullable + as int, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + distributionId: null == distributionId + ? _value.distributionId + : distributionId // ignore: cast_nullable_to_non_nullable + as String, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + record: null == record + ? _value.record + : record // ignore: cast_nullable_to_non_nullable + as Blob, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { + const _$ApiSenderKeyRecordImpl( + {required this.id, + this.deviceId = 0, + this.address = '', + this.distributionId = '', + required this.created_at, + @BlobConverter() required this.record}) + : super._(); + + factory _$ApiSenderKeyRecordImpl.fromJson(Map json) => + _$$ApiSenderKeyRecordImplFromJson(json); + + @override + final String id; + @override + @JsonKey() + final int deviceId; + @override + @JsonKey() + final String address; + @override + @JsonKey() + final String distributionId; + @override + final int created_at; + @override + @BlobConverter() + final Blob record; + + @override + String toString() { + return 'ApiSenderKeyRecord(id: $id, deviceId: $deviceId, address: $address, distributionId: $distributionId, created_at: $created_at, record: $record)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ApiSenderKeyRecordImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.deviceId, deviceId) || + other.deviceId == deviceId) && + (identical(other.address, address) || other.address == address) && + (identical(other.distributionId, distributionId) || + other.distributionId == distributionId) && + (identical(other.created_at, created_at) || + other.created_at == created_at) && + (identical(other.record, record) || other.record == record)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, id, deviceId, address, distributionId, created_at, record); + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ApiSenderKeyRecordImplCopyWith<_$ApiSenderKeyRecordImpl> get copyWith => + __$$ApiSenderKeyRecordImplCopyWithImpl<_$ApiSenderKeyRecordImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ApiSenderKeyRecordImplToJson( + this, + ); + } +} + +abstract class _ApiSenderKeyRecord extends ApiSenderKeyRecord { + const factory _ApiSenderKeyRecord( + {required final String id, + final int deviceId, + final String address, + final String distributionId, + required final int created_at, + @BlobConverter() required final Blob record}) = _$ApiSenderKeyRecordImpl; + const _ApiSenderKeyRecord._() : super._(); + + factory _ApiSenderKeyRecord.fromJson(Map json) = + _$ApiSenderKeyRecordImpl.fromJson; + + @override + String get id; + @override + int get deviceId; + @override + String get address; + @override + String get distributionId; + @override + int get created_at; + @override + @BlobConverter() + Blob get record; + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ApiSenderKeyRecordImplCopyWith<_$ApiSenderKeyRecordImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/data/lib/api/space/api_sender_key_record.g.dart b/data/lib/api/space/api_sender_key_record.g.dart new file mode 100644 index 00000000..14040ccd --- /dev/null +++ b/data/lib/api/space/api_sender_key_record.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_sender_key_record.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( + Map json) => + _$ApiSenderKeyRecordImpl( + id: json['id'] as String, + deviceId: (json['deviceId'] as num?)?.toInt() ?? 0, + address: json['address'] as String? ?? '', + distributionId: json['distributionId'] as String? ?? '', + created_at: (json['created_at'] as num).toInt(), + record: const BlobConverter() + .fromJson(json['record'] as Map?), + ); + +Map _$$ApiSenderKeyRecordImplToJson( + _$ApiSenderKeyRecordImpl instance) => + { + 'id': instance.id, + 'deviceId': instance.deviceId, + 'address': instance.address, + 'distributionId': instance.distributionId, + 'created_at': instance.created_at, + 'record': const BlobConverter().toJson(instance.record), + }; diff --git a/data/lib/api/space/space_models.freezed.dart b/data/lib/api/space/space_models.freezed.dart index 0b2670d9..96e7cf0d 100644 --- a/data/lib/api/space/space_models.freezed.dart +++ b/data/lib/api/space/space_models.freezed.dart @@ -25,8 +25,12 @@ mixin _$ApiSpace { String get name => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; + /// Serializes this ApiSpace to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiSpace + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiSpaceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -49,6 +53,8 @@ class _$ApiSpaceCopyWithImpl<$Res, $Val extends ApiSpace> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiSpace + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -97,6 +103,8 @@ class __$$ApiSpaceImplCopyWithImpl<$Res> _$ApiSpaceImpl _value, $Res Function(_$ApiSpaceImpl) _then) : super(_value, _then); + /// Create a copy of ApiSpace + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -166,11 +174,13 @@ class _$ApiSpaceImpl extends _ApiSpace { other.created_at == created_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, admin_id, name, created_at); - @JsonKey(ignore: true) + /// Create a copy of ApiSpace + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiSpaceImplCopyWith<_$ApiSpaceImpl> get copyWith => @@ -203,8 +213,11 @@ abstract class _ApiSpace extends ApiSpace { String get name; @override int? get created_at; + + /// Create a copy of ApiSpace + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiSpaceImplCopyWith<_$ApiSpaceImpl> get copyWith => throw _privateConstructorUsedError; } @@ -222,8 +235,12 @@ mixin _$ApiSpaceMember { bool get location_enabled => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; + /// Serializes this ApiSpaceMember to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiSpaceMember + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiSpaceMemberCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -253,6 +270,8 @@ class _$ApiSpaceMemberCopyWithImpl<$Res, $Val extends ApiSpaceMember> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiSpaceMember + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -317,6 +336,8 @@ class __$$ApiSpaceMemberImplCopyWithImpl<$Res> _$ApiSpaceMemberImpl _value, $Res Function(_$ApiSpaceMemberImpl) _then) : super(_value, _then); + /// Create a copy of ApiSpaceMember + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -405,12 +426,14 @@ class _$ApiSpaceMemberImpl extends _ApiSpaceMember { other.created_at == created_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, id, space_id, user_id, role, location_enabled, created_at); - @JsonKey(ignore: true) + /// Create a copy of ApiSpaceMember + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiSpaceMemberImplCopyWith<_$ApiSpaceMemberImpl> get copyWith => @@ -450,8 +473,11 @@ abstract class _ApiSpaceMember extends ApiSpaceMember { bool get location_enabled; @override int? get created_at; + + /// Create a copy of ApiSpaceMember + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiSpaceMemberImplCopyWith<_$ApiSpaceMemberImpl> get copyWith => throw _privateConstructorUsedError; } @@ -467,8 +493,12 @@ mixin _$ApiSpaceInvitation { String get code => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; + /// Serializes this ApiSpaceInvitation to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ApiSpaceInvitation + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ApiSpaceInvitationCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -492,6 +522,8 @@ class _$ApiSpaceInvitationCopyWithImpl<$Res, $Val extends ApiSpaceInvitation> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ApiSpaceInvitation + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -540,6 +572,8 @@ class __$$ApiSpaceInvitationImplCopyWithImpl<$Res> $Res Function(_$ApiSpaceInvitationImpl) _then) : super(_value, _then); + /// Create a copy of ApiSpaceInvitation + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -609,11 +643,13 @@ class _$ApiSpaceInvitationImpl extends _ApiSpaceInvitation { other.created_at == created_at)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, space_id, code, created_at); - @JsonKey(ignore: true) + /// Create a copy of ApiSpaceInvitation + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ApiSpaceInvitationImplCopyWith<_$ApiSpaceInvitationImpl> get copyWith => @@ -647,8 +683,11 @@ abstract class _ApiSpaceInvitation extends ApiSpaceInvitation { String get code; @override int? get created_at; + + /// Create a copy of ApiSpaceInvitation + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ApiSpaceInvitationImplCopyWith<_$ApiSpaceInvitationImpl> get copyWith => throw _privateConstructorUsedError; } @@ -662,8 +701,12 @@ mixin _$SpaceInfo { ApiSpace get space => throw _privateConstructorUsedError; List get members => throw _privateConstructorUsedError; + /// Serializes this SpaceInfo to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpaceInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpaceInfoCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -688,6 +731,8 @@ class _$SpaceInfoCopyWithImpl<$Res, $Val extends SpaceInfo> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SpaceInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -706,6 +751,8 @@ class _$SpaceInfoCopyWithImpl<$Res, $Val extends SpaceInfo> ) as $Val); } + /// Create a copy of SpaceInfo + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $ApiSpaceCopyWith<$Res> get space { @@ -737,6 +784,8 @@ class __$$SpaceInfoImplCopyWithImpl<$Res> _$SpaceInfoImpl _value, $Res Function(_$SpaceInfoImpl) _then) : super(_value, _then); + /// Create a copy of SpaceInfo + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -791,12 +840,14 @@ class _$SpaceInfoImpl extends _SpaceInfo { const DeepCollectionEquality().equals(other._members, _members)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, space, const DeepCollectionEquality().hash(_members)); - @JsonKey(ignore: true) + /// Create a copy of SpaceInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpaceInfoImplCopyWith<_$SpaceInfoImpl> get copyWith => @@ -823,8 +874,11 @@ abstract class _SpaceInfo extends SpaceInfo { ApiSpace get space; @override List get members; + + /// Create a copy of SpaceInfo + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpaceInfoImplCopyWith<_$SpaceInfoImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/api/subscription/subscription_models.freezed.dart b/data/lib/api/subscription/subscription_models.freezed.dart index 91042844..ea42d9bb 100644 --- a/data/lib/api/subscription/subscription_models.freezed.dart +++ b/data/lib/api/subscription/subscription_models.freezed.dart @@ -21,7 +21,9 @@ mixin _$SubscriptionPlan { String get planDetail => throw _privateConstructorUsedError; String get planInfo => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of SubscriptionPlan + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SubscriptionPlanCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -45,6 +47,8 @@ class _$SubscriptionPlanCopyWithImpl<$Res, $Val extends SubscriptionPlan> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SubscriptionPlan + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -93,6 +97,8 @@ class __$$SubscriptionPlanImplCopyWithImpl<$Res> $Res Function(_$SubscriptionPlanImpl) _then) : super(_value, _then); + /// Create a copy of SubscriptionPlan + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -161,7 +167,9 @@ class _$SubscriptionPlanImpl implements _SubscriptionPlan { @override int get hashCode => Object.hash(runtimeType, id, name, planDetail, planInfo); - @JsonKey(ignore: true) + /// Create a copy of SubscriptionPlan + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SubscriptionPlanImplCopyWith<_$SubscriptionPlanImpl> get copyWith => @@ -184,8 +192,11 @@ abstract class _SubscriptionPlan implements SubscriptionPlan { String get planDetail; @override String get planInfo; + + /// Create a copy of SubscriptionPlan + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SubscriptionPlanImplCopyWith<_$SubscriptionPlanImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/data/lib/converter/blob_converter.dart b/data/lib/converter/blob_converter.dart new file mode 100644 index 00000000..6aeac1c6 --- /dev/null +++ b/data/lib/converter/blob_converter.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +class BlobConverter implements JsonConverter?> { + const BlobConverter(); + + @override + Blob fromJson(Map? json) { + if (json == null || !json.containsKey('_byteString')) return Blob(Uint8List(0)); + final byteString = json['_byteString'] as String; + final bytes = base64Decode(byteString); + return Blob(Uint8List.fromList(bytes)); + } + + @override + Map? toJson(Blob? blob) { + if (blob == null) return null; + return { + '_byteString': base64Encode(blob.bytes), + }; + } + +} \ No newline at end of file diff --git a/data/lib/domain/journey_lat_lng_entension.dart b/data/lib/domain/journey_lat_lng_entension.dart deleted file mode 100644 index 598057c8..00000000 --- a/data/lib/domain/journey_lat_lng_entension.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:google_maps_flutter/google_maps_flutter.dart'; - -import '../api/location/journey/journey.dart'; - -extension JourneyRouteLatLngExtension on JourneyRoute { - LatLng toLatLng() { - return LatLng(latitude, longitude); - } -} \ No newline at end of file diff --git a/data/lib/domain/location_data_extension.dart b/data/lib/domain/location_data_extension.dart deleted file mode 100644 index f60e1c1b..00000000 --- a/data/lib/domain/location_data_extension.dart +++ /dev/null @@ -1,17 +0,0 @@ -import '../api/location/journey/journey.dart'; -import '../api/location/location.dart'; - -extension LocationDataExtension on LocationData { - JourneyRoute toJourneyRoute() { - return JourneyRoute(latitude: latitude, longitude: longitude); - } - - ApiLocationJourney toLocationJourney(String userId, String journeyId) { - return ApiLocationJourney( - id: journeyId, - user_id: userId, - from_latitude: latitude, - from_longitude: longitude, - ); - } -} \ No newline at end of file diff --git a/data/lib/repository/journey_generator.dart b/data/lib/repository/journey_generator.dart index 7fc88663..5686e5e5 100644 --- a/data/lib/repository/journey_generator.dart +++ b/data/lib/repository/journey_generator.dart @@ -26,7 +26,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs from_longitude: newLocation.longitude, type: JOURNEY_TYPE_STEADY, created_at: DateTime.now().millisecondsSinceEpoch, - update_at: DateTime.now().millisecondsSinceEpoch, + updated_at: DateTime.now().millisecondsSinceEpoch, ) ); } @@ -45,7 +45,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs } final timeDifference = (newLocation.timestamp.millisecondsSinceEpoch) - - (lastKnownJourney.update_at ?? 0); + lastKnownJourney.updated_at; bool dayChanged = _isDayChanged(newLocation, lastKnownJourney); @@ -53,7 +53,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs final updateJourney = lastKnownJourney.copyWith( from_latitude: newLocation.latitude, from_longitude: newLocation.longitude, - update_at: DateTime.now().millisecondsSinceEpoch, + updated_at: DateTime.now().millisecondsSinceEpoch, ); return (updatedJourney: updateJourney, newJourney: null); } @@ -77,7 +77,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs final updatedJourney = lastKnownJourney.copyWith( from_latitude: newLocation.latitude, from_longitude: newLocation.longitude, - update_at: DateTime.now().millisecondsSinceEpoch, + updated_at: DateTime.now().millisecondsSinceEpoch, ); // create new moving journey @@ -91,7 +91,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs route_distance: distance, route_duration: timeDifference, created_at: DateTime.now().millisecondsSinceEpoch, - update_at: DateTime.now().millisecondsSinceEpoch, + updated_at: DateTime.now().millisecondsSinceEpoch, ); return (updatedJourney: updatedJourney, newJourney: newJourney); @@ -100,7 +100,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs final updateJourney = lastKnownJourney.copyWith( from_latitude: newLocation.latitude, from_longitude: newLocation.longitude, - update_at: DateTime.now().millisecondsSinceEpoch, + updated_at: DateTime.now().millisecondsSinceEpoch, ); return (updatedJourney: updateJourney, newJourney: null); } @@ -112,8 +112,8 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs to_latitude: newLocation.latitude, to_longitude: newLocation.longitude, route_distance: distance, - route_duration: (lastKnownJourney.update_at ?? 0) - - (lastKnownJourney.created_at ?? 0), + route_duration: + lastKnownJourney.updated_at - lastKnownJourney.created_at, routes: lastKnownJourney.routes + [ JourneyRoute( @@ -129,8 +129,8 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs from_latitude: newLocation.latitude, from_longitude: newLocation.longitude, type: JOURNEY_TYPE_STEADY, - created_at: lastKnownJourney.update_at ?? DateTime.now().millisecondsSinceEpoch, - update_at: DateTime.now().millisecondsSinceEpoch, + created_at: lastKnownJourney.updated_at, + updated_at: DateTime.now().millisecondsSinceEpoch, ); return (updatedJourney: updatedJourney, newJourney: newJourney); } else if (distance > MIN_DISTANCE_FOR_MOVING && @@ -140,8 +140,8 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs to_latitude: newLocation.latitude, to_longitude: newLocation.longitude, route_distance: distance + (lastKnownJourney.route_distance ?? 0), - route_duration: (lastKnownJourney.update_at ?? 0) - - (lastKnownJourney.created_at ?? 0), + route_duration: + lastKnownJourney.updated_at - lastKnownJourney.created_at, routes: lastKnownJourney.routes + [ JourneyRoute( @@ -149,7 +149,7 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs longitude: newLocation.longitude, ) ], - update_at: DateTime.now().millisecondsSinceEpoch, + updated_at: DateTime.now().millisecondsSinceEpoch, ); return (updatedJourney: updatedJourney, newJourney: null); @@ -161,8 +161,8 @@ const MIN_UPDATE_INTERVAL_MINUTE = 30000; // 30 secs bool _isDayChanged( LocationData? extractedLocation, ApiLocationJourney lastKnownJourney) { - DateTime lastKnownDate = DateTime.fromMillisecondsSinceEpoch( - lastKnownJourney.update_at ?? DateTime.now().millisecondsSinceEpoch); + DateTime lastKnownDate = + DateTime.fromMillisecondsSinceEpoch(lastKnownJourney.updated_at); int lastKnownDay = lastKnownDate.day; DateTime currentDate = extractedLocation != null diff --git a/data/lib/service/auth_service.dart b/data/lib/service/auth_service.dart index cad27c8f..d943fa8e 100644 --- a/data/lib/service/auth_service.dart +++ b/data/lib/service/auth_service.dart @@ -1,10 +1,15 @@ // ignore_for_file: constant_identifier_names +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_functions/cloud_functions.dart'; import 'package:data/api/auth/api_user_service.dart'; import 'package:data/api/auth/auth_models.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; - +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import '../utils/private_key_helper.dart'; import '../log/logger.dart'; import '../storage/app_preferences.dart'; @@ -14,16 +19,18 @@ final authServiceProvider = Provider((ref) => AuthService( ref.read(currentUserPod), ref.read(apiUserServiceProvider), ref.read(currentUserJsonPod.notifier), - ref.read(currentUserSessionJsonPod.notifier))); + ref.read(currentUserSessionJsonPod.notifier), + ref.read(userPassKeyPod.notifier))); class AuthService { - final ApiUser? _currentUser; + ApiUser? _currentUser; final ApiUserService userService; final StateController userJsonNotifier; final StateController userSessionJsonNotifier; + final StateController userPassKeyNotifier; AuthService(this._currentUser, this.userService, this.userJsonNotifier, - this.userSessionJsonNotifier); + this.userSessionJsonNotifier, this.userPassKeyNotifier); ApiUser? get currentUser => _currentUser; @@ -46,11 +53,14 @@ class AuthService { profileImage: profileImg, authType: authType, ); - userJsonNotifier.state = (data['user'] as ApiUser).toJsonString(); + _currentUser = data['user'] as ApiUser; + userJsonNotifier.state = _currentUser!.toJsonString(); userSessionJsonNotifier.state = (data['session'] as ApiSession).toJsonString(); - return data['isNewUser'] as bool; + final isNewUser = data['isNewUser'] as bool; + if (isNewUser) generateAndSaveUserKeys("1111"); + return isNewUser; } Future updateCurrentUser(ApiUser user) async { @@ -91,4 +101,45 @@ class AuthService { onStatusChecked(user); }); } + + Future generateAndSaveUserKeys(String passKey) async { + final user = currentUser; + if (user == null) { + throw Exception("No user logged in"); + } + + final updatedUser = await _generateAndSaveUserKeys(user, passKey); + userJsonNotifier.state = updatedUser.toJsonString(); + } + + Future _generateAndSaveUserKeys(ApiUser user, String passKey) async { + final identityKeyPair = generateIdentityKeyPair(); + final salt = Uint8List(16) + ..setAll(0, List.generate(16, (_) => Random().nextInt(256))); + final encryptedPrivateKey = await encryptPrivateKey( + identityKeyPair.getPrivateKey().serialize(), + passKey, + salt, + ); + + final publicKey = + Blob(identityKeyPair.getPublicKey().publicKey.serialize()); + final privateKey = Blob(encryptedPrivateKey); + final saltBlob = Blob(salt); + + // Store passkey in preferences + userPassKeyNotifier.state = passKey; + await userService.updateKeys( + user.id, + publicKey, + privateKey, + saltBlob, + ); + + return user.copyWith( + identity_key_public: publicKey, + identity_key_private: privateKey, + identity_key_salt: saltBlob, + ); + } } diff --git a/data/lib/storage/app_preferences.dart b/data/lib/storage/app_preferences.dart index 328440eb..6b0d8604 100644 --- a/data/lib/storage/app_preferences.dart +++ b/data/lib/storage/app_preferences.dart @@ -57,4 +57,9 @@ final currentSpaceId = createPrefProvider( final lastBatteryDialogPod = createPrefProvider( prefKey: 'show_battery_dialog', defaultValue: null, -); \ No newline at end of file +); + +final userPassKeyPod = createPrefProvider( + prefKey: "user_passkey", + defaultValue: null, +); diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart new file mode 100644 index 00000000..eb940306 --- /dev/null +++ b/data/lib/utils/private_key_helper.dart @@ -0,0 +1,59 @@ +// ignore_for_file: constant_identifier_names + +import 'package:cryptography/cryptography.dart'; +import 'dart:typed_data'; + +class EncryptionException implements Exception { + final String message; + final dynamic cause; + + EncryptionException(this.message, [this.cause]); +} + +const int KEY_SIZE = 256; // bits +const int ITERATION_COUNT = 100000; +const int GCM_IV_SIZE = 12; // bytes +const int GCM_TAG_SIZE = 128; // bits + +/// Derives a SecretKey from the user's passkey/PIN using PBKDF2. +Future _deriveKeyFromPasskey(String passkey, Uint8List salt) async { + try { + final pbkdf2 = Pbkdf2( + macAlgorithm: Hmac.sha256(), + iterations: ITERATION_COUNT, + bits: KEY_SIZE, // 256 bits = 32 bytes output + ); + + final newSecretKey = await pbkdf2.deriveKeyFromPassword( + password: passkey, + nonce: salt, + ); + return newSecretKey; + } catch (e) { + throw EncryptionException('Key derivation failed', e); + } +} + +// Encrypt data using AES-GCM with the provided key. Returns the IV prepended to the ciphertext. +Future _encryptData(Uint8List data, SecretKey key) async { + try { + final gcm = AesGcm.with256bits(); + final iv = Uint8List(GCM_IV_SIZE) + ..setAll(0, List.generate(GCM_IV_SIZE, (i) => (i + 1) % 256)); + final secretBox = await gcm.encrypt(data, secretKey: key, nonce: iv); + return Uint8List.fromList(iv + secretBox.cipherText); + } catch (e) { + throw EncryptionException("Encryption failed", e); + } +} + +/// Encrypts the private key using the user's passkey/PIN. +/// Retrieves or generates the salt and stores it. +Future encryptPrivateKey( + Uint8List privateKey, String passkey, Uint8List salt) async { + if (salt.isEmpty) { + throw EncryptionException('Salt is empty'); + } + final key = await _deriveKeyFromPasskey(passkey, salt); + return await _encryptData(privateKey, key); +} diff --git a/data/pubspec.yaml b/data/pubspec.yaml index 6a1f7827..0034a4fe 100644 --- a/data/pubspec.yaml +++ b/data/pubspec.yaml @@ -33,6 +33,8 @@ dependencies: google_maps_flutter: ^2.2.8 connectivity_plus: ^6.0.5 battery_plus: ^6.2.0 + libsignal_protocol_dart: ^0.7.1 + cryptography_flutter: ^2.3.2 dev_dependencies: flutter_test: From 85c12c05a0f5bde22fa7ffc5596ba5f7e3404cb0 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Thu, 9 Jan 2025 19:14:45 +0530 Subject: [PATCH 02/19] Add buffer sender keystore and group distribution keys --- data/lib/api/space/api_group_key_model.dart | 32 +- .../space/api_group_key_model.freezed.dart | 338 ++++++++++++++++-- data/lib/api/space/api_group_key_model.g.dart | 31 +- data/lib/api/space/api_space_service.dart | 40 ++- data/lib/api/space/space_models.dart | 2 + data/lib/api/space/space_models.freezed.dart | 30 +- data/lib/api/space/space_models.g.dart | 10 + data/lib/service/space_service.dart | 32 +- data/lib/storage/app_preferences.dart | 11 + data/lib/utils/buffered_sender_keystore.dart | 161 +++++++++ .../lib/utils/distribution_key_generator.dart | 58 +++ .../utils/ephemeral_distribution_helper.dart | 90 +++++ 12 files changed, 779 insertions(+), 56 deletions(-) create mode 100644 data/lib/utils/buffered_sender_keystore.dart create mode 100644 data/lib/utils/distribution_key_generator.dart create mode 100644 data/lib/utils/ephemeral_distribution_helper.dart diff --git a/data/lib/api/space/api_group_key_model.dart b/data/lib/api/space/api_group_key_model.dart index 576a1e01..0fdae8fc 100644 --- a/data/lib/api/space/api_group_key_model.dart +++ b/data/lib/api/space/api_group_key_model.dart @@ -32,8 +32,8 @@ class ApiGroupKey with _$ApiGroupKey { @freezed class ApiMemberKeyData with _$ApiMemberKeyData { const factory ApiMemberKeyData({ - @Default(0)int memberDeviceId, - @Default(0) int dataUpdatedAt, + @Default(0)int member_device_id, + @Default(0) int data_updated_at, @Default([]) List distributions, }) = _ApiMemberKeyData; @@ -70,4 +70,30 @@ class EncryptedDistribution with _$EncryptedDistribution { "Invalid size for ciphertext: maximum allowed size is 64 KB, got ${ciphertext.bytes.length} bytes."); } } -} \ No newline at end of file +} + +@freezed +class ApiSenderKeyRecord with _$ApiSenderKeyRecord { + const ApiSenderKeyRecord._(); + + const factory ApiSenderKeyRecord({ + required String id, + required int device_id, + required String distribution_id, + @BlobConverter() required Blob record, + @Default('') String address, + required int created_at, + }) = _ApiSenderKeyRecord; + + factory ApiSenderKeyRecord.fromJson(Map json) => + _$ApiSenderKeyRecordFromJson(json); + + factory ApiSenderKeyRecord.fromFireStore( + DocumentSnapshot> snapshot, + SnapshotOptions? options) { + Map? data = snapshot.data(); + return ApiSenderKeyRecord.fromJson(data!); + } + + Map toFireStore(ApiSenderKeyRecord instance) => instance.toJson(); +} diff --git a/data/lib/api/space/api_group_key_model.freezed.dart b/data/lib/api/space/api_group_key_model.freezed.dart index 8651fb26..ada2bfb4 100644 --- a/data/lib/api/space/api_group_key_model.freezed.dart +++ b/data/lib/api/space/api_group_key_model.freezed.dart @@ -202,8 +202,8 @@ ApiMemberKeyData _$ApiMemberKeyDataFromJson(Map json) { /// @nodoc mixin _$ApiMemberKeyData { - int get memberDeviceId => throw _privateConstructorUsedError; - int get dataUpdatedAt => throw _privateConstructorUsedError; + int get member_device_id => throw _privateConstructorUsedError; + int get data_updated_at => throw _privateConstructorUsedError; List get distributions => throw _privateConstructorUsedError; @@ -224,8 +224,8 @@ abstract class $ApiMemberKeyDataCopyWith<$Res> { _$ApiMemberKeyDataCopyWithImpl<$Res, ApiMemberKeyData>; @useResult $Res call( - {int memberDeviceId, - int dataUpdatedAt, + {int member_device_id, + int data_updated_at, List distributions}); } @@ -244,18 +244,18 @@ class _$ApiMemberKeyDataCopyWithImpl<$Res, $Val extends ApiMemberKeyData> @pragma('vm:prefer-inline') @override $Res call({ - Object? memberDeviceId = null, - Object? dataUpdatedAt = null, + Object? member_device_id = null, + Object? data_updated_at = null, Object? distributions = null, }) { return _then(_value.copyWith( - memberDeviceId: null == memberDeviceId - ? _value.memberDeviceId - : memberDeviceId // ignore: cast_nullable_to_non_nullable + member_device_id: null == member_device_id + ? _value.member_device_id + : member_device_id // ignore: cast_nullable_to_non_nullable as int, - dataUpdatedAt: null == dataUpdatedAt - ? _value.dataUpdatedAt - : dataUpdatedAt // ignore: cast_nullable_to_non_nullable + data_updated_at: null == data_updated_at + ? _value.data_updated_at + : data_updated_at // ignore: cast_nullable_to_non_nullable as int, distributions: null == distributions ? _value.distributions @@ -274,8 +274,8 @@ abstract class _$$ApiMemberKeyDataImplCopyWith<$Res> @override @useResult $Res call( - {int memberDeviceId, - int dataUpdatedAt, + {int member_device_id, + int data_updated_at, List distributions}); } @@ -292,18 +292,18 @@ class __$$ApiMemberKeyDataImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? memberDeviceId = null, - Object? dataUpdatedAt = null, + Object? member_device_id = null, + Object? data_updated_at = null, Object? distributions = null, }) { return _then(_$ApiMemberKeyDataImpl( - memberDeviceId: null == memberDeviceId - ? _value.memberDeviceId - : memberDeviceId // ignore: cast_nullable_to_non_nullable + member_device_id: null == member_device_id + ? _value.member_device_id + : member_device_id // ignore: cast_nullable_to_non_nullable as int, - dataUpdatedAt: null == dataUpdatedAt - ? _value.dataUpdatedAt - : dataUpdatedAt // ignore: cast_nullable_to_non_nullable + data_updated_at: null == data_updated_at + ? _value.data_updated_at + : data_updated_at // ignore: cast_nullable_to_non_nullable as int, distributions: null == distributions ? _value._distributions @@ -317,8 +317,8 @@ class __$$ApiMemberKeyDataImplCopyWithImpl<$Res> @JsonSerializable() class _$ApiMemberKeyDataImpl implements _ApiMemberKeyData { const _$ApiMemberKeyDataImpl( - {this.memberDeviceId = 0, - this.dataUpdatedAt = 0, + {this.member_device_id = 0, + this.data_updated_at = 0, final List distributions = const []}) : _distributions = distributions; @@ -327,10 +327,10 @@ class _$ApiMemberKeyDataImpl implements _ApiMemberKeyData { @override @JsonKey() - final int memberDeviceId; + final int member_device_id; @override @JsonKey() - final int dataUpdatedAt; + final int data_updated_at; final List _distributions; @override @JsonKey() @@ -342,7 +342,7 @@ class _$ApiMemberKeyDataImpl implements _ApiMemberKeyData { @override String toString() { - return 'ApiMemberKeyData(memberDeviceId: $memberDeviceId, dataUpdatedAt: $dataUpdatedAt, distributions: $distributions)'; + return 'ApiMemberKeyData(member_device_id: $member_device_id, data_updated_at: $data_updated_at, distributions: $distributions)'; } @override @@ -350,18 +350,18 @@ class _$ApiMemberKeyDataImpl implements _ApiMemberKeyData { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ApiMemberKeyDataImpl && - (identical(other.memberDeviceId, memberDeviceId) || - other.memberDeviceId == memberDeviceId) && - (identical(other.dataUpdatedAt, dataUpdatedAt) || - other.dataUpdatedAt == dataUpdatedAt) && + (identical(other.member_device_id, member_device_id) || + other.member_device_id == member_device_id) && + (identical(other.data_updated_at, data_updated_at) || + other.data_updated_at == data_updated_at) && const DeepCollectionEquality() .equals(other._distributions, _distributions)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, memberDeviceId, dataUpdatedAt, - const DeepCollectionEquality().hash(_distributions)); + int get hashCode => Object.hash(runtimeType, member_device_id, + data_updated_at, const DeepCollectionEquality().hash(_distributions)); /// Create a copy of ApiMemberKeyData /// with the given fields replaced by the non-null parameter values. @@ -382,8 +382,8 @@ class _$ApiMemberKeyDataImpl implements _ApiMemberKeyData { abstract class _ApiMemberKeyData implements ApiMemberKeyData { const factory _ApiMemberKeyData( - {final int memberDeviceId, - final int dataUpdatedAt, + {final int member_device_id, + final int data_updated_at, final List distributions}) = _$ApiMemberKeyDataImpl; @@ -391,9 +391,9 @@ abstract class _ApiMemberKeyData implements ApiMemberKeyData { _$ApiMemberKeyDataImpl.fromJson; @override - int get memberDeviceId; + int get member_device_id; @override - int get dataUpdatedAt; + int get data_updated_at; @override List get distributions; @@ -639,3 +639,267 @@ abstract class _EncryptedDistribution extends EncryptedDistribution { _$$EncryptedDistributionImplCopyWith<_$EncryptedDistributionImpl> get copyWith => throw _privateConstructorUsedError; } + +ApiSenderKeyRecord _$ApiSenderKeyRecordFromJson(Map json) { + return _ApiSenderKeyRecord.fromJson(json); +} + +/// @nodoc +mixin _$ApiSenderKeyRecord { + String get id => throw _privateConstructorUsedError; + int get device_id => throw _privateConstructorUsedError; + String get distribution_id => throw _privateConstructorUsedError; + @BlobConverter() + Blob get record => throw _privateConstructorUsedError; + String get address => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; + + /// Serializes this ApiSenderKeyRecord to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ApiSenderKeyRecordCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApiSenderKeyRecordCopyWith<$Res> { + factory $ApiSenderKeyRecordCopyWith( + ApiSenderKeyRecord value, $Res Function(ApiSenderKeyRecord) then) = + _$ApiSenderKeyRecordCopyWithImpl<$Res, ApiSenderKeyRecord>; + @useResult + $Res call( + {String id, + int device_id, + String distribution_id, + @BlobConverter() Blob record, + String address, + int created_at}); +} + +/// @nodoc +class _$ApiSenderKeyRecordCopyWithImpl<$Res, $Val extends ApiSenderKeyRecord> + implements $ApiSenderKeyRecordCopyWith<$Res> { + _$ApiSenderKeyRecordCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? device_id = null, + Object? distribution_id = null, + Object? record = null, + Object? address = null, + Object? created_at = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + device_id: null == device_id + ? _value.device_id + : device_id // ignore: cast_nullable_to_non_nullable + as int, + distribution_id: null == distribution_id + ? _value.distribution_id + : distribution_id // ignore: cast_nullable_to_non_nullable + as String, + record: null == record + ? _value.record + : record // ignore: cast_nullable_to_non_nullable + as Blob, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ApiSenderKeyRecordImplCopyWith<$Res> + implements $ApiSenderKeyRecordCopyWith<$Res> { + factory _$$ApiSenderKeyRecordImplCopyWith(_$ApiSenderKeyRecordImpl value, + $Res Function(_$ApiSenderKeyRecordImpl) then) = + __$$ApiSenderKeyRecordImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + int device_id, + String distribution_id, + @BlobConverter() Blob record, + String address, + int created_at}); +} + +/// @nodoc +class __$$ApiSenderKeyRecordImplCopyWithImpl<$Res> + extends _$ApiSenderKeyRecordCopyWithImpl<$Res, _$ApiSenderKeyRecordImpl> + implements _$$ApiSenderKeyRecordImplCopyWith<$Res> { + __$$ApiSenderKeyRecordImplCopyWithImpl(_$ApiSenderKeyRecordImpl _value, + $Res Function(_$ApiSenderKeyRecordImpl) _then) + : super(_value, _then); + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? device_id = null, + Object? distribution_id = null, + Object? record = null, + Object? address = null, + Object? created_at = null, + }) { + return _then(_$ApiSenderKeyRecordImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + device_id: null == device_id + ? _value.device_id + : device_id // ignore: cast_nullable_to_non_nullable + as int, + distribution_id: null == distribution_id + ? _value.distribution_id + : distribution_id // ignore: cast_nullable_to_non_nullable + as String, + record: null == record + ? _value.record + : record // ignore: cast_nullable_to_non_nullable + as Blob, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { + const _$ApiSenderKeyRecordImpl( + {required this.id, + required this.device_id, + required this.distribution_id, + @BlobConverter() required this.record, + this.address = '', + required this.created_at}) + : super._(); + + factory _$ApiSenderKeyRecordImpl.fromJson(Map json) => + _$$ApiSenderKeyRecordImplFromJson(json); + + @override + final String id; + @override + final int device_id; + @override + final String distribution_id; + @override + @BlobConverter() + final Blob record; + @override + @JsonKey() + final String address; + @override + final int created_at; + + @override + String toString() { + return 'ApiSenderKeyRecord(id: $id, device_id: $device_id, distribution_id: $distribution_id, record: $record, address: $address, created_at: $created_at)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ApiSenderKeyRecordImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.device_id, device_id) || + other.device_id == device_id) && + (identical(other.distribution_id, distribution_id) || + other.distribution_id == distribution_id) && + (identical(other.record, record) || other.record == record) && + (identical(other.address, address) || other.address == address) && + (identical(other.created_at, created_at) || + other.created_at == created_at)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, id, device_id, distribution_id, record, address, created_at); + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ApiSenderKeyRecordImplCopyWith<_$ApiSenderKeyRecordImpl> get copyWith => + __$$ApiSenderKeyRecordImplCopyWithImpl<_$ApiSenderKeyRecordImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ApiSenderKeyRecordImplToJson( + this, + ); + } +} + +abstract class _ApiSenderKeyRecord extends ApiSenderKeyRecord { + const factory _ApiSenderKeyRecord( + {required final String id, + required final int device_id, + required final String distribution_id, + @BlobConverter() required final Blob record, + final String address, + required final int created_at}) = _$ApiSenderKeyRecordImpl; + const _ApiSenderKeyRecord._() : super._(); + + factory _ApiSenderKeyRecord.fromJson(Map json) = + _$ApiSenderKeyRecordImpl.fromJson; + + @override + String get id; + @override + int get device_id; + @override + String get distribution_id; + @override + @BlobConverter() + Blob get record; + @override + String get address; + @override + int get created_at; + + /// Create a copy of ApiSenderKeyRecord + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ApiSenderKeyRecordImplCopyWith<_$ApiSenderKeyRecordImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/data/lib/api/space/api_group_key_model.g.dart b/data/lib/api/space/api_group_key_model.g.dart index 2c8d7e99..655466de 100644 --- a/data/lib/api/space/api_group_key_model.g.dart +++ b/data/lib/api/space/api_group_key_model.g.dart @@ -25,8 +25,8 @@ Map _$$ApiGroupKeyImplToJson(_$ApiGroupKeyImpl instance) => _$ApiMemberKeyDataImpl _$$ApiMemberKeyDataImplFromJson( Map json) => _$ApiMemberKeyDataImpl( - memberDeviceId: (json['memberDeviceId'] as num?)?.toInt() ?? 0, - dataUpdatedAt: (json['dataUpdatedAt'] as num?)?.toInt() ?? 0, + member_device_id: (json['member_device_id'] as num?)?.toInt() ?? 0, + data_updated_at: (json['data_updated_at'] as num?)?.toInt() ?? 0, distributions: (json['distributions'] as List?) ?.map((e) => EncryptedDistribution.fromJson(e as Map)) @@ -37,8 +37,8 @@ _$ApiMemberKeyDataImpl _$$ApiMemberKeyDataImplFromJson( Map _$$ApiMemberKeyDataImplToJson( _$ApiMemberKeyDataImpl instance) => { - 'memberDeviceId': instance.memberDeviceId, - 'dataUpdatedAt': instance.dataUpdatedAt, + 'member_device_id': instance.member_device_id, + 'data_updated_at': instance.data_updated_at, 'distributions': instance.distributions.map((e) => e.toJson()).toList(), }; @@ -61,3 +61,26 @@ Map _$$EncryptedDistributionImplToJson( 'iv': const BlobConverter().toJson(instance.iv), 'ciphertext': const BlobConverter().toJson(instance.ciphertext), }; + +_$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( + Map json) => + _$ApiSenderKeyRecordImpl( + id: json['id'] as String, + device_id: (json['device_id'] as num).toInt(), + distribution_id: json['distribution_id'] as String, + record: const BlobConverter() + .fromJson(json['record'] as Map?), + address: json['address'] as String? ?? '', + created_at: (json['created_at'] as num).toInt(), + ); + +Map _$$ApiSenderKeyRecordImplToJson( + _$ApiSenderKeyRecordImpl instance) => + { + 'id': instance.id, + 'device_id': instance.device_id, + 'distribution_id': instance.distribution_id, + 'record': const BlobConverter().toJson(instance.record), + 'address': instance.address, + 'created_at': instance.created_at, + }; diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index 3ca26ad7..caadecf7 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -1,5 +1,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/network/client.dart'; +import 'package:data/api/space/api_group_key_model.dart'; import 'package:data/api/space/space_models.dart'; import 'package:data/storage/app_preferences.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -33,6 +34,17 @@ class ApiSpaceService { .collection('space_members'); } + DocumentReference spaceGroupKeysDocRef(String spaceId) { + return FirebaseFirestore.instance + .collection('spaces') + .doc(spaceId) + .collection('group_keys') + .doc('group_keys') + .withConverter( + fromFirestore: ApiGroupKey.fromFireStore, + toFirestore: (key, options) => key.toJson()); + } + Future createSpace(String name) async { final doc = _spaceRef.doc(); final adminId = _currentUser?.id ?? ""; @@ -56,7 +68,6 @@ class ApiSpaceService { Future joinSpace(String spaceId, String userId, {int role = SPACE_MEMBER_ROLE_MEMBER}) async { - final member = ApiSpaceMember( space_id: spaceId, user_id: userId, @@ -154,4 +165,31 @@ class ApiSpaceService { Future updateSpace(ApiSpace space) async { await _spaceRef.doc(space.id).set(space); } + + Future updateGroupKeys( + String spaceId, String userId, ApiMemberKeyData membersKeyData) async { + await _db.runTransaction((transaction) async { + final groupKeysDocRef = spaceGroupKeysDocRef(spaceId); + final snapshot = await transaction.get(groupKeysDocRef); + + final updatedAt = DateTime.now().millisecondsSinceEpoch; + final data = snapshot.data() ?? ApiGroupKey(docUpdatedAt: updatedAt); + + final oldMemberKeyData = + data.memberKeys[userId] ?? const ApiMemberKeyData(); + + final newMemberKeyData = oldMemberKeyData.copyWith( + member_device_id: membersKeyData.member_device_id, + data_updated_at: updatedAt, + distributions: membersKeyData.distributions, + ); + + final updates = { + 'member_keys.$userId': newMemberKeyData, + 'doc_updated_at': DateTime.now().millisecondsSinceEpoch, + }; + + transaction.update(groupKeysDocRef, updates); + }); + } } diff --git a/data/lib/api/space/space_models.dart b/data/lib/api/space/space_models.dart index dfe6553f..c8419a9b 100644 --- a/data/lib/api/space/space_models.dart +++ b/data/lib/api/space/space_models.dart @@ -2,6 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/auth_models.dart'; +import 'package:data/converter/blob_converter.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'space_models.freezed.dart'; @@ -41,6 +42,7 @@ class ApiSpaceMember with _$ApiSpaceMember { required String user_id, required int role, required bool location_enabled, + @BlobConverter() Blob? identity_key_public, int? created_at, }) = _ApiSpaceMember; diff --git a/data/lib/api/space/space_models.freezed.dart b/data/lib/api/space/space_models.freezed.dart index 96e7cf0d..0bd7d756 100644 --- a/data/lib/api/space/space_models.freezed.dart +++ b/data/lib/api/space/space_models.freezed.dart @@ -233,6 +233,8 @@ mixin _$ApiSpaceMember { String get user_id => throw _privateConstructorUsedError; int get role => throw _privateConstructorUsedError; bool get location_enabled => throw _privateConstructorUsedError; + @BlobConverter() + Blob? get identity_key_public => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; /// Serializes this ApiSpaceMember to a JSON map. @@ -257,6 +259,7 @@ abstract class $ApiSpaceMemberCopyWith<$Res> { String user_id, int role, bool location_enabled, + @BlobConverter() Blob? identity_key_public, int? created_at}); } @@ -280,6 +283,7 @@ class _$ApiSpaceMemberCopyWithImpl<$Res, $Val extends ApiSpaceMember> Object? user_id = null, Object? role = null, Object? location_enabled = null, + Object? identity_key_public = freezed, Object? created_at = freezed, }) { return _then(_value.copyWith( @@ -303,6 +307,10 @@ class _$ApiSpaceMemberCopyWithImpl<$Res, $Val extends ApiSpaceMember> ? _value.location_enabled : location_enabled // ignore: cast_nullable_to_non_nullable as bool, + identity_key_public: freezed == identity_key_public + ? _value.identity_key_public + : identity_key_public // ignore: cast_nullable_to_non_nullable + as Blob?, created_at: freezed == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -325,6 +333,7 @@ abstract class _$$ApiSpaceMemberImplCopyWith<$Res> String user_id, int role, bool location_enabled, + @BlobConverter() Blob? identity_key_public, int? created_at}); } @@ -346,6 +355,7 @@ class __$$ApiSpaceMemberImplCopyWithImpl<$Res> Object? user_id = null, Object? role = null, Object? location_enabled = null, + Object? identity_key_public = freezed, Object? created_at = freezed, }) { return _then(_$ApiSpaceMemberImpl( @@ -369,6 +379,10 @@ class __$$ApiSpaceMemberImplCopyWithImpl<$Res> ? _value.location_enabled : location_enabled // ignore: cast_nullable_to_non_nullable as bool, + identity_key_public: freezed == identity_key_public + ? _value.identity_key_public + : identity_key_public // ignore: cast_nullable_to_non_nullable + as Blob?, created_at: freezed == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -386,6 +400,7 @@ class _$ApiSpaceMemberImpl extends _ApiSpaceMember { required this.user_id, required this.role, required this.location_enabled, + @BlobConverter() this.identity_key_public, this.created_at}) : super._(); @@ -403,11 +418,14 @@ class _$ApiSpaceMemberImpl extends _ApiSpaceMember { @override final bool location_enabled; @override + @BlobConverter() + final Blob? identity_key_public; + @override final int? created_at; @override String toString() { - return 'ApiSpaceMember(id: $id, space_id: $space_id, user_id: $user_id, role: $role, location_enabled: $location_enabled, created_at: $created_at)'; + return 'ApiSpaceMember(id: $id, space_id: $space_id, user_id: $user_id, role: $role, location_enabled: $location_enabled, identity_key_public: $identity_key_public, created_at: $created_at)'; } @override @@ -422,14 +440,16 @@ class _$ApiSpaceMemberImpl extends _ApiSpaceMember { (identical(other.role, role) || other.role == role) && (identical(other.location_enabled, location_enabled) || other.location_enabled == location_enabled) && + (identical(other.identity_key_public, identity_key_public) || + other.identity_key_public == identity_key_public) && (identical(other.created_at, created_at) || other.created_at == created_at)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, id, space_id, user_id, role, location_enabled, created_at); + int get hashCode => Object.hash(runtimeType, id, space_id, user_id, role, + location_enabled, identity_key_public, created_at); /// Create a copy of ApiSpaceMember /// with the given fields replaced by the non-null parameter values. @@ -455,6 +475,7 @@ abstract class _ApiSpaceMember extends ApiSpaceMember { required final String user_id, required final int role, required final bool location_enabled, + @BlobConverter() final Blob? identity_key_public, final int? created_at}) = _$ApiSpaceMemberImpl; const _ApiSpaceMember._() : super._(); @@ -472,6 +493,9 @@ abstract class _ApiSpaceMember extends ApiSpaceMember { @override bool get location_enabled; @override + @BlobConverter() + Blob? get identity_key_public; + @override int? get created_at; /// Create a copy of ApiSpaceMember diff --git a/data/lib/api/space/space_models.g.dart b/data/lib/api/space/space_models.g.dart index e83fb6ba..a53c8f3e 100644 --- a/data/lib/api/space/space_models.g.dart +++ b/data/lib/api/space/space_models.g.dart @@ -29,6 +29,8 @@ _$ApiSpaceMemberImpl _$$ApiSpaceMemberImplFromJson(Map json) => user_id: json['user_id'] as String, role: (json['role'] as num).toInt(), location_enabled: json['location_enabled'] as bool, + identity_key_public: const BlobConverter() + .fromJson(json['identity_key_public'] as Map?), created_at: (json['created_at'] as num?)?.toInt(), ); @@ -40,9 +42,17 @@ Map _$$ApiSpaceMemberImplToJson( 'user_id': instance.user_id, 'role': instance.role, 'location_enabled': instance.location_enabled, + 'identity_key_public': _$JsonConverterToJson?, Blob>( + instance.identity_key_public, const BlobConverter().toJson), 'created_at': instance.created_at, }; +Json? _$JsonConverterToJson( + Value? value, + Json? Function(Value value) toJson, +) => + value == null ? null : toJson(value); + _$ApiSpaceInvitationImpl _$$ApiSpaceInvitationImplFromJson( Map json) => _$ApiSpaceInvitationImpl( diff --git a/data/lib/service/space_service.dart b/data/lib/service/space_service.dart index 07d58927..10a454ae 100644 --- a/data/lib/service/space_service.dart +++ b/data/lib/service/space_service.dart @@ -5,23 +5,25 @@ import 'package:data/api/space/api_space_invitation_service.dart'; import 'package:data/api/space/api_space_service.dart'; import 'package:data/api/space/space_models.dart'; import 'package:data/service/place_service.dart'; +import 'package:data/utils/buffered_sender_keystore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:rxdart/rxdart.dart'; import '../api/auth/auth_models.dart'; import '../api/place/api_place.dart'; import '../storage/app_preferences.dart'; +import '../utils/distribution_key_generator.dart'; import 'location_service.dart'; final spaceServiceProvider = Provider((ref) => SpaceService( - ref.read(currentUserPod), - ref.read(apiSpaceServiceProvider), - ref.read(apiSpaceInvitationServiceProvider), - ref.read(currentSpaceId.notifier), - ref.read(apiUserServiceProvider), - ref.read(locationServiceProvider), - ref.read(placeServiceProvider), - )); + ref.read(currentUserPod), + ref.read(apiSpaceServiceProvider), + ref.read(apiSpaceInvitationServiceProvider), + ref.read(currentSpaceId.notifier), + ref.read(apiUserServiceProvider), + ref.read(locationServiceProvider), + ref.read(placeServiceProvider), + ref.read(bufferedSenderKeystoreProvider))); class SpaceService { final ApiUser? currentUser; @@ -31,6 +33,7 @@ class SpaceService { final ApiUserService userService; final LocationService locationService; final PlaceService placeService; + final BufferedSenderKeystore bufferedSenderKeystore; SpaceService( this.currentUser, @@ -40,6 +43,7 @@ class SpaceService { this.userService, this.locationService, this.placeService, + this.bufferedSenderKeystore, ); String? get currentSpaceId => _currentSpaceIdController.state; @@ -61,6 +65,7 @@ class SpaceService { await placeService.joinUserToExistingPlaces( userId: userId, spaceId: spaceId); currentSpaceId = spaceId; + await _distributeSenderKeyToSpaceMembers(spaceId, userId); } Stream> streamAllSpace(String userId) { @@ -257,4 +262,15 @@ class SpaceService { return placesLists.expand((places) => places).toList(); }); } + + Future _distributeSenderKeyToSpaceMembers( + String spaceId, String userId) async { + final spaceMembers = await spaceService.getMembersBySpaceId(spaceId); + final membersKeyData = await generateMemberKeyData(spaceId, + senderUserId: userId, + spaceMembers: spaceMembers, + bufferedSenderKeyStore: bufferedSenderKeystore); + + await spaceService.updateGroupKeys(spaceId, userId, membersKeyData); + } } diff --git a/data/lib/storage/app_preferences.dart b/data/lib/storage/app_preferences.dart index 6b0d8604..4aaa3a8e 100644 --- a/data/lib/storage/app_preferences.dart +++ b/data/lib/storage/app_preferences.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; import '../api/auth/auth_models.dart'; +import '../api/space/api_group_key_model.dart'; final isIntroScreenShownPod = createPrefProvider( prefKey: "show_intro_screen", @@ -63,3 +64,13 @@ final userPassKeyPod = createPrefProvider( prefKey: "user_passkey", defaultValue: null, ); + +final senderKeyJsonPod = createPrefProvider( + prefKey: "sender_key", + defaultValue: null, +); + +final senderKeyPod = Provider((ref) { + final json = ref.watch(senderKeyJsonPod); + return json == null ? null : ApiSenderKeyRecord.fromJson(jsonDecode(json)); +}); diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart new file mode 100644 index 00000000..3cb4d4d3 --- /dev/null +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -0,0 +1,161 @@ +import 'dart:convert'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:data/api/auth/auth_models.dart'; +import 'package:data/api/space/api_space_service.dart'; +import 'package:data/log/logger.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; + +import '../api/network/client.dart'; +import '../api/space/api_group_key_model.dart'; +import '../storage/app_preferences.dart'; + +final bufferedSenderKeystoreProvider = Provider((ref) => BufferedSenderKeystore( + ref.read(firestoreProvider), + ref.read(senderKeyJsonPod.notifier), + ref.read(currentUserJsonPod.notifier), + )); + +class StoreKey { + final SignalProtocolAddress address; + final String groupId; + final int senderDeviceId; + + StoreKey(this.address, this.groupId, this.senderDeviceId); +} + +class BufferedSenderKeystore extends SenderKeyStore { + final StateController senderKeyJsonState; + final StateController userJsonState; + final FirebaseFirestore _db; + + BufferedSenderKeystore(this._db, this.senderKeyJsonState, this.userJsonState); + + final Map _inMemoryStore = {}; + + CollectionReference senderKeyRef( + String spaceId, String userId) { + return _db + .collection('spaces') + .doc(spaceId) + .collection('space_members') + .doc(userId) + .collection('sender_key_record') + .withConverter( + fromFirestore: ApiSenderKeyRecord.fromFireStore, + toFirestore: (senderKey, options) => senderKey.toJson()); + } + + ApiUser? get currentUser => userJsonState.state == null + ? ApiUser.fromJson(jsonDecode(userJsonState.state ?? '')) + : null; + + @override + Future loadSenderKey(SenderKeyName senderKeyName) { + final sender = senderKeyName.sender; + final key = StoreKey(sender, senderKeyName.groupId, sender.getDeviceId()); + + final cache = _inMemoryStore[key]; + + final stateFromPref = senderKeyJsonState.state; + final cacheSenderKey = stateFromPref == null + ? null + : ApiSenderKeyRecord.fromJson(jsonDecode(stateFromPref)); + + final keyRecordFuture = cache != null + ? Future.value(cache) + : (cacheSenderKey != null + ? Future.value( + SenderKeyRecord.fromSerialized(cacheSenderKey.record.bytes)) + : null) ?? + fetchSenderKeyFromServer(sender); + + keyRecordFuture.then((keyRecord) { + _inMemoryStore[key] = keyRecord; + }); + + return keyRecordFuture; + } + + @override + Future storeSenderKey( + SenderKeyName senderKeyName, SenderKeyRecord record) { + final sender = senderKeyName.sender; + final key = StoreKey(sender, senderKeyName.groupId, sender.getDeviceId()); + if (_inMemoryStore.containsKey(key)) { + return Future.value(); + } + + _inMemoryStore[key] = record; + + saveSenderKeyToServer(senderKeyName, record).then((value) { + if (value != null) { + senderKeyJsonState.state = jsonEncode(value.toJson()); + } + }); + + return Future.value(); + } + + Future fetchSenderKeyFromServer( + SignalProtocolAddress sender) { + final newKeyRecord = SenderKeyRecord(); + final currentUser = this.currentUser; + if (currentUser == null) { + return Future.value(newKeyRecord); + } + + return senderKeyRef(sender.getName(), currentUser.id) + .where('device_id', isEqualTo: sender.getDeviceId()) + .get() + .then((querySnapshot) { + final doc = querySnapshot.docs.firstOrNull; + if (doc == null) { + return newKeyRecord; + } + + final apiSenderKeyRecord = doc.data(); + try { + return SenderKeyRecord.fromSerialized(apiSenderKeyRecord.record.bytes); + } catch (e, s) { + logger.e("Failed to deserialize sender key record", + error: e, stackTrace: s); + return newKeyRecord; + } + }).catchError((e, s) { + logger.e("Failed to fetch sender key from server for sender: $sender", + error: e, stackTrace: s); + return newKeyRecord; + }); + } + + Future saveSenderKeyToServer( + SenderKeyName senderKeyName, SenderKeyRecord record) async { + try { + final currentUser = this.currentUser; + if (currentUser == null) { + return null; + } + + final distributionId = senderKeyName.groupId; + final deviceId = senderKeyName.sender.getDeviceId(); + final uniqueDocId = "$deviceId-$distributionId"; + final senderKeyRecord = ApiSenderKeyRecord( + id: uniqueDocId, + device_id: deviceId, + distribution_id: distributionId, + record: Blob(record.serialize()), + created_at: DateTime.now().millisecondsSinceEpoch); + + await senderKeyRef(senderKeyRecord.distribution_id, currentUser.id) + .doc(uniqueDocId) + .set(senderKeyRecord); + + return senderKeyRecord; + } catch (e, s) { + logger.d("Failed to save sender key to server", error: e, stackTrace: s); + return null; + } + } +} diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart new file mode 100644 index 00000000..e9d1553b --- /dev/null +++ b/data/lib/utils/distribution_key_generator.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:data/utils/private_key_helper.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import 'package:uuid/uuid.dart'; + +import '../api/space/api_group_key_model.dart'; +import '../api/space/space_models.dart'; +import 'buffered_sender_keystore.dart'; +import 'ephemeral_distribution_helper.dart'; + +Future generateMemberKeyData(String spaceId, + {required String senderUserId, + required List spaceMembers, + required BufferedSenderKeystore bufferedSenderKeyStore}) async { + final deviceId = Random.secure().nextInt(0x7FFFFFFF); + final groupAddress = SignalProtocolAddress(spaceId, deviceId); + final sessionBuilder = GroupSessionBuilder(bufferedSenderKeyStore); + final senderKey = SenderKeyName(const Uuid().v4(), groupAddress); + + final distributionMessage = await sessionBuilder.create(senderKey); + final distributionBytes = distributionMessage.serialize(); + + final List distributions = []; + + for (final member in spaceMembers) { + final publicBlob = member.identity_key_public; + + if (publicBlob == null) continue; + + try { + final publicKeyBytes = publicBlob.bytes; + if (publicKeyBytes.length != 33) { + print("Invalid public key size for member ${member.user_id}"); + continue; + } + + final publicKey = Curve.decodePoint(publicKeyBytes, 0); + + final encryptedDistribution = await EphemeralECDHUtils.encrypt( + member.user_id, + distributionBytes, + publicKey, + ); + distributions.add(encryptedDistribution); + } catch (e) { + print("Failed to decode public key for member ${member.user_id}: $e"); + continue; + } + } + + return ApiMemberKeyData( + data_updated_at: DateTime.now().millisecondsSinceEpoch, + member_device_id: deviceId, + distributions: distributions); +} diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart new file mode 100644 index 00000000..69d0b633 --- /dev/null +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:cryptography/cryptography.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; + +import '../api/space/api_group_key_model.dart'; + +class EphemeralECDHUtils { + static const int syntheticIvLength = + 16; // Length of the synthetic initialization vector (IV). + + /// Encrypts the provided plaintext for a specific recipient using their public key. + static Future encrypt( + String receiverId, + Uint8List plaintext, + ECPublicKey receiverPub, + ) async { + final ephemeralPubKey = Curve.generateKeyPair(); + final ephemeralPrivateKeyBytes = Curve.decodePrivatePoint(plaintext); + final masterSecret = + Curve.calculateAgreement(receiverPub, ephemeralPrivateKeyBytes); + + // Compute synthetic IV + final syntheticIv = await _computeSyntheticIv(SecretKey(masterSecret), plaintext); + + // Compute cipher key + final cipherKey = await _computeCipherKey(SecretKey(masterSecret), syntheticIv); + + // Encrypt plaintext + final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); + final secretKey = SecretKey(cipherKey); + final secretBox = await algorithm.encrypt( + plaintext, + secretKey: secretKey, + nonce: syntheticIv, + ); + + final ciphertext = Uint8List.fromList(secretBox.cipherText); + return EncryptedDistribution( + recipientId: receiverId, + ephemeralPub: Blob(ephemeralPubKey.publicKey.serialize()), + iv: Blob(Uint8List.fromList(syntheticIv)), + ciphertext: Blob(ciphertext), + ); + + } + + /// Computes a synthetic IV using the master secret and plaintext. + static Future _computeSyntheticIv( + SecretKey sharedSecret, + Uint8List plaintext, + ) async { + final mac = Hmac.sha256(); + + // Derive synthetic IV key + final syntheticIvKey = await mac.calculateMac( + utf8.encode("auth"), + secretKey: sharedSecret, + ); + + // Compute synthetic IV + final syntheticIv = await mac.calculateMac( + plaintext, + secretKey: SecretKey(syntheticIvKey.bytes), + ); + + return Uint8List.fromList(syntheticIv.bytes.sublist(0, syntheticIvLength)); + } + + static Future _computeCipherKey( + SecretKey masterSecret, + Uint8List syntheticIv, + ) async { + final mac = Hmac.sha256(); + + // Derive cipher key + final cipherKeyMaterial = await mac.calculateMac( + utf8.encode("cipher"), + secretKey: masterSecret, + ); + + final cipherKey = await mac.calculateMac( + syntheticIv, + secretKey: SecretKey(cipherKeyMaterial.bytes), + ); + + return Uint8List.fromList(cipherKey.bytes); + } +} From 60bf0a09b5882fc50c57138765bbdfa25e2040e3 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Mon, 13 Jan 2025 15:00:44 +0530 Subject: [PATCH 03/19] Fix user public key and group distribution keys --- .../ui/flow/auth/sign_in_method_screen.dart | 20 ++--- .../ui/flow/home/home_screen_viewmodel.dart | 30 ++++---- app/lib/ui/flow/navigation/routes.dart | 11 +-- app/lib/ui/flow/navigation/routes.g.dart | 14 ++-- app/lib/ui/flow/onboard/pick_name_screen.dart | 11 +-- .../ui/flow/onboard/pick_name_view_model.dart | 34 ++++++--- .../onboard/pick_name_view_model.freezed.dart | 47 ++---------- .../space/create/create_space_view_model.dart | 15 ++-- .../space/join/join_space_view_model.dart | 2 +- data/lib/api/auth/api_user_service.dart | 12 ++- data/lib/api/auth/auth_models.dart | 1 - data/lib/api/auth/auth_models.g.dart | 21 +++-- data/lib/api/location/journey/journey.g.dart | 22 ++---- data/lib/api/location/location.g.dart | 6 +- data/lib/api/space/api_group_key_model.dart | 4 +- .../space/api_group_key_model.freezed.dart | 76 +++++++++---------- data/lib/api/space/api_group_key_model.g.dart | 20 +++-- data/lib/api/space/api_sender_key_record.dart | 1 - .../api/space/api_sender_key_record.g.dart | 3 +- data/lib/api/space/api_space_service.dart | 59 ++++++++++---- data/lib/api/space/space_models.dart | 1 - data/lib/api/space/space_models.g.dart | 6 +- data/lib/converter/blob_converter.dart | 29 ++++--- data/lib/service/auth_service.dart | 44 +++++++++-- data/lib/service/space_service.dart | 68 +++++++++-------- data/lib/storage/app_preferences.dart | 1 - .../lib/utils/distribution_key_generator.dart | 12 ++- .../utils/ephemeral_distribution_helper.dart | 8 +- 28 files changed, 307 insertions(+), 271 deletions(-) diff --git a/app/lib/ui/flow/auth/sign_in_method_screen.dart b/app/lib/ui/flow/auth/sign_in_method_screen.dart index ce148195..74588e74 100644 --- a/app/lib/ui/flow/auth/sign_in_method_screen.dart +++ b/app/lib/ui/flow/auth/sign_in_method_screen.dart @@ -108,21 +108,17 @@ class _SignInMethodScreenState extends ConsumerState { final state = ref.watch(signInMethodsStateProvider); final user = ref.read(currentUserPod); - if (mounted && (user?.first_name == null || user!.first_name!.isEmpty)) { - PickNameRoute(user!).go(context); - } - - if (state.isNewUser && mounted) { - ConnectionRoute().go(context); - } else { - navigateToHome(); + if (mounted) { + if ((user?.first_name == null || user!.first_name!.isEmpty)) { + const PickNameRoute().go(context); + } else if (state.isNewUser && mounted) { + ConnectionRoute().go(context); + } else { + HomeRoute().go(context); + } } } - void navigateToHome() { - if (mounted) HomeRoute().go(context); - } - void _listenSignInSuccess() { ref.listen( signInMethodsStateProvider.select((value) => value.socialSignInCompleted), diff --git a/app/lib/ui/flow/home/home_screen_viewmodel.dart b/app/lib/ui/flow/home/home_screen_viewmodel.dart index 026b189b..bf892e2f 100644 --- a/app/lib/ui/flow/home/home_screen_viewmodel.dart +++ b/app/lib/ui/flow/home/home_screen_viewmodel.dart @@ -33,6 +33,7 @@ final homeViewStateProvider = ref.read(currentUserPod), ref.read(apiUserServiceProvider), ref.read(currentUserSessionPod), + ref.read(authServiceProvider) ); ref.listen(currentUserPod, (prev, user) { notifier._onUpdateUser(prevUser: prev, currentUser: user); @@ -54,6 +55,7 @@ class HomeViewNotifier extends StateNotifier { ApiUser? _currentUser; final ApiUserService userService; final ApiSession? _userSession; + final AuthService authService; HomeViewNotifier( this.spaceService, @@ -64,13 +66,15 @@ class HomeViewNotifier extends StateNotifier { this._currentUser, this.userService, this._userSession, + this.authService, ) : super(const HomeViewState()) { - updateUser(); + _listenUser(); fetchData(); _listenPlaces(); } StreamSubscription>? _spacesSubscription; + StreamSubscription? _userSubscription; StreamSubscription? _userSessionSubscription; StreamSubscription?>? _spacePlacesSubscription; @@ -86,18 +90,18 @@ class HomeViewNotifier extends StateNotifier { listenUserSession(); } - void updateUser() async { + void _listenUser() async { if (_currentUser == null) return; - try { - final user = await userService.getUser(_currentUser!.id); - if (user != null) { - _currentUser = user; - userService.updateUser(user); + + _userSubscription?.cancel(); + _userSubscription = + userService.getUserStream(_currentUser!.id).listen((user) { + if (_currentUser != user) { + authService.saveUser(user); } - } catch (error) { - logger.e( - 'HomeScreenViewModel: error while updating user ${_currentUser?.id}'); - } + }, onError: (error) { + logger.e('HomeScreenViewModel: error while get user $error'); + }); } void _listenPlaces() async { @@ -148,6 +152,7 @@ class HomeViewNotifier extends StateNotifier { } void _cancelSubscriptions() { + _userSubscription?.cancel(); _spacePlacesSubscription?.cancel(); _spacesSubscription?.cancel(); _userSessionSubscription?.cancel(); @@ -200,7 +205,6 @@ class HomeViewNotifier extends StateNotifier { await userService.updateCurrentUserState(userState, battery_pct: batterLevel); - } catch (error, stack) { logger.e( 'HomeViewNotifier: error while update current user state', @@ -210,8 +214,6 @@ class HomeViewNotifier extends StateNotifier { } } - - Future checkUserState(ConnectivityResult result) async { final isLocationEnabled = await permissionService.isLocationAlwaysEnabled(); final isConnected = result == ConnectivityResult.mobile || diff --git a/app/lib/ui/flow/navigation/routes.dart b/app/lib/ui/flow/navigation/routes.dart index 359d6b38..21a8340e 100644 --- a/app/lib/ui/flow/navigation/routes.dart +++ b/app/lib/ui/flow/navigation/routes.dart @@ -42,9 +42,10 @@ final goRouterProvider = FutureProvider((ref) async { final isIntroScreenShown = ref.read(isIntroScreenShownPod); final user = ref.read(currentUserPod); + print("XXX user $user"); if (!isIntroScreenShown) return IntroRoute().location; if (user == null) return SignInMethodRoute().location; - if (user.first_name?.isEmpty ?? true) return PickNameRoute(user).location; + if (user.first_name?.isEmpty ?? true) return const PickNameRoute().location; return HomeRoute().location; } @@ -86,15 +87,11 @@ class SignInMethodRoute extends GoRouteData { path: '/pick-name', ) class PickNameRoute extends GoRouteData { - final ApiUser $extra; - - const PickNameRoute(this.$extra); + const PickNameRoute(); @override Widget build(BuildContext context, GoRouterState state) { - return PickNameScreen( - user: $extra, - ); + return const PickNameScreen(); } } diff --git a/app/lib/ui/flow/navigation/routes.g.dart b/app/lib/ui/flow/navigation/routes.g.dart index 8689374f..df08c1b2 100644 --- a/app/lib/ui/flow/navigation/routes.g.dart +++ b/app/lib/ui/flow/navigation/routes.g.dart @@ -71,24 +71,20 @@ RouteBase get $pickNameRoute => GoRouteData.$route( ); extension $PickNameRouteExtension on PickNameRoute { - static PickNameRoute _fromState(GoRouterState state) => PickNameRoute( - state.extra as ApiUser, - ); + static PickNameRoute _fromState(GoRouterState state) => const PickNameRoute(); String get location => GoRouteData.$location( '/pick-name', ); - void go(BuildContext context) => context.go(location, extra: $extra); + void go(BuildContext context) => context.go(location); - Future push(BuildContext context) => - context.push(location, extra: $extra); + Future push(BuildContext context) => context.push(location); void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); + context.pushReplacement(location); - void replace(BuildContext context) => - context.replace(location, extra: $extra); + void replace(BuildContext context) => context.replace(location); } RouteBase get $connectionRoute => GoRouteData.$route( diff --git a/app/lib/ui/flow/onboard/pick_name_screen.dart b/app/lib/ui/flow/onboard/pick_name_screen.dart index 34af5566..adf55d82 100644 --- a/app/lib/ui/flow/onboard/pick_name_screen.dart +++ b/app/lib/ui/flow/onboard/pick_name_screen.dart @@ -1,4 +1,3 @@ -import 'package:data/api/auth/auth_models.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:style/button/bottom_sticky_overlay.dart'; @@ -16,9 +15,7 @@ import 'package:yourspace_flutter/ui/flow/onboard/pick_name_view_model.dart'; import '../../components/no_internet_screen.dart'; class PickNameScreen extends ConsumerStatefulWidget { - final ApiUser? user; - - const PickNameScreen({super.key, this.user}); + const PickNameScreen({super.key}); @override ConsumerState createState() => _PickNameScreenState(); @@ -68,7 +65,7 @@ class _PickNameScreenState extends ConsumerState { enabled: state.enableBtn, progress: state.savingUser, onPressed: () { - _checkInternet(widget.user ?? state.user); + _checkInternet(); }, ), ) @@ -113,9 +110,9 @@ class _PickNameScreenState extends ConsumerState { }); } - void _checkInternet(ApiUser user) async { + void _checkInternet() async { final isNetworkOff = await checkInternetConnectivity(); - isNetworkOff ? _showSnackBar() : _notifier.saveUser(user); + isNetworkOff ? _showSnackBar() : _notifier.saveUser(); } void _showSnackBar() { diff --git a/app/lib/ui/flow/onboard/pick_name_view_model.dart b/app/lib/ui/flow/onboard/pick_name_view_model.dart index 17e0c5fc..556f6454 100644 --- a/app/lib/ui/flow/onboard/pick_name_view_model.dart +++ b/app/lib/ui/flow/onboard/pick_name_view_model.dart @@ -8,11 +8,12 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'pick_name_view_model.freezed.dart'; -final pickNameStateNotifierProvider = StateNotifierProvider.autoDispose< - PickNameStateNotifier, PickNameState>((ref) { +final pickNameStateNotifierProvider = + StateNotifierProvider.autoDispose( + (ref) { return PickNameStateNotifier( - ref.watch(authServiceProvider), - ref.watch(currentUserPod), + ref.read(authServiceProvider), + ref.read(currentUserPod), ); }); @@ -21,17 +22,24 @@ class PickNameStateNotifier extends StateNotifier { final ApiUser? user; PickNameStateNotifier(this._authService, this.user) - : super(PickNameState(firstName: TextEditingController(), lastName: TextEditingController(), user: user!)); + : super(PickNameState( + firstName: TextEditingController(), + lastName: TextEditingController())); void enableNextButton() { - state = state.copyWith(enableBtn: state.firstName.text.isNotEmpty && state.firstName.text.length >= 3); + state = state.copyWith( + enableBtn: state.firstName.text.isNotEmpty && + state.firstName.text.length >= 3); } - Future saveUser(ApiUser user) async { + Future saveUser() async { try { + if (user == null) return; state = state.copyWith(savingUser: true, error: null); - final newUser = user.copyWith(first_name: state.firstName.text, last_name: state.lastName.text); - _authService.updateCurrentUser(newUser); + final firstName = state.firstName.text; + final lastName = state.lastName.text; + + _authService.updateUserName(firstName: firstName, lastName: lastName); state = state.copyWith(savingUser: false, saved: true, error: null); } catch (error, stack) { state = state.copyWith(savingUser: false, error: error); @@ -42,6 +50,13 @@ class PickNameStateNotifier extends StateNotifier { ); } } + + @override + void dispose() { + state.firstName.dispose(); + state.lastName.dispose(); + super.dispose(); + } } @freezed @@ -53,6 +68,5 @@ class PickNameState with _$PickNameState { Object? error, required TextEditingController firstName, required TextEditingController lastName, - required ApiUser user, }) = _PickNameState; } diff --git a/app/lib/ui/flow/onboard/pick_name_view_model.freezed.dart b/app/lib/ui/flow/onboard/pick_name_view_model.freezed.dart index f0444eb4..2342f712 100644 --- a/app/lib/ui/flow/onboard/pick_name_view_model.freezed.dart +++ b/app/lib/ui/flow/onboard/pick_name_view_model.freezed.dart @@ -22,7 +22,6 @@ mixin _$PickNameState { Object? get error => throw _privateConstructorUsedError; TextEditingController get firstName => throw _privateConstructorUsedError; TextEditingController get lastName => throw _privateConstructorUsedError; - ApiUser get user => throw _privateConstructorUsedError; @JsonKey(ignore: true) $PickNameStateCopyWith get copyWith => @@ -41,10 +40,7 @@ abstract class $PickNameStateCopyWith<$Res> { bool saved, Object? error, TextEditingController firstName, - TextEditingController lastName, - ApiUser user}); - - $ApiUserCopyWith<$Res> get user; + TextEditingController lastName}); } /// @nodoc @@ -66,7 +62,6 @@ class _$PickNameStateCopyWithImpl<$Res, $Val extends PickNameState> Object? error = freezed, Object? firstName = null, Object? lastName = null, - Object? user = null, }) { return _then(_value.copyWith( enableBtn: null == enableBtn @@ -90,20 +85,8 @@ class _$PickNameStateCopyWithImpl<$Res, $Val extends PickNameState> ? _value.lastName : lastName // ignore: cast_nullable_to_non_nullable as TextEditingController, - user: null == user - ? _value.user - : user // ignore: cast_nullable_to_non_nullable - as ApiUser, ) as $Val); } - - @override - @pragma('vm:prefer-inline') - $ApiUserCopyWith<$Res> get user { - return $ApiUserCopyWith<$Res>(_value.user, (value) { - return _then(_value.copyWith(user: value) as $Val); - }); - } } /// @nodoc @@ -120,11 +103,7 @@ abstract class _$$PickNameStateImplCopyWith<$Res> bool saved, Object? error, TextEditingController firstName, - TextEditingController lastName, - ApiUser user}); - - @override - $ApiUserCopyWith<$Res> get user; + TextEditingController lastName}); } /// @nodoc @@ -144,7 +123,6 @@ class __$$PickNameStateImplCopyWithImpl<$Res> Object? error = freezed, Object? firstName = null, Object? lastName = null, - Object? user = null, }) { return _then(_$PickNameStateImpl( enableBtn: null == enableBtn @@ -168,10 +146,6 @@ class __$$PickNameStateImplCopyWithImpl<$Res> ? _value.lastName : lastName // ignore: cast_nullable_to_non_nullable as TextEditingController, - user: null == user - ? _value.user - : user // ignore: cast_nullable_to_non_nullable - as ApiUser, )); } } @@ -185,8 +159,7 @@ class _$PickNameStateImpl implements _PickNameState { this.saved = false, this.error, required this.firstName, - required this.lastName, - required this.user}); + required this.lastName}); @override @JsonKey() @@ -203,12 +176,10 @@ class _$PickNameStateImpl implements _PickNameState { final TextEditingController firstName; @override final TextEditingController lastName; - @override - final ApiUser user; @override String toString() { - return 'PickNameState(enableBtn: $enableBtn, savingUser: $savingUser, saved: $saved, error: $error, firstName: $firstName, lastName: $lastName, user: $user)'; + return 'PickNameState(enableBtn: $enableBtn, savingUser: $savingUser, saved: $saved, error: $error, firstName: $firstName, lastName: $lastName)'; } @override @@ -225,13 +196,12 @@ class _$PickNameStateImpl implements _PickNameState { (identical(other.firstName, firstName) || other.firstName == firstName) && (identical(other.lastName, lastName) || - other.lastName == lastName) && - (identical(other.user, user) || other.user == user)); + other.lastName == lastName)); } @override int get hashCode => Object.hash(runtimeType, enableBtn, savingUser, saved, - const DeepCollectionEquality().hash(error), firstName, lastName, user); + const DeepCollectionEquality().hash(error), firstName, lastName); @JsonKey(ignore: true) @override @@ -247,8 +217,7 @@ abstract class _PickNameState implements PickNameState { final bool saved, final Object? error, required final TextEditingController firstName, - required final TextEditingController lastName, - required final ApiUser user}) = _$PickNameStateImpl; + required final TextEditingController lastName}) = _$PickNameStateImpl; @override bool get enableBtn; @@ -263,8 +232,6 @@ abstract class _PickNameState implements PickNameState { @override TextEditingController get lastName; @override - ApiUser get user; - @override @JsonKey(ignore: true) _$$PickNameStateImplCopyWith<_$PickNameStateImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/app/lib/ui/flow/space/create/create_space_view_model.dart b/app/lib/ui/flow/space/create/create_space_view_model.dart index 3273592b..9ac329d5 100644 --- a/app/lib/ui/flow/space/create/create_space_view_model.dart +++ b/app/lib/ui/flow/space/create/create_space_view_model.dart @@ -1,5 +1,7 @@ +import 'package:data/api/auth/auth_models.dart'; import 'package:data/log/logger.dart'; import 'package:data/service/space_service.dart'; +import 'package:data/storage/app_preferences.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -9,23 +11,26 @@ part 'create_space_view_model.freezed.dart'; final createSpaceViewStateProvider = StateNotifierProvider.autoDispose< CreateSpaceViewNotifier, CreateSpaceViewState>((ref) { return CreateSpaceViewNotifier( - ref.read(spaceServiceProvider), - ); + ref.read(spaceServiceProvider), ref.read(currentUserPod)); }); class CreateSpaceViewNotifier extends StateNotifier { final SpaceService spaceService; + final ApiUser? currentUser; - CreateSpaceViewNotifier(this.spaceService) + CreateSpaceViewNotifier(this.spaceService, this.currentUser) : super( CreateSpaceViewState(spaceName: TextEditingController()), ); Future createSpace() async { try { + if (currentUser == null) return; state = state.copyWith(isCreating: true, invitationCode: '', error: null); - final invitationCode = - await spaceService.createSpaceAndGetInviteCode(state.spaceName.text); + final invitationCode = await spaceService.createSpaceAndGetInviteCode( + spaceName: state.spaceName.text, + userId: currentUser!.id, + identityKeyPublic: currentUser!.identity_key_public); state = state.copyWith(isCreating: false, invitationCode: invitationCode); } catch (error, stack) { state = state.copyWith(error: error, isCreating: false); diff --git a/app/lib/ui/flow/space/join/join_space_view_model.dart b/app/lib/ui/flow/space/join/join_space_view_model.dart index 7880d95b..a83b3738 100644 --- a/app/lib/ui/flow/space/join/join_space_view_model.dart +++ b/app/lib/ui/flow/space/join/join_space_view_model.dart @@ -88,7 +88,7 @@ class JoinSpaceViewNotifier extends StateNotifier { try { if (state.space == null || _currentUser == null) return; state = state.copyWith(verifying: true, error: null); - await spaceService.joinSpace(state.space?.id ?? '', _currentUser.id); + await spaceService.joinSpace(state.space?.id ?? ''); state = state.copyWith(verifying: false, spaceJoined: true, error: null); } catch (error, stack) { state = state.copyWith(error: error, verifying: false); diff --git a/data/lib/api/auth/api_user_service.dart b/data/lib/api/auth/api_user_service.dart index 4349671f..baed74d7 100644 --- a/data/lib/api/auth/api_user_service.dart +++ b/data/lib/api/auth/api_user_service.dart @@ -108,7 +108,6 @@ class ApiUserService { created_at: DateTime.now().millisecondsSinceEpoch, ); await sessionDocRef.set(session); - // await _locationService.saveLastKnownLocation(uid); return {'isNewUser': true, 'user': user, 'session': session}; } } @@ -117,7 +116,7 @@ class ApiUserService { if (userId == null) return null; var snapshot = await _userRef.doc(userId).get(); if (snapshot.exists) { - return snapshot.data() as ApiUser; + return snapshot.data(); } return null; } @@ -162,6 +161,14 @@ class ApiUserService { await _userRef.doc(user.id).set(user); } + Future updateUserName(String userId, + {required String firstName, String? lastName}) async { + await _userRef.doc(userId).update({ + "first_name": firstName, + "last_name": lastName, + }); + } + Future deleteUser(String userId) async { await _db.collection("users").doc(userId).delete(); } @@ -290,7 +297,6 @@ class ApiUserService { Future updateKeys( String id, Blob publicKey, Blob privateKey, Blob saltBlob) async { - await _userRef.doc(id).update({ "identity_key_public": publicKey, "identity_key_private": privateKey, diff --git a/data/lib/api/auth/auth_models.dart b/data/lib/api/auth/auth_models.dart index 5195ef7d..fc557d29 100644 --- a/data/lib/api/auth/auth_models.dart +++ b/data/lib/api/auth/auth_models.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - import '../../converter/blob_converter.dart'; part 'auth_models.freezed.dart'; diff --git a/data/lib/api/auth/auth_models.g.dart b/data/lib/api/auth/auth_models.g.dart index 2076ff0b..ea5e1dd1 100644 --- a/data/lib/api/auth/auth_models.g.dart +++ b/data/lib/api/auth/auth_models.g.dart @@ -22,12 +22,12 @@ _$ApiUserImpl _$$ApiUserImplFromJson(Map json) => const [], battery_pct: (json['battery_pct'] as num?)?.toInt(), fcm_token: json['fcm_token'] as String? ?? "", - identity_key_public: const BlobConverter() - .fromJson(json['identity_key_public'] as Map?), - identity_key_private: const BlobConverter() - .fromJson(json['identity_key_private'] as Map?), - identity_key_salt: const BlobConverter() - .fromJson(json['identity_key_salt'] as Map?), + identity_key_public: + const BlobConverter().fromJson(json['identity_key_public']), + identity_key_private: + const BlobConverter().fromJson(json['identity_key_private']), + identity_key_salt: + const BlobConverter().fromJson(json['identity_key_salt']), state: (json['state'] as num?)?.toInt(), created_at: (json['created_at'] as num?)?.toInt(), updated_at: (json['updated_at'] as num?)?.toInt(), @@ -46,12 +46,11 @@ Map _$$ApiUserImplToJson(_$ApiUserImpl instance) => 'space_ids': instance.space_ids, 'battery_pct': instance.battery_pct, 'fcm_token': instance.fcm_token, - 'identity_key_public': _$JsonConverterToJson?, Blob>( + 'identity_key_public': _$JsonConverterToJson( instance.identity_key_public, const BlobConverter().toJson), - 'identity_key_private': - _$JsonConverterToJson?, Blob>( - instance.identity_key_private, const BlobConverter().toJson), - 'identity_key_salt': _$JsonConverterToJson?, Blob>( + 'identity_key_private': _$JsonConverterToJson( + instance.identity_key_private, const BlobConverter().toJson), + 'identity_key_salt': _$JsonConverterToJson( instance.identity_key_salt, const BlobConverter().toJson), 'state': instance.state, 'created_at': instance.created_at, diff --git a/data/lib/api/location/journey/journey.g.dart b/data/lib/api/location/journey/journey.g.dart index 8121e147..5f82195f 100644 --- a/data/lib/api/location/journey/journey.g.dart +++ b/data/lib/api/location/journey/journey.g.dart @@ -60,14 +60,10 @@ _$EncryptedLocationJourneyImpl _$$EncryptedLocationJourneyImplFromJson( _$EncryptedLocationJourneyImpl( id: json['id'] as String?, user_id: json['user_id'] as String, - from_latitude: const BlobConverter() - .fromJson(json['from_latitude'] as Map?), - from_longitude: const BlobConverter() - .fromJson(json['from_longitude'] as Map?), - to_latitude: const BlobConverter() - .fromJson(json['to_latitude'] as Map?), - to_longitude: const BlobConverter() - .fromJson(json['to_longitude'] as Map?), + from_latitude: const BlobConverter().fromJson(json['from_latitude']), + from_longitude: const BlobConverter().fromJson(json['from_longitude']), + to_latitude: const BlobConverter().fromJson(json['to_latitude']), + to_longitude: const BlobConverter().fromJson(json['to_longitude']), routes: (json['routes'] as List?) ?.map((e) => EncryptedJourneyRoute.fromJson(e as Map)) @@ -87,9 +83,9 @@ Map _$$EncryptedLocationJourneyImplToJson( 'user_id': instance.user_id, 'from_latitude': const BlobConverter().toJson(instance.from_latitude), 'from_longitude': const BlobConverter().toJson(instance.from_longitude), - 'to_latitude': _$JsonConverterToJson?, Blob>( + 'to_latitude': _$JsonConverterToJson( instance.to_latitude, const BlobConverter().toJson), - 'to_longitude': _$JsonConverterToJson?, Blob>( + 'to_longitude': _$JsonConverterToJson( instance.to_longitude, const BlobConverter().toJson), 'routes': instance.routes.map((e) => e.toJson()).toList(), 'route_distance': instance.route_distance, @@ -108,10 +104,8 @@ Json? _$JsonConverterToJson( _$EncryptedJourneyRouteImpl _$$EncryptedJourneyRouteImplFromJson( Map json) => _$EncryptedJourneyRouteImpl( - latitude: const BlobConverter() - .fromJson(json['latitude'] as Map?), - longitude: const BlobConverter() - .fromJson(json['longitude'] as Map?), + latitude: const BlobConverter().fromJson(json['latitude']), + longitude: const BlobConverter().fromJson(json['longitude']), ); Map _$$EncryptedJourneyRouteImplToJson( diff --git a/data/lib/api/location/location.g.dart b/data/lib/api/location/location.g.dart index b682a01c..1a66d526 100644 --- a/data/lib/api/location/location.g.dart +++ b/data/lib/api/location/location.g.dart @@ -29,10 +29,8 @@ _$EncryptedApiLocationImpl _$$EncryptedApiLocationImplFromJson( _$EncryptedApiLocationImpl( id: json['id'] as String, user_id: json['user_id'] as String, - latitude: const BlobConverter() - .fromJson(json['latitude'] as Map?), - longitude: const BlobConverter() - .fromJson(json['longitude'] as Map?), + latitude: const BlobConverter().fromJson(json['latitude']), + longitude: const BlobConverter().fromJson(json['longitude']), created_at: (json['created_at'] as num).toInt(), ); diff --git a/data/lib/api/space/api_group_key_model.dart b/data/lib/api/space/api_group_key_model.dart index 0fdae8fc..972051e1 100644 --- a/data/lib/api/space/api_group_key_model.dart +++ b/data/lib/api/space/api_group_key_model.dart @@ -11,8 +11,8 @@ class ApiGroupKey with _$ApiGroupKey { const ApiGroupKey._(); const factory ApiGroupKey({ - required int docUpdatedAt, - @Default({}) Map memberKeys, + required int doc_updated_at, + @Default({}) Map member_keys, }) = _ApiGroupKey; factory ApiGroupKey.fromJson(Map data) => diff --git a/data/lib/api/space/api_group_key_model.freezed.dart b/data/lib/api/space/api_group_key_model.freezed.dart index ada2bfb4..e00af8ba 100644 --- a/data/lib/api/space/api_group_key_model.freezed.dart +++ b/data/lib/api/space/api_group_key_model.freezed.dart @@ -20,8 +20,8 @@ ApiGroupKey _$ApiGroupKeyFromJson(Map json) { /// @nodoc mixin _$ApiGroupKey { - int get docUpdatedAt => throw _privateConstructorUsedError; - Map get memberKeys => + int get doc_updated_at => throw _privateConstructorUsedError; + Map get member_keys => throw _privateConstructorUsedError; /// Serializes this ApiGroupKey to a JSON map. @@ -40,7 +40,7 @@ abstract class $ApiGroupKeyCopyWith<$Res> { ApiGroupKey value, $Res Function(ApiGroupKey) then) = _$ApiGroupKeyCopyWithImpl<$Res, ApiGroupKey>; @useResult - $Res call({int docUpdatedAt, Map memberKeys}); + $Res call({int doc_updated_at, Map member_keys}); } /// @nodoc @@ -58,17 +58,17 @@ class _$ApiGroupKeyCopyWithImpl<$Res, $Val extends ApiGroupKey> @pragma('vm:prefer-inline') @override $Res call({ - Object? docUpdatedAt = null, - Object? memberKeys = null, + Object? doc_updated_at = null, + Object? member_keys = null, }) { return _then(_value.copyWith( - docUpdatedAt: null == docUpdatedAt - ? _value.docUpdatedAt - : docUpdatedAt // ignore: cast_nullable_to_non_nullable + doc_updated_at: null == doc_updated_at + ? _value.doc_updated_at + : doc_updated_at // ignore: cast_nullable_to_non_nullable as int, - memberKeys: null == memberKeys - ? _value.memberKeys - : memberKeys // ignore: cast_nullable_to_non_nullable + member_keys: null == member_keys + ? _value.member_keys + : member_keys // ignore: cast_nullable_to_non_nullable as Map, ) as $Val); } @@ -82,7 +82,7 @@ abstract class _$$ApiGroupKeyImplCopyWith<$Res> __$$ApiGroupKeyImplCopyWithImpl<$Res>; @override @useResult - $Res call({int docUpdatedAt, Map memberKeys}); + $Res call({int doc_updated_at, Map member_keys}); } /// @nodoc @@ -98,17 +98,17 @@ class __$$ApiGroupKeyImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? docUpdatedAt = null, - Object? memberKeys = null, + Object? doc_updated_at = null, + Object? member_keys = null, }) { return _then(_$ApiGroupKeyImpl( - docUpdatedAt: null == docUpdatedAt - ? _value.docUpdatedAt - : docUpdatedAt // ignore: cast_nullable_to_non_nullable + doc_updated_at: null == doc_updated_at + ? _value.doc_updated_at + : doc_updated_at // ignore: cast_nullable_to_non_nullable as int, - memberKeys: null == memberKeys - ? _value._memberKeys - : memberKeys // ignore: cast_nullable_to_non_nullable + member_keys: null == member_keys + ? _value._member_keys + : member_keys // ignore: cast_nullable_to_non_nullable as Map, )); } @@ -118,28 +118,28 @@ class __$$ApiGroupKeyImplCopyWithImpl<$Res> @JsonSerializable() class _$ApiGroupKeyImpl extends _ApiGroupKey { const _$ApiGroupKeyImpl( - {required this.docUpdatedAt, - final Map memberKeys = const {}}) - : _memberKeys = memberKeys, + {required this.doc_updated_at, + final Map member_keys = const {}}) + : _member_keys = member_keys, super._(); factory _$ApiGroupKeyImpl.fromJson(Map json) => _$$ApiGroupKeyImplFromJson(json); @override - final int docUpdatedAt; - final Map _memberKeys; + final int doc_updated_at; + final Map _member_keys; @override @JsonKey() - Map get memberKeys { - if (_memberKeys is EqualUnmodifiableMapView) return _memberKeys; + Map get member_keys { + if (_member_keys is EqualUnmodifiableMapView) return _member_keys; // ignore: implicit_dynamic_type - return EqualUnmodifiableMapView(_memberKeys); + return EqualUnmodifiableMapView(_member_keys); } @override String toString() { - return 'ApiGroupKey(docUpdatedAt: $docUpdatedAt, memberKeys: $memberKeys)'; + return 'ApiGroupKey(doc_updated_at: $doc_updated_at, member_keys: $member_keys)'; } @override @@ -147,16 +147,16 @@ class _$ApiGroupKeyImpl extends _ApiGroupKey { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ApiGroupKeyImpl && - (identical(other.docUpdatedAt, docUpdatedAt) || - other.docUpdatedAt == docUpdatedAt) && + (identical(other.doc_updated_at, doc_updated_at) || + other.doc_updated_at == doc_updated_at) && const DeepCollectionEquality() - .equals(other._memberKeys, _memberKeys)); + .equals(other._member_keys, _member_keys)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, docUpdatedAt, - const DeepCollectionEquality().hash(_memberKeys)); + int get hashCode => Object.hash(runtimeType, doc_updated_at, + const DeepCollectionEquality().hash(_member_keys)); /// Create a copy of ApiGroupKey /// with the given fields replaced by the non-null parameter values. @@ -176,17 +176,17 @@ class _$ApiGroupKeyImpl extends _ApiGroupKey { abstract class _ApiGroupKey extends ApiGroupKey { const factory _ApiGroupKey( - {required final int docUpdatedAt, - final Map memberKeys}) = _$ApiGroupKeyImpl; + {required final int doc_updated_at, + final Map member_keys}) = _$ApiGroupKeyImpl; const _ApiGroupKey._() : super._(); factory _ApiGroupKey.fromJson(Map json) = _$ApiGroupKeyImpl.fromJson; @override - int get docUpdatedAt; + int get doc_updated_at; @override - Map get memberKeys; + Map get member_keys; /// Create a copy of ApiGroupKey /// with the given fields replaced by the non-null parameter values. diff --git a/data/lib/api/space/api_group_key_model.g.dart b/data/lib/api/space/api_group_key_model.g.dart index 655466de..07e24758 100644 --- a/data/lib/api/space/api_group_key_model.g.dart +++ b/data/lib/api/space/api_group_key_model.g.dart @@ -8,8 +8,8 @@ part of 'api_group_key_model.dart'; _$ApiGroupKeyImpl _$$ApiGroupKeyImplFromJson(Map json) => _$ApiGroupKeyImpl( - docUpdatedAt: (json['docUpdatedAt'] as num).toInt(), - memberKeys: (json['memberKeys'] as Map?)?.map( + doc_updated_at: (json['doc_updated_at'] as num).toInt(), + member_keys: (json['member_keys'] as Map?)?.map( (k, e) => MapEntry( k, ApiMemberKeyData.fromJson(e as Map)), ) ?? @@ -18,8 +18,9 @@ _$ApiGroupKeyImpl _$$ApiGroupKeyImplFromJson(Map json) => Map _$$ApiGroupKeyImplToJson(_$ApiGroupKeyImpl instance) => { - 'docUpdatedAt': instance.docUpdatedAt, - 'memberKeys': instance.memberKeys.map((k, e) => MapEntry(k, e.toJson())), + 'doc_updated_at': instance.doc_updated_at, + 'member_keys': + instance.member_keys.map((k, e) => MapEntry(k, e.toJson())), }; _$ApiMemberKeyDataImpl _$$ApiMemberKeyDataImplFromJson( @@ -46,11 +47,9 @@ _$EncryptedDistributionImpl _$$EncryptedDistributionImplFromJson( Map json) => _$EncryptedDistributionImpl( recipientId: json['recipientId'] as String? ?? "", - ephemeralPub: const BlobConverter() - .fromJson(json['ephemeralPub'] as Map?), - iv: const BlobConverter().fromJson(json['iv'] as Map?), - ciphertext: const BlobConverter() - .fromJson(json['ciphertext'] as Map?), + ephemeralPub: const BlobConverter().fromJson(json['ephemeralPub']), + iv: const BlobConverter().fromJson(json['iv']), + ciphertext: const BlobConverter().fromJson(json['ciphertext']), ); Map _$$EncryptedDistributionImplToJson( @@ -68,8 +67,7 @@ _$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( id: json['id'] as String, device_id: (json['device_id'] as num).toInt(), distribution_id: json['distribution_id'] as String, - record: const BlobConverter() - .fromJson(json['record'] as Map?), + record: const BlobConverter().fromJson(json['record']), address: json['address'] as String? ?? '', created_at: (json['created_at'] as num).toInt(), ); diff --git a/data/lib/api/space/api_sender_key_record.dart b/data/lib/api/space/api_sender_key_record.dart index 8c5cda1e..1b2bc113 100644 --- a/data/lib/api/space/api_sender_key_record.dart +++ b/data/lib/api/space/api_sender_key_record.dart @@ -1,6 +1,5 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - import '../../converter/blob_converter.dart'; part 'api_sender_key_record.freezed.dart'; diff --git a/data/lib/api/space/api_sender_key_record.g.dart b/data/lib/api/space/api_sender_key_record.g.dart index 14040ccd..21940581 100644 --- a/data/lib/api/space/api_sender_key_record.g.dart +++ b/data/lib/api/space/api_sender_key_record.g.dart @@ -14,8 +14,7 @@ _$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( address: json['address'] as String? ?? '', distributionId: json['distributionId'] as String? ?? '', created_at: (json['created_at'] as num).toInt(), - record: const BlobConverter() - .fromJson(json['record'] as Map?), + record: const BlobConverter().fromJson(json['record']), ); Map _$$ApiSenderKeyRecordImplToJson( diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index caadecf7..7ed2d2e6 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -2,25 +2,29 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/network/client.dart'; import 'package:data/api/space/api_group_key_model.dart'; import 'package:data/api/space/space_models.dart'; -import 'package:data/storage/app_preferences.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; +import '../../utils/buffered_sender_keystore.dart'; +import '../../utils/distribution_key_generator.dart'; import '../auth/api_user_service.dart'; -import '../auth/auth_models.dart'; final apiSpaceServiceProvider = StateProvider((ref) => ApiSpaceService( ref.read(firestoreProvider), - ref.read(currentUserPod), ref.read(apiUserServiceProvider), + ref.read(bufferedSenderKeystoreProvider), )); class ApiSpaceService { final FirebaseFirestore _db; - final ApiUser? _currentUser; final ApiUserService userService; + final BufferedSenderKeystore bufferedSenderKeystore; - ApiSpaceService(this._db, this._currentUser, this.userService); + ApiSpaceService( + this._db, + this.userService, + this.bufferedSenderKeystore, + ); CollectionReference get _spaceRef => _db.collection("spaces").withConverter( @@ -45,16 +49,24 @@ class ApiSpaceService { toFirestore: (key, options) => key.toJson()); } - Future createSpace(String name) async { + Future createSpace( + String name, + String adminId, + Blob? identityKeyPublic, + ) async { final doc = _spaceRef.doc(); - final adminId = _currentUser?.id ?? ""; final space = ApiSpace( id: doc.id, admin_id: adminId, name: name, created_at: DateTime.now().millisecondsSinceEpoch); await doc.set(space); - await joinSpace(doc.id, adminId); + + final emptyGroupKeys = + ApiGroupKey(doc_updated_at: DateTime.now().millisecondsSinceEpoch); + + await spaceGroupKeysDocRef(doc.id).set(emptyGroupKeys); + await joinSpace(doc.id, adminId, identityKeyPublic); return doc.id; } @@ -66,12 +78,14 @@ class ApiSpaceService { return null; } - Future joinSpace(String spaceId, String userId, + Future joinSpace(String spaceId, String userId, Blob? identityKeyPublic, {int role = SPACE_MEMBER_ROLE_MEMBER}) async { + final member = ApiSpaceMember( space_id: spaceId, user_id: userId, role: role, + identity_key_public: identityKeyPublic, location_enabled: true, id: const Uuid().v4(), created_at: DateTime.now().millisecondsSinceEpoch, @@ -79,6 +93,8 @@ class ApiSpaceService { await spaceMemberRef(spaceId).doc(userId).set(member.toJson()); await userService.addSpaceId(userId, spaceId); + + await _distributeSenderKeyToSpaceMembers(spaceId, userId); } Future> getMembersBySpaceId(String spaceId) async { @@ -160,23 +176,40 @@ class ApiSpaceService { for (final doc in querySnapshot.docs) { await doc.reference.delete(); } + + final docRef = spaceGroupKeysDocRef(spaceId); + await docRef.update({ + "doc_updated_at": DateTime.now().millisecondsSinceEpoch, + "member_keys.$userId": FieldValue.delete() + }); } Future updateSpace(ApiSpace space) async { await _spaceRef.doc(space.id).set(space); } - Future updateGroupKeys( + Future _distributeSenderKeyToSpaceMembers( + String spaceId, String userId) async { + final spaceMembers = await getMembersBySpaceId(spaceId); + final membersKeyData = await generateMemberKeyData(spaceId, + senderUserId: userId, + spaceMembers: spaceMembers, + bufferedSenderKeyStore: bufferedSenderKeystore); + + await _updateGroupKeys(spaceId, userId, membersKeyData); + } + + Future _updateGroupKeys( String spaceId, String userId, ApiMemberKeyData membersKeyData) async { await _db.runTransaction((transaction) async { final groupKeysDocRef = spaceGroupKeysDocRef(spaceId); final snapshot = await transaction.get(groupKeysDocRef); final updatedAt = DateTime.now().millisecondsSinceEpoch; - final data = snapshot.data() ?? ApiGroupKey(docUpdatedAt: updatedAt); + final data = snapshot.data() ?? ApiGroupKey(doc_updated_at: updatedAt); final oldMemberKeyData = - data.memberKeys[userId] ?? const ApiMemberKeyData(); + data.member_keys[userId] ?? const ApiMemberKeyData(); final newMemberKeyData = oldMemberKeyData.copyWith( member_device_id: membersKeyData.member_device_id, @@ -185,7 +218,7 @@ class ApiSpaceService { ); final updates = { - 'member_keys.$userId': newMemberKeyData, + 'member_keys.$userId': newMemberKeyData.toJson(), 'doc_updated_at': DateTime.now().millisecondsSinceEpoch, }; diff --git a/data/lib/api/space/space_models.dart b/data/lib/api/space/space_models.dart index c8419a9b..3e76d748 100644 --- a/data/lib/api/space/space_models.dart +++ b/data/lib/api/space/space_models.dart @@ -4,7 +4,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/auth_models.dart'; import 'package:data/converter/blob_converter.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; - part 'space_models.freezed.dart'; part 'space_models.g.dart'; diff --git a/data/lib/api/space/space_models.g.dart b/data/lib/api/space/space_models.g.dart index a53c8f3e..5a86fd5c 100644 --- a/data/lib/api/space/space_models.g.dart +++ b/data/lib/api/space/space_models.g.dart @@ -29,8 +29,8 @@ _$ApiSpaceMemberImpl _$$ApiSpaceMemberImplFromJson(Map json) => user_id: json['user_id'] as String, role: (json['role'] as num).toInt(), location_enabled: json['location_enabled'] as bool, - identity_key_public: const BlobConverter() - .fromJson(json['identity_key_public'] as Map?), + identity_key_public: + const BlobConverter().fromJson(json['identity_key_public']), created_at: (json['created_at'] as num?)?.toInt(), ); @@ -42,7 +42,7 @@ Map _$$ApiSpaceMemberImplToJson( 'user_id': instance.user_id, 'role': instance.role, 'location_enabled': instance.location_enabled, - 'identity_key_public': _$JsonConverterToJson?, Blob>( + 'identity_key_public': _$JsonConverterToJson( instance.identity_key_public, const BlobConverter().toJson), 'created_at': instance.created_at, }; diff --git a/data/lib/converter/blob_converter.dart b/data/lib/converter/blob_converter.dart index 6aeac1c6..6d06f266 100644 --- a/data/lib/converter/blob_converter.dart +++ b/data/lib/converter/blob_converter.dart @@ -4,23 +4,32 @@ import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -class BlobConverter implements JsonConverter?> { +class BlobConverter implements JsonConverter { const BlobConverter(); @override - Blob fromJson(Map? json) { - if (json == null || !json.containsKey('_byteString')) return Blob(Uint8List(0)); - final byteString = json['_byteString'] as String; - final bytes = base64Decode(byteString); - return Blob(Uint8List.fromList(bytes)); + Blob fromJson(dynamic json) { + if (json is Blob) { + return json; + } + + if (json is Map && json.containsKey('_byteString')) { + final base64String = json['_byteString'] as String; + return Blob(base64Decode(base64String)); + } + + + if (json is List) { + return Blob(Uint8List.fromList(json.cast())); + } + + return Blob(Uint8List(0)); } @override - Map? toJson(Blob? blob) { - if (blob == null) return null; + dynamic toJson(Blob blob) { return { '_byteString': base64Encode(blob.bytes), }; } - -} \ No newline at end of file +} diff --git a/data/lib/service/auth_service.dart b/data/lib/service/auth_service.dart index d943fa8e..d4031883 100644 --- a/data/lib/service/auth_service.dart +++ b/data/lib/service/auth_service.dart @@ -15,12 +15,20 @@ import '../storage/app_preferences.dart'; const NETWORK_STATUS_CHECK_INTERVAL = 5 * 60 * 1000; -final authServiceProvider = Provider((ref) => AuthService( - ref.read(currentUserPod), - ref.read(apiUserServiceProvider), - ref.read(currentUserJsonPod.notifier), - ref.read(currentUserSessionJsonPod.notifier), - ref.read(userPassKeyPod.notifier))); +final authServiceProvider = Provider((ref) { + final provider = AuthService( + ref.read(currentUserPod), + ref.read(apiUserServiceProvider), + ref.read(currentUserJsonPod.notifier), + ref.read(currentUserSessionJsonPod.notifier), + ref.read(userPassKeyPod.notifier)); + + ref.listen(currentUserPod, (prev, user) { + provider._onUpdateUser(prevUser: prev, currentUser: user); + }); + + return provider; +}); class AuthService { ApiUser? _currentUser; @@ -59,10 +67,32 @@ class AuthService { (data['session'] as ApiSession).toJsonString(); final isNewUser = data['isNewUser'] as bool; - if (isNewUser) generateAndSaveUserKeys("1111"); + if (isNewUser) await generateAndSaveUserKeys("1111"); return isNewUser; } + Future updateUserName( + {required String firstName, String? lastName}) async { + final user = + currentUser?.copyWith(first_name: firstName, last_name: lastName); + if (user == null) { + throw Exception("No user logged in"); + } + + await userService.updateUserName(user.id, + firstName: firstName, lastName: lastName); + + userJsonNotifier.state = user.toJsonString(); + } + + void saveUser(ApiUser? user) { + userJsonNotifier.state = user?.toJsonString(); + } + + void _onUpdateUser({ApiUser? prevUser, ApiUser? currentUser}) { + _currentUser = currentUser; + } + Future updateCurrentUser(ApiUser user) async { await userService.updateUser(user); userJsonNotifier.state = user.toJsonString(); diff --git a/data/lib/service/space_service.dart b/data/lib/service/space_service.dart index 10a454ae..30352e00 100644 --- a/data/lib/service/space_service.dart +++ b/data/lib/service/space_service.dart @@ -1,49 +1,53 @@ import 'dart:async'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/api_user_service.dart'; import 'package:data/api/space/api_space_invitation_service.dart'; import 'package:data/api/space/api_space_service.dart'; import 'package:data/api/space/space_models.dart'; import 'package:data/service/place_service.dart'; -import 'package:data/utils/buffered_sender_keystore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:rxdart/rxdart.dart'; import '../api/auth/auth_models.dart'; import '../api/place/api_place.dart'; import '../storage/app_preferences.dart'; -import '../utils/distribution_key_generator.dart'; import 'location_service.dart'; -final spaceServiceProvider = Provider((ref) => SpaceService( - ref.read(currentUserPod), - ref.read(apiSpaceServiceProvider), - ref.read(apiSpaceInvitationServiceProvider), - ref.read(currentSpaceId.notifier), - ref.read(apiUserServiceProvider), - ref.read(locationServiceProvider), - ref.read(placeServiceProvider), - ref.read(bufferedSenderKeystoreProvider))); +final spaceServiceProvider = Provider((ref) { + final provider = SpaceService( + ref.read(currentUserPod), + ref.read(apiSpaceServiceProvider), + ref.read(apiSpaceInvitationServiceProvider), + ref.read(currentSpaceId.notifier), + ref.read(apiUserServiceProvider), + ref.read(locationServiceProvider), + ref.read(placeServiceProvider)); + + ref.listen(currentUserPod, (prev, user) { + provider._onUpdateUser(prevUser: prev, currentUser: user); + }); + + return provider; +}); class SpaceService { - final ApiUser? currentUser; + ApiUser? _currentUser; final ApiSpaceService spaceService; final ApiSpaceInvitationService spaceInvitationService; final StateController _currentSpaceIdController; final ApiUserService userService; final LocationService locationService; final PlaceService placeService; - final BufferedSenderKeystore bufferedSenderKeystore; SpaceService( - this.currentUser, + this._currentUser, this.spaceService, this.spaceInvitationService, this._currentSpaceIdController, this.userService, this.locationService, this.placeService, - this.bufferedSenderKeystore, ); String? get currentSpaceId => _currentSpaceIdController.state; @@ -52,20 +56,31 @@ class SpaceService { _currentSpaceIdController.state = value; } - Future createSpaceAndGetInviteCode(String spaceName) async { - final spaceId = await spaceService.createSpace(spaceName); + void _onUpdateUser({ApiUser? prevUser, ApiUser? currentUser}) { + _currentUser = currentUser; + } + + Future createSpaceAndGetInviteCode({ + required String spaceName, + required String userId, + required Blob? identityKeyPublic, + }) async { + final spaceId = + await spaceService.createSpace(spaceName, userId, identityKeyPublic); final generatedCode = await spaceInvitationService.createInvitation(spaceId); currentSpaceId = spaceId; return generatedCode; } - Future joinSpace(String spaceId, String userId) async { - await spaceService.joinSpace(spaceId, userId); + Future joinSpace(String spaceId) async { + final user = _currentUser; + if (user == null) return; + + await spaceService.joinSpace(spaceId, user.id, user.identity_key_public); await placeService.joinUserToExistingPlaces( - userId: userId, spaceId: spaceId); + userId: user.id, spaceId: spaceId); currentSpaceId = spaceId; - await _distributeSenderKeyToSpaceMembers(spaceId, userId); } Stream> streamAllSpace(String userId) { @@ -262,15 +277,4 @@ class SpaceService { return placesLists.expand((places) => places).toList(); }); } - - Future _distributeSenderKeyToSpaceMembers( - String spaceId, String userId) async { - final spaceMembers = await spaceService.getMembersBySpaceId(spaceId); - final membersKeyData = await generateMemberKeyData(spaceId, - senderUserId: userId, - spaceMembers: spaceMembers, - bufferedSenderKeyStore: bufferedSenderKeystore); - - await spaceService.updateGroupKeys(spaceId, userId, membersKeyData); - } } diff --git a/data/lib/storage/app_preferences.dart b/data/lib/storage/app_preferences.dart index 4aaa3a8e..5c94f47c 100644 --- a/data/lib/storage/app_preferences.dart +++ b/data/lib/storage/app_preferences.dart @@ -1,5 +1,4 @@ import 'dart:convert'; - import 'package:data/storage/preferences_provider.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index e9d1553b..5dc7f836 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -1,10 +1,7 @@ -import 'dart:convert'; import 'dart:math'; -import 'dart:typed_data'; -import 'package:data/utils/private_key_helper.dart'; +import 'package:data/log/logger.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:uuid/uuid.dart'; import '../api/space/api_group_key_model.dart'; import '../api/space/space_models.dart'; @@ -18,7 +15,7 @@ Future generateMemberKeyData(String spaceId, final deviceId = Random.secure().nextInt(0x7FFFFFFF); final groupAddress = SignalProtocolAddress(spaceId, deviceId); final sessionBuilder = GroupSessionBuilder(bufferedSenderKeyStore); - final senderKey = SenderKeyName(const Uuid().v4(), groupAddress); + final senderKey = SenderKeyName(Random().nextInt(0x7FFFFFFF).toString(), groupAddress); final distributionMessage = await sessionBuilder.create(senderKey); final distributionBytes = distributionMessage.serialize(); @@ -33,7 +30,7 @@ Future generateMemberKeyData(String spaceId, try { final publicKeyBytes = publicBlob.bytes; if (publicKeyBytes.length != 33) { - print("Invalid public key size for member ${member.user_id}"); + logger.e("Invalid public key size for member ${member.user_id}"); continue; } @@ -46,11 +43,12 @@ Future generateMemberKeyData(String spaceId, ); distributions.add(encryptedDistribution); } catch (e) { - print("Failed to decode public key for member ${member.user_id}: $e"); + logger.e("Failed to decode public key for member ${member.user_id}: $e"); continue; } } + return ApiMemberKeyData( data_updated_at: DateTime.now().millisecondsSinceEpoch, member_device_id: deviceId, diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index 69d0b633..cfd62407 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -7,8 +7,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import '../api/space/api_group_key_model.dart'; class EphemeralECDHUtils { - static const int syntheticIvLength = - 16; // Length of the synthetic initialization vector (IV). + static const int syntheticIvLength = 16; // Length of the synthetic initialization vector (IV). /// Encrypts the provided plaintext for a specific recipient using their public key. static Future encrypt( @@ -17,7 +16,7 @@ class EphemeralECDHUtils { ECPublicKey receiverPub, ) async { final ephemeralPubKey = Curve.generateKeyPair(); - final ephemeralPrivateKeyBytes = Curve.decodePrivatePoint(plaintext); + final ephemeralPrivateKeyBytes = ephemeralPubKey.privateKey; final masterSecret = Curve.calculateAgreement(receiverPub, ephemeralPrivateKeyBytes); @@ -79,12 +78,11 @@ class EphemeralECDHUtils { utf8.encode("cipher"), secretKey: masterSecret, ); - final cipherKey = await mac.calculateMac( syntheticIv, secretKey: SecretKey(cipherKeyMaterial.bytes), ); - return Uint8List.fromList(cipherKey.bytes); + return Uint8List.fromList(cipherKey.bytes.sublist(0, syntheticIvLength)); // TODO check if this is correct, getting 32 instead of 16 } } From 1b2fa151d037bd89e6eaa8c9be9c18be4b9d2e9f Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 15 Jan 2025 14:56:52 +0530 Subject: [PATCH 04/19] WIP - location e2ee --- app/lib/main.dart | 1 + .../space/join/join_space_view_model.dart | 2 +- app/pubspec.lock | 24 ++-- data/lib/api/space/api_group_key_model.dart | 9 +- .../space/api_group_key_model.freezed.dart | 109 ++++++++++------- data/lib/api/space/api_group_key_model.g.dart | 10 +- data/lib/service/location_manager.dart | 62 ++++++---- data/lib/service/location_service.dart | 73 +++++++++-- data/lib/utils/buffered_sender_keystore.dart | 17 ++- .../utils/ephemeral_distribution_helper.dart | 96 ++++++++++++--- data/lib/utils/private_key_helper.dart | 115 ++++++++++++++++++ 11 files changed, 393 insertions(+), 125 deletions(-) diff --git a/app/lib/main.dart b/app/lib/main.dart index f589a619..216da61c 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -101,6 +101,7 @@ Future _handleLocationUpdates(MethodCall call) async { ); if (locationPosition.latitude != 0 && locationPosition.longitude != 0) { + await LocationManager.instance.updateUserLocation(locationPosition); } } diff --git a/app/lib/ui/flow/space/join/join_space_view_model.dart b/app/lib/ui/flow/space/join/join_space_view_model.dart index 9f76731b..f59b03a0 100644 --- a/app/lib/ui/flow/space/join/join_space_view_model.dart +++ b/app/lib/ui/flow/space/join/join_space_view_model.dart @@ -116,7 +116,7 @@ class JoinSpaceViewNotifier extends StateNotifier { return; } - await spaceService.joinSpace(spaceId, _currentUser.id); + await spaceService.joinSpace(spaceId); final space = await spaceService.getSpace(spaceId); state = state.copyWith(space: space, verifying: false, spaceJoined: true); diff --git a/app/pubspec.lock b/app/pubspec.lock index f4ed3c64..23e0a0e1 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1209,18 +1209,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1273,18 +1273,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -1797,10 +1797,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" time: dependency: transitive description: @@ -1965,10 +1965,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/data/lib/api/space/api_group_key_model.dart b/data/lib/api/space/api_group_key_model.dart index 972051e1..826df61a 100644 --- a/data/lib/api/space/api_group_key_model.dart +++ b/data/lib/api/space/api_group_key_model.dart @@ -47,19 +47,20 @@ class EncryptedDistribution with _$EncryptedDistribution { const EncryptedDistribution._(); const factory EncryptedDistribution({ - @Default("") String recipientId, - @BlobConverter() required Blob ephemeralPub, + @Default("") String recipient_id, + @BlobConverter() required Blob ephemeral_pub, @BlobConverter() required Blob iv, @BlobConverter() required Blob ciphertext, + @Default(0) int created_at, }) = _EncryptedDistribution; factory EncryptedDistribution.fromJson(Map data) => _$EncryptedDistributionFromJson(data); void validateFieldSizes() { - if (ephemeralPub.bytes.length != 33 && ephemeralPub.bytes.isNotEmpty) { + if (ephemeral_pub.bytes.length != 33 && ephemeral_pub.bytes.isNotEmpty) { throw ArgumentError( - "Invalid size for ephemeralPub: expected 33 bytes, got ${ephemeralPub.bytes.length} bytes."); + "Invalid size for ephemeralPub: expected 33 bytes, got ${ephemeral_pub.bytes.length} bytes."); } if (iv.bytes.length != 16 && iv.bytes.isNotEmpty) { throw ArgumentError( diff --git a/data/lib/api/space/api_group_key_model.freezed.dart b/data/lib/api/space/api_group_key_model.freezed.dart index e00af8ba..5b3cb1ff 100644 --- a/data/lib/api/space/api_group_key_model.freezed.dart +++ b/data/lib/api/space/api_group_key_model.freezed.dart @@ -412,13 +412,14 @@ EncryptedDistribution _$EncryptedDistributionFromJson( /// @nodoc mixin _$EncryptedDistribution { - String get recipientId => throw _privateConstructorUsedError; + String get recipient_id => throw _privateConstructorUsedError; @BlobConverter() - Blob get ephemeralPub => throw _privateConstructorUsedError; + Blob get ephemeral_pub => throw _privateConstructorUsedError; @BlobConverter() Blob get iv => throw _privateConstructorUsedError; @BlobConverter() Blob get ciphertext => throw _privateConstructorUsedError; + int get created_at => throw _privateConstructorUsedError; /// Serializes this EncryptedDistribution to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -437,10 +438,11 @@ abstract class $EncryptedDistributionCopyWith<$Res> { _$EncryptedDistributionCopyWithImpl<$Res, EncryptedDistribution>; @useResult $Res call( - {String recipientId, - @BlobConverter() Blob ephemeralPub, + {String recipient_id, + @BlobConverter() Blob ephemeral_pub, @BlobConverter() Blob iv, - @BlobConverter() Blob ciphertext}); + @BlobConverter() Blob ciphertext, + int created_at}); } /// @nodoc @@ -459,19 +461,20 @@ class _$EncryptedDistributionCopyWithImpl<$Res, @pragma('vm:prefer-inline') @override $Res call({ - Object? recipientId = null, - Object? ephemeralPub = null, + Object? recipient_id = null, + Object? ephemeral_pub = null, Object? iv = null, Object? ciphertext = null, + Object? created_at = null, }) { return _then(_value.copyWith( - recipientId: null == recipientId - ? _value.recipientId - : recipientId // ignore: cast_nullable_to_non_nullable + recipient_id: null == recipient_id + ? _value.recipient_id + : recipient_id // ignore: cast_nullable_to_non_nullable as String, - ephemeralPub: null == ephemeralPub - ? _value.ephemeralPub - : ephemeralPub // ignore: cast_nullable_to_non_nullable + ephemeral_pub: null == ephemeral_pub + ? _value.ephemeral_pub + : ephemeral_pub // ignore: cast_nullable_to_non_nullable as Blob, iv: null == iv ? _value.iv @@ -481,6 +484,10 @@ class _$EncryptedDistributionCopyWithImpl<$Res, ? _value.ciphertext : ciphertext // ignore: cast_nullable_to_non_nullable as Blob, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, ) as $Val); } } @@ -495,10 +502,11 @@ abstract class _$$EncryptedDistributionImplCopyWith<$Res> @override @useResult $Res call( - {String recipientId, - @BlobConverter() Blob ephemeralPub, + {String recipient_id, + @BlobConverter() Blob ephemeral_pub, @BlobConverter() Blob iv, - @BlobConverter() Blob ciphertext}); + @BlobConverter() Blob ciphertext, + int created_at}); } /// @nodoc @@ -515,19 +523,20 @@ class __$$EncryptedDistributionImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? recipientId = null, - Object? ephemeralPub = null, + Object? recipient_id = null, + Object? ephemeral_pub = null, Object? iv = null, Object? ciphertext = null, + Object? created_at = null, }) { return _then(_$EncryptedDistributionImpl( - recipientId: null == recipientId - ? _value.recipientId - : recipientId // ignore: cast_nullable_to_non_nullable + recipient_id: null == recipient_id + ? _value.recipient_id + : recipient_id // ignore: cast_nullable_to_non_nullable as String, - ephemeralPub: null == ephemeralPub - ? _value.ephemeralPub - : ephemeralPub // ignore: cast_nullable_to_non_nullable + ephemeral_pub: null == ephemeral_pub + ? _value.ephemeral_pub + : ephemeral_pub // ignore: cast_nullable_to_non_nullable as Blob, iv: null == iv ? _value.iv @@ -537,6 +546,10 @@ class __$$EncryptedDistributionImplCopyWithImpl<$Res> ? _value.ciphertext : ciphertext // ignore: cast_nullable_to_non_nullable as Blob, + created_at: null == created_at + ? _value.created_at + : created_at // ignore: cast_nullable_to_non_nullable + as int, )); } } @@ -545,10 +558,11 @@ class __$$EncryptedDistributionImplCopyWithImpl<$Res> @JsonSerializable() class _$EncryptedDistributionImpl extends _EncryptedDistribution { const _$EncryptedDistributionImpl( - {this.recipientId = "", - @BlobConverter() required this.ephemeralPub, + {this.recipient_id = "", + @BlobConverter() required this.ephemeral_pub, @BlobConverter() required this.iv, - @BlobConverter() required this.ciphertext}) + @BlobConverter() required this.ciphertext, + this.created_at = 0}) : super._(); factory _$EncryptedDistributionImpl.fromJson(Map json) => @@ -556,20 +570,23 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { @override @JsonKey() - final String recipientId; + final String recipient_id; @override @BlobConverter() - final Blob ephemeralPub; + final Blob ephemeral_pub; @override @BlobConverter() final Blob iv; @override @BlobConverter() final Blob ciphertext; + @override + @JsonKey() + final int created_at; @override String toString() { - return 'EncryptedDistribution(recipientId: $recipientId, ephemeralPub: $ephemeralPub, iv: $iv, ciphertext: $ciphertext)'; + return 'EncryptedDistribution(recipient_id: $recipient_id, ephemeral_pub: $ephemeral_pub, iv: $iv, ciphertext: $ciphertext, created_at: $created_at)'; } @override @@ -577,19 +594,21 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { return identical(this, other) || (other.runtimeType == runtimeType && other is _$EncryptedDistributionImpl && - (identical(other.recipientId, recipientId) || - other.recipientId == recipientId) && - (identical(other.ephemeralPub, ephemeralPub) || - other.ephemeralPub == ephemeralPub) && + (identical(other.recipient_id, recipient_id) || + other.recipient_id == recipient_id) && + (identical(other.ephemeral_pub, ephemeral_pub) || + other.ephemeral_pub == ephemeral_pub) && (identical(other.iv, iv) || other.iv == iv) && (identical(other.ciphertext, ciphertext) || - other.ciphertext == ciphertext)); + other.ciphertext == ciphertext) && + (identical(other.created_at, created_at) || + other.created_at == created_at)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, recipientId, ephemeralPub, iv, ciphertext); + int get hashCode => Object.hash( + runtimeType, recipient_id, ephemeral_pub, iv, ciphertext, created_at); /// Create a copy of EncryptedDistribution /// with the given fields replaced by the non-null parameter values. @@ -610,27 +629,29 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { abstract class _EncryptedDistribution extends EncryptedDistribution { const factory _EncryptedDistribution( - {final String recipientId, - @BlobConverter() required final Blob ephemeralPub, - @BlobConverter() required final Blob iv, - @BlobConverter() required final Blob ciphertext}) = - _$EncryptedDistributionImpl; + {final String recipient_id, + @BlobConverter() required final Blob ephemeral_pub, + @BlobConverter() required final Blob iv, + @BlobConverter() required final Blob ciphertext, + final int created_at}) = _$EncryptedDistributionImpl; const _EncryptedDistribution._() : super._(); factory _EncryptedDistribution.fromJson(Map json) = _$EncryptedDistributionImpl.fromJson; @override - String get recipientId; + String get recipient_id; @override @BlobConverter() - Blob get ephemeralPub; + Blob get ephemeral_pub; @override @BlobConverter() Blob get iv; @override @BlobConverter() Blob get ciphertext; + @override + int get created_at; /// Create a copy of EncryptedDistribution /// with the given fields replaced by the non-null parameter values. diff --git a/data/lib/api/space/api_group_key_model.g.dart b/data/lib/api/space/api_group_key_model.g.dart index 07e24758..bcd61cb2 100644 --- a/data/lib/api/space/api_group_key_model.g.dart +++ b/data/lib/api/space/api_group_key_model.g.dart @@ -46,19 +46,21 @@ Map _$$ApiMemberKeyDataImplToJson( _$EncryptedDistributionImpl _$$EncryptedDistributionImplFromJson( Map json) => _$EncryptedDistributionImpl( - recipientId: json['recipientId'] as String? ?? "", - ephemeralPub: const BlobConverter().fromJson(json['ephemeralPub']), + recipient_id: json['recipient_id'] as String? ?? "", + ephemeral_pub: const BlobConverter().fromJson(json['ephemeral_pub']), iv: const BlobConverter().fromJson(json['iv']), ciphertext: const BlobConverter().fromJson(json['ciphertext']), + created_at: (json['created_at'] as num?)?.toInt() ?? 0, ); Map _$$EncryptedDistributionImplToJson( _$EncryptedDistributionImpl instance) => { - 'recipientId': instance.recipientId, - 'ephemeralPub': const BlobConverter().toJson(instance.ephemeralPub), + 'recipient_id': instance.recipient_id, + 'ephemeral_pub': const BlobConverter().toJson(instance.ephemeral_pub), 'iv': const BlobConverter().toJson(instance.iv), 'ciphertext': const BlobConverter().toJson(instance.ciphertext), + 'created_at': instance.created_at, }; _$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( diff --git a/data/lib/service/location_manager.dart b/data/lib/service/location_manager.dart index b6e4e806..ffab07a9 100644 --- a/data/lib/service/location_manager.dart +++ b/data/lib/service/location_manager.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:data/api/auth/auth_models.dart'; import 'package:data/repository/journey_repository.dart'; import 'package:data/service/location_service.dart'; import 'package:flutter/services.dart'; @@ -16,6 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../api/location/location.dart'; import '../log/logger.dart'; +import '../utils/private_key_helper.dart'; const MOVING_DISTANCE = 10; // meters const STEADY_DISTANCE = 50; // meters @@ -80,10 +82,12 @@ class LocationManager { isTrackingStarted = false; } - Future _getUserIdFromPreferences() async { + Future _getUserFromPreferences() async { final prefs = await SharedPreferences.getInstance(); final encodedUser = prefs.getString("user_account"); - return encodedUser != null ? jsonDecode(encodedUser)['id'] : null; + return encodedUser != null + ? ApiUser.fromJson(jsonDecode(encodedUser)) + : null; } void startTracking() async { @@ -91,8 +95,8 @@ class LocationManager { positionSubscription?.cancel(); - final userId = await _getUserIdFromPreferences(); - if (userId == null) return; + final user = await _getUserFromPreferences(); + if (user == null) return; positionSubscription = Geolocator.getPositionStream( locationSettings: LocationSettings( @@ -146,27 +150,20 @@ class LocationManager { } Future updateUserLocation(LocationData locationPosition) async { - final userId = await _getUserIdFromPreferences(); - if (userId != null) { - try { - await _locationService.saveCurrentLocation(userId, locationPosition); - - await _journeyRepository.saveLocationJourney( - extractedLocation: locationPosition, userId: userId); - } catch (error, stack) { - logger.e( - 'Error while updating user location and journey', - error: error, - stackTrace: stack, - ); - } - } - } - - Future saveLocation(LocationData locationPosition) async { - final userId = await _getUserIdFromPreferences(); - if (userId != null) { - await _locationService.saveCurrentLocation(userId, locationPosition); + final user = await _getUserFromPreferences(); + if (user == null) return; + + try { + await saveLocation(locationPosition); + + await _journeyRepository.saveLocationJourney( + extractedLocation: locationPosition, userId: user.id); + } catch (error, stack) { + logger.e( + 'Error while updating user location and journey', + error: error, + stackTrace: stack, + ); } } @@ -188,4 +185,19 @@ class LocationManager { movingDistance = isMoving ? MOVING_DISTANCE : STEADY_DISTANCE; startTracking(); } + + Future saveLocation(LocationData location) async { + try { + final prefs = await SharedPreferences.getInstance(); + final user = await _getUserFromPreferences(); + if (user == null) return; + + final passKey = prefs.getString("user_passkey"); + if (passKey == null) return; + + await _locationService.saveCurrentLocation(user, location, passKey); + } catch (e, s) { + logger.d("Failed to save encrypted location", error: e, stackTrace: s); + } + } } diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index 35037432..11c02d98 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -1,15 +1,18 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:data/log/logger.dart'; +import 'package:data/storage/app_preferences.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/auth/auth_models.dart'; import '../api/location/location.dart'; import '../api/network/client.dart'; +import '../api/space/api_group_key_model.dart'; +import '../utils/private_key_helper.dart'; -final locationServiceProvider = Provider((ref) => LocationService( - ref.read(firestoreProvider), - )); +final locationServiceProvider = + Provider((ref) => LocationService(ref.read(firestoreProvider))); class LocationService { final FirebaseFirestore _db; @@ -28,6 +31,17 @@ class LocationService { fromFirestore: ApiLocation.fromFireStore, toFirestore: (location, _) => location.toJson()); + DocumentReference spaceGroupKeysDocRef(String spaceId) { + return FirebaseFirestore.instance + .collection('spaces') + .doc(spaceId) + .collection('group_keys') + .doc('group_keys') + .withConverter( + fromFirestore: ApiGroupKey.fromFireStore, + toFirestore: (key, options) => key.toJson()); + } + Stream?> getCurrentLocationStream(String userId) { return _locationRef(userId) .where("user_id", isEqualTo: userId) @@ -55,19 +69,54 @@ class LocationService { return null; } + Future _getGroupKey(String spaceId) async { + final doc = await spaceGroupKeysDocRef(spaceId).get(); + return doc.data(); + } + Future saveCurrentLocation( - String userId, + ApiUser user, LocationData locationData, + String passKey, ) async { - final docRef = _locationRef(userId).doc(); + if (user.identity_key_private == null || + (user.identity_key_private?.bytes.isEmpty ?? true)) return; - final location = ApiLocation( - id: docRef.id, - user_id: userId, - latitude: locationData.latitude, - longitude: locationData.longitude, - created_at: DateTime.now().millisecondsSinceEpoch); + user.space_ids?.forEach((spaceId) async { + final groupKey = await _getGroupKey(spaceId); + if (groupKey == null) { + logger.e('LocationService: Group key not found for space $spaceId'); + return; + } - await docRef.set(location); + final memberKeyData = groupKey.member_keys[user.id]; + if (memberKeyData == null) { + logger.e( + 'LocationService: Member key not found for user ${user.id} in space $spaceId'); + return; + } + + final distributions = memberKeyData.distributions + .where((d) => d.recipient_id == user.id) + .toList() + ..sort((a, b) => b.created_at.compareTo(a.created_at)); + + final distribution = distributions.firstOrNull; + if (distribution == null) { + logger.e( + 'LocationService: Distribution not found for user ${user.id} in space $spaceId'); + return; + } + + final data = getGroupCipherAndDistributionMessage( + spaceId: spaceId, + deviceId: memberKeyData.member_device_id, + distribution: distribution, + privateKeyBytes: user.identity_key_private!.bytes, + salt: user.identity_key_salt!.bytes, + passkey: passKey, + bufferedSenderKeyStore: , + ); + }); } } diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index 3cb4d4d3..099d68a8 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/auth_models.dart'; -import 'package:data/api/space/api_space_service.dart'; import 'package:data/log/logger.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; @@ -17,14 +16,6 @@ final bufferedSenderKeystoreProvider = Provider((ref) => BufferedSenderKeystore( ref.read(currentUserJsonPod.notifier), )); -class StoreKey { - final SignalProtocolAddress address; - final String groupId; - final int senderDeviceId; - - StoreKey(this.address, this.groupId, this.senderDeviceId); -} - class BufferedSenderKeystore extends SenderKeyStore { final StateController senderKeyJsonState; final StateController userJsonState; @@ -159,3 +150,11 @@ class BufferedSenderKeystore extends SenderKeyStore { } } } + +class StoreKey { + final SignalProtocolAddress address; + final String groupId; + final int senderDeviceId; + + StoreKey(this.address, this.groupId, this.senderDeviceId); +} diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index cfd62407..90937c1f 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -2,12 +2,15 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cryptography/cryptography.dart'; +import 'package:data/log/logger.dart'; +import 'package:flutter/foundation.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import '../api/space/api_group_key_model.dart'; class EphemeralECDHUtils { - static const int syntheticIvLength = 16; // Length of the synthetic initialization vector (IV). + static const int syntheticIvLength = + 16; // Length of the synthetic initialization vector (IV). /// Encrypts the provided plaintext for a specific recipient using their public key. static Future encrypt( @@ -21,10 +24,12 @@ class EphemeralECDHUtils { Curve.calculateAgreement(receiverPub, ephemeralPrivateKeyBytes); // Compute synthetic IV - final syntheticIv = await _computeSyntheticIv(SecretKey(masterSecret), plaintext); + final syntheticIv = + await _computeSyntheticIv(SecretKey(masterSecret), plaintext); // Compute cipher key - final cipherKey = await _computeCipherKey(SecretKey(masterSecret), syntheticIv); + final cipherKey = + await _computeCipherKey(SecretKey(masterSecret), syntheticIv); // Encrypt plaintext final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); @@ -36,20 +41,21 @@ class EphemeralECDHUtils { ); final ciphertext = Uint8List.fromList(secretBox.cipherText); - return EncryptedDistribution( - recipientId: receiverId, - ephemeralPub: Blob(ephemeralPubKey.publicKey.serialize()), + final distribution = EncryptedDistribution( + recipient_id: receiverId, + ephemeral_pub: Blob(ephemeralPubKey.publicKey.serialize()), iv: Blob(Uint8List.fromList(syntheticIv)), ciphertext: Blob(ciphertext), ); - + distribution.validateFieldSizes(); + return distribution; } /// Computes a synthetic IV using the master secret and plaintext. static Future _computeSyntheticIv( - SecretKey sharedSecret, - Uint8List plaintext, - ) async { + SecretKey sharedSecret, + Uint8List plaintext, + ) async { final mac = Hmac.sha256(); // Derive synthetic IV key @@ -68,9 +74,9 @@ class EphemeralECDHUtils { } static Future _computeCipherKey( - SecretKey masterSecret, - Uint8List syntheticIv, - ) async { + SecretKey masterSecret, + Uint8List syntheticIv, + ) async { final mac = Hmac.sha256(); // Derive cipher key @@ -78,11 +84,73 @@ class EphemeralECDHUtils { utf8.encode("cipher"), secretKey: masterSecret, ); + final cipherKey = await mac.calculateMac( syntheticIv, secretKey: SecretKey(cipherKeyMaterial.bytes), ); - return Uint8List.fromList(cipherKey.bytes.sublist(0, syntheticIvLength)); // TODO check if this is correct, getting 32 instead of 16 + return Uint8List.fromList(cipherKey.bytes.sublist(0, + syntheticIvLength)); // TODO check if this is correct, getting 32 instead of 16 + } + + // Decrypts the provided ciphertext using the provided private key. + static Future decrypt( + EncryptedDistribution message, + ECPrivateKey receiverPrivateKey, + ) async { + try { + final mac = Hmac.sha256(); + + final syntheticIv = message.iv; + final cipherText = message.ciphertext; + + final ephemeralPublic = Curve.decodePoint(message.ephemeral_pub.bytes, 0); + final masterSecret = + Curve.calculateAgreement(ephemeralPublic, receiverPrivateKey); + + final cipherKeyPart1 = await mac.calculateMac( + utf8.encode("cipher"), + secretKey: SecretKey(masterSecret), + ); + + final cipherKey = await mac.calculateMac( + cipherKeyPart1.bytes, + secretKey: SecretKey(syntheticIv.bytes), + ); + + // Encrypt plaintext + final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); + final secretKey = SecretKey(cipherKey.bytes); + final secretBox = await algorithm.encrypt( + cipherText.bytes, + secretKey: secretKey, + nonce: syntheticIv.bytes, + ); + + final plaintext = Uint8List.fromList(secretBox.cipherText); + + final verificationPart1 = await mac.calculateMac( + utf8.encode("auth"), + secretKey: SecretKey(masterSecret), + ); + + final verificationPart2 = await mac.calculateMac( + plaintext, + secretKey: SecretKey(verificationPart1.bytes), + ); + + final ourSyntheticIv = verificationPart2.bytes.sublist(0, 16); + + if (!listEquals(ourSyntheticIv, syntheticIv.bytes)) { + throw Exception( + "The computed syntheticIv didn't match the actual syntheticIv."); + } + + return plaintext; + } catch (e, s) { + logger.e("Error while decrypting", error: e, stackTrace: s); + return null; + } } } diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index eb940306..ac7e30c5 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -1,8 +1,15 @@ // ignore_for_file: constant_identifier_names import 'package:cryptography/cryptography.dart'; +import 'package:data/log/logger.dart'; +import 'package:data/utils/ephemeral_distribution_helper.dart'; import 'dart:typed_data'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; + +import '../api/space/api_group_key_model.dart'; +import 'buffered_sender_keystore.dart'; + class EncryptionException implements Exception { final String message; final dynamic cause; @@ -57,3 +64,111 @@ Future encryptPrivateKey( final key = await _deriveKeyFromPasskey(passkey, salt); return await _encryptData(privateKey, key); } + +// Decrypts the provided ciphertext using the provided private key. +Future _decryptData( + Uint8List encryptedPrivateKey, Uint8List salt, String passkey) async { + try { + final key = await _deriveKeyFromPasskey(passkey, salt); + if (encryptedPrivateKey.length < GCM_IV_SIZE) { + throw EncryptionException("Encrypted data is too short"); + } + + final iv = Uint8List(GCM_IV_SIZE) + ..setAll(0, List.generate(GCM_IV_SIZE, (i) => (i + 1) % 256)); + + final ciphertext = encryptedPrivateKey.sublist(GCM_IV_SIZE); + + final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); + + final secretBox = SecretBox( + ciphertext, + nonce: iv, + mac: Mac.empty, + ); + + final clearText = await algorithm.decrypt( + secretBox, + secretKey: key, + ); + + return Uint8List.fromList(clearText); + } catch (e) { + throw EncryptionException("Decryption failed", e); + } +} + +Future?> + getGroupCipherAndDistributionMessage({ + required String spaceId, + required int deviceId, + required Uint8List privateKeyBytes, + required Uint8List salt, + required String passkey, + required EncryptedDistribution distribution, + required BufferedSenderKeystore bufferedSenderKeyStore, +}) async { + final privateKey = await _decodePrivateKey( + privateKeyBytes: privateKeyBytes, + salt: salt, + passkey: passkey, + ); + + if (privateKey == null) { + return null; + } + + final decryptedDistribution = + await EphemeralECDHUtils.decrypt(distribution, privateKey); + + if (decryptedDistribution == null) { + return null; + } + + final distributionMessage = + SenderKeyDistributionMessageWrapper.fromSerialized( + decryptedDistribution, + ); + + final groupAddress = SignalProtocolAddress(spaceId, deviceId); + + final senderKey = SenderKeyName( + distributionMessage.id.toString(), + groupAddress, + ); + + bufferedSenderKeyStore.loadSenderKey(senderKey); + + // TODO rotate sender key + + try { + GroupSessionBuilder(bufferedSenderKeyStore) + .process(senderKey, distributionMessage); + final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); + return Pair(distributionMessage, groupCipher); + } catch (e, s) { + logger.e("Error processing group session", error: e, stackTrace: s); + return null; + } +} + +Future _decodePrivateKey( + {required Uint8List privateKeyBytes, + required Uint8List salt, + required String passkey}) async { + try { + return Curve.decodePrivatePoint(privateKeyBytes); + } catch (e, s) { + logger.d("Error decoding private key", error: e, stackTrace: s); + final decodedPrivateKey = + await _decryptData(privateKeyBytes, salt, passkey); + return Curve.decodePrivatePoint(decodedPrivateKey); + } +} + +class Pair { + final T1 first; + final T2 second; + + Pair(this.first, this.second); +} From 30514892eacaf53d2f2c887599dd4bd09db20349 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 15 Jan 2025 18:49:19 +0530 Subject: [PATCH 05/19] WIP - location encryption --- app/lib/main.dart | 45 ++++++------ .../add/locate/locate_on_map_view_model.dart | 15 ++-- data/lib/api/space/api_group_key_model.dart | 1 + data/lib/api/space/api_sender_key_record.dart | 2 + data/lib/api/space/api_space_service.dart | 53 +++++++------- data/lib/repository/journey_repository.dart | 14 ++-- data/lib/service/location_manager.dart | 17 ++--- data/lib/service/location_service.dart | 69 ++++++++++++------- data/lib/service/network_service.dart | 19 +++-- data/lib/service/space_service.dart | 2 +- data/lib/utils/buffered_sender_keystore.dart | 7 +- data/lib/utils/private_key_helper.dart | 13 +--- 12 files changed, 141 insertions(+), 116 deletions(-) diff --git a/app/lib/main.dart b/app/lib/main.dart index 216da61c..5605aefc 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -34,37 +34,37 @@ void main() async { } final container = await _initContainer(); - FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + FirebaseMessaging.onBackgroundMessage((message) { + return firebaseMessagingBackgroundHandler(message, container); + }); FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; - if (Platform.isAndroid) _configureService(); + if (Platform.isAndroid) _configureService(container); if (await Permission.location.isGranted && Platform.isIOS) { await locationMethodChannel.invokeMethod('startTracking'); } - locationMethodChannel.setMethodCallHandler(_handleLocationUpdates); + locationMethodChannel.setMethodCallHandler((call) { + return _handleLocationUpdates(call, container); + }); runApp( UncontrolledProviderScope(container: container, child: const App()), ); } -Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { +Future firebaseMessagingBackgroundHandler( + RemoteMessage message, ProviderContainer container) async { await Firebase.initializeApp(); - final networkService = NetworkService(); - updateCurrentUserState(message, networkService); -} + final networkService = container.read(networkServiceProvider); -void updateCurrentUserState( - RemoteMessage message, NetworkService networkService) { final String? userId = message.data[NotificationNetworkStatusConst.KEY_USER_ID]; - final bool isTypeNetworkStatus = - message + final bool isTypeNetworkStatus = message .data[NotificationNetworkStatusConst.NOTIFICATION_TYPE_NETWORK_STATUS]; final bool isTypeUpdateState = - message.data[NotificationUpdateStateConst.NOTIFICATION_TYPE_UPDATE_STATE]; + message.data[NotificationUpdateStateConst.NOTIFICATION_TYPE_UPDATE_STATE]; if (userId != null && (isTypeNetworkStatus || isTypeUpdateState)) { networkService.updateUserNetworkState(userId); @@ -88,7 +88,8 @@ Future _initContainer() async { return container; } -Future _handleLocationUpdates(MethodCall call) async { +Future _handleLocationUpdates( + MethodCall call, ProviderContainer container) async { if (call.method == 'onLocationUpdate') { final Map locationData = Map.from(call.arguments); @@ -101,17 +102,20 @@ Future _handleLocationUpdates(MethodCall call) async { ); if (locationPosition.latitude != 0 && locationPosition.longitude != 0) { - - await LocationManager.instance.updateUserLocation(locationPosition); + await container + .read(locationManagerProvider) + .updateUserLocation(locationPosition); } } } // Android background location getting -void _configureService() async { +void _configureService(ProviderContainer container) async { await bgService.configure( androidConfiguration: AndroidConfiguration( - onStart: onStart, + onStart: (service) { + onStart(service, container); + }, autoStart: false, isForegroundMode: true, notificationChannelId: NOTIFICATION_CHANNEL_ID, @@ -121,12 +125,13 @@ void _configureService() async { ); if (await Permission.location.isGranted) { - LocationManager.instance.startService(); + container.read(locationManagerProvider).startService(); } } @pragma('vm:entry-point') -Future onStart(ServiceInstance service) async { +Future onStart( + ServiceInstance service, ProviderContainer container) async { WidgetsFlutterBinding.ensureInitialized(); if (!await Permission.location.isGranted) return; @@ -153,7 +158,7 @@ Future onStart(ServiceInstance service) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - LocationManager.instance.startTracking(); + container.read(locationManagerProvider).startTracking(); service.on('stopService').listen((event) { service.stopSelf(); diff --git a/app/lib/ui/flow/geofence/add/locate/locate_on_map_view_model.dart b/app/lib/ui/flow/geofence/add/locate/locate_on_map_view_model.dart index 13c31aba..610135f7 100644 --- a/app/lib/ui/flow/geofence/add/locate/locate_on_map_view_model.dart +++ b/app/lib/ui/flow/geofence/add/locate/locate_on_map_view_model.dart @@ -44,14 +44,15 @@ class LocateOnMapVieNotifier extends StateNotifier { void getCurrentUserLocation() async { try { + if (spaceService.currentSpaceId?.isEmpty ?? true) return; + final isEnabled = await permissionService.isLocationPermissionGranted(); if (isEnabled && _currentUser != null) { state = state.copyWith(loading: true); - final location = - await locationService.getCurrentLocation(_currentUser.id); + final location = await locationService.getCurrentLocation( + spaceService.currentSpaceId!, _currentUser.id); if (location != null) { - final latLng = - LatLng(location.latitude, location.longitude); + final latLng = LatLng(location.latitude, location.longitude); state = state.copyWith( currentLatLng: latLng, centerPosition: @@ -81,9 +82,9 @@ class LocateOnMapVieNotifier extends StateNotifier { void onTapAddPlaceBtn(String spaceId, String placeName) async { try { if (_currentUser != null && state.cameraLatLng != null) { - state = state.copyWith(addingPlace: true); - final members = await spaceService.getMemberBySpaceId(spaceId); - final memberIds = members.map((member) => member.user_id).toList(); + state = state.copyWith(addingPlace: true); + final members = await spaceService.getMemberBySpaceId(spaceId); + final memberIds = members.map((member) => member.user_id).toList(); await placesService.addPlace( spaceId, placeName, diff --git a/data/lib/api/space/api_group_key_model.dart b/data/lib/api/space/api_group_key_model.dart index 826df61a..0b147206 100644 --- a/data/lib/api/space/api_group_key_model.dart +++ b/data/lib/api/space/api_group_key_model.dart @@ -1,3 +1,4 @@ +// ignore_for_file: constant_identifier_names import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/converter/blob_converter.dart'; diff --git a/data/lib/api/space/api_sender_key_record.dart b/data/lib/api/space/api_sender_key_record.dart index 1b2bc113..d2cea53f 100644 --- a/data/lib/api/space/api_sender_key_record.dart +++ b/data/lib/api/space/api_sender_key_record.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../converter/blob_converter.dart'; diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index 7ed2d2e6..c3832a71 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/network/client.dart'; import 'package:data/api/space/api_group_key_model.dart'; @@ -15,6 +17,12 @@ final apiSpaceServiceProvider = StateProvider((ref) => ApiSpaceService( ref.read(bufferedSenderKeystoreProvider), )); +const FIRESTORE_SPACE_PATH = 'space'; +const FIRESTORE_SPACE_MEMBER_PATH = 'space_members'; +const FIRESTORE_SPACE_MEMBER_SENDER_KEY_RECORD = 'sender_key_record'; +const FIRESTORE_SPACE_MEMBER_LOCATION_PATH = 'user_locations'; +const FIRESTORE_SPACE_GROUP_KEYS_PATH = 'group_keys'; + class ApiSpaceService { final FirebaseFirestore _db; final ApiUserService userService; @@ -26,24 +34,27 @@ class ApiSpaceService { this.bufferedSenderKeystore, ); - CollectionReference get _spaceRef => - _db.collection("spaces").withConverter( + CollectionReference get _spaceRef => + _db.collection(FIRESTORE_SPACE_PATH).withConverter( fromFirestore: ApiSpace.fromFireStore, toFirestore: (space, options) => space.toJson()); - CollectionReference spaceMemberRef(String spaceId) { + CollectionReference spaceMemberRef(String spaceId) { return FirebaseFirestore.instance - .collection('spaces') + .collection(FIRESTORE_SPACE_PATH) .doc(spaceId) - .collection('space_members'); + .collection(FIRESTORE_SPACE_MEMBER_PATH) + .withConverter( + fromFirestore: ApiSpaceMember.fromFireStore, + toFirestore: (member, options) => member.toJson()); } DocumentReference spaceGroupKeysDocRef(String spaceId) { return FirebaseFirestore.instance - .collection('spaces') + .collection(FIRESTORE_SPACE_PATH) .doc(spaceId) - .collection('group_keys') - .doc('group_keys') + .collection(FIRESTORE_SPACE_GROUP_KEYS_PATH) + .doc(FIRESTORE_SPACE_GROUP_KEYS_PATH) .withConverter( fromFirestore: ApiGroupKey.fromFireStore, toFirestore: (key, options) => key.toJson()); @@ -65,22 +76,22 @@ class ApiSpaceService { final emptyGroupKeys = ApiGroupKey(doc_updated_at: DateTime.now().millisecondsSinceEpoch); - await spaceGroupKeysDocRef(doc.id).set(emptyGroupKeys); await joinSpace(doc.id, adminId, identityKeyPublic); + await spaceGroupKeysDocRef(doc.id).set(emptyGroupKeys); return doc.id; } Future getSpace(String spaceId) async { + print("XXX getSpace $spaceId"); final docSnapshot = await _spaceRef.doc(spaceId).get(); if (docSnapshot.exists) { - return docSnapshot.data() as ApiSpace; + return docSnapshot.data(); } return null; } Future joinSpace(String spaceId, String userId, Blob? identityKeyPublic, {int role = SPACE_MEMBER_ROLE_MEMBER}) async { - final member = ApiSpaceMember( space_id: spaceId, user_id: userId, @@ -91,35 +102,27 @@ class ApiSpaceService { created_at: DateTime.now().millisecondsSinceEpoch, ); - await spaceMemberRef(spaceId).doc(userId).set(member.toJson()); + await spaceMemberRef(spaceId).doc(userId).set(member); await userService.addSpaceId(userId, spaceId); await _distributeSenderKeyToSpaceMembers(spaceId, userId); } Future> getMembersBySpaceId(String spaceId) async { - final collectionRef = FirebaseFirestore.instance - .collection('spaces') - .doc(spaceId) - .collection('space_members'); - - final querySnapshot = await collectionRef.get(); + final querySnapshot = await spaceMemberRef(spaceId).get(); return querySnapshot.docs.map((doc) { - return ApiSpaceMember.fromJson(doc.data()); + return doc.data(); }).toList(); } Stream> getStreamSpaceMemberBySpaceId(String spaceId) { return spaceMemberRef(spaceId).snapshots().map((querySnapshot) => - querySnapshot.docs - .map((doc) => - ApiSpaceMember.fromJson(doc.data() as Map)) - .toList()); + querySnapshot.docs.map((doc) => doc.data()).toList()); } Future> getSpaceMemberByUserId(String userId) async { final querySnapshot = await _spaceRef.firestore - .collectionGroup('space_members') + .collectionGroup(FIRESTORE_SPACE_MEMBER_PATH) .where("user_id", isEqualTo: userId) .get(); @@ -130,7 +133,7 @@ class ApiSpaceService { Stream> streamSpaceMemberByUserId(String userId) { return _spaceRef.firestore - .collectionGroup('space_members') + .collectionGroup(FIRESTORE_SPACE_MEMBER_PATH) .where('user_id', isEqualTo: userId) .snapshots() .map((querySnapshot) { diff --git a/data/lib/repository/journey_repository.dart b/data/lib/repository/journey_repository.dart index d7d58bc2..b664ccda 100644 --- a/data/lib/repository/journey_repository.dart +++ b/data/lib/repository/journey_repository.dart @@ -7,30 +7,24 @@ import 'package:data/api/location/journey/journey.dart'; import 'package:data/api/location/location.dart'; import 'package:data/log/logger.dart'; import 'package:data/repository/journey_generator.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/location/journey/api_journey_service.dart'; import '../storage/location_caches.dart'; -class JourneyRepository { - static JourneyRepository? _instance; +final journeyRepositoryProvider = + Provider((ref) => JourneyRepository(ref.read(journeyServiceProvider))); +class JourneyRepository { final ApiJourneyService journeyService; final LocationCache locationCache = LocationCache.instance; JourneyRepository(this.journeyService); - static JourneyRepository get instance { - _instance ??= JourneyRepository( - ApiJourneyService(FirebaseFirestore.instance), - ); - return _instance!; - } - Future saveLocationJourney({ required LocationData extractedLocation, required String userId, }) async { - try { _cacheLocations(extractedLocation, userId); diff --git a/data/lib/service/location_manager.dart b/data/lib/service/location_manager.dart index ffab07a9..61f70eaf 100644 --- a/data/lib/service/location_manager.dart +++ b/data/lib/service/location_manager.dart @@ -17,32 +17,24 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../api/location/location.dart'; import '../log/logger.dart'; -import '../utils/private_key_helper.dart'; const MOVING_DISTANCE = 10; // meters const STEADY_DISTANCE = 50; // meters -final locationManagerProvider = Provider((ref) => LocationManager.instance); +final locationManagerProvider = Provider((ref) => LocationManager( + ref.read(locationServiceProvider), + ref.read(journeyRepositoryProvider), + )); final bgService = FlutterBackgroundService(); const locationMethodChannel = MethodChannel('com.grouptrack/location'); class LocationManager { - static LocationManager? _instance; - final LocationService _locationService; final JourneyRepository _journeyRepository; LocationManager(this._locationService, this._journeyRepository); - static LocationManager get instance { - _instance ??= LocationManager( - LocationService(FirebaseFirestore.instance), - JourneyRepository.instance, - ); - return _instance!; - } - StreamSubscription? positionSubscription; Position? _lastPosition; Position? _movingPosition; @@ -154,6 +146,7 @@ class LocationManager { if (user == null) return; try { + print("XXX updateUserLocation ${locationPosition}"); await saveLocation(locationPosition); await _journeyRepository.saveLocationJourney( diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index 11c02d98..1123397a 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -1,50 +1,52 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/log/logger.dart'; -import 'package:data/storage/app_preferences.dart'; +import 'package:data/utils/buffered_sender_keystore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/auth/auth_models.dart'; import '../api/location/location.dart'; import '../api/network/client.dart'; import '../api/space/api_group_key_model.dart'; +import '../api/space/api_space_service.dart'; import '../utils/private_key_helper.dart'; -final locationServiceProvider = - Provider((ref) => LocationService(ref.read(firestoreProvider))); +final locationServiceProvider = Provider((ref) => LocationService( + ref.read(firestoreProvider), ref.read(bufferedSenderKeystoreProvider))); class LocationService { final FirebaseFirestore _db; + final BufferedSenderKeystore _bufferedSenderKeystore; - LocationService(this._db); + LocationService(this._db, this._bufferedSenderKeystore); - CollectionReference get _userRef => - _db.collection("users").withConverter( - fromFirestore: ApiUser.fromFireStore, - toFirestore: (user, options) => user.toJson()); - - CollectionReference _locationRef(String userId) => _userRef + CollectionReference _locationRef(String spaceID, String userId) => _db + .collection(FIRESTORE_SPACE_PATH) + .doc(spaceID) + .collection(FIRESTORE_SPACE_MEMBER_PATH) .doc(userId) - .collection("user_locations") + .collection(FIRESTORE_SPACE_MEMBER_LOCATION_PATH) .withConverter( fromFirestore: ApiLocation.fromFireStore, toFirestore: (location, _) => location.toJson()); DocumentReference spaceGroupKeysDocRef(String spaceId) { - return FirebaseFirestore.instance - .collection('spaces') + return _db + .collection(FIRESTORE_SPACE_PATH) .doc(spaceId) - .collection('group_keys') - .doc('group_keys') + .collection(FIRESTORE_SPACE_GROUP_KEYS_PATH) + .doc(FIRESTORE_SPACE_GROUP_KEYS_PATH) .withConverter( fromFirestore: ApiGroupKey.fromFireStore, toFirestore: (key, options) => key.toJson()); } - Stream?> getCurrentLocationStream(String userId) { - return _locationRef(userId) - .where("user_id", isEqualTo: userId) + Stream?> getCurrentLocationStream( + String spaceId, String userId) { + return _locationRef(spaceId, userId) .orderBy('created_at', descending: true) .limit(1) .snapshots() @@ -56,9 +58,8 @@ class LocationService { }); } - Future getCurrentLocation(String userId) async { - var snapshot = await _locationRef(userId) - .where("user_id", isEqualTo: userId) + Future getCurrentLocation(String spaceId, String userId) async { + var snapshot = await _locationRef(spaceId, userId) .orderBy('created_at', descending: true) .limit(1) .get(); @@ -108,15 +109,37 @@ class LocationService { return; } - final data = getGroupCipherAndDistributionMessage( + final groupCipher = await getGroupCipher( spaceId: spaceId, deviceId: memberKeyData.member_device_id, distribution: distribution, privateKeyBytes: user.identity_key_private!.bytes, salt: user.identity_key_salt!.bytes, passkey: passKey, - bufferedSenderKeyStore: , + bufferedSenderKeyStore: _bufferedSenderKeystore, ); + + if (groupCipher == null) { + logger.e('LocationService: Error while getting group cipher'); + return; + } + + final encryptedLatitude = await groupCipher.encrypt( + Uint8List.fromList(utf8.encode(locationData.latitude.toString()))); + final encryptedLongitude = await groupCipher.encrypt( + Uint8List.fromList(utf8.encode(locationData.longitude.toString()))); + + final docRef = _locationRef(spaceId, user.id).doc(); + + final location = EncryptedApiLocation( + id: docRef.id, + user_id: user.id, + latitude: Blob(encryptedLatitude), + longitude: Blob(encryptedLongitude), + created_at: locationData.timestamp.millisecondsSinceEpoch, + ); + + await docRef.set(location); }); } } diff --git a/data/lib/service/network_service.dart b/data/lib/service/network_service.dart index 7d755b52..d9375989 100644 --- a/data/lib/service/network_service.dart +++ b/data/lib/service/network_service.dart @@ -2,15 +2,25 @@ import 'package:battery_plus/battery_plus.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; import '../api/auth/auth_models.dart'; import '../api/location/location.dart'; +import '../api/network/client.dart'; import '../log/logger.dart'; import 'location_manager.dart'; +final networkServiceProvider = Provider((ref) => NetworkService( + ref.read(locationManagerProvider), + ref.read(firestoreProvider), + )); + class NetworkService { - final FirebaseFirestore _db = FirebaseFirestore.instance; + final LocationManager locationManager; + final FirebaseFirestore _db; + + NetworkService(this.locationManager, this._db); CollectionReference get _userRef => _db.collection("users").withConverter( @@ -45,16 +55,15 @@ class NetworkService { void _updateLocation(String userId) async { try { - final position = await LocationManager.instance - .getLastLocation(timeout: const Duration(seconds: 30)); + final position = await locationManager.getLastLocation( + timeout: const Duration(seconds: 30)); if (position != null) { final location = LocationData( latitude: position.latitude, longitude: position.longitude, timestamp: position.timestamp); - LocationManager.instance.saveLocation(location); - + locationManager.saveLocation(location); } } catch (e, s) { logger.e("NetworkService: Error while update user location", diff --git a/data/lib/service/space_service.dart b/data/lib/service/space_service.dart index 0586ede3..fd87f520 100644 --- a/data/lib/service/space_service.dart +++ b/data/lib/service/space_service.dart @@ -247,7 +247,7 @@ class SpaceService { List> userInfoStreams = members.map((member) { return CombineLatestStream.combine4( userService.getUserStream(member.user_id), - locationService.getCurrentLocationStream(member.user_id), + locationService.getCurrentLocationStream(spaceId, member.user_id), Stream.value(member.location_enabled), userService.getUserSessionStream(member.user_id), (user, location, isLocationEnabled, session) { diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index 099d68a8..daf0956a 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -8,6 +8,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import '../api/network/client.dart'; import '../api/space/api_group_key_model.dart'; +import '../api/space/api_space_service.dart'; import '../storage/app_preferences.dart'; final bufferedSenderKeystoreProvider = Provider((ref) => BufferedSenderKeystore( @@ -28,11 +29,11 @@ class BufferedSenderKeystore extends SenderKeyStore { CollectionReference senderKeyRef( String spaceId, String userId) { return _db - .collection('spaces') + .collection(FIRESTORE_SPACE_PATH) .doc(spaceId) - .collection('space_members') + .collection(FIRESTORE_SPACE_MEMBER_PATH) .doc(userId) - .collection('sender_key_record') + .collection(FIRESTORE_SPACE_MEMBER_SENDER_KEY_RECORD) .withConverter( fromFirestore: ApiSenderKeyRecord.fromFireStore, toFirestore: (senderKey, options) => senderKey.toJson()); diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index ac7e30c5..4f4fa66e 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -98,8 +98,8 @@ Future _decryptData( } } -Future?> - getGroupCipherAndDistributionMessage({ +Future + getGroupCipher({ required String spaceId, required int deviceId, required Uint8List privateKeyBytes, @@ -145,7 +145,7 @@ Future?> GroupSessionBuilder(bufferedSenderKeyStore) .process(senderKey, distributionMessage); final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); - return Pair(distributionMessage, groupCipher); + return groupCipher; } catch (e, s) { logger.e("Error processing group session", error: e, stackTrace: s); return null; @@ -165,10 +165,3 @@ Future _decodePrivateKey( return Curve.decodePrivatePoint(decodedPrivateKey); } } - -class Pair { - final T1 first; - final T2 second; - - Pair(this.first, this.second); -} From 977d1059827b60818b6818478a509fff7ea4b150 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Thu, 16 Jan 2025 14:32:04 +0530 Subject: [PATCH 06/19] WIP - location encryption --- data/lib/api/space/api_space_service.dart | 5 +- data/lib/service/auth_service.dart | 5 +- data/lib/service/location_manager.dart | 9 +- data/lib/service/location_service.dart | 153 +++++++++++++++--- data/lib/service/place_service.dart | 3 +- .../utils/ephemeral_distribution_helper.dart | 4 +- data/lib/utils/private_key_helper.dart | 76 +++++---- 7 files changed, 178 insertions(+), 77 deletions(-) diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index c3832a71..864e11b7 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -17,7 +17,7 @@ final apiSpaceServiceProvider = StateProvider((ref) => ApiSpaceService( ref.read(bufferedSenderKeystoreProvider), )); -const FIRESTORE_SPACE_PATH = 'space'; +const FIRESTORE_SPACE_PATH = 'spaces'; const FIRESTORE_SPACE_MEMBER_PATH = 'space_members'; const FIRESTORE_SPACE_MEMBER_SENDER_KEY_RECORD = 'sender_key_record'; const FIRESTORE_SPACE_MEMBER_LOCATION_PATH = 'user_locations'; @@ -76,8 +76,9 @@ class ApiSpaceService { final emptyGroupKeys = ApiGroupKey(doc_updated_at: DateTime.now().millisecondsSinceEpoch); - await joinSpace(doc.id, adminId, identityKeyPublic); await spaceGroupKeysDocRef(doc.id).set(emptyGroupKeys); + await joinSpace(doc.id, adminId, identityKeyPublic); + return doc.id; } diff --git a/data/lib/service/auth_service.dart b/data/lib/service/auth_service.dart index d4031883..5b11e57e 100644 --- a/data/lib/service/auth_service.dart +++ b/data/lib/service/auth_service.dart @@ -144,14 +144,14 @@ class AuthService { Future _generateAndSaveUserKeys(ApiUser user, String passKey) async { final identityKeyPair = generateIdentityKeyPair(); - final salt = Uint8List(16) - ..setAll(0, List.generate(16, (_) => Random().nextInt(256))); + final salt = Uint8List.fromList(List.generate(16, (_) => Random().nextInt(256))); final encryptedPrivateKey = await encryptPrivateKey( identityKeyPair.getPrivateKey().serialize(), passKey, salt, ); + print("XXX _generateAndSaveUserKeys encryptedPrivateKey ${encryptedPrivateKey.length}"); final publicKey = Blob(identityKeyPair.getPublicKey().publicKey.serialize()); final privateKey = Blob(encryptedPrivateKey); @@ -167,6 +167,7 @@ class AuthService { ); return user.copyWith( + updated_at: DateTime.now().millisecondsSinceEpoch, identity_key_public: publicKey, identity_key_private: privateKey, identity_key_salt: saltBlob, diff --git a/data/lib/service/location_manager.dart b/data/lib/service/location_manager.dart index 61f70eaf..fc4f80cf 100644 --- a/data/lib/service/location_manager.dart +++ b/data/lib/service/location_manager.dart @@ -181,14 +181,7 @@ class LocationManager { Future saveLocation(LocationData location) async { try { - final prefs = await SharedPreferences.getInstance(); - final user = await _getUserFromPreferences(); - if (user == null) return; - - final passKey = prefs.getString("user_passkey"); - if (passKey == null) return; - - await _locationService.saveCurrentLocation(user, location, passKey); + await _locationService.saveCurrentLocation(location); } catch (e, s) { logger.d("Failed to save encrypted location", error: e, stackTrace: s); } diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index 1123397a..c9d5c535 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/log/logger.dart'; +import 'package:data/storage/app_preferences.dart'; import 'package:data/utils/buffered_sender_keystore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -14,24 +15,39 @@ import '../api/space/api_group_key_model.dart'; import '../api/space/api_space_service.dart'; import '../utils/private_key_helper.dart'; -final locationServiceProvider = Provider((ref) => LocationService( - ref.read(firestoreProvider), ref.read(bufferedSenderKeystoreProvider))); +final locationServiceProvider = Provider((ref) { + final service = LocationService( + ref.read(currentUserPod), + ref.read(userPassKeyPod), + ref.read(firestoreProvider), + ref.read(bufferedSenderKeystoreProvider)); + + ref.listen(currentUserPod, (prev, user) { + service.currentUser = user; + }); + return service; +}); class LocationService { + ApiUser? currentUser; + final String? userPasskey; final FirebaseFirestore _db; final BufferedSenderKeystore _bufferedSenderKeystore; - LocationService(this._db, this._bufferedSenderKeystore); - - CollectionReference _locationRef(String spaceID, String userId) => _db - .collection(FIRESTORE_SPACE_PATH) - .doc(spaceID) - .collection(FIRESTORE_SPACE_MEMBER_PATH) - .doc(userId) - .collection(FIRESTORE_SPACE_MEMBER_LOCATION_PATH) - .withConverter( - fromFirestore: ApiLocation.fromFireStore, - toFirestore: (location, _) => location.toJson()); + LocationService(this.currentUser, this.userPasskey, this._db, + this._bufferedSenderKeystore); + + CollectionReference _locationRef( + String spaceID, String userId) => + _db + .collection(FIRESTORE_SPACE_PATH) + .doc(spaceID) + .collection(FIRESTORE_SPACE_MEMBER_PATH) + .doc(userId) + .collection(FIRESTORE_SPACE_MEMBER_LOCATION_PATH) + .withConverter( + fromFirestore: EncryptedApiLocation.fromFireStore, + toFirestore: (location, _) => location.toJson()); DocumentReference spaceGroupKeysDocRef(String spaceId) { return _db @@ -44,17 +60,19 @@ class LocationService { toFirestore: (key, options) => key.toJson()); } - Stream?> getCurrentLocationStream( + Stream> getCurrentLocationStream( String spaceId, String userId) { return _locationRef(spaceId, userId) .orderBy('created_at', descending: true) .limit(1) .snapshots() - .map((snapshot) { + .asyncMap>((snapshot) async { if (snapshot.docs.isNotEmpty) { - return snapshot.docs.map((doc) => doc.data() as ApiLocation).toList(); + return await Future.wait(snapshot.docs.map((doc) async { + return toApiLocation(doc.data(), spaceId); + })); } - return null; + return List.empty(); }); } @@ -65,34 +83,121 @@ class LocationService { .get(); if (snapshot.docs.isNotEmpty) { - return snapshot.docs.map((doc) => doc.data() as ApiLocation).first; + return snapshot.docs.map((doc) async { + return await toApiLocation(doc.data(), spaceId); + }).first; } return null; } + Future toApiLocation( + EncryptedApiLocation location, String spaceId) async { + try { + final user = currentUser; + final passKey = userPasskey; + if (user == null || passKey == null) { + logger.d('LocationService: User not found'); + return null; + } + + if (user.identity_key_private == null || + (user.identity_key_private?.bytes.isEmpty ?? true)) return null; + + final groupKey = await _getGroupKey(spaceId); + if (groupKey == null) { + logger.d('LocationService: Group key not found for space $spaceId'); + return null; + } + + final memberKeyData = groupKey.member_keys[user.id]; + if (memberKeyData == null) { + logger.d( + 'LocationService: Member key not found for user ${user.id} in space $spaceId'); + return null; + } + + final distributions = memberKeyData.distributions + .where((d) => d.recipient_id == user.id) + .toList() + ..sort((a, b) => b.created_at.compareTo(a.created_at)); + + final distribution = distributions.firstOrNull; + if (distribution == null) { + logger.d( + 'LocationService: Distribution not found for user ${user.id} in space $spaceId'); + return null; + } + + final groupCipher = await getGroupCipher( + spaceId: spaceId, + deviceId: memberKeyData.member_device_id, + distribution: distribution, + privateKeyBytes: user.identity_key_private!.bytes, + salt: user.identity_key_salt!.bytes, + passkey: passKey, + bufferedSenderKeyStore: _bufferedSenderKeystore, + ); + + if (groupCipher == null) { + logger.d('LocationService: Error while getting group cipher'); + return null; + } + + final decryptedLatitude = + await groupCipher.decrypt(location.latitude.bytes); + final decryptedLongitude = + await groupCipher.decrypt(location.longitude.bytes); + + final latitude = double.tryParse(utf8.decode(decryptedLatitude)); + final longitude = double.tryParse(utf8.decode(decryptedLongitude)); + + if (latitude == null || longitude == null) { + return null; + } + + return ApiLocation( + id: location.id, + user_id: location.user_id, + latitude: latitude, + longitude: longitude, + created_at: location.created_at, + ); + } catch (e, s) { + logger.e('Error while decrypting location', error: e, stackTrace: s); + } + + return null; + } + Future _getGroupKey(String spaceId) async { final doc = await spaceGroupKeysDocRef(spaceId).get(); return doc.data(); } Future saveCurrentLocation( - ApiUser user, LocationData locationData, - String passKey, ) async { + final user = currentUser; + final passKey = "1111"; + if (user == null || passKey == null) { + logger.d('LocationService: User not found'); + return; + } + if (user.identity_key_private == null || (user.identity_key_private?.bytes.isEmpty ?? true)) return; user.space_ids?.forEach((spaceId) async { + print("XXX saveCurrentLocation ${spaceId}"); final groupKey = await _getGroupKey(spaceId); if (groupKey == null) { - logger.e('LocationService: Group key not found for space $spaceId'); + logger.d('LocationService: Group key not found for space $spaceId'); return; } final memberKeyData = groupKey.member_keys[user.id]; if (memberKeyData == null) { - logger.e( + logger.d( 'LocationService: Member key not found for user ${user.id} in space $spaceId'); return; } @@ -104,7 +209,7 @@ class LocationService { final distribution = distributions.firstOrNull; if (distribution == null) { - logger.e( + logger.d( 'LocationService: Distribution not found for user ${user.id} in space $spaceId'); return; } @@ -120,7 +225,7 @@ class LocationService { ); if (groupCipher == null) { - logger.e('LocationService: Error while getting group cipher'); + logger.d('LocationService: Error while getting group cipher'); return; } diff --git a/data/lib/service/place_service.dart b/data/lib/service/place_service.dart index 7da8a5a8..ffc4fc93 100644 --- a/data/lib/service/place_service.dart +++ b/data/lib/service/place_service.dart @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import '../api/network/client.dart'; +import '../api/space/api_space_service.dart'; import '../api/space/space_models.dart'; import '../config.dart'; @@ -25,7 +26,7 @@ class PlaceService { PlaceService(this._db); CollectionReference get _spaceRef => - _db.collection('spaces').withConverter( + _db.collection(FIRESTORE_SPACE_PATH).withConverter( fromFirestore: ApiSpace.fromFireStore, toFirestore: (space, options) => space.toJson()); diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index 90937c1f..8b5d9f1c 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cryptography/cryptography.dart'; import 'package:data/log/logger.dart'; @@ -105,7 +104,10 @@ class EphemeralECDHUtils { final syntheticIv = message.iv; final cipherText = message.ciphertext; + final ephemeralPublic = Curve.decodePoint(message.ephemeral_pub.bytes, 0); + print("XXX receiverPrivateKey ${receiverPrivateKey.serialize().length}"); + final masterSecret = Curve.calculateAgreement(ephemeralPublic, receiverPrivateKey); diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index 4f4fa66e..cac38b25 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -43,15 +43,11 @@ Future _deriveKeyFromPasskey(String passkey, Uint8List salt) async { // Encrypt data using AES-GCM with the provided key. Returns the IV prepended to the ciphertext. Future _encryptData(Uint8List data, SecretKey key) async { - try { final gcm = AesGcm.with256bits(); - final iv = Uint8List(GCM_IV_SIZE) - ..setAll(0, List.generate(GCM_IV_SIZE, (i) => (i + 1) % 256)); + final iv = + Uint8List.fromList(List.generate(GCM_IV_SIZE, (i) => (i + 1) % 256)); final secretBox = await gcm.encrypt(data, secretKey: key, nonce: iv); return Uint8List.fromList(iv + secretBox.cipherText); - } catch (e) { - throw EncryptionException("Encryption failed", e); - } } /// Encrypts the private key using the user's passkey/PIN. @@ -59,7 +55,7 @@ Future _encryptData(Uint8List data, SecretKey key) async { Future encryptPrivateKey( Uint8List privateKey, String passkey, Uint8List salt) async { if (salt.isEmpty) { - throw EncryptionException('Salt is empty'); + throw Exception('Salt is empty'); } final key = await _deriveKeyFromPasskey(passkey, salt); return await _encryptData(privateKey, key); @@ -68,38 +64,38 @@ Future encryptPrivateKey( // Decrypts the provided ciphertext using the provided private key. Future _decryptData( Uint8List encryptedPrivateKey, Uint8List salt, String passkey) async { - try { - final key = await _deriveKeyFromPasskey(passkey, salt); - if (encryptedPrivateKey.length < GCM_IV_SIZE) { - throw EncryptionException("Encrypted data is too short"); - } - final iv = Uint8List(GCM_IV_SIZE) - ..setAll(0, List.generate(GCM_IV_SIZE, (i) => (i + 1) % 256)); + final key = await _deriveKeyFromPasskey(passkey, salt); - final ciphertext = encryptedPrivateKey.sublist(GCM_IV_SIZE); + if (encryptedPrivateKey.length < GCM_IV_SIZE) { + throw Exception("Encrypted data is too short"); + } - final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); + print("XXX encryptedPrivateKey ${encryptedPrivateKey}"); - final secretBox = SecretBox( - ciphertext, - nonce: iv, - mac: Mac.empty, - ); + final iv = encryptedPrivateKey.sublist(0, GCM_IV_SIZE); - final clearText = await algorithm.decrypt( - secretBox, - secretKey: key, - ); + final ciphertext = encryptedPrivateKey.sublist(GCM_IV_SIZE); - return Uint8List.fromList(clearText); - } catch (e) { - throw EncryptionException("Decryption failed", e); - } + print("XXX _decryptData iv ${iv} ciphertext ${ciphertext}"); + + final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); + + final secretBox = SecretBox( + ciphertext, + nonce: iv, + mac: Mac.empty, + ); + + final clearText = await algorithm.decrypt( + secretBox, + secretKey: key, + ); + + return Uint8List.fromList(clearText); } -Future - getGroupCipher({ +Future getGroupCipher({ required String spaceId, required int deviceId, required Uint8List privateKeyBytes, @@ -114,6 +110,9 @@ Future passkey: passkey, ); + print( + "XXX privateKeyBytes ${privateKeyBytes.length} _decodePrivateKey ${privateKey?.serialize().length}"); + if (privateKey == null) { return null; } @@ -156,12 +155,11 @@ Future _decodePrivateKey( {required Uint8List privateKeyBytes, required Uint8List salt, required String passkey}) async { - try { - return Curve.decodePrivatePoint(privateKeyBytes); - } catch (e, s) { - logger.d("Error decoding private key", error: e, stackTrace: s); - final decodedPrivateKey = - await _decryptData(privateKeyBytes, salt, passkey); - return Curve.decodePrivatePoint(decodedPrivateKey); - } + // try { + // return Curve.decodePrivatePoint(privateKeyBytes); + // } catch (e, s) { + // logger.d("Error decoding private key", error: e, stackTrace: s); + final decodedPrivateKey = await _decryptData(privateKeyBytes, salt, passkey); + return Curve.decodePrivatePoint(decodedPrivateKey); + // } } From 31ab4759d91417f105695bca6be0b2b73da8f815 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Fri, 17 Jan 2025 15:07:56 +0530 Subject: [PATCH 07/19] WIP - location encryption --- data/lib/api/auth/api_user_service.dart | 1 + data/lib/api/space/api_space_service.dart | 1 + data/lib/converter/blob_converter.dart | 3 +- data/lib/service/auth_service.dart | 4 +- data/lib/service/location_service.dart | 20 +++---- data/lib/utils/buffered_sender_keystore.dart | 10 ++-- .../lib/utils/distribution_key_generator.dart | 2 +- .../utils/ephemeral_distribution_helper.dart | 32 +++++------ data/lib/utils/private_key_helper.dart | 53 ++++++++++--------- 9 files changed, 70 insertions(+), 56 deletions(-) diff --git a/data/lib/api/auth/api_user_service.dart b/data/lib/api/auth/api_user_service.dart index baed74d7..cf40ab1f 100644 --- a/data/lib/api/auth/api_user_service.dart +++ b/data/lib/api/auth/api_user_service.dart @@ -297,6 +297,7 @@ class ApiUserService { Future updateKeys( String id, Blob publicKey, Blob privateKey, Blob saltBlob) async { + await _userRef.doc(id).update({ "identity_key_public": publicKey, "identity_key_private": privateKey, diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index 864e11b7..0aa9e601 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -200,6 +200,7 @@ class ApiSpaceService { spaceMembers: spaceMembers, bufferedSenderKeyStore: bufferedSenderKeystore); + print("XXX membersKeyData $membersKeyData"); await _updateGroupKeys(spaceId, userId, membersKeyData); } diff --git a/data/lib/converter/blob_converter.dart b/data/lib/converter/blob_converter.dart index 6d06f266..7479daec 100644 --- a/data/lib/converter/blob_converter.dart +++ b/data/lib/converter/blob_converter.dart @@ -15,7 +15,8 @@ class BlobConverter implements JsonConverter { if (json is Map && json.containsKey('_byteString')) { final base64String = json['_byteString'] as String; - return Blob(base64Decode(base64String)); + print("XXX base64String $base64String"); + return Blob(Uint8List.fromList(base64String.codeUnits)); } diff --git a/data/lib/service/auth_service.dart b/data/lib/service/auth_service.dart index 5b11e57e..393cd944 100644 --- a/data/lib/service/auth_service.dart +++ b/data/lib/service/auth_service.dart @@ -144,14 +144,14 @@ class AuthService { Future _generateAndSaveUserKeys(ApiUser user, String passKey) async { final identityKeyPair = generateIdentityKeyPair(); - final salt = Uint8List.fromList(List.generate(16, (_) => Random().nextInt(256))); + final salt = Uint8List.fromList(List.generate(16, (_) => Random.secure().nextInt(256))); + print("XXX _generateAndSaveUserKeys encryptedPrivateKey ${identityKeyPair.getPrivateKey().serialize().length}"); final encryptedPrivateKey = await encryptPrivateKey( identityKeyPair.getPrivateKey().serialize(), passKey, salt, ); - print("XXX _generateAndSaveUserKeys encryptedPrivateKey ${encryptedPrivateKey.length}"); final publicKey = Blob(identityKeyPair.getPublicKey().publicKey.serialize()); final privateKey = Blob(encryptedPrivateKey); diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index c9d5c535..f749562e 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -67,11 +67,11 @@ class LocationService { .limit(1) .snapshots() .asyncMap>((snapshot) async { - if (snapshot.docs.isNotEmpty) { - return await Future.wait(snapshot.docs.map((doc) async { - return toApiLocation(doc.data(), spaceId); - })); - } + // if (snapshot.docs.isNotEmpty) { + // return await Future.wait(snapshot.docs.map((doc) async { + // return toApiLocation(doc.data(), spaceId); + // })); + // } return List.empty(); }); } @@ -82,11 +82,11 @@ class LocationService { .limit(1) .get(); - if (snapshot.docs.isNotEmpty) { - return snapshot.docs.map((doc) async { - return await toApiLocation(doc.data(), spaceId); - }).first; - } + // if (snapshot.docs.isNotEmpty) { + // return snapshot.docs.map((doc) async { + // return await toApiLocation(doc.data(), spaceId); + // }).first; + // } return null; } diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index daf0956a..41b70c52 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -39,8 +39,8 @@ class BufferedSenderKeystore extends SenderKeyStore { toFirestore: (senderKey, options) => senderKey.toJson()); } - ApiUser? get currentUser => userJsonState.state == null - ? ApiUser.fromJson(jsonDecode(userJsonState.state ?? '')) + ApiUser? get currentUser => userJsonState.state != null + ? ApiUser.fromJson(jsonDecode(userJsonState.state!)) : null; @override @@ -133,14 +133,18 @@ class BufferedSenderKeystore extends SenderKeyStore { final distributionId = senderKeyName.groupId; final deviceId = senderKeyName.sender.getDeviceId(); final uniqueDocId = "$deviceId-$distributionId"; + final spaceId = senderKeyName.sender.getName(); final senderKeyRecord = ApiSenderKeyRecord( id: uniqueDocId, device_id: deviceId, + address: spaceId, distribution_id: distributionId, record: Blob(record.serialize()), created_at: DateTime.now().millisecondsSinceEpoch); - await senderKeyRef(senderKeyRecord.distribution_id, currentUser.id) + print("XXX store senderKeyRecord ${senderKeyName.sender.getName()}"); + + await senderKeyRef(spaceId, currentUser.id) .doc(uniqueDocId) .set(senderKeyRecord); diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index 5dc7f836..8e7c2b87 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -30,7 +30,7 @@ Future generateMemberKeyData(String spaceId, try { final publicKeyBytes = publicBlob.bytes; if (publicKeyBytes.length != 33) { - logger.e("Invalid public key size for member ${member.user_id}"); + logger.e("Invalid public key size for member ${member.user_id} length: ${publicKeyBytes.length}"); continue; } diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index 8b5d9f1c..41d47dfe 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -104,7 +104,6 @@ class EphemeralECDHUtils { final syntheticIv = message.iv; final cipherText = message.ciphertext; - final ephemeralPublic = Curve.decodePoint(message.ephemeral_pub.bytes, 0); print("XXX receiverPrivateKey ${receiverPrivateKey.serialize().length}"); @@ -115,22 +114,23 @@ class EphemeralECDHUtils { utf8.encode("cipher"), secretKey: SecretKey(masterSecret), ); - - final cipherKey = await mac.calculateMac( - cipherKeyPart1.bytes, - secretKey: SecretKey(syntheticIv.bytes), + // + // Derive cipherKey + final cipherKeyFull = await mac.calculateMac( + syntheticIv.bytes, + secretKey: SecretKey(cipherKeyPart1.bytes), ); - // Encrypt plaintext + // // Truncate the cipherKey to 16 bytes for AES-128 + final cipherKeyBytes = cipherKeyFull.bytes.sublist(0, 16); + final macBytes = cipherKeyFull.bytes.sublist(16); + final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); - final secretKey = SecretKey(cipherKey.bytes); - final secretBox = await algorithm.encrypt( - cipherText.bytes, - secretKey: secretKey, - nonce: syntheticIv.bytes, - ); - final plaintext = Uint8List.fromList(secretBox.cipherText); + final decrypted = await algorithm.decrypt( + secretKey: SecretKey(cipherKeyBytes), + SecretBox(cipherText.bytes, nonce: syntheticIv.bytes, mac: Mac.empty), + ); final verificationPart1 = await mac.calculateMac( utf8.encode("auth"), @@ -138,18 +138,20 @@ class EphemeralECDHUtils { ); final verificationPart2 = await mac.calculateMac( - plaintext, + decrypted, secretKey: SecretKey(verificationPart1.bytes), ); final ourSyntheticIv = verificationPart2.bytes.sublist(0, 16); + print( + "XXXX ourSyntheticIv ${ourSyntheticIv} syntheticIv ${syntheticIv.bytes}"); if (!listEquals(ourSyntheticIv, syntheticIv.bytes)) { throw Exception( "The computed syntheticIv didn't match the actual syntheticIv."); } - return plaintext; + return Uint8List.fromList(decrypted); } catch (e, s) { logger.e("Error while decrypting", error: e, stackTrace: s); return null; diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index cac38b25..acee08bf 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -1,5 +1,8 @@ // ignore_for_file: constant_identifier_names +import 'dart:convert'; +import 'dart:math'; + import 'package:cryptography/cryptography.dart'; import 'package:data/log/logger.dart'; import 'package:data/utils/ephemeral_distribution_helper.dart'; @@ -17,7 +20,7 @@ class EncryptionException implements Exception { EncryptionException(this.message, [this.cause]); } -const int KEY_SIZE = 256; // bits +const int KEY_SIZE = 128; // bits const int ITERATION_COUNT = 100000; const int GCM_IV_SIZE = 12; // bytes const int GCM_TAG_SIZE = 128; // bits @@ -25,10 +28,9 @@ const int GCM_TAG_SIZE = 128; // bits /// Derives a SecretKey from the user's passkey/PIN using PBKDF2. Future _deriveKeyFromPasskey(String passkey, Uint8List salt) async { try { - final pbkdf2 = Pbkdf2( - macAlgorithm: Hmac.sha256(), + final pbkdf2 = Pbkdf2.hmacSha256( iterations: ITERATION_COUNT, - bits: KEY_SIZE, // 256 bits = 32 bytes output + bits: KEY_SIZE, // 128 bits = 16 bytes output ); final newSecretKey = await pbkdf2.deriveKeyFromPassword( @@ -43,11 +45,11 @@ Future _deriveKeyFromPasskey(String passkey, Uint8List salt) async { // Encrypt data using AES-GCM with the provided key. Returns the IV prepended to the ciphertext. Future _encryptData(Uint8List data, SecretKey key) async { - final gcm = AesGcm.with256bits(); - final iv = - Uint8List.fromList(List.generate(GCM_IV_SIZE, (i) => (i + 1) % 256)); - final secretBox = await gcm.encrypt(data, secretKey: key, nonce: iv); - return Uint8List.fromList(iv + secretBox.cipherText); + final gcm = AesGcm.with128bits(); + final iv = gcm.newNonce(); + + final secretBox = await gcm.encrypt(data, secretKey: key, nonce: iv); + return secretBox.concatenation(); } /// Encrypts the private key using the user's passkey/PIN. @@ -57,6 +59,7 @@ Future encryptPrivateKey( if (salt.isEmpty) { throw Exception('Salt is empty'); } + final key = await _deriveKeyFromPasskey(passkey, salt); return await _encryptData(privateKey, key); } @@ -64,35 +67,38 @@ Future encryptPrivateKey( // Decrypts the provided ciphertext using the provided private key. Future _decryptData( Uint8List encryptedPrivateKey, Uint8List salt, String passkey) async { - final key = await _deriveKeyFromPasskey(passkey, salt); if (encryptedPrivateKey.length < GCM_IV_SIZE) { throw Exception("Encrypted data is too short"); } - print("XXX encryptedPrivateKey ${encryptedPrivateKey}"); + print( + "XXX encryptedPrivateKey ${encryptedPrivateKey.length} key ${encryptedPrivateKey}"); final iv = encryptedPrivateKey.sublist(0, GCM_IV_SIZE); - final ciphertext = encryptedPrivateKey.sublist(GCM_IV_SIZE); + final ciphertext = + encryptedPrivateKey.sublist(GCM_IV_SIZE, encryptedPrivateKey.length - 16); + final mac = encryptedPrivateKey.sublist(encryptedPrivateKey.length - 16); - print("XXX _decryptData iv ${iv} ciphertext ${ciphertext}"); + print( + "XXX _decryptData iv ${iv.length} ciphertext ${ciphertext.length} mac ${mac.length}"); - final algorithm = AesCtr.with128bits(macAlgorithm: MacAlgorithm.empty); + final algorithm = AesGcm.with128bits(); final secretBox = SecretBox( ciphertext, nonce: iv, - mac: Mac.empty, + mac: Mac(mac), ); - final clearText = await algorithm.decrypt( + final decrypted = await algorithm.decrypt( secretBox, secretKey: key, ); - return Uint8List.fromList(clearText); + return Uint8List.fromList(decrypted); } Future getGroupCipher({ @@ -125,24 +131,23 @@ Future getGroupCipher({ } final distributionMessage = - SenderKeyDistributionMessageWrapper.fromSerialized( - decryptedDistribution, - ); + SenderKeyDistributionMessageWrapper.fromSerialized(decryptedDistribution); final groupAddress = SignalProtocolAddress(spaceId, deviceId); final senderKey = SenderKeyName( + // groupAddress.getDeviceId().toString(), distributionMessage.id.toString(), groupAddress, ); + bufferedSenderKeyStore.loadSenderKey(senderKey); // TODO rotate sender key try { - GroupSessionBuilder(bufferedSenderKeyStore) - .process(senderKey, distributionMessage); + GroupSessionBuilder(bufferedSenderKeyStore).process(senderKey, distributionMessage); final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); return groupCipher; } catch (e, s) { @@ -158,8 +163,8 @@ Future _decodePrivateKey( // try { // return Curve.decodePrivatePoint(privateKeyBytes); // } catch (e, s) { - // logger.d("Error decoding private key", error: e, stackTrace: s); + // logger.d("Error decoding private key", error: e, stackTrace: s); final decodedPrivateKey = await _decryptData(privateKeyBytes, salt, passkey); return Curve.decodePrivatePoint(decodedPrivateKey); - // } + //} } From 03411fad829667d77680554e62882a590f64538f Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Fri, 17 Jan 2025 18:48:41 +0530 Subject: [PATCH 08/19] Store encrypted location --- data/lib/api/auth/api_user_service.dart | 16 ++- data/lib/api/auth/auth_models.dart | 7 +- data/lib/api/auth/auth_models.freezed.dart | 66 ++++----- data/lib/api/auth/auth_models.g.dart | 24 ++-- data/lib/api/location/journey/journey.dart | 13 +- .../api/location/journey/journey.freezed.dart | 131 +++++++++--------- data/lib/api/location/journey/journey.g.dart | 26 ++-- data/lib/api/location/location.dart | 5 +- data/lib/api/location/location.freezed.dart | 47 ++++--- data/lib/api/location/location.g.dart | 4 +- data/lib/api/space/api_group_key_model.dart | 21 +-- .../space/api_group_key_model.freezed.dart | 87 ++++++------ data/lib/api/space/api_group_key_model.g.dart | 9 +- data/lib/api/space/api_sender_key_record.dart | 3 +- .../space/api_sender_key_record.freezed.dart | 33 ++--- .../api/space/api_sender_key_record.g.dart | 2 +- data/lib/api/space/api_space_service.dart | 6 +- data/lib/api/space/space_models.dart | 4 +- data/lib/api/space/space_models.freezed.dart | 31 +++-- data/lib/api/space/space_models.g.dart | 12 +- data/lib/converter/blob_converter.dart | 27 +--- data/lib/service/auth_service.dart | 6 +- data/lib/service/location_service.dart | 42 +++--- data/lib/service/space_service.dart | 3 +- data/lib/utils/buffered_sender_keystore.dart | 6 +- .../lib/utils/distribution_key_generator.dart | 5 +- .../utils/ephemeral_distribution_helper.dart | 16 +-- 27 files changed, 345 insertions(+), 307 deletions(-) diff --git a/data/lib/api/auth/api_user_service.dart b/data/lib/api/auth/api_user_service.dart index cf40ab1f..a3c505ab 100644 --- a/data/lib/api/auth/api_user_service.dart +++ b/data/lib/api/auth/api_user_service.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:typed_data'; + import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/network/client.dart'; import 'package:data/service/device_service.dart'; @@ -295,13 +298,14 @@ class ApiUserService { userPassKeyNotifier.state = null; } - Future updateKeys( - String id, Blob publicKey, Blob privateKey, Blob saltBlob) async { - + Future updateKeys(String id, Uint8List? publicKey, + Uint8List? privateKey, Uint8List? saltBlob) async { await _userRef.doc(id).update({ - "identity_key_public": publicKey, - "identity_key_private": privateKey, - "identity_key_salt": saltBlob, + "identity_key_public": + publicKey != null ? base64UrlEncode(publicKey) : null, + "identity_key_private": + privateKey != null ? base64UrlEncode(privateKey) : null, + "identity_key_salt": saltBlob != null ? base64UrlEncode(saltBlob) : null, }); } } diff --git a/data/lib/api/auth/auth_models.dart b/data/lib/api/auth/auth_models.dart index 92c43f31..37de2a0c 100644 --- a/data/lib/api/auth/auth_models.dart +++ b/data/lib/api/auth/auth_models.dart @@ -6,6 +6,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../converter/blob_converter.dart'; +import 'dart:typed_data'; part 'auth_models.freezed.dart'; @@ -33,9 +34,9 @@ class ApiUser with _$ApiUser { @Default([]) List? space_ids, int? battery_pct, @Default("") String? fcm_token, - @BlobConverter() Blob? identity_key_public, - @BlobConverter() Blob? identity_key_private, - @BlobConverter() Blob? identity_key_salt, + @BlobConverter() Uint8List? identity_key_public, + @BlobConverter() Uint8List? identity_key_private, + @BlobConverter() Uint8List? identity_key_salt, int? state, int? created_at, int? updated_at, diff --git a/data/lib/api/auth/auth_models.freezed.dart b/data/lib/api/auth/auth_models.freezed.dart index 3d3923a4..275e1eb7 100644 --- a/data/lib/api/auth/auth_models.freezed.dart +++ b/data/lib/api/auth/auth_models.freezed.dart @@ -32,11 +32,11 @@ mixin _$ApiUser { int? get battery_pct => throw _privateConstructorUsedError; String? get fcm_token => throw _privateConstructorUsedError; @BlobConverter() - Blob? get identity_key_public => throw _privateConstructorUsedError; + Uint8List? get identity_key_public => throw _privateConstructorUsedError; @BlobConverter() - Blob? get identity_key_private => throw _privateConstructorUsedError; + Uint8List? get identity_key_private => throw _privateConstructorUsedError; @BlobConverter() - Blob? get identity_key_salt => throw _privateConstructorUsedError; + Uint8List? get identity_key_salt => throw _privateConstructorUsedError; int? get state => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; int? get updated_at => throw _privateConstructorUsedError; @@ -67,9 +67,9 @@ abstract class $ApiUserCopyWith<$Res> { List? space_ids, int? battery_pct, String? fcm_token, - @BlobConverter() Blob? identity_key_public, - @BlobConverter() Blob? identity_key_private, - @BlobConverter() Blob? identity_key_salt, + @BlobConverter() Uint8List? identity_key_public, + @BlobConverter() Uint8List? identity_key_private, + @BlobConverter() Uint8List? identity_key_salt, int? state, int? created_at, int? updated_at}); @@ -156,15 +156,15 @@ class _$ApiUserCopyWithImpl<$Res, $Val extends ApiUser> identity_key_public: freezed == identity_key_public ? _value.identity_key_public : identity_key_public // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, identity_key_private: freezed == identity_key_private ? _value.identity_key_private : identity_key_private // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, identity_key_salt: freezed == identity_key_salt ? _value.identity_key_salt : identity_key_salt // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, state: freezed == state ? _value.state : state // ignore: cast_nullable_to_non_nullable @@ -200,9 +200,9 @@ abstract class _$$ApiUserImplCopyWith<$Res> implements $ApiUserCopyWith<$Res> { List? space_ids, int? battery_pct, String? fcm_token, - @BlobConverter() Blob? identity_key_public, - @BlobConverter() Blob? identity_key_private, - @BlobConverter() Blob? identity_key_salt, + @BlobConverter() Uint8List? identity_key_public, + @BlobConverter() Uint8List? identity_key_private, + @BlobConverter() Uint8List? identity_key_salt, int? state, int? created_at, int? updated_at}); @@ -287,15 +287,15 @@ class __$$ApiUserImplCopyWithImpl<$Res> identity_key_public: freezed == identity_key_public ? _value.identity_key_public : identity_key_public // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, identity_key_private: freezed == identity_key_private ? _value.identity_key_private : identity_key_private // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, identity_key_salt: freezed == identity_key_salt ? _value.identity_key_salt : identity_key_salt // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, state: freezed == state ? _value.state : state // ignore: cast_nullable_to_non_nullable @@ -374,13 +374,13 @@ class _$ApiUserImpl extends _ApiUser { final String? fcm_token; @override @BlobConverter() - final Blob? identity_key_public; + final Uint8List? identity_key_public; @override @BlobConverter() - final Blob? identity_key_private; + final Uint8List? identity_key_private; @override @BlobConverter() - final Blob? identity_key_salt; + final Uint8List? identity_key_salt; @override final int? state; @override @@ -420,12 +420,12 @@ class _$ApiUserImpl extends _ApiUser { other.battery_pct == battery_pct) && (identical(other.fcm_token, fcm_token) || other.fcm_token == fcm_token) && - (identical(other.identity_key_public, identity_key_public) || - other.identity_key_public == identity_key_public) && - (identical(other.identity_key_private, identity_key_private) || - other.identity_key_private == identity_key_private) && - (identical(other.identity_key_salt, identity_key_salt) || - other.identity_key_salt == identity_key_salt) && + const DeepCollectionEquality() + .equals(other.identity_key_public, identity_key_public) && + const DeepCollectionEquality() + .equals(other.identity_key_private, identity_key_private) && + const DeepCollectionEquality() + .equals(other.identity_key_salt, identity_key_salt) && (identical(other.state, state) || other.state == state) && (identical(other.created_at, created_at) || other.created_at == created_at) && @@ -448,9 +448,9 @@ class _$ApiUserImpl extends _ApiUser { const DeepCollectionEquality().hash(_space_ids), battery_pct, fcm_token, - identity_key_public, - identity_key_private, - identity_key_salt, + const DeepCollectionEquality().hash(identity_key_public), + const DeepCollectionEquality().hash(identity_key_private), + const DeepCollectionEquality().hash(identity_key_salt), state, created_at, updated_at); @@ -484,9 +484,9 @@ abstract class _ApiUser extends ApiUser { final List? space_ids, final int? battery_pct, final String? fcm_token, - @BlobConverter() final Blob? identity_key_public, - @BlobConverter() final Blob? identity_key_private, - @BlobConverter() final Blob? identity_key_salt, + @BlobConverter() final Uint8List? identity_key_public, + @BlobConverter() final Uint8List? identity_key_private, + @BlobConverter() final Uint8List? identity_key_salt, final int? state, final int? created_at, final int? updated_at}) = _$ApiUserImpl; @@ -518,13 +518,13 @@ abstract class _ApiUser extends ApiUser { String? get fcm_token; @override @BlobConverter() - Blob? get identity_key_public; + Uint8List? get identity_key_public; @override @BlobConverter() - Blob? get identity_key_private; + Uint8List? get identity_key_private; @override @BlobConverter() - Blob? get identity_key_salt; + Uint8List? get identity_key_salt; @override int? get state; @override diff --git a/data/lib/api/auth/auth_models.g.dart b/data/lib/api/auth/auth_models.g.dart index ea5e1dd1..0d375854 100644 --- a/data/lib/api/auth/auth_models.g.dart +++ b/data/lib/api/auth/auth_models.g.dart @@ -22,12 +22,12 @@ _$ApiUserImpl _$$ApiUserImplFromJson(Map json) => const [], battery_pct: (json['battery_pct'] as num?)?.toInt(), fcm_token: json['fcm_token'] as String? ?? "", - identity_key_public: - const BlobConverter().fromJson(json['identity_key_public']), - identity_key_private: - const BlobConverter().fromJson(json['identity_key_private']), - identity_key_salt: - const BlobConverter().fromJson(json['identity_key_salt']), + identity_key_public: _$JsonConverterFromJson( + json['identity_key_public'], const BlobConverter().fromJson), + identity_key_private: _$JsonConverterFromJson( + json['identity_key_private'], const BlobConverter().fromJson), + identity_key_salt: _$JsonConverterFromJson( + json['identity_key_salt'], const BlobConverter().fromJson), state: (json['state'] as num?)?.toInt(), created_at: (json['created_at'] as num?)?.toInt(), updated_at: (json['updated_at'] as num?)?.toInt(), @@ -46,17 +46,23 @@ Map _$$ApiUserImplToJson(_$ApiUserImpl instance) => 'space_ids': instance.space_ids, 'battery_pct': instance.battery_pct, 'fcm_token': instance.fcm_token, - 'identity_key_public': _$JsonConverterToJson( + 'identity_key_public': _$JsonConverterToJson( instance.identity_key_public, const BlobConverter().toJson), - 'identity_key_private': _$JsonConverterToJson( + 'identity_key_private': _$JsonConverterToJson( instance.identity_key_private, const BlobConverter().toJson), - 'identity_key_salt': _$JsonConverterToJson( + 'identity_key_salt': _$JsonConverterToJson( instance.identity_key_salt, const BlobConverter().toJson), 'state': instance.state, 'created_at': instance.created_at, 'updated_at': instance.updated_at, }; +Value? _$JsonConverterFromJson( + Object? json, + Value? Function(Json json) fromJson, +) => + json == null ? null : fromJson(json as Json); + Json? _$JsonConverterToJson( Value? value, Json? Function(Value value) toJson, diff --git a/data/lib/api/location/journey/journey.dart b/data/lib/api/location/journey/journey.dart index 6472632a..4462e026 100644 --- a/data/lib/api/location/journey/journey.dart +++ b/data/lib/api/location/journey/journey.dart @@ -4,6 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/location/location.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'dart:typed_data'; import '../../../converter/blob_converter.dart'; @@ -99,10 +100,10 @@ class EncryptedLocationJourney with _$EncryptedLocationJourney { const factory EncryptedLocationJourney({ String? id, required String user_id, - @BlobConverter() required Blob from_latitude, - @BlobConverter() required Blob from_longitude, - @BlobConverter() Blob? to_latitude, - @BlobConverter() Blob? to_longitude, + @BlobConverter() required Uint8List from_latitude, + @BlobConverter() required Uint8List from_longitude, + @BlobConverter() Uint8List? to_latitude, + @BlobConverter() Uint8List? to_longitude, @Default([]) List routes, double? route_distance, int? route_duration, @@ -125,8 +126,8 @@ class EncryptedLocationJourney with _$EncryptedLocationJourney { @freezed class EncryptedJourneyRoute with _$EncryptedJourneyRoute { const factory EncryptedJourneyRoute({ - @BlobConverter() required Blob latitude, - @BlobConverter() required Blob longitude, + @BlobConverter() required Uint8List latitude, + @BlobConverter() required Uint8List longitude, }) = _EncryptedJourneyRoute; factory EncryptedJourneyRoute.fromJson(Map json) => diff --git a/data/lib/api/location/journey/journey.freezed.dart b/data/lib/api/location/journey/journey.freezed.dart index 71b1dbc5..2eddf312 100644 --- a/data/lib/api/location/journey/journey.freezed.dart +++ b/data/lib/api/location/journey/journey.freezed.dart @@ -598,13 +598,13 @@ mixin _$EncryptedLocationJourney { String? get id => throw _privateConstructorUsedError; String get user_id => throw _privateConstructorUsedError; @BlobConverter() - Blob get from_latitude => throw _privateConstructorUsedError; + Uint8List get from_latitude => throw _privateConstructorUsedError; @BlobConverter() - Blob get from_longitude => throw _privateConstructorUsedError; + Uint8List get from_longitude => throw _privateConstructorUsedError; @BlobConverter() - Blob? get to_latitude => throw _privateConstructorUsedError; + Uint8List? get to_latitude => throw _privateConstructorUsedError; @BlobConverter() - Blob? get to_longitude => throw _privateConstructorUsedError; + Uint8List? get to_longitude => throw _privateConstructorUsedError; List get routes => throw _privateConstructorUsedError; double? get route_distance => throw _privateConstructorUsedError; int? get route_duration => throw _privateConstructorUsedError; @@ -631,10 +631,10 @@ abstract class $EncryptedLocationJourneyCopyWith<$Res> { $Res call( {String? id, String user_id, - @BlobConverter() Blob from_latitude, - @BlobConverter() Blob from_longitude, - @BlobConverter() Blob? to_latitude, - @BlobConverter() Blob? to_longitude, + @BlobConverter() Uint8List from_latitude, + @BlobConverter() Uint8List from_longitude, + @BlobConverter() Uint8List? to_latitude, + @BlobConverter() Uint8List? to_longitude, List routes, double? route_distance, int? route_duration, @@ -684,19 +684,19 @@ class _$EncryptedLocationJourneyCopyWithImpl<$Res, from_latitude: null == from_latitude ? _value.from_latitude : from_latitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, from_longitude: null == from_longitude ? _value.from_longitude : from_longitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, to_latitude: freezed == to_latitude ? _value.to_latitude : to_latitude // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, to_longitude: freezed == to_longitude ? _value.to_longitude : to_longitude // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, routes: null == routes ? _value.routes : routes // ignore: cast_nullable_to_non_nullable @@ -737,10 +737,10 @@ abstract class _$$EncryptedLocationJourneyImplCopyWith<$Res> $Res call( {String? id, String user_id, - @BlobConverter() Blob from_latitude, - @BlobConverter() Blob from_longitude, - @BlobConverter() Blob? to_latitude, - @BlobConverter() Blob? to_longitude, + @BlobConverter() Uint8List from_latitude, + @BlobConverter() Uint8List from_longitude, + @BlobConverter() Uint8List? to_latitude, + @BlobConverter() Uint8List? to_longitude, List routes, double? route_distance, int? route_duration, @@ -789,19 +789,19 @@ class __$$EncryptedLocationJourneyImplCopyWithImpl<$Res> from_latitude: null == from_latitude ? _value.from_latitude : from_latitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, from_longitude: null == from_longitude ? _value.from_longitude : from_longitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, to_latitude: freezed == to_latitude ? _value.to_latitude : to_latitude // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, to_longitude: freezed == to_longitude ? _value.to_longitude : to_longitude // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, routes: null == routes ? _value._routes : routes // ignore: cast_nullable_to_non_nullable @@ -858,16 +858,16 @@ class _$EncryptedLocationJourneyImpl extends _EncryptedLocationJourney { final String user_id; @override @BlobConverter() - final Blob from_latitude; + final Uint8List from_latitude; @override @BlobConverter() - final Blob from_longitude; + final Uint8List from_longitude; @override @BlobConverter() - final Blob? to_latitude; + final Uint8List? to_latitude; @override @BlobConverter() - final Blob? to_longitude; + final Uint8List? to_longitude; final List _routes; @override @JsonKey() @@ -900,14 +900,14 @@ class _$EncryptedLocationJourneyImpl extends _EncryptedLocationJourney { other is _$EncryptedLocationJourneyImpl && (identical(other.id, id) || other.id == id) && (identical(other.user_id, user_id) || other.user_id == user_id) && - (identical(other.from_latitude, from_latitude) || - other.from_latitude == from_latitude) && - (identical(other.from_longitude, from_longitude) || - other.from_longitude == from_longitude) && - (identical(other.to_latitude, to_latitude) || - other.to_latitude == to_latitude) && - (identical(other.to_longitude, to_longitude) || - other.to_longitude == to_longitude) && + const DeepCollectionEquality() + .equals(other.from_latitude, from_latitude) && + const DeepCollectionEquality() + .equals(other.from_longitude, from_longitude) && + const DeepCollectionEquality() + .equals(other.to_latitude, to_latitude) && + const DeepCollectionEquality() + .equals(other.to_longitude, to_longitude) && const DeepCollectionEquality().equals(other._routes, _routes) && (identical(other.route_distance, route_distance) || other.route_distance == route_distance) && @@ -926,10 +926,10 @@ class _$EncryptedLocationJourneyImpl extends _EncryptedLocationJourney { runtimeType, id, user_id, - from_latitude, - from_longitude, - to_latitude, - to_longitude, + const DeepCollectionEquality().hash(from_latitude), + const DeepCollectionEquality().hash(from_longitude), + const DeepCollectionEquality().hash(to_latitude), + const DeepCollectionEquality().hash(to_longitude), const DeepCollectionEquality().hash(_routes), route_distance, route_duration, @@ -958,10 +958,10 @@ abstract class _EncryptedLocationJourney extends EncryptedLocationJourney { const factory _EncryptedLocationJourney( {final String? id, required final String user_id, - @BlobConverter() required final Blob from_latitude, - @BlobConverter() required final Blob from_longitude, - @BlobConverter() final Blob? to_latitude, - @BlobConverter() final Blob? to_longitude, + @BlobConverter() required final Uint8List from_latitude, + @BlobConverter() required final Uint8List from_longitude, + @BlobConverter() final Uint8List? to_latitude, + @BlobConverter() final Uint8List? to_longitude, final List routes, final double? route_distance, final int? route_duration, @@ -979,16 +979,16 @@ abstract class _EncryptedLocationJourney extends EncryptedLocationJourney { String get user_id; @override @BlobConverter() - Blob get from_latitude; + Uint8List get from_latitude; @override @BlobConverter() - Blob get from_longitude; + Uint8List get from_longitude; @override @BlobConverter() - Blob? get to_latitude; + Uint8List? get to_latitude; @override @BlobConverter() - Blob? get to_longitude; + Uint8List? get to_longitude; @override List get routes; @override @@ -1018,9 +1018,9 @@ EncryptedJourneyRoute _$EncryptedJourneyRouteFromJson( /// @nodoc mixin _$EncryptedJourneyRoute { @BlobConverter() - Blob get latitude => throw _privateConstructorUsedError; + Uint8List get latitude => throw _privateConstructorUsedError; @BlobConverter() - Blob get longitude => throw _privateConstructorUsedError; + Uint8List get longitude => throw _privateConstructorUsedError; /// Serializes this EncryptedJourneyRoute to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1038,7 +1038,9 @@ abstract class $EncryptedJourneyRouteCopyWith<$Res> { $Res Function(EncryptedJourneyRoute) then) = _$EncryptedJourneyRouteCopyWithImpl<$Res, EncryptedJourneyRoute>; @useResult - $Res call({@BlobConverter() Blob latitude, @BlobConverter() Blob longitude}); + $Res call( + {@BlobConverter() Uint8List latitude, + @BlobConverter() Uint8List longitude}); } /// @nodoc @@ -1064,11 +1066,11 @@ class _$EncryptedJourneyRouteCopyWithImpl<$Res, latitude: null == latitude ? _value.latitude : latitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, longitude: null == longitude ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, ) as $Val); } } @@ -1082,7 +1084,9 @@ abstract class _$$EncryptedJourneyRouteImplCopyWith<$Res> __$$EncryptedJourneyRouteImplCopyWithImpl<$Res>; @override @useResult - $Res call({@BlobConverter() Blob latitude, @BlobConverter() Blob longitude}); + $Res call( + {@BlobConverter() Uint8List latitude, + @BlobConverter() Uint8List longitude}); } /// @nodoc @@ -1106,11 +1110,11 @@ class __$$EncryptedJourneyRouteImplCopyWithImpl<$Res> latitude: null == latitude ? _value.latitude : latitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, longitude: null == longitude ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, )); } } @@ -1127,10 +1131,10 @@ class _$EncryptedJourneyRouteImpl implements _EncryptedJourneyRoute { @override @BlobConverter() - final Blob latitude; + final Uint8List latitude; @override @BlobConverter() - final Blob longitude; + final Uint8List longitude; @override String toString() { @@ -1142,15 +1146,16 @@ class _$EncryptedJourneyRouteImpl implements _EncryptedJourneyRoute { return identical(this, other) || (other.runtimeType == runtimeType && other is _$EncryptedJourneyRouteImpl && - (identical(other.latitude, latitude) || - other.latitude == latitude) && - (identical(other.longitude, longitude) || - other.longitude == longitude)); + const DeepCollectionEquality().equals(other.latitude, latitude) && + const DeepCollectionEquality().equals(other.longitude, longitude)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, latitude, longitude); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(latitude), + const DeepCollectionEquality().hash(longitude)); /// Create a copy of EncryptedJourneyRoute /// with the given fields replaced by the non-null parameter values. @@ -1171,8 +1176,8 @@ class _$EncryptedJourneyRouteImpl implements _EncryptedJourneyRoute { abstract class _EncryptedJourneyRoute implements EncryptedJourneyRoute { const factory _EncryptedJourneyRoute( - {@BlobConverter() required final Blob latitude, - @BlobConverter() required final Blob longitude}) = + {@BlobConverter() required final Uint8List latitude, + @BlobConverter() required final Uint8List longitude}) = _$EncryptedJourneyRouteImpl; factory _EncryptedJourneyRoute.fromJson(Map json) = @@ -1180,10 +1185,10 @@ abstract class _EncryptedJourneyRoute implements EncryptedJourneyRoute { @override @BlobConverter() - Blob get latitude; + Uint8List get latitude; @override @BlobConverter() - Blob get longitude; + Uint8List get longitude; /// Create a copy of EncryptedJourneyRoute /// with the given fields replaced by the non-null parameter values. diff --git a/data/lib/api/location/journey/journey.g.dart b/data/lib/api/location/journey/journey.g.dart index 5f82195f..9cd96722 100644 --- a/data/lib/api/location/journey/journey.g.dart +++ b/data/lib/api/location/journey/journey.g.dart @@ -60,10 +60,14 @@ _$EncryptedLocationJourneyImpl _$$EncryptedLocationJourneyImplFromJson( _$EncryptedLocationJourneyImpl( id: json['id'] as String?, user_id: json['user_id'] as String, - from_latitude: const BlobConverter().fromJson(json['from_latitude']), - from_longitude: const BlobConverter().fromJson(json['from_longitude']), - to_latitude: const BlobConverter().fromJson(json['to_latitude']), - to_longitude: const BlobConverter().fromJson(json['to_longitude']), + from_latitude: + const BlobConverter().fromJson(json['from_latitude'] as String), + from_longitude: + const BlobConverter().fromJson(json['from_longitude'] as String), + to_latitude: _$JsonConverterFromJson( + json['to_latitude'], const BlobConverter().fromJson), + to_longitude: _$JsonConverterFromJson( + json['to_longitude'], const BlobConverter().fromJson), routes: (json['routes'] as List?) ?.map((e) => EncryptedJourneyRoute.fromJson(e as Map)) @@ -83,9 +87,9 @@ Map _$$EncryptedLocationJourneyImplToJson( 'user_id': instance.user_id, 'from_latitude': const BlobConverter().toJson(instance.from_latitude), 'from_longitude': const BlobConverter().toJson(instance.from_longitude), - 'to_latitude': _$JsonConverterToJson( + 'to_latitude': _$JsonConverterToJson( instance.to_latitude, const BlobConverter().toJson), - 'to_longitude': _$JsonConverterToJson( + 'to_longitude': _$JsonConverterToJson( instance.to_longitude, const BlobConverter().toJson), 'routes': instance.routes.map((e) => e.toJson()).toList(), 'route_distance': instance.route_distance, @@ -95,6 +99,12 @@ Map _$$EncryptedLocationJourneyImplToJson( 'type': instance.type, }; +Value? _$JsonConverterFromJson( + Object? json, + Value? Function(Json json) fromJson, +) => + json == null ? null : fromJson(json as Json); + Json? _$JsonConverterToJson( Value? value, Json? Function(Value value) toJson, @@ -104,8 +114,8 @@ Json? _$JsonConverterToJson( _$EncryptedJourneyRouteImpl _$$EncryptedJourneyRouteImplFromJson( Map json) => _$EncryptedJourneyRouteImpl( - latitude: const BlobConverter().fromJson(json['latitude']), - longitude: const BlobConverter().fromJson(json['longitude']), + latitude: const BlobConverter().fromJson(json['latitude'] as String), + longitude: const BlobConverter().fromJson(json['longitude'] as String), ); Map _$$EncryptedJourneyRouteImplToJson( diff --git a/data/lib/api/location/location.dart b/data/lib/api/location/location.dart index a009c502..62f5c993 100644 --- a/data/lib/api/location/location.dart +++ b/data/lib/api/location/location.dart @@ -4,6 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/converter/blob_converter.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:geolocator/geolocator.dart'; +import 'dart:typed_data'; import 'package:google_maps_flutter/google_maps_flutter.dart'; part 'location.freezed.dart'; @@ -42,8 +43,8 @@ class EncryptedApiLocation with _$EncryptedApiLocation { const factory EncryptedApiLocation({ required String id, required String user_id, - @BlobConverter() required Blob latitude, - @BlobConverter() required Blob longitude, + @BlobConverter() required Uint8List latitude, + @BlobConverter() required Uint8List longitude, required int created_at, }) = _EncryptedApiLocation; diff --git a/data/lib/api/location/location.freezed.dart b/data/lib/api/location/location.freezed.dart index 4841fcd7..96a09881 100644 --- a/data/lib/api/location/location.freezed.dart +++ b/data/lib/api/location/location.freezed.dart @@ -262,9 +262,9 @@ mixin _$EncryptedApiLocation { String get id => throw _privateConstructorUsedError; String get user_id => throw _privateConstructorUsedError; @BlobConverter() - Blob get latitude => throw _privateConstructorUsedError; + Uint8List get latitude => throw _privateConstructorUsedError; @BlobConverter() - Blob get longitude => throw _privateConstructorUsedError; + Uint8List get longitude => throw _privateConstructorUsedError; int get created_at => throw _privateConstructorUsedError; /// Serializes this EncryptedApiLocation to a JSON map. @@ -286,8 +286,8 @@ abstract class $EncryptedApiLocationCopyWith<$Res> { $Res call( {String id, String user_id, - @BlobConverter() Blob latitude, - @BlobConverter() Blob longitude, + @BlobConverter() Uint8List latitude, + @BlobConverter() Uint8List longitude, int created_at}); } @@ -325,11 +325,11 @@ class _$EncryptedApiLocationCopyWithImpl<$Res, latitude: null == latitude ? _value.latitude : latitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, longitude: null == longitude ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -349,8 +349,8 @@ abstract class _$$EncryptedApiLocationImplCopyWith<$Res> $Res call( {String id, String user_id, - @BlobConverter() Blob latitude, - @BlobConverter() Blob longitude, + @BlobConverter() Uint8List latitude, + @BlobConverter() Uint8List longitude, int created_at}); } @@ -385,11 +385,11 @@ class __$$EncryptedApiLocationImplCopyWithImpl<$Res> latitude: null == latitude ? _value.latitude : latitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, longitude: null == longitude ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -418,10 +418,10 @@ class _$EncryptedApiLocationImpl extends _EncryptedApiLocation { final String user_id; @override @BlobConverter() - final Blob latitude; + final Uint8List latitude; @override @BlobConverter() - final Blob longitude; + final Uint8List longitude; @override final int created_at; @@ -437,18 +437,21 @@ class _$EncryptedApiLocationImpl extends _EncryptedApiLocation { other is _$EncryptedApiLocationImpl && (identical(other.id, id) || other.id == id) && (identical(other.user_id, user_id) || other.user_id == user_id) && - (identical(other.latitude, latitude) || - other.latitude == latitude) && - (identical(other.longitude, longitude) || - other.longitude == longitude) && + const DeepCollectionEquality().equals(other.latitude, latitude) && + const DeepCollectionEquality().equals(other.longitude, longitude) && (identical(other.created_at, created_at) || other.created_at == created_at)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, id, user_id, latitude, longitude, created_at); + int get hashCode => Object.hash( + runtimeType, + id, + user_id, + const DeepCollectionEquality().hash(latitude), + const DeepCollectionEquality().hash(longitude), + created_at); /// Create a copy of EncryptedApiLocation /// with the given fields replaced by the non-null parameter values. @@ -472,8 +475,8 @@ abstract class _EncryptedApiLocation extends EncryptedApiLocation { const factory _EncryptedApiLocation( {required final String id, required final String user_id, - @BlobConverter() required final Blob latitude, - @BlobConverter() required final Blob longitude, + @BlobConverter() required final Uint8List latitude, + @BlobConverter() required final Uint8List longitude, required final int created_at}) = _$EncryptedApiLocationImpl; const _EncryptedApiLocation._() : super._(); @@ -486,10 +489,10 @@ abstract class _EncryptedApiLocation extends EncryptedApiLocation { String get user_id; @override @BlobConverter() - Blob get latitude; + Uint8List get latitude; @override @BlobConverter() - Blob get longitude; + Uint8List get longitude; @override int get created_at; diff --git a/data/lib/api/location/location.g.dart b/data/lib/api/location/location.g.dart index 1a66d526..69835738 100644 --- a/data/lib/api/location/location.g.dart +++ b/data/lib/api/location/location.g.dart @@ -29,8 +29,8 @@ _$EncryptedApiLocationImpl _$$EncryptedApiLocationImplFromJson( _$EncryptedApiLocationImpl( id: json['id'] as String, user_id: json['user_id'] as String, - latitude: const BlobConverter().fromJson(json['latitude']), - longitude: const BlobConverter().fromJson(json['longitude']), + latitude: const BlobConverter().fromJson(json['latitude'] as String), + longitude: const BlobConverter().fromJson(json['longitude'] as String), created_at: (json['created_at'] as num).toInt(), ); diff --git a/data/lib/api/space/api_group_key_model.dart b/data/lib/api/space/api_group_key_model.dart index 0b147206..7bba59fb 100644 --- a/data/lib/api/space/api_group_key_model.dart +++ b/data/lib/api/space/api_group_key_model.dart @@ -3,6 +3,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/converter/blob_converter.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:typed_data'; part 'api_group_key_model.freezed.dart'; part 'api_group_key_model.g.dart'; @@ -49,9 +50,9 @@ class EncryptedDistribution with _$EncryptedDistribution { const factory EncryptedDistribution({ @Default("") String recipient_id, - @BlobConverter() required Blob ephemeral_pub, - @BlobConverter() required Blob iv, - @BlobConverter() required Blob ciphertext, + @BlobConverter() required Uint8List ephemeral_pub, + @BlobConverter() required Uint8List iv, + @BlobConverter() required Uint8List ciphertext, @Default(0) int created_at, }) = _EncryptedDistribution; @@ -59,17 +60,17 @@ class EncryptedDistribution with _$EncryptedDistribution { _$EncryptedDistributionFromJson(data); void validateFieldSizes() { - if (ephemeral_pub.bytes.length != 33 && ephemeral_pub.bytes.isNotEmpty) { + if (ephemeral_pub.length != 33 && ephemeral_pub.isNotEmpty) { throw ArgumentError( - "Invalid size for ephemeralPub: expected 33 bytes, got ${ephemeral_pub.bytes.length} bytes."); + "Invalid size for ephemeralPub: expected 33 bytes, got ${ephemeral_pub.length} bytes."); } - if (iv.bytes.length != 16 && iv.bytes.isNotEmpty) { + if (iv.length != 16 && iv.isNotEmpty) { throw ArgumentError( - "Invalid size for iv: expected 16 bytes, got ${iv.bytes.length} bytes."); + "Invalid size for iv: expected 16 bytes, got ${iv.length} bytes."); } - if (ciphertext.bytes.length > 64 * 1024 && ciphertext.bytes.isNotEmpty) { + if (ciphertext.length > 64 * 1024 && ciphertext.isNotEmpty) { throw ArgumentError( - "Invalid size for ciphertext: maximum allowed size is 64 KB, got ${ciphertext.bytes.length} bytes."); + "Invalid size for ciphertext: maximum allowed size is 64 KB, got ${ciphertext.length} bytes."); } } } @@ -82,7 +83,7 @@ class ApiSenderKeyRecord with _$ApiSenderKeyRecord { required String id, required int device_id, required String distribution_id, - @BlobConverter() required Blob record, + @BlobConverter() required Uint8List record, @Default('') String address, required int created_at, }) = _ApiSenderKeyRecord; diff --git a/data/lib/api/space/api_group_key_model.freezed.dart b/data/lib/api/space/api_group_key_model.freezed.dart index 5b3cb1ff..f41eb727 100644 --- a/data/lib/api/space/api_group_key_model.freezed.dart +++ b/data/lib/api/space/api_group_key_model.freezed.dart @@ -414,11 +414,11 @@ EncryptedDistribution _$EncryptedDistributionFromJson( mixin _$EncryptedDistribution { String get recipient_id => throw _privateConstructorUsedError; @BlobConverter() - Blob get ephemeral_pub => throw _privateConstructorUsedError; + Uint8List get ephemeral_pub => throw _privateConstructorUsedError; @BlobConverter() - Blob get iv => throw _privateConstructorUsedError; + Uint8List get iv => throw _privateConstructorUsedError; @BlobConverter() - Blob get ciphertext => throw _privateConstructorUsedError; + Uint8List get ciphertext => throw _privateConstructorUsedError; int get created_at => throw _privateConstructorUsedError; /// Serializes this EncryptedDistribution to a JSON map. @@ -439,9 +439,9 @@ abstract class $EncryptedDistributionCopyWith<$Res> { @useResult $Res call( {String recipient_id, - @BlobConverter() Blob ephemeral_pub, - @BlobConverter() Blob iv, - @BlobConverter() Blob ciphertext, + @BlobConverter() Uint8List ephemeral_pub, + @BlobConverter() Uint8List iv, + @BlobConverter() Uint8List ciphertext, int created_at}); } @@ -475,15 +475,15 @@ class _$EncryptedDistributionCopyWithImpl<$Res, ephemeral_pub: null == ephemeral_pub ? _value.ephemeral_pub : ephemeral_pub // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, iv: null == iv ? _value.iv : iv // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, ciphertext: null == ciphertext ? _value.ciphertext : ciphertext // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -503,9 +503,9 @@ abstract class _$$EncryptedDistributionImplCopyWith<$Res> @useResult $Res call( {String recipient_id, - @BlobConverter() Blob ephemeral_pub, - @BlobConverter() Blob iv, - @BlobConverter() Blob ciphertext, + @BlobConverter() Uint8List ephemeral_pub, + @BlobConverter() Uint8List iv, + @BlobConverter() Uint8List ciphertext, int created_at}); } @@ -537,15 +537,15 @@ class __$$EncryptedDistributionImplCopyWithImpl<$Res> ephemeral_pub: null == ephemeral_pub ? _value.ephemeral_pub : ephemeral_pub // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, iv: null == iv ? _value.iv : iv // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, ciphertext: null == ciphertext ? _value.ciphertext : ciphertext // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, created_at: null == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -573,13 +573,13 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { final String recipient_id; @override @BlobConverter() - final Blob ephemeral_pub; + final Uint8List ephemeral_pub; @override @BlobConverter() - final Blob iv; + final Uint8List iv; @override @BlobConverter() - final Blob ciphertext; + final Uint8List ciphertext; @override @JsonKey() final int created_at; @@ -596,11 +596,11 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { other is _$EncryptedDistributionImpl && (identical(other.recipient_id, recipient_id) || other.recipient_id == recipient_id) && - (identical(other.ephemeral_pub, ephemeral_pub) || - other.ephemeral_pub == ephemeral_pub) && - (identical(other.iv, iv) || other.iv == iv) && - (identical(other.ciphertext, ciphertext) || - other.ciphertext == ciphertext) && + const DeepCollectionEquality() + .equals(other.ephemeral_pub, ephemeral_pub) && + const DeepCollectionEquality().equals(other.iv, iv) && + const DeepCollectionEquality() + .equals(other.ciphertext, ciphertext) && (identical(other.created_at, created_at) || other.created_at == created_at)); } @@ -608,7 +608,12 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( - runtimeType, recipient_id, ephemeral_pub, iv, ciphertext, created_at); + runtimeType, + recipient_id, + const DeepCollectionEquality().hash(ephemeral_pub), + const DeepCollectionEquality().hash(iv), + const DeepCollectionEquality().hash(ciphertext), + created_at); /// Create a copy of EncryptedDistribution /// with the given fields replaced by the non-null parameter values. @@ -630,9 +635,9 @@ class _$EncryptedDistributionImpl extends _EncryptedDistribution { abstract class _EncryptedDistribution extends EncryptedDistribution { const factory _EncryptedDistribution( {final String recipient_id, - @BlobConverter() required final Blob ephemeral_pub, - @BlobConverter() required final Blob iv, - @BlobConverter() required final Blob ciphertext, + @BlobConverter() required final Uint8List ephemeral_pub, + @BlobConverter() required final Uint8List iv, + @BlobConverter() required final Uint8List ciphertext, final int created_at}) = _$EncryptedDistributionImpl; const _EncryptedDistribution._() : super._(); @@ -643,13 +648,13 @@ abstract class _EncryptedDistribution extends EncryptedDistribution { String get recipient_id; @override @BlobConverter() - Blob get ephemeral_pub; + Uint8List get ephemeral_pub; @override @BlobConverter() - Blob get iv; + Uint8List get iv; @override @BlobConverter() - Blob get ciphertext; + Uint8List get ciphertext; @override int get created_at; @@ -671,7 +676,7 @@ mixin _$ApiSenderKeyRecord { int get device_id => throw _privateConstructorUsedError; String get distribution_id => throw _privateConstructorUsedError; @BlobConverter() - Blob get record => throw _privateConstructorUsedError; + Uint8List get record => throw _privateConstructorUsedError; String get address => throw _privateConstructorUsedError; int get created_at => throw _privateConstructorUsedError; @@ -695,7 +700,7 @@ abstract class $ApiSenderKeyRecordCopyWith<$Res> { {String id, int device_id, String distribution_id, - @BlobConverter() Blob record, + @BlobConverter() Uint8List record, String address, int created_at}); } @@ -738,7 +743,7 @@ class _$ApiSenderKeyRecordCopyWithImpl<$Res, $Val extends ApiSenderKeyRecord> record: null == record ? _value.record : record // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, address: null == address ? _value.address : address // ignore: cast_nullable_to_non_nullable @@ -763,7 +768,7 @@ abstract class _$$ApiSenderKeyRecordImplCopyWith<$Res> {String id, int device_id, String distribution_id, - @BlobConverter() Blob record, + @BlobConverter() Uint8List record, String address, int created_at}); } @@ -804,7 +809,7 @@ class __$$ApiSenderKeyRecordImplCopyWithImpl<$Res> record: null == record ? _value.record : record // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, address: null == address ? _value.address : address // ignore: cast_nullable_to_non_nullable @@ -840,7 +845,7 @@ class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { final String distribution_id; @override @BlobConverter() - final Blob record; + final Uint8List record; @override @JsonKey() final String address; @@ -862,7 +867,7 @@ class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { other.device_id == device_id) && (identical(other.distribution_id, distribution_id) || other.distribution_id == distribution_id) && - (identical(other.record, record) || other.record == record) && + const DeepCollectionEquality().equals(other.record, record) && (identical(other.address, address) || other.address == address) && (identical(other.created_at, created_at) || other.created_at == created_at)); @@ -870,8 +875,8 @@ class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, id, device_id, distribution_id, record, address, created_at); + int get hashCode => Object.hash(runtimeType, id, device_id, distribution_id, + const DeepCollectionEquality().hash(record), address, created_at); /// Create a copy of ApiSenderKeyRecord /// with the given fields replaced by the non-null parameter values. @@ -895,7 +900,7 @@ abstract class _ApiSenderKeyRecord extends ApiSenderKeyRecord { {required final String id, required final int device_id, required final String distribution_id, - @BlobConverter() required final Blob record, + @BlobConverter() required final Uint8List record, final String address, required final int created_at}) = _$ApiSenderKeyRecordImpl; const _ApiSenderKeyRecord._() : super._(); @@ -911,7 +916,7 @@ abstract class _ApiSenderKeyRecord extends ApiSenderKeyRecord { String get distribution_id; @override @BlobConverter() - Blob get record; + Uint8List get record; @override String get address; @override diff --git a/data/lib/api/space/api_group_key_model.g.dart b/data/lib/api/space/api_group_key_model.g.dart index bcd61cb2..06a6ffe0 100644 --- a/data/lib/api/space/api_group_key_model.g.dart +++ b/data/lib/api/space/api_group_key_model.g.dart @@ -47,9 +47,10 @@ _$EncryptedDistributionImpl _$$EncryptedDistributionImplFromJson( Map json) => _$EncryptedDistributionImpl( recipient_id: json['recipient_id'] as String? ?? "", - ephemeral_pub: const BlobConverter().fromJson(json['ephemeral_pub']), - iv: const BlobConverter().fromJson(json['iv']), - ciphertext: const BlobConverter().fromJson(json['ciphertext']), + ephemeral_pub: + const BlobConverter().fromJson(json['ephemeral_pub'] as String), + iv: const BlobConverter().fromJson(json['iv'] as String), + ciphertext: const BlobConverter().fromJson(json['ciphertext'] as String), created_at: (json['created_at'] as num?)?.toInt() ?? 0, ); @@ -69,7 +70,7 @@ _$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( id: json['id'] as String, device_id: (json['device_id'] as num).toInt(), distribution_id: json['distribution_id'] as String, - record: const BlobConverter().fromJson(json['record']), + record: const BlobConverter().fromJson(json['record'] as String), address: json['address'] as String? ?? '', created_at: (json['created_at'] as num).toInt(), ); diff --git a/data/lib/api/space/api_sender_key_record.dart b/data/lib/api/space/api_sender_key_record.dart index d2cea53f..66bdec67 100644 --- a/data/lib/api/space/api_sender_key_record.dart +++ b/data/lib/api/space/api_sender_key_record.dart @@ -3,6 +3,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../converter/blob_converter.dart'; +import 'dart:typed_data'; part 'api_sender_key_record.freezed.dart'; part 'api_sender_key_record.g.dart'; @@ -17,7 +18,7 @@ class ApiSenderKeyRecord with _$ApiSenderKeyRecord { @Default('') String address, @Default('') String distributionId, required int created_at, - @BlobConverter() required Blob record, + @BlobConverter() required Uint8List record, }) = _ApiSenderKeyRecord; factory ApiSenderKeyRecord.fromJson(Map data) => diff --git a/data/lib/api/space/api_sender_key_record.freezed.dart b/data/lib/api/space/api_sender_key_record.freezed.dart index 9bd534e5..9f55354d 100644 --- a/data/lib/api/space/api_sender_key_record.freezed.dart +++ b/data/lib/api/space/api_sender_key_record.freezed.dart @@ -26,7 +26,7 @@ mixin _$ApiSenderKeyRecord { String get distributionId => throw _privateConstructorUsedError; int get created_at => throw _privateConstructorUsedError; @BlobConverter() - Blob get record => throw _privateConstructorUsedError; + Uint8List get record => throw _privateConstructorUsedError; /// Serializes this ApiSenderKeyRecord to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -50,7 +50,7 @@ abstract class $ApiSenderKeyRecordCopyWith<$Res> { String address, String distributionId, int created_at, - @BlobConverter() Blob record}); + @BlobConverter() Uint8List record}); } /// @nodoc @@ -99,7 +99,7 @@ class _$ApiSenderKeyRecordCopyWithImpl<$Res, $Val extends ApiSenderKeyRecord> record: null == record ? _value.record : record // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, ) as $Val); } } @@ -118,7 +118,7 @@ abstract class _$$ApiSenderKeyRecordImplCopyWith<$Res> String address, String distributionId, int created_at, - @BlobConverter() Blob record}); + @BlobConverter() Uint8List record}); } /// @nodoc @@ -165,7 +165,7 @@ class __$$ApiSenderKeyRecordImplCopyWithImpl<$Res> record: null == record ? _value.record : record // ignore: cast_nullable_to_non_nullable - as Blob, + as Uint8List, )); } } @@ -200,7 +200,7 @@ class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { final int created_at; @override @BlobConverter() - final Blob record; + final Uint8List record; @override String toString() { @@ -220,13 +220,13 @@ class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { other.distributionId == distributionId) && (identical(other.created_at, created_at) || other.created_at == created_at) && - (identical(other.record, record) || other.record == record)); + const DeepCollectionEquality().equals(other.record, record)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash( - runtimeType, id, deviceId, address, distributionId, created_at, record); + int get hashCode => Object.hash(runtimeType, id, deviceId, address, + distributionId, created_at, const DeepCollectionEquality().hash(record)); /// Create a copy of ApiSenderKeyRecord /// with the given fields replaced by the non-null parameter values. @@ -247,12 +247,13 @@ class _$ApiSenderKeyRecordImpl extends _ApiSenderKeyRecord { abstract class _ApiSenderKeyRecord extends ApiSenderKeyRecord { const factory _ApiSenderKeyRecord( - {required final String id, - final int deviceId, - final String address, - final String distributionId, - required final int created_at, - @BlobConverter() required final Blob record}) = _$ApiSenderKeyRecordImpl; + {required final String id, + final int deviceId, + final String address, + final String distributionId, + required final int created_at, + @BlobConverter() required final Uint8List record}) = + _$ApiSenderKeyRecordImpl; const _ApiSenderKeyRecord._() : super._(); factory _ApiSenderKeyRecord.fromJson(Map json) = @@ -270,7 +271,7 @@ abstract class _ApiSenderKeyRecord extends ApiSenderKeyRecord { int get created_at; @override @BlobConverter() - Blob get record; + Uint8List get record; /// Create a copy of ApiSenderKeyRecord /// with the given fields replaced by the non-null parameter values. diff --git a/data/lib/api/space/api_sender_key_record.g.dart b/data/lib/api/space/api_sender_key_record.g.dart index 21940581..7c9678f5 100644 --- a/data/lib/api/space/api_sender_key_record.g.dart +++ b/data/lib/api/space/api_sender_key_record.g.dart @@ -14,7 +14,7 @@ _$ApiSenderKeyRecordImpl _$$ApiSenderKeyRecordImplFromJson( address: json['address'] as String? ?? '', distributionId: json['distributionId'] as String? ?? '', created_at: (json['created_at'] as num).toInt(), - record: const BlobConverter().fromJson(json['record']), + record: const BlobConverter().fromJson(json['record'] as String), ); Map _$$ApiSenderKeyRecordImplToJson( diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index 0aa9e601..9578d861 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -1,5 +1,7 @@ // ignore_for_file: constant_identifier_names +import 'dart:typed_data'; + import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/network/client.dart'; import 'package:data/api/space/api_group_key_model.dart'; @@ -63,7 +65,7 @@ class ApiSpaceService { Future createSpace( String name, String adminId, - Blob? identityKeyPublic, + Uint8List? identityKeyPublic, ) async { final doc = _spaceRef.doc(); final space = ApiSpace( @@ -91,7 +93,7 @@ class ApiSpaceService { return null; } - Future joinSpace(String spaceId, String userId, Blob? identityKeyPublic, + Future joinSpace(String spaceId, String userId, Uint8List? identityKeyPublic, {int role = SPACE_MEMBER_ROLE_MEMBER}) async { final member = ApiSpaceMember( space_id: spaceId, diff --git a/data/lib/api/space/space_models.dart b/data/lib/api/space/space_models.dart index 3e76d748..511cc99a 100644 --- a/data/lib/api/space/space_models.dart +++ b/data/lib/api/space/space_models.dart @@ -4,6 +4,8 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/auth_models.dart'; import 'package:data/converter/blob_converter.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:typed_data'; + part 'space_models.freezed.dart'; part 'space_models.g.dart'; @@ -41,7 +43,7 @@ class ApiSpaceMember with _$ApiSpaceMember { required String user_id, required int role, required bool location_enabled, - @BlobConverter() Blob? identity_key_public, + @BlobConverter() Uint8List? identity_key_public, int? created_at, }) = _ApiSpaceMember; diff --git a/data/lib/api/space/space_models.freezed.dart b/data/lib/api/space/space_models.freezed.dart index 0bd7d756..929b61f4 100644 --- a/data/lib/api/space/space_models.freezed.dart +++ b/data/lib/api/space/space_models.freezed.dart @@ -234,7 +234,7 @@ mixin _$ApiSpaceMember { int get role => throw _privateConstructorUsedError; bool get location_enabled => throw _privateConstructorUsedError; @BlobConverter() - Blob? get identity_key_public => throw _privateConstructorUsedError; + Uint8List? get identity_key_public => throw _privateConstructorUsedError; int? get created_at => throw _privateConstructorUsedError; /// Serializes this ApiSpaceMember to a JSON map. @@ -259,7 +259,7 @@ abstract class $ApiSpaceMemberCopyWith<$Res> { String user_id, int role, bool location_enabled, - @BlobConverter() Blob? identity_key_public, + @BlobConverter() Uint8List? identity_key_public, int? created_at}); } @@ -310,7 +310,7 @@ class _$ApiSpaceMemberCopyWithImpl<$Res, $Val extends ApiSpaceMember> identity_key_public: freezed == identity_key_public ? _value.identity_key_public : identity_key_public // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, created_at: freezed == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -333,7 +333,7 @@ abstract class _$$ApiSpaceMemberImplCopyWith<$Res> String user_id, int role, bool location_enabled, - @BlobConverter() Blob? identity_key_public, + @BlobConverter() Uint8List? identity_key_public, int? created_at}); } @@ -382,7 +382,7 @@ class __$$ApiSpaceMemberImplCopyWithImpl<$Res> identity_key_public: freezed == identity_key_public ? _value.identity_key_public : identity_key_public // ignore: cast_nullable_to_non_nullable - as Blob?, + as Uint8List?, created_at: freezed == created_at ? _value.created_at : created_at // ignore: cast_nullable_to_non_nullable @@ -419,7 +419,7 @@ class _$ApiSpaceMemberImpl extends _ApiSpaceMember { final bool location_enabled; @override @BlobConverter() - final Blob? identity_key_public; + final Uint8List? identity_key_public; @override final int? created_at; @@ -440,16 +440,23 @@ class _$ApiSpaceMemberImpl extends _ApiSpaceMember { (identical(other.role, role) || other.role == role) && (identical(other.location_enabled, location_enabled) || other.location_enabled == location_enabled) && - (identical(other.identity_key_public, identity_key_public) || - other.identity_key_public == identity_key_public) && + const DeepCollectionEquality() + .equals(other.identity_key_public, identity_key_public) && (identical(other.created_at, created_at) || other.created_at == created_at)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, space_id, user_id, role, - location_enabled, identity_key_public, created_at); + int get hashCode => Object.hash( + runtimeType, + id, + space_id, + user_id, + role, + location_enabled, + const DeepCollectionEquality().hash(identity_key_public), + created_at); /// Create a copy of ApiSpaceMember /// with the given fields replaced by the non-null parameter values. @@ -475,7 +482,7 @@ abstract class _ApiSpaceMember extends ApiSpaceMember { required final String user_id, required final int role, required final bool location_enabled, - @BlobConverter() final Blob? identity_key_public, + @BlobConverter() final Uint8List? identity_key_public, final int? created_at}) = _$ApiSpaceMemberImpl; const _ApiSpaceMember._() : super._(); @@ -494,7 +501,7 @@ abstract class _ApiSpaceMember extends ApiSpaceMember { bool get location_enabled; @override @BlobConverter() - Blob? get identity_key_public; + Uint8List? get identity_key_public; @override int? get created_at; diff --git a/data/lib/api/space/space_models.g.dart b/data/lib/api/space/space_models.g.dart index 5a86fd5c..b32052b8 100644 --- a/data/lib/api/space/space_models.g.dart +++ b/data/lib/api/space/space_models.g.dart @@ -29,8 +29,8 @@ _$ApiSpaceMemberImpl _$$ApiSpaceMemberImplFromJson(Map json) => user_id: json['user_id'] as String, role: (json['role'] as num).toInt(), location_enabled: json['location_enabled'] as bool, - identity_key_public: - const BlobConverter().fromJson(json['identity_key_public']), + identity_key_public: _$JsonConverterFromJson( + json['identity_key_public'], const BlobConverter().fromJson), created_at: (json['created_at'] as num?)?.toInt(), ); @@ -42,11 +42,17 @@ Map _$$ApiSpaceMemberImplToJson( 'user_id': instance.user_id, 'role': instance.role, 'location_enabled': instance.location_enabled, - 'identity_key_public': _$JsonConverterToJson( + 'identity_key_public': _$JsonConverterToJson( instance.identity_key_public, const BlobConverter().toJson), 'created_at': instance.created_at, }; +Value? _$JsonConverterFromJson( + Object? json, + Value? Function(Json json) fromJson, +) => + json == null ? null : fromJson(json as Json); + Json? _$JsonConverterToJson( Value? value, Json? Function(Value value) toJson, diff --git a/data/lib/converter/blob_converter.dart b/data/lib/converter/blob_converter.dart index 7479daec..2ead51ca 100644 --- a/data/lib/converter/blob_converter.dart +++ b/data/lib/converter/blob_converter.dart @@ -4,33 +4,16 @@ import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -class BlobConverter implements JsonConverter { +class BlobConverter implements JsonConverter { const BlobConverter(); @override - Blob fromJson(dynamic json) { - if (json is Blob) { - return json; - } - - if (json is Map && json.containsKey('_byteString')) { - final base64String = json['_byteString'] as String; - print("XXX base64String $base64String"); - return Blob(Uint8List.fromList(base64String.codeUnits)); - } - - - if (json is List) { - return Blob(Uint8List.fromList(json.cast())); - } - - return Blob(Uint8List(0)); + Uint8List fromJson(String json) { + return base64Decode(json); } @override - dynamic toJson(Blob blob) { - return { - '_byteString': base64Encode(blob.bytes), - }; + String toJson(Uint8List object) { + return base64UrlEncode(object); } } diff --git a/data/lib/service/auth_service.dart b/data/lib/service/auth_service.dart index 393cd944..bc676b0a 100644 --- a/data/lib/service/auth_service.dart +++ b/data/lib/service/auth_service.dart @@ -153,9 +153,9 @@ class AuthService { ); final publicKey = - Blob(identityKeyPair.getPublicKey().publicKey.serialize()); - final privateKey = Blob(encryptedPrivateKey); - final saltBlob = Blob(salt); + identityKeyPair.getPublicKey().publicKey.serialize(); + final privateKey = encryptedPrivateKey; + final saltBlob = salt; // Store passkey in preferences userPassKeyNotifier.state = passKey; diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index f749562e..2c92f16e 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -67,11 +67,11 @@ class LocationService { .limit(1) .snapshots() .asyncMap>((snapshot) async { - // if (snapshot.docs.isNotEmpty) { - // return await Future.wait(snapshot.docs.map((doc) async { - // return toApiLocation(doc.data(), spaceId); - // })); - // } + if (snapshot.docs.isNotEmpty) { + return await Future.wait(snapshot.docs.map((doc) async { + return toApiLocation(doc.data(), spaceId); + })); + } return List.empty(); }); } @@ -82,11 +82,11 @@ class LocationService { .limit(1) .get(); - // if (snapshot.docs.isNotEmpty) { - // return snapshot.docs.map((doc) async { - // return await toApiLocation(doc.data(), spaceId); - // }).first; - // } + if (snapshot.docs.isNotEmpty) { + return snapshot.docs.map((doc) async { + return await toApiLocation(doc.data(), spaceId); + }).first; + } return null; } @@ -101,7 +101,7 @@ class LocationService { } if (user.identity_key_private == null || - (user.identity_key_private?.bytes.isEmpty ?? true)) return null; + (user.identity_key_private?.isEmpty ?? true)) return null; final groupKey = await _getGroupKey(spaceId); if (groupKey == null) { @@ -132,8 +132,8 @@ class LocationService { spaceId: spaceId, deviceId: memberKeyData.member_device_id, distribution: distribution, - privateKeyBytes: user.identity_key_private!.bytes, - salt: user.identity_key_salt!.bytes, + privateKeyBytes: user.identity_key_private!, + salt: user.identity_key_salt!, passkey: passKey, bufferedSenderKeyStore: _bufferedSenderKeystore, ); @@ -143,10 +143,8 @@ class LocationService { return null; } - final decryptedLatitude = - await groupCipher.decrypt(location.latitude.bytes); - final decryptedLongitude = - await groupCipher.decrypt(location.longitude.bytes); + final decryptedLatitude = await groupCipher.decrypt(location.latitude); + final decryptedLongitude = await groupCipher.decrypt(location.longitude); final latitude = double.tryParse(utf8.decode(decryptedLatitude)); final longitude = double.tryParse(utf8.decode(decryptedLongitude)); @@ -185,7 +183,7 @@ class LocationService { } if (user.identity_key_private == null || - (user.identity_key_private?.bytes.isEmpty ?? true)) return; + (user.identity_key_private?.isEmpty ?? true)) return; user.space_ids?.forEach((spaceId) async { print("XXX saveCurrentLocation ${spaceId}"); @@ -218,8 +216,8 @@ class LocationService { spaceId: spaceId, deviceId: memberKeyData.member_device_id, distribution: distribution, - privateKeyBytes: user.identity_key_private!.bytes, - salt: user.identity_key_salt!.bytes, + privateKeyBytes: user.identity_key_private!, + salt: user.identity_key_salt!, passkey: passKey, bufferedSenderKeyStore: _bufferedSenderKeystore, ); @@ -239,8 +237,8 @@ class LocationService { final location = EncryptedApiLocation( id: docRef.id, user_id: user.id, - latitude: Blob(encryptedLatitude), - longitude: Blob(encryptedLongitude), + latitude: encryptedLatitude, + longitude: encryptedLongitude, created_at: locationData.timestamp.millisecondsSinceEpoch, ); diff --git a/data/lib/service/space_service.dart b/data/lib/service/space_service.dart index fd87f520..de97ee30 100644 --- a/data/lib/service/space_service.dart +++ b/data/lib/service/space_service.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/api_user_service.dart'; @@ -63,7 +64,7 @@ class SpaceService { Future createSpaceAndGetInviteCode({ required String spaceName, required String userId, - required Blob? identityKeyPublic, + required Uint8List? identityKeyPublic, }) async { final spaceId = await spaceService.createSpace(spaceName, userId, identityKeyPublic); diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index 41b70c52..345cac1d 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -59,7 +59,7 @@ class BufferedSenderKeystore extends SenderKeyStore { ? Future.value(cache) : (cacheSenderKey != null ? Future.value( - SenderKeyRecord.fromSerialized(cacheSenderKey.record.bytes)) + SenderKeyRecord.fromSerialized(cacheSenderKey.record)) : null) ?? fetchSenderKeyFromServer(sender); @@ -109,7 +109,7 @@ class BufferedSenderKeystore extends SenderKeyStore { final apiSenderKeyRecord = doc.data(); try { - return SenderKeyRecord.fromSerialized(apiSenderKeyRecord.record.bytes); + return SenderKeyRecord.fromSerialized(apiSenderKeyRecord.record); } catch (e, s) { logger.e("Failed to deserialize sender key record", error: e, stackTrace: s); @@ -139,7 +139,7 @@ class BufferedSenderKeystore extends SenderKeyStore { device_id: deviceId, address: spaceId, distribution_id: distributionId, - record: Blob(record.serialize()), + record: record.serialize(), created_at: DateTime.now().millisecondsSinceEpoch); print("XXX store senderKeyRecord ${senderKeyName.sender.getName()}"); diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index 8e7c2b87..e36b4dd0 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -23,12 +23,11 @@ Future generateMemberKeyData(String spaceId, final List distributions = []; for (final member in spaceMembers) { - final publicBlob = member.identity_key_public; + final publicKeyBytes = member.identity_key_public; - if (publicBlob == null) continue; + if (publicKeyBytes == null) continue; try { - final publicKeyBytes = publicBlob.bytes; if (publicKeyBytes.length != 33) { logger.e("Invalid public key size for member ${member.user_id} length: ${publicKeyBytes.length}"); continue; diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index 41d47dfe..40a467c0 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -42,9 +42,9 @@ class EphemeralECDHUtils { final ciphertext = Uint8List.fromList(secretBox.cipherText); final distribution = EncryptedDistribution( recipient_id: receiverId, - ephemeral_pub: Blob(ephemeralPubKey.publicKey.serialize()), - iv: Blob(Uint8List.fromList(syntheticIv)), - ciphertext: Blob(ciphertext), + ephemeral_pub: ephemeralPubKey.publicKey.serialize(), + iv: Uint8List.fromList(syntheticIv), + ciphertext: ciphertext, ); distribution.validateFieldSizes(); return distribution; @@ -104,7 +104,7 @@ class EphemeralECDHUtils { final syntheticIv = message.iv; final cipherText = message.ciphertext; - final ephemeralPublic = Curve.decodePoint(message.ephemeral_pub.bytes, 0); + final ephemeralPublic = Curve.decodePoint(message.ephemeral_pub, 0); print("XXX receiverPrivateKey ${receiverPrivateKey.serialize().length}"); final masterSecret = @@ -117,7 +117,7 @@ class EphemeralECDHUtils { // // Derive cipherKey final cipherKeyFull = await mac.calculateMac( - syntheticIv.bytes, + syntheticIv, secretKey: SecretKey(cipherKeyPart1.bytes), ); @@ -129,7 +129,7 @@ class EphemeralECDHUtils { final decrypted = await algorithm.decrypt( secretKey: SecretKey(cipherKeyBytes), - SecretBox(cipherText.bytes, nonce: syntheticIv.bytes, mac: Mac.empty), + SecretBox(cipherText, nonce: syntheticIv, mac: Mac.empty), ); final verificationPart1 = await mac.calculateMac( @@ -145,8 +145,8 @@ class EphemeralECDHUtils { final ourSyntheticIv = verificationPart2.bytes.sublist(0, 16); print( - "XXXX ourSyntheticIv ${ourSyntheticIv} syntheticIv ${syntheticIv.bytes}"); - if (!listEquals(ourSyntheticIv, syntheticIv.bytes)) { + "XXXX ourSyntheticIv ${ourSyntheticIv} syntheticIv ${syntheticIv}"); + if (!listEquals(ourSyntheticIv, syntheticIv)) { throw Exception( "The computed syntheticIv didn't match the actual syntheticIv."); } From a03d93558c27ba33c61bcd0cb21478e5650754c9 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Mon, 20 Jan 2025 12:25:08 +0530 Subject: [PATCH 09/19] Cleanup --- data/lib/api/space/api_space_service.dart | 2 -- data/lib/service/location_manager.dart | 1 - data/lib/service/location_service.dart | 3 +++ data/lib/utils/ephemeral_distribution_helper.dart | 3 --- data/lib/utils/private_key_helper.dart | 12 +++--------- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index 9578d861..9891facf 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -85,7 +85,6 @@ class ApiSpaceService { } Future getSpace(String spaceId) async { - print("XXX getSpace $spaceId"); final docSnapshot = await _spaceRef.doc(spaceId).get(); if (docSnapshot.exists) { return docSnapshot.data(); @@ -202,7 +201,6 @@ class ApiSpaceService { spaceMembers: spaceMembers, bufferedSenderKeyStore: bufferedSenderKeystore); - print("XXX membersKeyData $membersKeyData"); await _updateGroupKeys(spaceId, userId, membersKeyData); } diff --git a/data/lib/service/location_manager.dart b/data/lib/service/location_manager.dart index fc4f80cf..2aa85f6f 100644 --- a/data/lib/service/location_manager.dart +++ b/data/lib/service/location_manager.dart @@ -146,7 +146,6 @@ class LocationManager { if (user == null) return; try { - print("XXX updateUserLocation ${locationPosition}"); await saveLocation(locationPosition); await _journeyRepository.saveLocationJourney( diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index 2c92f16e..77129434 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -182,6 +182,9 @@ class LocationService { return; } + print("XXX updateUserLocation ${locationData.latitude}:${locationData.longitude}"); + + if (user.identity_key_private == null || (user.identity_key_private?.isEmpty ?? true)) return; diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index 40a467c0..af1bf254 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -105,7 +105,6 @@ class EphemeralECDHUtils { final cipherText = message.ciphertext; final ephemeralPublic = Curve.decodePoint(message.ephemeral_pub, 0); - print("XXX receiverPrivateKey ${receiverPrivateKey.serialize().length}"); final masterSecret = Curve.calculateAgreement(ephemeralPublic, receiverPrivateKey); @@ -144,8 +143,6 @@ class EphemeralECDHUtils { final ourSyntheticIv = verificationPart2.bytes.sublist(0, 16); - print( - "XXXX ourSyntheticIv ${ourSyntheticIv} syntheticIv ${syntheticIv}"); if (!listEquals(ourSyntheticIv, syntheticIv)) { throw Exception( "The computed syntheticIv didn't match the actual syntheticIv."); diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index acee08bf..ac538fde 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -82,9 +82,6 @@ Future _decryptData( encryptedPrivateKey.sublist(GCM_IV_SIZE, encryptedPrivateKey.length - 16); final mac = encryptedPrivateKey.sublist(encryptedPrivateKey.length - 16); - print( - "XXX _decryptData iv ${iv.length} ciphertext ${ciphertext.length} mac ${mac.length}"); - final algorithm = AesGcm.with128bits(); final secretBox = SecretBox( @@ -116,9 +113,6 @@ Future getGroupCipher({ passkey: passkey, ); - print( - "XXX privateKeyBytes ${privateKeyBytes.length} _decodePrivateKey ${privateKey?.serialize().length}"); - if (privateKey == null) { return null; } @@ -136,18 +130,18 @@ Future getGroupCipher({ final groupAddress = SignalProtocolAddress(spaceId, deviceId); final senderKey = SenderKeyName( - // groupAddress.getDeviceId().toString(), + // groupAddress.getDeviceId().toString(), distributionMessage.id.toString(), groupAddress, ); - bufferedSenderKeyStore.loadSenderKey(senderKey); // TODO rotate sender key try { - GroupSessionBuilder(bufferedSenderKeyStore).process(senderKey, distributionMessage); + await GroupSessionBuilder(bufferedSenderKeyStore) + .process(senderKey, distributionMessage); final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); return groupCipher; } catch (e, s) { From a8b5494bc10f56662624af4b7f395dd4e4cf2150 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Mon, 20 Jan 2025 15:29:24 +0530 Subject: [PATCH 10/19] WIP - sender key issue --- data/lib/api/auth/api_user_service.dart | 9 ++++-- data/lib/service/location_service.dart | 2 -- data/lib/utils/buffered_sender_keystore.dart | 28 +++++++++++++------ .../lib/utils/distribution_key_generator.dart | 12 ++++++-- data/lib/utils/private_key_helper.dart | 18 +++++++----- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/data/lib/api/auth/api_user_service.dart b/data/lib/api/auth/api_user_service.dart index a3c505ab..d65545f6 100644 --- a/data/lib/api/auth/api_user_service.dart +++ b/data/lib/api/auth/api_user_service.dart @@ -23,7 +23,8 @@ final apiUserServiceProvider = StateProvider((ref) => ApiUserService( ref.read(isOnboardingShownPod.notifier), ref.read(currentUserPod), ref.read(locationManagerProvider), - ref.read(userPassKeyPod.notifier))); + ref.read(userPassKeyPod.notifier), + ref.read(senderKeyJsonPod.notifier))); class ApiUserService { final FirebaseFirestore _db; @@ -33,6 +34,8 @@ class ApiUserService { final StateController userSessionJsonNotifier; final StateController onBoardNotifier; final StateController userPassKeyNotifier; + final StateController senderKeyNotifier; + ApiUser? currentUser; final LocationManager locationManager; @@ -45,7 +48,8 @@ class ApiUserService { this.onBoardNotifier, this.currentUser, this.locationManager, - this.userPassKeyNotifier); + this.userPassKeyNotifier, + this.senderKeyNotifier); CollectionReference get _userRef => _db.collection("users").withConverter( @@ -296,6 +300,7 @@ class ApiUserService { onBoardNotifier.state = false; currentUserSpaceId.state = null; userPassKeyNotifier.state = null; + senderKeyNotifier.state = null; } Future updateKeys(String id, Uint8List? publicKey, diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index 77129434..fef4cf0f 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -184,12 +184,10 @@ class LocationService { print("XXX updateUserLocation ${locationData.latitude}:${locationData.longitude}"); - if (user.identity_key_private == null || (user.identity_key_private?.isEmpty ?? true)) return; user.space_ids?.forEach((spaceId) async { - print("XXX saveCurrentLocation ${spaceId}"); final groupKey = await _getGroupKey(spaceId); if (groupKey == null) { logger.d('LocationService: Group key not found for space $spaceId'); diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index 345cac1d..64f5a755 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -55,6 +55,8 @@ class BufferedSenderKeystore extends SenderKeyStore { ? null : ApiSenderKeyRecord.fromJson(jsonDecode(stateFromPref)); + print("XXX cacheSenderKey record ${cacheSenderKey?.record}"); + final keyRecordFuture = cache != null ? Future.value(cache) : (cacheSenderKey != null @@ -63,11 +65,11 @@ class BufferedSenderKeystore extends SenderKeyStore { : null) ?? fetchSenderKeyFromServer(sender); - keyRecordFuture.then((keyRecord) { + return keyRecordFuture.then((keyRecord) { _inMemoryStore[key] = keyRecord; + return keyRecord; }); - - return keyRecordFuture; + ; } @override @@ -79,15 +81,16 @@ class BufferedSenderKeystore extends SenderKeyStore { return Future.value(); } + print( + "XXX storeSenderKey record ${record.serialize()}"); + _inMemoryStore[key] = record; - saveSenderKeyToServer(senderKeyName, record).then((value) { + return saveSenderKeyToServer(senderKeyName, record).then((value) { if (value != null) { senderKeyJsonState.state = jsonEncode(value.toJson()); } }); - - return Future.value(); } Future fetchSenderKeyFromServer( @@ -109,7 +112,16 @@ class BufferedSenderKeystore extends SenderKeyStore { final apiSenderKeyRecord = doc.data(); try { - return SenderKeyRecord.fromSerialized(apiSenderKeyRecord.record); + final record = + SenderKeyRecord.fromSerialized(apiSenderKeyRecord.record); + print("XXX fetchSenderKeyFromServer record ${record.serialize()}"); + + if (record.getSenderKeyState().signingKeyPrivate.serialize().isEmpty) { + logger.e("Fetched record is incomplete, initializing new key."); + return SenderKeyRecord(); // Ensure this initializes all fields correctly. + } + + return record; } catch (e, s) { logger.e("Failed to deserialize sender key record", error: e, stackTrace: s); @@ -142,8 +154,6 @@ class BufferedSenderKeystore extends SenderKeyStore { record: record.serialize(), created_at: DateTime.now().millisecondsSinceEpoch); - print("XXX store senderKeyRecord ${senderKeyName.sender.getName()}"); - await senderKeyRef(spaceId, currentUser.id) .doc(uniqueDocId) .set(senderKeyRecord); diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index e36b4dd0..f6e93494 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:data/log/logger.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; +import 'package:uuid/uuid.dart'; import '../api/space/api_group_key_model.dart'; import '../api/space/space_models.dart'; @@ -15,7 +16,12 @@ Future generateMemberKeyData(String spaceId, final deviceId = Random.secure().nextInt(0x7FFFFFFF); final groupAddress = SignalProtocolAddress(spaceId, deviceId); final sessionBuilder = GroupSessionBuilder(bufferedSenderKeyStore); - final senderKey = SenderKeyName(Random().nextInt(0x7FFFFFFF).toString(), groupAddress); + final senderKey = + SenderKeyName("1111", groupAddress); + + print("XXXX create senderKey groupId ${senderKey.groupId}"); + print( + "XXXX create senderKey name ${senderKey.sender.getName()}, devicId ${senderKey.sender.getDeviceId()}"); final distributionMessage = await sessionBuilder.create(senderKey); final distributionBytes = distributionMessage.serialize(); @@ -29,7 +35,8 @@ Future generateMemberKeyData(String spaceId, try { if (publicKeyBytes.length != 33) { - logger.e("Invalid public key size for member ${member.user_id} length: ${publicKeyBytes.length}"); + logger.e( + "Invalid public key size for member ${member.user_id} length: ${publicKeyBytes.length}"); continue; } @@ -47,7 +54,6 @@ Future generateMemberKeyData(String spaceId, } } - return ApiMemberKeyData( data_updated_at: DateTime.now().millisecondsSinceEpoch, member_device_id: deviceId, diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index ac538fde..fb9eb584 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -73,9 +73,6 @@ Future _decryptData( throw Exception("Encrypted data is too short"); } - print( - "XXX encryptedPrivateKey ${encryptedPrivateKey.length} key ${encryptedPrivateKey}"); - final iv = encryptedPrivateKey.sublist(0, GCM_IV_SIZE); final ciphertext = @@ -128,18 +125,25 @@ Future getGroupCipher({ SenderKeyDistributionMessageWrapper.fromSerialized(decryptedDistribution); final groupAddress = SignalProtocolAddress(spaceId, deviceId); - +/*create senderKey groupId 1111 +flutter: XXXX create senderKey name V2clL6Z09pnUXzPOptGg, devicId 2068516999*/ final senderKey = SenderKeyName( - // groupAddress.getDeviceId().toString(), - distributionMessage.id.toString(), + //groupAddress.getDeviceId().toString(), + // distributionMessage.id.toString(), + "1111", + //spaceId, groupAddress, ); - bufferedSenderKeyStore.loadSenderKey(senderKey); + // await bufferedSenderKeyStore.loadSenderKey(senderKey); // TODO rotate sender key try { + print("XXXX process senderKey ${senderKey.groupId}"); + print( + "XXXX process senderKey name ${senderKey.sender.getName()}: device ${senderKey.sender.getDeviceId()}"); + await GroupSessionBuilder(bufferedSenderKeyStore) .process(senderKey, distributionMessage); final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); From 5ffcfc4bac66a87a5d033f4d64341421867fd7dc Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Mon, 20 Jan 2025 18:34:44 +0530 Subject: [PATCH 11/19] WIP - sender key issue --- app/lib/ui/flow/navigation/routes.dart | 1 - data/lib/service/location_service.dart | 2 +- data/lib/utils/buffered_sender_keystore.dart | 59 +++++++++++-------- .../lib/utils/distribution_key_generator.dart | 9 ++- data/lib/utils/private_key_helper.dart | 12 ++-- 5 files changed, 45 insertions(+), 38 deletions(-) diff --git a/app/lib/ui/flow/navigation/routes.dart b/app/lib/ui/flow/navigation/routes.dart index 21a8340e..dea1bea3 100644 --- a/app/lib/ui/flow/navigation/routes.dart +++ b/app/lib/ui/flow/navigation/routes.dart @@ -42,7 +42,6 @@ final goRouterProvider = FutureProvider((ref) async { final isIntroScreenShown = ref.read(isIntroScreenShownPod); final user = ref.read(currentUserPod); - print("XXX user $user"); if (!isIntroScreenShown) return IntroRoute().location; if (user == null) return SignInMethodRoute().location; if (user.first_name?.isEmpty ?? true) return const PickNameRoute().location; diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index fef4cf0f..cb830bcd 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -182,7 +182,7 @@ class LocationService { return; } - print("XXX updateUserLocation ${locationData.latitude}:${locationData.longitude}"); + print("XXX location encrypt"); if (user.identity_key_private == null || (user.identity_key_private?.isEmpty ?? true)) return; diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index 64f5a755..ded61fc1 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -4,6 +4,7 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:data/api/auth/auth_models.dart'; import 'package:data/log/logger.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import '../api/network/client.dart'; @@ -11,6 +12,8 @@ import '../api/space/api_group_key_model.dart'; import '../api/space/api_space_service.dart'; import '../storage/app_preferences.dart'; +part 'buffered_sender_keystore.freezed.dart'; + final bufferedSenderKeystoreProvider = Provider((ref) => BufferedSenderKeystore( ref.read(firestoreProvider), ref.read(senderKeyJsonPod.notifier), @@ -46,7 +49,10 @@ class BufferedSenderKeystore extends SenderKeyStore { @override Future loadSenderKey(SenderKeyName senderKeyName) { final sender = senderKeyName.sender; - final key = StoreKey(sender, senderKeyName.groupId, sender.getDeviceId()); + final key = StoreKey( + address: sender, + groupId: senderKeyName.groupId, + senderDeviceId: sender.getDeviceId()); final cache = _inMemoryStore[key]; @@ -55,35 +61,39 @@ class BufferedSenderKeystore extends SenderKeyStore { ? null : ApiSenderKeyRecord.fromJson(jsonDecode(stateFromPref)); - print("XXX cacheSenderKey record ${cacheSenderKey?.record}"); + print( + "XXX loadSenderKey: ${cacheSenderKey?.record.length} cache : ${cache?.serialize().length}"); final keyRecordFuture = cache != null ? Future.value(cache) - : (cacheSenderKey != null - ? Future.value( - SenderKeyRecord.fromSerialized(cacheSenderKey.record)) - : null) ?? - fetchSenderKeyFromServer(sender); - - return keyRecordFuture.then((keyRecord) { - _inMemoryStore[key] = keyRecord; - return keyRecord; - }); - ; + : (cacheSenderKey != null) + ? Future.value( + SenderKeyRecord.fromSerialized(cacheSenderKey.record)) + : fetchSenderKeyFromServer(sender); + + // keyRecordFuture.then((keyRecord) { + // _inMemoryStore[key] = keyRecord; + // print("XXX loadSenderKey update cache: ${keyRecord.serialize().length}"); + // }); + return keyRecordFuture; } @override Future storeSenderKey( SenderKeyName senderKeyName, SenderKeyRecord record) { final sender = senderKeyName.sender; - final key = StoreKey(sender, senderKeyName.groupId, sender.getDeviceId()); + final key = StoreKey( + address: sender, + groupId: senderKeyName.groupId, + senderDeviceId: sender.getDeviceId()); + + print( + "XXX storeSenderKey: ${_inMemoryStore[key]?.serialize().length} key ${key.hashCode}"); + if (_inMemoryStore.containsKey(key)) { return Future.value(); } - print( - "XXX storeSenderKey record ${record.serialize()}"); - _inMemoryStore[key] = record; return saveSenderKeyToServer(senderKeyName, record).then((value) { @@ -114,7 +124,7 @@ class BufferedSenderKeystore extends SenderKeyStore { try { final record = SenderKeyRecord.fromSerialized(apiSenderKeyRecord.record); - print("XXX fetchSenderKeyFromServer record ${record.serialize()}"); + print("XXX fetchSenderKeyFromServer: ${record.serialize().length}"); if (record.getSenderKeyState().signingKeyPrivate.serialize().isEmpty) { logger.e("Fetched record is incomplete, initializing new key."); @@ -142,6 +152,7 @@ class BufferedSenderKeystore extends SenderKeyStore { return null; } + print("XXX save to store"); final distributionId = senderKeyName.groupId; final deviceId = senderKeyName.sender.getDeviceId(); final uniqueDocId = "$deviceId-$distributionId"; @@ -166,10 +177,10 @@ class BufferedSenderKeystore extends SenderKeyStore { } } -class StoreKey { - final SignalProtocolAddress address; - final String groupId; - final int senderDeviceId; - - StoreKey(this.address, this.groupId, this.senderDeviceId); +@freezed +class StoreKey with _$StoreKey { + const factory StoreKey( + {required SignalProtocolAddress address, + required String groupId, + required int senderDeviceId}) = _StoreKey; } diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index f6e93494..6bdc9777 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -16,16 +16,15 @@ Future generateMemberKeyData(String spaceId, final deviceId = Random.secure().nextInt(0x7FFFFFFF); final groupAddress = SignalProtocolAddress(spaceId, deviceId); final sessionBuilder = GroupSessionBuilder(bufferedSenderKeyStore); - final senderKey = - SenderKeyName("1111", groupAddress); + final senderKey = SenderKeyName(Random().nextInt(0x7FFFFFFF).toString(), groupAddress); - print("XXXX create senderKey groupId ${senderKey.groupId}"); - print( - "XXXX create senderKey name ${senderKey.sender.getName()}, devicId ${senderKey.sender.getDeviceId()}"); + print("XXXX generateMemberKeyData"); final distributionMessage = await sessionBuilder.create(senderKey); final distributionBytes = distributionMessage.serialize(); + print("XXXX create senderKey groupId ${senderKey.groupId} sender name ${senderKey.sender.getName()}, devicId ${senderKey.sender.getDeviceId()}"); + final List distributions = []; for (final member in spaceMembers) { diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index fb9eb584..b2bad445 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -128,10 +128,9 @@ Future getGroupCipher({ /*create senderKey groupId 1111 flutter: XXXX create senderKey name V2clL6Z09pnUXzPOptGg, devicId 2068516999*/ final senderKey = SenderKeyName( + // deviceId.toString(), //groupAddress.getDeviceId().toString(), - // distributionMessage.id.toString(), - "1111", - //spaceId, + distributionMessage.id.toString(), groupAddress, ); @@ -140,12 +139,11 @@ flutter: XXXX create senderKey name V2clL6Z09pnUXzPOptGg, devicId 2068516999*/ // TODO rotate sender key try { - print("XXXX process senderKey ${senderKey.groupId}"); - print( - "XXXX process senderKey name ${senderKey.sender.getName()}: device ${senderKey.sender.getDeviceId()}"); - await GroupSessionBuilder(bufferedSenderKeyStore) .process(senderKey, distributionMessage); + print( + "XXXX process senderKey ${senderKey.groupId} senderKey name ${senderKey.sender.getName()}: device ${senderKey.sender.getDeviceId()}"); + final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); return groupCipher; } catch (e, s) { From 4bb5c1c531a79cd1d0027a33c67a6a4c0971566c Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Mon, 20 Jan 2025 18:34:57 +0530 Subject: [PATCH 12/19] WIP - sender key issue --- .../buffered_sender_keystore.freezed.dart | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 data/lib/utils/buffered_sender_keystore.freezed.dart diff --git a/data/lib/utils/buffered_sender_keystore.freezed.dart b/data/lib/utils/buffered_sender_keystore.freezed.dart new file mode 100644 index 00000000..5986ea47 --- /dev/null +++ b/data/lib/utils/buffered_sender_keystore.freezed.dart @@ -0,0 +1,184 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'buffered_sender_keystore.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$StoreKey { + SignalProtocolAddress get address => throw _privateConstructorUsedError; + String get groupId => throw _privateConstructorUsedError; + int get senderDeviceId => throw _privateConstructorUsedError; + + /// Create a copy of StoreKey + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $StoreKeyCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $StoreKeyCopyWith<$Res> { + factory $StoreKeyCopyWith(StoreKey value, $Res Function(StoreKey) then) = + _$StoreKeyCopyWithImpl<$Res, StoreKey>; + @useResult + $Res call( + {SignalProtocolAddress address, String groupId, int senderDeviceId}); +} + +/// @nodoc +class _$StoreKeyCopyWithImpl<$Res, $Val extends StoreKey> + implements $StoreKeyCopyWith<$Res> { + _$StoreKeyCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of StoreKey + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? address = null, + Object? groupId = null, + Object? senderDeviceId = null, + }) { + return _then(_value.copyWith( + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as SignalProtocolAddress, + groupId: null == groupId + ? _value.groupId + : groupId // ignore: cast_nullable_to_non_nullable + as String, + senderDeviceId: null == senderDeviceId + ? _value.senderDeviceId + : senderDeviceId // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$StoreKeyImplCopyWith<$Res> + implements $StoreKeyCopyWith<$Res> { + factory _$$StoreKeyImplCopyWith( + _$StoreKeyImpl value, $Res Function(_$StoreKeyImpl) then) = + __$$StoreKeyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {SignalProtocolAddress address, String groupId, int senderDeviceId}); +} + +/// @nodoc +class __$$StoreKeyImplCopyWithImpl<$Res> + extends _$StoreKeyCopyWithImpl<$Res, _$StoreKeyImpl> + implements _$$StoreKeyImplCopyWith<$Res> { + __$$StoreKeyImplCopyWithImpl( + _$StoreKeyImpl _value, $Res Function(_$StoreKeyImpl) _then) + : super(_value, _then); + + /// Create a copy of StoreKey + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? address = null, + Object? groupId = null, + Object? senderDeviceId = null, + }) { + return _then(_$StoreKeyImpl( + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as SignalProtocolAddress, + groupId: null == groupId + ? _value.groupId + : groupId // ignore: cast_nullable_to_non_nullable + as String, + senderDeviceId: null == senderDeviceId + ? _value.senderDeviceId + : senderDeviceId // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$StoreKeyImpl implements _StoreKey { + const _$StoreKeyImpl( + {required this.address, + required this.groupId, + required this.senderDeviceId}); + + @override + final SignalProtocolAddress address; + @override + final String groupId; + @override + final int senderDeviceId; + + @override + String toString() { + return 'StoreKey(address: $address, groupId: $groupId, senderDeviceId: $senderDeviceId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$StoreKeyImpl && + (identical(other.address, address) || other.address == address) && + (identical(other.groupId, groupId) || other.groupId == groupId) && + (identical(other.senderDeviceId, senderDeviceId) || + other.senderDeviceId == senderDeviceId)); + } + + @override + int get hashCode => + Object.hash(runtimeType, address, groupId, senderDeviceId); + + /// Create a copy of StoreKey + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$StoreKeyImplCopyWith<_$StoreKeyImpl> get copyWith => + __$$StoreKeyImplCopyWithImpl<_$StoreKeyImpl>(this, _$identity); +} + +abstract class _StoreKey implements StoreKey { + const factory _StoreKey( + {required final SignalProtocolAddress address, + required final String groupId, + required final int senderDeviceId}) = _$StoreKeyImpl; + + @override + SignalProtocolAddress get address; + @override + String get groupId; + @override + int get senderDeviceId; + + /// Create a copy of StoreKey + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$StoreKeyImplCopyWith<_$StoreKeyImpl> get copyWith => + throw _privateConstructorUsedError; +} From 3b162742af31412f23f02bcba004422923c9e756 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Tue, 21 Jan 2025 10:04:52 +0530 Subject: [PATCH 13/19] TEMP --- data/lib/converter/blob_converter.dart | 2 +- data/lib/utils/distribution_key_generator.dart | 2 +- data/lib/utils/private_key_helper.dart | 13 +++---------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/data/lib/converter/blob_converter.dart b/data/lib/converter/blob_converter.dart index 2ead51ca..5e5de56e 100644 --- a/data/lib/converter/blob_converter.dart +++ b/data/lib/converter/blob_converter.dart @@ -14,6 +14,6 @@ class BlobConverter implements JsonConverter { @override String toJson(Uint8List object) { - return base64UrlEncode(object); + return base64Encode(object); } } diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index 6bdc9777..bb3a9bdf 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -16,7 +16,7 @@ Future generateMemberKeyData(String spaceId, final deviceId = Random.secure().nextInt(0x7FFFFFFF); final groupAddress = SignalProtocolAddress(spaceId, deviceId); final sessionBuilder = GroupSessionBuilder(bufferedSenderKeyStore); - final senderKey = SenderKeyName(Random().nextInt(0x7FFFFFFF).toString(), groupAddress); + final senderKey = SenderKeyName(spaceId, groupAddress); print("XXXX generateMemberKeyData"); diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index b2bad445..b0aa748e 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -125,14 +125,7 @@ Future getGroupCipher({ SenderKeyDistributionMessageWrapper.fromSerialized(decryptedDistribution); final groupAddress = SignalProtocolAddress(spaceId, deviceId); -/*create senderKey groupId 1111 -flutter: XXXX create senderKey name V2clL6Z09pnUXzPOptGg, devicId 2068516999*/ - final senderKey = SenderKeyName( - // deviceId.toString(), - //groupAddress.getDeviceId().toString(), - distributionMessage.id.toString(), - groupAddress, - ); + final senderKey = SenderKeyName(spaceId, groupAddress); // await bufferedSenderKeyStore.loadSenderKey(senderKey); @@ -159,8 +152,8 @@ Future _decodePrivateKey( // try { // return Curve.decodePrivatePoint(privateKeyBytes); // } catch (e, s) { - // logger.d("Error decoding private key", error: e, stackTrace: s); + //logger.d("Error decoding private key", error: e, stackTrace: s); final decodedPrivateKey = await _decryptData(privateKeyBytes, salt, passkey); return Curve.decodePrivatePoint(decodedPrivateKey); - //} + // } } From 398f422280657bd6cadad8d296a2312f47b785f9 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Tue, 21 Jan 2025 10:11:56 +0530 Subject: [PATCH 14/19] Minor fix --- data/lib/api/auth/api_user_service.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/lib/api/auth/api_user_service.dart b/data/lib/api/auth/api_user_service.dart index d65545f6..9f4b6b6a 100644 --- a/data/lib/api/auth/api_user_service.dart +++ b/data/lib/api/auth/api_user_service.dart @@ -307,10 +307,10 @@ class ApiUserService { Uint8List? privateKey, Uint8List? saltBlob) async { await _userRef.doc(id).update({ "identity_key_public": - publicKey != null ? base64UrlEncode(publicKey) : null, + publicKey != null ? base64Encode(publicKey) : null, "identity_key_private": - privateKey != null ? base64UrlEncode(privateKey) : null, - "identity_key_salt": saltBlob != null ? base64UrlEncode(saltBlob) : null, + privateKey != null ? base64Encode(privateKey) : null, + "identity_key_salt": saltBlob != null ? base64Encode(saltBlob) : null, }); } } From 3a43f84145c9f2d786e82948faa834e7c1305a8b Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 22 Jan 2025 14:50:26 +0530 Subject: [PATCH 15/19] Fix permission issue --- data/lib/service/space_service.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/lib/service/space_service.dart b/data/lib/service/space_service.dart index de97ee30..d9d9190b 100644 --- a/data/lib/service/space_service.dart +++ b/data/lib/service/space_service.dart @@ -216,16 +216,16 @@ class SpaceService { } Future deleteSpace(String spaceId) async { + await userService.removeSpaceIdForAllSpaceMember(spaceId: spaceId); await spaceInvitationService.deleteInvitations(spaceId); await spaceService.deleteSpace(spaceId); - await userService.removeSpaceIdForAllSpaceMember(spaceId: spaceId); currentSpaceId = null; } Future leaveSpace(String spaceId, String userId) async { + await userService.removeSpaceId(userId: userId, spaceId: spaceId); await placeService.removedUserFromExistingPlaces(spaceId, userId); await spaceService.removeUserFromSpace(spaceId, userId); - await userService.removeSpaceId(userId: userId, spaceId: spaceId); currentSpaceId = null; } From 4197f3be517c1726ffdd1233f39d2ced25747321 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 22 Jan 2025 14:51:27 +0530 Subject: [PATCH 16/19] Minor change --- data/lib/utils/ephemeral_distribution_helper.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/lib/utils/ephemeral_distribution_helper.dart b/data/lib/utils/ephemeral_distribution_helper.dart index af1bf254..d701918a 100644 --- a/data/lib/utils/ephemeral_distribution_helper.dart +++ b/data/lib/utils/ephemeral_distribution_helper.dart @@ -41,11 +41,11 @@ class EphemeralECDHUtils { final ciphertext = Uint8List.fromList(secretBox.cipherText); final distribution = EncryptedDistribution( - recipient_id: receiverId, - ephemeral_pub: ephemeralPubKey.publicKey.serialize(), - iv: Uint8List.fromList(syntheticIv), - ciphertext: ciphertext, - ); + recipient_id: receiverId, + ephemeral_pub: ephemeralPubKey.publicKey.serialize(), + iv: Uint8List.fromList(syntheticIv), + ciphertext: ciphertext, + created_at: DateTime.now().millisecondsSinceEpoch); distribution.validateFieldSizes(); return distribution; } From 2d6b57910199eda3c77079fac5e331136dfbb309 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 22 Jan 2025 14:52:01 +0530 Subject: [PATCH 17/19] Minor change --- data/lib/api/space/api_space_service.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index 9891facf..e0783ce5 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -7,6 +7,7 @@ import 'package:data/api/network/client.dart'; import 'package:data/api/space/api_group_key_model.dart'; import 'package:data/api/space/space_models.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:uuid/uuid.dart'; import '../../utils/buffered_sender_keystore.dart'; @@ -107,7 +108,7 @@ class ApiSpaceService { await spaceMemberRef(spaceId).doc(userId).set(member); await userService.addSpaceId(userId, spaceId); - await _distributeSenderKeyToSpaceMembers(spaceId, userId); + await distributeSenderKeyToSpaceMembers(spaceId, userId); } Future> getMembersBySpaceId(String spaceId) async { @@ -193,7 +194,7 @@ class ApiSpaceService { await _spaceRef.doc(space.id).set(space); } - Future _distributeSenderKeyToSpaceMembers( + Future distributeSenderKeyToSpaceMembers( String spaceId, String userId) async { final spaceMembers = await getMembersBySpaceId(spaceId); final membersKeyData = await generateMemberKeyData(spaceId, @@ -224,7 +225,7 @@ class ApiSpaceService { final updates = { 'member_keys.$userId': newMemberKeyData.toJson(), - 'doc_updated_at': DateTime.now().millisecondsSinceEpoch, + 'doc_updated_at': updatedAt, }; transaction.update(groupKeysDocRef, updates); From 0da5799d02f83a0cb411fc207a386d4834747192 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 22 Jan 2025 16:07:48 +0530 Subject: [PATCH 18/19] Fix location e2ee --- data/lib/api/space/api_space_service.dart | 21 +- data/lib/service/location_service.dart | 78 +++++++- data/lib/service/space_service.dart | 2 +- data/lib/utils/buffered_sender_keystore.dart | 37 +--- .../buffered_sender_keystore.freezed.dart | 184 ------------------ .../lib/utils/distribution_key_generator.dart | 13 +- data/lib/utils/private_key_helper.dart | 56 ++++-- 7 files changed, 142 insertions(+), 249 deletions(-) delete mode 100644 data/lib/utils/buffered_sender_keystore.freezed.dart diff --git a/data/lib/api/space/api_space_service.dart b/data/lib/api/space/api_space_service.dart index e0783ce5..bb212e6b 100644 --- a/data/lib/api/space/api_space_service.dart +++ b/data/lib/api/space/api_space_service.dart @@ -108,7 +108,7 @@ class ApiSpaceService { await spaceMemberRef(spaceId).doc(userId).set(member); await userService.addSpaceId(userId, spaceId); - await distributeSenderKeyToSpaceMembers(spaceId, userId); + await _distributeSenderKeyToSpaceMembers(spaceId, userId); } Future> getMembersBySpaceId(String spaceId) async { @@ -176,6 +176,13 @@ class ApiSpaceService { } Future removeUserFromSpace(String spaceId, String userId) async { + + final docRef = spaceGroupKeysDocRef(spaceId); + await docRef.update({ + "doc_updated_at": DateTime.now().millisecondsSinceEpoch, + "member_keys.$userId": FieldValue.delete() + }); + final querySnapshot = await spaceMemberRef(spaceId).where("user_id", isEqualTo: userId).get(); @@ -183,18 +190,13 @@ class ApiSpaceService { await doc.reference.delete(); } - final docRef = spaceGroupKeysDocRef(spaceId); - await docRef.update({ - "doc_updated_at": DateTime.now().millisecondsSinceEpoch, - "member_keys.$userId": FieldValue.delete() - }); } Future updateSpace(ApiSpace space) async { await _spaceRef.doc(space.id).set(space); } - Future distributeSenderKeyToSpaceMembers( + Future _distributeSenderKeyToSpaceMembers( String spaceId, String userId) async { final spaceMembers = await getMembersBySpaceId(spaceId); final membersKeyData = await generateMemberKeyData(spaceId, @@ -202,11 +204,6 @@ class ApiSpaceService { spaceMembers: spaceMembers, bufferedSenderKeyStore: bufferedSenderKeystore); - await _updateGroupKeys(spaceId, userId, membersKeyData); - } - - Future _updateGroupKeys( - String spaceId, String userId, ApiMemberKeyData membersKeyData) async { await _db.runTransaction((transaction) async { final groupKeysDocRef = spaceGroupKeysDocRef(spaceId); final snapshot = await transaction.get(groupKeysDocRef); diff --git a/data/lib/service/location_service.dart b/data/lib/service/location_service.dart index cb830bcd..124b62ab 100644 --- a/data/lib/service/location_service.dart +++ b/data/lib/service/location_service.dart @@ -7,12 +7,15 @@ import 'package:data/log/logger.dart'; import 'package:data/storage/app_preferences.dart'; import 'package:data/utils/buffered_sender_keystore.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import '../api/auth/auth_models.dart'; import '../api/location/location.dart'; import '../api/network/client.dart'; import '../api/space/api_group_key_model.dart'; import '../api/space/api_space_service.dart'; +import '../api/space/space_models.dart'; +import '../utils/distribution_key_generator.dart'; import '../utils/private_key_helper.dart'; final locationServiceProvider = Provider((ref) { @@ -60,6 +63,16 @@ class LocationService { toFirestore: (key, options) => key.toJson()); } + CollectionReference spaceMemberRef(String spaceId) { + return FirebaseFirestore.instance + .collection(FIRESTORE_SPACE_PATH) + .doc(spaceId) + .collection(FIRESTORE_SPACE_MEMBER_PATH) + .withConverter( + fromFirestore: ApiSpaceMember.fromFireStore, + toFirestore: (member, options) => member.toJson()); + } + Stream> getCurrentLocationStream( String spaceId, String userId) { return _locationRef(spaceId, userId) @@ -94,7 +107,7 @@ class LocationService { EncryptedApiLocation location, String spaceId) async { try { final user = currentUser; - final passKey = userPasskey; + final passKey = "1111"; if (user == null || passKey == null) { logger.d('LocationService: User not found'); return null; @@ -109,26 +122,32 @@ class LocationService { return null; } - final memberKeyData = groupKey.member_keys[user.id]; + final memberKeyData = groupKey.member_keys[location.user_id]; if (memberKeyData == null) { logger.d( 'LocationService: Member key not found for user ${user.id} in space $spaceId'); return null; } + // print( + // "XXX toApiLocation of ${location.user_id} memberKeyData $memberKeyData "); + final distributions = memberKeyData.distributions .where((d) => d.recipient_id == user.id) .toList() ..sort((a, b) => b.created_at.compareTo(a.created_at)); final distribution = distributions.firstOrNull; + + // print("XXX toApiLocation of user ${user.id} distribution $distribution "); + if (distribution == null) { logger.d( - 'LocationService: Distribution not found for user ${user.id} in space $spaceId'); + 'LocationService: Distribution not found for user ${location.user_id} in space $spaceId'); return null; } - final groupCipher = await getGroupCipher( + final groupCipher = await getDecryptGroupCipher( spaceId: spaceId, deviceId: memberKeyData.member_device_id, distribution: distribution, @@ -213,7 +232,7 @@ class LocationService { return; } - final groupCipher = await getGroupCipher( + final groupCipher = await getEncryptGroupCipher( spaceId: spaceId, deviceId: memberKeyData.member_device_id, distribution: distribution, @@ -228,6 +247,16 @@ class LocationService { return; } + print( + "XXX rotateSenderKey : ${memberKeyData.data_updated_at }:${groupKey.doc_updated_at}"); + if (memberKeyData.data_updated_at < groupKey.doc_updated_at) { + // Here means the sender key data is outdated, so we need to distribute the sender key to the users. + print( + "XXX rotateSenderKey : ${memberKeyData.data_updated_at < groupKey.doc_updated_at}"); + rotateSenderKey(spaceId, + deviceId: memberKeyData.member_device_id, currentUser: user); + } + final encryptedLatitude = await groupCipher.encrypt( Uint8List.fromList(utf8.encode(locationData.latitude.toString()))); final encryptedLongitude = await groupCipher.encrypt( @@ -246,4 +275,43 @@ class LocationService { await docRef.set(location); }); } + + void rotateSenderKey(String spaceId, + {required int deviceId, required ApiUser currentUser}) async { + final userId = currentUser.id; + + final querySnapshot = await spaceMemberRef(spaceId).get(); + final spaceMembers = querySnapshot.docs.map((doc) { + return doc.data(); + }).toList(); + + final membersKeyData = await generateMemberKeyData(spaceId, + senderUserId: currentUser.id, + spaceMembers: spaceMembers, + bufferedSenderKeyStore: _bufferedSenderKeystore, + memberDeviceId: deviceId); + + await _db.runTransaction((transaction) async { + final groupKeysDocRef = spaceGroupKeysDocRef(spaceId); + final snapshot = await transaction.get(groupKeysDocRef); + + final updatedAt = DateTime.now().millisecondsSinceEpoch; + final data = snapshot.data() ?? ApiGroupKey(doc_updated_at: updatedAt); + + final oldMemberKeyData = + data.member_keys[userId] ?? const ApiMemberKeyData(); + + final newMemberKeyData = oldMemberKeyData.copyWith( + member_device_id: membersKeyData.member_device_id, + data_updated_at: updatedAt, + distributions: membersKeyData.distributions, + ); + + final updates = { + 'member_keys.$userId': newMemberKeyData.toJson(), + }; + + transaction.update(groupKeysDocRef, updates); + }); + } } diff --git a/data/lib/service/space_service.dart b/data/lib/service/space_service.dart index d9d9190b..74846141 100644 --- a/data/lib/service/space_service.dart +++ b/data/lib/service/space_service.dart @@ -223,9 +223,9 @@ class SpaceService { } Future leaveSpace(String spaceId, String userId) async { - await userService.removeSpaceId(userId: userId, spaceId: spaceId); await placeService.removedUserFromExistingPlaces(spaceId, userId); await spaceService.removeUserFromSpace(spaceId, userId); + await userService.removeSpaceId(userId: userId, spaceId: spaceId); currentSpaceId = null; } diff --git a/data/lib/utils/buffered_sender_keystore.dart b/data/lib/utils/buffered_sender_keystore.dart index ded61fc1..b3d52bc6 100644 --- a/data/lib/utils/buffered_sender_keystore.dart +++ b/data/lib/utils/buffered_sender_keystore.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart'; @@ -12,8 +13,6 @@ import '../api/space/api_group_key_model.dart'; import '../api/space/api_space_service.dart'; import '../storage/app_preferences.dart'; -part 'buffered_sender_keystore.freezed.dart'; - final bufferedSenderKeystoreProvider = Provider((ref) => BufferedSenderKeystore( ref.read(firestoreProvider), ref.read(senderKeyJsonPod.notifier), @@ -27,7 +26,7 @@ class BufferedSenderKeystore extends SenderKeyStore { BufferedSenderKeystore(this._db, this.senderKeyJsonState, this.userJsonState); - final Map _inMemoryStore = {}; + final _inMemoryStore = HashMap(); CollectionReference senderKeyRef( String spaceId, String userId) { @@ -49,12 +48,7 @@ class BufferedSenderKeystore extends SenderKeyStore { @override Future loadSenderKey(SenderKeyName senderKeyName) { final sender = senderKeyName.sender; - final key = StoreKey( - address: sender, - groupId: senderKeyName.groupId, - senderDeviceId: sender.getDeviceId()); - - final cache = _inMemoryStore[key]; + final cache = _inMemoryStore[senderKeyName]; final stateFromPref = senderKeyJsonState.state; final cacheSenderKey = stateFromPref == null @@ -71,30 +65,20 @@ class BufferedSenderKeystore extends SenderKeyStore { SenderKeyRecord.fromSerialized(cacheSenderKey.record)) : fetchSenderKeyFromServer(sender); - // keyRecordFuture.then((keyRecord) { - // _inMemoryStore[key] = keyRecord; - // print("XXX loadSenderKey update cache: ${keyRecord.serialize().length}"); - // }); return keyRecordFuture; } @override Future storeSenderKey( SenderKeyName senderKeyName, SenderKeyRecord record) { - final sender = senderKeyName.sender; - final key = StoreKey( - address: sender, - groupId: senderKeyName.groupId, - senderDeviceId: sender.getDeviceId()); + // print( + // "XXX storeSenderKey: ${_inMemoryStore[key]?.serialize().length} key ${key.hashCode}"); - print( - "XXX storeSenderKey: ${_inMemoryStore[key]?.serialize().length} key ${key.hashCode}"); - - if (_inMemoryStore.containsKey(key)) { + if (_inMemoryStore.containsKey(senderKeyName)) { return Future.value(); } - _inMemoryStore[key] = record; + _inMemoryStore[senderKeyName] = record; return saveSenderKeyToServer(senderKeyName, record).then((value) { if (value != null) { @@ -177,10 +161,3 @@ class BufferedSenderKeystore extends SenderKeyStore { } } -@freezed -class StoreKey with _$StoreKey { - const factory StoreKey( - {required SignalProtocolAddress address, - required String groupId, - required int senderDeviceId}) = _StoreKey; -} diff --git a/data/lib/utils/buffered_sender_keystore.freezed.dart b/data/lib/utils/buffered_sender_keystore.freezed.dart deleted file mode 100644 index 5986ea47..00000000 --- a/data/lib/utils/buffered_sender_keystore.freezed.dart +++ /dev/null @@ -1,184 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'buffered_sender_keystore.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -/// @nodoc -mixin _$StoreKey { - SignalProtocolAddress get address => throw _privateConstructorUsedError; - String get groupId => throw _privateConstructorUsedError; - int get senderDeviceId => throw _privateConstructorUsedError; - - /// Create a copy of StoreKey - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $StoreKeyCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $StoreKeyCopyWith<$Res> { - factory $StoreKeyCopyWith(StoreKey value, $Res Function(StoreKey) then) = - _$StoreKeyCopyWithImpl<$Res, StoreKey>; - @useResult - $Res call( - {SignalProtocolAddress address, String groupId, int senderDeviceId}); -} - -/// @nodoc -class _$StoreKeyCopyWithImpl<$Res, $Val extends StoreKey> - implements $StoreKeyCopyWith<$Res> { - _$StoreKeyCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of StoreKey - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? address = null, - Object? groupId = null, - Object? senderDeviceId = null, - }) { - return _then(_value.copyWith( - address: null == address - ? _value.address - : address // ignore: cast_nullable_to_non_nullable - as SignalProtocolAddress, - groupId: null == groupId - ? _value.groupId - : groupId // ignore: cast_nullable_to_non_nullable - as String, - senderDeviceId: null == senderDeviceId - ? _value.senderDeviceId - : senderDeviceId // ignore: cast_nullable_to_non_nullable - as int, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$StoreKeyImplCopyWith<$Res> - implements $StoreKeyCopyWith<$Res> { - factory _$$StoreKeyImplCopyWith( - _$StoreKeyImpl value, $Res Function(_$StoreKeyImpl) then) = - __$$StoreKeyImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {SignalProtocolAddress address, String groupId, int senderDeviceId}); -} - -/// @nodoc -class __$$StoreKeyImplCopyWithImpl<$Res> - extends _$StoreKeyCopyWithImpl<$Res, _$StoreKeyImpl> - implements _$$StoreKeyImplCopyWith<$Res> { - __$$StoreKeyImplCopyWithImpl( - _$StoreKeyImpl _value, $Res Function(_$StoreKeyImpl) _then) - : super(_value, _then); - - /// Create a copy of StoreKey - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? address = null, - Object? groupId = null, - Object? senderDeviceId = null, - }) { - return _then(_$StoreKeyImpl( - address: null == address - ? _value.address - : address // ignore: cast_nullable_to_non_nullable - as SignalProtocolAddress, - groupId: null == groupId - ? _value.groupId - : groupId // ignore: cast_nullable_to_non_nullable - as String, - senderDeviceId: null == senderDeviceId - ? _value.senderDeviceId - : senderDeviceId // ignore: cast_nullable_to_non_nullable - as int, - )); - } -} - -/// @nodoc - -class _$StoreKeyImpl implements _StoreKey { - const _$StoreKeyImpl( - {required this.address, - required this.groupId, - required this.senderDeviceId}); - - @override - final SignalProtocolAddress address; - @override - final String groupId; - @override - final int senderDeviceId; - - @override - String toString() { - return 'StoreKey(address: $address, groupId: $groupId, senderDeviceId: $senderDeviceId)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$StoreKeyImpl && - (identical(other.address, address) || other.address == address) && - (identical(other.groupId, groupId) || other.groupId == groupId) && - (identical(other.senderDeviceId, senderDeviceId) || - other.senderDeviceId == senderDeviceId)); - } - - @override - int get hashCode => - Object.hash(runtimeType, address, groupId, senderDeviceId); - - /// Create a copy of StoreKey - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$StoreKeyImplCopyWith<_$StoreKeyImpl> get copyWith => - __$$StoreKeyImplCopyWithImpl<_$StoreKeyImpl>(this, _$identity); -} - -abstract class _StoreKey implements StoreKey { - const factory _StoreKey( - {required final SignalProtocolAddress address, - required final String groupId, - required final int senderDeviceId}) = _$StoreKeyImpl; - - @override - SignalProtocolAddress get address; - @override - String get groupId; - @override - int get senderDeviceId; - - /// Create a copy of StoreKey - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$StoreKeyImplCopyWith<_$StoreKeyImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/data/lib/utils/distribution_key_generator.dart b/data/lib/utils/distribution_key_generator.dart index bb3a9bdf..fdef03b3 100644 --- a/data/lib/utils/distribution_key_generator.dart +++ b/data/lib/utils/distribution_key_generator.dart @@ -12,18 +12,23 @@ import 'ephemeral_distribution_helper.dart'; Future generateMemberKeyData(String spaceId, {required String senderUserId, required List spaceMembers, - required BufferedSenderKeystore bufferedSenderKeyStore}) async { - final deviceId = Random.secure().nextInt(0x7FFFFFFF); + required BufferedSenderKeystore bufferedSenderKeyStore, + int? memberDeviceId}) async { + final deviceId = memberDeviceId ?? Random.secure().nextInt(0x7FFFFFFF); final groupAddress = SignalProtocolAddress(spaceId, deviceId); final sessionBuilder = GroupSessionBuilder(bufferedSenderKeyStore); final senderKey = SenderKeyName(spaceId, groupAddress); - print("XXXX generateMemberKeyData"); + //print("XXXX generateMemberKeyData senderUserId $senderUserId"); final distributionMessage = await sessionBuilder.create(senderKey); final distributionBytes = distributionMessage.serialize(); - print("XXXX create senderKey groupId ${senderKey.groupId} sender name ${senderKey.sender.getName()}, devicId ${senderKey.sender.getDeviceId()}"); + // print("XXX create DM:${distributionMessage.serialize()}"); + //print("XXX create SK ${senderKey.serialize()}"); + + // print( + // "XXXX create senderKey groupId ${senderKey.groupId} sender name ${senderKey.sender.getName()}, devicId ${senderKey.sender.getDeviceId()}"); final List distributions = []; diff --git a/data/lib/utils/private_key_helper.dart b/data/lib/utils/private_key_helper.dart index b0aa748e..fb144c80 100644 --- a/data/lib/utils/private_key_helper.dart +++ b/data/lib/utils/private_key_helper.dart @@ -95,7 +95,7 @@ Future _decryptData( return Uint8List.fromList(decrypted); } -Future getGroupCipher({ +Future getDecryptGroupCipher({ required String spaceId, required int deviceId, required Uint8List privateKeyBytes, @@ -104,6 +104,8 @@ Future getGroupCipher({ required EncryptedDistribution distribution, required BufferedSenderKeystore bufferedSenderKeyStore, }) async { + final bufferedSenderKeyStore = InMemorySenderKeyStore(); + final privateKey = await _decodePrivateKey( privateKeyBytes: privateKeyBytes, salt: salt, @@ -127,18 +129,51 @@ Future getGroupCipher({ final groupAddress = SignalProtocolAddress(spaceId, deviceId); final senderKey = SenderKeyName(spaceId, groupAddress); - // await bufferedSenderKeyStore.loadSenderKey(senderKey); - - // TODO rotate sender key + // print("XXX distributionMessage ${distributionMessage.serialize()}"); + // print("XXX senderKey ${senderKey.serialize()}"); try { await GroupSessionBuilder(bufferedSenderKeyStore) .process(senderKey, distributionMessage); - print( - "XXXX process senderKey ${senderKey.groupId} senderKey name ${senderKey.sender.getName()}: device ${senderKey.sender.getDeviceId()}"); - final groupCipher = GroupCipher(bufferedSenderKeyStore, senderKey); - return groupCipher; + return GroupCipher(bufferedSenderKeyStore, senderKey); + } catch (e, s) { + logger.e("Error processing group session", error: e, stackTrace: s); + return null; + } +} + +Future getEncryptGroupCipher({ + required String spaceId, + required int deviceId, + required Uint8List privateKeyBytes, + required Uint8List salt, + required String passkey, + required EncryptedDistribution distribution, + required BufferedSenderKeystore bufferedSenderKeyStore, +}) async { + final privateKey = await _decodePrivateKey( + privateKeyBytes: privateKeyBytes, + salt: salt, + passkey: passkey, + ); + + if (privateKey == null) { + return null; + } + + final decryptedDistribution = + await EphemeralECDHUtils.decrypt(distribution, privateKey); + + if (decryptedDistribution == null) { + return null; + } + + final groupAddress = SignalProtocolAddress(spaceId, deviceId); + final senderKey = SenderKeyName(spaceId, groupAddress); + + try { + return GroupCipher(bufferedSenderKeyStore, senderKey); } catch (e, s) { logger.e("Error processing group session", error: e, stackTrace: s); return null; @@ -149,11 +184,6 @@ Future _decodePrivateKey( {required Uint8List privateKeyBytes, required Uint8List salt, required String passkey}) async { - // try { - // return Curve.decodePrivatePoint(privateKeyBytes); - // } catch (e, s) { - //logger.d("Error decoding private key", error: e, stackTrace: s); final decodedPrivateKey = await _decryptData(privateKeyBytes, salt, passkey); return Curve.decodePrivatePoint(decodedPrivateKey); - // } } From 57c71e6dc957f3f595bcd6b75ade71fe342bc148 Mon Sep 17 00:00:00 2001 From: Radhika Canopas Date: Wed, 22 Jan 2025 16:16:28 +0530 Subject: [PATCH 19/19] Resolve conflicts --- .../ui/flow/journey/timeline/journey_timeline_screen.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart b/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart index 8688f38b..1d737cd3 100644 --- a/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart +++ b/app/lib/ui/flow/journey/timeline/journey_timeline_screen.dart @@ -217,9 +217,9 @@ class _JourneyTimelineScreenState extends ConsumerState { ) { final location = LatLng(journey.from_latitude, journey.from_longitude); final steadyDuration = - notifier.getSteadyDuration(journey.created_at!, journey.update_at!); + notifier.getSteadyDuration(journey.created_at, journey.updated_at); final formattedTime = _getFormattedJourneyTime( - journey.created_at ?? 0, journey.update_at ?? 0); + journey.created_at, journey.updated_at); return Padding( padding: EdgeInsets.only(top: isFirstItem ? 16 : 0), child: SizedBox( @@ -273,7 +273,7 @@ class _JourneyTimelineScreenState extends ConsumerState { String mapType, ) { final time = _getFormattedJourneyTime( - journey.created_at ?? 0, journey.update_at ?? 0); + journey.created_at, journey.updated_at); final distance = notifier.getDistanceString(journey.route_distance ?? 0); final fromLatLng = LatLng(journey.from_latitude, journey.from_longitude); final toLatLng =