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.

136 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. radius: 50,
  38. ),
  39. const SizedBox(height: 10),
  40. Text(
  41. widget.user.username,
  42. style: const TextStyle(
  43. fontSize: 35,
  44. ),
  45. ),
  46. const SizedBox(height: 30),
  47. TextButton(
  48. onPressed: sendFriendRequest,
  49. child: Text(
  50. 'Send Friend Request',
  51. style: TextStyle(
  52. color: Theme.of(context).colorScheme.onPrimary,
  53. fontSize: 20,
  54. ),
  55. ),
  56. style: ButtonStyle(
  57. backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.primary),
  58. padding: MaterialStateProperty.all<EdgeInsets>(
  59. const EdgeInsets.only(left: 20, right: 20, top: 8, bottom: 8)),
  60. ),
  61. ),
  62. showFailed ? const SizedBox(height: 20) : const SizedBox.shrink(),
  63. failedMessage(context),
  64. ],
  65. ),
  66. ),
  67. );
  68. }
  69. Widget failedMessage(BuildContext context) {
  70. if (!showFailed) {
  71. return const SizedBox.shrink();
  72. }
  73. return Text(
  74. 'Failed to send friend request',
  75. style: TextStyle(
  76. color: Theme.of(context).colorScheme.error,
  77. fontSize: 16,
  78. ),
  79. );
  80. }
  81. Future<void> sendFriendRequest() async {
  82. MyProfile profile = await MyProfile.getProfile();
  83. String publicKeyString = CryptoUtils.encodeRSAPublicKeyToPem(profile.publicKey!);
  84. RSAPublicKey friendPublicKey = CryptoUtils.rsaPublicKeyFromPem(widget.user.publicKey);
  85. final symmetricKey = AesHelper.deriveKey(generateRandomString(32));
  86. String payloadJson = jsonEncode({
  87. 'user_id': widget.user.id,
  88. 'friend_id': base64.encode(CryptoUtils.rsaEncrypt(
  89. Uint8List.fromList(profile.id.codeUnits),
  90. friendPublicKey,
  91. )),
  92. 'friend_username': base64.encode(CryptoUtils.rsaEncrypt(
  93. Uint8List.fromList(profile.username.codeUnits),
  94. friendPublicKey,
  95. )),
  96. 'symmetric_key': base64.encode(CryptoUtils.rsaEncrypt(
  97. Uint8List.fromList(symmetricKey),
  98. friendPublicKey,
  99. )),
  100. 'asymmetric_public_key': AesHelper.aesEncrypt(
  101. symmetricKey,
  102. Uint8List.fromList(publicKeyString.codeUnits),
  103. ),
  104. });
  105. var resp = await http.post(
  106. await MyProfile.getServerUrl('api/v1/auth/friend_request'),
  107. headers: {
  108. 'cookie': await getSessionCookie(),
  109. },
  110. body: payloadJson,
  111. );
  112. if (resp.statusCode != 200) {
  113. showFailed = true;
  114. setState(() {});
  115. return;
  116. }
  117. Navigator.pop(context);
  118. }
  119. }