From e067344298f144ba717ad72bf9a81f7e740bcf31 Mon Sep 17 00:00:00 2001 From: Pavithra Ramesh Date: Sun, 11 Sep 2022 07:33:39 -0700 Subject: [PATCH 1/2] Export TOTP symbols to be picked up by demo app. --- common/api-review/auth.api.md | 26 ++++++++++++++ packages/auth/index.ts | 6 ++++ packages/auth/src/mfa/assertions/totp.ts | 44 +++++++++++++++++------- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/common/api-review/auth.api.md b/common/api-review/auth.api.md index b9d192e01cb..738c72048bb 100644 --- a/common/api-review/auth.api.md +++ b/common/api-review/auth.api.md @@ -750,10 +750,36 @@ export function signOut(auth: Auth): Promise; export interface TotpMultiFactorAssertion extends MultiFactorAssertion { } +// @public +export class TotpMultiFactorGenerator { + static assertionForEnrollment(secret: TotpSecret, oneTimePassword: string): TotpMultiFactorAssertion; + static assertionForSignIn(enrollmentId: string, oneTimePassword: string): TotpMultiFactorAssertion; + // Warning: (ae-forgotten-export) The symbol "FactorId" needs to be exported by the entry point index.d.ts + static FACTOR_ID: FactorId_2; + static generateSecret(session: MultiFactorSession): Promise; +} + // @public export interface TotpMultiFactorInfo extends MultiFactorInfo { } +// @public +export class TotpSecret { + readonly codeIntervalSeconds: number; + readonly codeLength: number; + // Warning: (ae-forgotten-export) The symbol "StartTotpMfaEnrollmentResponse" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + static _fromStartTotpMfaEnrollmentResponse(response: StartTotpMfaEnrollmentResponse, auth: AuthInternal): TotpSecret; + generateQrCodeUrl(accountName?: string, issuer?: string): string; + readonly hashingAlgorithm: string; + // Warning: (ae-forgotten-export) The symbol "TotpVerificationInfo" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + _makeTotpVerificationInfo(otp: string): TotpVerificationInfo; + readonly secretKey: string; + } + // @public export class TwitterAuthProvider extends BaseOAuthProvider { constructor(); diff --git a/packages/auth/index.ts b/packages/auth/index.ts index 6e0338435d1..6ed92b361ac 100644 --- a/packages/auth/index.ts +++ b/packages/auth/index.ts @@ -73,6 +73,10 @@ import { browserPopupRedirectResolver } from './src/platform_browser/popup_redir // MFA import { PhoneMultiFactorGenerator } from './src/platform_browser/mfa/assertions/phone'; +import { + TotpMultiFactorGenerator, + TotpSecret +} from './src/mfa/assertions/totp'; // Initialization and registration of Auth import { getAuth } from './src/platform_browser'; @@ -96,5 +100,7 @@ export { RecaptchaVerifier, browserPopupRedirectResolver, PhoneMultiFactorGenerator, + TotpMultiFactorGenerator, + TotpSecret, getAuth }; diff --git a/packages/auth/src/mfa/assertions/totp.ts b/packages/auth/src/mfa/assertions/totp.ts index 39cc18376e5..8ab819c4c62 100644 --- a/packages/auth/src/mfa/assertions/totp.ts +++ b/packages/auth/src/mfa/assertions/totp.ts @@ -171,23 +171,41 @@ export class TotpMultiFactorAssertionImpl */ export class TotpSecret { /** - * Constructor for TotpSecret. - * @param secretKey - Shared secret key/seed used for enrolling in TOTP MFA and generating otps. - * @param hashingAlgorithm - Hashing algorithm used. - * @param codeLength - Length of the one-time passwords to be generated. - * @param codeIntervalSeconds - The interval (in seconds) when the OTP codes should change. + * Shared secret key/seed used for enrolling in TOTP MFA and generating otps. */ + readonly secretKey: string; + /** + * Hashing algorithm used. + */ + readonly hashingAlgorithm: string; + /** + * Length of the one-time passwords to be generated. + */ + readonly codeLength: number; + /** + * The interval (in seconds) when the OTP codes should change. + */ + readonly codeIntervalSeconds: number; + // TODO(prameshj) - make this public after API review. + // This can be used by callers to show a countdown of when to enter OTP code by. + private readonly finalizeEnrollmentBy: string; + + // The public members are declared outside the constructor so the docs can be generated. private constructor( - readonly secretKey: string, - readonly hashingAlgorithm: string, - readonly codeLength: number, - readonly codeIntervalSeconds: number, - // TODO(prameshj) - make this public after API review. - // This can be used by callers to show a countdown of when to enter OTP code by. - private readonly finalizeEnrollmentBy: string, + secretKey: string, + hashingAlgorithm: string, + codeLength: number, + codeIntervalSeconds: number, + finalizeEnrollmentBy: string, private readonly sessionInfo: string, private readonly auth: AuthInternal - ) {} + ) { + this.secretKey = secretKey; + this.hashingAlgorithm = hashingAlgorithm; + this.codeLength = codeLength; + this.codeIntervalSeconds = codeIntervalSeconds; + this.finalizeEnrollmentBy = finalizeEnrollmentBy; + } /** @internal */ static _fromStartTotpMfaEnrollmentResponse( From d73dfbb2a10601d3116ba92548ddd81289399dd7 Mon Sep 17 00:00:00 2001 From: Pavithra Ramesh Date: Sun, 11 Sep 2022 07:34:23 -0700 Subject: [PATCH 2/2] Update the demo app to support TOTP enrollment, use local firebase auth version. The QR code image is generated using the qrserver api at https://goqr.me/api/doc/ --- packages/auth/demo/package.json | 2 +- packages/auth/demo/public/index.html | 28 ++++++++++++++- packages/auth/demo/public/style.css | 11 ++++++ packages/auth/demo/src/index.js | 51 ++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/packages/auth/demo/package.json b/packages/auth/demo/package.json index 6b6dc71f85d..a26042ec5e0 100644 --- a/packages/auth/demo/package.json +++ b/packages/auth/demo/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@firebase/app": "0.7.24", - "@firebase/auth": "0.20.1", + "@firebase/auth": "file:..", "@firebase/logger": "0.3.2", "@firebase/util": "1.6.0", "tslib": "^2.1.0" diff --git a/packages/auth/demo/public/index.html b/packages/auth/demo/public/index.html index aa32e144ed5..960a3c3f4a9 100644 --- a/packages/auth/demo/public/index.html +++ b/packages/auth/demo/public/index.html @@ -483,6 +483,12 @@ Phone +
  • + + TOTP + +
  • @@ -500,7 +506,27 @@ class="form-control" placeholder="Display Name" /> + + +
    +
    +
    + +
    + +
    + +
    + + +
    diff --git a/packages/auth/demo/public/style.css b/packages/auth/demo/public/style.css index cdd999f8e8b..84750c29ea1 100644 --- a/packages/auth/demo/public/style.css +++ b/packages/auth/demo/public/style.css @@ -177,6 +177,17 @@ input + .form, margin-right: 10px; } +.totp-text { + font-family: 'Courier New', Courier; +} +.totp-qr-image { + height: 120px; + margin-right: 10px; + border: 1px solid #ddd; + border-radius: 4px; + width: 120px; +} + .profile-email-not-verified { color: #d9534f; } diff --git a/packages/auth/demo/src/index.js b/packages/auth/demo/src/index.js index 86c2af49a65..026777369da 100644 --- a/packages/auth/demo/src/index.js +++ b/packages/auth/demo/src/index.js @@ -49,6 +49,8 @@ import { signInWithCredential, signInWithCustomToken, signInWithEmailAndPassword, + TotpMultiFactorGenerator, + TotpSecret, unlink, updateEmail, updatePassword, @@ -97,6 +99,7 @@ let multiFactorErrorResolver = null; let selectedMultiFactorHint = null; let recaptchaSize = 'normal'; let webWorker = null; +let totpSecret = null; // The corresponding Font Awesome icons for each provider. const providersIcons = { @@ -652,6 +655,50 @@ function onFinalizeEnrollWithPhoneMultiFactor() { }, onAuthError); } +async function onStartEnrollWithTotpMultiFactor() { + console.log('Starting TOTP enrollment!'); + if (!activeUser()) { + alertError('No active user found.'); + return; + } + try { + multiFactorSession = await multiFactor(activeUser()).getSession(); + totpSecret = await TotpMultiFactorGenerator.generateSecret( + multiFactorSession + ); + const url = totpSecret.generateQrCodeUrl('test', 'testissuer'); + console.log('TOTP URL is ' + url); + // Use the QRServer API documented at https://goqr.me/api/doc/ + const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?data=${url}&size=30x30`; + $('img.totp-qr-image').attr('src', qrCodeUrl).show(); + $('p.totp-text').show(); + } catch (e) { + onAuthError(e); + } +} + +async function onFinalizeEnrollWithTotpMultiFactor() { + const verificationCode = $('#enroll-mfa-totp-verification-code').val(); + if (!activeUser() || !totpSecret || !verificationCode) { + alertError(' Missing active user OR TOTP secret OR verification code.'); + return; + } + + const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment( + totpSecret, + verificationCode + ); + const displayName = $('#enroll-mfa-totp-display-name').val() || undefined; + + try { + await multiFactor(activeUser()).enroll(multiFactorAssertion, displayName); + refreshUserData(); + alertSuccess('TOTP MFA enrolled!'); + } catch (e) { + onAuthError(e); + } +} + /** * Signs in or links a provider's credential, based on current tab opened. * @param {!AuthCredential} credential The provider's credential. @@ -1944,6 +1991,10 @@ function initApp() { $('#enroll-mfa-confirm-phone-verification').click( onFinalizeEnrollWithPhoneMultiFactor ); + // Starts multi-factor enrollment with TOTP. + $('#enroll-mfa-totp-start').click(onStartEnrollWithTotpMultiFactor); + // Completes multi-factor enrollment with supplied OTP(One-Time Password). + $('#enroll-mfa-totp-finalize').click(onFinalizeEnrollWithTotpMultiFactor); } $(initApp);