From 0195506549ca43694a7e4f6f9faa7e6be1944369 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Thu, 24 Sep 2020 11:00:50 -0300 Subject: [PATCH] [NEW] E2E Encryption push (Android) (#2481) * poc push encryption android * eof * format code * react-native-simple-crypto update * prevent find sub twice * remove storage and use ejson storage * invalidate yarn cache * Bump crypto and fix db path * Fix google-services path Co-authored-by: Diego Mello --- .circleci/config.yml | 2 +- .../chat/rocket/reactnative/Callback.java | 1 + .../reactnative/CustomPushNotification.java | 68 +++--- .../reactnative/DismissNotification.java | 10 +- .../java/chat/rocket/reactnative/Ejson.java | 10 + .../chat/rocket/reactnative/Encryption.java | 210 ++++++++++++++++++ .../rocket/reactnative/LoadNotification.java | 133 +++++------ .../rocket/reactnative/ReplyBroadcast.java | 25 ++- package.json | 4 +- patches/@nozbe+watermelondb+0.19.0.patch | 22 ++ yarn.lock | 8 +- 11 files changed, 381 insertions(+), 112 deletions(-) create mode 100644 android/app/src/play/java/chat/rocket/reactnative/Encryption.java diff --git a/.circleci/config.yml b/.circleci/config.yml index a1cf5ced..7667897c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -170,7 +170,7 @@ jobs: if [[ $KEYSTORE ]]; then echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json fi - working_directory: android/app + working_directory: android/app/src/play - run: name: Config variables diff --git a/android/app/src/play/java/chat/rocket/reactnative/Callback.java b/android/app/src/play/java/chat/rocket/reactnative/Callback.java index 2c2b2833..b94484eb 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/Callback.java +++ b/android/app/src/play/java/chat/rocket/reactnative/Callback.java @@ -1,6 +1,7 @@ package chat.rocket.reactnative; import android.os.Bundle; + import androidx.annotation.Nullable; public class Callback { diff --git a/android/app/src/play/java/chat/rocket/reactnative/CustomPushNotification.java b/android/app/src/play/java/chat/rocket/reactnative/CustomPushNotification.java index ebc3fb7c..d9b8ebf5 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/CustomPushNotification.java +++ b/android/app/src/play/java/chat/rocket/reactnative/CustomPushNotification.java @@ -14,12 +14,14 @@ import android.graphics.drawable.Icon; import android.os.Build; import android.os.Bundle; import android.app.Person; + import androidx.annotation.Nullable; import com.google.gson.Gson; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; + import java.util.concurrent.ExecutionException; import java.lang.InterruptedException; @@ -84,6 +86,15 @@ public class CustomPushNotification extends PushNotification { boolean hasSender = loadedEjson.sender != null; String title = bundle.getString("title"); + // If it has a encrypted message + if (loadedEjson.msg != null) { + // Override message with the decrypted content + String decrypted = Encryption.shared.decryptMessage(loadedEjson, reactApplicationContext); + if (decrypted != null) { + bundle.putString("message", decrypted); + } + } + bundle.putLong("time", new Date().getTime()); bundle.putString("username", hasSender ? loadedEjson.sender.username : title); bundle.putString("senderId", hasSender ? loadedEjson.sender._id : "1"); @@ -114,12 +125,12 @@ public class CustomPushNotification extends PushNotification { Ejson ejson = new Gson().fromJson(bundle.getString("ejson", "{}"), Ejson.class); notification - .setContentTitle(title) - .setContentText(message) - .setContentIntent(intent) - .setPriority(Notification.PRIORITY_HIGH) - .setDefaults(Notification.DEFAULT_ALL) - .setAutoCancel(true); + .setContentTitle(title) + .setContentText(message) + .setContentIntent(intent) + .setPriority(Notification.PRIORITY_HIGH) + .setDefaults(Notification.DEFAULT_ALL) + .setAutoCancel(true); Integer notificationId = Integer.parseInt(notId); notificationColor(notification); @@ -132,7 +143,7 @@ public class CustomPushNotification extends PushNotification { notificationStyle(notification, notificationId, bundle); notificationReply(notification, notificationId, bundle); - // message couldn't be loaded from server (Fallback notification) + // message couldn't be loaded from server (Fallback notification) } else { Gson gson = new Gson(); // iterate over the current notification ids to dismiss fallback notifications from same server @@ -163,11 +174,11 @@ public class CustomPushNotification extends PushNotification { private Bitmap getAvatar(String uri) { try { return Glide.with(mContext) - .asBitmap() - .apply(RequestOptions.bitmapTransform(new RoundedCorners(10))) - .load(uri) - .submit(100, 100) - .get(); + .asBitmap() + .apply(RequestOptions.bitmapTransform(new RoundedCorners(10))) + .load(uri) + .submit(100, 100) + .get(); } catch (final ExecutionException | InterruptedException e) { return largeIcon(); } @@ -203,8 +214,8 @@ public class CustomPushNotification extends PushNotification { String CHANNEL_NAME = "All"; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, - CHANNEL_NAME, - NotificationManager.IMPORTANCE_DEFAULT); + CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT); final NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.createNotificationChannel(channel); @@ -253,9 +264,9 @@ public class CustomPushNotification extends PushNotification { messageStyle = new Notification.MessagingStyle(""); } else { Person sender = new Person.Builder() - .setKey("") - .setName("") - .build(); + .setKey("") + .setName("") + .build(); messageStyle = new Notification.MessagingStyle(sender); } @@ -279,9 +290,14 @@ public class CustomPushNotification extends PushNotification { } else { Bitmap avatar = getAvatar(avatarUri); + String name = username; + if (ejson.senderName != null) { + name = ejson.senderName; + } + Person.Builder sender = new Person.Builder() - .setKey(senderId) - .setName(username); + .setKey(senderId) + .setName(name); if (avatar != null) { sender.setIcon(Icon.createWithBitmap(avatar)); @@ -317,18 +333,18 @@ public class CustomPushNotification extends PushNotification { PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT); RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY) - .setLabel(label) - .build(); + .setLabel(label) + .build(); CharSequence title = label; Notification.Action replyAction = new Notification.Action.Builder(smallIconResId, title, replyPendingIntent) - .addRemoteInput(remoteInput) - .setAllowGeneratedReplies(true) - .build(); + .addRemoteInput(remoteInput) + .setAllowGeneratedReplies(true) + .build(); notification - .setShowWhen(true) - .addAction(replyAction); + .setShowWhen(true) + .addAction(replyAction); } private void notificationDismiss(Notification.Builder notification, int notificationId) { diff --git a/android/app/src/play/java/chat/rocket/reactnative/DismissNotification.java b/android/app/src/play/java/chat/rocket/reactnative/DismissNotification.java index 32524a35..b43cf295 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/DismissNotification.java +++ b/android/app/src/play/java/chat/rocket/reactnative/DismissNotification.java @@ -5,9 +5,9 @@ import android.content.Context; import android.content.Intent; public class DismissNotification extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - int notId = intent.getExtras().getInt(CustomPushNotification.NOTIFICATION_ID); - CustomPushNotification.clearMessages(notId); - } + @Override + public void onReceive(Context context, Intent intent) { + int notId = intent.getExtras().getInt(CustomPushNotification.NOTIFICATION_ID); + CustomPushNotification.clearMessages(notId); + } } diff --git a/android/app/src/play/java/chat/rocket/reactnative/Ejson.java b/android/app/src/play/java/chat/rocket/reactnative/Ejson.java index 499ddf6c..0ce46634 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/Ejson.java +++ b/android/app/src/play/java/chat/rocket/reactnative/Ejson.java @@ -31,6 +31,8 @@ public class Ejson { Sender sender; String messageId; String notificationType; + String senderName; + String msg; private MMKV mmkv; @@ -82,6 +84,14 @@ public class Ejson { return ""; } + public String privateKey() { + String serverURL = serverURL(); + if (mmkv != null && serverURL != null) { + return mmkv.decodeString(serverURL.concat("-RC_E2E_PRIVATE_KEY")); + } + return null; + } + public String serverURL() { String url = this.host; if (url != null && url.endsWith("/")) { diff --git a/android/app/src/play/java/chat/rocket/reactnative/Encryption.java b/android/app/src/play/java/chat/rocket/reactnative/Encryption.java new file mode 100644 index 00000000..83c2559c --- /dev/null +++ b/android/app/src/play/java/chat/rocket/reactnative/Encryption.java @@ -0,0 +1,210 @@ +package chat.rocket.reactnative; + +import android.util.Log; +import android.util.Base64; +import android.database.Cursor; + +import com.pedrouid.crypto.RSA; +import com.pedrouid.crypto.RCTAes; +import com.pedrouid.crypto.RCTRsaUtils; +import com.pedrouid.crypto.Util; + +import com.google.gson.Gson; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; + +import com.nozbe.watermelondb.Database; + +import java.util.Arrays; +import java.security.SecureRandom; + +class Message { + String _id; + String userId; + String text; + + Message(String id, String userId, String text) { + this._id = id; + this.userId = userId; + this.text = text; + } +} + +class PrivateKey { + String d; + String dp; + String dq; + String e; + String n; + String p; + String q; + String qi; +} + +class RoomKey { + String k; +} + +class Room { + String e2eKey; + Boolean encrypted; + + Room(String e2eKey, Boolean encrypted) { + this.e2eKey = e2eKey; + this.encrypted = encrypted; + } +} + +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) { + Database database = new Database(ejson.serverURL().replace("https://", "") + "-experimental.db", reactContext); + 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); + } + + public String readUserKey(final Ejson ejson) throws Exception { + String privateKey = ejson.privateKey(); + if (privateKey == null) { + return null; + } + + PrivateKey privKey = gson.fromJson(privateKey, PrivateKey.class); + + WritableMap jwk = Arguments.createMap(); + jwk.putString("n", privKey.n); + jwk.putString("e", privKey.e); + jwk.putString("d", privKey.d); + jwk.putString("p", privKey.p); + jwk.putString("q", privKey.q); + jwk.putString("dp", privKey.dp); + jwk.putString("dq", privKey.dq); + jwk.putString("qi", privKey.qi); + + return new RCTRsaUtils().jwkToPrivatePkcs1(jwk); + } + + public String decryptRoomKey(final String e2eKey, final Ejson ejson) throws Exception { + String key = e2eKey.substring(12, e2eKey.length()); + keyId = e2eKey.substring(0, 12); + + String userKey = readUserKey(ejson); + if (userKey == null) { + return null; + } + + RSA rsa = new RSA(); + rsa.setPrivateKey(userKey); + String decrypted = rsa.decrypt(key); + + RoomKey roomKey = gson.fromJson(decrypted, RoomKey.class); + byte[] decoded = Base64.decode(roomKey.k, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE); + + return Util.bytesToHex(decoded); + } + + public String decryptMessage(final Ejson ejson, final ReactApplicationContext reactContext) { + try { + this.reactContext = reactContext; + + Room room = readRoom(ejson); + if (room == null || room.e2eKey == null) { + return null; + } + String e2eKey = decryptRoomKey(room.e2eKey, ejson); + if (e2eKey == null) { + 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); + + return m.text; + } catch (Exception e) { + Log.d("[ROCKETCHAT][ENCRYPTION]", Log.getStackTraceString(e)); + } + + return null; + } + + public String encryptMessage(final String message, final String id, final Ejson ejson) { + try { + Room room = readRoom(ejson); + if (room == null || !room.encrypted || room.e2eKey == null) { + return message; + } + + String e2eKey = decryptRoomKey(room.e2eKey, ejson); + if (e2eKey == null) { + return message; + } + + Message m = new Message(id, ejson.userId(), message); + String cypher = gson.toJson(m); + + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[16]; + random.nextBytes(bytes); + + String encrypted = RCTAes.encrypt(Base64.encodeToString(cypher.getBytes("UTF-8"), Base64.NO_WRAP), e2eKey, Util.bytesToHex(bytes)); + byte[] data = Base64.decode(encrypted, Base64.NO_WRAP); + + return keyId + Base64.encodeToString(concat(bytes, data), Base64.NO_WRAP); + } catch (Exception e) { + Log.d("[ROCKETCHAT][ENCRYPTION]", 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; + } + + // 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; + } + + return result; + } +} diff --git a/android/app/src/play/java/chat/rocket/reactnative/LoadNotification.java b/android/app/src/play/java/chat/rocket/reactnative/LoadNotification.java index 83ebfe8b..20d1313d 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/LoadNotification.java +++ b/android/app/src/play/java/chat/rocket/reactnative/LoadNotification.java @@ -11,89 +11,90 @@ import okhttp3.Response; import okhttp3.Interceptor; import com.google.gson.Gson; + import java.io.IOException; import com.facebook.react.bridge.ReactApplicationContext; class JsonResponse { - Data data; + Data data; - class Data { - Notification notification; + class Data { + Notification notification; - class Notification { - String notId; - String title; - String text; - Payload payload; + class Notification { + String notId; + String title; + String text; + Payload payload; - class Payload { - String host; - String rid; - String type; - Sender sender; - String messageId; - String notificationType; - String name; - String messageType; + class Payload { + String host; + String rid; + String type; + Sender sender; + String messageId; + String notificationType; + String name; + String messageType; - class Sender { - String _id; - String username; - String name; + class Sender { + String _id; + String username; + String name; + } + } } - } } - } } public class LoadNotification { - private static int RETRY_COUNT = 0; - private static int[] TIMEOUT = new int[]{ 0, 1, 3, 5, 10 }; - private static String TOKEN_KEY = "reactnativemeteor_usertoken-"; + private static int RETRY_COUNT = 0; + private static int[] TIMEOUT = new int[]{0, 1, 3, 5, 10}; + private static String TOKEN_KEY = "reactnativemeteor_usertoken-"; - public static void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) { - final OkHttpClient client = new OkHttpClient(); - HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder(); + public static void load(ReactApplicationContext reactApplicationContext, final Ejson ejson, Callback callback) { + final OkHttpClient client = new OkHttpClient(); + HttpUrl.Builder url = HttpUrl.parse(ejson.serverURL().concat("/api/v1/push.get")).newBuilder(); - Request request = new Request.Builder() - .header("x-user-id", ejson.userId()) - .header("x-auth-token", ejson.token()) - .url(url.addQueryParameter("id", ejson.messageId).build()) - .build(); + Request request = new Request.Builder() + .header("x-user-id", ejson.userId()) + .header("x-auth-token", ejson.token()) + .url(url.addQueryParameter("id", ejson.messageId).build()) + .build(); - runRequest(client, request, callback); - } - - private static void runRequest(OkHttpClient client, Request request, Callback callback) { - try { - Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000); - - Response response = client.newCall(request).execute(); - String body = response.body().string(); - if (!response.isSuccessful()) { - throw new Exception("Error"); - } - - Gson gson = new Gson(); - JsonResponse json = gson.fromJson(body, JsonResponse.class); - - Bundle bundle = new Bundle(); - bundle.putString("notId", json.data.notification.notId); - bundle.putString("title", json.data.notification.title); - bundle.putString("message", json.data.notification.text); - bundle.putString("ejson", gson.toJson(json.data.notification.payload)); - bundle.putBoolean("notificationLoaded", true); - - callback.call(bundle); - - } catch (Exception e) { - if (RETRY_COUNT <= TIMEOUT.length) { - RETRY_COUNT++; runRequest(client, request, callback); - } else { - callback.call(null); - } } - } + + private static void runRequest(OkHttpClient client, Request request, Callback callback) { + try { + Thread.sleep(TIMEOUT[RETRY_COUNT] * 1000); + + Response response = client.newCall(request).execute(); + String body = response.body().string(); + if (!response.isSuccessful()) { + throw new Exception("Error"); + } + + Gson gson = new Gson(); + JsonResponse json = gson.fromJson(body, JsonResponse.class); + + Bundle bundle = new Bundle(); + bundle.putString("notId", json.data.notification.notId); + bundle.putString("title", json.data.notification.title); + bundle.putString("message", json.data.notification.text); + bundle.putString("ejson", gson.toJson(json.data.notification.payload)); + bundle.putBoolean("notificationLoaded", true); + + callback.call(bundle); + + } catch (Exception e) { + if (RETRY_COUNT <= TIMEOUT.length) { + RETRY_COUNT++; + runRequest(client, request, callback); + } else { + callback.call(null); + } + } + } } diff --git a/android/app/src/play/java/chat/rocket/reactnative/ReplyBroadcast.java b/android/app/src/play/java/chat/rocket/reactnative/ReplyBroadcast.java index 7f266b06..b3e5c43e 100644 --- a/android/app/src/play/java/chat/rocket/reactnative/ReplyBroadcast.java +++ b/android/app/src/play/java/chat/rocket/reactnative/ReplyBroadcast.java @@ -11,10 +11,12 @@ import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.util.Log; + import java.io.IOException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; + import java.util.HashMap; import java.util.Map; @@ -64,7 +66,7 @@ public class ReplyBroadcast extends BroadcastReceiver { final OkHttpClient client = new OkHttpClient(); final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - String json = buildMessage(rid, message.toString()); + String json = buildMessage(rid, message.toString(), ejson); CustomPushNotification.clearMessages(notId); @@ -101,25 +103,32 @@ public class ReplyBroadcast extends BroadcastReceiver { int count = 17; StringBuilder builder = new StringBuilder(); while (count-- != 0) { - int character = (int)(Math.random()*ALPHA_NUMERIC_STRING.length()); + int character = (int) (Math.random() * ALPHA_NUMERIC_STRING.length()); builder.append(ALPHA_NUMERIC_STRING.charAt(character)); } return builder.toString(); } - protected String buildMessage(String rid, String message) { + protected String buildMessage(String rid, String message, Ejson ejson) { Gson gsonBuilder = new GsonBuilder().create(); + String id = getMessageId(); + + String msg = Encryption.shared.encryptMessage(message, id, ejson); + Map msgMap = new HashMap(); - msgMap.put("_id", getMessageId()); + msgMap.put("_id", id); msgMap.put("rid", rid); - msgMap.put("msg", message); + msgMap.put("msg", msg); + if (msg != message) { + msgMap.put("t", "e2e"); + } msgMap.put("tmid", null); - Map msg = new HashMap(); - msg.put("message", msgMap); + Map m = new HashMap(); + m.put("message", msgMap); - String json = gsonBuilder.toJson(msg); + String json = gsonBuilder.toJson(m); return json; } diff --git a/package.json b/package.json index 30825c43..b007a6dd 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@codler/react-native-keyboard-aware-scroll-view": "^1.0.1", - "@nozbe/watermelondb": "^0.19.0", + "@nozbe/watermelondb": "0.19.0", "@react-native-community/art": "^1.2.0", "@react-native-community/async-storage": "1.11.0", "@react-native-community/cameraroll": "4.0.0", @@ -88,7 +88,7 @@ "react-native-keycommands": "2.0.3", "react-native-localize": "1.4.0", "react-native-mime-types": "2.3.0", - "react-native-mmkv-storage": "^0.3.5", + "react-native-mmkv-storage": "0.3.5", "react-native-modal": "11.5.6", "react-native-navigation-bar-color": "2.0.1", "react-native-notifications": "2.1.7", diff --git a/patches/@nozbe+watermelondb+0.19.0.patch b/patches/@nozbe+watermelondb+0.19.0.patch index b30d2730..dfad30c5 100644 --- a/patches/@nozbe+watermelondb+0.19.0.patch +++ b/patches/@nozbe+watermelondb+0.19.0.patch @@ -1,3 +1,25 @@ +diff --git a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt +index 2217222..5b2eb73 100644 +--- a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt ++++ b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt +@@ -5,7 +5,7 @@ import android.database.Cursor + import android.database.sqlite.SQLiteDatabase + import java.io.File + +-class Database(private val name: String, private val context: Context) { ++public class Database(private val name: String, private val context: Context) { + + private val db: SQLiteDatabase by lazy { + SQLiteDatabase.openOrCreateDatabase( +@@ -41,7 +41,7 @@ class Database(private val name: String, private val context: Context) { + + fun delete(query: SQL, args: QueryArgs) = db.execSQL(query, args) + +- fun rawQuery(query: SQL, args: RawQueryArgs = emptyArray()): Cursor = db.rawQuery(query, args) ++ public fun rawQuery(query: SQL, args: RawQueryArgs = emptyArray()): Cursor = db.rawQuery(query, args) + + fun count(query: SQL, args: RawQueryArgs = emptyArray()): Int = + rawQuery(query, args).use { diff --git a/node_modules/@nozbe/watermelondb/decorators/date/index.js b/node_modules/@nozbe/watermelondb/decorators/date/index.js index 65690af..ce71aa0 100644 --- a/node_modules/@nozbe/watermelondb/decorators/date/index.js diff --git a/yarn.lock b/yarn.lock index 70666f6a..8760093c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1922,7 +1922,7 @@ resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.31.1.tgz#ffd394ad7c188c6b73f89fd6e1ccb849a1b96dba" integrity sha512-z5+GdcHZl9OQ1g0pnygORAnwCYUlYw/gQxdW/8rS0HxD2Gnn/k3DBQOvqQIH4Z3Z3KWVMbGUYhcH1v4SqTAdwg== -"@nozbe/watermelondb@^0.19.0": +"@nozbe/watermelondb@0.19.0": version "0.19.0" resolved "https://registry.yarnpkg.com/@nozbe/watermelondb/-/watermelondb-0.19.0.tgz#70dae4bfca9dde8d61819a6092846e178a50e2f7" integrity sha512-x7sclLu/4RDmLzANIYQioKjWdmoIxgqYw7OJnS7UtWEtJGn28l5Z69SWojll/RM0X2KOEem0BIcjqM46CA53GA== @@ -12848,7 +12848,7 @@ react-native-mime-types@2.3.0: dependencies: mime-db "~1.37.0" -react-native-mmkv-storage@^0.3.5: +react-native-mmkv-storage@0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/react-native-mmkv-storage/-/react-native-mmkv-storage-0.3.5.tgz#9c2f064c0efdaf960e9646c68dc6ae7f11640fa5" integrity sha512-xp0E55Qdi81k8CTeq3PUrXGwT2tMmfNfmYvZ7Emq9qWpvg3ko1/M6B1kXEXOgEou/hgqB503TGcsR/mpN5HSMA== @@ -12958,8 +12958,8 @@ react-native-scrollable-tab-view@^1.0.0: react-timer-mixin "^0.13.3" react-native-simple-crypto@RocketChat/react-native-simple-crypto: - version "0.3.1" - resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/b3deaf18e83cd4acc418fe65adb2d837164c2a20" + version "0.4.0" + resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/21d36ccc8c75771428239f9dcca19afe0c95e95f" dependencies: base64-js "^1.3.0" hex-lite "^1.5.0"