Skip to content
2 changes: 1 addition & 1 deletion RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
25.5
-----

* [***] Use web-based sign-in flow (hidden under a feature flag) [#23675]

25.4
-----
Expand Down
58 changes: 58 additions & 0 deletions WordPress/Classes/Login/WordPressDotComAuthenticator.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AuthenticationServices
import Foundation
import UIKit
import WordPressAuthenticator

import Alamofire

Expand All @@ -17,7 +18,64 @@ struct WordPressDotComAuthenticator {
case unknown(Swift.Error)
}

@MainActor
func signIn(from viewController: UINavigationController) async {
let token: String
do {
token = try await authenticate(from: viewController)
Copy link
Contributor

@kean kean Oct 17, 2024

Choose a reason for hiding this comment

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

The animation at the top is distracting.

Untitled.mp4

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure why it looks like this for me. It's different on your recording. I think there is a bug with the first welcome page.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we present this login screen as a pushed navigation item instead of a popover?

That'd be more consistent with the current setup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I thought we had the ability to do it in the SwiftUI setup for self-hosted sites, but it's ok – we can dig into that further once we're not relying on a bunch of WPAuthenticator stuff anymore.

I have a few suggestions about this in #23702

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm I thought we had the ability to do it in the SwiftUI setup for self-hosted sites

I just checked. The application password flow for self-hosted site also presents Safari view as a modal.

} catch {
if let error = error as? WordPressDotComAuthenticator.Error {
// Show an alert for non-cancellation errors.
// `.cancelled` error is thrown when user taps the cancel button in the presented Safari view controller.
if case .cancelled = error {
// Do nothing
} else {
// All other errors are unexpected.
wpAssertionFailure("WP.com web login failed", userInfo: ["error": "\(error)"])

let alert = UIAlertController(
title: NSLocalizedString("wpComLogin.error.title", value: "Error", comment: "Error"),
message: SharedStrings.Error.generic,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: SharedStrings.Button.close, style: .cancel, handler: nil))
viewController.present(alert, animated: true)
}
} else {
wpAssertionFailure("WP.com web login failed", userInfo: ["error": "\(error)"])
}
return
Copy link
Contributor

Choose a reason for hiding this comment

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

(nit) Extract the first part into a separate method so that there is no return in the middle of this long method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 43409de

}

let delegate = WordPressAuthenticator.shared.delegate!
let credentials = AuthenticatorCredentials(wpcom: WordPressComCredentials(authToken: token, isJetpackLogin: false, multifactor: false))
SVProgressHUD.show()
delegate.sync(credentials: credentials) {
SVProgressHUD.dismiss()

delegate.presentLoginEpilogue(
in: viewController,
for: credentials,
source: .custom(source: "web-login"),
onDismiss: { /* Do nothing */ }
)
}
}

func authenticate(from viewController: UIViewController) async throws -> String {
WPAnalytics.track(.wpcomWebSignIn, properties: ["stage": "start"])

do {
let value = try await _authenticate(from: viewController)
WPAnalytics.track(.wpcomWebSignIn, properties: ["stage": "success"])
return value
} catch {
WPAnalytics.track(.wpcomWebSignIn, properties: ["stage": "error", "error": "\(error)"])
throw error
}
}

private func _authenticate(from viewController: UIViewController) async throws -> String {
let clientId = ApiCredentials.client
let clientSecret = ApiCredentials.secret
let redirectURI = "x-wordpress-app://oauth2-callback"
Expand Down
5 changes: 0 additions & 5 deletions WordPress/Classes/System/SplitViewRootPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,13 @@ final class SplitViewRootPresenter: RootViewPresenter {
}

@MainActor private func signIn() async {
WPAnalytics.track(.wpcomWebSignIn, properties: ["source": "sidebar", "stage": "start"])

let token: String
do {
token = try await WordPressDotComAuthenticator().authenticate(from: splitVC)
} catch {
WPAnalytics.track(.wpcomWebSignIn, properties: ["source": "sidebar", "stage": "error", "error": "\(error)"])
return
}

WPAnalytics.track(.wpcomWebSignIn, properties: ["source": "sidebar", "stage": "success"])

SVProgressHUD.show()
let credentials = WordPressComCredentials(authToken: token, isJetpackLogin: false, multifactor: false)
WordPressAuthenticator.shared.delegate!.sync(credentials: .init(wpcom: credentials)) {
Expand Down
20 changes: 19 additions & 1 deletion WordPress/Classes/System/WordPressAuthenticatorProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ protocol WordPressAuthenticatorProtocol {

extension WordPressAuthenticator: WordPressAuthenticatorProtocol {
static func loginUI() -> UIViewController? {
Self.loginUI(showCancel: false, restrictToWPCom: false, onLoginButtonTapped: nil)
Self.loginUI(showCancel: false, restrictToWPCom: false, onLoginButtonTapped: nil, continueWithDotCom: { viewController in
// TODO: Replce with a remote feature flag.
// Enable web-based login for debug builds until the remote feature flag is available.
#if DEBUG
let webLoginEnabled = true
Copy link
Contributor

Choose a reason for hiding this comment

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

If there is no need to update the flag remotely, you can use extension FeatureFlag: RolloutConfigurableFlag { to implement the percentage-based rollout locally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In that case, we'd need to release the app to update the rollout percentage, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Personally, I'd prefer a remote feature flag because we can completely turn it off if there are horrible issues.

#else
let webLoginEnabled = false
#endif

guard webLoginEnabled, let navigationController = viewController.navigationController else {
return false
}

Task { @MainActor in
await WordPressDotComAuthenticator().signIn(from: navigationController)
}

return true
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -525,18 +525,13 @@ class MeViewController: UITableViewController {
///
fileprivate func promptForLoginOrSignup() {
Task { @MainActor in
WPAnalytics.track(.wpcomWebSignIn, properties: ["source": "me", "stage": "start"])

let token: String
do {
token = try await WordPressDotComAuthenticator().authenticate(from: self)
} catch {
WPAnalytics.track(.wpcomWebSignIn, properties: ["source": "me", "stage": "error", "error": "\(error)"])
return
}

WPAnalytics.track(.wpcomWebSignIn, properties: ["source": "me", "stage": "success"])

SVProgressHUD.show()
let credentials = WordPressComCredentials(authToken: token, isJetpackLogin: false, multifactor: false)
WordPressAuthenticator.shared.delegate!.sync(credentials: .init(wpcom: credentials)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ import WordPressKit
/// - restrictToWPCom: Whether only WordPress.com login is enabled.
/// - onLoginButtonTapped: Called when the login button on the prologue screen is tapped.
/// - Returns: The root view controller for the login flow.
public class func loginUI(showCancel: Bool = false, restrictToWPCom: Bool = false, onLoginButtonTapped: (() -> Void)? = nil) -> UIViewController? {
public class func loginUI(showCancel: Bool = false, restrictToWPCom: Bool = false, onLoginButtonTapped: (() -> Void)? = nil, continueWithDotCom: ((UIViewController) -> Bool)? = nil) -> UIViewController? {
let storyboard = Storyboard.login.instance
guard let controller = storyboard.instantiateInitialViewController() else {
assertionFailure("Cannot instantiate initial login controller from Login.storyboard")
Expand All @@ -174,6 +174,7 @@ import WordPressKit

if let loginNavController = controller as? LoginNavigationController, let loginPrologueViewController = loginNavController.viewControllers.first as? LoginPrologueViewController {
loginPrologueViewController.showCancel = showCancel
loginPrologueViewController.continueWithDotComOverwrite = continueWithDotCom
}

controller.modalPresentationStyle = .fullScreen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class LoginPrologueViewController: LoginViewController {
private var buttonViewController: NUXButtonViewController?
private var stackedButtonsViewController: NUXStackedButtonsViewController?
var showCancel = false
var continueWithDotComOverwrite: ((UIViewController) -> Bool)? = nil

@IBOutlet private weak var buttonContainerView: UIView!
/// Blur effect on button container view
Expand Down Expand Up @@ -526,6 +527,10 @@ class LoginPrologueViewController: LoginViewController {
/// Unified "Continue with WordPress.com" prologue button action.
///
private func continueWithDotCom() {
if let continueWithDotComOverwrite, continueWithDotComOverwrite(self) {
return
}

guard let vc = GetStartedViewController.instantiate(from: .getStarted) else {
WPAuthenticatorLogError("Failed to navigate from LoginPrologueViewController to GetStartedViewController")
return
Expand Down