[PM-7084] 6/6: Introduce shared duo two-factor component (#9772)
* Add shared duo component * Fix duo import * Fix wrong i18n service DI in duo desktop component * Remove duo v2 * Add override to functions * Remove web duo implementation * Update apps/browser/src/auth/popup/two-factor-auth-duo.component.ts Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> * Update apps/desktop/src/auth/two-factor-auth-duo.component.ts Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> * Update libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.ts Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com> * Fix missing service on duo components * Fix missing service on base duo auth component * Fix constructor super calls in duo auth component * Fix duo auth components incorrectly extending base class --------- Co-authored-by: Ike <137194738+ike-kottlowski@users.noreply.github.com>
This commit is contained in:
parent
bc1ee0a169
commit
05e8b45edb
|
@ -0,0 +1,105 @@
|
|||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
|
||||
import { Subject, Subscription, filter, firstValueFrom, takeUntil } from "rxjs";
|
||||
|
||||
import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-duo.component";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions";
|
||||
import { ButtonModule } from "../../../../../libs/components/src/button";
|
||||
import { FormFieldModule } from "../../../../../libs/components/src/form-field";
|
||||
import { LinkModule } from "../../../../../libs/components/src/link";
|
||||
import { I18nPipe } from "../../../../../libs/components/src/shared/i18n.pipe";
|
||||
import { TypographyModule } from "../../../../../libs/components/src/typography";
|
||||
import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-duo",
|
||||
templateUrl:
|
||||
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent {
|
||||
private destroy$ = new Subject<void>();
|
||||
duoResultSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private browserMessagingApi: ZonedMessageListenerService,
|
||||
private environmentService: EnvironmentService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(i18nService, platformUtilsService, toastService);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
protected override setupDuoResultListener() {
|
||||
if (!this.duoResultSubscription) {
|
||||
this.duoResultSubscription = this.browserMessagingApi
|
||||
.messageListener$()
|
||||
.pipe(
|
||||
filter((msg: any) => msg.command === "duoResult"),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe((msg: { command: string; code: string; state: string }) => {
|
||||
this.token.emit(msg.code + "|" + msg.state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
override async launchDuoFrameless() {
|
||||
if (this.duoFramelessUrl === null) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const duoHandOffMessage = {
|
||||
title: this.i18nService.t("youSuccessfullyLoggedIn"),
|
||||
message: this.i18nService.t("youMayCloseThisWindow"),
|
||||
isCountdown: false,
|
||||
};
|
||||
|
||||
// we're using the connector here as a way to set a cookie with translations
|
||||
// before continuing to the duo frameless url
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const launchUrl =
|
||||
env.getWebVaultUrl() +
|
||||
"/duo-redirect-connector.html" +
|
||||
"?duoFramelessUrl=" +
|
||||
encodeURIComponent(this.duoFramelessUrl) +
|
||||
"&handOffMessage=" +
|
||||
encodeURIComponent(JSON.stringify(duoHandOffMessage));
|
||||
this.platformUtilsService.launchUri(launchUrl);
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ import {
|
|||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
|
||||
|
||||
import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component";
|
||||
import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
|
||||
|
||||
@Component({
|
||||
|
@ -65,6 +66,7 @@ import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
|
|||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthDuoComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, NgZone } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
FormFieldModule,
|
||||
LinkModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "TwoFactorComponent";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-duo",
|
||||
templateUrl:
|
||||
"../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent {
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private broadcasterService: BroadcasterService,
|
||||
private ngZone: NgZone,
|
||||
private environmentService: EnvironmentService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(i18nService, platformUtilsService, toastService);
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
duoCallbackSubscriptionEnabled: boolean = false;
|
||||
|
||||
protected override setupDuoResultListener() {
|
||||
if (!this.duoCallbackSubscriptionEnabled) {
|
||||
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
|
||||
await this.ngZone.run(async () => {
|
||||
if (message.command === "duoCallback") {
|
||||
this.token.emit(message.code + "|" + message.state);
|
||||
}
|
||||
});
|
||||
});
|
||||
this.duoCallbackSubscriptionEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
override async launchDuoFrameless() {
|
||||
if (this.duoFramelessUrl === null) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const duoHandOffMessage = {
|
||||
title: this.i18nService.t("youSuccessfullyLoggedIn"),
|
||||
message: this.i18nService.t("youMayCloseThisWindow"),
|
||||
isCountdown: false,
|
||||
};
|
||||
|
||||
// we're using the connector here as a way to set a cookie with translations
|
||||
// before continuing to the duo frameless url
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const launchUrl =
|
||||
env.getWebVaultUrl() +
|
||||
"/duo-redirect-connector.html" +
|
||||
"?duoFramelessUrl=" +
|
||||
encodeURIComponent(this.duoFramelessUrl) +
|
||||
"&handOffMessage=" +
|
||||
encodeURIComponent(JSON.stringify(duoHandOffMessage));
|
||||
this.platformUtilsService.launchUri(launchUrl);
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
if (this.duoCallbackSubscriptionEnabled) {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
this.duoCallbackSubscriptionEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ import { LinkModule } from "../../../../libs/components/src/link";
|
|||
import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe";
|
||||
import { TypographyModule } from "../../../../libs/components/src/typography";
|
||||
|
||||
import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl:
|
||||
|
@ -40,6 +42,7 @@ import { TypographyModule } from "../../../../libs/components/src/typography";
|
|||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthDuoComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
|
||||
import { TwoFactorAuthDuoComponent as TwoFactorAuthDuoBaseComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component";
|
||||
import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions";
|
||||
import { ButtonModule } from "../../../../../libs/components/src/button";
|
||||
import { FormFieldModule } from "../../../../../libs/components/src/form-field";
|
||||
import { LinkModule } from "../../../../../libs/components/src/link";
|
||||
import { TypographyModule } from "../../../../../libs/components/src/typography";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-duo",
|
||||
templateUrl:
|
||||
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-duo.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
export class TwoFactorAuthDuoComponent extends TwoFactorAuthDuoBaseComponent {
|
||||
async ngOnInit(): Promise<void> {
|
||||
await super.ngOnInit();
|
||||
}
|
||||
|
||||
private duoResultChannel: BroadcastChannel;
|
||||
|
||||
protected override setupDuoResultListener() {
|
||||
if (!this.duoResultChannel) {
|
||||
this.duoResultChannel = new BroadcastChannel("duoResult");
|
||||
this.duoResultChannel.addEventListener("message", this.handleDuoResultMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private handleDuoResultMessage = async (msg: { data: { code: string; state: string } }) => {
|
||||
this.token.emit(msg.data.code + "|" + msg.data.state);
|
||||
};
|
||||
|
||||
async ngOnDestroy() {
|
||||
if (this.duoResultChannel) {
|
||||
// clean up duo listener if it was initialized.
|
||||
this.duoResultChannel.removeEventListener("message", this.handleDuoResultMessage);
|
||||
this.duoResultChannel.close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,8 @@ import { AsyncActionsModule } from "../../../../../libs/components/src/async-act
|
|||
import { ButtonModule } from "../../../../../libs/components/src/button";
|
||||
import { FormFieldModule } from "../../../../../libs/components/src/form-field";
|
||||
|
||||
import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl:
|
||||
|
@ -55,6 +57,7 @@ import { FormFieldModule } from "../../../../../libs/components/src/form-field";
|
|||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthDuoComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<ng-container>
|
||||
<p bitTypography="body1" class="tw-mb-0">
|
||||
{{ "duoRequiredByOrgForAccount" | i18n }}
|
||||
</p>
|
||||
<p bitTypography="body1">{{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }}</p>
|
||||
</ng-container>
|
|
@ -0,0 +1,79 @@
|
|||
import { DialogModule } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { ReactiveFormsModule, FormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-duo",
|
||||
templateUrl: "two-factor-auth-duo.component.html",
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
ButtonModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [I18nPipe],
|
||||
})
|
||||
export class TwoFactorAuthDuoComponent {
|
||||
@Output() token = new EventEmitter<string>();
|
||||
@Input() providerData: any;
|
||||
|
||||
duoFramelessUrl: string = null;
|
||||
duoResultListenerInitialized = false;
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected toastService: ToastService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
// Setup listener for duo-redirect.ts connector to send back the code
|
||||
if (!this.duoResultListenerInitialized) {
|
||||
// setup client specific duo result listener
|
||||
this.setupDuoResultListener();
|
||||
this.duoResultListenerInitialized = true;
|
||||
}
|
||||
|
||||
// flow must be launched by user so they can choose to remember the device or not.
|
||||
this.duoFramelessUrl = this.providerData.AuthUrl;
|
||||
}
|
||||
|
||||
// Each client will have own implementation
|
||||
protected setupDuoResultListener(): void {}
|
||||
async launchDuoFrameless(): Promise<void> {
|
||||
if (this.duoFramelessUrl === null) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.launchUri(this.duoFramelessUrl);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,15 @@
|
|||
(token)="token = $event; submitForm()"
|
||||
*ngIf="selectedProviderType === providerType.WebAuthn"
|
||||
/>
|
||||
<app-two-factor-auth-duo
|
||||
(token)="token = $event; submitForm()"
|
||||
[providerData]="providerData"
|
||||
*ngIf="
|
||||
selectedProviderType === providerType.OrganizationDuo ||
|
||||
selectedProviderType === providerType.Duo
|
||||
"
|
||||
#duoComponent
|
||||
/>
|
||||
<bit-form-control *ngIf="selectedProviderType != null">
|
||||
<bit-label>{{ "rememberMe" | i18n }}</bit-label>
|
||||
<input type="checkbox" bitCheckbox formControlName="remember" />
|
||||
|
@ -35,10 +44,28 @@
|
|||
buttonType="primary"
|
||||
bitButton
|
||||
bitFormButton
|
||||
*ngIf="selectedProviderType != null && selectedProviderType !== providerType.WebAuthn"
|
||||
*ngIf="
|
||||
selectedProviderType != null &&
|
||||
selectedProviderType !== providerType.WebAuthn &&
|
||||
selectedProviderType !== providerType.Duo &&
|
||||
selectedProviderType !== providerType.OrganizationDuo
|
||||
"
|
||||
>
|
||||
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ actionButtonText }} </span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
bitButton
|
||||
(click)="launchDuo()"
|
||||
*ngIf="
|
||||
selectedProviderType === providerType.Duo ||
|
||||
selectedProviderType === providerType.OrganizationDuo
|
||||
"
|
||||
>
|
||||
<span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "launchDuo" | i18n }}</span>
|
||||
</button>
|
||||
|
||||
<a routerLink="/login" bitButton buttonType="secondary">
|
||||
{{ "cancel" | i18n }}
|
||||
</a>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { Component, Inject, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, NavigationExtras, Router, RouterLink } from "@angular/router";
|
||||
import { Subject, takeUntil, lastValueFrom, first, firstValueFrom } from "rxjs";
|
||||
|
@ -39,6 +39,7 @@ import {
|
|||
import { CaptchaProtectedComponent } from "../captcha-protected.component";
|
||||
|
||||
import { TwoFactorAuthAuthenticatorComponent } from "./two-factor-auth-authenticator.component";
|
||||
import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component";
|
||||
import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component";
|
||||
import { TwoFactorAuthWebAuthnComponent } from "./two-factor-auth-webauthn.component";
|
||||
import { TwoFactorAuthYubikeyComponent } from "./two-factor-auth-yubikey.component";
|
||||
|
@ -63,6 +64,7 @@ import {
|
|||
TwoFactorOptionsComponent,
|
||||
TwoFactorAuthAuthenticatorComponent,
|
||||
TwoFactorAuthEmailComponent,
|
||||
TwoFactorAuthDuoComponent,
|
||||
TwoFactorAuthYubikeyComponent,
|
||||
TwoFactorAuthWebAuthnComponent,
|
||||
],
|
||||
|
@ -78,6 +80,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
|
|||
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
|
||||
providerData: any;
|
||||
|
||||
@ViewChild("duoComponent") duoComponent!: TwoFactorAuthDuoComponent;
|
||||
formGroup = this.formBuilder.group({
|
||||
token: [
|
||||
"",
|
||||
|
@ -220,6 +223,12 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements
|
|||
}
|
||||
}
|
||||
|
||||
async launchDuo() {
|
||||
if (this.duoComponent != null) {
|
||||
await this.duoComponent.launchDuoFrameless();
|
||||
}
|
||||
}
|
||||
|
||||
protected handleMigrateEncryptionKey(result: AuthResult): boolean {
|
||||
if (!result.requiresEncryptionKeyMigration) {
|
||||
return false;
|
||||
|
|
Loading…
Reference in New Issue