[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 { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, takeUntil, map } from "rxjs";
|
||||
import { concatMap, takeUntil, map, lastValueFrom } from "rxjs";
|
||||
import { tap } from "rxjs/operators";
|
||||
|
||||
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 { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor-setup.component";
|
||||
import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor-verify.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor-setup",
|
||||
|
@ -65,21 +66,19 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
|
|||
async manage(type: TwoFactorProviderType) {
|
||||
switch (type) {
|
||||
case TwoFactorProviderType.OrganizationDuo: {
|
||||
const result: AuthResponse<TwoFactorDuoResponse> = await this.callTwoFactorVerifyDialog(
|
||||
TwoFactorProviderType.OrganizationDuo,
|
||||
const twoFactorVerifyDialogRef = TwoFactorVerifyComponent.open(this.dialogService, {
|
||||
data: { type: type, organizationId: this.organizationId },
|
||||
});
|
||||
const result: AuthResponse<TwoFactorDuoResponse> = await lastValueFrom(
|
||||
twoFactorVerifyDialogRef.closed,
|
||||
);
|
||||
|
||||
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 = 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;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -1,98 +1,58 @@
|
|||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="2faDuoTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title" id="2faDuoTitle">
|
||||
{{ "twoStepLogin" | i18n }}
|
||||
<small>Duo</small>
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
appA11yTitle="{{ 'close' | i18n }}"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form
|
||||
#form
|
||||
(ngSubmit)="submit()"
|
||||
[appApiAction]="formPromise"
|
||||
ngNativeValidate
|
||||
*ngIf="authed"
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<ng-container *ngIf="enabled">
|
||||
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||
</app-callout>
|
||||
<img class="float-right ml-3 mfaType2" alt="Duo logo" />
|
||||
<strong>{{ "twoFactorDuoClientId" | i18n }}:</strong> {{ clientId }}
|
||||
<br />
|
||||
<strong>{{ "twoFactorDuoClientSecret" | i18n }}:</strong> {{ clientSecret }}
|
||||
<br />
|
||||
<strong>{{ "twoFactorDuoApiHostname" | i18n }}:</strong> {{ host }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!enabled">
|
||||
<img class="float-right ml-3 mfaType2" alt="Duo logo" />
|
||||
<p>{{ "twoFactorDuoDesc" | i18n }}</p>
|
||||
<div class="form-group">
|
||||
<label for="ClientId">{{ "twoFactorDuoClientId" | i18n }}</label>
|
||||
<input
|
||||
id="ClientId"
|
||||
type="text"
|
||||
name="ClientId"
|
||||
class="form-control"
|
||||
[(ngModel)]="clientId"
|
||||
required
|
||||
appInputVerbatim
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ClientSecret">{{ "twoFactorDuoClientSecret" | i18n }}</label>
|
||||
<input
|
||||
id="ClientSecret"
|
||||
type="password"
|
||||
name="ClientSecret"
|
||||
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>
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit" *ngIf="authed" autocomplete="off">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>
|
||||
{{ "twoStepLogin" | i18n }}
|
||||
<span bitTypography="body1">Duo</span>
|
||||
</span>
|
||||
<ng-container bitDialogContent>
|
||||
<ng-container *ngIf="enabled">
|
||||
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||
</app-callout>
|
||||
<img class="tw-float-right tw-ml-3 mfaType2" alt="Duo logo" />
|
||||
<strong>{{ "twoFactorDuoClientId" | i18n }}:</strong> {{ clientId }}
|
||||
<br />
|
||||
<strong>{{ "twoFactorDuoClientSecret" | i18n }}:</strong> {{ clientSecret }}
|
||||
<br />
|
||||
<strong>{{ "twoFactorDuoApiHostname" | i18n }}:</strong> {{ host }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!enabled">
|
||||
<img class="tw-float-right tw-ml-3 mfaType2" alt="Duo logo" />
|
||||
<p bitTypography="body1">{{ "twoFactorDuoDesc" | i18n }}</p>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "twoFactorDuoClientId" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="clientId" appInputVerbatim />
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "twoFactorDuoClientSecret" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="password"
|
||||
formControlName="clientSecret"
|
||||
appInputVerbatim
|
||||
autocomplete="new-password"
|
||||
/>
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "twoFactorDuoApiHostname" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="text"
|
||||
formControlName="host"
|
||||
placeholder="{{ 'ex' | i18n }} api-xxxxxxxx.duosecurity.com"
|
||||
appInputVerbatim
|
||||
/>
|
||||
</bit-form-field>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton bitFormButton type="submit" buttonType="primary">
|
||||
<span *ngIf="!enabled">{{ "enable" | i18n }}</span>
|
||||
<span *ngIf="enabled">{{ "disable" | i18n }}</span>
|
||||
</button>
|
||||
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
|
|
|
@ -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 { 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",
|
||||
})
|
||||
export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
|
||||
type = TwoFactorProviderType.Duo;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
host: string;
|
||||
formPromise: Promise<TwoFactorDuoResponse>;
|
||||
@Output() onChangeStatus: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
type = TwoFactorProviderType.Duo;
|
||||
formGroup = this.formBuilder.group({
|
||||
clientId: ["", [Validators.required]],
|
||||
clientSecret: ["", [Validators.required]],
|
||||
host: ["", [Validators.required]],
|
||||
});
|
||||
override componentName = "app-two-factor-duo";
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorDuoResponse>,
|
||||
apiService: ApiService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
userVerificationService: UserVerificationService,
|
||||
dialogService: DialogService,
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogRef: DialogRef,
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
|
@ -44,43 +51,81 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
|
|||
);
|
||||
}
|
||||
|
||||
auth(authResponse: AuthResponse<TwoFactorDuoResponse>) {
|
||||
super.auth(authResponse);
|
||||
this.processResponse(authResponse.response);
|
||||
get clientId() {
|
||||
return this.formGroup.get("clientId").value;
|
||||
}
|
||||
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() {
|
||||
if (this.enabled) {
|
||||
return super.disable(this.formPromise);
|
||||
} else {
|
||||
return this.enable();
|
||||
}
|
||||
async ngOnInit() {
|
||||
super.auth(this.data);
|
||||
this.processResponse(this.data.response);
|
||||
}
|
||||
|
||||
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() {
|
||||
const request = await this.buildRequestModel(UpdateTwoFactorDuoRequest);
|
||||
request.clientId = this.clientId;
|
||||
request.clientSecret = this.clientSecret;
|
||||
request.host = this.host;
|
||||
|
||||
return super.enable(async () => {
|
||||
if (this.organizationId != null) {
|
||||
this.formPromise = this.apiService.putTwoFactorOrganizationDuo(
|
||||
this.organizationId,
|
||||
request,
|
||||
);
|
||||
} else {
|
||||
this.formPromise = this.apiService.putTwoFactorDuo(request);
|
||||
}
|
||||
const response = await this.formPromise;
|
||||
await this.processResponse(response);
|
||||
});
|
||||
let response: TwoFactorDuoResponse;
|
||||
|
||||
if (this.organizationId != null) {
|
||||
response = await this.apiService.putTwoFactorOrganizationDuo(this.organizationId, request);
|
||||
} else {
|
||||
response = await this.apiService.putTwoFactorDuo(request);
|
||||
}
|
||||
|
||||
this.processResponse(response);
|
||||
this.onUpdated.emit(true);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.dialogRef.close(this.enabled);
|
||||
};
|
||||
|
||||
private processResponse(response: TwoFactorDuoResponse) {
|
||||
this.clientId = response.clientId;
|
||||
this.clientSecret = response.clientSecret;
|
||||
this.host = response.host;
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
const duoComp = await this.openModal(this.duoModalRef, TwoFactorDuoComponent);
|
||||
duoComp.auth(result);
|
||||
duoComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => {
|
||||
const duoComp: DialogRef<boolean, any> = TwoFactorDuoComponent.open(this.dialogService, {
|
||||
data: result,
|
||||
});
|
||||
duoComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => {
|
||||
this.updateStatus(enabled, TwoFactorProviderType.Duo);
|
||||
});
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue