Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: upgrade WatermelonDB to 0.27.1 #5865

Merged
merged 20 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions android/app/src/play/java/chat/rocket/reactnative/Ejson.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class Ejson {

String tmid;

Content content;

private MMKV mmkv;

private String TOKEN_KEY = "reactnativemeteor_usertoken-";
Expand Down Expand Up @@ -102,4 +104,9 @@ public class Sender {
String username;
String _id;
}

public class Content {
String ciphertext;
String algorithm;
}
}
131 changes: 85 additions & 46 deletions android/app/src/play/java/chat/rocket/reactnative/Encryption.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.google.gson.Gson;
import com.nozbe.watermelondb.Database;
import com.pedrouid.crypto.RCTAes;
import com.pedrouid.crypto.RCTRsaUtils;
import com.pedrouid.crypto.RSA;
import com.pedrouid.crypto.Util;

import java.io.File;
import java.lang.reflect.Field;
import java.security.SecureRandom;
import java.util.Arrays;
Expand All @@ -31,6 +31,14 @@ class Message {
}
}

class DecryptedContent {
String msg;

DecryptedContent(String msg) {
this.msg = msg;
}
}

class PrivateKey {
String d;
String dp;
Expand Down Expand Up @@ -58,44 +66,66 @@ class Room {

class Encryption {
private Gson gson = new Gson();
private String E2ERoomKey;
private String keyId;

public static Encryption shared = new Encryption();
private ReactApplicationContext reactContext;

public Room readRoom(final Ejson ejson) throws NoSuchFieldException {
public Room readRoom(final Ejson ejson) {
String dbName = getDatabaseName(ejson.serverURL());
SQLiteDatabase db = null;

try {
db = SQLiteDatabase.openDatabase(dbName, null, SQLiteDatabase.OPEN_READONLY);
String[] queryArgs = {ejson.rid};

Cursor cursor = db.rawQuery("SELECT * FROM subscriptions WHERE id == ? LIMIT 1", queryArgs);

if (cursor.getCount() == 0) {
cursor.close();
return null;
}

cursor.moveToFirst();
String e2eKey = cursor.getString(cursor.getColumnIndex("e2e_key"));
Boolean encrypted = cursor.getInt(cursor.getColumnIndex("encrypted")) > 0;
cursor.close();

return new Room(e2eKey, encrypted);

} catch (Exception e) {
Log.e("[ENCRYPTION]", "Error reading room", e);
return null;

} finally {
if (db != null) {
db.close();
}
}
}

private String getDatabaseName(String serverUrl) {
int resId = reactContext.getResources().getIdentifier("rn_config_reader_custom_package", "string", reactContext.getPackageName());
String className = reactContext.getString(resId);
Class clazz = null;
Boolean isOfficial = false;

try {
clazz = Class.forName(className + ".BuildConfig");
Class<?> clazz = Class.forName(className + ".BuildConfig");
Field IS_OFFICIAL = clazz.getField("IS_OFFICIAL");
isOfficial = (Boolean) IS_OFFICIAL.get(null);
} catch (ClassNotFoundException | IllegalAccessException e) {
} catch (Exception e) {
e.printStackTrace();
}
String dbName = ejson.serverURL().replace("https://", "");

String dbName = serverUrl.replace("https://", "");
if (!isOfficial) {
dbName += "-experimental";
}
dbName += ".db";
Database database = new Database(dbName, reactContext, SQLiteDatabase.CREATE_IF_NECESSARY);
String[] query = {ejson.rid};
Cursor cursor = database.rawQuery("select * from subscriptions where id == ? limit 1", query);

// Room not found
if (cursor.getCount() == 0) {
return null;
}

cursor.moveToFirst();
String e2eKey = cursor.getString(cursor.getColumnIndex("e2e_key"));
Boolean encrypted = cursor.getInt(cursor.getColumnIndex("encrypted")) > 0;
cursor.close();

return new Room(e2eKey, encrypted);
// Old issue. Safer to accept it then to migrate away from it.
dbName += ".db.db";
// https://github.com/Nozbe/WatermelonDB/blob/a757e646141437ad9a06f7314ad5555a8a4d252e/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java#L18
File databasePath = new File(reactContext.getDatabasePath(dbName).getPath().replace("/databases", ""));
return databasePath.getPath();
}

public String readUserKey(final Ejson ejson) throws Exception {
Expand All @@ -120,7 +150,7 @@ public String readUserKey(final Ejson ejson) throws Exception {
}

public String decryptRoomKey(final String e2eKey, final Ejson ejson) throws Exception {
String key = e2eKey.substring(12, e2eKey.length());
String key = e2eKey.substring(12);
keyId = e2eKey.substring(0, 12);

String userKey = readUserKey(ejson);
Expand All @@ -138,6 +168,15 @@ public String decryptRoomKey(final String e2eKey, final Ejson ejson) throws Exce
return Util.bytesToHex(decoded);
}

private String decryptText(String text, String e2eKey) throws Exception {
String msg = text.substring(12);
byte[] msgData = Base64.decode(msg, Base64.NO_WRAP);
String b64 = Base64.encodeToString(Arrays.copyOfRange(msgData, 16, msgData.length), Base64.DEFAULT);
String decrypted = RCTAes.decrypt(b64, e2eKey, Util.bytesToHex(Arrays.copyOfRange(msgData, 0, 16)));
byte[] data = Base64.decode(decrypted, Base64.NO_WRAP);
return new String(data, "UTF-8");
}

public String decryptMessage(final Ejson ejson, final ReactApplicationContext reactContext) {
try {
this.reactContext = reactContext;
Expand All @@ -151,19 +190,22 @@ public String decryptMessage(final Ejson ejson, final ReactApplicationContext re
return null;
}

String message = ejson.msg;
String msg = message.substring(12, message.length());
byte[] msgData = Base64.decode(msg, Base64.NO_WRAP);

String b64 = Base64.encodeToString(Arrays.copyOfRange(msgData, 16, msgData.length), Base64.DEFAULT);

String decrypted = RCTAes.decrypt(b64, e2eKey, Util.bytesToHex(Arrays.copyOfRange(msgData, 0, 16)));
byte[] data = Base64.decode(decrypted, Base64.NO_WRAP);
Message m = gson.fromJson(new String(data, "UTF-8"), Message.class);
if (ejson.msg != null && !ejson.msg.isEmpty()) {
String message = ejson.msg;
String decryptedText = decryptText(message, e2eKey);
Message m = gson.fromJson(decryptedText, Message.class);
return m.text;
} else if (ejson.content != null && "rc.v1.aes-sha2".equals(ejson.content.algorithm)) {
String message = ejson.content.ciphertext;
String decryptedText = decryptText(message, e2eKey);
DecryptedContent m = gson.fromJson(decryptedText, DecryptedContent.class);
return m.msg;
} else {
return null;
}

return m.text;
} catch (Exception e) {
Log.d("[ROCKETCHAT][E2E]", Log.getStackTraceString(e));
Log.e("[ROCKETCHAT][E2E]", Log.getStackTraceString(e));
}

return null;
Expand Down Expand Up @@ -193,29 +235,26 @@ public String encryptMessage(final String message, final String id, final Ejson

return keyId + Base64.encodeToString(concat(bytes, data), Base64.NO_WRAP);
} catch (Exception e) {
Log.d("[ROCKETCHAT][E2E]", Log.getStackTraceString(e));
Log.e("[ROCKETCHAT][E2E]", Log.getStackTraceString(e));
}

return message;
}

static byte[] concat(byte[]... arrays) {
// Determine the length of the result array
int totalLength = 0;
for (int i = 0; i < arrays.length; i++) {
totalLength += arrays[i].length;
for (byte[] array : arrays) {
totalLength += array.length;
}

// create the result array
byte[] result = new byte[totalLength];

// copy the source arrays into the result array
int currentIndex = 0;
for (int i = 0; i < arrays.length; i++) {
System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length);
currentIndex += arrays[i].length;

for (byte[] array : arrays) {
System.arraycopy(array, 0, result, currentIndex, array.length);
currentIndex += array.length;
}

return result;
}
}
}
2 changes: 1 addition & 1 deletion app/sagas/createChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const handleRequest = function* handleRequest({ data }) {
try {
const db = database.active;
const subCollection = db.get('subscriptions');
yield db.action(async () => {
yield db.write(async () => {
await subCollection.create(s => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
Object.assign(s, sub);
Expand Down
2 changes: 1 addition & 1 deletion app/sagas/createDiscussion.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const handleRequest = function* handleRequest({ data }) {
try {
const db = database.active;
const subCollection = db.get('subscriptions');
yield db.action(async () => {
yield db.write(async () => {
await subCollection.create(s => {
s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema);
Object.assign(s, sub);
Expand Down
4 changes: 2 additions & 2 deletions app/sagas/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const handleLoginRequest = function* handleLoginRequest({
// Saves username on server history
const serversDB = database.servers;
const serversHistoryCollection = serversDB.get('servers_history');
yield serversDB.action(async () => {
yield serversDB.write(async () => {
try {
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
if (serversHistory?.length) {
Expand Down Expand Up @@ -216,7 +216,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
nickname: user.nickname,
requirePasswordChange: user.requirePasswordChange
};
yield serversDB.action(async () => {
yield serversDB.write(async () => {
try {
const userRecord = await usersCollection.find(user.id);
await userRecord.update(record => {
Expand Down
4 changes: 2 additions & 2 deletions app/sagas/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) {
try {
const serverRecord = yield serversCollection.find(server);

return serversDB.action(async () => {
return serversDB.write(async () => {
await serverRecord.update(record => {
record.roomsUpdatedAt = newRoomsUpdatedAt;
});
Expand Down Expand Up @@ -123,7 +123,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
})
];

yield db.action(async () => {
yield db.write(async () => {
await db.batch(...allRecords);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,4 @@
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTViewManager.h>
#import <React/RCTBridgeModule.h>

// Silence warning
#import "../../node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/SupportingFiles/Bridging.h"
#import <React/RCTBridgeModule.h>
22 changes: 17 additions & 5 deletions ios/NotificationService/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,23 @@ class NotificationService: UNNotificationServiceExtension {
func processPayload(payload: Payload) {
// If is a encrypted message
if payload.messageType == .e2e {
if let message = payload.msg, let rid = payload.rid {
if let decryptedMessage = rocketchat?.decryptMessage(rid: rid, message: message) {
bestAttemptContent?.body = decryptedMessage
if let roomType = payload.type, roomType == .group, let sender = payload.senderName {
bestAttemptContent?.body = "\(sender): \(decryptedMessage)"
if let rid = payload.rid {
let messageToDecrypt: String?

if let msg = payload.msg, !msg.isEmpty {
messageToDecrypt = msg
} else if let content = payload.content, content.algorithm == "rc.v1.aes-sha2" {
messageToDecrypt = content.ciphertext
} else {
messageToDecrypt = nil
}

if let messageToDecrypt = messageToDecrypt, !messageToDecrypt.isEmpty {
if let decryptedMessage = rocketchat?.decryptMessage(rid: rid, message: messageToDecrypt) {
bestAttemptContent?.body = decryptedMessage
if let roomType = payload.type, roomType == .group, let sender = payload.senderName {
bestAttemptContent?.body = "\(sender): \(decryptedMessage)"
}
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1306,12 +1306,12 @@ PODS:
- SDWebImageWebPCoder (0.13.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- simdjson (1.0.0)
- simdjson (3.1.0-wmelon1)
- SocketRocket (0.6.1)
- TOCropViewController (2.6.1)
- WatermelonDB (0.25.5):
- WatermelonDB (0.27.1):
- React
- React-jsi
- simdjson
- Yoga (1.14.0)
- ZXingObjC/Core (3.6.9)
- ZXingObjC/OneD (3.6.9):
Expand Down Expand Up @@ -1773,10 +1773,10 @@ SPEC CHECKSUMS:
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
SDWebImageWebPCoder: af09429398d99d524cae2fe00f6f0f6e491ed102
simdjson: c96317b3a50dff3468a42f586ab7ed22c6ab2fd9
simdjson: e6bfae9ce4bcdc80452d388d593816f1ca2106f3
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
WatermelonDB: 6ae836b52d11281d87187ff2283480e44b111771
WatermelonDB: 842d22ba555425aa9f3ce551239a001200c539bc
Yoga: d17d2cc8105eed528474683b42e2ea310e1daf61
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5

Expand Down
Loading