Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
3.9 KiB

  1. import 'dart:convert';
  2. import 'dart:typed_data';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter_dotenv/flutter_dotenv.dart';
  5. import 'package:http/http.dart' as http;
  6. import 'package:pointycastle/impl.dart';
  7. import '/components/custom_circle_avatar.dart';
  8. import '/data_models/user_search.dart';
  9. import '/models/my_profile.dart';
  10. import '/utils/encryption/aes_helper.dart';
  11. import '/utils/storage/session_cookie.dart';
  12. import '/utils/strings.dart';
  13. import '/utils/encryption/crypto_utils.dart';
  14. @immutable
  15. class UserSearchResult extends StatefulWidget {
  16. final UserSearch user;
  17. const UserSearchResult({
  18. Key? key,
  19. required this.user,
  20. }) : super(key: key);
  21. @override
  22. _UserSearchResultState createState() => _UserSearchResultState();
  23. }
  24. class _UserSearchResultState extends State<UserSearchResult>{
  25. bool showFailed = false;
  26. @override
  27. Widget build(BuildContext context) {
  28. return Center(
  29. child: Padding(
  30. padding: const EdgeInsets.only(top: 30),
  31. child: Column(
  32. crossAxisAlignment: CrossAxisAlignment.center,
  33. children: <Widget>[
  34. CustomCircleAvatar(
  35. initials: widget.user.username[0].toUpperCase(),
  36. icon: const Icon(Icons.person, size: 80),
  37. imagePath: null,
  38. radius: 50,
  39. ),
  40. const SizedBox(height: 10),
  41. Text(
  42. widget.user.username,
  43. style: const TextStyle(
  44. fontSize: 35,
  45. ),
  46. ),
  47. const SizedBox(height: 30),
  48. TextButton(
  49. onPressed: sendFriendRequest,
  50. child: Text(
  51. 'Send Friend Request',
  52. style: TextStyle(
  53. color: Theme.of(context).colorScheme.onPrimary,
  54. fontSize: 20,
  55. ),
  56. ),
  57. style: ButtonStyle(
  58. backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.primary),
  59. padding: MaterialStateProperty.all<EdgeInsets>(
  60. const EdgeInsets.only(left: 20, right: 20, top: 8, bottom: 8)),
  61. ),
  62. ),
  63. showFailed ? const SizedBox(height: 20) : const SizedBox.shrink(),
  64. failedMessage(context),
  65. ],
  66. ),
  67. ),
  68. );
  69. }
  70. Widget failedMessage(BuildContext context) {
  71. if (!showFailed) {
  72. return const SizedBox.shrink();
  73. }
  74. return Text(
  75. 'Failed to send friend request',
  76. style: TextStyle(
  77. color: Theme.of(context).colorScheme.error,
  78. fontSize: 16,
  79. ),
  80. );
  81. }
  82. Future<void> sendFriendRequest() async {
  83. MyProfile profile = await MyProfile.getProfile();
  84. String publicKeyString = CryptoUtils.encodeRSAPublicKeyToPem(profile.publicKey!);
  85. RSAPublicKey friendPublicKey = CryptoUtils.rsaPublicKeyFromPem(widget.user.publicKey);
  86. final symmetricKey = AesHelper.deriveKey(generateRandomString(32));
  87. String payloadJson = jsonEncode({
  88. 'user_id': widget.user.id,
  89. 'friend_id': base64.encode(CryptoUtils.rsaEncrypt(
  90. Uint8List.fromList(profile.id.codeUnits),
  91. friendPublicKey,
  92. )),
  93. 'friend_username': base64.encode(CryptoUtils.rsaEncrypt(
  94. Uint8List.fromList(profile.username.codeUnits),
  95. friendPublicKey,
  96. )),
  97. 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(
  98. Uint8List.fromList(symmetricKey),
  99. friendPublicKey,
  100. )),
  101. 'asymmetric_public_key': AesHelper.aesEncrypt(
  102. symmetricKey,
  103. Uint8List.fromList(publicKeyString.codeUnits),
  104. ),
  105. });
  106. var resp = await http.post(
  107. await MyProfile.getServerUrl('api/v1/auth/friend_request'),
  108. headers: {
  109. 'cookie': await getSessionCookie(),
  110. },
  111. body: payloadJson,
  112. );
  113. if (resp.statusCode != 200) {
  114. showFailed = true;
  115. setState(() {});
  116. return;
  117. }
  118. Navigator.pop(context);
  119. }
  120. }