From 9b509cd329ec90e07daea02c5f86b1b267a5d71c Mon Sep 17 00:00:00 2001 From: Ike <137194738+ike-kottlowski@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:48:19 -0700 Subject: [PATCH] [PM-8943] Update QRious script initialization in Authenticator two-factor provider (#9926) * create onload() for qrious as well as error messaging if QR code cannot be displayed * button and message updates and formpromise removal * load QR script async * rename and reorder methods --- .../two-factor-authenticator.component.html | 11 ++- .../two-factor-authenticator.component.ts | 75 +++++++++++-------- apps/web/src/locales/en/messages.json | 3 + 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor-authenticator.component.html index f7ea36e02d..c9214d59ca 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.html @@ -72,7 +72,14 @@

-
+ + +

+ {{ "twoStepAuthenticatorQRCanvasError" | i18n }} +

+ + +
{{ key }}

@@ -90,7 +97,7 @@ > {{ (enabled ? "disable" : "enable") | i18n }} - diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts index 6870610531..7672ef7028 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts @@ -43,9 +43,9 @@ export class TwoFactorAuthenticatorComponent @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.Authenticator; key: string; - formPromise: Promise; override componentName = "app-two-factor-authenticator"; + qrScriptError = false; private qrScript: HTMLScriptElement; formGroup = this.formBuilder.group({ @@ -90,7 +90,7 @@ export class TwoFactorAuthenticatorComponent this.formGroup.controls.token.markAsTouched(); } - auth(authResponse: AuthResponse) { + async auth(authResponse: AuthResponse) { super.auth(authResponse); return this.processResponse(authResponse.response); } @@ -100,56 +100,69 @@ export class TwoFactorAuthenticatorComponent return; } if (this.enabled) { - await this.disableAuthentication(this.formPromise); - this.onChangeStatus.emit(this.enabled); - this.close(); + await this.disableMethod(); + this.dialogRef.close(this.enabled); } else { await this.enable(); - this.onChangeStatus.emit(this.enabled); } + this.onChangeStatus.emit(this.enabled); }; - private async disableAuthentication(promise: Promise) { - return super.disable(promise); - } - protected async enable() { const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest); request.token = this.formGroup.value.token; request.key = this.key; - return super.enable(async () => { - this.formPromise = this.apiService.putTwoFactorAuthenticator(request); - const response = await this.formPromise; - await this.processResponse(response); - }); + const response = await this.apiService.putTwoFactorAuthenticator(request); + await this.processResponse(response); + this.onUpdated.emit(true); } private async processResponse(response: TwoFactorAuthenticatorResponse) { this.formGroup.get("token").setValue(null); this.enabled = response.enabled; this.key = response.key; + + await this.waitForQRiousToLoadOrError().catch((error) => { + this.logService.error(error); + this.qrScriptError = true; + }); + + await this.createQRCode(); + } + + private async waitForQRiousToLoadOrError(): Promise { + // Check if QRious is already loaded or if there was an error loading it either way don't wait for it to try and load again + if (typeof window.QRious !== "undefined" || this.qrScriptError) { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + this.qrScript.onload = () => resolve(); + this.qrScript.onerror = () => + reject(new Error(this.i18nService.t("twoStepAuthenticatorQRCanvasError"))); + }); + } + + private async createQRCode() { + if (this.qrScriptError) { + return; + } const email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), ); - window.setTimeout(() => { - new window.QRious({ - element: document.getElementById("qr"), - value: - "otpauth://totp/Bitwarden:" + - Utils.encodeRFC3986URIComponent(email) + - "?secret=" + - encodeURIComponent(this.key) + - "&issuer=Bitwarden", - size: 160, - }); - }, 100); + new window.QRious({ + element: document.getElementById("qr"), + value: + "otpauth://totp/Bitwarden:" + + Utils.encodeRFC3986URIComponent(email) + + "?secret=" + + encodeURIComponent(this.key) + + "&issuer=Bitwarden", + size: 160, + }); } - close = () => { - this.dialogRef.close(this.enabled); - }; - static open( dialogService: DialogService, config: DialogConfig>, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 37ebe62641..f61a1396ed 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1691,6 +1691,9 @@ "twoStepAuthenticatorScanCodeV2": { "message": "Scan the QR code below with your authenticator app or enter the key." }, + "twoStepAuthenticatorQRCanvasError": { + "message": "Could not load QR code. Try again or use the key below." + }, "key": { "message": "Key" },