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

Commit b20a8de

Browse files
authored
Merge pull request #662 from wordpress-mobile/wcios/7437-magic-link-auto-sent
Prefer magic link to password under a configuration
2 parents ac1d4e8 + 51f90cd commit b20a8de

File tree

12 files changed

+483
-3
lines changed

12 files changed

+483
-3
lines changed

WordPressAuthenticator.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Pod::Spec.new do |s|
44
s.name = 'WordPressAuthenticator'
5-
s.version = '2.2.0-beta.3'
5+
s.version = '2.2.0-beta.4'
66

77
s.summary = 'WordPressAuthenticator implements an easy and elegant way to authenticate your WordPress Apps.'
88
s.description = <<-DESC

WordPressAuthenticator.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
/* Begin PBXBuildFile section */
1010
020BE74A23B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 020BE74923B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift */; };
11+
02A526CA28A3499C00FD1812 /* MagicLinkRequestedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A526C828A3499C00FD1812 /* MagicLinkRequestedViewController.swift */; };
12+
02A526CB28A3499C00FD1812 /* MagicLinkRequestedViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 02A526C928A3499C00FD1812 /* MagicLinkRequestedViewController.xib */; };
13+
02A526CD28A3A23900FD1812 /* UIButton+Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A526CC28A3A23900FD1812 /* UIButton+Styles.swift */; };
14+
02A526CF28A3A35D00FD1812 /* PasswordCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A526CE28A3A35D00FD1812 /* PasswordCoordinator.swift */; };
1115
1A21EE9822832BC300C940C6 /* WordPressComOAuthClientFacade+Swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A21EE9722832BC200C940C6 /* WordPressComOAuthClientFacade+Swift.swift */; };
1216
1A4095182271AEFC009AA86D /* WPAuthenticator-Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A4095152271AEFC009AA86D /* WPAuthenticator-Swift.h */; settings = {ATTRIBUTES = (Private, ); }; };
1317
3108613125AFA4830022F75E /* PasteboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3108613025AFA4830022F75E /* PasteboardTests.swift */; };
@@ -209,6 +213,10 @@
209213

210214
/* Begin PBXFileReference section */
211215
020BE74923B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressAuthenticatorDisplayImages.swift; sourceTree = "<group>"; };
216+
02A526C828A3499C00FD1812 /* MagicLinkRequestedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicLinkRequestedViewController.swift; sourceTree = "<group>"; };
217+
02A526C928A3499C00FD1812 /* MagicLinkRequestedViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MagicLinkRequestedViewController.xib; sourceTree = "<group>"; };
218+
02A526CC28A3A23900FD1812 /* UIButton+Styles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Styles.swift"; sourceTree = "<group>"; };
219+
02A526CE28A3A35D00FD1812 /* PasswordCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordCoordinator.swift; sourceTree = "<group>"; };
212220
1A21EE9722832BC200C940C6 /* WordPressComOAuthClientFacade+Swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WordPressComOAuthClientFacade+Swift.swift"; sourceTree = "<group>"; };
213221
1A4095152271AEFC009AA86D /* WPAuthenticator-Swift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "WPAuthenticator-Swift.h"; sourceTree = "<group>"; };
214222
276354F054C34AD36CA32AB6 /* Pods-WordPressAuthenticator.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressAuthenticator.release-alpha.xcconfig"; path = "Pods/Target Support Files/Pods-WordPressAuthenticator/Pods-WordPressAuthenticator.release-alpha.xcconfig"; sourceTree = "<group>"; };
@@ -478,6 +486,7 @@
478486
children = (
479487
984D508124D4B21A00251A63 /* PasswordViewController.swift */,
480488
984D508324D4B24500251A63 /* Password.storyboard */,
489+
02A526CE28A3A35D00FD1812 /* PasswordCoordinator.swift */,
481490
);
482491
path = Password;
483492
sourceTree = "<group>";
@@ -691,6 +700,7 @@
691700
CE9091FD249A720E00AB50BD /* UIView+AuthHelpers.swift */,
692701
F11448EB258B827B0048203D /* URL+JetpackConnect.swift */,
693702
EE633D01287560E50002DE03 /* UITableView+Helpers.swift */,
703+
02A526CC28A3A23900FD1812 /* UIButton+Styles.swift */,
694704
);
695705
path = Extensions;
696706
sourceTree = "<group>";
@@ -849,6 +859,8 @@
849859
children = (
850860
CE811D6624EDC0FB00F4CCD6 /* LoginMagicLinkViewController.swift */,
851861
CE811D6824EDC14A00F4CCD6 /* LoginMagicLink.storyboard */,
862+
02A526C828A3499C00FD1812 /* MagicLinkRequestedViewController.swift */,
863+
02A526C928A3499C00FD1812 /* MagicLinkRequestedViewController.xib */,
852864
);
853865
path = Login;
854866
sourceTree = "<group>";
@@ -1069,6 +1081,7 @@
10691081
isa = PBXResourcesBuildPhase;
10701082
buildActionMask = 2147483647;
10711083
files = (
1084+
02A526CB28A3499C00FD1812 /* MagicLinkRequestedViewController.xib in Resources */,
10721085
B5E07FF4208FD13800657A9A /* Assets.xcassets in Resources */,
10731086
98F40AF024F5E13200A72911 /* TextWithLinkTableViewCell.xib in Resources */,
10741087
B56090CA208A4F5400399AE4 /* NUXButtonView.storyboard in Resources */,
@@ -1218,6 +1231,7 @@
12181231
D8611A63257622ED00A5DF27 /* NavigateBack.swift in Sources */,
12191232
B5609143208A563800399AE4 /* LoginSocialErrorViewController.swift in Sources */,
12201233
B56090F8208A533200399AE4 /* WordPressAuthenticator+Notifications.swift in Sources */,
1234+
02A526CF28A3A35D00FD1812 /* PasswordCoordinator.swift in Sources */,
12211235
98ED483624802F8F00992B2D /* GoogleAuthViewController.swift in Sources */,
12221236
F5C817E72582B2F300BD5A3B /* UIPasteboard+Detect.swift in Sources */,
12231237
B56090EA208A51D000399AE4 /* LoginFields+Validation.swift in Sources */,
@@ -1242,6 +1256,7 @@
12421256
CE2D03E024E5DD4500D18942 /* UnifiedSignupViewController.swift in Sources */,
12431257
98CF18F7248725370047B66C /* GoogleSignupConfirmationViewController.swift in Sources */,
12441258
1A21EE9822832BC300C940C6 /* WordPressComOAuthClientFacade+Swift.swift in Sources */,
1259+
02A526CD28A3A23900FD1812 /* UIButton+Styles.swift in Sources */,
12451260
CEC77C6624854F2E00FB9050 /* SiteAddressViewController.swift in Sources */,
12461261
CEDE0D952420121D00CB3345 /* UIStoryboard+Helpers.swift in Sources */,
12471262
B5ED7920207E993E00A8FD8C /* WPAuthenticatorLogging.m in Sources */,
@@ -1307,6 +1322,7 @@
13071322
B56090D0208A4F5400399AE4 /* NUXViewControllerBase.swift in Sources */,
13081323
3F550D5123DA4A9C007E5897 /* LinkMailPresenter.swift in Sources */,
13091324
98D9A4B12474A526002E491C /* GoogleAuthenticator.swift in Sources */,
1325+
02A526CA28A3499C00FD1812 /* MagicLinkRequestedViewController.swift in Sources */,
13101326
CE1BBF8C24D48580001D2E3E /* GravatarEmailTableViewCell.swift in Sources */,
13111327
B56090DE208A4F9D00399AE4 /* WPWalkthroughOverlayView.m in Sources */,
13121328
B560910A208A54F800399AE4 /* WordPressComAccountService.swift in Sources */,

WordPressAuthenticator/Analytics/AuthenticatorAnalyticsTracker.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ public class AuthenticatorAnalyticsTracker {
125125
/// Triggered when a user enters site credentials and sees the screen with instructions to verify email. (`VerifyEmailViewController`)
126126
///
127127
case verifyEmailInstructions = "instructions_to_verify_email"
128+
129+
/// Triggered when a magic link is automatically requested after filling in email address and the requested screen is shown
130+
///
131+
case magicLinkAutoRequested = "magic_link_auto_requested"
128132
}
129133

130134
public enum ClickTarget: String {

WordPressAuthenticator/Authenticator/WordPressAuthenticatorConfiguration.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ public struct WordPressAuthenticatorConfiguration {
104104
/// Disabled by default
105105
let isWPComLoginRequiredForSiteCredentialsLogin: Bool
106106

107+
/// If enabled, a magic link is sent automatically in place of password then fall back to password.
108+
/// If disabled, password is shown by default with an option to send a magic link.
109+
let isWPComMagicLinkPreferredToPassword: Bool
110+
107111
/// Designated Initializer
108112
///
109113
public init (wpcomClientId: String,
@@ -126,7 +130,8 @@ public struct WordPressAuthenticatorConfiguration {
126130
displayHintButtons: Bool = true,
127131
continueWithSiteAddressFirst: Bool = false,
128132
enableSiteCredentialsLoginForSelfHostedSites: Bool = false,
129-
isWPComLoginRequiredForSiteCredentialsLogin: Bool = false) {
133+
isWPComLoginRequiredForSiteCredentialsLogin: Bool = false,
134+
isWPComMagicLinkPreferredToPassword: Bool = false) {
130135

131136
self.wpcomClientId = wpcomClientId
132137
self.wpcomSecret = wpcomSecret
@@ -149,5 +154,6 @@ public struct WordPressAuthenticatorConfiguration {
149154
self.continueWithSiteAddressFirst = continueWithSiteAddressFirst
150155
self.enableSiteCredentialsLoginForSelfHostedSites = enableSiteCredentialsLoginForSelfHostedSites
151156
self.isWPComLoginRequiredForSiteCredentialsLogin = isWPComLoginRequiredForSiteCredentialsLogin
157+
self.isWPComMagicLinkPreferredToPassword = isWPComMagicLinkPreferredToPassword
152158
}
153159
}

WordPressAuthenticator/Authenticator/WordPressSupportSourceTag.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,7 @@ extension WordPressSupportSourceTag {
7575
return WordPressSupportSourceTag(name: "wpComSignupApple", origin: "origin:signup-apple")
7676
}
7777

78+
public static var wpComLoginMagicLinkAutoRequested: WordPressSupportSourceTag {
79+
return WordPressSupportSourceTag(name: "wpComLoginMagicLinkAutoRequested", origin: "origin:login-email")
80+
}
7881
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import UIKit
2+
3+
extension UIButton {
4+
/// Applies the style that looks like a plain text link.
5+
func applyLinkButtonStyle() {
6+
backgroundColor = .clear
7+
titleLabel?.font = WPStyleGuide.fontForTextStyle(.body)
8+
titleLabel?.textAlignment = .natural
9+
10+
let buttonTitleColor = WordPressAuthenticator.shared.unifiedStyle?.textButtonColor ?? WordPressAuthenticator.shared.style.textButtonColor
11+
let buttonHighlightColor = WordPressAuthenticator.shared.unifiedStyle?.textButtonHighlightColor ?? WordPressAuthenticator.shared.style.textButtonHighlightColor
12+
setTitleColor(buttonTitleColor, for: .normal)
13+
setTitleColor(buttonHighlightColor, for: .highlighted)
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "email.pdf",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true
14+
}
15+
}
Binary file not shown.

WordPressAuthenticator/Unified Auth/View Related/Get Started/GetStartedViewController.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class GetStartedViewController: LoginViewController, NUXKeyboardResponder {
7373
private let configuration = WordPressAuthenticator.shared.configuration
7474
private var shouldChangeVoiceOverFocus: Bool = false
7575

76+
private var passwordCoordinator: PasswordCoordinator?
77+
7678
/// Sign in with site credentials button will be displayed based on the `screenMode`
7779
///
7880
private var screenMode: ScreenMode {
@@ -499,7 +501,7 @@ private extension GetStartedViewController {
499501
success: { [weak self] passwordless in
500502
self?.configureViewLoading(false)
501503
self?.loginFields.meta.passwordless = passwordless
502-
passwordless ? self?.requestAuthenticationLink() : self?.showPasswordView()
504+
passwordless ? self?.requestAuthenticationLink() : self?.showPasswordOrMagicLinkView()
503505
},
504506
failure: { [weak self] error in
505507
WordPressAuthenticator.track(.loginFailed, error: error)
@@ -528,6 +530,26 @@ private extension GetStartedViewController {
528530
navigationController?.pushViewController(vc, animated: true)
529531
}
530532

533+
/// Show the password or magic link view based on the configuration.
534+
///
535+
func showPasswordOrMagicLinkView() {
536+
guard let navigationController = navigationController else {
537+
return
538+
}
539+
configureViewLoading(true)
540+
let coordinator = PasswordCoordinator(navigationController: navigationController,
541+
source: source,
542+
loginFields: loginFields,
543+
tracker: tracker,
544+
configuration: WordPressAuthenticator.shared.configuration)
545+
passwordCoordinator = coordinator
546+
Task { @MainActor [weak self] in
547+
guard let self = self else { return }
548+
await coordinator.start()
549+
self.configureViewLoading(false)
550+
}
551+
}
552+
531553
/// Handle errors when attempting to log in with an email address
532554
///
533555
func handleLoginError(_ error: Error) {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import UIKit
2+
import WordPressUI
3+
4+
final class MagicLinkRequestedViewController: LoginViewController {
5+
6+
// MARK: Properties
7+
8+
@IBOutlet private weak var titleLabel: UILabel!
9+
@IBOutlet private weak var subtitleLabel: UILabel!
10+
@IBOutlet private weak var emailLabel: UILabel!
11+
@IBOutlet private weak var cannotFindEmailLabel: UILabel!
12+
@IBOutlet private weak var buttonContainerView: UIView!
13+
@IBOutlet private weak var loginWithPasswordButton: UIButton!
14+
15+
private let email: String
16+
private let loginWithPassword: () -> Void
17+
18+
private lazy var buttonViewController: NUXButtonViewController = .instance()
19+
20+
init(email: String, loginWithPassword: @escaping () -> Void) {
21+
self.email = email
22+
self.loginWithPassword = loginWithPassword
23+
super.init(nibName: "MagicLinkRequestedViewController", bundle: WordPressAuthenticator.bundle)
24+
}
25+
26+
required init?(coder: NSCoder) {
27+
fatalError("init(coder:) has not been implemented")
28+
}
29+
30+
override var sourceTag: WordPressSupportSourceTag {
31+
.wpComLoginMagicLinkAutoRequested
32+
}
33+
34+
// MARK: - View lifecycle
35+
36+
override func viewDidLoad() {
37+
super.viewDidLoad()
38+
39+
navigationItem.title = WordPressAuthenticator.shared.displayStrings.logInTitle
40+
styleNavigationBar(forUnified: true)
41+
42+
setupButtons()
43+
setupTitleLabel()
44+
setupSubtitleLabel()
45+
setupEmailLabel()
46+
setupCannotFindEmailLabel()
47+
}
48+
49+
override func viewDidAppear(_ animated: Bool) {
50+
super.viewDidAppear(animated)
51+
52+
tracker.set(flow: .loginWithMagicLink)
53+
54+
if isBeingPresentedInAnyWay {
55+
tracker.track(step: .magicLinkAutoRequested)
56+
} else {
57+
tracker.set(step: .magicLinkAutoRequested)
58+
}
59+
}
60+
61+
// MARK: - Overrides
62+
63+
/// Style individual ViewController backgrounds, for now.
64+
///
65+
override func styleBackground() {
66+
guard let unifiedBackgroundColor = WordPressAuthenticator.shared.unifiedStyle?.viewControllerBackgroundColor else {
67+
return super.styleBackground()
68+
}
69+
view.backgroundColor = unifiedBackgroundColor
70+
}
71+
72+
/// Style individual ViewController status bars.
73+
///
74+
override var preferredStatusBarStyle: UIStatusBarStyle {
75+
WordPressAuthenticator.shared.unifiedStyle?.statusBarStyle ?? WordPressAuthenticator.shared.style.statusBarStyle
76+
}
77+
}
78+
79+
private extension MagicLinkRequestedViewController {
80+
func setupButtons() {
81+
setupContinueMailButton()
82+
setupLoginWithPasswordButton()
83+
}
84+
85+
/// Configures the primary button using the shared NUXButton style without a Storyboard.
86+
func setupContinueMailButton() {
87+
buttonViewController.setupTopButton(title: WordPressAuthenticator.shared.displayStrings.openMailButtonTitle, isPrimary: true, onTap: { [weak self] in
88+
guard let self = self else { return }
89+
guard let topButton = self.buttonViewController.topButton else {
90+
return
91+
}
92+
self.openMail(sender: topButton)
93+
})
94+
buttonViewController.move(to: self, into: buttonContainerView)
95+
}
96+
97+
/// Unfortunately, the plain text button style is not available in `NUXButton` as it currently supports primary or secondary.
98+
/// The plain text button is configured manually here.
99+
func setupLoginWithPasswordButton() {
100+
loginWithPasswordButton.setTitle(Localization.loginWithPasswordAction, for: .normal)
101+
loginWithPasswordButton.applyLinkButtonStyle()
102+
loginWithPasswordButton.on(.touchUpInside) { [weak self] _ in
103+
self?.loginWithPassword()
104+
}
105+
}
106+
107+
func setupTitleLabel() {
108+
titleLabel.text = Localization.title
109+
titleLabel.font = WPStyleGuide.mediumWeightFont(forStyle: .title3)
110+
titleLabel.textColor = WordPressAuthenticator.shared.unifiedStyle?.textColor
111+
titleLabel.numberOfLines = 0
112+
}
113+
114+
func setupSubtitleLabel() {
115+
subtitleLabel.text = Localization.subtitle
116+
subtitleLabel.font = WPStyleGuide.fontForTextStyle(.body)
117+
subtitleLabel.textColor = WordPressAuthenticator.shared.unifiedStyle?.textColor
118+
subtitleLabel.numberOfLines = 0
119+
}
120+
121+
func setupEmailLabel() {
122+
emailLabel.text = email
123+
emailLabel.numberOfLines = 0
124+
emailLabel.font = WPStyleGuide.fontForTextStyle(.body, fontWeight: .bold)
125+
emailLabel.textColor = WordPressAuthenticator.shared.unifiedStyle?.textColor
126+
}
127+
128+
func setupCannotFindEmailLabel() {
129+
cannotFindEmailLabel.text = Localization.cannotFindMailLoginInstructions
130+
cannotFindEmailLabel.numberOfLines = 0
131+
cannotFindEmailLabel.font = WPStyleGuide.fontForTextStyle(.footnote)
132+
cannotFindEmailLabel.textColor = WordPressAuthenticator.shared.unifiedStyle?.textSubtleColor
133+
}
134+
}
135+
136+
private extension MagicLinkRequestedViewController {
137+
func openMail(sender: UIView) {
138+
tracker.track(click: .openEmailClient)
139+
tracker.track(step: .emailOpened)
140+
141+
let linkMailPresenter = LinkMailPresenter(emailAddress: email)
142+
let appSelector = AppSelector(sourceView: sender)
143+
linkMailPresenter.presentEmailClients(on: self, appSelector: appSelector)
144+
}
145+
}
146+
147+
private extension MagicLinkRequestedViewController {
148+
enum Localization {
149+
static let cannotFindMailLoginInstructions = NSLocalizedString("If you can’t find the email, please check your junk or spam email folder",
150+
comment: "The instructions text about not being able to find the magic link email.")
151+
static let title = NSLocalizedString("Check your email on this device!",
152+
comment: "The title text on the magic link requested screen.")
153+
static let subtitle = NSLocalizedString("We just sent a magic link to",
154+
comment: "The subtitle text on the magic link requested screen followed by the email address.")
155+
static let loginWithPasswordAction = NSLocalizedString("Use password to sign in",
156+
comment: "The button title text for logging in with WP.com password instead of magic link.")
157+
}
158+
}

0 commit comments

Comments
 (0)