Skip to content

Commit

Permalink
chore: upgrade WatermelonDB to 0.27.1 (#5865)
Browse files Browse the repository at this point in the history
  • Loading branch information
diegolmello authored Nov 5, 2024
1 parent 84a6510 commit a748cd6
Show file tree
Hide file tree
Showing 22 changed files with 656 additions and 559 deletions.
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

0 comments on commit a748cd6

Please sign in to comment.