Skip to content

Commit 323dbf4

Browse files
authored
Merge pull request #20066 from mozilla/password-fallback-design
feat(settings): create password fallback page for Sync
2 parents 7874216 + 771c48b commit 323dbf4

7 files changed

Lines changed: 334 additions & 1 deletion

File tree

packages/fxa-content-server/server/lib/routes/react-app/content-server-routes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ const FRONTEND_ROUTES = [
7474
'signin_recovery_choice',
7575
'signin_recovery_phone',
7676
'signin_confirmed',
77+
// TODO: FXA-13100 - Uncomment when passkey fallback is fully implemented
78+
// 'signin_passkey_fallback',
7779
'signin_permissions',
7880
'signin_reported',
7981
'signin_unblock',

packages/fxa-settings/src/components/App/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ const SigninBounced = lazy(() => import('../../pages/Signin/SigninBounced'));
105105
const SigninConfirmed = lazy(
106106
() => import('../../pages/Signin/SigninConfirmed')
107107
);
108+
const SigninPasskeyFallback = lazy(
109+
() => import('../../pages/Signin/SigninPasskeyFallback')
110+
);
108111
const SigninRecoveryCodeContainer = lazy(
109112
() => import('../../pages/Signin/SigninRecoveryCode/container')
110113
);
@@ -314,7 +317,8 @@ export const App = ({
314317
recoveryKey: data.account.recoveryKey?.exists ?? false,
315318
hasSecondaryVerifiedEmail:
316319
data.account.emails.length > 1 && data.account.emails[1].verified,
317-
totpActive: (data.account.totp?.exists && data.account.totp?.verified) ?? false,
320+
totpActive:
321+
(data.account.totp?.exists && data.account.totp?.verified) ?? false,
318322
});
319323
}
320324
}, [
@@ -615,6 +619,7 @@ const AuthAndAccountSetupRoutes = ({
615619
path="/signin_confirmed/*"
616620
{...{ isSignedIn, serviceName, integration }}
617621
/>
622+
<SigninPasskeyFallback path="/signin_passkey_fallback/*" />
618623
<SigninRecoveryChoiceContainer
619624
path="/signin_recovery_choice/*"
620625
{...{ integration }}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`SigninPasskeyFallback renders as expected 1`] = `
4+
<div>
5+
<div
6+
class="flex min-h-screen flex-col items-center"
7+
data-testid="app"
8+
>
9+
<div
10+
class="w-full hidden mobileLandscape:block"
11+
id="body-top"
12+
/>
13+
<header
14+
class="w-full px-6 py-4 mobileLandscape:py-6"
15+
>
16+
<a
17+
class="mobileLandscape:inline-block"
18+
data-testid="link-external"
19+
href="https://www.mozilla.org/about/?utm_source=firefox-accounts&utm_medium=Referral"
20+
rel="author"
21+
target="_blank"
22+
>
23+
<img
24+
alt="Mozilla logo"
25+
class="h-auto w-[140px] mx-0"
26+
src="moz-logo-bw-rgb.svg"
27+
/>
28+
<span
29+
class="sr-only"
30+
>
31+
Opens in new window
32+
</span>
33+
</a>
34+
</header>
35+
<main
36+
class="flex mobileLandscape:items-center flex-1"
37+
>
38+
<section>
39+
<div
40+
class="card"
41+
>
42+
<span
43+
data-testid="ftlmsg-mock"
44+
id="signin-passkey-fallback-header"
45+
>
46+
<p
47+
class="text-sm text-grey-500 mb-2"
48+
>
49+
Finish sign in
50+
</p>
51+
</span>
52+
<span
53+
data-testid="ftlmsg-mock"
54+
id="signin-passkey-fallback-heading"
55+
>
56+
<h1
57+
class="card-header mb-4"
58+
>
59+
Enter your password to sync
60+
</h1>
61+
</span>
62+
<span
63+
data-testid="ftlmsg-mock"
64+
id="signin-passkey-fallback-body"
65+
>
66+
<p
67+
class="text-sm mb-6"
68+
>
69+
To keep your data safe, you need to enter your password when you use this passkey.
70+
</p>
71+
</span>
72+
<form>
73+
<span
74+
data-testid="ftlmsg-mock"
75+
id="signin-passkey-fallback-password-label"
76+
>
77+
<div
78+
class="relative"
79+
>
80+
<label
81+
class="flex text-start rounded transition-all duration-100 ease-in-out border relative outline-none border-grey-200 bg-white mb-6"
82+
data-testid="password-input-container"
83+
>
84+
<span
85+
class="block flex-auto"
86+
>
87+
<span
88+
class="px-3 w-full cursor-text absolute ltr:origin-top-left rtl:origin-top-right text-sm transition-all duration-100 ease-in-out truncate font-body text-grey-900 mt-3 pt-px"
89+
data-testid="password-input-label"
90+
>
91+
Password
92+
</span>
93+
<input
94+
autocomplete="off"
95+
class="pb-1 pt-5 px-3 font-body rounded text-start w-[90%]"
96+
data-testid="password-input-field"
97+
name="password"
98+
spellcheck="false"
99+
type="password"
100+
value=""
101+
/>
102+
</span>
103+
</label>
104+
<button
105+
aria-label="Your password is currently hidden."
106+
aria-pressed="false"
107+
class="absolute end-0 inset-y-0 my-auto mx-2 px-2 text-grey-500 box-content focus-visible-default rounded"
108+
data-testid="password-visibility-toggle"
109+
title="Show password"
110+
type="button"
111+
>
112+
<svg
113+
aria-hidden="true"
114+
class="stroke-current"
115+
height="18"
116+
width="24"
117+
>
118+
eye-open.svg
119+
</svg>
120+
</button>
121+
</div>
122+
</span>
123+
<div
124+
class="flex flex-col tablet:flex-row gap-4"
125+
>
126+
<span
127+
data-testid="ftlmsg-mock"
128+
id="signin-passkey-fallback-go-to-settings"
129+
>
130+
<button
131+
class="cta-neutral cta-base-p tablet:flex-1"
132+
data-testid="go-to-settings-button"
133+
type="button"
134+
>
135+
Go to settings
136+
</button>
137+
</span>
138+
<span
139+
data-testid="ftlmsg-mock"
140+
id="signin-passkey-fallback-continue"
141+
>
142+
<button
143+
class="cta-primary cta-base-p tablet:flex-1"
144+
data-testid="continue-button"
145+
type="submit"
146+
>
147+
Continue
148+
</button>
149+
</span>
150+
</div>
151+
</form>
152+
</div>
153+
</section>
154+
</main>
155+
</div>
156+
<div
157+
class="w-full block mobileLandscape:hidden"
158+
id="body-bottom"
159+
/>
160+
</div>
161+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## SigninPasskeyFallback page
2+
## Users who authenticate with a passkey to access Sync must also enter their password.
3+
4+
signin-passkey-fallback-header = Finish sign-in
5+
signin-passkey-fallback-heading = Enter your password to sync
6+
signin-passkey-fallback-body = To keep your data safe, you need to enter your password when you use this passkey.
7+
signin-passkey-fallback-password-label = Password
8+
signin-passkey-fallback-go-to-settings = Go to settings
9+
signin-passkey-fallback-continue = Continue
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import React from 'react';
6+
import SigninPasskeyFallback from '.';
7+
import { LocationProvider } from '@reach/router';
8+
import { Meta } from '@storybook/react';
9+
import { withLocalization } from 'fxa-react/lib/storybooks';
10+
11+
export default {
12+
title: 'Pages/Signin/SigninPasskeyFallback',
13+
component: SigninPasskeyFallback,
14+
decorators: [withLocalization],
15+
} as Meta;
16+
17+
export const Default = () => (
18+
<LocationProvider>
19+
<SigninPasskeyFallback />
20+
</LocationProvider>
21+
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import React from 'react';
6+
import { screen } from '@testing-library/react';
7+
import userEvent from '@testing-library/user-event';
8+
import { renderWithRouter } from '../../../models/mocks';
9+
import SigninPasskeyFallback from '.';
10+
11+
describe('SigninPasskeyFallback', () => {
12+
beforeEach(() => {
13+
jest.clearAllMocks();
14+
});
15+
16+
it('renders as expected', () => {
17+
const { container } = renderWithRouter(<SigninPasskeyFallback />);
18+
expect(container).toMatchSnapshot();
19+
});
20+
21+
it('clears password error text on input change', async () => {
22+
const user = userEvent.setup();
23+
renderWithRouter(<SigninPasskeyFallback />);
24+
const passwordInput = screen.getByLabelText('Password');
25+
26+
await user.type(passwordInput, 'newpassword');
27+
28+
expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument();
29+
});
30+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import React, { useCallback, useState } from 'react';
6+
import { RouteComponentProps } from '@reach/router';
7+
import { useForm } from 'react-hook-form';
8+
import { FtlMsg } from 'fxa-react/lib/utils';
9+
import AppLayout from '../../../components/AppLayout';
10+
import InputPassword from '../../../components/InputPassword';
11+
12+
export type SigninPasskeyFallbackProps = {};
13+
14+
type FormData = {
15+
password: string;
16+
};
17+
18+
export const viewName = 'signin-passkey-fallback';
19+
20+
const SigninPasskeyFallback = (
21+
_props: SigninPasskeyFallbackProps & RouteComponentProps
22+
) => {
23+
// State
24+
const [passwordErrorText, setPasswordErrorText] = useState('');
25+
26+
// Form
27+
const { handleSubmit, register } = useForm<FormData>({
28+
mode: 'onTouched',
29+
criteriaMode: 'all',
30+
defaultValues: { password: '' },
31+
});
32+
33+
// Handlers
34+
const onContinue = useCallback(async (data: FormData) => {
35+
// TODO: FXA-13100 - Hook up password verification when container is added
36+
}, []);
37+
38+
const onGoToSettings = useCallback(() => {
39+
// TODO: FXA-13100 - Hook up navigation when container is added
40+
}, []);
41+
42+
return (
43+
<AppLayout>
44+
<FtlMsg id="signin-passkey-fallback-header">
45+
<p className="text-sm text-grey-500 mb-2">Finish sign in</p>
46+
</FtlMsg>
47+
48+
<FtlMsg id="signin-passkey-fallback-heading">
49+
<h1 className="card-header mb-4">Enter your password to sync</h1>
50+
</FtlMsg>
51+
52+
<FtlMsg id="signin-passkey-fallback-body">
53+
<p className="text-sm mb-6">
54+
To keep your data safe, you need to enter your password when you use
55+
this passkey.
56+
</p>
57+
</FtlMsg>
58+
59+
<form onSubmit={handleSubmit(onContinue)}>
60+
<FtlMsg
61+
id="signin-passkey-fallback-password-label"
62+
attrs={{ label: true }}
63+
>
64+
<InputPassword
65+
name="password"
66+
label="Password"
67+
className="mb-6"
68+
errorText={passwordErrorText}
69+
anchorPosition="start"
70+
autoFocus
71+
onChange={() => setPasswordErrorText('')}
72+
inputRef={register({ required: true })}
73+
prefixDataTestId="password"
74+
/>
75+
</FtlMsg>
76+
77+
{/* TODO: FXA-13100 - Add disabled={isSubmitting} while the password is submitting. */}
78+
<div className="flex flex-col tablet:flex-row gap-4">
79+
<FtlMsg id="signin-passkey-fallback-go-to-settings">
80+
<button
81+
type="button"
82+
className="cta-neutral cta-base-p tablet:flex-1"
83+
onClick={onGoToSettings}
84+
data-testid="go-to-settings-button"
85+
>
86+
Go to settings
87+
</button>
88+
</FtlMsg>
89+
90+
<FtlMsg id="signin-passkey-fallback-continue">
91+
<button
92+
type="submit"
93+
className="cta-primary cta-base-p tablet:flex-1"
94+
data-testid="continue-button"
95+
>
96+
Continue
97+
</button>
98+
</FtlMsg>
99+
</div>
100+
</form>
101+
</AppLayout>
102+
);
103+
};
104+
105+
export default SigninPasskeyFallback;

0 commit comments

Comments
 (0)