mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-10-31 12:07:24 +01:00 
			
		
		
		
	feat: Better encryption / verification
This commit is contained in:
		
							parent
							
								
									ede963fffc
								
							
						
					
					
						commit
						1ff986e7be
					
				| @ -1,3 +1,11 @@ | ||||
| # Version 0.22.0 | ||||
| ### Features | ||||
| - Broadcast self-verification | ||||
| ### Changes | ||||
| - Undecryptable events have a "verify" button, if you haven't verified yet | ||||
| - User bottom sheet lists verified status | ||||
| - Lock icon next to input bar can now be red | ||||
| 
 | ||||
| # Version 0.21.0 - 2020-10-28 | ||||
| ### Features | ||||
| - New user viewer | ||||
|  | ||||
| @ -62,20 +62,30 @@ class _EncryptionButtonState extends State<EncryptionButton> { | ||||
|         .onSync | ||||
|         .stream | ||||
|         .listen((s) => setState(() => null)); | ||||
|     return FutureBuilder<List<DeviceKeys>>( | ||||
|         future: widget.room.encrypted ? widget.room.getUserDeviceKeys() : null, | ||||
|     return FutureBuilder<List<User>>( | ||||
|         future: | ||||
|             widget.room.encrypted ? widget.room.requestParticipants() : null, | ||||
|         builder: (BuildContext context, snapshot) { | ||||
|           Color color; | ||||
|           if (widget.room.encrypted && snapshot.hasData) { | ||||
|             var data = snapshot.data; | ||||
|             final deviceKeysList = data; | ||||
|             color = Colors.orange; | ||||
|             if (deviceKeysList.indexWhere((DeviceKeys deviceKeys) => | ||||
|                     deviceKeys.verified == false && | ||||
|                     deviceKeys.blocked == false) == | ||||
|                 -1) { | ||||
|               color = Colors.black.withGreen(220).withOpacity(0.75); | ||||
|             final users = snapshot.data; | ||||
|             users.removeWhere((u) => | ||||
|                 !{Membership.invite, Membership.join}.contains(u.membership) || | ||||
|                 !widget.room.client.userDeviceKeys.containsKey(u.id)); | ||||
|             var allUsersValid = true; | ||||
|             var oneUserInvalid = false; | ||||
|             for (final u in users) { | ||||
|               final status = widget.room.client.userDeviceKeys[u.id].verified; | ||||
|               if (status != UserVerifiedStatus.verified) { | ||||
|                 allUsersValid = false; | ||||
|               } | ||||
|               if (status == UserVerifiedStatus.unknownDevice) { | ||||
|                 oneUserInvalid = true; | ||||
|               } | ||||
|             } | ||||
|             color = oneUserInvalid | ||||
|                 ? Colors.red | ||||
|                 : (allUsersValid ? Colors.green : Colors.orange); | ||||
|           } else if (!widget.room.encrypted && | ||||
|               widget.room.joinRules != JoinRules.public) { | ||||
|             color = null; | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import 'package:famedlysdk/famedlysdk.dart'; | ||||
| import 'package:famedlysdk/encryption.dart'; | ||||
| import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; | ||||
| import 'package:fluffychat/components/message_content.dart'; | ||||
| import 'package:fluffychat/components/reply_content.dart'; | ||||
| @ -13,6 +14,8 @@ import '../avatar.dart'; | ||||
| import '../matrix.dart'; | ||||
| import '../message_reactions.dart'; | ||||
| import 'state_message.dart'; | ||||
| import '../../views/key_verification.dart'; | ||||
| import '../../utils/app_route.dart'; | ||||
| 
 | ||||
| class Message extends StatelessWidget { | ||||
|   final Event event; | ||||
| @ -37,6 +40,36 @@ class Message extends StatelessWidget { | ||||
|   /// of touchscreen. | ||||
|   static bool useMouse = false; | ||||
| 
 | ||||
|   void _verifyOrRequestKey(BuildContext context) async { | ||||
|     final client = Matrix.of(context).client; | ||||
|     if (client.isUnknownSession && client.encryption.crossSigning.enabled) { | ||||
|       final req = | ||||
|           await client.userDeviceKeys[client.userID].startVerification(); | ||||
|       req.onUpdate = () async { | ||||
|         if (req.state == KeyVerificationState.done) { | ||||
|           for (var i = 0; i < 12; i++) { | ||||
|             if (await client.encryption.keyManager.isCached()) { | ||||
|               break; | ||||
|             } | ||||
|             await Future.delayed(Duration(seconds: 1)); | ||||
|           } | ||||
|           final timeline = await event.room.getTimeline(); | ||||
|           timeline.requestKeys(); | ||||
|           timeline.cancelSubscriptions(); | ||||
|         } | ||||
|       }; | ||||
|       await Navigator.of(context).push( | ||||
|         AppRoute.defaultRoute( | ||||
|           context, | ||||
|           KeyVerificationView(request: req), | ||||
|         ), | ||||
|       ); | ||||
|     } else { | ||||
|       await SimpleDialogs(context).tryRequestWithLoadingDialog( | ||||
|           event.getDisplayEvent(timeline).requestKey()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (event.type == EventTypes.Unknown) { | ||||
| @ -137,12 +170,13 @@ class Message extends StatelessWidget { | ||||
|                       RaisedButton( | ||||
|                         color: color.withAlpha(100), | ||||
|                         child: Text( | ||||
|                           L10n.of(context).requestPermission, | ||||
|                           client.isUnknownSession && | ||||
|                                   client.encryption.crossSigning.enabled | ||||
|                               ? L10n.of(context).verify | ||||
|                               : L10n.of(context).requestPermission, | ||||
|                           style: TextStyle(color: textColor), | ||||
|                         ), | ||||
|                         onPressed: () => SimpleDialogs(context) | ||||
|                             .tryRequestWithLoadingDialog( | ||||
|                                 displayEvent.requestKey()), | ||||
|                         onPressed: () => _verifyOrRequestKey(context), | ||||
|                       ), | ||||
|                     SizedBox(height: 4), | ||||
|                     Opacity( | ||||
|  | ||||
| @ -269,10 +269,21 @@ class MatrixState extends State<Matrix> { | ||||
|       }); | ||||
|       onKeyVerificationRequestSub ??= client.onKeyVerificationRequest.stream | ||||
|           .listen((KeyVerification request) async { | ||||
|         var hidPopup = false; | ||||
|         request.onUpdate = () { | ||||
|           if (!hidPopup && | ||||
|               {KeyVerificationState.done, KeyVerificationState.error} | ||||
|                   .contains(request.state)) { | ||||
|             Navigator.of(context, rootNavigator: true).pop('dialog'); | ||||
|           } | ||||
|           hidPopup = true; | ||||
|         }; | ||||
|         if (await SimpleDialogs(context).askConfirmation( | ||||
|           titleText: L10n.of(context).newVerificationRequest, | ||||
|           contentText: L10n.of(context).askVerificationRequest(request.userId), | ||||
|         )) { | ||||
|           request.onUpdate = null; | ||||
|           hidPopup = true; | ||||
|           await request.acceptVerification(); | ||||
|           await Navigator.of(context).push( | ||||
|             AppRoute.defaultRoute( | ||||
| @ -281,6 +292,8 @@ class MatrixState extends State<Matrix> { | ||||
|             ), | ||||
|           ); | ||||
|         } else { | ||||
|           request.onUpdate = null; | ||||
|           hidPopup = true; | ||||
|           await request.rejectVerification(); | ||||
|         } | ||||
|       }); | ||||
|  | ||||
| @ -12,6 +12,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; | ||||
| import '../utils/presence_extension.dart'; | ||||
| import 'dialogs/simple_dialogs.dart'; | ||||
| import 'matrix.dart'; | ||||
| import '../views/key_verification.dart'; | ||||
| import '../utils/app_route.dart'; | ||||
| 
 | ||||
| class UserBottomSheet extends StatelessWidget { | ||||
|   final User user; | ||||
| @ -72,9 +74,22 @@ class UserBottomSheet extends StatelessWidget { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void _verifyAction(BuildContext context) async { | ||||
|     final client = Matrix.of(context).client; | ||||
|     final req = await client.userDeviceKeys[user.id].startVerification(); | ||||
|     await Navigator.of(context).push( | ||||
|       AppRoute.defaultRoute( | ||||
|         context, | ||||
|         KeyVerificationView(request: req), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final presence = Matrix.of(context).client.presences[user.id]; | ||||
|     final client = Matrix.of(context).client; | ||||
|     final presence = client.presences[user.id]; | ||||
|     final verificationStatus = client.userDeviceKeys[user.id]?.verified; | ||||
|     var items = <PopupMenuEntry<String>>[]; | ||||
| 
 | ||||
|     if (onMention != null) { | ||||
| @ -145,6 +160,21 @@ class UserBottomSheet extends StatelessWidget { | ||||
|                 ), | ||||
|                 title: Text(user.calcDisplayname()), | ||||
|                 actions: [ | ||||
|                   if (verificationStatus != null) | ||||
|                     InkWell( | ||||
|                       child: Icon( | ||||
|                         Icons.lock, | ||||
|                         color: { | ||||
|                               UserVerifiedStatus.unknownDevice: Colors.red, | ||||
|                               UserVerifiedStatus.verified: Colors.green, | ||||
|                             }[verificationStatus] ?? | ||||
|                             Colors.orange, | ||||
|                       ), | ||||
|                       onTap: () => | ||||
|                           verificationStatus == UserVerifiedStatus.unknown | ||||
|                               ? _verifyAction(context) | ||||
|                               : null, | ||||
|                     ), | ||||
|                   if (user.id != Matrix.of(context).client.userID) | ||||
|                     PopupMenuButton( | ||||
|                       itemBuilder: (_) => items, | ||||
|  | ||||
| @ -143,11 +143,10 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> { | ||||
|                             itemBuilder: (c) { | ||||
|                               var items = <PopupMenuEntry<String>>[]; | ||||
|                               if (room | ||||
|                                           .client | ||||
|                                           .userDeviceKeys[deviceKeys[i].userId] | ||||
|                                           .verified == | ||||
|                                       UserVerifiedStatus.unknown && | ||||
|                                   deviceKeys[i].userId != room.client.userID) { | ||||
|                                       .client | ||||
|                                       .userDeviceKeys[deviceKeys[i].userId] | ||||
|                                       .verified == | ||||
|                                   UserVerifiedStatus.unknown) { | ||||
|                                 items.add(PopupMenuItem( | ||||
|                                   child: Text(L10n.of(context).verifyUser), | ||||
|                                   value: 'verify_user', | ||||
| @ -211,7 +210,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> { | ||||
|                         }, | ||||
|                         child: ListTile( | ||||
|                           title: Text( | ||||
|                             "${deviceKeys[i].unsigned["device_display_name"] ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}", | ||||
|                             '${deviceKeys[i].deviceDisplayName ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}', | ||||
|                             style: TextStyle( | ||||
|                                 color: deviceKeys[i].blocked | ||||
|                                     ? Colors.red | ||||
| @ -220,9 +219,7 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> { | ||||
|                                         : Colors.orange), | ||||
|                           ), | ||||
|                           subtitle: Text( | ||||
|                             deviceKeys[i] | ||||
|                                 .keys['ed25519:${deviceKeys[i].deviceId}'] | ||||
|                                 .beautified, | ||||
|                             deviceKeys[i].ed25519Key.beautified, | ||||
|                             style: TextStyle( | ||||
|                                 color: Theme.of(context) | ||||
|                                     .textTheme | ||||
|  | ||||
							
								
								
									
										46
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								pubspec.lock
									
									
									
									
									
								
							| @ -49,7 +49,7 @@ packages: | ||||
|       name: async | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.5.0-nullsafety.2" | ||||
|     version: "2.5.0-nullsafety.1" | ||||
|   base58check: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -63,7 +63,7 @@ packages: | ||||
|       name: boolean_selector | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.0-nullsafety.2" | ||||
|     version: "2.1.0-nullsafety.1" | ||||
|   bot_toast: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -98,14 +98,14 @@ packages: | ||||
|       name: characters | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.1.0-nullsafety.4" | ||||
|     version: "1.1.0-nullsafety.3" | ||||
|   charcode: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: charcode | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.2.0-nullsafety.2" | ||||
|     version: "1.2.0-nullsafety.1" | ||||
|   circular_check_box: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -126,14 +126,14 @@ packages: | ||||
|       name: clock | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.1.0-nullsafety.2" | ||||
|     version: "1.1.0-nullsafety.1" | ||||
|   collection: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: collection | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.15.0-nullsafety.4" | ||||
|     version: "1.15.0-nullsafety.3" | ||||
|   convert: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -203,13 +203,13 @@ packages: | ||||
|       name: fake_async | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.2.0-nullsafety.2" | ||||
|     version: "1.2.0-nullsafety.1" | ||||
|   famedlysdk: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       path: "." | ||||
|       ref: "955fb747c29eab76b17eb9a13ebc15026e917fb8" | ||||
|       resolved-ref: "955fb747c29eab76b17eb9a13ebc15026e917fb8" | ||||
|       ref: "15d817023d34f813e95eba6ca8c71c575b8c2457" | ||||
|       resolved-ref: "15d817023d34f813e95eba6ca8c71c575b8c2457" | ||||
|       url: "https://gitlab.com/famedly/famedlysdk.git" | ||||
|     source: git | ||||
|     version: "0.0.1" | ||||
| @ -540,7 +540,7 @@ packages: | ||||
|       name: matcher | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.12.10-nullsafety.2" | ||||
|     version: "0.12.10-nullsafety.1" | ||||
|   matrix_file_e2ee: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -561,7 +561,7 @@ packages: | ||||
|       name: meta | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.3.0-nullsafety.5" | ||||
|     version: "1.3.0-nullsafety.3" | ||||
|   mime: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -661,7 +661,7 @@ packages: | ||||
|       name: path | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.8.0-nullsafety.2" | ||||
|     version: "1.8.0-nullsafety.1" | ||||
|   path_provider: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -876,7 +876,7 @@ packages: | ||||
|       name: source_span | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.8.0-nullsafety.3" | ||||
|     version: "1.8.0-nullsafety.2" | ||||
|   sqflite: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -911,21 +911,21 @@ packages: | ||||
|       name: stack_trace | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.10.0-nullsafety.5" | ||||
|     version: "1.10.0-nullsafety.1" | ||||
|   stream_channel: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: stream_channel | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.0-nullsafety.2" | ||||
|     version: "2.1.0-nullsafety.1" | ||||
|   string_scanner: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: string_scanner | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.1.0-nullsafety.2" | ||||
|     version: "1.1.0-nullsafety.1" | ||||
|   swipe_to_action: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -946,28 +946,28 @@ packages: | ||||
|       name: term_glyph | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.2.0-nullsafety.2" | ||||
|     version: "1.2.0-nullsafety.1" | ||||
|   test: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: test | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.16.0-nullsafety.7" | ||||
|     version: "1.16.0-nullsafety.5" | ||||
|   test_api: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: test_api | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.2.19-nullsafety.4" | ||||
|     version: "0.2.19-nullsafety.2" | ||||
|   test_core: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: test_core | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.3.12-nullsafety.7" | ||||
|     version: "0.3.12-nullsafety.5" | ||||
|   timezone: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -981,7 +981,7 @@ packages: | ||||
|       name: typed_data | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.3.0-nullsafety.4" | ||||
|     version: "1.3.0-nullsafety.3" | ||||
|   universal_html: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -1065,7 +1065,7 @@ packages: | ||||
|       name: vector_math | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.0-nullsafety.4" | ||||
|     version: "2.1.0-nullsafety.3" | ||||
|   vm_service: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -1137,5 +1137,5 @@ packages: | ||||
|     source: hosted | ||||
|     version: "0.1.2" | ||||
| sdks: | ||||
|   dart: ">=2.11.0-0.0 <=2.11.0-260.0.dev" | ||||
|   dart: ">=2.10.2 <=2.11.0-161.0.dev" | ||||
|   flutter: ">=1.22.2 <2.0.0" | ||||
|  | ||||
| @ -23,7 +23,7 @@ dependencies: | ||||
|   famedlysdk: | ||||
|     git: | ||||
|       url: https://gitlab.com/famedly/famedlysdk.git | ||||
|       ref: 955fb747c29eab76b17eb9a13ebc15026e917fb8 | ||||
|       ref: 15d817023d34f813e95eba6ca8c71c575b8c2457 | ||||
| 
 | ||||
|   localstorage: ^3.0.3+6 | ||||
|   file_picker_cross: 4.2.2 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sorunome
						Sorunome