Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ struct ApplicationPasswordTutorialViewModel {
comment: "Reason for why the user could not login tin the application password tutorial screen")
case .invalidCredentials:
return error.localizedDescription
case .failedAuthenticationChallenge:
fatalError("Failure to handle authentication challenge is not eligible for application password flow.")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,9 @@ extension AuthenticationManager: WordPressAuthenticatorDelegate {

let isAppPasswordAuthError = {
switch error {
case SiteCredentialLoginError.genericFailure, SiteCredentialLoginError.invalidCredentials:
case SiteCredentialLoginError.genericFailure,
SiteCredentialLoginError.invalidCredentials,
SiteCredentialLoginError.failedAuthenticationChallenge:
return false
case is SiteCredentialLoginError:
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum SiteCredentialLoginError: LocalizedError {
case inaccessibleLoginPage
case inaccessibleAdminPage
case unacceptableStatusCode(code: Int)
case failedAuthenticationChallenge
case genericFailure(underlyingError: Error)

/// Used for tracking error code
Expand All @@ -26,7 +27,8 @@ enum SiteCredentialLoginError: LocalizedError {
.invalidLoginResponse,
.invalidCredentials,
.loginFailed,
.unacceptableStatusCode:
.unacceptableStatusCode,
.failedAuthenticationChallenge:
return NSError(domain: Self.errorDomain, code: errorCode, userInfo: [NSLocalizedDescriptionKey: errorMessage])
case .genericFailure(let underlyingError):
return underlyingError as NSError
Expand All @@ -45,6 +47,8 @@ enum SiteCredentialLoginError: LocalizedError {
return 401
case .unacceptableStatusCode(let code):
return code
case .failedAuthenticationChallenge:
return -2
case .genericFailure(let underlyingError):
return (underlyingError as NSError).code
}
Expand All @@ -64,6 +68,8 @@ enum SiteCredentialLoginError: LocalizedError {
return message
case .unacceptableStatusCode(let code):
return String(format: Localization.unacceptableStatusCode, code)
case .failedAuthenticationChallenge:
return Localization.failedAuthenticationChallenge
case .genericFailure:
return ""
}
Expand All @@ -83,7 +89,8 @@ enum SiteCredentialLoginError: LocalizedError {
comment: "Error message explaining login failure due to blocked WP Admin page"
)
static let invalidLoginResponse = NSLocalizedString(
"Unable to login due to an unexpected response from your site. We are working on fixing this issue.",
"siteCredentialLoginError.invalidLoginResponse.message",
value: "Unable to login due to an unexpected response from your site.",
comment: "Error message explaining login failure due to unexpected response."
)
static let unacceptableStatusCode = NSLocalizedString(
Expand All @@ -94,6 +101,11 @@ enum SiteCredentialLoginError: LocalizedError {
"It seems the username or password you entered doesn't quite match. Double-check your credentials and try again.",
comment: "Error message explaining login failure due to invalid credentials."
)
static let failedAuthenticationChallenge = NSLocalizedString(
"siteCredentialLoginError.failedAuthenticationChallenge.message",
value: "Unable to log in due to an unexpected security measure on your store. Please contact support for troubleshooting.",
comment: "Error message explaining login failure due to an unexpected authentication challenge."
)
}
}

Expand All @@ -104,12 +116,19 @@ enum SiteCredentialLoginError: LocalizedError {
/// - If the request does not redirect or the redirect fails, login fails.
/// Ref: pe5sF9-1iQ-p2
///
final class SiteCredentialLoginUseCase: NSObject, SiteCredentialLoginProtocol {
final class SiteCredentialLoginUseCase: NSObject, SiteCredentialLoginProtocol, URLSessionTaskDelegate {
private let siteURL: String
private let cookieJar: HTTPCookieStorage
private var successHandler: (() -> Void)?
private var errorHandler: ((SiteCredentialLoginError) -> Void)?
private lazy var session = URLSession(configuration: .default)

private var receivedAuthChallengeMethod: String?

private lazy var session = {
var configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForResource = 60
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()

init(siteURL: String,
cookieJar: HTTPCookieStorage = HTTPCookieStorage.shared) {
Expand All @@ -128,18 +147,26 @@ final class SiteCredentialLoginUseCase: NSObject, SiteCredentialLoginProtocol {
// Old cookies can make the login succeeds even with incorrect credentials
// So we need to clear all cookies before login.
clearAllCookies()

receivedAuthChallengeMethod = nil

guard let loginRequest = buildLoginRequest(username: username, password: password) else {
DDLogError("⛔️ Error constructing login requests")
return
}

Task { @MainActor in
do {
try await startLogin(with: loginRequest)
successHandler?()
} catch let error as SiteCredentialLoginError {
errorHandler?(error)
} catch {
errorHandler?(.genericFailure(underlyingError: error as NSError))
if receivedAuthChallengeMethod != nil {
errorHandler?(.failedAuthenticationChallenge)
} else {
errorHandler?(.genericFailure(underlyingError: error as NSError))
}
}
}
}
Expand Down Expand Up @@ -293,3 +320,15 @@ private extension String {
return wholeMatch(of: regex) != nil
}
}

// MARK: - URLSessionTaskDelegate
extension SiteCredentialLoginUseCase {
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let authMethod = challenge.protectionSpace.authenticationMethod
receivedAuthChallengeMethod = authMethod
DDLogWarn("⚠️ An authentication challenge is required for login: \(authMethod)")
completionHandler(.cancelAuthenticationChallenge, nil)
}
}