Как поддерживать синхронизацию между временными OTP-приложениями, такими как Google authenticator, Authy в iOS - PullRequest
0 голосов
/ 17 мая 2018

Я работаю над приложением, которое генерирует топы каждые 30 секунд, используя таймер.Я получаю секретные ключи из отсканированных QR-кодов и добавляю их в один массив моделей, а для каждого секретного ключа я генерирую tOTP и добавляю их в другой массив моделей.С этим массивом модели я заполняю таблицу.

Ячейка таблицы содержит метку для отображения otp и пользовательский круговой обзор прогресса для отслеживания прогресса.Основная проблема заключается в том, что я не могу поддерживать синхронизацию с другими приложениями TOTP, такими как Google Authenticator.Когда я запускаю таймер на каждую секунду, я могу генерировать otps из библиотеки генератора TOTP каждую секунду и обновлять метку с повторной загрузкой таблицы.Но эта функциональность влияет на представление хода выполнения, удаляя ячейки редактирования таблицы просмотра, так как я запускаю таймер каждую секунду для генерации OTP и перезагрузки таблицы.Надеюсь, кто-нибудь поможет ...

Вот мой код ...

class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {

    var tOTPS = [TOTP]()
    var tOTPModel = [TOTP]()

    var secretKeys = [String]()
    var generator: OTPGenerator?
    var timer: Timer?
    var currentTimeInterval = TimeInterval()

    @IBOutlet weak var tOtpTableView: UITableView!
    @IBOutlet weak var btnInfo: UIBarButtonItem!
    @IBOutlet weak var btnAddQRCode: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tOtpTableView.tableFooterView = UIView()
        self.emptyDataString = "Click + to add new account"
        let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
        longPressGesture.minimumPressDuration = 1.0
        longPressGesture.delegate = self
        self.tOtpTableView.addGestureRecognizer(longPressGesture)
        setUpViews()
        getTotps()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
      //  let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
        if self.tOTPS.isEmpty == false {
            self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
            self.timer?.fire()
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getTotps() {
        if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
            self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
        }
    }

    @IBAction func handleAddQRCode(_ sender: UIButton) {
        let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
        controllerToPresent.delegate = self
        controllerToPresent.tOTPS = self.tOTPS
        self.timer?.invalidate()
        DispatchQueue.main.async {
            self.navigationController?.pushViewController(controllerToPresent, animated: true)
        }
    }

    @objc func generateTOTP() {
        self.tOTPModel = []
        for tOtpObject in self.tOTPS {
            self.generator = Generator.generatorWithSecretKey(key: tOtpObject.secretKey)
            let tOtp = (self.generator as! TOTPGenerator).generateOTP()
            self.tOTPModel.append(TOTP(secretKey: tOtp!, issuer: tOtpObject.issuer, scheme: tOtpObject.scheme, createdDate: tOtpObject.createdDate))
        }
        self.tOtpTableView.reloadData()
    }

    func numberOfSections(in tableView: UITableView) -> Int {
         return self.tOTPModel.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
        let tOTP = self.tOTPModel[indexPath.section]
        cell.lblTOTP.text = tOTP.secretKey.separate(every: 3, with: " ")
        cell.lblIssuer.text = tOTP.issuer
        cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"

        cell.lblCreatedDate.isHidden = true
        cell.issuerConstraint.isActive = true

      // let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)

        currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!

        let fromValue = 1
        let toValue = 0

        cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)
    }

    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
            let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
                let textField = alertController.textFields![0] as UITextField

                self.tOTPModel[indexPath.section].issuer = textField.text!
                self.tOTPS[indexPath.section].issuer = textField.text!

                let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
                KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
                tableView.reloadData()

            }))
            alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
                let tOTP = self.tOTPModel[indexPath.section]
                textField.placeholder = "Enter Issuer"
                textField.text = tOTP.issuer

            })
            self.present(alertController, animated: true, completion: nil)
        }
        editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)

        let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in

            let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
            let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
                self.tOTPS.remove(at: indexPath.section)
                self.tOTPModel.remove(at: indexPath.section)

                let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
                KeychainWrapper.standard.set(encodedData, forKey: "tOtps")

                tableView.deleteSections([indexPath.section], with: .automatic)
                tableView.reloadData()
            })
            let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
                self.dismiss(animated: true, completion: nil)
            })
            alertController.addAction(cancelAction)
            alertController.addAction(okAction)
            self.present(alertController, animated: true, completion: nil)

        }
        deleteAction.backgroundColor = .red

        return [editAction,deleteAction]
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    // Delegate Method that adds tOtp from QRCode scanner controller
    func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
        self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
        let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
        KeychainWrapper.standard.set(encodedData, forKey: "tOtps")

    }
}

Это объект модели ...

class TOTP: NSObject, NSCoding {

    var secretKey: String
    var issuer: String
    var scheme: String
    var createdDate: String

    init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
        self.secretKey = secretKey
        self.issuer = issuer
        self.scheme = scheme
        self.createdDate = createdDate
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(secretKey, forKey: "secretKey")
        aCoder.encode(issuer, forKey: "issuer")
        aCoder.encode(scheme, forKey: "scheme")
        aCoder.encode(createdDate, forKey: "timeInterval")
    }

    required init?(coder aDecoder: NSCoder) {
        secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
        issuer = aDecoder.decodeObject(forKey: "issuer") as! String
        scheme = aDecoder.decodeObject(forKey: "scheme") as! String
        createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
    }
}

Это класс генератора, который генерируетTOTP ...

class Generator {
    static func generatorWithSecretKey(key: String) -> OTPGenerator {
      //  let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
        let secretKey = MF_Base32Codec.data(fromBase32String: key)
        return TOTPGenerator(secret: secretKey, algorithm: OTPGenerator.defaultAlgorithm(), digits: 6, period: 30)
    }
}

1 Ответ

0 голосов
/ 17 мая 2018

Не понимаю, зачем вам отдельный массив tOTPModel.Я бы удалил это и просто использовал массив tOTPS, и я бы поместил генератор в объект TOTP, к которому он принадлежит.

Теперь вы можете просто перезагружать видимые строки каждый раз, когда срабатывает таймер.

class TOTP: NSObject, NSCoding {

    var secretKey: String
    var issuer: String
    var scheme: String
    var createdDate: String
    private var generator: OTPGenerator

    var otp: String = {
        return generator.generateOTP()
    }

    init(secretKey: String, issuer: String, scheme: String, createdDate: String) {
        self.secretKey = secretKey
        self.issuer = issuer
        self.scheme = scheme
        self.createdDate = createdDate
        self.generator =  Generator.generatorWithSecretKey(key: secretKey)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(secretKey, forKey: "secretKey")
        aCoder.encode(issuer, forKey: "issuer")
        aCoder.encode(scheme, forKey: "scheme")
        aCoder.encode(createdDate, forKey: "timeInterval")
    }

    required init?(coder aDecoder: NSCoder) {
        secretKey = aDecoder.decodeObject(forKey: "secretKey") as! String
        issuer = aDecoder.decodeObject(forKey: "issuer") as! String
        scheme = aDecoder.decodeObject(forKey: "scheme") as! String
        createdDate = aDecoder.decodeObject(forKey: "timeInterval") as! String
        generator =  Generator.generatorWithSecretKey(key: secretKey)
    }
}


class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource,AddTOTPDelegate, UIGestureRecognizerDelegate {

    var tOTPS = [TOTP]()

    var secretKeys = [String]()
    var timer: Timer?
    var currentTimeInterval = TimeInterval()

    @IBOutlet weak var tOtpTableView: UITableView!
    @IBOutlet weak var btnInfo: UIBarButtonItem!
    @IBOutlet weak var btnAddQRCode: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tOtpTableView.tableFooterView = UIView()
        self.emptyDataString = "Click + to add new account"
        let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longPress(_:)))
        longPressGesture.minimumPressDuration = 1.0
        longPressGesture.delegate = self
        self.tOtpTableView.addGestureRecognizer(longPressGesture)
        setUpViews()
        getTotps()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
      //  let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)
        if !self.tOTPS.isEmpty {
            self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(generateTOTP), userInfo: nil, repeats: true)
            self.timer?.fire()
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getTotps() {
        if let decodedData = KeychainWrapper.standard.data(forKey: "tOtps") {
            self.tOTPS = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! [TOTP]
        }
    }

    @IBAction func handleAddQRCode(_ sender: UIButton) {
        let controllerToPresent = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "QRScannerController") as! QRScannerController
        controllerToPresent.delegate = self
        controllerToPresent.tOTPS = self.tOTPS
        self.timer?.invalidate()
        DispatchQueue.main.async {
            self.navigationController?.pushViewController(controllerToPresent, animated: true)
        }
    }

    @objc func generateTOTP() {
        tableView.reloadRows(at:tableView.indexPathsForVisibleRows, with:.none)

    }

    func numberOfSections(in tableView: UITableView) -> Int {
         return self.tOTPModel.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "totpCell", for: indexPath) as! TotpViewCell
        let tOTP = self.tOTPs[indexPath.section]
        cell.lblTOTP.text = tOTP.otp.separate(every: 3, with: " ")
        cell.lblIssuer.text = tOTP.issuer
        cell.lblCreatedDate.text = "Created Date: \(tOTP.createdDate)"

        cell.lblCreatedDate.isHidden = true
        cell.issuerConstraint.isActive = true

      // let period = (Date().timeIntervalSince1970 / 1000).truncatingRemainder(dividingBy: 30)

        currentTimeInterval = (self.timer?.fireDate.timeIntervalSince(Date()))!

        let fromValue = 1
        let toValue = 0

        cell.progressView.handleAnimation(fromValue: fromValue, tVal: toValue, duration: currentTimeInterval)
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)
    }

    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let editAction = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexPath) in
            let alertController = UIAlertController(title: "Authenticator", message: "Enter the issuer", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { alert -> Void in
                let textField = alertController.textFields![0] as UITextField

                self.tOTPModel[indexPath.section].issuer = textField.text!
                self.tOTPS[indexPath.section].issuer = textField.text!

                let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
                KeychainWrapper.standard.set(encodedData, forKey: "tOtps")
                tableView.reloadData()

            }))
            alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            alertController.addTextField(configurationHandler: {(textField : UITextField!) -> Void in
                let tOTP = self.tOTPModel[indexPath.section]
                textField.placeholder = "Enter Issuer"
                textField.text = tOTP.issuer

            })
            self.present(alertController, animated: true, completion: nil)
        }
        editAction.backgroundColor = UIColor(red: 0/255, green: 145/255, blue: 147/255, alpha: 1.0)

        let deleteAction = UITableViewRowAction(style: .normal, title: "Delete") { (rowAction, indexPath) in

            let alertController = UIAlertController(title: "Authenticator", message: "Are you sure you want remove this account?", preferredStyle: .alert)
            let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
                self.tOTPS.remove(at: indexPath.section)
                self.tOTPModel.remove(at: indexPath.section)

                let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
                KeychainWrapper.standard.set(encodedData, forKey: "tOtps")

                tableView.deleteSections([indexPath.section], with: .automatic)
                tableView.reloadData()
            })
            let cancelAction = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) in
                self.dismiss(animated: true, completion: nil)
            })
            alertController.addAction(cancelAction)
            alertController.addAction(okAction)
            self.present(alertController, animated: true, completion: nil)

        }
        deleteAction.backgroundColor = .red

        return [editAction,deleteAction]
    }

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    // Delegate Method that adds tOtp from QRCode scanner controller
    func addTOTP(withSecret secret: String, issuer: String, scheme: String,createdDate: String) {
        self.tOTPS.append(TOTP(secretKey: secret, issuer: issuer, scheme: scheme, createdDate: createdDate))
        let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: self.tOTPS)
        KeychainWrapper.standard.set(encodedData, forKey: "tOtps")

    }
}
...