feat(password reset): log user in post-password-reset

Because:
 - the user could have a verified session after successfully resetting
   their password for an account

This commit:
 - logs the account in and take the user to /settings if the session is
   verified and the user did not use a recovery key during the reset
This commit is contained in:
Barry Chen 2024-09-30 08:52:59 -07:00
Родитель 5a1ff395a3
Коммит 19fe669101
Не найден ключ, соответствующий данной подписи
10 изменённых файлов: 73 добавлений и 62 удалений

Просмотреть файл

@ -118,8 +118,9 @@ test.describe('severity-2 #smoke', () => {
await page.goto(
`${target.contentServerUrl}/?forceExperiment=generalizedReactApp&forceExperimentGroup=react&${signinVersion.query}`
);
await signin.fillOutEmailFirstForm(email);
await signin.fillOutPasswordForm(password);
// a successful password reset means that the user is signed in
await expect(signin.cachedSigninHeading).toBeVisible();
await signin.signInButton.click();
await expect(page).toHaveURL(/settings/);
const keys2 = await _getKeys(

Просмотреть файл

@ -98,14 +98,6 @@ test.describe('severity-2 #smoke', () => {
await resetPassword.fillOutNewPasswordForm(password);
await expect(page).toHaveURL(/reset_password_verified/);
await page.goto(
`${target.contentServerUrl}/?forceExperiment=generalizedReactApp&forceExperimentGroup=react&${signinVersion.query}`
);
await signin.fillOutEmailFirstForm(email);
await signin.fillOutPasswordForm(password);
await expect(page).toHaveURL(/settings/);
const keys2 = await _getKeys(
signinVersion.version,

Просмотреть файл

@ -43,12 +43,11 @@ test.describe('severity-1 #smoke', () => {
await relier.goto();
await relier.clickEmailFirst();
await signin.fillOutEmailFirstForm(credentials.email);
// old password fails
await signin.fillOutPasswordForm(credentials.password);
await expect(page.getByText('Incorrect password')).toBeVisible();
// new passwowrd works
await signin.fillOutPasswordForm(newPassword);
// a successful password reset means that the user is signed in
await expect(signin.cachedSigninHeading).toBeVisible();
await signin.signInButton.click();
await expect(page).toHaveURL(target.relierUrl);
expect(await relier.isLoggedIn()).toBe(true);

Просмотреть файл

@ -11,7 +11,13 @@ test.describe('severity-1 #smoke', () => {
test.describe('oauth reset password Sync mobile react', () => {
test('reset password through Sync mobile', async ({
target,
syncBrowserPages: { page, connectAnotherDevice, resetPassword, signin },
syncBrowserPages: {
page,
connectAnotherDevice,
resetPassword,
signin,
settings,
},
testAccountTracker,
}) => {
const credentials = await testAccountTracker.signUp();
@ -32,25 +38,10 @@ test.describe('severity-1 #smoke', () => {
await resetPassword.fillOutResetPasswordCodeForm(code);
await resetPassword.fillOutNewPasswordForm(newPassword);
await expect(page).toHaveURL(/reset_password_verified/);
await expect(
resetPassword.passwordResetConfirmationHeading
).toBeVisible();
// TODO in FXA-9561 - Remove this temporary test of sign in with new password
await page.goto(
`${
target.contentServerUrl
}/authorization/?${syncMobileOAuthQueryParams.toString()}`
await expect(settings.settingsHeading).toBeVisible();
await expect(settings.alertBar).toHaveText(
'Your password has been reset'
);
// expect user to be signed in to sync and prompted for cached signin
// old password fails
await signin.fillOutPasswordForm(credentials.password);
await expect(page.getByText('Incorrect password')).toBeVisible();
// new passwowrd works
await signin.fillOutPasswordForm(newPassword);
await expect(connectAnotherDevice.fxaConnected).toBeVisible();
// update password for cleanup function
credentials.password = newPassword;

Просмотреть файл

@ -27,15 +27,6 @@ test.describe('severity-1 #smoke', () => {
// Create and submit new password
await resetPassword.fillOutNewPasswordForm(newPassword);
// Wait for new page to navigate
await expect(page).toHaveURL(/reset_password_verified/);
await page.goto(target.contentServerUrl);
await signin.fillOutEmailFirstForm(credentials.email);
await signin.fillOutPasswordForm(newPassword);
await expect(settings.settingsHeading).toBeVisible();
// Cleanup requires setting this value to correct password

Просмотреть файл

@ -127,11 +127,10 @@ test.describe('severity-1 #smoke', () => {
await expect(resetPassword.dataLossWarning).toBeVisible();
await resetPassword.fillOutNewPasswordForm(newPassword);
await expect(
resetPassword.passwordResetConfirmationHeading
).toBeVisible();
await signinAccount(credentials.email, newPassword, settings, signin);
await expect(settings.settingsHeading).toBeVisible();
await expect(settings.alertBar).toHaveText(
'Your password has been reset'
);
await expect(settings.recoveryKey.status).toHaveText('Not Set');

Просмотреть файл

@ -8,7 +8,7 @@ test.describe('severity-1 #smoke', () => {
test.describe('Firefox Desktop Sync v3 reset password react', () => {
test('reset pw for sync user', async ({
target,
syncBrowserPages: { page, resetPassword },
syncBrowserPages: { page, resetPassword, settings },
testAccountTracker,
}) => {
const credentials = await testAccountTracker.signUp();
@ -27,13 +27,10 @@ test.describe('severity-1 #smoke', () => {
await expect(resetPassword.dataLossWarning).toBeVisible();
await resetPassword.fillOutNewPasswordForm(newPassword);
await expect(page).toHaveURL(/reset_password_verified/);
await expect(
resetPassword.passwordResetConfirmationHeading
).toBeVisible();
// TODO in FXA-9561 - Verify that the service name is displayed in the "Continue to ${serviceName}" button
// This functionality is not yet implemented in the reset password flow
await expect(settings.settingsHeading).toBeVisible();
await expect(settings.alertBar).toHaveText(
'Your password has been reset'
);
// Update credentials file so that account can be deleted as part of test cleanup
credentials.password = newPassword;

Просмотреть файл

@ -95,6 +95,7 @@ export const App = ({
// Determine if user is actually signed in
const [isSignedIn, setIsSignedIn] = useState<boolean | undefined>(undefined);
useEffect(() => {
if (!integration) {
return;

Просмотреть файл

@ -773,6 +773,10 @@ export class Account implements AccountData {
accountReset.unwrapBKeyVersion2 = credentialsV2?.unwrapBKey;
currentAccount(getStoredAccountData(accountReset));
sessionToken(accountReset.sessionToken);
this.apolloClient.cache.writeQuery({
query: GET_LOCAL_SIGNED_IN_STATUS,
data: { isSignedIn: true },
});
return accountReset;
} catch (err) {
throw getHandledError(err);

Просмотреть файл

@ -7,7 +7,9 @@ import { RouteComponentProps, useLocation } from '@reach/router';
import { useValidatedQueryParams } from '../../../lib/hooks/useValidate';
import {
Integration,
isWebIntegration,
useAccount,
useAlertBar,
useConfig,
useFtlMsgResolver,
} from '../../../models';
@ -23,6 +25,8 @@ import firefox from '../../../lib/channels/firefox';
import { useState } from 'react';
import { useNavigateWithQuery } from '../../../lib/hooks/useNavigateWithQuery';
import { getLocalizedErrorMessage } from '../../../lib/error-utils';
import { storeAccountData } from '../../../lib/storage-utils';
import { SETTINGS_PATH } from '../../../constants';
// This component is used for both /complete_reset_password and /account_recovery_reset_password routes
// for easier maintenance
@ -35,6 +39,7 @@ const CompleteResetPasswordContainer = ({
const keyStretchExperiment = useValidatedQueryParams(KeyStretchExperiment);
const account = useAccount();
const alertBar = useAlertBar();
const config = useConfig();
const ftlMsgResolver = useFtlMsgResolver();
const navigate = useNavigateWithQuery();
@ -72,7 +77,22 @@ const CompleteResetPasswordContainer = ({
navigate('/reset_password_with_recovery_key_verified');
};
const handleNavigationWithoutRecoveryKey = () => {
const handleNavigationWithoutRecoveryKey = async (
accountResetData: AccountResetData
) => {
if (
accountResetData.verified &&
(isWebIntegration(integration) || integration.isSync())
) {
alertBar.success(
ftlMsgResolver.getMsg(
'reset-password-complete-header',
'Your password has been reset'
)
);
return navigate(SETTINGS_PATH, { replace: true });
}
navigate('/reset_password_verified', {
replace: true,
});
@ -120,7 +140,17 @@ const CompleteResetPasswordContainer = ({
return accountResetData;
};
const notifyBrowserOfSignin = async (accountResetData: AccountResetData) => {
const notifyClientOfSignin = (accountResetData: AccountResetData) => {
if (accountResetData.verified) {
storeAccountData({
uid: accountResetData.uid,
email,
lastLogin: Date.now(),
sessionToken: accountResetData.sessionToken,
verified: accountResetData.verified,
});
}
if (integration.isSync()) {
firefox.fxaLoginSignedInUser({
authAt: accountResetData.authAt,
@ -156,7 +186,7 @@ const CompleteResetPasswordContainer = ({
recoveryKeyId
);
// TODO add frontend Glean event for successful reset?
notifyBrowserOfSignin(accountResetData);
notifyClientOfSignin(accountResetData);
handleNavigationWithRecoveryKey();
} else if (isResetWithoutRecoveryKey) {
GleanMetrics.passwordReset.createNewSubmit();
@ -167,8 +197,14 @@ const CompleteResetPasswordContainer = ({
token
);
// TODO add frontend Glean event for successful reset?
notifyBrowserOfSignin(accountResetData);
handleNavigationWithoutRecoveryKey();
notifyClientOfSignin(accountResetData);
// DO NOT REMOVE THIS MAKES THE NAVIGATION WORK
// Despite all the awaiting in the code path, the signed in state in
// the apollo client cache does not seem to update before the re-render
// from the navigate call.
await new Promise((resolve) => setTimeout(resolve, 0));
await handleNavigationWithoutRecoveryKey(accountResetData);
}
} catch (err) {
const localizedBannerMessage = getLocalizedErrorMessage(