diff --git a/Session/Utilities/Storage+Resetting.swift b/Session/Utilities/Storage+Resetting.swift index ed132c393..0cd749f28 100644 --- a/Session/Utilities/Storage+Resetting.swift +++ b/Session/Utilities/Storage+Resetting.swift @@ -1,23 +1,43 @@ +import PromiseKit extension Storage { - static func reset() { + static func prepareForV2KeyPairMigration() { let userDefaults = UserDefaults.standard - if userDefaults[.isUsingFullAPNs], let hexEncodedToken = userDefaults[.deviceToken] { + let isUsingAPNs = userDefaults[.isUsingFullAPNs] + if isUsingAPNs, let hexEncodedToken = userDefaults[.deviceToken] { let token = Data(hex: hexEncodedToken) PushNotificationAPI.unregister(token).retainUntilComplete() // TODO: Wait for this to complete? } - + let displayName = OWSProfileManager.shared().localProfileName() let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.stopPoller() appDelegate.stopClosedGroupPoller() appDelegate.stopOpenGroupPollers() - OWSStorage.resetAllStorage() OWSUserProfile.resetProfileStorage() Environment.shared.preferences.clear() AppEnvironment.shared.notificationPresenter.clearAllNotifications() - + userDefaults[.isUsingFullAPNs] = isUsingAPNs + userDefaults[.displayName] = displayName + userDefaults[.isMigratingToV2KeyPair] = true exit(0) } + + static func finishV2KeyPairMigration(navigationController: UINavigationController) { + let seed = Data.getSecureRandomData(ofSize: 16)! + let (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed) + KeyPairUtilities.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) + TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519KeyPair.hexEncodedPublicKey + OWSPrimaryStorage.shared().setRestorationTime(0) + UserDefaults.standard[.hasViewedSeed] = false + let displayName = UserDefaults.standard[.displayName]! // Checked earlier + OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: nil, success: { }, failure: { _ in }, requiresSync: false) + TSAccountManager.sharedInstance().didRegister() + let homeVC = HomeVC() + navigationController.setViewControllers([ homeVC ], animated: true) + let syncTokensJob = SyncPushTokensJob(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences) + syncTokensJob.uploadOnlyIfStale = false + let _: Promise = syncTokensJob.run() + } } diff --git a/Session/View Controllers/HomeVC.swift b/Session/View Controllers/HomeVC.swift index 8664d5f16..aa5982eec 100644 --- a/Session/View Controllers/HomeVC.swift +++ b/Session/View Controllers/HomeVC.swift @@ -162,6 +162,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol isViewVisible = true UserDefaults.standard[.hasLaunchedOnce] = true showKeyPairMigrationNudgeIfNeeded() + showKeyPairMigrationSuccessModalIfNeeded() } private func showKeyPairMigrationNudgeIfNeeded() { @@ -177,6 +178,16 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol UserDefaults.standard[.lastKeyPairMigrationNudge] = Date() } + private func showKeyPairMigrationSuccessModalIfNeeded() { + let userDefaults = UserDefaults.standard + guard KeyPairUtilities.hasV2KeyPair() && userDefaults[.isMigratingToV2KeyPair] else { return } + let sheet = KeyPairMigrationSuccessSheet() + sheet.modalPresentationStyle = .overFullScreen + sheet.modalTransitionStyle = .crossDissolve + present(sheet, animated: true, completion: nil) + UserDefaults.standard[.isMigratingToV2KeyPair] = false + } + override func viewWillDisappear(_ animated: Bool) { isViewVisible = false super.viewWillDisappear(animated) diff --git a/Session/View Controllers/KeyPairMigrationSheet.swift b/Session/View Controllers/KeyPairMigrationSheet.swift index c5ebecaae..9c4c45c01 100644 --- a/Session/View Controllers/KeyPairMigrationSheet.swift +++ b/Session/View Controllers/KeyPairMigrationSheet.swift @@ -34,12 +34,12 @@ final class KeyPairMigrationSheet : Sheet { // Upgrade now button let upgradeNowButton = Button(style: .prominentOutline, size: .large) upgradeNowButton.set(.width, to: 240) - upgradeNowButton.setTitle(NSLocalizedString("Upgrade Now", comment: ""), for: UIControl.State.normal) + upgradeNowButton.setTitle("Upgrade Now", for: UIControl.State.normal) upgradeNowButton.addTarget(self, action: #selector(upgradeNow), for: UIControl.Event.touchUpInside) // Upgrade later button let upgradeLaterButton = Button(style: .prominentOutline, size: .large) upgradeLaterButton.set(.width, to: 240) - upgradeLaterButton.setTitle(NSLocalizedString("Upgrade Later", comment: ""), for: UIControl.State.normal) + upgradeLaterButton.setTitle("Upgrade Later", for: UIControl.State.normal) upgradeLaterButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) // Button stack view let buttonStackView = UIStackView(arrangedSubviews: [ upgradeNowButton, upgradeLaterButton ]) @@ -64,7 +64,7 @@ final class KeyPairMigrationSheet : Sheet { let message = "You’re upgrading to a new Session ID. This will give you improved privacy and security, but it will clear ALL app data. Contacts and conversations will be lost. Proceed?" let alert = UIAlertController(title: "Upgrade Session ID?", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in - Storage.reset() + Storage.prepareForV2KeyPairMigration() }) alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil)) presentingVC.dismiss(animated: true) { // Dismiss self first diff --git a/Session/View Controllers/KeyPairMigrationSuccessSheet.swift b/Session/View Controllers/KeyPairMigrationSuccessSheet.swift new file mode 100644 index 000000000..842eef20d --- /dev/null +++ b/Session/View Controllers/KeyPairMigrationSuccessSheet.swift @@ -0,0 +1,94 @@ + +final class KeyPairMigrationSuccessSheet : Sheet { + + private lazy var sessionIDLabel: UILabel = { + let result = UILabel() + result.textColor = Colors.text + result.font = Fonts.spaceMono(ofSize: isIPhone5OrSmaller ? Values.mediumFontSize : 20) + result.numberOfLines = 0 + result.lineBreakMode = .byCharWrapping + return result + }() + + private lazy var copyButton: Button = { + let result = Button(style: .prominentOutline, size: .large) + result.set(.width, to: 240) + result.setTitle(NSLocalizedString("copy", comment: ""), for: UIControl.State.normal) + result.addTarget(self, action: #selector(copySessionID), for: UIControl.Event.touchUpInside) + return result + }() + + override func populateContentView() { + // Image view + let imageView = UIImageView(image: #imageLiteral(resourceName: "Shield").withTint(Colors.text)) + imageView.set(.width, to: 64) + imageView.set(.height, to: 64) + imageView.contentMode = .scaleAspectFit + // Title label + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) + titleLabel.text = "Upgrade Successful!" + titleLabel.numberOfLines = 0 + titleLabel.lineBreakMode = .byWordWrapping + // Top stack view + let topStackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ]) + topStackView.axis = .vertical + topStackView.spacing = Values.largeSpacing + topStackView.alignment = .center + // Explanation label + let explanationLabel = UILabel() + explanationLabel.textColor = Colors.text + explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) + explanationLabel.textAlignment = .center + explanationLabel.text = "Your new and improved Session ID is:" + explanationLabel.numberOfLines = 0 + explanationLabel.lineBreakMode = .byWordWrapping + // Session ID label + sessionIDLabel.text = getUserHexEncodedPublicKey() + // Session ID container + let sessionIDContainer = UIView() + sessionIDContainer.addSubview(sessionIDLabel) + sessionIDLabel.pin(to: sessionIDContainer, withInset: Values.mediumSpacing) + sessionIDContainer.layer.cornerRadius = Values.textFieldCornerRadius + sessionIDContainer.layer.borderWidth = Values.borderThickness + sessionIDContainer.layer.borderColor = Colors.text.cgColor + // OK button + let okButton = Button(style: .prominentOutline, size: .large) + okButton.set(.width, to: 240) + okButton.setTitle("OK", for: UIControl.State.normal) + okButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Button stack view + let buttonStackView = UIStackView(arrangedSubviews: [ copyButton, okButton ]) + buttonStackView.axis = .vertical + buttonStackView.spacing = Values.mediumSpacing + buttonStackView.alignment = .center + // Main stack view + let stackView = UIStackView(arrangedSubviews: [ topStackView, explanationLabel, sessionIDContainer, buttonStackView ]) + stackView.axis = .vertical + stackView.spacing = Values.veryLargeSpacing + stackView.alignment = .center + // Constraints + contentView.addSubview(stackView) + stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.veryLargeSpacing) + stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.veryLargeSpacing) + contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.veryLargeSpacing + overshoot) + } + + @objc private func copySessionID() { + UIPasteboard.general.string = getUserHexEncodedPublicKey() + copyButton.isUserInteractionEnabled = false + UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: { + self.copyButton.setTitle("Copied", for: UIControl.State.normal) + }, completion: nil) + Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(enableCopyButton), userInfo: nil, repeats: false) + } + + @objc private func enableCopyButton() { + copyButton.isUserInteractionEnabled = true + UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: { + self.copyButton.setTitle(NSLocalizedString("copy", comment: ""), for: UIControl.State.normal) + }, completion: nil) + } +} diff --git a/Session/View Controllers/LandingVC.swift b/Session/View Controllers/LandingVC.swift index 5bb68763d..0639d5c12 100644 --- a/Session/View Controllers/LandingVC.swift +++ b/Session/View Controllers/LandingVC.swift @@ -70,6 +70,13 @@ final class LandingVC : BaseVC { view.addSubview(mainStackView) mainStackView.pin(to: view) topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor, multiplier: 1).isActive = true + // Auto-migrate if needed + let userDefaults = UserDefaults.standard + if userDefaults[.isMigratingToV2KeyPair] { + if userDefaults[.displayName] != nil { + Storage.finishV2KeyPairMigration(navigationController: navigationController!) + } + } } override func viewDidDisappear(_ animated: Bool) { diff --git a/Session/View Controllers/SettingsVC.swift b/Session/View Controllers/SettingsVC.swift index cbfe660df..edb577b90 100644 --- a/Session/View Controllers/SettingsVC.swift +++ b/Session/View Controllers/SettingsVC.swift @@ -402,7 +402,7 @@ final class SettingsVC : BaseVC, AvatarViewHelperDelegate { let message = "You’re upgrading to a new Session ID. This will give you improved privacy and security, but it will clear ALL app data. Contacts and conversations will be lost. Proceed?" let alert = UIAlertController(title: "Upgrade Session ID?", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in - Storage.reset() + Storage.prepareForV2KeyPairMigration() }) alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil)) present(alert, animated: true, completion: nil) diff --git a/SessionUtilitiesKit/LKUserDefaults.swift b/SessionUtilitiesKit/LKUserDefaults.swift index 1286814ab..052fa58f8 100644 --- a/SessionUtilitiesKit/LKUserDefaults.swift +++ b/SessionUtilitiesKit/LKUserDefaults.swift @@ -7,6 +7,7 @@ public enum LKUserDefaults { case hasSeenGIFMetadataWarning case hasViewedSeed case isUsingFullAPNs + case isMigratingToV2KeyPair } public enum Date : Swift.String { @@ -24,6 +25,8 @@ public enum LKUserDefaults { public enum String : Swift.String { case deviceToken + /// Just used for migration purposes. + case displayName } } diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 639462ac6..3a7346d4e 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -281,6 +281,7 @@ B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */; }; B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */; }; B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0742339EDCF00B4D94D /* NukeDataModal.swift */; }; + B8A14D702589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */; }; B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */; }; B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BB82A4238F627000BA5194 /* HomeVC.swift */; }; B8BC00C0257D90E30032E807 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BC00BF257D90E30032E807 /* General.swift */; }; @@ -1389,6 +1390,7 @@ B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersVC.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 = ""; }; + B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSuccessSheet.swift; sourceTree = ""; }; B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionCandidateSelectionView.swift; sourceTree = ""; }; B8B5BCEB2394D869003823C9 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; B8BB829F238F322400BA5194 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; @@ -2626,6 +2628,7 @@ B8BB82A4238F627000BA5194 /* HomeVC.swift */, B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */, B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */, + B8A14D6F2589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift */, B82B40872399EB0E00A248E7 /* LandingVC.swift */, C329FEEB24F7277900B1C64C /* LightModeSheet.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, @@ -5514,6 +5517,7 @@ B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* ConversationMediaView.swift in Sources */, 45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */, + B8A14D702589CE9000E70D57 /* KeyPairMigrationSuccessSheet.swift in Sources */, C396DAEF2518408B00FF6DC5 /* ParsingState.swift in Sources */, 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */, 34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,