org 2fa setting for duo

This commit is contained in:
Kyle Spearrin 2018-07-18 17:10:26 -04:00
parent 5131ebb9c9
commit ee4d2400c9
23 changed files with 148 additions and 34 deletions

2
jslib

@ -1 +1 @@
Subproject commit f4ed6a5566b49e1a7175d931408bbe14e4dc0af7
Subproject commit e555536f2413927508db91affa371560ba633c88

View File

@ -28,6 +28,9 @@ import { PeopleComponent as OrgPeopleComponent } from './organizations/manage/pe
import { AccountComponent as OrgAccountComponent } from './organizations/settings/account.component';
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
import { SettingsComponent as OrgSettingsComponent } from './organizations/settings/settings.component';
import {
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
} from './organizations/settings/two-factor-setup.component';
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
@ -188,6 +191,7 @@ const routes: Routes = [
children: [
{ path: '', pathMatch: 'full', redirectTo: 'account' },
{ path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } },
{ path: 'two-factor', component: OrgTwoFactorSetupComponent, data: { titleId: 'twoStepLogin' } },
{
path: 'billing',
component: OrganizationBillingComponent,

View File

@ -56,6 +56,9 @@ import { AdjustSeatsComponent } from './organizations/settings/adjust-seats.comp
import { DeleteOrganizationComponent } from './organizations/settings/delete-organization.component';
import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component';
import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component';
import {
TwoFactorSetupComponent as OrgTwoFactorSetupComponent,
} from './organizations/settings/two-factor-setup.component';
import { ExportComponent as OrgExportComponent } from './organizations/tools/export.component';
import { ImportComponent as OrgImportComponent } from './organizations/tools/import.component';
@ -210,6 +213,7 @@ import { SearchPipe } from 'jslib/angular/pipes/search.pipe';
OrgPeopleComponent,
OrgSettingComponent,
OrgToolsComponent,
OrgTwoFactorSetupComponent,
OrgUserAddEditComponent,
OrgUserGroupsComponent,
OrganizationsComponent,

View File

@ -10,6 +10,9 @@
<a routerLink="billing" class="list-group-item" routerLinkActive="active">
{{'billingAndLicensing' | i18n}}
</a>
<a routerLink="two-factor" class="list-group-item" routerLinkActive="active" *ngIf="access2fa">
{{'twoStepLogin' | i18n}}
</a>
</div>
</div>
</div>

View File

@ -1,7 +1,21 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UserService } from 'jslib/abstractions/user.service';
@Component({
selector: 'app-org-settings',
templateUrl: 'settings.component.html',
})
export class SettingsComponent { }
export class SettingsComponent {
access2fa = false;
constructor(private route: ActivatedRoute, private userService: UserService) { }
ngOnInit() {
this.route.parent.params.subscribe(async (params) => {
const organization = await this.userService.getOrganization(params.organizationId);
this.access2fa = organization.use2fa;
});
}
}

View File

@ -0,0 +1,58 @@
import {
Component,
ComponentFactoryResolver,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApiService } from 'jslib/abstractions/api.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { TokenService } from 'jslib/abstractions/token.service';
import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
import { TwoFactorDuoComponent } from '../../settings/two-factor-duo.component';
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from '../../settings/two-factor-setup.component';
@Component({
selector: 'app-two-factor-setup',
templateUrl: '../../settings/two-factor-setup.component.html',
})
export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
organizationId: string;
constructor(apiService: ApiService, tokenService: TokenService,
componentFactoryResolver: ComponentFactoryResolver, messagingService: MessagingService,
private route: ActivatedRoute) {
super(apiService, tokenService, componentFactoryResolver, messagingService);
}
async ngOnInit() {
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
await super.ngOnInit();
});
}
manage(type: TwoFactorProviderType) {
switch (type) {
case TwoFactorProviderType.OrganizationDuo:
const duoComp = this.openModal(this.duoModalRef, TwoFactorDuoComponent);
duoComp.type = TwoFactorProviderType.OrganizationDuo;
duoComp.organizationId = this.organizationId;
duoComp.onUpdated.subscribe((enabled: boolean) => {
this.updateStatus(enabled, TwoFactorProviderType.OrganizationDuo);
});
break;
default:
break;
}
}
protected getTwoFactorProviders() {
return this.apiService.getTwoFactorOrganizationProviders(this.organizationId);
}
protected filterProvider(type: TwoFactorProviderType) {
return type !== TwoFactorProviderType.OrganizationDuo;
}
}

View File

@ -10,7 +10,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify [type]="twoFactorProviderType.Authenticator" (onAuthed)="auth($event)" *ngIf="!authed">
<app-two-factor-verify [organizationId]="organizationId" [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
<div class="modal-body">

View File

@ -24,6 +24,7 @@ import { TwoFactorBaseComponent } from './two-factor-base.component';
templateUrl: 'two-factor-authenticator.component.html',
})
export class TwoFactorAuthenticatorComponent extends TwoFactorBaseComponent implements OnInit, OnDestroy {
type = TwoFactorProviderType.Authenticator;
key: string;
token: string;
formPromise: Promise<any>;
@ -33,8 +34,7 @@ export class TwoFactorAuthenticatorComponent extends TwoFactorBaseComponent impl
constructor(apiService: ApiService, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService,
private userService: UserService, platformUtilsService: PlatformUtilsService) {
super(apiService, i18nService, analytics, toasterService, platformUtilsService,
TwoFactorProviderType.Authenticator);
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
this.qrScript = window.document.createElement('script');
this.qrScript.src = 'scripts/qrious.min.js';
this.qrScript.async = true;

View File

@ -16,6 +16,8 @@ import { TwoFactorProviderRequest } from 'jslib/models/request/twoFactorProvider
export abstract class TwoFactorBaseComponent {
@Output() onUpdated = new EventEmitter<boolean>();
type: TwoFactorProviderType;
organizationId: string;
twoFactorProviderType = TwoFactorProviderType;
enabled = false;
authed = false;
@ -24,7 +26,7 @@ export abstract class TwoFactorBaseComponent {
constructor(protected apiService: ApiService, protected i18nService: I18nService,
protected analytics: Angulartics2, protected toasterService: ToasterService,
protected platformUtilsService: PlatformUtilsService, private type: TwoFactorProviderType) { }
protected platformUtilsService: PlatformUtilsService) { }
protected auth(authResponse: any) {
this.masterPasswordHash = authResponse.masterPasswordHash;
@ -52,7 +54,11 @@ export abstract class TwoFactorBaseComponent {
const request = new TwoFactorProviderRequest();
request.masterPasswordHash = this.masterPasswordHash;
request.type = this.type;
promise = this.apiService.putTwoFactorDisable(request);
if (this.organizationId != null) {
promise = this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request);
} else {
promise = this.apiService.putTwoFactorDisable(request);
}
await promise;
this.enabled = false;
this.analytics.eventTrack.next({

View File

@ -10,7 +10,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify [type]="twoFactorProviderType.Duo" (onAuthed)="auth($event)" *ngIf="!authed">
<app-two-factor-verify [organizationId]="organizationId" [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
<div class="modal-body">

View File

@ -18,6 +18,7 @@ import { TwoFactorBaseComponent } from './two-factor-base.component';
templateUrl: 'two-factor-duo.component.html',
})
export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
type = TwoFactorProviderType.Duo;
ikey: string;
skey: string;
host: string;
@ -26,8 +27,7 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
constructor(apiService: ApiService, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService,
platformUtilsService: PlatformUtilsService) {
super(apiService, i18nService, analytics, toasterService, platformUtilsService,
TwoFactorProviderType.Duo);
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
}
auth(authResponse: any) {
@ -51,7 +51,11 @@ export class TwoFactorDuoComponent extends TwoFactorBaseComponent {
request.host = this.host;
return super.enable(async () => {
this.formPromise = this.apiService.putTwoFactorDuo(request);
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);
});

View File

@ -10,7 +10,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify [type]="twoFactorProviderType.Email" (onAuthed)="auth($event)" *ngIf="!authed">
<app-two-factor-verify [organizationId]="organizationId" [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
<div class="modal-body">

View File

@ -21,6 +21,7 @@ import { TwoFactorBaseComponent } from './two-factor-base.component';
templateUrl: 'two-factor-email.component.html',
})
export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
type = TwoFactorProviderType.Email;
email: string;
token: string;
sentEmail: string;
@ -30,8 +31,7 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent {
constructor(apiService: ApiService, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService,
platformUtilsService: PlatformUtilsService, private userService: UserService) {
super(apiService, i18nService, analytics, toasterService, platformUtilsService,
TwoFactorProviderType.Email);
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
}
auth(authResponse: any) {

View File

@ -10,7 +10,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify [type]="-1" (onAuthed)="auth($event)" *ngIf="!authed">
<app-two-factor-verify [organizationId]="organizationId" [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<ng-container *ngIf="authed">
<div class="modal-body text-center">

View File

@ -11,6 +11,7 @@ import { TwoFactorProviderType } from 'jslib/enums/twoFactorProviderType';
templateUrl: 'two-factor-recovery.component.html',
})
export class TwoFactorRecoveryComponent {
type = -1;
code: string;
authed: boolean;
twoFactorProviderType = TwoFactorProviderType;

View File

@ -1,11 +1,13 @@
<div class="page-header">
<h1>{{'twoStepLogin' | i18n}}</h1>
</div>
<app-callout type="warning">
<p *ngIf="!organizationId">{{'twoStepLoginDesc' | i18n}}</p>
<p *ngIf="organizationId">{{'twoStepLoginOrganizationDesc' | i18n}}</p>
<app-callout type="warning" *ngIf="!organizationId">
<p>{{'twoStepLoginRecoveryWarning' | i18n}}</p>
<button type="button" class="btn btn-outline-secondary" (click)="recoveryCode()">{{'viewRecoveryCode' | i18n}}</button>
</app-callout>
<h2 class="mt-5">
<h2 [ngClass]="{'mt-5':!organizationId}">
{{'providers' | i18n}}
<small *ngIf="loading">
<i class="fa fa-spinner fa-spin fa-fw text-muted" title="{{'loading' | i18n}}"></i>

View File

@ -42,8 +42,8 @@ export class TwoFactorSetupComponent implements OnInit {
private modal: ModalComponent = null;
constructor(private apiService: ApiService, private tokenService: TokenService,
private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService) { }
constructor(protected apiService: ApiService, protected tokenService: TokenService,
protected componentFactoryResolver: ComponentFactoryResolver, protected messagingService: MessagingService) { }
async ngOnInit() {
this.premium = this.tokenService.getPremium();
@ -54,7 +54,7 @@ export class TwoFactorSetupComponent implements OnInit {
}
const p = (TwoFactorProviders as any)[key];
if (p.type === TwoFactorProviderType.OrganizationDuo) {
if (this.filterProvider(p.type)) {
continue;
}
@ -74,7 +74,7 @@ export class TwoFactorSetupComponent implements OnInit {
async load() {
this.loading = true;
const providerList = await this.apiService.getTwoFactorProviders();
const providerList = await this.getTwoFactorProviders();
providerList.data.forEach((p) => {
this.providers.forEach((p2) => {
if (p.type === p2.type) {
@ -134,7 +134,15 @@ export class TwoFactorSetupComponent implements OnInit {
}
}
private openModal<T>(ref: ViewContainerRef, type: Type<T>): T {
protected getTwoFactorProviders() {
return this.apiService.getTwoFactorProviders();
}
protected filterProvider(type: TwoFactorProviderType) {
return type === TwoFactorProviderType.OrganizationDuo;
}
protected openModal<T>(ref: ViewContainerRef, type: Type<T>): T {
if (this.modal != null) {
this.modal.close();
}
@ -149,7 +157,7 @@ export class TwoFactorSetupComponent implements OnInit {
return childComponent;
}
private updateStatus(enabled: boolean, type: TwoFactorProviderType) {
protected updateStatus(enabled: boolean, type: TwoFactorProviderType) {
if (!enabled && this.modal != null) {
this.modal.close();
}

View File

@ -10,7 +10,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify [type]="twoFactorProviderType.U2f" (onAuthed)="auth($event)" *ngIf="!authed">
<app-two-factor-verify [organizationId]="organizationId" [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
<div class="modal-body">

View File

@ -22,6 +22,7 @@ import { TwoFactorBaseComponent } from './two-factor-base.component';
templateUrl: 'two-factor-u2f.component.html',
})
export class TwoFactorU2fComponent extends TwoFactorBaseComponent implements OnInit, OnDestroy {
type = TwoFactorProviderType.U2f;
u2fChallenge: any;
u2fError: boolean;
u2fListening: boolean;
@ -34,8 +35,7 @@ export class TwoFactorU2fComponent extends TwoFactorBaseComponent implements OnI
constructor(apiService: ApiService, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService,
platformUtilsService: PlatformUtilsService) {
super(apiService, i18nService, analytics, toasterService, platformUtilsService,
TwoFactorProviderType.U2f);
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
this.u2fScript = window.document.createElement('script');
this.u2fScript.src = 'scripts/u2f.js';
this.u2fScript.async = true;

View File

@ -20,10 +20,9 @@ import { PasswordVerificationRequest } from 'jslib/models/request/passwordVerifi
templateUrl: 'two-factor-verify.component.html',
})
export class TwoFactorVerifyComponent {
@Input()
type: TwoFactorProviderType;
@Output()
onAuthed = new EventEmitter<any>();
@Input() type: TwoFactorProviderType;
@Input() organizationId: string;
@Output() onAuthed = new EventEmitter<any>();
masterPassword: string;
formPromise: Promise<any>;
@ -50,7 +49,12 @@ export class TwoFactorVerifyComponent {
this.formPromise = this.apiService.getTwoFactorRecover(request);
break;
case TwoFactorProviderType.Duo:
this.formPromise = this.apiService.getTwoFactorDuo(request);
case TwoFactorProviderType.OrganizationDuo:
if (this.organizationId != null) {
this.formPromise = this.apiService.getTwoFactorOrganizationDuo(this.organizationId, request);
} else {
this.formPromise = this.apiService.getTwoFactorDuo(request);
}
break;
case TwoFactorProviderType.Email:
this.formPromise = this.apiService.getTwoFactorEmail(request);

View File

@ -10,7 +10,7 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<app-two-factor-verify [type]="twoFactorProviderType.Yubikey" (onAuthed)="auth($event)" *ngIf="!authed">
<app-two-factor-verify [organizationId]="organizationId" [type]="type" (onAuthed)="auth($event)" *ngIf="!authed">
</app-two-factor-verify>
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" ngNativeValidate *ngIf="authed">
<div class="modal-body">

View File

@ -19,6 +19,7 @@ import { TwoFactorBaseComponent } from './two-factor-base.component';
templateUrl: 'two-factor-yubikey.component.html',
})
export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent {
type = TwoFactorProviderType.Yubikey;
keys: any[];
nfc = false;
@ -28,8 +29,7 @@ export class TwoFactorYubiKeyComponent extends TwoFactorBaseComponent {
constructor(apiService: ApiService, i18nService: I18nService,
analytics: Angulartics2, toasterService: ToasterService,
platformUtilsService: PlatformUtilsService) {
super(apiService, i18nService, analytics, toasterService, platformUtilsService,
TwoFactorProviderType.Yubikey);
super(apiService, i18nService, analytics, toasterService, platformUtilsService);
}
auth(authResponse: any) {

View File

@ -1003,6 +1003,12 @@
"twoStepLogin": {
"message": "Two-step Login"
},
"twoStepLoginDesc": {
"message": "Secure your account by requiring an additional step when logging in."
},
"twoStepLoginOrganizationDesc": {
"message": "Require two-step login for your organization's users by configuring providers at the organization level."
},
"twoStepLoginRecoveryWarning": {
"message": "Enabling two-step login can permanently lock you out of your Bitwarden account. A recovery code allows you to access your account in the event that you can no longer use your normal two-step login provider (ex. you lose your device). Bitwarden support will not be able to assist you if you lose access to your account. We recommend you write down or print the recovery code and keep it in a safe place."
},