Signal-iOS/Signal/src/ViewControllers/OWS2FAReminderViewController.swift
2020-01-30 20:29:28 -08:00

158 lines
6.3 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import UIKit
import PromiseKit
@objc
public class OWS2FAReminderViewController: UIViewController, PinEntryViewDelegate {
private var ows2FAManager: OWS2FAManager {
return OWS2FAManager.shared()
}
var pinEntryView: PinEntryView!
@objc
public class func wrappedInNavController() -> OWSNavigationController {
let navController = OWSNavigationController()
navController.pushViewController(OWS2FAReminderViewController(), animated: false)
return navController
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
pinEntryView.makePinTextFieldFirstResponder()
}
override public func loadView() {
assert(ows2FAManager.is2FAEnabled())
self.navigationItem.title = NSLocalizedString("REMINDER_2FA_NAV_TITLE", comment: "Navbar title for when user is periodically prompted to enter their registration lock PIN")
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(didPressCloseButton))
let view = UIView()
self.view = view
view.backgroundColor = Theme.backgroundColor
let pinEntryView = PinEntryView()
self.pinEntryView = pinEntryView
pinEntryView.delegate = self
let instructionsTextHeader = NSLocalizedString("REMINDER_2FA_BODY_HEADER", comment: "Body header for when user is periodically prompted to enter their registration lock PIN")
let instructionsTextBody = NSLocalizedString("REMINDER_2FA_BODY", comment: "Body text for when user is periodically prompted to enter their registration lock PIN")
let attributes = [NSAttributedString.Key.font: pinEntryView.boldLabelFont]
let attributedInstructionsText = NSAttributedString(string: instructionsTextHeader, attributes: attributes) + " " + instructionsTextBody
pinEntryView.attributedInstructionsText = attributedInstructionsText
view.addSubview(pinEntryView)
pinEntryView.autoPinWidthToSuperview(withMargin: 20)
pinEntryView.autoPin(toTopLayoutGuideOf: self, withInset: ScaleFromIPhone5(16))
pinEntryView.autoPin(toBottomLayoutGuideOf: self, withInset: 0)
}
// MARK: PinEntryViewDelegate
public func pinEntryView(_ entryView: PinEntryView, submittedPinCode pinCode: String) {
Logger.info("")
ows2FAManager.verifyPin(pinCode) { success in
if success {
self.didSubmitCorrectPin()
} else {
self.didSubmitWrongPin()
}
}
}
//textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
public func pinEntryView(_ entryView: PinEntryView, pinCodeDidChange pinCode: String) {
// optimistically match, without having to press "done"
ows2FAManager.verifyPin(pinCode) { success in
if success {
self.didSubmitCorrectPin()
// We have a legacy pin that may have been truncated to 16 characters.
} else if self.ows2FAManager.needsLegacyPinMigration(), pinCode.count > kLegacyTruncated2FAv1PinLength {
let truncatedPinCode = pinCode.substring(to: Int(kLegacyTruncated2FAv1PinLength))
self.ows2FAManager.verifyPin(truncatedPinCode) { success in
if success {
self.didSubmitCorrectPin()
}
}
}
}
}
public func pinEntryViewForgotPinLinkTapped(_ entryView: PinEntryView) {
Logger.info("")
let alertBody = NSLocalizedString("REMINDER_2FA_FORGOT_PIN_ALERT_MESSAGE",
comment: "Alert message explaining what happens if you forget your 'two-factor auth pin'")
OWSActionSheets.showActionSheet(title: nil, message: alertBody)
}
// MARK: Helpers
@objc
private func didPressCloseButton(sender: UIButton) {
Logger.info("")
// We'll ask again next time they launch
self.dismiss(animated: true)
}
private func didSubmitCorrectPin() {
Logger.info("noWrongGuesses: \(noWrongGuesses)")
// Migrate to 2FA v2 if they've proved they know their pin
if let pinCode = ows2FAManager.pinCode, RemoteConfig.kbs, ows2FAManager.mode == .V1 {
// enabling 2fa v2 automatically disables v1 on the server
ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { _ in
self.ows2FAManager.enable2FAPromise(with: pinCode)
.ensure {
self.presentingViewController?.dismiss(animated: true)
}.catch { error in
// We don't need to bubble this up to the user, since they
// don't know / care that something is changing in this moment.
// We can try and migrate them again during their next reminder.
owsFailDebug("Unexpected error \(error) while migrating to reg lock v2")
}.retainUntilComplete()
}
} else {
self.dismiss(animated: true)
}
OWS2FAManager.shared().updateRepetitionInterval(withWasSuccessful: noWrongGuesses)
}
var noWrongGuesses = true
private func didSubmitWrongPin() {
noWrongGuesses = false
Logger.info("")
let alertTitle = NSLocalizedString("REMINDER_2FA_WRONG_PIN_ALERT_TITLE",
comment: "Alert title after wrong guess for 'two-factor auth pin' reminder activity")
let alertBody = NSLocalizedString("REMINDER_2FA_WRONG_PIN_ALERT_BODY",
comment: "Alert body after wrong guess for 'two-factor auth pin' reminder activity")
OWSActionSheets.showActionSheet(title: alertTitle, message: alertBody)
self.pinEntryView.clearText()
}
}
extension OWS2FAManager {
func enable2FAPromise(with pin: String) -> Promise<Void> {
return Promise { resolver in
requestEnable2FA(withPin: pin, success: {
resolver.fulfill(())
}, failure: { error in
resolver.reject(error)
})
}
}
}