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.

175 lines
4.5 KiB

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:pointycastle/impl.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
import 'package:sqflite/sqflite.dart';
import 'package:uuid/uuid.dart';
import 'package:http/http.dart' as http;
import '/components/custom_title_bar.dart';
import '/database/models/friends.dart';
import '/database/models/my_profile.dart';
import '/utils/encryption/aes_helper.dart';
import '/utils/encryption/crypto_utils.dart';
import '/utils/storage/database.dart';
import '/utils/strings.dart';
import '/utils/storage/session_cookie.dart';
import 'flash_message.dart';
class QrReader extends StatefulWidget {
const QrReader({
Key? key,
}) : super(key: key);
@override
State<QrReader> createState() => _QrReaderState();
}
class _QrReaderState extends State<QrReader> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
Barcode? result;
QRViewController? controller;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
@override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
} else if (Platform.isIOS) {
controller!.resumeCamera();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomTitleBar(
title: Text(
'Add Friend',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).appBarTheme.toolbarTextStyle?.color
)
),
showBack: true,
backgroundColor: Colors.transparent,
),
body: Column(
children: <Widget>[
Expanded(
flex: 5,
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
formatsAllowed: const [BarcodeFormat.qrcode],
overlay: QrScannerOverlayShape(),
),
),
],
),
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
addFriend(scanData)
.then((dynamic ret) {
if (ret) {
// Delay exit to prevent exit mid way through rendering
Future.delayed(Duration.zero, () {
Navigator.of(context).pop();
});
}
});
});
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
Future<bool> addFriend(Barcode scanData) async {
Map<String, dynamic> friendJson = jsonDecode(scanData.code!);
RSAPublicKey publicKey = CryptoUtils.rsaPublicKeyFromPem(
String.fromCharCodes(
base64.decode(
friendJson['k']
)
)
);
MyProfile profile = await MyProfile.getProfile();
var uuid = const Uuid();
final symmetricKey1 = AesHelper.deriveKey(generateRandomString(32));
final symmetricKey2 = AesHelper.deriveKey(generateRandomString(32));
Friend request1 = Friend(
id: uuid.v4(),
userId: friendJson['i'],
username: profile.username,
friendId: profile.id,
friendSymmetricKey: base64.encode(symmetricKey1),
publicKey: profile.publicKey!,
acceptedAt: DateTime.now(),
);
Friend request2 = Friend(
id: uuid.v4(),
userId: profile.id,
friendId: friendJson['i'],
username: friendJson['u'],
friendSymmetricKey: base64.encode(symmetricKey2),
publicKey: publicKey,
acceptedAt: DateTime.now(),
);
String payload = jsonEncode([
request1.payloadJson(),
request2.payloadJson(),
]);
var resp = await http.post(
await MyProfile.getServerUrl('api/v1/auth/friend_request/qr_code'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'cookie': await getSessionCookie(),
},
body: payload,
);
if (resp.statusCode != 200) {
showMessage(
'Failed to add friend, please try again later',
context
);
return false;
}
final db = await getDatabaseConnection();
await db.insert(
'friends',
request1.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
await db.insert(
'friends',
request2.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return true;
}
}