Skip to content

Commit 3e1ac5e

Browse files
committed
move all TOTP implementation into core/mfa/assertions.
We do not need to restrict this to platform_browser. SMS mfa is in platform_browser since it requires a recaptcha step.
1 parent b3212b3 commit 3e1ac5e

File tree

4 files changed

+333
-388
lines changed

4 files changed

+333
-388
lines changed

packages/auth/src/mfa/assertions/totp.test.ts

+179-5
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,24 @@ import { expect, use } from 'chai';
1919
import chaiAsPromised from 'chai-as-promised';
2020

2121
import { mockEndpoint } from '../../../test/helpers/api/helper';
22-
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
22+
import { testAuth, TestAuth, testUser } from '../../../test/helpers/mock_auth';
2323
import * as mockFetch from '../../../test/helpers/mock_fetch';
2424
import { Endpoint } from '../../api';
2525
import { MultiFactorSessionImpl } from '../../mfa/mfa_session';
2626
import { StartTotpMfaEnrollmentResponse } from '../../api/account_management/mfa';
27-
import { TotpSecret } from '../../platform_browser/mfa/assertions/totp';
28-
import { TotpMultiFactorGenerator } from './totp';
29-
import { FactorId } from '../../model/public_types';
27+
import { FinalizeMfaResponse } from '../../api/authentication/mfa';
28+
import {
29+
TotpMultiFactorAssertionImpl,
30+
TotpMultiFactorGenerator,
31+
TotpSecret
32+
} from './totp';
33+
import { Auth, FactorId } from '../../model/public_types';
3034
import { AuthErrorCode } from '../../core/errors';
35+
import { FirebaseApp, initializeApp } from '@firebase/app';
36+
import { AppName } from '../../model/auth';
37+
import { getAuth } from '../../platform_node';
38+
import { initializeAuth } from '../../core';
39+
import { _castAuth } from '../../core/auth/auth_impl';
3140

3241
use(chaiAsPromised);
3342

@@ -81,7 +90,7 @@ describe('core/mfa/assertions/totp/TotpMultiFactorGenerator', () => {
8190
'enrollment-id-token',
8291
undefined
8392
);
84-
const totpSecret = await TotpMultiFactorGenerator.generateSecret(
93+
await TotpMultiFactorGenerator.generateSecret(
8594
session
8695
);
8796
} catch (e) {
@@ -119,3 +128,168 @@ describe('core/mfa/assertions/totp/TotpMultiFactorGenerator', () => {
119128
});
120129
});
121130
});
131+
132+
describe('core/mfa/totp/assertions/TotpMultiFactorAssertionImpl', () => {
133+
let auth: TestAuth;
134+
let assertion: TotpMultiFactorAssertionImpl;
135+
let session: MultiFactorSessionImpl;
136+
let secret: TotpSecret;
137+
138+
const serverResponse: FinalizeMfaResponse = {
139+
idToken: 'final-id-token',
140+
refreshToken: 'refresh-token'
141+
};
142+
143+
const startEnrollmentResponse: StartTotpMfaEnrollmentResponse = {
144+
totpSessionInfo: {
145+
sharedSecretKey: 'key123',
146+
verificationCodeLength: 6,
147+
hashingAlgorithm: 'SHA1',
148+
periodSec: 30,
149+
sessionInfo: 'verification-id',
150+
finalizeEnrollmentTime: 1662586196
151+
}
152+
};
153+
154+
beforeEach(async () => {
155+
mockFetch.setUp();
156+
auth = await testAuth();
157+
secret = TotpSecret.fromStartTotpMfaEnrollmentResponse(
158+
startEnrollmentResponse,
159+
auth.name
160+
);
161+
assertion = TotpMultiFactorAssertionImpl._fromSecret(secret, '123456');
162+
});
163+
afterEach(mockFetch.tearDown);
164+
165+
describe('enroll', () => {
166+
beforeEach(() => {
167+
session = MultiFactorSessionImpl._fromIdtoken(
168+
'enrollment-id-token',
169+
auth
170+
);
171+
});
172+
173+
it('should finalize the MFA enrollment', async () => {
174+
const mock = mockEndpoint(
175+
Endpoint.FINALIZE_MFA_ENROLLMENT,
176+
serverResponse
177+
);
178+
const response = await assertion._process(auth, session);
179+
expect(response).to.eql(serverResponse);
180+
expect(mock.calls[0].request).to.eql({
181+
idToken: 'enrollment-id-token',
182+
totpVerificationInfo: {
183+
verificationCode: '123456',
184+
sessionInfo: 'verification-id'
185+
}
186+
});
187+
expect(session.auth).to.eql(auth);
188+
});
189+
190+
context('with display name', () => {
191+
it('should set the display name', async () => {
192+
const mock = mockEndpoint(
193+
Endpoint.FINALIZE_MFA_ENROLLMENT,
194+
serverResponse
195+
);
196+
const response = await assertion._process(
197+
auth,
198+
session,
199+
'display-name'
200+
);
201+
expect(response).to.eql(serverResponse);
202+
expect(mock.calls[0].request).to.eql({
203+
idToken: 'enrollment-id-token',
204+
displayName: 'display-name',
205+
totpVerificationInfo: {
206+
verificationCode: '123456',
207+
sessionInfo: 'verification-id'
208+
}
209+
});
210+
expect(session.auth).to.eql(auth);
211+
});
212+
});
213+
});
214+
});
215+
216+
describe('core/mfa/assertions/totp/TotpSecret', () => {
217+
const serverResponse: StartTotpMfaEnrollmentResponse = {
218+
totpSessionInfo: {
219+
sharedSecretKey: 'key123',
220+
verificationCodeLength: 6,
221+
hashingAlgorithm: 'SHA1',
222+
periodSec: 30,
223+
sessionInfo: 'verification-id',
224+
finalizeEnrollmentTime: 1662586196
225+
}
226+
};
227+
const fakeAppName: AppName = 'test-app';
228+
const fakeEmail: string = 'user@email';
229+
const secret = TotpSecret.fromStartTotpMfaEnrollmentResponse(
230+
serverResponse,
231+
fakeAppName
232+
);
233+
234+
describe('fromStartTotpMfaEnrollmentResponse', () => {
235+
it('fields from the response are parsed correctly', () => {
236+
expect(secret.secretKey).to.eq('key123');
237+
expect(secret.codeIntervalSeconds).to.eq(30);
238+
expect(secret.codeLength).to.eq(6);
239+
expect(secret.hashingAlgorithm).to.eq('SHA1');
240+
});
241+
});
242+
describe('generateQrCodeUrl', () => {
243+
let app: FirebaseApp;
244+
let auth: Auth;
245+
246+
beforeEach(async () => {
247+
app = initializeApp(
248+
{
249+
apiKey: 'fake-key',
250+
appId: 'fake-app-id',
251+
authDomain: 'fake-auth-domain'
252+
},
253+
fakeAppName
254+
);
255+
auth = initializeAuth(app);
256+
await auth.updateCurrentUser(
257+
testUser(_castAuth(auth), 'uid', fakeEmail, true)
258+
);
259+
});
260+
261+
it('with account name and issuer provided', () => {
262+
const url = secret.generateQrCodeUrl('user@myawesomeapp', 'myawesomeapp');
263+
expect(url).to.eq(
264+
'otpauth://totp/myawesomeapp:user@myawesomeapp?secret=key123&issuer=myawesomeapp&algorithm=SHA1&digits=6'
265+
);
266+
});
267+
it('only accountName provided', () => {
268+
const url = secret.generateQrCodeUrl('user@myawesomeapp', '');
269+
const auth2 = getAuth(app);
270+
console.log('Current user is ' + auth2);
271+
expect(url).to.eq(
272+
`otpauth://totp/${fakeAppName}:user@myawesomeapp?secret=key123&issuer=${fakeAppName}&algorithm=SHA1&digits=6`
273+
);
274+
});
275+
it('only issuer provided', () => {
276+
const url = secret.generateQrCodeUrl('', 'myawesomeapp');
277+
expect(url).to.eq(
278+
`otpauth://totp/myawesomeapp:${fakeEmail}?secret=key123&issuer=myawesomeapp&algorithm=SHA1&digits=6`
279+
);
280+
});
281+
it('with defaults', () => {
282+
const url = secret.generateQrCodeUrl();
283+
expect(url).to.eq(
284+
`otpauth://totp/${fakeAppName}:${fakeEmail}?secret=key123&issuer=${fakeAppName}&algorithm=SHA1&digits=6`
285+
);
286+
});
287+
it('with defaults, without currentUser', async () => {
288+
await auth.updateCurrentUser(null);
289+
const url = secret.generateQrCodeUrl();
290+
expect(url).to.eq(
291+
`otpauth://totp/${fakeAppName}:unknownuser?secret=key123&issuer=${fakeAppName}&algorithm=SHA1&digits=6`
292+
);
293+
});
294+
});
295+
});

0 commit comments

Comments
 (0)