1
0
mirror of https://github.com/bitwarden/web synced 2025-12-06 00:03:28 +00:00

Vault Timeout Policy (#1171)

This commit is contained in:
Oscar Hinton
2021-09-10 15:27:00 +02:00
committed by GitHub
parent 17166dad4d
commit a1c1fea976
15 changed files with 248 additions and 29 deletions

View File

@@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { AppComponent as BaseAppComponent } from 'src/app/app.component';
import { MaximumVaultTimeoutPolicy } from './policies/maximum-vault-timeout.component';
@Component({
selector: 'app-root',
templateUrl: '../../../src/app/app.component.html',
})
export class AppComponent extends BaseAppComponent {
ngOnInit() {
super.ngOnInit();
this.policyListService.addPolicies([
new MaximumVaultTimeoutPolicy(),
]);
}
}

View File

@@ -3,27 +3,36 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { DragDropModule } from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MaximumVaultTimeoutPolicyComponent } from './policies/maximum-vault-timeout.component';
import { AppComponent } from 'src/app/app.component';
import { OssRoutingModule } from 'src/app/oss-routing.module'; import { OssRoutingModule } from 'src/app/oss-routing.module';
import { OssModule } from 'src/app/oss.module'; import { OssModule } from 'src/app/oss.module';
import { ServicesModule } from 'src/app/services/services.module'; import { ServicesModule } from 'src/app/services/services.module';
@NgModule({ @NgModule({
imports: [ imports: [
OssModule, OssModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
ReactiveFormsModule,
ServicesModule, ServicesModule,
ToasterModule.forRoot(), ToasterModule.forRoot(),
InfiniteScrollModule, InfiniteScrollModule,
DragDropModule, DragDropModule,
AppRoutingModule, AppRoutingModule,
OssRoutingModule, OssRoutingModule,
RouterModule,
],
declarations: [
AppComponent,
MaximumVaultTimeoutPolicyComponent,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

View File

@@ -0,0 +1,27 @@
<app-callout type="tip" title="{{'prerequisite' | i18n}}">
{{'requireSsoPolicyReq' | i18n}}
</app-callout>
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="enabled" [formControl]="enabled" name="Enabled">
<label class="form-check-label" for="enabled">{{'enabled' | i18n}}</label>
</div>
</div>
<div [formGroup]="data">
<div class="form-group">
<label for="hours">{{'maximumVaultTimeoutLabel' | i18n}}</label>
<div class="row">
<div class="col-6">
<input id="hours" class="form-control" type="number" min="0" name="hours" formControlName="hours">
<small>{{'hours' | i18n }}</small>
</div>
<div class="col-6">
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes"
formControlName="minutes">
<small>{{'minutes' | i18n }}</small>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,65 @@
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { I18nService } from 'jslib-common/abstractions/i18n.service';
import { PolicyType } from 'jslib-common/enums/policyType';
import { PolicyRequest } from 'jslib-common/models/request/policyRequest';
import { BasePolicy, BasePolicyComponent } from 'src/app/organizations/policies/base-policy.component';
export class MaximumVaultTimeoutPolicy extends BasePolicy {
name = 'maximumVaultTimeout';
description = 'maximumVaultTimeoutDesc';
type = PolicyType.MaximumVaultTimeout;
component = MaximumVaultTimeoutPolicyComponent;
}
@Component({
selector: 'policy-maximum-timeout',
templateUrl: 'maximum-vault-timeout.component.html',
})
export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
data = this.fb.group({
hours: [null],
minutes: [null],
});
constructor(private fb: FormBuilder, private i18nService: I18nService) {
super();
}
loadData() {
const minutes = this.policyResponse.data?.minutes;
if (minutes == null) {
return;
}
this.data.patchValue({
hours: Math.floor(minutes / 60),
minutes: minutes % 60,
});
}
buildRequestData() {
if (this.data.value.hours == null && this.data.value.minutes == null) {
return null;
}
return {
minutes: this.data.value.hours * 60 + this.data.value.minutes,
};
}
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>): Promise<PolicyRequest> {
const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
if (this.enabled.value && !singleOrgEnabled) {
throw new Error(this.i18nService.t('requireSsoPolicyReqError'));
}
return super.buildRequest(policiesEnabledMap);
}
}

2
jslib

Submodule jslib updated: 5f64d95652...32774561f3

View File

@@ -22,6 +22,9 @@ import { ServicesModule } from './services/services.module';
DragDropModule, DragDropModule,
OssRoutingModule, OssRoutingModule,
], ],
declarations: [
AppComponent,
],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule { }

View File

@@ -35,19 +35,28 @@ export abstract class BasePolicyComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.enabled.setValue(this.policyResponse.enabled); this.enabled.setValue(this.policyResponse.enabled);
if (this.data != null) { if (this.policyResponse.data != null) {
this.loadData();
}
}
loadData() {
this.data.patchValue(this.policyResponse.data ?? {}); this.data.patchValue(this.policyResponse.data ?? {});
} }
buildRequestData() {
if (this.data != null) {
return this.data.value;
}
return null;
} }
buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) { buildRequest(policiesEnabledMap: Map<PolicyType, boolean>) {
const request = new PolicyRequest(); const request = new PolicyRequest();
request.enabled = this.enabled.value; request.enabled = this.enabled.value;
request.type = this.policy.type; request.type = this.policy.type;
request.data = this.buildRequestData();
if (this.data != null) {
request.data = this.data.value;
}
return Promise.resolve(request); return Promise.resolve(request);
} }

View File

@@ -11,8 +11,6 @@ import { RouterModule } from '@angular/router';
import { ToasterModule } from 'angular2-toaster'; import { ToasterModule } from 'angular2-toaster';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { AppComponent } from './app.component';
import { AvatarComponent } from './components/avatar.component'; import { AvatarComponent } from './components/avatar.component';
import { PasswordRepromptComponent } from './components/password-reprompt.component'; import { PasswordRepromptComponent } from './components/password-reprompt.component';
import { PasswordStrengthComponent } from './components/password-strength.component'; import { PasswordStrengthComponent } from './components/password-strength.component';
@@ -144,6 +142,7 @@ import { UpdateKeyComponent } from './settings/update-key.component';
import { UpdateLicenseComponent } from './settings/update-license.component'; import { UpdateLicenseComponent } from './settings/update-license.component';
import { UserBillingComponent } from './settings/user-billing.component'; import { UserBillingComponent } from './settings/user-billing.component';
import { UserSubscriptionComponent } from './settings/user-subscription.component'; import { UserSubscriptionComponent } from './settings/user-subscription.component';
import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component';
import { VerifyEmailComponent } from './settings/verify-email.component'; import { VerifyEmailComponent } from './settings/verify-email.component';
import { BreachReportComponent } from './tools/breach-report.component'; import { BreachReportComponent } from './tools/breach-report.component';
@@ -307,7 +306,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
AdjustStorageComponent, AdjustStorageComponent,
ApiActionDirective, ApiActionDirective,
ApiKeyComponent, ApiKeyComponent,
AppComponent,
AttachmentsComponent, AttachmentsComponent,
AutofocusDirective, AutofocusDirective,
AvatarComponent, AvatarComponent,
@@ -457,6 +455,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
DisableSendPolicyComponent, DisableSendPolicyComponent,
SendOptionsPolicyComponent, SendOptionsPolicyComponent,
ResetPasswordPolicyComponent, ResetPasswordPolicyComponent,
VaultTimeoutInputComponent,
], ],
exports: [ exports: [
A11yTitleDirective, A11yTitleDirective,

View File

@@ -124,7 +124,7 @@ const sendService = new SendService(cryptoService, userService, apiService, file
i18nService, cryptoFunctionService); i18nService, cryptoFunctionService);
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService, const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService, cryptoService, platformUtilsService, storageService, messagingService, searchService, userService, tokenService,
null, async () => messagingService.send('logout', { expired: false })); policyService, null, async () => messagingService.send('logout', { expired: false }));
const syncService = new SyncService(userService, apiService, settingsService, const syncService = new SyncService(userService, apiService, settingsService,
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService, folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired })); sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired }));

View File

@@ -5,13 +5,8 @@
<form (ngSubmit)="submit()" ngNativeValidate> <form (ngSubmit)="submit()" ngNativeValidate>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<div class="form-group"> <app-vault-timeout-input [vaultTimeouts]="vaultTimeouts" [formControl]="vaultTimeout" ngDefaultControl>
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label> </app-vault-timeout-input>
<select id="vaultTimeout" name="VaultTimeout" [(ngModel)]="vaultTimeout" class="form-control">
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
</div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -2,6 +2,7 @@ import {
Component, Component,
OnInit, OnInit,
} from '@angular/core'; } from '@angular/core';
import { FormControl } from '@angular/forms';
import { ToasterService } from 'angular2-toaster'; import { ToasterService } from 'angular2-toaster';
@@ -21,15 +22,16 @@ import { Utils } from 'jslib-common/misc/utils';
templateUrl: 'options.component.html', templateUrl: 'options.component.html',
}) })
export class OptionsComponent implements OnInit { export class OptionsComponent implements OnInit {
vaultTimeout: number = null;
vaultTimeoutAction: string = 'lock'; vaultTimeoutAction: string = 'lock';
disableIcons: boolean; disableIcons: boolean;
enableGravatars: boolean; enableGravatars: boolean;
enableFullWidth: boolean; enableFullWidth: boolean;
locale: string; locale: string;
vaultTimeouts: any[]; vaultTimeouts: { name: string; value: number; }[];
localeOptions: any[]; localeOptions: any[];
vaultTimeout: FormControl = new FormControl(null);
private startingLocale: string; private startingLocale: string;
constructor(private storageService: StorageService, private stateService: StateService, constructor(private storageService: StorageService, private stateService: StateService,
@@ -63,7 +65,7 @@ export class OptionsComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.vaultTimeout = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey); this.vaultTimeout.setValue(await this.vaultTimeoutService.getVaultTimeout());
this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey); this.vaultTimeoutAction = await this.storageService.get<string>(ConstantsService.vaultTimeoutActionKey);
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
this.enableGravatars = await this.storageService.get<boolean>('enableGravatars'); this.enableGravatars = await this.storageService.get<boolean>('enableGravatars');
@@ -72,8 +74,12 @@ export class OptionsComponent implements OnInit {
} }
async submit() { async submit() {
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout != null ? this.vaultTimeout : null, if (!this.vaultTimeout.valid) {
this.vaultTimeoutAction); this.toasterService.popAsync('error', null, this.i18nService.t('vaultTimeoutToLarge'));
return;
}
await this.vaultTimeoutService.setVaultTimeoutOptions(this.vaultTimeout.value, this.vaultTimeoutAction);
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons); await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons); await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
await this.storageService.save('enableGravatars', this.enableGravatars); await this.storageService.save('enableGravatars', this.enableGravatars);

View File

@@ -0,0 +1,28 @@
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
{{'vaultTimeoutPolicyInEffect' | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes}}
</app-callout>
<div [formGroup]="form">
<div class="form-group">
<label for="vaultTimeout">{{'vaultTimeout' | i18n}}</label>
<select id="vaultTimeout" name="VaultTimeout" formControlName="vaultTimeout" class="form-control">
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{o.name}}</option>
</select>
<small class="form-text text-muted">{{'vaultTimeoutDesc' | i18n}}</small>
</div>
<div class="form-group" *ngIf="showCustom" formGroupName="custom">
<label for="customVaultTimeout">{{'customVaultTimeout' | i18n}}</label>
<div class="row">
<div class="col-6">
<input id="hours" class="form-control" type="number" min="0" name="hours"
formControlName="hours">
<small>{{'hours' | i18n }}</small>
</div>
<div class="col-6">
<input id="minutes" class="form-control" type="number" min="0" max="59" name="minutes"
formControlName="minutes">
<small>{{'minutes' | i18n }}</small>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,28 @@
import { Component } from '@angular/core';
import {
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
import {
VaultTimeoutInputComponent as VaultTimeoutInputComponentBase
} from 'jslib-angular/components/settings/vault-timeout-input.component';
@Component({
selector: 'app-vault-timeout-input',
templateUrl: 'vault-timeout-input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: VaultTimeoutInputComponent,
},
],
})
export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {
}

View File

@@ -4198,5 +4198,39 @@
}, },
"updateMasterPasswordWarning": { "updateMasterPasswordWarning": {
"message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." "message": "Your Master Password was recently changed by an administrator in your organization. In order to access the vault, you must update your Master Password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour."
},
"maximumVaultTimeout": {
"message": "Vault Timeout"
},
"maximumVaultTimeoutDesc": {
"message": "Configure a maximum vault timeout for all users."
},
"maximumVaultTimeoutLabel": {
"message": "Maximum Vault Timeout"
},
"hours": {
"message": "Hours"
},
"minutes": {
"message": "Minutes"
},
"vaultTimeoutPolicyInEffect": {
"message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)",
"placeholders": {
"hours": {
"content": "$1",
"example": "5"
},
"minutes": {
"content": "$2",
"example": "5"
}
}
},
"customVaultTimeout": {
"message": "Custom Vault Timeout"
},
"vaultTimeoutToLarge": {
"message": "Your vault timeout exceeds the restriction set by your organization."
} }
} }

View File

@@ -82,10 +82,6 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
return Promise.resolve(false); return Promise.resolve(false);
} }
lockTimeout(): number {
return null;
}
launchUri(uri: string, options?: any): void { launchUri(uri: string, options?: any): void {
const a = document.createElement('a'); const a = document.createElement('a');
a.href = uri; a.href = uri;