diff --git a/packages/functional-tests/tests/misc/relayIntegration.spec.ts b/packages/functional-tests/tests/misc/relayIntegration.spec.ts
index 171975a47d3..12efa9a946c 100644
--- a/packages/functional-tests/tests/misc/relayIntegration.spec.ts
+++ b/packages/functional-tests/tests/misc/relayIntegration.spec.ts
@@ -32,6 +32,10 @@ test.describe('relay integration', () => {
).toBeVisible();
await signup.passwordTextbox.fill(password);
+ // keys_optional can't be sent in Firefox tests yet (FXA-13647), so this
+ // key-deriving desktop signup requires a password and renders the
+ // confirmation field.
+ await signup.verifyPasswordTextbox.fill(password);
await signup.createAccountButton.click();
await page.waitForURL(/confirm_signup_code/);
diff --git a/packages/functional-tests/tests/misc/smartWindowIntegration.spec.ts b/packages/functional-tests/tests/misc/smartWindowIntegration.spec.ts
index 365eecfd19e..f25665ef223 100644
--- a/packages/functional-tests/tests/misc/smartWindowIntegration.spec.ts
+++ b/packages/functional-tests/tests/misc/smartWindowIntegration.spec.ts
@@ -28,6 +28,10 @@ test.describe('smart window integration', () => {
await page.waitForURL(/signup/);
await signup.passwordTextbox.fill(password);
+ // keys_optional can't be sent in Firefox tests yet (FXA-13647), so this
+ // key-deriving desktop signup requires a password and renders the
+ // confirmation field.
+ await signup.verifyPasswordTextbox.fill(password);
await signup.createAccountButton.click();
await page.waitForURL(/confirm_signup_code/);
diff --git a/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.stories.tsx b/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.stories.tsx
index b7497f01b3d..12c51650c50 100644
--- a/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.stories.tsx
+++ b/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.stories.tsx
@@ -27,3 +27,8 @@ export const Default = storyWithProps();
export const WithCms = storyWithProps({
integration: createMockIntegrationWithCms(),
});
+
+export const OtpWithCms = storyWithProps({
+ passwordCreationReason: 'otp',
+ integration: createMockIntegrationWithCms(),
+});
diff --git a/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.test.tsx b/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.test.tsx
index e906f17e8be..bd046e13d12 100644
--- a/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.test.tsx
+++ b/packages/fxa-settings/src/pages/PostVerify/SetPassword/index.test.tsx
@@ -6,7 +6,11 @@ import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localiz
import { createMockIntegration, Subject } from './mocks';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import { MOCK_EMAIL } from '../../mocks';
+import {
+ MOCK_EMAIL,
+ MOCK_CMS_INFO,
+ createMockIntegrationWithCms,
+} from '../../mocks';
import { RelierCmsInfo } from '../../../models';
import GleanMetrics from '../../../lib/glean';
@@ -55,14 +59,14 @@ describe('SetPassword page', () => {
});
it('renders CMS overrides when PostVerifySetPasswordPage is set', () => {
- const cmsInfo = {
- shared: { buttonColor: '#333' },
+ const cmsInfo: RelierCmsInfo = {
+ ...MOCK_CMS_INFO,
PostVerifySetPasswordPage: {
headline: 'Set a sync password',
description: 'Your password encrypts your synced data.',
primaryButtonText: 'Sync now',
},
- } as unknown as RelierCmsInfo;
+ };
renderWithLocalizationProvider(
{cmsDescription}
- ) : ( -- This encrypts your data. It needs to be different from your Google - or Apple account password. -
-{cmsDescription}
+ ) : passwordCreationReason === 'third_party_auth' ? ( ++ This encrypts your data. It needs to be different from your Google + or Apple account password. +
+
diff --git a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.test.tsx b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.test.tsx
index a055eac3d67..11fbb486cee 100644
--- a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.test.tsx
+++ b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.test.tsx
@@ -418,6 +418,31 @@ describe('ConfirmSignupCode page', () => {
});
});
+ it('does not navigate to service_welcome when service=vpn on mobile (page automatically closes on fxaOAuthLogin)', async () => {
+ const integration = createMockOAuthNativeIntegration(
+ false,
+ OAuthNativeServices.Vpn
+ );
+ jest.spyOn(integration, 'isFirefoxMobileClient').mockReturnValue(true);
+ renderWithSession({
+ session,
+ integration,
+ finishOAuthFlowHandler: mockFinishOAuthFlowHandler,
+ });
+ submit();
+
+ await waitFor(() => {
+ expect(fxaOAuthLoginSpy).toHaveBeenCalledWith({
+ action: 'signup',
+ code: MOCK_OAUTH_FLOW_HANDLER_RESPONSE.code,
+ redirect: MOCK_OAUTH_FLOW_HANDLER_RESPONSE.redirect,
+ state: MOCK_OAUTH_FLOW_HANDLER_RESPONSE.state,
+ scope: MOCK_OAUTH_FLOW_HANDLER_RESPONSE.scope,
+ });
+ });
+ expect(mockNavigate).not.toHaveBeenCalled();
+ });
+
it('does not submit on paste when service=sync', async () => {
const user = userEvent.setup();
const integration = createMockOAuthNativeIntegration();
diff --git a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx
index b6d70129f7f..ad524b45e77 100644
--- a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx
+++ b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/index.tsx
@@ -294,9 +294,15 @@ const ConfirmSignupCode = ({
scope,
});
if (integration.isFirefoxClientServiceVpn()) {
- navigate('/post_verify/service_welcome', {
- state: { origin: 'signup' },
- });
+ // Don't navigate mobile users. The client controls the web view
+ // and users will see a "blip" of the service welcome page before
+ // the client closes the view in response to the fxaOAuthLogin web
+ // channel message. See FXA-11944.
+ if (!integration.isFirefoxMobileClient()) {
+ navigate('/post_verify/service_welcome', {
+ state: { origin: 'signup' },
+ });
+ }
} else {
goToSettingsWithAlertSuccess();
}
diff --git a/packages/fxa-settings/src/pages/Signup/container.test.tsx b/packages/fxa-settings/src/pages/Signup/container.test.tsx
index c7b172f1525..2c59b337cf7 100644
--- a/packages/fxa-settings/src/pages/Signup/container.test.tsx
+++ b/packages/fxa-settings/src/pages/Signup/container.test.tsx
@@ -58,6 +58,7 @@ function mockIntegration() {
isSync: () => true,
requiresKeys: () => true,
wantsKeys: () => true,
+ requiresPasswordForLogin: () => true,
isFirefoxClientServiceRelay: () => false,
isFirefoxClientServiceSmartWindow: () => false,
isFirefoxClientServiceVpn: () => false,
diff --git a/packages/fxa-settings/src/pages/Signup/index.test.tsx b/packages/fxa-settings/src/pages/Signup/index.test.tsx
index f676c9430e6..722d4170fbb 100644
--- a/packages/fxa-settings/src/pages/Signup/index.test.tsx
+++ b/packages/fxa-settings/src/pages/Signup/index.test.tsx
@@ -110,9 +110,10 @@ async function submit() {
const user = userEvent.setup();
-// Note: we are removing the password confirmation requirement for signup
-// except for Sync (in in FXA-10467). All tests by default will assume that
-// password confirmation is not present or required except for Sync/Desktop Relay.
+// Password confirmation is required only when the password derives encryption
+// keys: Sync (keys mandatory), and non-Sync Firefox services (Relay/VPN/
+// SmartWindow) when the browser lacks the keys-optional capability
+// (`supportsKeysOptionalLogin` false). Plain web/OAuth-web signups omit it.
describe('Signup page', () => {
beforeEach(() => {
@@ -187,7 +188,7 @@ describe('Signup page', () => {
);
});
- expect(screen.queryByLabelText('Repeat password')).not.toBeInTheDocument();
+ expect(screen.getByLabelText('Repeat password')).toBeVisible();
// newsletters and third party auth should not be displayed
expect(screen.queryAllByRole('checkbox')).toHaveLength(0);
@@ -246,6 +247,42 @@ describe('Signup page', () => {
).toBeInTheDocument();
});
+ it('requires password confirmation for a service when keys-optional login is unsupported', async () => {
+ await act(() => {
+ renderWithLocalizationProvider(
+