Skip to content

refactor(signin): Remove Authorize, split signin into smaller components#20602

Open
LZoog wants to merge 1 commit into
mainfrom
FXA-13660
Open

refactor(signin): Remove Authorize, split signin into smaller components#20602
LZoog wants to merge 1 commit into
mainfrom
FXA-13660

Conversation

@LZoog
Copy link
Copy Markdown
Contributor

@LZoog LZoog commented May 15, 2026

Because:

  • The Authorize state was a near-duplicate of cached signin that we were able to consolidate (FXA-13660)
  • The monolithic Signin component branched internally on several conditions, making routing hard to read and test (FXA-13302)

This commit:

  • Removes the Authorize state; authorization-flow rendering is handled by the cached signin path via the cached page CMS
  • Splits Signin into SigninCached, SigninThirdParty, and a new SigninDecider that owns the routing decision and SESSION_EXPIRED
  • Extracts SigninUserLockup (shared avatar+email block) and useCachedSigninLockup (shared CMS + banner state) so cached and third-party views stay in sync
  • Moves non-routed components under pages/Signin/components/
  • Reorganizes tests: SigninDecider owns routing tests; child render behavior lives in SigninCached/SigninThirdParty test files; Signin/index.test.tsx is scoped to the password flow

closes FXA-13660
closes FXA-13302
closes FXA-13718


Re: 13718, I just got more solid steps to reproduce from Ipsita, I'm pretty sure that I've fixed the issue here since I reproduced on main once and couldn't repro here but going to double check.

This diff probably looks larger than it is since much of this was moving things into other files, but this still required a lot of manual checks. Please feel free to double check me that comments and logic etc. was all preserved. I'm open to naming and directory etc. suggestions. I'm kind of torn on changing up our Signin story placements because I think it kind of works to have them in one file for Product and others trying to see all signin states but you could convince me otherwise or to add states etc.

On removing Authorize, and simplifying as much as I could: here's a Claude response but edited by me to help explain:

PR #20427 added an "Authorize" state to the signin page to support Mobile-initiated authorization flows where the user is already signed into Firefox. That state did three things:

  1. Skipped the password input (Mobile is not yet decoupled from Sync, so capabilities.keys_optional wasn't being sent — withholding it avoided the broader index-page/login UX changes that the capability triggers)
  2. Hid the "Use a different account" link in mobile (see the SigninCached comment around this)
  3. Allowed a separate Strapi AuthorizePage for customizable copy because the user is "Authorizing" a service and not "signing in"

Since then: Product (and me!) determined the Authorize state is confusing — its main state driver was if the user was already signed into Firefox and authorizing/signing into a new browser service, visually it's nearly indistinguishable from cached sign-in, and Product doesn't need to customize its copy differently from cached signin now. Mobile can go ahead and send capabilities.keys_optional after all, on the reasoning that the only Mobile flows that trigger this are the ones where the user sees cached sign-in and therefore can't reach the index page to change accounts (since Sync is not decoupled this would be problematic). With Mobile sending keys_optional, those users see the standard cached-signin view so deleting the Authorize state helps simplify the state.

We are keeping the wantsKeys() → requiresKeys() / wantsKeysIfPasswordEntered() / wantsKeys() split, because it is semantically clearer than the original OAuthNative.wantsKeys() → true, which was not always the case. We had other logic around if the password input was rendered to determine if it actually "required" keys.

Because:
- The Authorize state was a near-duplicate of cached signin that we were able to consolidate (FXA-13660)
- The monolithic Signin component branched internally on several conditions, making routing hard to read and test (FXA-13302)

This commit:
- Removes the Authorize state; authorization-flow rendering is handled by the cached signin path via the cached page CMS
- Splits Signin into SigninCached, SigninThirdParty, and a new SigninDecider that owns the routing decision and SESSION_EXPIRED
- Extracts SigninUserLockup (shared avatar+email block) and useCachedSigninLockup (shared CMS + banner state) so cached and third-party views stay in sync
- Moves non-routed components under pages/Signin/components/
- Reorganizes tests: SigninDecider owns routing tests; child render behavior lives in SigninCached/SigninThirdParty test files; Signin/index.test.tsx is scoped to the password flow

closes FXA-13660
closes FXA-13302
closes FXA-13718
@LZoog LZoog changed the title refactor(signin): remove Authorize and split signin states refactor(signin): Remove Authorize, split signin into smaller components May 15, 2026
@LZoog LZoog marked this pull request as ready for review May 15, 2026 00:10
Copilot AI review requested due to automatic review settings May 15, 2026 00:10
@LZoog LZoog requested a review from a team as a code owner May 15, 2026 00:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the Settings sign-in flow by removing the dedicated “Authorize” state and splitting the previously monolithic Signin route into smaller, more focused components with a new SigninDecider that owns view routing (password vs cached vs third-party) and SESSION_EXPIRED handoff behavior.

Changes:

  • Removed the Authorize CMS page/state and routed authorization-flow rendering through the cached sign-in path.
  • Split the sign-in UI into SigninDecider, SigninCached, SigninThirdParty, and extracted shared UI/state (SigninUserLockup, useCachedSigninLockup).
  • Reorganized/added tests to match the new component boundaries and updated functional coverage around the authorization/cached behavior.

Reviewed changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/fxa-settings/src/pages/Signin/useCachedSigninLockup.ts New shared hook for cached/third-party “lockup” derived state (CMS/layout/banner).
packages/fxa-settings/src/pages/Signin/mocks.tsx Updated test subject to route through SigninDecider and expanded helper prop typing.
packages/fxa-settings/src/pages/Signin/interfaces.ts Split shared props from routed props; introduced SigninCachedProps / SigninThirdPartyProps.
packages/fxa-settings/src/pages/Signin/index.tsx Scoped to password-entry sign-in view; moved cached/authorize logic out.
packages/fxa-settings/src/pages/Signin/index.test.tsx Trimmed to password flow tests; removed cached/authorize routing assertions.
packages/fxa-settings/src/pages/Signin/index.stories.tsx Removed authorization stories; added cached signed-into-Firefox story.
packages/fxa-settings/src/pages/Signin/container.tsx Switched routed component from Signin to SigninDecider.
packages/fxa-settings/src/pages/Signin/container.test.tsx Updated container tests/mocks to assert SigninDecider usage and new integration methods.
packages/fxa-settings/src/pages/Signin/components/SigninUserLockup/index.tsx New shared avatar+email (+ accessibility info) component.
packages/fxa-settings/src/pages/Signin/components/SigninThirdParty/index.tsx New third-party-only sign-in view for linked-passwordless users.
packages/fxa-settings/src/pages/Signin/components/SigninThirdParty/index.test.tsx New unit tests for third-party-only view rendering.
packages/fxa-settings/src/pages/Signin/components/SigninDecider/index.tsx New routing component deciding cached vs password vs third-party; hoists SESSION_EXPIRED behavior.
packages/fxa-settings/src/pages/Signin/components/SigninDecider/index.test.tsx New routing tests (keys_optional, wants keys, SESSION_EXPIRED handoff).
packages/fxa-settings/src/pages/Signin/components/SigninCached/index.tsx New cached sign-in view; owns cached submission and SESSION_EXPIRED callback.
packages/fxa-settings/src/pages/Signin/components/SigninCached/index.test.tsx New unit tests for cached sign-in UI behavior.
packages/fxa-settings/src/models/integrations/relier-interfaces.ts Removed AuthorizePage from Relier CMS info interface.
packages/functional-tests/tests/misc/vpnIntegration.spec.ts Updated VPN integration test to assert account-switch link is hidden in signed-into-Firefox cached view.
libs/shared/cms/src/lib/queries/relying-party/types.ts Removed AuthorizePage from relying-party query result type.
libs/shared/cms/src/lib/queries/relying-party/query.ts Removed AuthorizePage selection from relying-party GraphQL query.
libs/shared/cms/src/lib/queries/relying-party/factories.ts Removed AuthorizePage from relying-party factory data.
libs/shared/cms/src/generated/graphql.ts Regenerated GraphQL output to reflect AuthorizePage removal from query results.
libs/shared/cms/src/generated/gql.ts Regenerated gql document mapping to reflect AuthorizePage removal from query results.
Files not reviewed (2)
  • libs/shared/cms/src/generated/gql.ts: Language not supported
  • libs/shared/cms/src/generated/graphql.ts: Language not supported
Comments suppressed due to low confidence (2)

packages/fxa-settings/src/pages/Signin/components/SigninDecider/index.tsx:92

  • syncNotDecoupledRequiresPassword no longer excludes the signed-into-Firefox (authorization) flow. Without the prior !isSignedIntoFirefox guard, users in an authorization flow on platforms without keys_optional (e.g. older Firefox/mobile) can be forced into the password view even though they’ve already authenticated, reintroducing the behavior the removed Authorize state was covering. Consider including an !isSignedIntoFirefox check (or otherwise special-casing the authorization flow) in this condition.
  // Relay browser service login launched in Firefox desktop 135, and the "keys optional"
  // capability (Sync decoupling) launched in Fx desktop 147, meaning all Relay service users
  // in those Fx versions require a password.
  // This also covers Mobile until Sync has been decoupled.
  const syncNotDecoupledRequiresPassword =
    !useFxAStatusResult.supportsKeysOptionalLogin &&
    integration.wantsKeysIfPasswordEntered();

packages/fxa-settings/src/pages/Signin/components/SigninDecider/index.tsx:49

  • The doc comment says SESSION_EXPIRED is handled by “flipping to cached signin”, but the implementation flips away from cached (it marks the cached session invalid and routes to password/third-party). Update the comment to match the actual behavior to avoid confusion for future maintainers.
/**
 * Decides which of the three signin views the user sees and
 * handles SESSION_EXPIRED by flipping to cached signin.
 */

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +82 to +86
// Hide "Use a different account" when the user is signed into Firefox Desktop.
// Users cannot choose another account due to the inability to merge
// account/sync data (the "merge stop"/warning).
const hideAccountSwitchLink =
isSignedIntoFirefox && integration.isFirefoxDesktopClient();
Comment on lines +49 to +53
// Hide "Use a different account" when the user is signed into Firefox Desktop.
// Users cannot choose another account due to the inability to merge
// account/sync data (the "merge stop"/warning).
const hideAccountSwitchLink =
isSignedIntoFirefox && integration.isFirefoxDesktopClient();
import { UseFxAStatusResult } from '../../../../lib/hooks/useFxAStatus';
import { MozServices } from '../../../../lib/types';
import { useFinishOAuthFlowHandler } from '../../../../lib/oauth/hooks';
import { QueryParams } from '../../../..';
Comment on lines +31 to +35
// If cachedPageCms is the active page but does not have a CMS entry,
// fall back to signinPageCms.splitLayout
const splitLayout = cachedPageCms
? cachedPageCms.splitLayout
: signinPageCms?.splitLayout;
// "Use a different account" link is hidden when signed into Firefox with
// a Firefox client + service requested on cached sign-in
await expect(
page.getByRole('link', { name: /use a different account/i })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Assuming we don't have a state where the wrong cached account might be suggested


screen.getByRole('heading', { name: 'Sign in' });
screen.getByRole('button', { name: 'Sign in' });
expect(screen.queryByLabelText('Password')).not.toBeInTheDocument();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

queryByRole doesn't work for the password input?

error
);
if (error.errno === AuthUiErrors.SESSION_EXPIRED.errno) {
// Container hoists us into the password view and carries this message
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

password view = signin page? This error could also apply to passwordless accounts IIUC


/**
* Decides which of the three signin views the user sees and
* handles SESSION_EXPIRED by flipping to cached signin.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

handles SESSION_EXPIRED by flipping to cached signin --> this seems incorrect?

// service doesn't require keys (Sync always requires them), OR
// - The browser supports "keys optional" (Sync decoupled from other services)
//
// Passwordless users always see cached sign-in and are redirected to set a
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

They only see cached sign-in if there's a cached session, otherwise they might be directed to SigninThirdParty or Passwordless OTP. Does the passwordless OTP redirect happen from the SigninPage? Wondering if that should also be controlled by the decider.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Flagging that from testing Dan is doing on #20575, it looks like passwordless accounts that should go to cached signin may be instead directed to passwordless otp. (If so, this PR would be a good place to fix)

// users will be taken to "Set password" after signing in.
if (hasLinkedAccount && !hasPassword) {
return (
<SigninThirdParty
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will soon also include Signin with passkey option, not just third party auth. In case that informs naming.

localizedBannerError,
} = useCachedSigninLockup({ integration, localizedErrorFromLocationState });

// Hide "Use a different account" when the user is signed into Firefox Desktop.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

They could pick a different account from the Google/Apple auth signin flows, so they could still theoretically choose a different account and get blocked down the road.

isSignedIntoFirefox && integration.isFirefoxDesktopClient();

useEffect(() => {
// NOTE: linked-passwordless users were historically tracked under
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ticket about these metrics being lumped together: https://mozilla-hub.atlassian.net/browse/FXA-13713

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants