Skip to content

Commit 577c9fd

Browse files
committed
cleanup(2fa): Remove feature flags and associated old 2FA flows
Because: * The updated flows are fully rolled out in production * The totp/create endpoint is no longer responsible for creating recovery codes This commit: * Remove updated2faSetupFlow, updatedInlineTotpSetupFlow, updatedInlineRecoverySetupFlow flags * Remove code from endpoint that optionally generated and returned recovery codes * Remove skipRecoveryCodes option * Remove old components * Remove tests for old flows * Remove conditional logic around flows Closes #FXA-12813
1 parent 20c070c commit 577c9fd

43 files changed

Lines changed: 135 additions & 2189 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/functional-tests/tests/oauth/totp.spec.ts

Lines changed: 3 additions & 294 deletions
Original file line numberDiff line numberDiff line change
@@ -74,294 +74,10 @@ test.describe('severity-1 #smoke', () => {
7474
expect(await relier.isLoggedIn()).toBe(true);
7575
});
7676

77-
test('can setup TOTP inline and login', async ({
78-
target,
79-
pages: {
80-
page,
81-
relier,
82-
settings,
83-
signin,
84-
totp,
85-
inlineTotpSetup,
86-
configPage,
87-
},
88-
testAccountTracker,
89-
}) => {
90-
const config = await configPage.getConfig();
91-
test.skip(
92-
!config.featureFlags?.updatedInlineTotpSetupFlow ||
93-
config.featureFlags?.updatedInlineRecoverySetupFlow,
94-
'The updated Inline TOTP setup flow is not enabled'
95-
);
96-
const credentials = await testAccountTracker.signUp();
97-
98-
await relier.goto();
99-
await relier.clickRequire2FA();
100-
await signin.fillOutEmailFirstForm(credentials.email);
101-
await signin.fillOutPasswordForm(credentials.password);
102-
103-
await page.waitForURL(/inline_totp_setup/);
104-
105-
await expect(inlineTotpSetup.introHeading).toBeVisible();
106-
await inlineTotpSetup.continueButton.click();
107-
await expect(totp.setup2faAppHeading).toBeVisible();
108-
await totp.step1CantScanCodeLink.click();
109-
const secret = (await totp.step1ManualCode.innerText())?.replace(
110-
/\s/g,
111-
''
112-
);
113-
const code = await getTotpCode(secret);
114-
await totp.step1AuthenticationCodeTextbox.fill(code);
115-
await totp.step1SubmitButton.click();
116-
117-
await page.waitForURL(/inline_recovery_setup/);
118-
119-
const codesRaw = await signin.page.getByTestId('datablock').innerText();
120-
const recoveryCodes = codesRaw.trim().split(/\s+/);
121-
122-
await signin.page.getByRole('button', { name: 'Continue' }).click();
123-
124-
await expect(
125-
signin.page.getByText('Confirm backup authentication code')
126-
).toBeVisible();
127-
128-
await signin.page
129-
.getByRole('textbox', { name: 'Backup authentication code' })
130-
.fill(recoveryCodes[0]);
131-
132-
await signin.page.getByRole('button', { name: 'Confirm' }).click();
133-
134-
expect(await relier.isLoggedIn()).toBe(true);
135-
136-
await settings.goto();
137-
await settings.disconnectTotp();
138-
});
139-
140-
test('can setup TOTP inline and login (old)', async ({
141-
target,
142-
pages: { page, relier, settings, signin, configPage },
143-
testAccountTracker,
144-
}) => {
145-
const config = await configPage.getConfig();
146-
test.skip(
147-
config.featureFlags?.updatedInlineTotpSetupFlow ||
148-
config.featureFlags?.updatedInlineRecoverySetupFlow,
149-
'The updated Inline TOTP setup flow is enabled, skipping old tests'
150-
);
151-
const credentials = await testAccountTracker.signUp();
152-
153-
await relier.goto();
154-
await relier.clickRequire2FA();
155-
await signin.fillOutEmailFirstForm(credentials.email);
156-
await signin.fillOutPasswordForm(credentials.password);
157-
158-
await page.waitForURL(/inline_totp_setup/);
159-
160-
await expect(
161-
signin.page.getByText('Enable two-step authentication')
162-
).toBeVisible();
163-
164-
await signin.page.getByRole('button', { name: 'Continue' }).click();
165-
166-
await expect(
167-
signin.page.getByText('Scan authentication code')
168-
).toBeVisible();
169-
170-
await signin.page
171-
.getByRole('button', { name: 'Can’t scan code?' })
172-
.click();
173-
174-
await expect(signin.page.getByText('Enter code manually')).toBeVisible();
175-
176-
const secret = (
177-
await signin.page.getByTestId('manual-datablock').innerText()
178-
)?.replace(/\s/g, '');
179-
const code = await getTotpCode(secret);
180-
181-
await signin.page
182-
.getByRole('textbox', { name: 'Authentication code' })
183-
.fill(code);
184-
185-
await signin.page.getByRole('button', { name: 'Ready' }).click();
186-
187-
await page.waitForURL(/inline_recovery_setup/);
188-
189-
const codesRaw = await signin.page.getByTestId('datablock').innerText();
190-
const recoveryCodes = codesRaw.trim().split(/\s+/);
191-
192-
await signin.page.getByRole('button', { name: 'Continue' }).click();
193-
194-
await expect(
195-
signin.page.getByText('Confirm backup authentication code')
196-
).toBeVisible();
197-
198-
await signin.page
199-
.getByRole('textbox', { name: 'Backup authentication code' })
200-
.fill(recoveryCodes[0]);
201-
202-
await signin.page.getByRole('button', { name: 'Confirm' }).click();
203-
204-
expect(await relier.isLoggedIn()).toBe(true);
205-
206-
await settings.goto();
207-
await settings.disconnectTotp();
208-
});
209-
210-
test('can setup TOTP inline with backup codes choice (old setup)', async ({
211-
target,
212-
pages: { page, relier, settings, signin, configPage, totp },
213-
testAccountTracker,
214-
}) => {
215-
const config = await configPage.getConfig();
216-
test.skip(
217-
config.featureFlags?.updatedInlineTotpSetupFlow ||
218-
!config.featureFlags?.updatedInlineRecoverySetupFlow,
219-
'Skip unless old totp setup with new recovery flow'
220-
);
221-
const credentials = await testAccountTracker.signUp();
222-
223-
await relier.goto();
224-
await relier.clickRequire2FA();
225-
await signin.fillOutEmailFirstForm(credentials.email);
226-
await signin.fillOutPasswordForm(credentials.password);
227-
228-
await page.waitForURL(/inline_totp_setup/);
229-
230-
await expect(
231-
signin.page.getByText('Enable two-step authentication')
232-
).toBeVisible();
233-
234-
await signin.page.getByRole('button', { name: 'Continue' }).click();
235-
236-
await expect(
237-
signin.page.getByText('Scan authentication code')
238-
).toBeVisible();
239-
240-
await signin.page
241-
.getByRole('button', { name: 'Can’t scan code?' })
242-
.click();
243-
244-
await expect(signin.page.getByText('Enter code manually')).toBeVisible();
245-
246-
const secret = (
247-
await signin.page.getByTestId('manual-datablock').innerText()
248-
)?.replace(/\s/g, '');
249-
const code = await getTotpCode(secret);
250-
251-
await signin.page
252-
.getByRole('textbox', { name: 'Authentication code' })
253-
.fill(code);
254-
255-
await signin.page.getByRole('button', { name: 'Ready' }).click();
256-
257-
await page.waitForURL(/inline_recovery_setup/);
258-
259-
await totp.chooseBackupCodesOption();
260-
const recoveryCodes = await totp.backupCodesDownloadStep();
261-
await totp.confirmBackupCodeStep(recoveryCodes[0]);
262-
await page.getByRole('button', { name: 'Continue' }).click();
263-
264-
expect(await relier.isLoggedIn()).toBe(true);
265-
266-
await settings.goto();
267-
await settings.disconnectTotp();
268-
});
269-
270-
test('can setup TOTP inline with recovery phone choice (old setup)', async ({
271-
target,
272-
pages: {
273-
page,
274-
relier,
275-
settings,
276-
signin,
277-
configPage,
278-
totp,
279-
recoveryPhone,
280-
},
77+
test('can setup TOTP inline with backup codes choice', async ({
78+
pages: { page, relier, settings, signin, totp, inlineTotpSetup },
28179
testAccountTracker,
28280
}) => {
283-
const config = await configPage.getConfig();
284-
test.skip(
285-
config.featureFlags?.updatedInlineTotpSetupFlow ||
286-
!config.featureFlags?.updatedInlineRecoverySetupFlow,
287-
'Skip unless old totp setup with new recovery flow'
288-
);
289-
const credentials = await testAccountTracker.signUp();
290-
291-
await relier.goto();
292-
await relier.clickRequire2FA();
293-
await signin.fillOutEmailFirstForm(credentials.email);
294-
await signin.fillOutPasswordForm(credentials.password);
295-
296-
await page.waitForURL(/inline_totp_setup/);
297-
298-
await expect(
299-
signin.page.getByText('Enable two-step authentication')
300-
).toBeVisible();
301-
302-
await signin.page.getByRole('button', { name: 'Continue' }).click();
303-
304-
await expect(
305-
signin.page.getByText('Scan authentication code')
306-
).toBeVisible();
307-
308-
await signin.page
309-
.getByRole('button', { name: 'Can’t scan code?' })
310-
.click();
311-
312-
await expect(signin.page.getByText('Enter code manually')).toBeVisible();
313-
314-
const secret = (
315-
await signin.page.getByTestId('manual-datablock').innerText()
316-
)?.replace(/\s/g, '');
317-
const code = await getTotpCode(secret);
318-
319-
await signin.page
320-
.getByRole('textbox', { name: 'Authentication code' })
321-
.fill(code);
322-
323-
await signin.page.getByRole('button', { name: 'Ready' }).click();
324-
325-
await page.waitForURL(/inline_recovery_setup/);
326-
327-
await totp.chooseRecoveryPhoneOption();
328-
await recoveryPhone.enterPhoneNumber(target.smsClient.getPhoneNumber());
329-
await recoveryPhone.clickSendCode();
330-
331-
await expect(recoveryPhone.confirmHeader).toBeVisible();
332-
333-
const smsCode = await target.smsClient.getCode({ ...credentials });
334-
335-
await recoveryPhone.enterCode(smsCode);
336-
await recoveryPhone.clickConfirm();
337-
338-
await page.getByRole('button', { name: 'Continue' }).click();
339-
340-
expect(await relier.isLoggedIn()).toBe(true);
341-
342-
await settings.goto();
343-
await settings.disconnectTotp();
344-
});
345-
346-
test('can setup TOTP inline with backup codes choice (new setup)', async ({
347-
target,
348-
pages: {
349-
page,
350-
relier,
351-
settings,
352-
signin,
353-
configPage,
354-
totp,
355-
inlineTotpSetup,
356-
},
357-
testAccountTracker,
358-
}) => {
359-
const config = await configPage.getConfig();
360-
test.skip(
361-
!config.featureFlags?.updatedInlineTotpSetupFlow ||
362-
!config.featureFlags?.updatedInlineRecoverySetupFlow,
363-
'Skip unless new totp setup with new recovery flow'
364-
);
36581
const credentials = await testAccountTracker.signUp();
36682

36783
await relier.goto();
@@ -396,26 +112,19 @@ test.describe('severity-1 #smoke', () => {
396112
await settings.disconnectTotp();
397113
});
398114

399-
test('can setup TOTP inline with recovery phone choice (new setup)', async ({
115+
test('can setup TOTP inline with recovery phone choice', async ({
400116
target,
401117
pages: {
402118
page,
403119
relier,
404120
settings,
405121
signin,
406-
configPage,
407122
totp,
408123
recoveryPhone,
409124
inlineTotpSetup,
410125
},
411126
testAccountTracker,
412127
}) => {
413-
const config = await configPage.getConfig();
414-
test.skip(
415-
!config.featureFlags?.updatedInlineTotpSetupFlow ||
416-
!config.featureFlags?.updatedInlineRecoverySetupFlow,
417-
'Skip unless new totp setup with new recovery flow'
418-
);
419128
const credentials = await testAccountTracker.signUp();
420129

421130
await relier.goto();

packages/fxa-auth-client/lib/client.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,13 +1744,11 @@ export default class AuthClient {
17441744
sessionToken: hexstring,
17451745
options: {
17461746
metricsContext?: MetricsContext;
1747-
skipRecoveryCodes?: boolean;
17481747
},
17491748
headers?: Headers
17501749
): Promise<{
17511750
qrCodeUrl: string;
17521751
secret: string;
1753-
recoveryCodes: string[];
17541752
}> {
17551753
return this.sessionPost('/totp/create', sessionToken, options, headers);
17561754
}
@@ -1790,7 +1788,7 @@ export default class AuthClient {
17901788
* @param sessionToken - required, must be a verified session token
17911789
* @param options - Optional request options such as metrics context
17921790
* @param headers - Optional additional headers for the request
1793-
* @returns A promise resolving to the QR code URL, secret, and optional recovery codes
1791+
* @returns A promise resolving to the QR code URL and secret
17941792
*/
17951793
async startReplaceTotpToken(
17961794
sessionToken: hexstring,
@@ -1801,7 +1799,6 @@ export default class AuthClient {
18011799
): Promise<{
18021800
qrCodeUrl: string;
18031801
secret: string;
1804-
recoveryCodes: string[];
18051802
}> {
18061803
return this.sessionPost(
18071804
'/totp/replace/start',
@@ -1842,7 +1839,7 @@ export default class AuthClient {
18421839
* @param jwt - MFA access token (must include the `mfa:2fa` scope)
18431840
* @param options - Optional request options such as metrics context
18441841
* @param headers - Optional additional headers for the request
1845-
* @returns A promise resolving to the QR code URL, secret, and optional recovery codes
1842+
* @returns A promise resolving to the QR code URL and secret
18461843
*/
18471844
async startReplaceTotpTokenWithJwt(
18481845
jwt: string,
@@ -1853,7 +1850,6 @@ export default class AuthClient {
18531850
): Promise<{
18541851
qrCodeUrl: string;
18551852
secret: string;
1856-
recoveryCodes: string[];
18571853
}> {
18581854
return this.jwtPost('/mfa/totp/replace/start', jwt, options, headers);
18591855
}

0 commit comments

Comments
 (0)