yubikey 2fa config

This commit is contained in:
Kyle Spearrin 2018-06-27 13:45:11 -04:00
parent 246d605e5c
commit 4b4bedaef3
7 changed files with 278 additions and 3 deletions

View File

@ -44,6 +44,7 @@ import { PurgeVaultComponent } from './settings/purge-vault.component';
import { SettingsComponent } from './settings/settings.component';
import { TwoFactorAuthenticatorComponent } from './settings/two-factor-authenticator.component';
import { TwoFactorSetupComponent } from './settings/two-factor-setup.component';
import { TwoFactorYubiKeyComponent } from './settings/two-factor-yubikey.component';
import { ExportComponent } from './tools/export.component';
import { ImportComponent } from './tools/import.component';
@ -147,6 +148,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
TwoFactorAuthenticatorComponent,
TwoFactorComponent,
TwoFactorOptionsComponent,
TwoFactorYubiKeyComponent,
TwoFactorSetupComponent,
UserLayoutComponent,
VaultComponent,
@ -167,6 +169,7 @@ import { SearchCiphersPipe } from 'jslib/angular/pipes/search-ciphers.pipe';
ShareComponent,
TwoFactorAuthenticatorComponent,
TwoFactorOptionsComponent,
TwoFactorYubiKeyComponent,
],
providers: [],
bootstrap: [AppComponent],

View File

@ -28,6 +28,7 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
<div class="modal-body">
<ng-container *ngIf="!enabled">
<img src="../../images/two-factor/0.png" class="float-right" alt="">
<p>{{'twoStepAuthenticatorDesc' | i18n}}</p>
<p>
<strong>1. {{'twoStepAuthenticatorDownloadApp' | i18n}}</strong>
@ -38,6 +39,7 @@
<p>{{'twoStepLoginProviderEnabled' | i18n}}</p>
{{'twoStepAuthenticatorReaddDesc' | i18n}}
</app-callout>
<img src="../../images/two-factor/0.png" class="float-right" alt="">
<p>{{'twoStepAuthenticatorNeedApp' | i18n}}</p>
</ng-container>
<ul class="fa-ul">
@ -66,7 +68,7 @@
</p>
<ng-container *ngIf="!enabled">
<label for="token">3. {{'twoStepAuthenticatorEnterCode' | i18n}}</label>
<input id="token" type="text" name="Token" class="form-control" [(ngModel)]="token" required>
<input id="token" type="text" name="Token" class="form-control" [(ngModel)]="token" required appInputVerbatim>
</ng-container>
</div>
<div class="modal-footer">

View File

@ -17,6 +17,7 @@ import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { ModalComponent } from '../modal.component';
import { TwoFactorAuthenticatorComponent } from './two-factor-authenticator.component';
import { TwoFactorYubiKeyComponent } from './two-factor-yubikey.component';
@Component({
selector: 'app-two-factor-setup',
@ -81,11 +82,17 @@ export class TwoFactorSetupComponent implements OnInit {
manage(type: TwoFactorProviderType) {
switch (type) {
case TwoFactorProviderType.Authenticator:
const component = this.openModal(this.authenticatorModalRef, TwoFactorAuthenticatorComponent);
component.onUpdated.subscribe((enabled: boolean) => {
const authComp = this.openModal(this.authenticatorModalRef, TwoFactorAuthenticatorComponent);
authComp.onUpdated.subscribe((enabled: boolean) => {
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)
});
break;
default:
break;
}

View File

@ -0,0 +1,83 @@
<div class="modal fade">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">
{{'twoStepLogin' | i18n}}
<small>{{'yubiKeyTitle' | i18n}}</small>
</h2>
<button type="button" class="close" data-dismiss="modal" attr.aria-label="{{'close' | i18n}}">
<span aria-hidden="true">&times;</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">
<app-callout type="success" title="{{'enabled' | i18n}}" icon="fa-check-circle" *ngIf="enabled">
{{'twoStepLoginProviderEnabled' | i18n}}
</app-callout>
<app-callout type="warning">
<p>{{'twoFactorYubikeyWarning' | i18n}}</p>
<ul class="mb-0">
<li>{{'twoFactorYubikeySupportUsb' | i18n}}</li>
<li>{{'twoFactorYubikeySupportMobile' | i18n}}</li>
</ul>
</app-callout>
<img src="../../images/two-factor/3.png" class="float-right" alt="">
<p>{{'twoFactorYubikeyAdd' | i18n}}:</p>
<ol>
<li>{{'twoFactorYubikeyPlugIn' | i18n}}</li>
<li>{{'twoFactorYubikeySelectKey' | i18n}}</li>
<li>{{'twoFactorYubikeyTouchButton' | i18n}}</li>
<li>{{'twoFactorYubikeySaveForm' | i18n}}</li>
</ol>
<hr>
<div class="row">
<div class="form-group col-6" *ngFor="let k of keys; let i = index">
<label for="key{{i + 1}}">{{'yubikeyX' | i18n : i + 1}}</label>
<input id="key{{i + 1}}" type="text" name="Key{{i + 1}}" class="form-control" [(ngModel)]="k.key" *ngIf="!k.existingKey" appInputVerbatim>
<div class="d-flex" *ngIf="k.existingKey">
<span class="mr-2">{{k.existingKey}}</span>
<button type="button" class="btn btn-link text-danger ml-auto" appBlurClick (click)="remove(k)" title="{{'remove' | i18n}}">
<i class="fa fa-minus-circle fa-lg"></i>
</button>
</div>
</div>
</div>
<strong class="d-block mb-2">{{'nfcSupport' | i18n}}</strong>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">{{'twoFactorYubikeySupportsNfc' | i18n}}</label>
</div>
<small class="form-text text-muted">{{'twoFactorYubikeySupportsNfcDesc' | i18n}}</small>
</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>{{'save' | i18n}}</span>
</button>
<button #disableBtn appBlurClick type="button" class="btn btn-outline-secondary btn-submit" [appApiAction]="disablePromise"
[disabled]="disableBtn.loading" (click)="disable()">
<i class="fa fa-spinner fa-spin"></i>
<span>{{'disableAllKeys' | i18n}}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">{{'close' | i18n}}</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,130 @@
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 { UpdateTwoFactorYubioOtpRequest } from 'jslib/models/request/updateTwoFactorYubioOtpRequest';
import { TwoFactorYubiKeyResponse } from 'jslib/models/response/twoFactorYubiKeyResponse';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
@Component({
selector: 'app-two-factor-yubikey',
templateUrl: 'two-factor-yubikey.component.html',
})
export class TwoFactorYubiKeyComponent {
@Output() onUpdated = new EventEmitter<boolean>();
enabled = false;
authed = false;
keys: any[];
nfc = false;
masterPassword: string;
authPromise: Promise<TwoFactorYubiKeyResponse>;
formPromise: Promise<any>;
disablePromise: 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.getTwoFactorYubiKey(request);
const response = await this.authPromise;
this.authed = true;
this.processResponse(response);
} catch { }
}
async submit() {
const request = new UpdateTwoFactorYubioOtpRequest();
request.masterPasswordHash = this.masterPasswordHash;
request.key1 = this.keys != null && this.keys.length > 0 ? this.keys[0].key : null;
request.key2 = this.keys != null && this.keys.length > 1 ? this.keys[1].key : null;
request.key3 = this.keys != null && this.keys.length > 2 ? this.keys[2].key : null;
request.key4 = this.keys != null && this.keys.length > 3 ? this.keys[3].key : null;
request.key5 = this.keys != null && this.keys.length > 4 ? this.keys[4].key : null;
request.nfc = this.nfc;
try {
this.formPromise = this.apiService.putTwoFactorYubiKey(request);
const response = await this.formPromise;
await this.processResponse(response);
this.analytics.eventTrack.next({ action: 'Enabled Two-step YubiKey' });
this.processResponse(response);
this.toasterService.popAsync('success', null, this.i18nService.t('yubikeysUpdated'));
this.onUpdated.emit(true);
} catch { }
}
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.Yubikey;
this.disablePromise = this.apiService.putTwoFactorDisable(request);
await this.disablePromise;
this.enabled = false;
this.analytics.eventTrack.next({ action: 'Disabled Two-step YubiKey' });
this.toasterService.popAsync('success', null, this.i18nService.t('twoStepDisabled'));
this.onUpdated.emit(false);
} catch { }
}
remove(key: any) {
key.existingKey = null;
key.key = null;
}
private processResponse(response: TwoFactorYubiKeyResponse) {
this.enabled = response.enabled;
this.keys = [
{ key: response.key1, existingKey: this.padRight(response.key1) },
{ key: response.key2, existingKey: this.padRight(response.key2) },
{ key: response.key3, existingKey: this.padRight(response.key3) },
{ key: response.key4, existingKey: this.padRight(response.key4) },
{ key: response.key5, existingKey: this.padRight(response.key5) },
];
this.nfc = response.nfc || !response.enabled;
}
private padRight(str: string, character = '•', size = 44) {
if (str == null || character == null || str.length >= size) {
return str;
}
const max = (size - str.length) / character.length;
for (let i = 0; i < max; i++) {
str += character;
}
return str;
}
}

View File

@ -1026,5 +1026,53 @@
},
"twoStepDisabled": {
"message": "Two-step login provider disabled."
},
"twoFactorYubikeyAdd": {
"message": "Add a new YubiKey to your account"
},
"twoFactorYubikeyPlugIn": {
"message": "Plug the YubiKey (NEO or 4 series) into your computer's USB port."
},
"twoFactorYubikeySelectKey": {
"message": "Select in the first empty YubiKey input field below."
},
"twoFactorYubikeyTouchButton": {
"message": "Touch the YubiKey's button."
},
"twoFactorYubikeySaveForm": {
"message": "Save the form."
},
"twoFactorYubikeyWarning": {
"message": "Due to platform limitations, YubiKeys cannot be used on all Bitwarden applications. You should enable another two-step login provider so that you can access your account when YubiKeys cannot be used. Supported platforms:"
},
"twoFactorYubikeySupportUsb": {
"message": "Web vault, desktop application, CLI, and all browser extensions on a device with a USB port that can accept your YubiKey."
},
"twoFactorYubikeySupportMobile": {
"message": "Mobile apps on a device with NFC capabilities or a USB port that can accept your YubiKey."
},
"yubikeyX": {
"message": "YubiKey $INDEX$",
"placeholders": {
"index": {
"content": "$1",
"example": "2"
}
}
},
"nfcSupport": {
"message": "NFC Support"
},
"twoFactorYubikeySupportsNfc": {
"message": "One of my keys supports NFC."
},
"twoFactorYubikeySupportsNfcDesc": {
"message": "If one of your YubiKeys supports NFC (such as a YubiKey NEO), you will be prompted on mobile devices whenever NFC availability is detected."
},
"yubikeysUpdated": {
"message": "YubiKeys updated"
},
"disableAllKeys": {
"message": "Disable All Keys"
}
}

View File

@ -25,6 +25,8 @@ $h4-font-size: 1rem;
$h5-font-size: 1rem;
$h6-font-size: 1rem;
$code-font-size: 100%;
$navbar-padding-y: .75rem;
$grid-gutter-width: 20px;
$card-spacer-y: .6rem;