Skip to content

Commit b343151

Browse files
authored
Merge pull request #20110 from mozilla/fxa-12623
fix(test): Migrate more Mocha integration tests to Jest PART 2
2 parents 2fac797 + aabdc27 commit b343151

13 files changed

Lines changed: 4282 additions & 0 deletions
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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 { createTestServer, TestServerInstance } from '../support/helpers/test-server';
6+
import crypto from 'crypto';
7+
8+
const Client = require('../client')();
9+
const otplib = require('otplib');
10+
const { default: Container } = require('typedi');
11+
const {
12+
PlaySubscriptions,
13+
} = require('../../lib/payments/iap/google-play/subscriptions');
14+
const {
15+
AppStoreSubscriptions,
16+
} = require('../../lib/payments/iap/apple-app-store/subscriptions');
17+
18+
let server: TestServerInstance;
19+
20+
// Ensure tests generate TOTP codes using the same encoding as the server
21+
otplib.authenticator.options = {
22+
crypto: crypto,
23+
encoding: 'hex',
24+
window: 10,
25+
};
26+
27+
beforeAll(async () => {
28+
Container.set(PlaySubscriptions, {});
29+
Container.set(AppStoreSubscriptions, {});
30+
31+
server = await createTestServer({
32+
configOverrides: {
33+
securityHistory: { ipProfiling: {} },
34+
signinConfirmation: { skipForNewAccounts: { enabled: false } },
35+
mfa: {
36+
enabled: true,
37+
actions: ['2fa', 'test'],
38+
},
39+
},
40+
});
41+
}, 120000);
42+
43+
afterAll(async () => {
44+
await server.stop();
45+
});
46+
47+
const testVersions = [
48+
{ version: '', tag: '' },
49+
{ version: 'V2', tag: 'V2' },
50+
];
51+
52+
const password = 'pssssst';
53+
const metricsContext = {
54+
flowBeginTime: Date.now(),
55+
flowId: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
56+
};
57+
58+
describe.each(testVersions)(
59+
'#integration$tag - remote mfa totp',
60+
({ version, tag }) => {
61+
const testOptions = { version };
62+
let mfaEmail: string;
63+
let mfaClient: any;
64+
65+
beforeEach(async () => {
66+
mfaEmail = server.uniqueEmail();
67+
mfaClient = await Client.createAndVerify(
68+
server.publicUrl,
69+
mfaEmail,
70+
password,
71+
server.mailbox,
72+
testOptions
73+
);
74+
});
75+
76+
async function getMfaAccessTokenFor2fa(clientInstance: any) {
77+
// Request an OTP for MFA action '2fa'
78+
await clientInstance.api.doRequest(
79+
'POST',
80+
`${clientInstance.api.baseURL}/mfa/otp/request`,
81+
await clientInstance.api.Token.SessionToken.fromHex(
82+
clientInstance.sessionToken
83+
),
84+
{ action: '2fa' }
85+
);
86+
87+
// Read OTP code from mailbox
88+
const code = await server.mailbox.waitForMfaCode(clientInstance.email);
89+
90+
// Verify OTP and get back a JWT access token
91+
const verifyRes = await clientInstance.api.doRequest(
92+
'POST',
93+
`${clientInstance.api.baseURL}/mfa/otp/verify`,
94+
await clientInstance.api.Token.SessionToken.fromHex(
95+
clientInstance.sessionToken
96+
),
97+
{ action: '2fa', code }
98+
);
99+
return verifyRes.accessToken;
100+
}
101+
102+
async function createSetupCompleteTOTPUsingJwt(
103+
clientInstance: any,
104+
accessToken: string
105+
) {
106+
// Create (start) TOTP via JWT route
107+
const createRes = await clientInstance.api.doRequestWithBearerToken(
108+
'POST',
109+
`${clientInstance.api.baseURL}/mfa/totp/create`,
110+
accessToken,
111+
{ metricsContext }
112+
);
113+
114+
// Verify setup code using the returned secret
115+
const setupAuthenticator = new otplib.authenticator.Authenticator();
116+
setupAuthenticator.options = Object.assign(
117+
{},
118+
otplib.authenticator.options,
119+
{ secret: createRes.secret }
120+
);
121+
const code = setupAuthenticator.generate();
122+
const verifySetupRes = await clientInstance.api.doRequestWithBearerToken(
123+
'POST',
124+
`${clientInstance.api.baseURL}/mfa/totp/setup/verify`,
125+
accessToken,
126+
{ code, metricsContext }
127+
);
128+
129+
// Complete setup
130+
const completeRes = await clientInstance.api.doRequestWithBearerToken(
131+
'POST',
132+
`${clientInstance.api.baseURL}/mfa/totp/setup/complete`,
133+
accessToken,
134+
{ metricsContext }
135+
);
136+
137+
return { createRes, verifySetupRes, completeRes };
138+
}
139+
140+
it('should create/setup/complete TOTP using jwt', async () => {
141+
const accessToken = await getMfaAccessTokenFor2fa(mfaClient);
142+
const { createRes, verifySetupRes, completeRes } =
143+
await createSetupCompleteTOTPUsingJwt(mfaClient, accessToken);
144+
145+
expect(createRes.secret).toBeTruthy();
146+
expect(createRes.qrCodeUrl).toBeTruthy();
147+
expect(verifySetupRes.success).toBe(true);
148+
expect(completeRes.success).toBe(true);
149+
150+
const emailData = await server.mailbox.waitForEmail(mfaEmail);
151+
expect(emailData.headers['x-template-name']).toBe(
152+
'postAddTwoStepAuthentication'
153+
);
154+
});
155+
156+
it('should replace TOTP using jwt', async () => {
157+
const accessToken = await getMfaAccessTokenFor2fa(mfaClient);
158+
const { completeRes } = await createSetupCompleteTOTPUsingJwt(
159+
mfaClient,
160+
accessToken
161+
);
162+
expect(completeRes.success).toBe(true);
163+
164+
const email1 = await server.mailbox.waitForEmail(mfaEmail);
165+
expect(email1.headers['x-template-name']).toBe(
166+
'postAddTwoStepAuthentication'
167+
);
168+
169+
// Start replace
170+
const startRes = await mfaClient.api.doRequestWithBearerToken(
171+
'POST',
172+
`${mfaClient.api.baseURL}/mfa/totp/replace/start`,
173+
accessToken,
174+
{ metricsContext }
175+
);
176+
expect(startRes.secret).toBeTruthy();
177+
expect(startRes.qrCodeUrl).toBeTruthy();
178+
179+
// Confirm replace with valid code
180+
const replaceAuthenticator = new otplib.authenticator.Authenticator();
181+
replaceAuthenticator.options = Object.assign(
182+
{},
183+
otplib.authenticator.options,
184+
{ secret: startRes.secret }
185+
);
186+
const code = replaceAuthenticator.generate();
187+
const confirmRes = await mfaClient.api.doRequestWithBearerToken(
188+
'POST',
189+
`${mfaClient.api.baseURL}/mfa/totp/replace/confirm`,
190+
accessToken,
191+
{ code }
192+
);
193+
expect(confirmRes.success).toBe(true);
194+
195+
const email2 = await server.mailbox.waitForEmail(mfaEmail);
196+
expect(email2.headers['x-template-name']).toBe(
197+
'postChangeTwoStepAuthentication'
198+
);
199+
});
200+
}
201+
);

0 commit comments

Comments
 (0)