-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[NEW] E2E Encryption push (iOS) #2463
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
Merged
Merged
Changes from 52 commits
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
11e2bd6
link pods to notification service
djorkaeffalexandre a635329
push encryption poc
djorkaeffalexandre 3d2da35
decrypt room key poc
djorkaeffalexandre a13fc75
read user key from mmkv and cast into a pkcs
djorkaeffalexandre c57f075
push decrypt poc (iOS)
djorkaeffalexandre 91880d4
expose needed watermelon methods
djorkaeffalexandre 6e3b04f
watermelon -> database
djorkaeffalexandre 9bf5ec6
indent & simple-crypto update
djorkaeffalexandre bb056bb
string extensions
djorkaeffalexandre 26396c6
storage
djorkaeffalexandre a8bf330
toBase64 -> toData
djorkaeffalexandre fc0d5f3
remove a forced unwrap
djorkaeffalexandre 97ffcec
remove unused import
djorkaeffalexandre 7c95551
database driver
djorkaeffalexandre ebd398c
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre f87fee6
improvement
djorkaeffalexandre ea23f57
folder structure & watermelon bridge
djorkaeffalexandre 31969c1
more improvement stuff
djorkaeffalexandre 09da5a2
watermelon -> database
djorkaeffalexandre 80ea1dd
reuse database instance
djorkaeffalexandre 9cfd69f
improvement
djorkaeffalexandre 87902d0
database fix: bypass watermelon cache
djorkaeffalexandre 71d5097
some code improvements
djorkaeffalexandre 847a32a
encryption instances
djorkaeffalexandre c2575a3
start api stuff
djorkaeffalexandre ce4dd5d
network layer
djorkaeffalexandre e720cd0
improve notification service
djorkaeffalexandre 25ff9ed
improve folder structure
djorkaeffalexandre 2f7cc95
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre cb6458d
watermelon patch
djorkaeffalexandre ff88451
retry fetch logic
djorkaeffalexandre bff9b7d
rocketchat class
djorkaeffalexandre 1d1cc0c
fix try to decrypt without a roomKey
djorkaeffalexandre 7bb61a7
fallback to original content that is translated
djorkaeffalexandre 4bb0dbd
some fixes to rocketchat logic
djorkaeffalexandre dc924da
merge develop
djorkaeffalexandre 0d9c8c6
remove unnecessary extension
djorkaeffalexandre 4356679
merge develop
djorkaeffalexandre 2dfc471
[CHORE] Improve reply notification code (iOS)
djorkaeffalexandre b39618a
undo sign changes
djorkaeffalexandre 303d7ca
remove mocked value
djorkaeffalexandre e4000c4
import direct from library
djorkaeffalexandre a90081b
send message request
djorkaeffalexandre d07ea84
Merge branch 'chore.reply-notification' into new.e2e-encryption-push
djorkaeffalexandre c3d2b56
reply notification with encrypted message working properly
djorkaeffalexandre a044793
revert apple sign
djorkaeffalexandre 9bb9252
fix api onerror
djorkaeffalexandre e7ad091
trick to display sender name on group notifications
djorkaeffalexandre a058f57
revert data.host change
djorkaeffalexandre bdd7591
fix some multithread issues
djorkaeffalexandre 3b8f8dc
use sendername sent by server
djorkaeffalexandre 79150ff
small improvement
djorkaeffalexandre 9fad837
Merge branch 'develop' into new.e2e-encryption-push
diegolmello 5bc64e0
Bump crypto lib
diegolmello cd878de
Update ios/NotificationService/NotificationService.swift
diegolmello dfe50d5
add experimental string
djorkaeffalexandre 8342256
remove trailing slash
djorkaeffalexandre 8dc40c9
invalidate ci & merge
djorkaeffalexandre 9598092
remove trailing slash on reply
djorkaeffalexandre e111618
merge develop
djorkaeffalexandre a6f620e
Merge branch 'develop' into new.e2e-encryption-push
diegolmello 4a1fac6
Merge branch 'develop' of https://github.com/RocketChat/Rocket.Chat.R…
djorkaeffalexandre 074cd34
Merge branch 'new.e2e-encryption-push' of https://github.com/RocketCh…
djorkaeffalexandre b6116b1
fix decrypt messages
djorkaeffalexandre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,177 +1,55 @@ | ||
| import CoreLocation | ||
| import UserNotifications | ||
|
|
||
| struct PushResponse: Decodable { | ||
| let success: Bool | ||
| let data: Data | ||
|
|
||
| struct Data: Decodable { | ||
| let notification: Notification | ||
|
|
||
| struct Notification: Decodable { | ||
| let notId: Int | ||
| let title: String | ||
| let text: String | ||
| let payload: Payload | ||
|
|
||
| struct Payload: Decodable, Encodable { | ||
| let host: String | ||
| let rid: String? | ||
| let type: String? | ||
| let sender: Sender? | ||
| let messageId: String | ||
| let notificationType: String? | ||
| let name: String? | ||
| let messageType: String? | ||
|
|
||
| struct Sender: Decodable, Encodable { | ||
| let _id: String | ||
| let username: String | ||
| let name: String? | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class NotificationService: UNNotificationServiceExtension { | ||
|
|
||
| var contentHandler: ((UNNotificationContent) -> Void)? | ||
| var bestAttemptContent: UNMutableNotificationContent? | ||
|
|
||
| var retryCount = 0 | ||
| var retryTimeout = [1.0, 3.0, 5.0, 10.0] | ||
|
|
||
| override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { | ||
| self.contentHandler = contentHandler | ||
| bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) | ||
|
|
||
| if let bestAttemptContent = bestAttemptContent { | ||
| let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)! | ||
| guard let data = try? (JSONDecoder().decode(PushResponse.Data.Notification.Payload.self, from: ejson)) else { | ||
| return | ||
| } | ||
|
|
||
| let notificationType = data.notificationType ?? "" | ||
|
|
||
| // If the notification have the content at her payload, show it | ||
| if notificationType != "message-id-only" { | ||
| contentHandler(bestAttemptContent) | ||
| return | ||
| } | ||
|
|
||
| let mmapID = "default" | ||
| let instanceID = "com.MMKV.\(mmapID)" | ||
| let secureStorage = SecureStorage() | ||
| var cryptKey: Data = Data() | ||
| // get mmkv instance password from keychain | ||
| secureStorage.getSecureKey(instanceID.toHex()) { (response) -> () in | ||
| guard let password = response?[1] as? String else { | ||
| // kill the process and show notification as it came from APN | ||
| exit(0) | ||
| } | ||
| cryptKey = password.data(using: .utf8)! | ||
| } | ||
|
|
||
| // Get App Group directory | ||
| let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String | ||
| guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else { | ||
| return | ||
| } | ||
|
|
||
| // Set App Group dir | ||
| MMKV.initialize(rootDir: nil, groupDir: directory.path, logLevel: MMKVLogLevel.none) | ||
| guard let mmkv = MMKV(mmapID: mmapID, cryptKey: cryptKey, mode: MMKVMode.multiProcess) else { | ||
| return | ||
| } | ||
|
|
||
| var server = data.host | ||
| if (server.last == "/") { | ||
| server.removeLast() | ||
| } | ||
| let msgId = data.messageId | ||
|
|
||
| let userId = mmkv.string(forKey: "reactnativemeteor_usertoken-\(server)") ?? "" | ||
| let token = mmkv.string(forKey: "reactnativemeteor_usertoken-\(userId)") ?? "" | ||
|
|
||
| if userId.isEmpty || token.isEmpty { | ||
| contentHandler(bestAttemptContent) | ||
| return | ||
| } | ||
|
|
||
| var urlComponents = URLComponents(string: "\(server)/api/v1/push.get")! | ||
| let queryItems = [URLQueryItem(name: "id", value: msgId)] | ||
| urlComponents.queryItems = queryItems | ||
|
|
||
| var request = URLRequest(url: urlComponents.url!) | ||
| request.httpMethod = "GET" | ||
| request.addValue(userId, forHTTPHeaderField: "x-user-id") | ||
| request.addValue(token, forHTTPHeaderField: "x-auth-token") | ||
|
|
||
| runRequest(request: request, bestAttemptContent: bestAttemptContent, contentHandler: contentHandler) | ||
|
|
||
| var contentHandler: ((UNNotificationContent) -> Void)? | ||
| var bestAttemptContent: UNMutableNotificationContent? | ||
| var rocketchat: RocketChat? | ||
|
|
||
| override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { | ||
| self.contentHandler = contentHandler | ||
| bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) | ||
|
|
||
| if let bestAttemptContent = bestAttemptContent { | ||
| let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)! | ||
| guard let data = try? (JSONDecoder().decode(Payload.self, from: ejson)) else { | ||
| return | ||
| } | ||
|
|
||
| rocketchat = RocketChat.instanceForServer(server: data.host) | ||
|
|
||
| // If the notification have the content at her payload, show it | ||
| if data.notificationType != .messageIdOnly { | ||
| self.processPayload(payload: data) | ||
| return | ||
| } | ||
|
|
||
| // Request the content from server | ||
| rocketchat?.getPushWithId(data.messageId) { notification in | ||
| if let notification = notification { | ||
| self.bestAttemptContent?.title = notification.title | ||
| self.bestAttemptContent?.body = notification.text | ||
| self.processPayload(payload: notification.payload) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func runRequest(request: URLRequest, bestAttemptContent: UNMutableNotificationContent, contentHandler: @escaping (UNNotificationContent) -> Void) { | ||
| let task = URLSession.shared.dataTask(with: request) {(data, response, error) in | ||
|
|
||
| func retryRequest() { | ||
| // if we can try again | ||
| if self.retryCount < self.retryTimeout.count { | ||
| // Try again after X seconds | ||
| DispatchQueue.main.asyncAfter(deadline: .now() + self.retryTimeout[self.retryCount], execute: { | ||
| self.runRequest(request: request, bestAttemptContent: bestAttemptContent, contentHandler: contentHandler) | ||
| self.retryCount += 1 | ||
| }) | ||
| 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 some error happened | ||
| if error != nil { | ||
| retryRequest() | ||
|
|
||
| // Check if the request did successfully | ||
| } else if let response = response as? HTTPURLResponse { | ||
| // if it not was successfully | ||
| if response.statusCode != 200 { | ||
| retryRequest() | ||
|
|
||
| // If the response status is 200 | ||
| } else { | ||
| // Process data | ||
| if let data = data { | ||
| // Parse data of response | ||
| let push = try? (JSONDecoder().decode(PushResponse.self, from: data)) | ||
| if let push = push { | ||
| if push.success { | ||
| bestAttemptContent.title = push.data.notification.title | ||
| bestAttemptContent.body = push.data.notification.text | ||
|
|
||
| let payload = try? (JSONEncoder().encode(push.data.notification.payload)) | ||
| if let payload = payload { | ||
| bestAttemptContent.userInfo["ejson"] = String(data: payload, encoding: .utf8) ?? "{}" | ||
| } | ||
|
|
||
| // Show notification with the content modified | ||
| contentHandler(bestAttemptContent) | ||
| return | ||
| } | ||
| } | ||
| } | ||
| retryRequest() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| task.resume() | ||
| } | ||
|
|
||
| override func serviceExtensionTimeWillExpire() { | ||
| // Called just before the extension will be terminated by the system. | ||
| // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. | ||
| if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { | ||
| contentHandler(bestAttemptContent) | ||
| } | ||
| if let bestAttemptContent = bestAttemptContent { | ||
| contentHandler?(bestAttemptContent) | ||
| } | ||
|
|
||
| } | ||
| } | ||
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.