configure duo 2fa
This commit is contained in:
parent
4b4bedaef3
commit
a097793b0d
|
@ -43,6 +43,7 @@ import { ProfileComponent } from './settings/profile.component';
|
||||||
import { PurgeVaultComponent } from './settings/purge-vault.component';
|
import { PurgeVaultComponent } from './settings/purge-vault.component';
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { TwoFactorAuthenticatorComponent } from './settings/two-factor-authenticator.component';
|
import { TwoFactorAuthenticatorComponent } from './settings/two-factor-authenticator.component';
|
||||||
|
import { TwoFactorDuoComponent } from './settings/two-factor-duo.component';
|
||||||
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
|
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
|
||||||
import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
|
import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
|
||||||
|
|
||||||
|
@ -147,6 +148,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||||
TrueFalseValueDirective,
|
TrueFalseValueDirective,
|
||||||
TwoFactorAuthenticatorComponent,
|
TwoFactorAuthenticatorComponent,
|
||||||
TwoFactorComponent,
|
TwoFactorComponent,
|
||||||
|
TwoFactorDuoComponent,
|
||||||
TwoFactorOptionsComponent,
|
TwoFactorOptionsComponent,
|
||||||
TwoFactorYubiKeyComponent,
|
TwoFactorYubiKeyComponent,
|
||||||
TwoFactorSetupComponent,
|
TwoFactorSetupComponent,
|
||||||
|
@ -168,6 +170,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
|
||||||
PurgeVaultComponent,
|
PurgeVaultComponent,
|
||||||
ShareComponent,
|
ShareComponent,
|
||||||
TwoFactorAuthenticatorComponent,
|
TwoFactorAuthenticatorComponent,
|
||||||
|
TwoFactorDuoComponent,
|
||||||
TwoFactorOptionsComponent,
|
TwoFactorOptionsComponent,
|
||||||
TwoFactorYubiKeyComponent,
|
TwoFactorYubiKeyComponent,
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title">
|
||||||
|
{{'twoStepLogin' | i18n}}
|
||||||
|
<small>Duo</small>
|
||||||
|
</h2>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form #authForm (ngSubmit)="auth()" [appApiAction]="authPromise" ngNativeValidate *ngIf="!authed">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>{{'twoStepLoginAuthDesc' | i18n}}</p>
|
||||||
|
<label for="masterPassword">{{'masterPass' | i18n}}</label>
|
||||||
|
<input id="masterPassword" type="password" name="MasterPasswordHash" class="form-control" [(ngModel)]="masterPassword" required
|
||||||
|
appAutoFocus>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button appBlurClick type="submit" class="btn btn-primary btn-submit" [disabled]="authForm.loading">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
<span>{{'continue' | i18n}}</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
|
||||||
|
<div class="modal-body">
|
||||||
|
<ng-container *ngIf="enabled">
|
||||||
|
<app-callout type="success" title="{{'enabled' | i18n}}" icon="fa-check-circle">
|
||||||
|
{{'twoStepLoginProviderEnabled' | i18n}}
|
||||||
|
</app-callout>
|
||||||
|
<img src="../../images/two-factor/2.png" class="float-right ml-3" alt="">
|
||||||
|
<strong>{{'twoFactorDuoIntegrationKey' | i18n}}:</strong> {{ikey}}
|
||||||
|
<br>
|
||||||
|
<strong>{{'twoFactorDuoSecretKey' | i18n}}:</strong> {{skey}}
|
||||||
|
<br>
|
||||||
|
<strong>{{'twoFactorDuoApiHostname' | i18n}}:</strong> {{host}}
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!enabled">
|
||||||
|
<img src="../../images/two-factor/2.png" class="float-right ml-3" alt="">
|
||||||
|
<p>{{'twoFactorDuoDesc' | i18n}}</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="ikey">{{'twoFactorDuoIntegrationKey' | i18n}}</label>
|
||||||
|
<input id="ikey" type="text" name="IntegrationKey" class="form-control" [(ngModel)]="ikey" required appInputVerbatim>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="skey">{{'twoFactorDuoSecretKey' | i18n}}</label>
|
||||||
|
<input id="skey" type="password" name="SecretKey" class="form-control" [(ngModel)]="skey" required>
|
||||||
|
</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 appBlurClick type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||||
|
<i class="fa fa-spinner fa-spin"></i>
|
||||||
|
<span *ngIf="!enabled">{{'save' | 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>
|
|
@ -0,0 +1,112 @@
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
|
||||||
|
import { ApiService } from 'jslib/abstractions/api.service';
|
||||||
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
|
|
||||||
|
import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerificationRequest';
|
||||||
|
import { TwoFactorProviderRequest } from 'jslib/models/request/twoFactorProviderRequest';
|
||||||
|
|
||||||
|
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
||||||
|
import { UpdateTwoFactorDuoRequest } from 'jslib/models/request/updateTwoFactorDuoRequest';
|
||||||
|
import { TwoFactorDuoResponse } from 'jslib/models/response/twoFactorDuoResponse';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-two-factor-duo',
|
||||||
|
templateUrl: 'two-factor-duo.component.html',
|
||||||
|
})
|
||||||
|
export class TwoFactorDuoComponent {
|
||||||
|
@Output() onUpdated = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
enabled = false;
|
||||||
|
authed = false;
|
||||||
|
ikey: string;
|
||||||
|
skey: string;
|
||||||
|
host: string;
|
||||||
|
masterPassword: string;
|
||||||
|
|
||||||
|
authPromise: Promise<any>;
|
||||||
|
formPromise: Promise<any>;
|
||||||
|
|
||||||
|
private masterPasswordHash: string;
|
||||||
|
|
||||||
|
constructor(private apiService: ApiService, private i18nService: I18nService,
|
||||||
|
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||||
|
private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService) { }
|
||||||
|
|
||||||
|
async auth() {
|
||||||
|
if (this.masterPassword == null || this.masterPassword === '') {
|
||||||
|
this.toasterService.popAsync('error', this.i18nService.t('errorOccurred'),
|
||||||
|
this.i18nService.t('masterPassRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = new PasswordVerificationRequest();
|
||||||
|
request.masterPasswordHash = this.masterPasswordHash =
|
||||||
|
await this.cryptoService.hashPassword(this.masterPassword, null);
|
||||||
|
try {
|
||||||
|
this.authPromise = this.apiService.getTwoFactorDuo(request);
|
||||||
|
const response = await this.authPromise;
|
||||||
|
this.authed = true;
|
||||||
|
await this.processResponse(response);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
async submit() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.disable();
|
||||||
|
} else {
|
||||||
|
this.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enable() {
|
||||||
|
const request = new UpdateTwoFactorDuoRequest();
|
||||||
|
request.masterPasswordHash = this.masterPasswordHash;
|
||||||
|
request.integrationKey = this.ikey;
|
||||||
|
request.secretKey = this.skey;
|
||||||
|
request.host = this.host;
|
||||||
|
try {
|
||||||
|
this.formPromise = this.apiService.putTwoFactorDuo(request);
|
||||||
|
const response = await this.formPromise;
|
||||||
|
await this.processResponse(response);
|
||||||
|
this.analytics.eventTrack.next({ action: 'Enabled Two-step Duo' });
|
||||||
|
this.onUpdated.emit(true);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async disable() {
|
||||||
|
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('twoStepDisableDesc'),
|
||||||
|
this.i18nService.t('disable'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = new TwoFactorProviderRequest();
|
||||||
|
request.masterPasswordHash = this.masterPasswordHash;
|
||||||
|
request.type = TwoFactorProviderType.Duo;
|
||||||
|
this.formPromise = this.apiService.putTwoFactorDisable(request);
|
||||||
|
await this.formPromise;
|
||||||
|
this.enabled = false;
|
||||||
|
this.analytics.eventTrack.next({ action: 'Disabled Two-step Duo' });
|
||||||
|
this.toasterService.popAsync('success', null, this.i18nService.t('twoStepDisabled'));
|
||||||
|
this.onUpdated.emit(false);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processResponse(response: TwoFactorDuoResponse) {
|
||||||
|
this.ikey = response.integrationKey;
|
||||||
|
this.skey = response.secretKey;
|
||||||
|
this.host = response.host;
|
||||||
|
this.enabled = response.enabled;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
<h2 class="mt-5">
|
<h2 class="mt-5">
|
||||||
{{'providers' | i18n}}
|
{{'providers' | i18n}}
|
||||||
<small *ngIf="loading">
|
<small *ngIf="loading">
|
||||||
<i class="fa fa-spinner fa-spin text-muted"></i>
|
<i class="fa fa-spinner fa-spin fa-fw text-muted"></i>
|
||||||
</small>
|
</small>
|
||||||
</h2>
|
</h2>
|
||||||
<ul class="list-group list-group-2fa">
|
<ul class="list-group list-group-2fa">
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
|
||||||
import { ModalComponent } from '../modal.component';
|
import { ModalComponent } from '../modal.component';
|
||||||
|
|
||||||
import { TwoFactorAuthenticatorComponent } from './two-factor-authenticator.component';
|
import { TwoFactorAuthenticatorComponent } from './two-factor-authenticator.component';
|
||||||
|
import { TwoFactorDuoComponent } from './two-factor-duo.component';
|
||||||
import { TwoFactorYubiKeyComponent } from './two-factor-yubikey.component';
|
import { TwoFactorYubiKeyComponent } from './two-factor-yubikey.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -84,13 +85,19 @@ export class TwoFactorSetupComponent implements OnInit {
|
||||||
case TwoFactorProviderType.Authenticator:
|
case TwoFactorProviderType.Authenticator:
|
||||||
const authComp = this.openModal(this.authenticatorModalRef, TwoFactorAuthenticatorComponent);
|
const authComp = this.openModal(this.authenticatorModalRef, TwoFactorAuthenticatorComponent);
|
||||||
authComp.onUpdated.subscribe((enabled: boolean) => {
|
authComp.onUpdated.subscribe((enabled: boolean) => {
|
||||||
this.updateStatus(enabled, TwoFactorProviderType.Authenticator)
|
this.updateStatus(enabled, TwoFactorProviderType.Authenticator);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case TwoFactorProviderType.Yubikey:
|
case TwoFactorProviderType.Yubikey:
|
||||||
const yubiComp = this.openModal(this.yubikeyModalRef, TwoFactorYubiKeyComponent);
|
const yubiComp = this.openModal(this.yubikeyModalRef, TwoFactorYubiKeyComponent);
|
||||||
yubiComp.onUpdated.subscribe((enabled: boolean) => {
|
yubiComp.onUpdated.subscribe((enabled: boolean) => {
|
||||||
this.updateStatus(enabled, TwoFactorProviderType.Yubikey)
|
this.updateStatus(enabled, TwoFactorProviderType.Yubikey);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case TwoFactorProviderType.Duo:
|
||||||
|
const duoComp = this.openModal(this.duoModalRef, TwoFactorDuoComponent);
|
||||||
|
duoComp.onUpdated.subscribe((enabled: boolean) => {
|
||||||
|
this.updateStatus(enabled, TwoFactorProviderType.Duo);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1074,5 +1074,17 @@
|
||||||
},
|
},
|
||||||
"disableAllKeys": {
|
"disableAllKeys": {
|
||||||
"message": "Disable All Keys"
|
"message": "Disable All Keys"
|
||||||
|
},
|
||||||
|
"twoFactorDuoDesc": {
|
||||||
|
"message": "Enter the Bitwarden application information from your Duo Admin panel."
|
||||||
|
},
|
||||||
|
"twoFactorDuoIntegrationKey": {
|
||||||
|
"message": "Integration Key"
|
||||||
|
},
|
||||||
|
"twoFactorDuoSecretKey": {
|
||||||
|
"message": "Secret Key"
|
||||||
|
},
|
||||||
|
"twoFactorDuoApiHostname": {
|
||||||
|
"message": "API Hostname"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue