Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.

Conversation

@allendav
Copy link
Contributor

@allendav allendav commented Jan 11, 2021

Fixes #557

To test, e.g. with WooCommerce iOS

  • Build
  • Ensure all unit tests pass, including the two new tests added for this under PasteboardTests
  • Update WooCommerce iOS' Podfile to point at a local clone of this branch
  • Build WooCommerce iOS with this pod
  • Launch
  • Sign in to WordPress.com using an account with 2FA enabled
  • At the 2FA code prompt, swipe back to the home screen and open your authenticator app
  • Select a 2FA code with a leading zero and tap it to copy it
  • Swipe back into the WooCommerce iOS app
  • Ensure the 2FA code (all six digits) is automatically pasted for you
  • Don't actually complete login. Instead, return to the authenticator app and grab a 2FA code without a leading zero.
  • Swipe back into the WooCommerce iOS app
  • Ensure the 2FA code (all six digits) is automatically pasted for you
  • Lastly, do it again but this time grab the code for your actual login and ensure you can complete login with it.

@allendav
Copy link
Contributor Author

Interesting. It isn't the cast after all. It is how .number pattern matching is working with the pasteboard, i.e.:

                pasteboard.detectValues(for: [.number]) { valuesResult in
                    switch valuesResult {
                        case .success(let twofactorcode):
                            let foo = twofactorcode.first
                            foo?.value

returns 11234 when the pasteboard contains 011234

@allendav
Copy link
Contributor Author

LOL. I've been digging into this more today. Finally have a robust sequence for pulling out two factor codes. Will update the PR with it tomorrow:

import UIKit

let pasteboard = UIPasteboard.general

pasteboard.string = "123123" /// Try different values here like 123456 123 123123123 asvab 3.1415 -99 etc

/// Detects a two factor authorization code in the pasteboard (if present)
/// and calls the completion with it, prepended as needed with zeros to yield six digits
func detectTwoFactorCode(completion: @escaping (Result<String, Error>) -> Void) {
    pasteboard.detectPatterns(for: [.number]) { result in
        var twoFactorCode: String = ""

        switch result {
            case .success(let detections):
                if detections.isEmpty {
                    completion(.success(""))
                    return
                }
                
                pasteboard.detectValues(for: [.number]) { valuesResult in
                    switch valuesResult {
                        case .success(let match):
                            if let matchedNumber = match.first?.value as? NSNumber {
                                twoFactorCode = matchedNumber.stringValue
                                
                                /// Reject numbers with decimal points or signs in them
                                let possibleCodeSet = CharacterSet(charactersIn: twoFactorCode)
                                if !possibleCodeSet.isSubset(of: CharacterSet.decimalDigits) {
                                    completion(.success(""))
                                    return
                                }
                                
                                /// Reject numbers with more than 6 digits
                                if twoFactorCode.count > 6 {
                                    completion(.success(""))
                                    return
                                }
                                
                                /// Prepend zeros to get to 6 digits
                                while twoFactorCode.count < 6 {
                                    twoFactorCode = "0" + twoFactorCode
                                }

                                completion(.success(twoFactorCode))
                                return
                            }
                        case .failure(let error):
                            completion(.failure(error))
                            return
                    }
                }
            case .failure(let error):
                completion(.failure(error))
                return
        }
    }
}

detectTwoFactorCode() { result in
    switch result {
    case .success(let twoFactorCode):
        twoFactorCode
    case .failure(_):
        "Failure"
    }
}

@allendav allendav marked this pull request as ready for review January 13, 2021 22:53
@allendav allendav self-assigned this Jan 13, 2021
@allendav allendav requested a review from bjtitus January 13, 2021 22:56
Copy link
Contributor

@bjtitus bjtitus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor code suggestions. Otherwise, everything looks good!

}

/// We need 6 digits. No more, no less.
if authenticationCode.count > 6 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, but I wonder if the length should be a parameter of this method. If not, it should probably go as a constant at the top. I think I prefer making it a parameter with a default value, though.

public func detectAuthenticatorCode(length: Int = 6, completion: @escaping (Result<String, Error>) -> Void)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e109be8

Comment on lines 1 to 7
//
// PasteboardTests.swift
// WordPressAuthenticatorTests
//
// Created by Allen Snook on 1/13/21.
// Copyright © 2021 Automattic. All rights reserved.
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this generated header?

We generally keep them out of the projects, but there's no good linter rule available to do it. 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c96f997

@bjtitus
Copy link
Contributor

bjtitus commented Jan 16, 2021

It's pretty annoying that Apple is stripping the leading zeros and doesn't mention it.
We should file feedback to have them at least add it to the docs or, better yet, don't strip the leading digits in case they're significant...

@allendav
Copy link
Contributor Author

We should file feedback...

Agreed. Done. Feedback ID FB8974839

@allendav
Copy link
Contributor Author

All feedback addressed. Ready for next round of review. Thank you @bjtitus !

@bjtitus bjtitus self-requested a review January 20, 2021 20:24
Copy link
Contributor

@bjtitus bjtitus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks good to me!

Thanks for fixing this @allendav!

@allendav allendav merged commit 135f66a into develop Jan 21, 2021
@allendav allendav deleted the fix/557-leading-zero-2fa branch January 21, 2021 18:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Google Authenticator 2FA codes with leading zeros are copied without the leading zero

3 participants