diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 6829f69ab..b022ddb77 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -253,6 +253,7 @@ B88A1AC725C90A4700E6D421 /* TypingIndicatorInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B6A904218B4C90007C4606 /* TypingIndicatorInteraction.swift */; }; B88FA7B826045D100049422F /* OpenGroupAPIV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */; }; B88FA7F2260C3EB10049422F /* OpenGroupSuggestionGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7F1260C3EB10049422F /* OpenGroupSuggestionGrid.swift */; }; + B88FA7FB26114EA70049422F /* Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88FA7FA26114EA70049422F /* Hex.swift */; }; B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; }; B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; }; B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */; }; @@ -1243,6 +1244,7 @@ B886B4A82398BA1500211ABE /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = ""; }; B88FA7B726045D100049422F /* OpenGroupAPIV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPIV2.swift; sourceTree = ""; }; B88FA7F1260C3EB10049422F /* OpenGroupSuggestionGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionGrid.swift; sourceTree = ""; }; + B88FA7FA26114EA70049422F /* Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hex.swift; sourceTree = ""; }; B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeWrapperVC.swift; sourceTree = ""; }; B894D0742339EDCF00B4D94D /* NukeDataModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeDataModal.swift; sourceTree = ""; }; B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToBottomButton.swift; sourceTree = ""; }; @@ -2266,6 +2268,7 @@ C3C2ABD12553C6C900C340D1 /* Data+SecureRandom.swift */, C3A71D662558A0170043A11F /* DiffieHellman.swift */, C33FDA73255A57FA00E217F9 /* ECKeyPair+Hexadecimal.swift */, + B88FA7FA26114EA70049422F /* Hex.swift */, C3A71F882558BA9F0043A11F /* Mnemonic.swift */, ); path = Crypto; @@ -4732,6 +4735,7 @@ C3D9E41F25676C870040E4F3 /* OWSPrimaryStorageProtocol.swift in Sources */, C3BBE0A72554D4DE0050F1E3 /* Promise+Retrying.swift in Sources */, B8856D7B256F14F4001CE70E /* UIView+OWS.m in Sources */, + B88FA7FB26114EA70049422F /* Hex.swift in Sources */, C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */, C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */, C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */, diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index f2e29f2be..b8a340ccb 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -124,15 +124,55 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView joinOpenGroup(with: string) } - fileprivate func joinOpenGroup(with url: String) { - - // TODO: V1 open groups - + fileprivate func joinOpenGroup(with string: String) { + // A V1 open group URL will look like: https:// + + // A V2 open group URL will look like: + + + + + // The host doesn't parse if no explicit scheme is provided + if let url = URL(string: string), let host = url.host ?? given(string.split(separator: "/").first, { String($0) }) { + if let query = url.query { + // Inputs that should work: + // https://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c + // http://sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c + // sessionopengroup.co/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c (does NOT go to HTTPS) + // https://143.198.213.225:443/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c + // 143.198.213.255:80/main?public_key=658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231c + let useTLS = (url.scheme == "https") + let room = String(url.path.dropFirst()) // Drop the leading slash + let queryParts = query.split(separator: "=") + guard !room.isEmpty && !room.contains("/"), queryParts.count == 2, queryParts[0] == "public_key" else { + let title = NSLocalizedString("invalid_url", comment: "") + let message = "Please check the URL you entered and try again." + return showError(title: title, message: message) + } + let publicKey = String(queryParts[1]) + guard publicKey.count == 64 && Hex.isValid(publicKey) else { + let title = NSLocalizedString("invalid_url", comment: "") + let message = "Please check the URL you entered and try again." + return showError(title: title, message: message) + } + var server = (useTLS ? "https://" : "http://") + host + if let port = url.port { server += ":\(port)" } + joinV2OpenGroup(room: room, server: server, publicKey: publicKey) + } else { + // Inputs that should work: + // loki.opensession.id + // https://loki.opensession.id + // http://loki.opensession.id (still goes to HTTPS) + joinV1OpenGroup("https://" + host) + } + } else { + let title = NSLocalizedString("invalid_url", comment: "") + let message = "Please check the URL you entered and try again." + showError(title: title, message: message) + } + } + + private func joinV1OpenGroup(_ string: String) { guard !isJoining else { return } isJoining = true ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in Storage.shared.write { transaction in - OpenGroupManagerV2.shared.add(room: "main", server: "https://sessionopengroup.com", publicKey: "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b", using: transaction) + OpenGroupManager.shared.add(with: string, using: transaction) .done(on: DispatchQueue.main) { [weak self] _ in self?.presentingViewController!.dismiss(animated: true, completion: nil) let appDelegate = UIApplication.shared.delegate as! AppDelegate @@ -140,8 +180,12 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView } .catch(on: DispatchQueue.main) { [weak self] error in self?.dismiss(animated: true, completion: nil) // Dismiss the loader - let title = "Couldn't Join" - let message = error.localizedDescription + var title = "Couldn't Join" + var message = "" + if case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, _) = error, statusCode == 401 || statusCode == 403 { + title = "Unauthorized" + message = "Please ask the open group operator to add you to the group." + } self?.isJoining = false self?.showError(title: title, message: message) } @@ -149,7 +193,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView } } - fileprivate func join(_ room: String, on server: String, with publicKey: String) { + fileprivate func joinV2OpenGroup(room: String, server: String, publicKey: String) { guard !isJoining else { return } isJoining = true ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in @@ -187,6 +231,7 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate, let result = TextField(placeholder: NSLocalizedString("vc_enter_chat_url_text_field_hint", comment: "")) result.keyboardType = .URL result.autocapitalizationType = .none + result.autocorrectionType = .no return result }() @@ -251,7 +296,7 @@ private final class EnterURLVC : UIViewController, UIGestureRecognizerDelegate, } func join(_ room: OpenGroupAPIV2.Info) { - joinOpenGroupVC.join(room.id, on: OpenGroupAPIV2.defaultServer, with: OpenGroupAPIV2.defaultServerPublicKey) + joinOpenGroupVC.joinV2OpenGroup(room: room.id, server: OpenGroupAPIV2.defaultServer, publicKey: OpenGroupAPIV2.defaultServerPublicKey) } @objc private func joinOpenGroup() { diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift index 580593fa6..9f544a5b1 100644 --- a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift +++ b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift @@ -2,7 +2,6 @@ import PromiseKit import SessionSnodeKit // TODO: Show images w/ room suggestions -// TODO: Distinguish between V1 and V2 open groups in the join open group screen @objc(SNOpenGroupAPIV2) public final class OpenGroupAPIV2 : NSObject { diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupManagerV2.swift b/SessionMessagingKit/Open Groups/V2/OpenGroupManagerV2.swift index e637ad115..e491a4ae1 100644 --- a/SessionMessagingKit/Open Groups/V2/OpenGroupManagerV2.swift +++ b/SessionMessagingKit/Open Groups/V2/OpenGroupManagerV2.swift @@ -34,10 +34,10 @@ public final class OpenGroupManagerV2 : NSObject { storage.removeLastMessageServerID(for: room, on: server, using: transaction) storage.removeLastDeletionServerID(for: room, on: server, using: transaction) storage.removeAuthToken(for: room, on: server, using: transaction) + storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction) let (promise, seal) = Promise.pending() let transaction = transaction as! YapDatabaseReadWriteTransaction transaction.addCompletionQueue(DispatchQueue.global(qos: .default)) { - storage.setOpenGroupPublicKey(for: server, to: publicKey, using: transaction) OpenGroupAPIV2.getInfo(for: room, on: server).done(on: DispatchQueue.global(qos: .default)) { info in let openGroup = OpenGroupV2(server: server, room: room, name: info.name, imageID: info.imageID) let groupID = LKGroupUtilities.getEncodedOpenGroupIDAsData(openGroup.id) diff --git a/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift b/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift index 2adc83430..585d1965f 100644 --- a/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift +++ b/SessionUtilitiesKit/Crypto/ECKeyPair+Hexadecimal.swift @@ -13,8 +13,7 @@ public extension ECKeyPair { @objc static func isValidHexEncodedPublicKey(candidate: String) -> Bool { // Check that it's a valid hexadecimal encoding - let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") - guard candidate.uppercased().unicodeScalars.allSatisfy({ allowedCharacters.contains($0) }) else { return false } + guard Hex.isValid(candidate) else { return false } // Check that it has length 66 and a leading "05" guard candidate.count == 66 && candidate.hasPrefix("05") else { return false } // It appears to be a valid public key diff --git a/SessionUtilitiesKit/Crypto/Hex.swift b/SessionUtilitiesKit/Crypto/Hex.swift new file mode 100644 index 000000000..b90a916fa --- /dev/null +++ b/SessionUtilitiesKit/Crypto/Hex.swift @@ -0,0 +1,8 @@ + +public enum Hex { + + public static func isValid(_ string: String) -> Bool { + let allowedCharacters = CharacterSet(charactersIn: "0123456789ABCDEF") + return string.uppercased().unicodeScalars.allSatisfy { allowedCharacters.contains($0) } + } +}