diff --git a/packages/fxa-graphql-api/src/gql/dto/payload/password-forgot.ts b/packages/fxa-graphql-api/src/gql/dto/payload/password-forgot.ts
index fbaf56333b8..44b47c64a5c 100644
--- a/packages/fxa-graphql-api/src/gql/dto/payload/password-forgot.ts
+++ b/packages/fxa-graphql-api/src/gql/dto/payload/password-forgot.ts
@@ -64,4 +64,7 @@ export class AccountResetPayload {
@Field({ nullable: true })
public keyFetchToken?: string;
+
+ @Field({ nullable: true })
+ public unwrapBKey?: string;
}
diff --git a/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.test.tsx b/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.test.tsx
index 416acdad2ff..54757183785 100644
--- a/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.test.tsx
+++ b/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.test.tsx
@@ -3,11 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from 'react';
-import { LocationProvider } from '@reach/router';
-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { fireEvent, screen, waitFor } from '@testing-library/react';
import { LinkExpiredResetPassword } from '.';
-import { mockAppContext, MOCK_ACCOUNT } from '../../models/mocks';
-import { Account, AppContext } from '../../models';
+import {
+ mockAppContext,
+ MOCK_ACCOUNT,
+ createHistoryWithQuery,
+ renderWithRouter,
+ createAppContext,
+} from '../../models/mocks';
+import { Account } from '../../models';
import { FIREFOX_NOREPLY_EMAIL } from 'fxa-settings/src/constants';
const viewName = 'example-view-name';
@@ -17,25 +22,31 @@ jest.mock('@reach/router', () => ({
...jest.requireActual('@reach/router'),
}));
-function renderLinkExpiredResetPasswordWithAccount(account: Account) {
- render(
-
-
-
-
-
+const route = '/bloop';
+const renderWithHistory = (ui: any, queryParams = '', account?: Account) => {
+ const history = createHistoryWithQuery(route, queryParams);
+ return renderWithRouter(
+ ui,
+ {
+ route,
+ history,
+ },
+ mockAppContext({
+ ...createAppContext(history),
+ ...(account && { account }),
+ })
);
-}
+};
describe('LinkExpiredResetPassword', () => {
- const account = {} as unknown as Account;
+ const component = ;
afterEach(() => {
jest.clearAllMocks();
});
it('renders the component as expected for an expired Reset Password link', () => {
- renderLinkExpiredResetPasswordWithAccount(account);
+ renderWithHistory(component);
screen.getByRole('heading', {
name: 'Reset password link expired',
@@ -47,10 +58,10 @@ describe('LinkExpiredResetPassword', () => {
});
it('displays a success banner when clicking on "receive a new link" is successful', async () => {
const account = {
- resendResetPassword: jest.fn().mockResolvedValue(true),
+ resetPassword: jest.fn().mockResolvedValue(true),
} as unknown as Account;
- renderLinkExpiredResetPasswordWithAccount(account);
+ renderWithHistory(component, '', account);
const receiveNewLinkButton = screen.getByRole('button', {
name: 'Receive new link',
});
@@ -65,10 +76,10 @@ describe('LinkExpiredResetPassword', () => {
});
it('displays an error banner when clicking on "receive a new link" is unsuccessful', async () => {
const account = {
- resendResetPassword: jest.fn().mockRejectedValue('error'),
+ resetPassword: jest.fn().mockRejectedValue('error'),
} as unknown as Account;
- renderLinkExpiredResetPasswordWithAccount(account);
+ renderWithHistory(component, '', account);
const receiveNewLinkButton = screen.getByRole('button', {
name: 'Receive new link',
});
diff --git a/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.tsx b/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.tsx
index f6abdc51409..ab95d12b77c 100644
--- a/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.tsx
+++ b/packages/fxa-settings/src/components/LinkExpiredResetPassword/index.tsx
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import React, { useState } from 'react';
-import { useAccount } from '../../models';
+import { CreateRelier, useAccount } from '../../models';
import { ResendStatus } from '../../lib/types';
import { logViewEvent } from 'fxa-settings/src/lib/metrics';
import { REACT_ENTRYPOINT } from 'fxa-settings/src/constants';
@@ -20,6 +20,8 @@ export const LinkExpiredResetPassword = ({
}: LinkExpiredResetPasswordProps) => {
// TODO in FXA-7630 add metrics event and associated tests for users hitting the LinkExpired page
const account = useAccount();
+ const relier = CreateRelier();
+ const serviceName = relier.getServiceName();
const [resendStatus, setResendStatus] = useState(
ResendStatus['not sent']
@@ -27,7 +29,7 @@ export const LinkExpiredResetPassword = ({
const resendResetPasswordLink = async () => {
try {
- await account.resendResetPassword(email);
+ await account.resetPassword(email, serviceName);
logViewEvent(viewName, 'resend', REACT_ENTRYPOINT);
setResendStatus(ResendStatus['sent']);
} catch (e) {
diff --git a/packages/fxa-settings/src/lib/channels/helpers.ts b/packages/fxa-settings/src/lib/channels/helpers.ts
index 54d35897c05..d2bdd678bf9 100644
--- a/packages/fxa-settings/src/lib/channels/helpers.ts
+++ b/packages/fxa-settings/src/lib/channels/helpers.ts
@@ -2,93 +2,49 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-import { AccountData } from '../../models';
+import firefox, { FxALoginRequest } from './firefox';
-// const ALLOWED_LOGIN_FIELDS = [
-// 'declinedSyncEngines',
-// 'email',
-// 'keyFetchToken',
-// 'offeredSyncEngines',
-// 'sessionToken',
-// 'services',
-// 'uid',
-// 'unwrapBKey',
-// 'verified',
-// ];
-
-// const REQUIRED_LOGIN_FIELDS = [
-// 'email',
-// 'keyFetchToken',
-// 'sessionToken',
-// 'uid',
-// 'unwrapBKey',
-// 'verified',
-// ];
-
-// Might need to go on the Integration?
+// TODO: might need to go on Integration?
// let uidOfLoginNotification: hexstring = '';
-// function hasRequiredLoginFields(loginData) {
-// const loginFields = Object.keys(loginData);
-// return (
-// REQUIRED_LOGIN_FIELDS.filter((field) => !loginFields.includes(field))
-// .length === 0
-// );
-// }
-
-/**
- * Get login data from `account` to send to the browser.
- * All returned keys have a defined value.
- */
-// function getLoginData(account: AccountData) {
-// let loginData: Partial = {};
-// for (const key of ALLOWED_LOGIN_FIELDS) {
-// loginData[key] = account[key];
-// }
-// // TODO: account for multiservice when we combine reliers
-// // const isMultiService = this.relier && this.relier.get('multiService');
-// // if (isMultiService) {
-// // loginData = this._formatForMultiServiceBrowser(loginData);
-// // }
-// loginData.verified = !!loginData.verified;
-// // TODO: this is set in the `beforeSignIn` auth-broker method
-// // loginData.verifiedCanLinkAccount = !!this._verifiedCanLinkEmail;
-// return Object.fromEntries(
-// Object.entries(loginData).filter(([key, value]) => value !== undefined)
-// );
-// }
+const REQUIRED_LOGIN_FIELDS: Array = [
+ 'authAt',
+ 'email',
+ 'keyFetchToken',
+ 'sessionToken',
+ 'uid',
+ 'unwrapBKey',
+ 'verified',
+];
-// TODO in FXA-7172
export function notifyFirefoxOfLogin(
- account: AccountData,
+ accountData: FxALoginRequest,
isSessionVerified: boolean
) {
- // only notify the browser of the login if the user does not have
- // to verify their account/session
- if (!isSessionVerified) {
- return;
- }
-
- /**
- * Workaround for #3078. If the user signs up but does not verify
- * their account, then visit `/` or `/settings`, they are
+ /* Only notify the browser of the login if the user 1) does not have to
+ * verify their account/session and 2) when all required fields are present,
+ * which is a workaround for #3078. If the user signs up but does not
+ * verify their account, then visit `/` or `/settings`, they are
* redirected to `/confirm` which attempts to notify the browser of
* login. Since `unwrapBKey` and `keyFetchToken` are not persisted to
* disk, the passed in account lacks these items. The browser can't
- * do anything without this data, so don't actually send the message.
- *
- * Also works around #3514. With e10s enabled, localStorage in
- * about:accounts and localStorage in the verification page are not
- * shared. This lack of shared state causes the original tab of
- * a password reset from about:accounts to not have all the
- * required data. The verification tab sends a WebChannel message
- * already, so no need here too.
- */
- // const loginData = getLoginData(account);
- // if (!hasRequiredLoginFields(loginData)) {
- // return;
+ * do anything without this data, so don't actually send the message. */
+
+ if (
+ !isSessionVerified ||
+ !accountData.verified ||
+ !REQUIRED_LOGIN_FIELDS.every((key) => key in accountData)
+ ) {
+ return;
+ }
+
+ // TODO: account for multiservice TODO with relier/integration combination
+ // const isMultiService = this.relier && this.relier.get('multiService');
+ // if (isMultiService) {
+ // loginData = this._formatForMultiServiceBrowser(loginData);
// }
+ // TODO with relier/integration combination or during login tickets.
// Only send one login notification per uid to avoid race
// conditions within the browser. Two attempts to send
// a login message occur for users that verify while
@@ -98,6 +54,5 @@ export function notifyFirefoxOfLogin(
// if (loginData.uid !== uidOfLoginNotification) {
// uidOfLoginNotification = loginData.uid;
- // send web channel LOGIN command with loginData
- // }
+ firefox.fxaLogin(accountData);
}
diff --git a/packages/fxa-settings/src/models/Account.ts b/packages/fxa-settings/src/models/Account.ts
index 08c4a6ef5fc..c5e258a4d1e 100644
--- a/packages/fxa-settings/src/models/Account.ts
+++ b/packages/fxa-settings/src/models/Account.ts
@@ -17,6 +17,8 @@ import Storage from '../lib/storage';
import random from '../lib/random';
import { AuthUiErrorNos, AuthUiErrors } from '../lib/auth-errors/auth-errors';
import { GET_SESSION_VERIFIED } from './Session';
+import { MozServices } from '../lib/types';
+import { CreateRelier } from './reliers';
export interface DeviceLocation {
city: string | null;
@@ -538,7 +540,10 @@ export class Account implements AccountData {
});
}
- async resetPassword(email: string): Promise {
+ async resetPassword(
+ email: string,
+ service?: string
+ ): Promise {
try {
const result = await this.apolloClient.mutate({
mutation: gql`
@@ -550,7 +555,17 @@ export class Account implements AccountData {
}
}
`,
- variables: { input: { email } },
+ variables: {
+ input: {
+ email,
+ // Only include the `service` option if the service is Sync.
+ // This becomes a query param (service=sync) on the email link.
+ // We need to modify this in FXA-7657 to send the `client_id` param
+ // when we work on the OAuth flow.
+ ...(service &&
+ service === MozServices.FirefoxSync && { service: 'sync' }),
+ },
+ },
});
return result.data.passwordForgotSendCode;
} catch (err) {
@@ -614,51 +629,6 @@ export class Account implements AccountData {
}
}
- async resendResetPassword(
- email: string
- ): Promise {
- try {
- const result = await this.apolloClient.mutate({
- mutation: gql`
- mutation passwordForgotSendCode(
- $input: PasswordForgotSendCodeInput!
- ) {
- passwordForgotSendCode(input: $input) {
- clientMutationId
- passwordForgotToken
- }
- }
- `,
- variables: { input: { email } },
- });
- return result.data.passwordForgotSendCode;
- } catch (err) {
- const graphQlError = ((err as ApolloError) || (err as ThrottledError))
- .graphQLErrors[0];
- const errno = graphQlError.extensions?.errno;
- if (
- (err as ThrottledError) &&
- errno &&
- AuthUiErrorNos[errno] &&
- errno === AuthUiErrors.THROTTLED.errno
- ) {
- const throttledErrorWithRetryAfter = {
- ...AuthUiErrorNos[errno],
- retryAfter: graphQlError.extensions?.retryAfter,
- retryAfterLocalized: graphQlError.extensions?.retryAfterLocalized,
- };
- throw throttledErrorWithRetryAfter;
- } else if (
- errno &&
- AuthUiErrorNos[errno] &&
- errno !== AuthUiErrors.THROTTLED.errno
- ) {
- throw AuthUiErrorNos[errno];
- }
- throw AuthUiErrors.UNEXPECTED_ERROR;
- }
- }
-
/**
* Verify a passwordForgotToken, which returns an accountResetToken that can
* be used to perform the actual password reset.
@@ -721,6 +691,10 @@ export class Account implements AccountData {
clientMutationId
sessionToken
uid
+ authAt
+ keyFetchToken
+ verified
+ unwrapBKey
}
}
`,
@@ -729,12 +703,13 @@ export class Account implements AccountData {
accountResetToken,
email,
newPassword,
- options: { sessionToken: true },
+ options: { sessionToken: true, keys: true },
},
},
});
currentAccount(getOldSettingsData(accountReset));
sessionToken(accountReset.sessionToken);
+ return accountReset;
} catch (err) {
const errno = (err as ApolloError).graphQLErrors[0].extensions?.errno;
if (errno && AuthUiErrorNos[errno]) {
@@ -1280,7 +1255,7 @@ export class Account implements AccountData {
opts.password,
opts.recoveryKeyId,
{ kB: opts.kB },
- { sessionToken: true }
+ { sessionToken: true, keys: true }
);
currentAccount(currentAccount(getOldSettingsData(data)));
sessionToken(data.sessionToken);
@@ -1293,5 +1268,6 @@ export class Account implements AccountData {
},
},
});
+ return data;
}
}
diff --git a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/index.test.tsx b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/index.test.tsx
index a33b8197c2d..2fe440895b3 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/index.test.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/index.test.tsx
@@ -10,7 +10,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { logPageViewEvent, logViewEvent } from '../../../lib/metrics';
import { viewName } from '.';
import {
- Subject,
MOCK_RECOVERY_KEY,
MOCK_RESET_TOKEN,
MOCK_RECOVERY_KEY_ID,
@@ -19,6 +18,7 @@ import {
paramsWithMissingToken,
paramsWithMissingCode,
paramsWithMissingEmail,
+ renderSubject,
} from './mocks';
import { REACT_ENTRYPOINT } from '../../../constants';
import { Account } from '../../../models';
@@ -72,11 +72,11 @@ const accountWithValidResetToken = {
.mockResolvedValue({ accountResetToken: MOCK_RESET_TOKEN }),
} as unknown as Account;
-const renderSubject = ({
+const renderSubjectWithDefaults = ({
account = accountWithValidResetToken,
params = mockCompleteResetPasswordParams,
} = {}) => {
- render();
+ renderSubject(account, params);
};
describe('PageAccountRecoveryConfirmKey', () => {
@@ -88,7 +88,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
// });
it('renders as expected when the link is valid', async () => {
- renderSubject();
+ renderSubjectWithDefaults();
// testAllL10n(screen, bundle);
await screen.findByRole('heading', {
@@ -114,7 +114,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
throw AuthUiErrors.INVALID_TOKEN;
}),
} as unknown as Account;
- renderSubject({ account: accountWithTokenError });
+ renderSubjectWithDefaults({ account: accountWithTokenError });
await screen.findByRole('heading', {
name: 'Reset password link expired',
@@ -123,7 +123,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
describe('renders the component as expected when provided with a damaged link', () => {
it('with missing token', async () => {
- renderSubject({ params: paramsWithMissingToken });
+ renderSubjectWithDefaults({ params: paramsWithMissingToken });
await screen.findByRole('heading', {
name: 'Reset password link damaged',
@@ -133,14 +133,14 @@ describe('PageAccountRecoveryConfirmKey', () => {
);
});
it('with missing code', async () => {
- renderSubject({ params: paramsWithMissingCode });
+ renderSubjectWithDefaults({ params: paramsWithMissingCode });
await screen.findByRole('heading', {
name: 'Reset password link damaged',
});
});
it('with missing email', async () => {
- renderSubject({ params: paramsWithMissingEmail });
+ renderSubjectWithDefaults({ params: paramsWithMissingEmail });
await screen.findByRole('heading', {
name: 'Reset password link damaged',
@@ -151,7 +151,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
describe('submit', () => {
describe('displays error and does not allow submission', () => {
it('with an empty recovery key', async () => {
- renderSubject();
+ renderSubjectWithDefaults();
fireEvent.click(
await screen.findByRole('button', {
name: 'Confirm account recovery key',
@@ -170,7 +170,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
});
it('with less than 32 characters', async () => {
- renderSubject();
+ renderSubjectWithDefaults();
const submitButton = await screen.findByRole('button', {
name: 'Confirm account recovery key',
});
@@ -191,7 +191,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
});
it('with more than 32 characters', async () => {
- renderSubject({ account: accountWithValidResetToken });
+ renderSubjectWithDefaults({ account: accountWithValidResetToken });
const submitButton = await screen.findByRole('button', {
name: 'Confirm account recovery key',
});
@@ -206,7 +206,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
});
it('with invalid Crockford base32', async () => {
- renderSubject();
+ renderSubjectWithDefaults();
const submitButton = await screen.findByRole('button', {
name: 'Confirm account recovery key',
});
@@ -222,7 +222,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
});
it('submits successfully with spaces in recovery key', async () => {
- renderSubject();
+ renderSubjectWithDefaults();
const submitButton = await screen.findByRole('button', {
name: 'Confirm account recovery key',
});
@@ -270,7 +270,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
}),
} as unknown as Account;
- renderSubject({ account: accountWithKeyInvalidOnce });
+ renderSubjectWithDefaults({ account: accountWithKeyInvalidOnce });
await screen.findByRole('heading', {
level: 1,
name: 'Reset password with account recovery key to continue to account settings',
@@ -313,7 +313,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
describe('emits metrics events', () => {
afterEach(() => jest.clearAllMocks());
it('on engage, submit, success', async () => {
- renderSubject();
+ renderSubjectWithDefaults();
const submitButton = await screen.findByRole('button', {
name: 'Confirm account recovery key',
});
@@ -350,7 +350,7 @@ describe('PageAccountRecoveryConfirmKey', () => {
resetPasswordStatus: jest.fn().mockResolvedValue(true),
getRecoveryKeyBundle: jest.fn().mockRejectedValue(new Error('Boop')),
} as unknown as Account;
- renderSubject({ account: accountWithInvalidKey });
+ renderSubjectWithDefaults({ account: accountWithInvalidKey });
const submitButton = await screen.findByRole('button', {
name: 'Confirm account recovery key',
diff --git a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/mocks.tsx b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/mocks.tsx
index 0a1527e5c1c..82a30dc8ecc 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/mocks.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryConfirmKey/mocks.tsx
@@ -6,7 +6,13 @@ import React from 'react';
import { MozServices } from '../../../lib/types';
import { LocationProvider } from '@reach/router';
import { Account, AppContext } from '../../../models';
-import { mockAppContext, MOCK_ACCOUNT } from '../../../models/mocks';
+import {
+ mockAppContext,
+ MOCK_ACCOUNT,
+ createHistoryWithQuery,
+ renderWithRouter,
+ createAppContext,
+} from '../../../models/mocks';
import { LinkType } from 'fxa-settings/src/lib/types';
import LinkValidator from '../../../components/LinkValidator';
@@ -77,38 +83,37 @@ class StorageDataMock extends StorageData {
}
}
-export const Subject = ({
- account,
- params,
-}: {
- account: Account;
- params: Record;
-}) => {
+const route = '/account_recovery_confirm_key';
+export const renderSubject = (
+ account: Account,
+ params?: Record
+) => {
const windowWrapper = new ReachRouterWindow();
const urlQueryData = mockUrlQueryData(params);
-
- return (
- {
+ return new CompleteResetPasswordLink(urlQueryData);
+ }}
>
-
- {
- return new CompleteResetPasswordLink(urlQueryData);
- }}
- >
- {({ setLinkStatus, params }) => (
-
- )}
-
-
-
+ {({ setLinkStatus, params }) => (
+
+ )}
+ ,
+ {
+ route,
+ history,
+ },
+ mockAppContext({
+ ...createAppContext(history),
+ ...(account && { account }),
+ storageData: new StorageDataMock(windowWrapper),
+ })
);
};
diff --git a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.test.tsx b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.test.tsx
index 84222d2c5cd..947756f7561 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.test.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.test.tsx
@@ -104,6 +104,22 @@ describe('AccountRecoveryResetPassword page', () => {
jest.restoreAllMocks();
}
+ // const route = '/confirm_reset_password';
+ // const renderWithHistory = (ui: any, queryParams = '', account?: Account) => {
+ // const history = createHistoryWithQuery(route, queryParams);
+ // return renderWithRouter(
+ // ui,
+ // {
+ // route,
+ // history,
+ // },
+ // mockAppContext({
+ // ...createAppContext(history),
+ // ...(account && { account }),
+ // })
+ // );
+ // };
+
async function renderPage() {
render(
@@ -248,7 +264,9 @@ describe('AccountRecoveryResetPassword page', () => {
describe('successful reset', () => {
beforeEach(async () => {
mockAccount.setLastLogin = jest.fn();
- mockAccount.resetPasswordWithRecoveryKey = jest.fn();
+ mockAccount.resetPasswordWithRecoveryKey = jest
+ .fn()
+ .mockResolvedValue(mocks.MOCK_RESET_DATA);
mockAccount.isSessionVerifiedAuthClient = jest.fn();
mockAccount.hasTotpAuthClient = jest.fn().mockResolvedValue(false);
@@ -327,7 +345,9 @@ describe('AccountRecoveryResetPassword page', () => {
beforeEach(async () => {
window.location.href = originalWindow.href;
mockAccount.setLastLogin = jest.fn();
- mockAccount.resetPasswordWithRecoveryKey = jest.fn();
+ mockAccount.resetPasswordWithRecoveryKey = jest
+ .fn()
+ .mockResolvedValue(mocks.MOCK_RESET_DATA);
mockAccount.isSessionVerifiedAuthClient = jest.fn();
mockAccount.hasTotpAuthClient = jest.fn().mockResolvedValue(true);
await renderPage();
diff --git a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.tsx b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.tsx
index 861bafb4609..653378d3e50 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/index.tsx
@@ -252,7 +252,9 @@ const AccountRecoveryResetPassword = ({
verificationInfo.emailToHashWith || verificationInfo.email,
};
- await account.resetPasswordWithRecoveryKey(options);
+ const accountResetData = await account.resetPasswordWithRecoveryKey(
+ options
+ );
// must come after completeResetPassword since that receives the sessionToken
// required for this check
const sessionIsVerified = await account.isSessionVerifiedAuthClient();
@@ -267,8 +269,22 @@ const AccountRecoveryResetPassword = ({
logViewEvent(viewName, 'verification.success');
switch (integration.type) {
+ // NOTE: SyncBasic check is temporary until we implement codes
+ // See https://docs.google.com/document/d/1K4AD69QgfOCZwFLp7rUcMOkOTslbLCh7jjSdR9zpAkk/edit#heading=h.kkt4eylho93t
case IntegrationType.SyncDesktop:
- notifyFirefoxOfLogin(account, sessionIsVerified);
+ case IntegrationType.SyncBasic:
+ notifyFirefoxOfLogin(
+ {
+ authAt: accountResetData.authAt,
+ email: verificationInfo.email,
+ keyFetchToken: accountResetData.keyFetchToken,
+ sessionToken: accountResetData.sessionToken,
+ uid: accountResetData.uid,
+ unwrapBKey: accountResetData.unwrapBKey,
+ verified: accountResetData.verified,
+ },
+ sessionIsVerified
+ );
break;
case IntegrationType.OAuth:
if (
diff --git a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/mocks.tsx b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/mocks.tsx
index 72889999985..838e72a861f 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/mocks.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/AccountRecoveryResetPassword/mocks.tsx
@@ -277,3 +277,10 @@ export function mockContext() {
}
export const MOCK_SERVICE_NAME = MozServices.FirefoxSync;
+export const MOCK_RESET_DATA = {
+ authAt: 12345,
+ keyFetchToken: 'keyFetchToken',
+ sessionToken: 'sessionToken',
+ unwrapBKey: 'unwrapBKey',
+ verified: true,
+};
diff --git a/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.test.tsx b/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.test.tsx
index f95cc6be622..3fb59a74659 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.test.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.test.tsx
@@ -14,12 +14,14 @@ import { Account } from '../../../models';
import { logPageViewEvent } from '../../../lib/metrics';
import { REACT_ENTRYPOINT, SHOW_BALLOON_TIMEOUT } from '../../../constants';
import {
+ MOCK_RESET_DATA,
mockCompleteResetPasswordParams,
paramsWithMissingCode,
paramsWithMissingEmail,
paramsWithMissingEmailToHashWith,
paramsWithMissingToken,
paramsWithSyncDesktop,
+ renderSubject,
Subject,
} from './mocks';
import { notifyFirefoxOfLogin } from '../../../lib/channels/helpers';
@@ -64,6 +66,23 @@ const mockLocation = () => {
};
};
+// // TODO: be consistent
+// const route = '/complete_reset_password';
+// const renderWithHistory = (ui: any, queryParams = '', account?: Account) => {
+// const history = createHistoryWithQuery(route, queryParams);
+// return renderWithRouter(
+// ui,
+// {
+// route,
+// history,
+// },
+// mockAppContext({
+// ...createAppContext(history),
+// ...(account && { account }),
+// })
+// );
+// };
+
jest.mock('../../../lib/channels/helpers', () => {
return {
notifyFirefoxOfLogin: jest.fn(),
@@ -76,9 +95,9 @@ jest.mock('@reach/router', () => ({
useLocation: () => mockLocation(),
}));
-function renderSubject(account: Account, params?: Record) {
- render();
-}
+// function renderSubject(account: Account, params?: Record) {
+// render();
+// }
describe('CompleteResetPassword page', () => {
// TODO: enable l10n tests when they've been updated to handle embedded tags in ftl strings
@@ -92,7 +111,7 @@ describe('CompleteResetPassword page', () => {
account = {
resetPasswordStatus: jest.fn().mockResolvedValue(true),
- completeResetPassword: jest.fn().mockResolvedValue(true),
+ completeResetPassword: jest.fn().mockResolvedValue(MOCK_RESET_DATA),
hasRecoveryKey: jest.fn().mockResolvedValue(false),
hasTotpAuthClient: jest.fn().mockResolvedValue(false),
isSessionVerifiedAuthClient: jest.fn().mockResolvedValue(true),
@@ -140,6 +159,7 @@ describe('CompleteResetPassword page', () => {
it('renders the component as expected when provided with an expired link', async () => {
account = {
+ ...account,
resetPasswordStatus: jest.fn().mockResolvedValue(false),
} as unknown as Account;
@@ -252,7 +272,7 @@ describe('CompleteResetPassword page', () => {
describe('account has recovery key', () => {
const accountWithRecoveryKey = {
resetPasswordStatus: jest.fn().mockResolvedValue(true),
- completeResetPassword: jest.fn().mockResolvedValue(true),
+ completeResetPassword: jest.fn().mockResolvedValue(MOCK_RESET_DATA),
hasRecoveryKey: jest.fn().mockResolvedValue(true),
} as unknown as Account;
diff --git a/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.tsx b/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.tsx
index 4975829e638..a80254c7594 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/index.tsx
@@ -175,7 +175,7 @@ const CompleteResetPassword = ({
// how account password hashing works previously.
const emailToUse = emailToHashWith || email;
- await account.completeResetPassword(
+ const accountResetData = await account.completeResetPassword(
token,
code,
emailToUse,
@@ -196,8 +196,22 @@ const CompleteResetPassword = ({
let hardNavigate = false;
switch (integration.type) {
+ // NOTE: SyncBasic check is temporary until we implement codes
+ // See https://docs.google.com/document/d/1K4AD69QgfOCZwFLp7rUcMOkOTslbLCh7jjSdR9zpAkk/edit#heading=h.kkt4eylho93t
case IntegrationType.SyncDesktop:
- notifyFirefoxOfLogin(account, sessionIsVerified);
+ case IntegrationType.SyncBasic:
+ notifyFirefoxOfLogin(
+ {
+ authAt: accountResetData.authAt,
+ email,
+ keyFetchToken: accountResetData.keyFetchToken,
+ sessionToken: accountResetData.sessionToken,
+ uid: accountResetData.uid,
+ unwrapBKey: accountResetData.unwrapBKey,
+ verified: accountResetData.verified,
+ },
+ sessionIsVerified
+ );
break;
case IntegrationType.OAuth:
if (
diff --git a/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/mocks.tsx b/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/mocks.tsx
index 2af8851cb1e..07999390052 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/mocks.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/CompleteResetPassword/mocks.tsx
@@ -9,7 +9,13 @@ import CompleteResetPassword from '.';
import LinkValidator from '../../../components/LinkValidator';
import { StorageData, UrlQueryData } from '../../../lib/model-data';
import { Account, AppContext } from '../../../models';
-import { mockAppContext, MOCK_ACCOUNT } from '../../../models/mocks';
+import {
+ mockAppContext,
+ MOCK_ACCOUNT,
+ createHistoryWithQuery,
+ renderWithRouter,
+ createAppContext,
+} from '../../../models/mocks';
import { CompleteResetPasswordLink } from '../../../models/reset-password/verification';
import { ReachRouterWindow } from '../../../lib/window';
@@ -49,6 +55,14 @@ export const paramsWithMissingToken = {
token: '',
};
+export const MOCK_RESET_DATA = {
+ authAt: 12345,
+ keyFetchToken: 'keyFetchToken',
+ sessionToken: 'sessionToken',
+ unwrapBKey: 'unwrapBKey',
+ verified: true,
+};
+
export function mockUrlQueryData(
params: Record = mockCompleteResetPasswordParams
) {
@@ -69,38 +83,37 @@ class StorageDataMock extends StorageData {
}
}
-export const Subject = ({
- account,
- params,
-}: {
- account: Account;
- params?: Record;
-}) => {
+const route = '/complete_reset_password';
+export const renderSubject = (
+ account: Account,
+ params?: Record
+) => {
const windowWrapper = new ReachRouterWindow();
const urlQueryData = mockUrlQueryData(params);
-
- return (
- {
+ return new CompleteResetPasswordLink(urlQueryData);
+ }}
>
-
- {
- return new CompleteResetPasswordLink(urlQueryData);
- }}
- >
- {({ setLinkStatus, params }) => (
-
- )}
-
-
-
+ {({ setLinkStatus, params }) => (
+
+ )}
+ ,
+ {
+ route,
+ history,
+ },
+ mockAppContext({
+ ...createAppContext(history),
+ ...(account && { account }),
+ storageData: new StorageDataMock(windowWrapper),
+ })
);
};
diff --git a/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.test.tsx b/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.test.tsx
index f57a9d2cf3a..3017752daf7 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.test.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.test.tsx
@@ -3,16 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import React from 'react';
-import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { fireEvent, screen, waitFor } from '@testing-library/react';
import ConfirmResetPassword, { viewName } from '.';
// import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils';
// import { FluentBundle } from '@fluent/bundle';
import { MOCK_EMAIL, MOCK_PASSWORD_FORGOT_TOKEN } from './mocks';
import { REACT_ENTRYPOINT } from '../../../constants';
-import { LocationProvider } from '@reach/router';
-import { Account, AppContext } from '../../../models';
-import { mockAppContext, MOCK_ACCOUNT } from '../../../models/mocks';
+import { Account } from '../../../models';
+import {
+ mockAppContext,
+ MOCK_ACCOUNT,
+ createAppContext,
+ renderWithRouter,
+ createHistoryWithQuery,
+} from '../../../models/mocks';
import { usePageViewEvent, logViewEvent } from '../../../lib/metrics';
+import { MozServices } from '../../../lib/types';
jest.mock('../../../lib/metrics', () => ({
logViewEvent: jest.fn(),
@@ -21,15 +27,21 @@ jest.mock('../../../lib/metrics', () => ({
const account = MOCK_ACCOUNT as unknown as Account;
-function renderWithAccount(account: Account) {
- render(
-
-
-
-
-
+const route = '/confirm_reset_password';
+const renderWithHistory = (ui: any, queryParams = '', account?: Account) => {
+ const history = createHistoryWithQuery(route, queryParams);
+ return renderWithRouter(
+ ui,
+ {
+ route,
+ history,
+ },
+ mockAppContext({
+ ...createAppContext(history),
+ ...(account && { account }),
+ })
);
-}
+};
const mockNavigate = jest.fn();
jest.mock('@reach/router', () => ({
@@ -53,7 +65,7 @@ describe('ConfirmResetPassword page', () => {
// });
it('renders as expected', () => {
- render();
+ renderWithHistory();
// testAllL10n(screen, bundle);
@@ -72,8 +84,8 @@ describe('ConfirmResetPassword page', () => {
});
it('sends a new email when clicking on resend button', async () => {
- renderWithAccount(account);
- account.resendResetPassword = jest.fn().mockResolvedValue('');
+ renderWithHistory(, '', account);
+ account.resetPassword = jest.fn().mockResolvedValue('');
const resendEmailButton = screen.getByRole('button', {
name: 'Not in inbox or spam folder? Resend',
@@ -81,7 +93,12 @@ describe('ConfirmResetPassword page', () => {
fireEvent.click(resendEmailButton);
- await waitFor(() => expect(account.resendResetPassword).toHaveBeenCalled());
+ await waitFor(() =>
+ expect(account.resetPassword).toHaveBeenCalledWith(
+ MOCK_EMAIL,
+ MozServices.Default
+ )
+ );
expect(logViewEvent).toHaveBeenCalledWith(
'confirm-reset-password',
'resend',
@@ -90,12 +107,12 @@ describe('ConfirmResetPassword page', () => {
});
it('emits the expected metrics on render', async () => {
- render();
+ renderWithHistory();
expect(usePageViewEvent).toHaveBeenCalledWith(viewName, REACT_ENTRYPOINT);
});
it('renders a "Remember your password?" link', () => {
- render();
+ renderWithHistory();
expect(
screen.getByRole('link', { name: 'Remember your password? Sign in' })
).toBeInTheDocument();
diff --git a/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.tsx b/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.tsx
index 45082fe5e6b..390e01f6b85 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/ConfirmResetPassword/index.tsx
@@ -7,7 +7,7 @@ import { RouteComponentProps, useLocation, useNavigate } from '@reach/router';
import { POLLING_INTERVAL_MS, REACT_ENTRYPOINT } from '../../../constants';
import { usePageViewEvent, logViewEvent } from '../../../lib/metrics';
import { ResendStatus } from '../../../lib/types';
-import { useAccount, useInterval } from '../../../models';
+import { CreateRelier, useAccount, useInterval } from '../../../models';
import AppLayout from '../../../components/AppLayout';
import ConfirmWithLink, {
ConfirmWithLinkPageStrings,
@@ -24,6 +24,8 @@ export type ConfirmResetPasswordLocationState = {
const ConfirmResetPassword = (_: RouteComponentProps) => {
usePageViewEvent(viewName, REACT_ENTRYPOINT);
+ const relier = CreateRelier();
+ const serviceName = relier.getServiceName();
const navigate = useNavigate();
let { state } = useLocation();
@@ -70,7 +72,7 @@ const ConfirmResetPassword = (_: RouteComponentProps) => {
const resendEmailHandler = async () => {
try {
- const result = await account.resendResetPassword(email);
+ const result = await account.resetPassword(email, serviceName);
logViewEvent(viewName, 'resend', REACT_ENTRYPOINT);
setCurrentPasswordForgotToken(result.passwordForgotToken);
setResendStatus(ResendStatus['sent']);
diff --git a/packages/fxa-settings/src/pages/ResetPassword/index.test.tsx b/packages/fxa-settings/src/pages/ResetPassword/index.test.tsx
index 2d93d181b3e..3ad49eba485 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/index.test.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/index.test.tsx
@@ -13,7 +13,7 @@ import ResetPassword, { viewName } from '.';
import { REACT_ENTRYPOINT } from '../../constants';
import { MOCK_ACCOUNT, mockAppContext } from '../../models/mocks';
-import { Account, AppContext } from '../../models';
+import { Account } from '../../models';
import { AuthUiErrorNos } from '../../lib/auth-errors/auth-errors';
import { typeByLabelText } from '../../lib/test-utils';
import {
@@ -21,6 +21,7 @@ import {
createHistoryWithQuery,
renderWithRouter,
} from '../../models/mocks';
+import { MozServices } from '../../lib/types';
const mockLogViewEvent = jest.fn();
const mockLogPageViewEvent = jest.fn();
@@ -148,7 +149,8 @@ describe('PageResetPassword', () => {
});
expect(account.resetPassword).toHaveBeenCalledWith(
- MOCK_ACCOUNT.primaryEmail.email
+ MOCK_ACCOUNT.primaryEmail.email,
+ MozServices.Default
);
expect(mockNavigate).toHaveBeenCalledWith(
diff --git a/packages/fxa-settings/src/pages/ResetPassword/index.tsx b/packages/fxa-settings/src/pages/ResetPassword/index.tsx
index 0fb5fc9fc7b..c59ab29f51c 100644
--- a/packages/fxa-settings/src/pages/ResetPassword/index.tsx
+++ b/packages/fxa-settings/src/pages/ResetPassword/index.tsx
@@ -100,7 +100,7 @@ const ResetPassword = ({
async (email: string) => {
try {
clearError();
- const result = await account.resetPassword(email);
+ const result = await account.resetPassword(email, serviceName);
navigateToConfirmPwReset({
passwordForgotToken: result.passwordForgotToken,
email,