diff --git a/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt b/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt index 0b32584..121a218 100644 --- a/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt +++ b/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt @@ -224,8 +224,10 @@ class OnboardingFlowTest { // add" button sit below the fold of this scrolling screen, and a positional click on an // off-screen button is a silent no-op (which is why this passed only on API 37's taller AVD). waitForText(string(R.string.app_password_email)) - // Gmail requires 2-Step Verification before app passwords, so its screen (and only its - // screen — see yahooSetup_hasNoTwoFactorHelpLink) links Google's setup article (issue #98). + // Gmail requires 2-Step Verification before app passwords, so its screen links Google's + // setup article (issue #98). iCloud gets the same kind of link, in Apple's own terminology + // (see icloudSetup_hasTwoFactorHelpLink); Yahoo does not (see + // yahooSetup_hasNoTwoFactorHelpLink). composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help)) .performScrollTo().assertIsDisplayed() composeTestRule.onNodeWithText(string(R.string.app_password_email)) @@ -254,7 +256,27 @@ class OnboardingFlowTest { // Yahoo's setup screen keeps its app-password link… waitForText(string(R.string.app_password_open_page, "Yahoo Mail")) - // …but gains no 2-Step Verification link: that prerequisite is Gmail-specific (issue #98). + // …but gains no two-factor help link: unlike Gmail and iCloud, Yahoo gates nothing on it + // (issue #98, #153). composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help)).assertDoesNotExist() + composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help_icloud)).assertDoesNotExist() + } + + @Test + fun icloudSetup_hasTwoFactorHelpLink() { + setOnboardingContent(FakeAccountRepository(), FakeMailRepository()) + + composeTestRule.onNodeWithText(string(R.string.onboarding_add_account)).performClick() + waitForText("iCloud Mail") + composeTestRule.onNodeWithText("iCloud Mail").performClick() + + // Apple also won't issue an app-specific password until two-factor authentication is on, + // so iCloud's screen links Apple's own setup article too — using Apple's terminology for + // the button ("Two-Factor Authentication"), not Google's "2-Step Verification" (issue #153). + waitForText(string(R.string.app_password_2fa_help_icloud)) + composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help_icloud)) + .performScrollTo().assertIsDisplayed() + composeTestRule.onNodeWithText(string(R.string.app_password_open_page, "iCloud Mail")) + .assertIsDisplayed() } } diff --git a/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt b/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt index 9d9ff26..5f93cc2 100644 --- a/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt +++ b/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt @@ -31,8 +31,8 @@ enum class MailProvider( val appPasswordHelpUrl: String, /** * Setup instructions for the provider's two-factor prerequisite, or null when there isn't one. - * Only Gmail refuses to create app passwords until 2-Step Verification is on, so only Gmail - * links its setup article; Yahoo and iCloud gate nothing on it. + * Gmail and iCloud both refuse to issue app passwords until two-factor auth is on, so both + * link their own setup article; Yahoo gates nothing on it. */ val twoFactorHelpUrl: String? = null, private val imapHost: String, @@ -74,7 +74,12 @@ enum class MailProvider( ICLOUD( key = "icloud", displayName = "iCloud Mail", - appPasswordHelpUrl = "https://appleid.apple.com", + // Apple's own app-specific-password instructions, not just the generic Apple ID sign-in + // page — this article also states the two-factor prerequisite below. + appPasswordHelpUrl = "https://support.apple.com/en-us/102654", + // Like Gmail, Apple won't issue an app-specific password until two-factor authentication + // is on, so link Apple's dedicated setup article too (issue #153). + twoFactorHelpUrl = "https://support.apple.com/en-us/102660", imapHost = "imap.mail.me.com", smtpHost = "smtp.mail.me.com", // Apple documents smtp.mail.me.com:587 with STARTTLS for iCloud Mail. diff --git a/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt b/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt index 357d4fe..2482252 100644 --- a/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt +++ b/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt @@ -151,15 +151,16 @@ fun AppPasswordSetupScreen( ) Spacer(Modifier.height(12.dp)) - // Only Gmail has a two-factor prerequisite (see MailProvider.twoFactorHelpUrl): its - // app-passwords page rejects accounts without 2-Step Verification, so point users - // there first instead of sending them to the app-passwords page's dead end. + // Gmail and iCloud both have a two-factor prerequisite (see + // MailProvider.twoFactorHelpUrl): neither will issue an app password until it's on, so + // point users there first instead of sending them to the app-passwords page's dead + // end. Yahoo has no twoFactorHelpUrl, so this renders nothing for it. provider.twoFactorHelpUrl?.let { twoFactorHelpUrl -> OutlinedButton( onClick = { openUrl(twoFactorHelpUrl) }, modifier = Modifier.fillMaxWidth(), ) { - Text(stringResource(R.string.app_password_2fa_help)) + Text(stringResource(twoFactorHelpLabel(provider))) } Spacer(Modifier.height(8.dp)) } @@ -269,6 +270,16 @@ private fun providerIntro(provider: MailProvider): Int = when (provider) { MailProvider.ICLOUD -> R.string.app_password_intro_icloud } +/** + * Button copy for the two-factor prerequisite link, in each provider's own terminology — Google + * calls it "2-Step Verification", Apple "Two-Factor Authentication". Only reached for providers + * that expose [MailProvider.twoFactorHelpUrl]; Yahoo has none, so its branch here is unused. + */ +private fun twoFactorHelpLabel(provider: MailProvider): Int = when (provider) { + MailProvider.ICLOUD -> R.string.app_password_2fa_help_icloud + MailProvider.GMAIL, MailProvider.YAHOO -> R.string.app_password_2fa_help +} + private fun MailSecurity.label(): String = when (this) { MailSecurity.SSL_TLS -> "SSL/TLS" MailSecurity.STARTTLS -> "STARTTLS" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 976335b..529f981 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -184,6 +184,7 @@ Store this app password carefully — it grants full access to your email. LibreMail keeps it only on this device. Create an app password for %1$s How to turn on 2-Step Verification + How to turn on Two-Factor Authentication Couldn\'t open your browser Email address App password diff --git a/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt b/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt index 5ddfa07..69d16de 100644 --- a/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt +++ b/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt @@ -82,7 +82,7 @@ class MailProviderTest { } @Test - fun `only gmail links two-factor setup help, over https`() { + fun `gmail and icloud link two-factor setup help over https, yahoo does not`() { // Gmail's app-passwords page rejects accounts without 2-Step Verification, so its setup // screen must offer the setup article as a way out (issue #98). val gmailUrl = assertNotNull( @@ -91,9 +91,18 @@ class MailProviderTest { ) assertTrue(gmailUrl.startsWith("https://"), "gmail 2FA help URL must be https") - // Yahoo and iCloud gate nothing on two-factor, so they must not grow the extra link. + // Apple also won't issue an app-specific password until two-factor authentication is on, + // so iCloud needs the same escape hatch — pointed at Apple's dedicated article, not a + // generic Apple ID sign-in page (issue #153). + assertEquals("https://support.apple.com/en-us/102660", MailProvider.ICLOUD.twoFactorHelpUrl) + + // Yahoo gates nothing on two-factor, so it must not grow the extra link. assertNull(MailProvider.YAHOO.twoFactorHelpUrl) - assertNull(MailProvider.ICLOUD.twoFactorHelpUrl) + } + + @Test + fun `icloud app-password help points at Apple's specific instructions, not the generic sign-in page`() { + assertEquals("https://support.apple.com/en-us/102654", MailProvider.ICLOUD.appPasswordHelpUrl) } @Test