diff --git a/bitwarden_license/src/app/app.component.ts b/bitwarden_license/src/app/app.component.ts
new file mode 100644
index 0000000000..33c6e828c8
--- /dev/null
+++ b/bitwarden_license/src/app/app.component.ts
@@ -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(),
+ ]);
+ }
+
+}
diff --git a/bitwarden_license/src/app/app.module.ts b/bitwarden_license/src/app/app.module.ts
index cdf705c1c3..c69bc1b356 100644
--- a/bitwarden_license/src/app/app.module.ts
+++ b/bitwarden_license/src/app/app.module.ts
@@ -3,27 +3,36 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterModule } from '@angular/router';
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 { OssModule } from 'src/app/oss.module';
import { ServicesModule } from 'src/app/services/services.module';
+
@NgModule({
imports: [
OssModule,
BrowserAnimationsModule,
FormsModule,
+ ReactiveFormsModule,
ServicesModule,
ToasterModule.forRoot(),
InfiniteScrollModule,
DragDropModule,
AppRoutingModule,
OssRoutingModule,
+ RouterModule,
+ ],
+ declarations: [
+ AppComponent,
+ MaximumVaultTimeoutPolicyComponent,
],
bootstrap: [AppComponent],
})
diff --git a/bitwarden_license/src/app/policies/maximum-vault-timeout.component.html b/bitwarden_license/src/app/policies/maximum-vault-timeout.component.html
new file mode 100644
index 0000000000..ac43f2d120
--- /dev/null
+++ b/bitwarden_license/src/app/policies/maximum-vault-timeout.component.html
@@ -0,0 +1,27 @@
+
+ {{'requireSsoPolicyReq' | i18n}}
+
+
+
+
+
diff --git a/bitwarden_license/src/app/policies/maximum-vault-timeout.component.ts b/bitwarden_license/src/app/policies/maximum-vault-timeout.component.ts
new file mode 100644
index 0000000000..496cf5a3e2
--- /dev/null
+++ b/bitwarden_license/src/app/policies/maximum-vault-timeout.component.ts
@@ -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): Promise {
+ const singleOrgEnabled = policiesEnabledMap.get(PolicyType.SingleOrg) ?? false;
+ if (this.enabled.value && !singleOrgEnabled) {
+ throw new Error(this.i18nService.t('requireSsoPolicyReqError'));
+ }
+
+ return super.buildRequest(policiesEnabledMap);
+ }
+}
diff --git a/jslib b/jslib
index 5f64d95652..32774561f3 160000
--- a/jslib
+++ b/jslib
@@ -1 +1 @@
-Subproject commit 5f64d956520612a681611a27c5f4f2e5f27b640e
+Subproject commit 32774561f37bdcf9abb80276c5d1958b7ec192de
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 1066149beb..a8e2f88a92 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -22,6 +22,9 @@ import { ServicesModule } from './services/services.module';
DragDropModule,
OssRoutingModule,
],
+ declarations: [
+ AppComponent,
+ ],
bootstrap: [AppComponent],
})
export class AppModule { }
diff --git a/src/app/organizations/policies/base-policy.component.ts b/src/app/organizations/policies/base-policy.component.ts
index bc4b3e3d46..c42647a760 100644
--- a/src/app/organizations/policies/base-policy.component.ts
+++ b/src/app/organizations/policies/base-policy.component.ts
@@ -35,19 +35,28 @@ export abstract class BasePolicyComponent implements OnInit {
ngOnInit(): void {
this.enabled.setValue(this.policyResponse.enabled);
- if (this.data != null) {
- this.data.patchValue(this.policyResponse.data ?? {});
+ if (this.policyResponse.data != null) {
+ this.loadData();
}
}
+ loadData() {
+ this.data.patchValue(this.policyResponse.data ?? {});
+ }
+
+ buildRequestData() {
+ if (this.data != null) {
+ return this.data.value;
+ }
+
+ return null;
+ }
+
buildRequest(policiesEnabledMap: Map) {
const request = new PolicyRequest();
request.enabled = this.enabled.value;
request.type = this.policy.type;
-
- if (this.data != null) {
- request.data = this.data.value;
- }
+ request.data = this.buildRequestData();
return Promise.resolve(request);
}
diff --git a/src/app/oss.module.ts b/src/app/oss.module.ts
index 06b7301952..3ea40c4e4a 100644
--- a/src/app/oss.module.ts
+++ b/src/app/oss.module.ts
@@ -11,8 +11,6 @@ import { RouterModule } from '@angular/router';
import { ToasterModule } from 'angular2-toaster';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
-import { AppComponent } from './app.component';
-
import { AvatarComponent } from './components/avatar.component';
import { PasswordRepromptComponent } from './components/password-reprompt.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 { UserBillingComponent } from './settings/user-billing.component';
import { UserSubscriptionComponent } from './settings/user-subscription.component';
+import { VaultTimeoutInputComponent } from './settings/vault-timeout-input.component';
import { VerifyEmailComponent } from './settings/verify-email.component';
import { BreachReportComponent } from './tools/breach-report.component';
@@ -307,7 +306,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
AdjustStorageComponent,
ApiActionDirective,
ApiKeyComponent,
- AppComponent,
AttachmentsComponent,
AutofocusDirective,
AvatarComponent,
@@ -457,6 +455,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
DisableSendPolicyComponent,
SendOptionsPolicyComponent,
ResetPasswordPolicyComponent,
+ VaultTimeoutInputComponent,
],
exports: [
A11yTitleDirective,
diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts
index 6639268f13..144fe7db6c 100644
--- a/src/app/services/services.module.ts
+++ b/src/app/services/services.module.ts
@@ -124,7 +124,7 @@ const sendService = new SendService(cryptoService, userService, apiService, file
i18nService, cryptoFunctionService);
const vaultTimeoutService = new VaultTimeoutService(cipherService, folderService, collectionService,
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,
folderService, cipherService, cryptoService, collectionService, storageService, messagingService, policyService,
sendService, async (expired: boolean) => messagingService.send('logout', { expired: expired }));
diff --git a/src/app/settings/options.component.html b/src/app/settings/options.component.html
index b369b90f6f..8619be274a 100644
--- a/src/app/settings/options.component.html
+++ b/src/app/settings/options.component.html
@@ -5,13 +5,8 @@