Skip to content

Commit 3af11d8

Browse files
committed
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 41935af commit 3af11d8

File tree

7 files changed

+153
-15
lines changed

7 files changed

+153
-15
lines changed

common/api-review/auth.api.md

Lines changed: 26 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,12 @@
487487
Phone
488488
</a>
489489
</li>
490+
<li role="presentation">
491+
<a href="#mfa-totp-section" aria-controls="mfa-totp-section"
492+
data-toggle="tab" role="tab">
493+
TOTP
494+
</a>
495+
</li>
490496
</ul>
491497
<div class="tab-content">
492498
<div class="tab-pane active" id="mfa-phone-section">
@@ -504,7 +510,27 @@
504510
class="form-control" placeholder="Display Name" />
505511
<button class="btn btn-block btn-primary"
506512
id="enroll-mfa-confirm-phone-verification">
507-
Complete Enrollment
513+
Complete Phone Enrollment
514+
</button>
515+
516+
</form>
517+
</div>
518+
<div class="tab-pane" id="mfa-totp-section">
519+
<form class="form form-bordered no-submit">
520+
<button class="btn btn-block btn-primary" id="enroll-mfa-totp-start">
521+
Start TOTP Enrollment
522+
</button>
523+
<br>
524+
<p hidden class="totp-text" id="totp-text"> Please scan the QR code below in a TOTP app </p>
525+
<br>
526+
<img hidden class="totp-qr-image" id="totp-qr-image"/>
527+
<br>
528+
<input type="text" id="enroll-mfa-totp-verification-code"
529+
class="form-control" placeholder="TOTP Verification Code(from App)" />
530+
<input type="text" id="enroll-mfa-totp-display-name"
531+
class="form-control" placeholder="Display Name" />
532+
<button class="btn btn-block btn-primary" id="enroll-mfa-totp-finalize">
533+
Complete TOTP Enrollment
508534
</button>
509535
</form>
510536
</div>

packages/auth/demo/public/style.css

Lines changed: 11 additions & 0 deletions
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

Lines changed: 51 additions & 0 deletions
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 = {
@@ -687,6 +690,50 @@ function onFinalizeEnrollWithPhoneMultiFactor() {
687690
}, onAuthError);
688691
}
689692

693+
async function onStartEnrollWithTotpMultiFactor() {
694+
console.log('Starting TOTP enrollment!');
695+
if (!activeUser()) {
696+
alertError('No active user found.');
697+
return;
698+
}
699+
try {
700+
multiFactorSession = await multiFactor(activeUser()).getSession();
701+
totpSecret = await TotpMultiFactorGenerator.generateSecret(
702+
multiFactorSession
703+
);
704+
const url = totpSecret.generateQrCodeUrl('test', 'testissuer');
705+
console.log('TOTP URL is ' + url);
706+
// Use the QRServer API documented at https://goqr.me/api/doc/
707+
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?data=${url}&amp;size=30x30`;
708+
$('img.totp-qr-image').attr('src', qrCodeUrl).show();
709+
$('p.totp-text').show();
710+
} catch (e) {
711+
onAuthError(e);
712+
}
713+
}
714+
715+
async function onFinalizeEnrollWithTotpMultiFactor() {
716+
const verificationCode = $('#enroll-mfa-totp-verification-code').val();
717+
if (!activeUser() || !totpSecret || !verificationCode) {
718+
alertError(' Missing active user OR TOTP secret OR verification code.');
719+
return;
720+
}
721+
722+
const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
723+
totpSecret,
724+
verificationCode
725+
);
726+
const displayName = $('#enroll-mfa-totp-display-name').val() || undefined;
727+
728+
try {
729+
await multiFactor(activeUser()).enroll(multiFactorAssertion, displayName);
730+
refreshUserData();
731+
alertSuccess('TOTP MFA enrolled!');
732+
} catch (e) {
733+
onAuthError(e);
734+
}
735+
}
736+
690737
/**
691738
* Signs in or links a provider's credential, based on current tab opened.
692739
* @param {!AuthCredential} credential The provider's credential.
@@ -2005,6 +2052,10 @@ function initApp() {
20052052
$('#enroll-mfa-confirm-phone-verification').click(
20062053
onFinalizeEnrollWithPhoneMultiFactor
20072054
);
2055+
// Starts multi-factor enrollment with TOTP.
2056+
$('#enroll-mfa-totp-start').click(onStartEnrollWithTotpMultiFactor);
2057+
// Completes multi-factor enrollment with supplied OTP(One-Time Password).
2058+
$('#enroll-mfa-totp-finalize').click(onFinalizeEnrollWithTotpMultiFactor);
20082059
}
20092060

20102061
$(initApp);

packages/auth/index.ts

Lines changed: 6 additions & 0 deletions
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

Lines changed: 31 additions & 13 deletions
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)