[PM-2056] update two factor duo dialog (#8976)
* migrating two factor duo component * migrating two factor duo component * two factor duo component migration * two factor duo component migration * removed null check from two-factor-setup * cleanup duo changes * remove ikey and skey references * clean up --------- Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
parent
b38629b4c9
commit
ab83e822f7
|
@ -1,6 +1,6 @@
|
||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { concatMap, takeUntil, map } from "rxjs";
|
import { concatMap, takeUntil, map, lastValueFrom } from "rxjs";
|
||||||
import { tap } from "rxjs/operators";
|
import { tap } from "rxjs/operators";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
@ -16,6 +16,7 @@ import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { TwoFactorDuoComponent } from "../../../auth/settings/two-factor-duo.component";
|
import { TwoFactorDuoComponent } from "../../../auth/settings/two-factor-duo.component";
|
||||||
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor-setup.component";
|
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor-setup.component";
|
||||||
|
import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor-verify.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-two-factor-setup",
|
selector: "app-two-factor-setup",
|
||||||
|
@ -65,21 +66,19 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
|
||||||
async manage(type: TwoFactorProviderType) {
|
async manage(type: TwoFactorProviderType) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TwoFactorProviderType.OrganizationDuo: {
|
case TwoFactorProviderType.OrganizationDuo: {
|
||||||
const result: AuthResponse<TwoFactorDuoResponse> = await this.callTwoFactorVerifyDialog(
|
const twoFactorVerifyDialogRef = TwoFactorVerifyComponent.open(this.dialogService, {
|
||||||
TwoFactorProviderType.OrganizationDuo,
|
data: { type: type, organizationId: this.organizationId },
|
||||||
|
});
|
||||||
|
const result: AuthResponse<TwoFactorDuoResponse> = await lastValueFrom(
|
||||||
|
twoFactorVerifyDialogRef.closed,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const duoComp = TwoFactorDuoComponent.open(this.dialogService, { data: result });
|
||||||
|
const enabled: boolean = await lastValueFrom(duoComp.closed);
|
||||||
|
this.updateStatus(enabled, TwoFactorProviderType.Duo);
|
||||||
|
|
||||||
const duoComp = await this.openModal(this.duoModalRef, TwoFactorDuoComponent);
|
|
||||||
duoComp.type = TwoFactorProviderType.OrganizationDuo;
|
|
||||||
duoComp.organizationId = this.organizationId;
|
|
||||||
duoComp.auth(result);
|
|
||||||
duoComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
|
|
||||||
this.updateStatus(enabled, TwoFactorProviderType.OrganizationDuo);
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,98 +1,58 @@
|
||||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="2faDuoTitle">
|
<form [formGroup]="formGroup" [bitSubmit]="submit" *ngIf="authed" autocomplete="off">
|
||||||
<div class="modal-dialog" role="document">
|
<bit-dialog>
|
||||||
<div class="modal-content">
|
<span bitDialogTitle>
|
||||||
<div class="modal-header">
|
{{ "twoStepLogin" | i18n }}
|
||||||
<h1 class="modal-title" id="2faDuoTitle">
|
<span bitTypography="body1">Duo</span>
|
||||||
{{ "twoStepLogin" | i18n }}
|
</span>
|
||||||
<small>Duo</small>
|
<ng-container bitDialogContent>
|
||||||
</h1>
|
<ng-container *ngIf="enabled">
|
||||||
<button
|
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
||||||
type="button"
|
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||||
class="close"
|
</app-callout>
|
||||||
data-dismiss="modal"
|
<img class="tw-float-right tw-ml-3 mfaType2" alt="Duo logo" />
|
||||||
appA11yTitle="{{ 'close' | i18n }}"
|
<strong>{{ "twoFactorDuoClientId" | i18n }}:</strong> {{ clientId }}
|
||||||
>
|
<br />
|
||||||
<span aria-hidden="true">×</span>
|
<strong>{{ "twoFactorDuoClientSecret" | i18n }}:</strong> {{ clientSecret }}
|
||||||
</button>
|
<br />
|
||||||
</div>
|
<strong>{{ "twoFactorDuoApiHostname" | i18n }}:</strong> {{ host }}
|
||||||
<form
|
</ng-container>
|
||||||
#form
|
<ng-container *ngIf="!enabled">
|
||||||
(ngSubmit)="submit()"
|
<img class="tw-float-right tw-ml-3 mfaType2" alt="Duo logo" />
|
||||||
[appApiAction]="formPromise"
|
<p bitTypography="body1">{{ "twoFactorDuoDesc" | i18n }}</p>
|
||||||
ngNativeValidate
|
<bit-form-field>
|
||||||
*ngIf="authed"
|
<bit-label>{{ "twoFactorDuoClientId" | i18n }}</bit-label>
|
||||||
autocomplete="off"
|
<input bitInput type="text" formControlName="clientId" appInputVerbatim />
|
||||||
>
|
</bit-form-field>
|
||||||
<div class="modal-body">
|
<bit-form-field>
|
||||||
<ng-container *ngIf="enabled">
|
<bit-label>{{ "twoFactorDuoClientSecret" | i18n }}</bit-label>
|
||||||
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
<input
|
||||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
bitInput
|
||||||
</app-callout>
|
type="password"
|
||||||
<img class="float-right ml-3 mfaType2" alt="Duo logo" />
|
formControlName="clientSecret"
|
||||||
<strong>{{ "twoFactorDuoClientId" | i18n }}:</strong> {{ clientId }}
|
appInputVerbatim
|
||||||
<br />
|
autocomplete="new-password"
|
||||||
<strong>{{ "twoFactorDuoClientSecret" | i18n }}:</strong> {{ clientSecret }}
|
/>
|
||||||
<br />
|
</bit-form-field>
|
||||||
<strong>{{ "twoFactorDuoApiHostname" | i18n }}:</strong> {{ host }}
|
<bit-form-field>
|
||||||
</ng-container>
|
<bit-label>{{ "twoFactorDuoApiHostname" | i18n }}</bit-label>
|
||||||
<ng-container *ngIf="!enabled">
|
<input
|
||||||
<img class="float-right ml-3 mfaType2" alt="Duo logo" />
|
bitInput
|
||||||
<p>{{ "twoFactorDuoDesc" | i18n }}</p>
|
type="text"
|
||||||
<div class="form-group">
|
formControlName="host"
|
||||||
<label for="ClientId">{{ "twoFactorDuoClientId" | i18n }}</label>
|
placeholder="{{ 'ex' | i18n }} api-xxxxxxxx.duosecurity.com"
|
||||||
<input
|
appInputVerbatim
|
||||||
id="ClientId"
|
/>
|
||||||
type="text"
|
</bit-form-field>
|
||||||
name="ClientId"
|
</ng-container>
|
||||||
class="form-control"
|
</ng-container>
|
||||||
[(ngModel)]="clientId"
|
<ng-container bitDialogFooter>
|
||||||
required
|
<button bitButton bitFormButton type="submit" buttonType="primary">
|
||||||
appInputVerbatim
|
<span *ngIf="!enabled">{{ "enable" | i18n }}</span>
|
||||||
/>
|
<span *ngIf="enabled">{{ "disable" | i18n }}</span>
|
||||||
</div>
|
</button>
|
||||||
<div class="form-group">
|
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
|
||||||
<label for="ClientSecret">{{ "twoFactorDuoClientSecret" | i18n }}</label>
|
{{ "close" | i18n }}
|
||||||
<input
|
</button>
|
||||||
id="ClientSecret"
|
</ng-container>
|
||||||
type="password"
|
</bit-dialog>
|
||||||
name="ClientSecret"
|
</form>
|
||||||
class="form-control"
|
|
||||||
[(ngModel)]="clientSecret"
|
|
||||||
required
|
|
||||||
appInputVerbatim
|
|
||||||
autocomplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="host">{{ "twoFactorDuoApiHostname" | i18n }}</label>
|
|
||||||
<input
|
|
||||||
id="host"
|
|
||||||
type="text"
|
|
||||||
name="Host"
|
|
||||||
class="form-control"
|
|
||||||
[(ngModel)]="host"
|
|
||||||
placeholder="{{ 'ex' | i18n }} api-xxxxxxxx.duosecurity.com"
|
|
||||||
required
|
|
||||||
appInputVerbatim
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span *ngIf="!enabled">{{ "enable" | i18n }}</span>
|
|
||||||
<span *ngIf="enabled">{{ "disable" | i18n }}</span>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
|
|
||||||
{{ "close" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { Component } from "@angular/core";
|
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||||
|
import { Component, EventEmitter, Inject, Output } from "@angular/core";
|
||||||
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||||
|
@ -18,21 +20,26 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component";
|
||||||
templateUrl: "two-factor-duo.component.html",
|
templateUrl: "two-factor-duo.component.html",
|
||||||
})
|
})
|
||||||
export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
|
export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
|
||||||
type = TwoFactorProviderType.Duo;
|
@Output() onChangeStatus: EventEmitter<boolean> = new EventEmitter();
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
host: string;
|
|
||||||
formPromise: Promise<TwoFactorDuoResponse>;
|
|
||||||
|
|
||||||
|
type = TwoFactorProviderType.Duo;
|
||||||
|
formGroup = this.formBuilder.group({
|
||||||
|
clientId: ["", [Validators.required]],
|
||||||
|
clientSecret: ["", [Validators.required]],
|
||||||
|
host: ["", [Validators.required]],
|
||||||
|
});
|
||||||
override componentName = "app-two-factor-duo";
|
override componentName = "app-two-factor-duo";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorDuoResponse>,
|
||||||
apiService: ApiService,
|
apiService: ApiService,
|
||||||
i18nService: I18nService,
|
i18nService: I18nService,
|
||||||
platformUtilsService: PlatformUtilsService,
|
platformUtilsService: PlatformUtilsService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
userVerificationService: UserVerificationService,
|
userVerificationService: UserVerificationService,
|
||||||
dialogService: DialogService,
|
dialogService: DialogService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private dialogRef: DialogRef,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
apiService,
|
apiService,
|
||||||
|
@ -44,43 +51,81 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
auth(authResponse: AuthResponse<TwoFactorDuoResponse>) {
|
get clientId() {
|
||||||
super.auth(authResponse);
|
return this.formGroup.get("clientId").value;
|
||||||
this.processResponse(authResponse.response);
|
}
|
||||||
|
get clientSecret() {
|
||||||
|
return this.formGroup.get("clientSecret").value;
|
||||||
|
}
|
||||||
|
get host() {
|
||||||
|
return this.formGroup.get("host").value;
|
||||||
|
}
|
||||||
|
set clientId(value: string) {
|
||||||
|
this.formGroup.get("clientId").setValue(value);
|
||||||
|
}
|
||||||
|
set clientSecret(value: string) {
|
||||||
|
this.formGroup.get("clientSecret").setValue(value);
|
||||||
|
}
|
||||||
|
set host(value: string) {
|
||||||
|
this.formGroup.get("host").setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
async ngOnInit() {
|
||||||
if (this.enabled) {
|
super.auth(this.data);
|
||||||
return super.disable(this.formPromise);
|
this.processResponse(this.data.response);
|
||||||
} else {
|
|
||||||
return this.enable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
submit = async () => {
|
||||||
|
this.formGroup.markAllAsTouched();
|
||||||
|
if (this.formGroup.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.enabled) {
|
||||||
|
await this.disableMethod();
|
||||||
|
} else {
|
||||||
|
await this.enable();
|
||||||
|
}
|
||||||
|
this.onChangeStatus.emit(this.enabled);
|
||||||
|
};
|
||||||
|
|
||||||
protected async enable() {
|
protected async enable() {
|
||||||
const request = await this.buildRequestModel(UpdateTwoFactorDuoRequest);
|
const request = await this.buildRequestModel(UpdateTwoFactorDuoRequest);
|
||||||
request.clientId = this.clientId;
|
request.clientId = this.clientId;
|
||||||
request.clientSecret = this.clientSecret;
|
request.clientSecret = this.clientSecret;
|
||||||
request.host = this.host;
|
request.host = this.host;
|
||||||
|
|
||||||
return super.enable(async () => {
|
let response: TwoFactorDuoResponse;
|
||||||
if (this.organizationId != null) {
|
|
||||||
this.formPromise = this.apiService.putTwoFactorOrganizationDuo(
|
if (this.organizationId != null) {
|
||||||
this.organizationId,
|
response = await this.apiService.putTwoFactorOrganizationDuo(this.organizationId, request);
|
||||||
request,
|
} else {
|
||||||
);
|
response = await this.apiService.putTwoFactorDuo(request);
|
||||||
} else {
|
}
|
||||||
this.formPromise = this.apiService.putTwoFactorDuo(request);
|
|
||||||
}
|
this.processResponse(response);
|
||||||
const response = await this.formPromise;
|
this.onUpdated.emit(true);
|
||||||
await this.processResponse(response);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
this.dialogRef.close(this.enabled);
|
||||||
|
};
|
||||||
|
|
||||||
private processResponse(response: TwoFactorDuoResponse) {
|
private processResponse(response: TwoFactorDuoResponse) {
|
||||||
this.clientId = response.clientId;
|
this.clientId = response.clientId;
|
||||||
this.clientSecret = response.clientSecret;
|
this.clientSecret = response.clientSecret;
|
||||||
this.host = response.host;
|
this.host = response.host;
|
||||||
this.enabled = response.enabled;
|
this.enabled = response.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strongly typed helper to open a TwoFactorDuoComponentComponent
|
||||||
|
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||||
|
* @param config Configuration for the dialog
|
||||||
|
*/
|
||||||
|
static open = (
|
||||||
|
dialogService: DialogService,
|
||||||
|
config: DialogConfig<AuthResponse<TwoFactorDuoResponse>>,
|
||||||
|
) => {
|
||||||
|
return dialogService.open<boolean>(TwoFactorDuoComponent, config);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,9 +161,10 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const duoComp = await this.openModal(this.duoModalRef, TwoFactorDuoComponent);
|
const duoComp: DialogRef<boolean, any> = TwoFactorDuoComponent.open(this.dialogService, {
|
||||||
duoComp.auth(result);
|
data: result,
|
||||||
duoComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
|
});
|
||||||
|
duoComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => {
|
||||||
this.updateStatus(enabled, TwoFactorProviderType.Duo);
|
this.updateStatus(enabled, TwoFactorProviderType.Duo);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue