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.

162 lines
4.1 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:flutter/material.dart';
  4. import 'package:pointycastle/impl.dart';
  5. import 'package:qr_code_scanner/qr_code_scanner.dart';
  6. import 'package:sqflite/sqflite.dart';
  7. import 'package:uuid/uuid.dart';
  8. import 'package:http/http.dart' as http;
  9. import '/models/friends.dart';
  10. import '/models/my_profile.dart';
  11. import '/utils/encryption/aes_helper.dart';
  12. import '/utils/encryption/crypto_utils.dart';
  13. import '/utils/storage/database.dart';
  14. import '/utils/strings.dart';
  15. import '/utils/storage/session_cookie.dart';
  16. import 'flash_message.dart';
  17. class QrReader extends StatefulWidget {
  18. const QrReader({
  19. Key? key,
  20. }) : super(key: key);
  21. @override
  22. State<QrReader> createState() => _QrReaderState();
  23. }
  24. class _QrReaderState extends State<QrReader> {
  25. final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
  26. Barcode? result;
  27. QRViewController? controller;
  28. // In order to get hot reload to work we need to pause the camera if the platform
  29. // is android, or resume the camera if the platform is iOS.
  30. @override
  31. void reassemble() {
  32. super.reassemble();
  33. if (Platform.isAndroid) {
  34. controller!.pauseCamera();
  35. } else if (Platform.isIOS) {
  36. controller!.resumeCamera();
  37. }
  38. }
  39. @override
  40. Widget build(BuildContext context) {
  41. return Scaffold(
  42. body: Column(
  43. children: <Widget>[
  44. Expanded(
  45. flex: 5,
  46. child: QRView(
  47. key: qrKey,
  48. onQRViewCreated: _onQRViewCreated,
  49. formatsAllowed: const [BarcodeFormat.qrcode],
  50. overlay: QrScannerOverlayShape(),
  51. ),
  52. ),
  53. ],
  54. ),
  55. );
  56. }
  57. void _onQRViewCreated(QRViewController controller) {
  58. this.controller = controller;
  59. controller.scannedDataStream.listen((scanData) {
  60. addFriend(scanData)
  61. .then((dynamic ret) {
  62. if (ret) {
  63. // Delay exit to prevent exit mid way through rendering
  64. Future.delayed(Duration.zero, () {
  65. Navigator.of(context).pop();
  66. });
  67. }
  68. });
  69. });
  70. }
  71. @override
  72. void dispose() {
  73. controller?.dispose();
  74. super.dispose();
  75. }
  76. Future<bool> addFriend(Barcode scanData) async {
  77. Map<String, dynamic> friendJson = jsonDecode(scanData.code!);
  78. RSAPublicKey publicKey = CryptoUtils.rsaPublicKeyFromPem(
  79. String.fromCharCodes(
  80. base64.decode(
  81. friendJson['k']
  82. )
  83. )
  84. );
  85. MyProfile profile = await MyProfile.getProfile();
  86. var uuid = const Uuid();
  87. final symmetricKey1 = AesHelper.deriveKey(generateRandomString(32));
  88. final symmetricKey2 = AesHelper.deriveKey(generateRandomString(32));
  89. Friend request1 = Friend(
  90. id: uuid.v4(),
  91. userId: friendJson['i'],
  92. username: profile.username,
  93. friendId: profile.id,
  94. friendSymmetricKey: base64.encode(symmetricKey1),
  95. publicKey: profile.publicKey!,
  96. acceptedAt: DateTime.now(),
  97. );
  98. Friend request2 = Friend(
  99. id: uuid.v4(),
  100. userId: profile.id,
  101. friendId: friendJson['i'],
  102. username: friendJson['u'],
  103. friendSymmetricKey: base64.encode(symmetricKey2),
  104. publicKey: publicKey,
  105. acceptedAt: DateTime.now(),
  106. );
  107. String payload = jsonEncode([
  108. request1.payloadJson(),
  109. request2.payloadJson(),
  110. ]);
  111. var resp = await http.post(
  112. await MyProfile.getServerUrl('api/v1/auth/friend_request/qr_code'),
  113. headers: <String, String>{
  114. 'Content-Type': 'application/json; charset=UTF-8',
  115. 'cookie': await getSessionCookie(),
  116. },
  117. body: payload,
  118. );
  119. if (resp.statusCode != 200) {
  120. showMessage(
  121. 'Failed to add friend, please try again later',
  122. context
  123. );
  124. return false;
  125. }
  126. final db = await getDatabaseConnection();
  127. await db.insert(
  128. 'friends',
  129. request1.toMap(),
  130. conflictAlgorithm: ConflictAlgorithm.replace,
  131. );
  132. await db.insert(
  133. 'friends',
  134. request2.toMap(),
  135. conflictAlgorithm: ConflictAlgorithm.replace,
  136. );
  137. return true;
  138. }
  139. }