Skip to content

Commit 3f94477

Browse files
prameshjbhparijat
authored andcommitted
Mfa totp demoapp (#6629)
* Export TOTP symbols to be picked up by demo app. * 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/
1 parent 6dd2ecf commit 3f94477

File tree

7 files changed

+153
-15
lines changed

7 files changed

+153
-15
lines changed

common/api-review/auth.api.md

+26
Original file line numberDiff line numberDiff line change
@@ -750,10 +750,36 @@ export function signOut(auth: Auth): Promise<void>;
750750
export interface TotpMultiFactorAssertion extends MultiFactorAssertion {
751751
}
752752

753+
// @public
754+
export class TotpMultiFactorGenerator {
755+
static assertionForEnrollment(secret: TotpSecret, oneTimePassword: string): TotpMultiFactorAssertion;
756+
static assertionForSignIn(enrollmentId: string, oneTimePassword: string): TotpMultiFactorAssertion;
757+
// Warning: (ae-forgotten-export) The symbol "FactorId" needs to be exported by the entry point index.d.ts
758+
static FACTOR_ID: FactorId_2;
759+
static generateSecret(session: MultiFactorSession): Promise<TotpSecret>;
760+
}
761+
753762
// @public
754763
export interface TotpMultiFactorInfo extends MultiFactorInfo {
755764
}
756765

766+
// @public
767+
export class TotpSecret {
768+
readonly codeIntervalSeconds: number;
769+
readonly codeLength: number;
770+
// Warning: (ae-forgotten-export) The symbol "StartTotpMfaEnrollmentResponse" needs to be exported by the entry point index.d.ts
771+
//
772+
// @internal (undocumented)
773+
static _fromStartTotpMfaEnrollmentResponse(response: StartTotpMfaEnrollmentResponse, auth: AuthInternal): TotpSecret;
774+
generateQrCodeUrl(accountName?: string, issuer?: string): string;
775+
readonly hashingAlgorithm: string;
776+
// Warning: (ae-forgotten-export) The symbol "TotpVerificationInfo" needs to be exported by the entry point index.d.ts
777+
//
778+
// @internal (undocumented)
779+
_makeTotpVerificationInfo(otp: string): TotpVerificationInfo;
780+
readonly secretKey: string;
781+
}
782+
757783
// @public
758784
export class TwitterAuthProvider extends BaseOAuthProvider {
759785
constructor();

packages/auth/demo/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
},
1919
"dependencies": {
2020
"@firebase/app": "0.7.24",
21-
"@firebase/auth": "0.20.1",
21+
"@firebase/auth": "file:..",
2222
"@firebase/logger": "0.3.2",
2323
"@firebase/util": "1.6.0",
2424
"tslib": "^2.1.0"

packages/auth/demo/public/index.html

+27-1
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,12 @@
483483
Phone
484484
</a>
485485
</li>
486+
<li role="presentation">
487+
<a href="#mfa-totp-section" aria-controls="mfa-totp-section"
488+
data-toggle="tab" role="tab">
489+
TOTP
490+
</a>
491+
</li>
486492
</ul>
487493
<div class="tab-content">
488494
<div class="tab-pane active" id="mfa-phone-section">
@@ -500,7 +506,27 @@
500506
class="form-control" placeholder="Display Name" />
501507
<button class="btn btn-block btn-primary"
502508
id="enroll-mfa-confirm-phone-verification">
503-
Complete Enrollment
509+
Complete Phone Enrollment
510+
</button>
511+
512+
</form>
513+
</div>
514+
<div class="tab-pane" id="mfa-totp-section">
515+
<form class="form form-bordered no-submit">
516+
<button class="btn btn-block btn-primary" id="enroll-mfa-totp-start">
517+
Start TOTP Enrollment
518+
</button>
519+
<br>
520+
<p hidden class="totp-text" id="totp-text"> Please scan the QR code below in a TOTP app </p>
521+
<br>
522+
<img hidden class="totp-qr-image" id="totp-qr-image"/>
523+
<br>
524+
<input type="text" id="enroll-mfa-totp-verification-code"
525+
class="form-control" placeholder="TOTP Verification Code(from App)" />
526+
<input type="text" id="enroll-mfa-totp-display-name"
527+
class="form-control" placeholder="Display Name" />
528+
<button class="btn btn-block btn-primary" id="enroll-mfa-totp-finalize">
529+
Complete TOTP Enrollment
504530
</button>
505531
</form>
506532
</div>

packages/auth/demo/public/style.css

+11
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,17 @@ input + .form,
177177
margin-right: 10px;
178178
}
179179

180+
.totp-text {
181+
font-family: 'Courier New', Courier;
182+
}
183+
.totp-qr-image {
184+
height: 120px;
185+
margin-right: 10px;
186+
border: 1px solid #ddd;
187+
border-radius: 4px;
188+
width: 120px;
189+
}
190+
180191
.profile-email-not-verified {
181192
color: #d9534f;
182193
}

packages/auth/demo/src/index.js

+51
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import {
4949
signInWithCredential,
5050
signInWithCustomToken,
5151
signInWithEmailAndPassword,
52+
TotpMultiFactorGenerator,
53+
TotpSecret,
5254
unlink,
5355
updateEmail,
5456
updatePassword,
@@ -97,6 +99,7 @@ let multiFactorErrorResolver = null;
9799
let selectedMultiFactorHint = null;
98100
let recaptchaSize = 'normal';
99101
let webWorker = null;
102+
let totpSecret = null;
100103

101104
// The corresponding Font Awesome icons for each provider.
102105
const providersIcons = {
@@ -652,6 +655,50 @@ function onFinalizeEnrollWithPhoneMultiFactor() {
652655
}, onAuthError);
653656
}
654657

658+
async function onStartEnrollWithTotpMultiFactor() {
659+
console.log('Starting TOTP enrollment!');
660+
if (!activeUser()) {
661+
alertError('No active user found.');
662+
return;
663+
}
664+
try {
665+
multiFactorSession = await multiFactor(activeUser()).getSession();
666+
totpSecret = await TotpMultiFactorGenerator.generateSecret(
667+
multiFactorSession
668+
);
669+
const url = totpSecret.generateQrCodeUrl('test', 'testissuer');
670+
console.log('TOTP URL is ' + url);
671+
// Use the QRServer API documented at https://goqr.me/api/doc/
672+
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?data=${url}&amp;size=30x30`;
673+
$('img.totp-qr-image').attr('src', qrCodeUrl).show();
674+
$('p.totp-text').show();
675+
} catch (e) {
676+
onAuthError(e);
677+
}
678+
}
679+
680+
async function onFinalizeEnrollWithTotpMultiFactor() {
681+
const verificationCode = $('#enroll-mfa-totp-verification-code').val();
682+
if (!activeUser() || !totpSecret || !verificationCode) {
683+
alertError(' Missing active user OR TOTP secret OR verification code.');
684+
return;
685+
}
686+
687+
const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
688+
totpSecret,
689+
verificationCode
690+
);
691+
const displayName = $('#enroll-mfa-totp-display-name').val() || undefined;
692+
693+
try {
694+
await multiFactor(activeUser()).enroll(multiFactorAssertion, displayName);
695+
refreshUserData();
696+
alertSuccess('TOTP MFA enrolled!');
697+
} catch (e) {
698+
onAuthError(e);
699+
}
700+
}
701+
655702
/**
656703
* Signs in or links a provider's credential, based on current tab opened.
657704
* @param {!AuthCredential} credential The provider's credential.
@@ -1944,6 +1991,10 @@ function initApp() {
19441991
$('#enroll-mfa-confirm-phone-verification').click(
19451992
onFinalizeEnrollWithPhoneMultiFactor
19461993
);
1994+
// Starts multi-factor enrollment with TOTP.
1995+
$('#enroll-mfa-totp-start').click(onStartEnrollWithTotpMultiFactor);
1996+
// Completes multi-factor enrollment with supplied OTP(One-Time Password).
1997+
$('#enroll-mfa-totp-finalize').click(onFinalizeEnrollWithTotpMultiFactor);
19471998
}
19481999

19492000
$(initApp);

packages/auth/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ import { browserPopupRedirectResolver } from './src/platform_browser/popup_redir
7373

7474
// MFA
7575
import { PhoneMultiFactorGenerator } from './src/platform_browser/mfa/assertions/phone';
76+
import {
77+
TotpMultiFactorGenerator,
78+
TotpSecret
79+
} from './src/mfa/assertions/totp';
7680

7781
// Initialization and registration of Auth
7882
import { getAuth } from './src/platform_browser';
@@ -96,5 +100,7 @@ export {
96100
RecaptchaVerifier,
97101
browserPopupRedirectResolver,
98102
PhoneMultiFactorGenerator,
103+
TotpMultiFactorGenerator,
104+
TotpSecret,
99105
getAuth
100106
};

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

+31-13
Original file line numberDiff line numberDiff line change
@@ -171,23 +171,41 @@ export class TotpMultiFactorAssertionImpl
171171
*/
172172
export class TotpSecret {
173173
/**
174-
* Constructor for TotpSecret.
175-
* @param secretKey - Shared secret key/seed used for enrolling in TOTP MFA and generating otps.
176-
* @param hashingAlgorithm - Hashing algorithm used.
177-
* @param codeLength - Length of the one-time passwords to be generated.
178-
* @param codeIntervalSeconds - The interval (in seconds) when the OTP codes should change.
174+
* Shared secret key/seed used for enrolling in TOTP MFA and generating otps.
179175
*/
176+
readonly secretKey: string;
177+
/**
178+
* Hashing algorithm used.
179+
*/
180+
readonly hashingAlgorithm: string;
181+
/**
182+
* Length of the one-time passwords to be generated.
183+
*/
184+
readonly codeLength: number;
185+
/**
186+
* The interval (in seconds) when the OTP codes should change.
187+
*/
188+
readonly codeIntervalSeconds: number;
189+
// TODO(prameshj) - make this public after API review.
190+
// This can be used by callers to show a countdown of when to enter OTP code by.
191+
private readonly finalizeEnrollmentBy: string;
192+
193+
// The public members are declared outside the constructor so the docs can be generated.
180194
private constructor(
181-
readonly secretKey: string,
182-
readonly hashingAlgorithm: string,
183-
readonly codeLength: number,
184-
readonly codeIntervalSeconds: number,
185-
// TODO(prameshj) - make this public after API review.
186-
// This can be used by callers to show a countdown of when to enter OTP code by.
187-
private readonly finalizeEnrollmentBy: string,
195+
secretKey: string,
196+
hashingAlgorithm: string,
197+
codeLength: number,
198+
codeIntervalSeconds: number,
199+
finalizeEnrollmentBy: string,
188200
private readonly sessionInfo: string,
189201
private readonly auth: AuthInternal
190-
) {}
202+
) {
203+
this.secretKey = secretKey;
204+
this.hashingAlgorithm = hashingAlgorithm;
205+
this.codeLength = codeLength;
206+
this.codeIntervalSeconds = codeIntervalSeconds;
207+
this.finalizeEnrollmentBy = finalizeEnrollmentBy;
208+
}
191209

192210
/** @internal */
193211
static _fromStartTotpMfaEnrollmentResponse(

0 commit comments

Comments
 (0)