From a097793b0dbb85ca13a06e9d180d78b37b65407d Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 27 Jun 2018 15:27:59 -0400 Subject: [PATCH] configure duo 2fa --- src/app/app.module.ts | 3 + .../settings/two-factor-duo.component.html | 70 +++++++++++ src/app/settings/two-factor-duo.component.ts | 112 ++++++++++++++++++ .../settings/two-factor-setup.component.html | 2 +- .../settings/two-factor-setup.component.ts | 11 +- src/locales/en/messages.json | 12 ++ 6 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 src/app/settings/two-factor-duo.component.html create mode 100644 src/app/settings/two-factor-duo.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aa55249438..bf08d3ad56 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -43,6 +43,7 @@ import { ProfileComponent } from './settings/profile.component'; import { PurgeVaultComponent } from './settings/purge-vault.component'; import { SettingsComponent } from './settings/settings.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 { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component'; @@ -147,6 +148,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; TrueFalseValueDirective, TwoFactorAuthenticatorComponent, TwoFactorComponent, + TwoFactorDuoComponent, TwoFactorOptionsComponent, TwoFactorYubiKeyComponent, TwoFactorSetupComponent, @@ -168,6 +170,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe'; PurgeVaultComponent, ShareComponent, TwoFactorAuthenticatorComponent, + TwoFactorDuoComponent, TwoFactorOptionsComponent, TwoFactorYubiKeyComponent, ], diff --git a/src/app/settings/two-factor-duo.component.html b/src/app/settings/two-factor-duo.component.html new file mode 100644 index 0000000000..da2fcb2edf --- /dev/null +++ b/src/app/settings/two-factor-duo.component.html @@ -0,0 +1,70 @@ + diff --git a/src/app/settings/two-factor-duo.component.ts b/src/app/settings/two-factor-duo.component.ts new file mode 100644 index 0000000000..68ec8d2736 --- /dev/null +++ b/src/app/settings/two-factor-duo.component.ts @@ -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(); + + enabled = false; + authed = false; + ikey: string; + skey: string; + host: string; + masterPassword: string; + + authPromise: Promise; + formPromise: Promise; + + 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; + } +} diff --git a/src/app/settings/two-factor-setup.component.html b/src/app/settings/two-factor-setup.component.html index 13a9e077d0..a09d738b74 100644 --- a/src/app/settings/two-factor-setup.component.html +++ b/src/app/settings/two-factor-setup.component.html @@ -8,7 +8,7 @@

{{'providers' | i18n}} - +

    diff --git a/src/app/settings/two-factor-setup.component.ts b/src/app/settings/two-factor-setup.component.ts index 22dbe6c853..9d13c3720f 100644 --- a/src/app/settings/two-factor-setup.component.ts +++ b/src/app/settings/two-factor-setup.component.ts @@ -17,6 +17,7 @@ import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType'; import { ModalComponent } from '../modal.component'; import { TwoFactorAuthenticatorComponent } from './two-factor-authenticator.component'; +import { TwoFactorDuoComponent } from './two-factor-duo.component'; import { TwoFactorYubiKeyComponent } from './two-factor-yubikey.component'; @Component({ @@ -84,13 +85,19 @@ export class TwoFactorSetupComponent implements OnInit { case TwoFactorProviderType.Authenticator: const authComp = this.openModal(this.authenticatorModalRef, TwoFactorAuthenticatorComponent); authComp.onUpdated.subscribe((enabled: boolean) => { - this.updateStatus(enabled, TwoFactorProviderType.Authenticator) + this.updateStatus(enabled, TwoFactorProviderType.Authenticator); }); break; case TwoFactorProviderType.Yubikey: const yubiComp = this.openModal(this.yubikeyModalRef, TwoFactorYubiKeyComponent); 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; default: diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json index 56eb78a96f..f88f75dfce 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json @@ -1074,5 +1074,17 @@ }, "disableAllKeys": { "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" } }