fix duo subscriptions and org vs individual duo setup (#9859)

This commit is contained in:
Jake Fink 2024-07-02 10:44:27 -04:00 committed by GitHub
parent ed9d82ef1e
commit a5e7fde413
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 30 deletions

View File

@ -1,7 +1,8 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, takeUntil, map, lastValueFrom } from "rxjs";
import { tap } from "rxjs/operators";
import { first, tap } from "rxjs/operators";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@ -64,6 +65,9 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
}
async manage(type: TwoFactorProviderType) {
// clear any existing subscriptions before creating a new one
this.twoFactorSetupSubscription?.unsubscribe();
switch (type) {
case TwoFactorProviderType.OrganizationDuo: {
const twoFactorVerifyDialogRef = TwoFactorVerifyComponent.open(this.dialogService, {
@ -75,9 +79,18 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
if (!result) {
return;
}
const duoComp = TwoFactorDuoComponent.open(this.dialogService, { data: result });
const enabled: boolean = await lastValueFrom(duoComp.closed);
this.updateStatus(enabled, TwoFactorProviderType.Duo);
const duoComp: DialogRef<boolean, any> = TwoFactorDuoComponent.open(this.dialogService, {
data: {
authResponse: result,
organizationId: this.organizationId,
},
});
this.twoFactorSetupSubscription = duoComp.componentInstance.onChangeStatus
.pipe(first(), takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
duoComp.close();
this.updateStatus(enabled, TwoFactorProviderType.OrganizationDuo);
});
break;
}

View File

@ -31,7 +31,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
override componentName = "app-two-factor-duo";
constructor(
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorDuoResponse>,
@Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig,
apiService: ApiService,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
@ -71,8 +71,17 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
}
async ngOnInit() {
super.auth(this.data);
this.processResponse(this.data.response);
if (!this.data?.authResponse) {
throw Error("TwoFactorDuoComponent requires a TwoFactorDuoResponse to initialize");
}
super.auth(this.data.authResponse);
this.processResponse(this.data.authResponse.response);
if (this.data.organizationId) {
this.type = TwoFactorProviderType.OrganizationDuo;
this.organizationId = this.data.organizationId;
}
}
submit = async () => {
@ -124,8 +133,13 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
*/
static open = (
dialogService: DialogService,
config: DialogConfig<AuthResponse<TwoFactorDuoResponse>>,
config: DialogConfig<TwoFactorDuoComponentConfig>,
) => {
return dialogService.open<boolean>(TwoFactorDuoComponent, config);
};
}
type TwoFactorDuoComponentConfig = {
authResponse: AuthResponse<TwoFactorDuoResponse>;
organizationId?: string;
};

View File

@ -1,6 +1,14 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core";
import { firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
import {
first,
firstValueFrom,
lastValueFrom,
Observable,
Subject,
Subscription,
takeUntil,
} from "rxjs";
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@ -36,9 +44,6 @@ import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component";
export class TwoFactorSetupComponent implements OnInit, OnDestroy {
@ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true })
yubikeyModalRef: ViewContainerRef;
@ViewChild("duoTemplate", { read: ViewContainerRef, static: true }) duoModalRef: ViewContainerRef;
@ViewChild("emailTemplate", { read: ViewContainerRef, static: true })
emailModalRef: ViewContainerRef;
organizationId: string;
organization: Organization;
@ -53,6 +58,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
protected destroy$ = new Subject<void>();
private twoFactorAuthPolicyAppliesToActiveUser: boolean;
protected twoFactorSetupSubscription: Subscription;
constructor(
protected dialogService: DialogService,
@ -126,6 +132,9 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
}
async manage(type: TwoFactorProviderType) {
// clear any existing subscriptions before creating a new one
this.twoFactorSetupSubscription?.unsubscribe();
switch (type) {
case TwoFactorProviderType.Authenticator: {
const result: AuthResponse<TwoFactorAuthenticatorResponse> =
@ -137,9 +146,12 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
this.dialogService,
{ data: result },
);
authComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Authenticator);
});
this.twoFactorSetupSubscription = authComp.componentInstance.onChangeStatus
.pipe(first(), takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
authComp.close();
this.updateStatus(enabled, TwoFactorProviderType.Authenticator);
});
break;
}
case TwoFactorProviderType.Yubikey: {
@ -150,9 +162,11 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
}
const yubiComp = await this.openModal(this.yubikeyModalRef, TwoFactorYubiKeyComponent);
yubiComp.auth(result);
yubiComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Yubikey);
});
this.twoFactorSetupSubscription = yubiComp.onUpdated
.pipe(first(), takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Yubikey);
});
break;
}
case TwoFactorProviderType.Duo: {
@ -162,11 +176,16 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
return;
}
const duoComp: DialogRef<boolean, any> = TwoFactorDuoComponent.open(this.dialogService, {
data: result,
});
duoComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.Duo);
data: {
authResponse: result,
},
});
this.twoFactorSetupSubscription = duoComp.componentInstance.onChangeStatus
.pipe(first(), takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
duoComp.close();
this.updateStatus(enabled, TwoFactorProviderType.Duo);
});
break;
}
case TwoFactorProviderType.Email: {
@ -175,12 +194,16 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
if (!result) {
return;
}
const authComp: DialogRef<boolean, any> = TwoFactorEmailComponent.open(this.dialogService, {
data: result,
});
authComp.componentInstance.onChangeStatus
.pipe(takeUntil(this.destroy$))
const emailComp: DialogRef<boolean, any> = TwoFactorEmailComponent.open(
this.dialogService,
{
data: result,
},
);
this.twoFactorSetupSubscription = emailComp.componentInstance.onChangeStatus
.pipe(first(), takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
emailComp.close();
this.updateStatus(enabled, TwoFactorProviderType.Email);
});
break;
@ -195,9 +218,12 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
this.dialogService,
{ data: result },
);
webAuthnComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.WebAuthn);
});
this.twoFactorSetupSubscription = webAuthnComp.componentInstance.onChangeStatus
.pipe(first(), takeUntil(this.destroy$))
.subscribe((enabled: boolean) => {
webAuthnComp.close();
this.updateStatus(enabled, TwoFactorProviderType.WebAuthn);
});
break;
}
default: