mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-21 21:11:35 +01:00
[AC-1045] add action to vault timeout policy (#4782)
This commit is contained in:
parent
37230aa47f
commit
fbbaf10488
@ -1863,7 +1863,7 @@
|
||||
"message": "Minutes"
|
||||
},
|
||||
"vaultTimeoutPolicyInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed Vault Timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)",
|
||||
"message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
@ -1875,6 +1875,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutPolicyWithActionInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "5"
|
||||
},
|
||||
"action": {
|
||||
"content": "$3",
|
||||
"example": "Lock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutActionPolicyInEffect": {
|
||||
"message": "Your organization policies have set your vault timeout action to $ACTION$.",
|
||||
"placeholders": {
|
||||
"action": {
|
||||
"content": "$1",
|
||||
"example": "Lock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutTooLarge": {
|
||||
"message": "Your vault timeout exceeds the restrictions set by your organization."
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
|
||||
import { BrowserStateService } from "../services/abstractions/browser-state.service";
|
||||
|
||||
@ -45,7 +46,7 @@ export default class IdleBackground {
|
||||
if (timeout === -2) {
|
||||
// On System Lock vault timeout option
|
||||
const action = await this.stateService.getVaultTimeoutAction();
|
||||
if (action === "logOut") {
|
||||
if (action === VaultTimeoutAction.LogOut) {
|
||||
await this.vaultTimeoutService.logOut();
|
||||
} else {
|
||||
await this.vaultTimeoutService.lock();
|
||||
|
@ -7,7 +7,7 @@
|
||||
</h1>
|
||||
<div class="right"></div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<main tabindex="-1" [formGroup]="form">
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "manage" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
@ -48,9 +48,23 @@
|
||||
<div class="box list">
|
||||
<h2 class="box-header">{{ "security" | i18n }}</h2>
|
||||
<div class="box-content single-line">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n : policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n : policy.timeout.hours : policy.timeout.minutes }}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n : (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeouts]="vaultTimeouts"
|
||||
[formControl]="vaultTimeout"
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</app-vault-timeout-input>
|
||||
@ -60,15 +74,16 @@
|
||||
#vaultTimeoutActionSelect
|
||||
id="vaultTimeoutAction"
|
||||
name="VaultTimeoutActions"
|
||||
[ngModel]="vaultTimeoutAction"
|
||||
(ngModelChange)="saveVaultTimeoutAction($event)"
|
||||
formControlName="vaultTimeoutAction"
|
||||
>
|
||||
<option *ngFor="let o of vaultTimeoutActions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
<option *ngFor="let o of vaultTimeoutActionOptions" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="pin">{{ "unlockWithPin" | i18n }}</label>
|
||||
<input id="pin" type="checkbox" (change)="updatePin()" [(ngModel)]="pin" />
|
||||
<input id="pin" type="checkbox" (change)="updatePin()" formControlName="pin" />
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow *ngIf="supportsBiometric">
|
||||
<label for="biometric">{{ "unlockWithBiometrics" | i18n }}</label>
|
||||
@ -76,21 +91,20 @@
|
||||
id="biometric"
|
||||
type="checkbox"
|
||||
(change)="updateBiometric()"
|
||||
[(ngModel)]="biometric"
|
||||
formControlName="biometric"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box-content-row box-content-row-checkbox"
|
||||
appBoxRow
|
||||
*ngIf="supportsBiometric && biometric"
|
||||
*ngIf="supportsBiometric && this.form.value.biometric"
|
||||
>
|
||||
<label for="autoBiometricsPrompt">{{ "enableAutoBiometricsPrompt" | i18n }}</label>
|
||||
<input
|
||||
id="autoBiometricsPrompt"
|
||||
type="checkbox"
|
||||
(change)="updateAutoBiometricsPrompt()"
|
||||
[disabled]="!biometric"
|
||||
[(ngModel)]="enableAutoBiometricsPrompt"
|
||||
formControlName="enableAutoBiometricsPrompt"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, ElementRef, OnInit, ViewChild } from "@angular/core";
|
||||
import { UntypedFormControl } from "@angular/forms";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { concatMap, filter, map, Observable, Subject, takeUntil, tap } from "rxjs";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
@ -12,8 +13,11 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { DeviceType } from "@bitwarden/common/enums";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
|
||||
import { BrowserApi } from "../../browser/browserApi";
|
||||
import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors";
|
||||
@ -44,19 +48,29 @@ const RateUrls = {
|
||||
export class SettingsComponent implements OnInit {
|
||||
@ViewChild("vaultTimeoutActionSelect", { read: ElementRef, static: true })
|
||||
vaultTimeoutActionSelectRef: ElementRef;
|
||||
vaultTimeouts: any[];
|
||||
vaultTimeoutActions: any[];
|
||||
vaultTimeoutAction: string;
|
||||
pin: boolean = null;
|
||||
vaultTimeoutOptions: any[];
|
||||
vaultTimeoutActionOptions: any[];
|
||||
vaultTimeoutPolicyCallout: Observable<{
|
||||
timeout: { hours: number; minutes: number };
|
||||
action: VaultTimeoutAction;
|
||||
}>;
|
||||
supportsBiometric: boolean;
|
||||
biometric = false;
|
||||
enableAutoBiometricsPrompt = true;
|
||||
previousVaultTimeout: number = null;
|
||||
showChangeMasterPass = true;
|
||||
|
||||
vaultTimeout: UntypedFormControl = new UntypedFormControl(null);
|
||||
form = this.formBuilder.group({
|
||||
vaultTimeout: [null as number | null],
|
||||
vaultTimeoutAction: [VaultTimeoutAction.Lock],
|
||||
pin: [null as boolean | null],
|
||||
biometric: false,
|
||||
enableAutoBiometricsPrompt: true,
|
||||
});
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private policyService: PolicyService,
|
||||
private formBuilder: FormBuilder,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private vaultTimeoutService: VaultTimeoutService,
|
||||
@ -72,10 +86,31 @@ export class SettingsComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.vaultTimeoutPolicyCallout = this.policyService.get$(PolicyType.MaximumVaultTimeout).pipe(
|
||||
filter((policy) => policy != null),
|
||||
map((policy) => {
|
||||
let timeout;
|
||||
if (policy.data?.minutes) {
|
||||
timeout = {
|
||||
hours: Math.floor(policy.data?.minutes / 60),
|
||||
minutes: policy.data?.minutes % 60,
|
||||
};
|
||||
}
|
||||
return { timeout: timeout, action: policy.data?.action };
|
||||
}),
|
||||
tap((policy) => {
|
||||
if (policy.action) {
|
||||
this.form.controls.vaultTimeoutAction.disable({ emitEvent: false });
|
||||
} else {
|
||||
this.form.controls.vaultTimeoutAction.enable({ emitEvent: false });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const showOnLocked =
|
||||
!this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari();
|
||||
|
||||
this.vaultTimeouts = [
|
||||
this.vaultTimeoutOptions = [
|
||||
{ name: this.i18nService.t("immediately"), value: 0 },
|
||||
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
||||
{ name: this.i18nService.t("fiveMinutes"), value: 5 },
|
||||
@ -88,40 +123,63 @@ export class SettingsComponent implements OnInit {
|
||||
];
|
||||
|
||||
if (showOnLocked) {
|
||||
this.vaultTimeouts.push({ name: this.i18nService.t("onLocked"), value: -2 });
|
||||
this.vaultTimeoutOptions.push({ name: this.i18nService.t("onLocked"), value: -2 });
|
||||
}
|
||||
|
||||
this.vaultTimeouts.push({ name: this.i18nService.t("onRestart"), value: -1 });
|
||||
this.vaultTimeouts.push({ name: this.i18nService.t("never"), value: null });
|
||||
this.vaultTimeoutOptions.push({ name: this.i18nService.t("onRestart"), value: -1 });
|
||||
this.vaultTimeoutOptions.push({ name: this.i18nService.t("never"), value: null });
|
||||
|
||||
this.vaultTimeoutActions = [
|
||||
{ name: this.i18nService.t("lock"), value: "lock" },
|
||||
{ name: this.i18nService.t("logOut"), value: "logOut" },
|
||||
this.vaultTimeoutActionOptions = [
|
||||
{ name: this.i18nService.t(VaultTimeoutAction.Lock), value: VaultTimeoutAction.Lock },
|
||||
{ name: this.i18nService.t(VaultTimeoutAction.LogOut), value: VaultTimeoutAction.LogOut },
|
||||
];
|
||||
|
||||
let timeout = await this.vaultTimeoutSettingsService.getVaultTimeout();
|
||||
if (timeout != null) {
|
||||
if (timeout === -2 && !showOnLocked) {
|
||||
timeout = -1;
|
||||
}
|
||||
this.vaultTimeout.setValue(timeout);
|
||||
if (timeout === -2 && !showOnLocked) {
|
||||
timeout = -1;
|
||||
}
|
||||
this.previousVaultTimeout = this.vaultTimeout.value;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.vaultTimeout.valueChanges.subscribe(async (value) => {
|
||||
await this.saveVaultTimeout(value);
|
||||
});
|
||||
|
||||
const action = await this.stateService.getVaultTimeoutAction();
|
||||
this.vaultTimeoutAction = action == null ? "lock" : action;
|
||||
|
||||
const pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||
this.pin = pinSet[0] || pinSet[1];
|
||||
|
||||
const initialValues = {
|
||||
vaultTimeout: timeout,
|
||||
vaultTimeoutAction: await this.vaultTimeoutSettingsService.getVaultTimeoutAction(),
|
||||
pin: pinSet[0] || pinSet[1],
|
||||
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
||||
enableAutoBiometricsPrompt: !(await this.stateService.getDisableAutoBiometricsPrompt()),
|
||||
};
|
||||
this.form.setValue(initialValues, { emitEvent: false });
|
||||
|
||||
this.previousVaultTimeout = timeout;
|
||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||
this.biometric = await this.vaultTimeoutSettingsService.isBiometricLockSet();
|
||||
this.enableAutoBiometricsPrompt = !(await this.stateService.getDisableAutoBiometricsPrompt());
|
||||
this.showChangeMasterPass = !(await this.keyConnectorService.getUsesKeyConnector());
|
||||
|
||||
this.form.controls.vaultTimeout.valueChanges
|
||||
.pipe(
|
||||
concatMap(async (value) => {
|
||||
await this.saveVaultTimeout(value);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.form.controls.vaultTimeoutAction.valueChanges
|
||||
.pipe(
|
||||
concatMap(async (action) => {
|
||||
await this.saveVaultTimeoutAction(action);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.form.controls.biometric.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enabled) => {
|
||||
if (enabled) {
|
||||
this.form.controls.enableAutoBiometricsPrompt.enable();
|
||||
} else {
|
||||
this.form.controls.enableAutoBiometricsPrompt.disable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async saveVaultTimeout(newValue: number) {
|
||||
@ -134,14 +192,14 @@ export class SettingsComponent implements OnInit {
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.vaultTimeout.setValue(this.previousVaultTimeout);
|
||||
this.form.controls.vaultTimeout.setValue(this.previousVaultTimeout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The minTimeoutError does not apply to browser because it supports Immediately
|
||||
// So only check for the policyError
|
||||
if (this.vaultTimeout.hasError("policyError")) {
|
||||
if (this.form.controls.vaultTimeout.hasError("policyError")) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
@ -150,19 +208,19 @@ export class SettingsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.previousVaultTimeout = this.vaultTimeout.value;
|
||||
this.previousVaultTimeout = this.form.value.vaultTimeout;
|
||||
|
||||
await this.vaultTimeoutSettingsService.setVaultTimeoutOptions(
|
||||
this.vaultTimeout.value,
|
||||
this.vaultTimeoutAction
|
||||
newValue,
|
||||
this.form.value.vaultTimeoutAction
|
||||
);
|
||||
if (this.previousVaultTimeout == null) {
|
||||
this.messagingService.send("bgReseedStorage");
|
||||
}
|
||||
}
|
||||
|
||||
async saveVaultTimeoutAction(newValue: string) {
|
||||
if (newValue === "logOut") {
|
||||
async saveVaultTimeoutAction(newValue: VaultTimeoutAction) {
|
||||
if (newValue === VaultTimeoutAction.LogOut) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
|
||||
@ -171,17 +229,20 @@ export class SettingsComponent implements OnInit {
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.vaultTimeoutActions.forEach((option: any, i) => {
|
||||
if (option.value === this.vaultTimeoutAction) {
|
||||
this.vaultTimeoutActionOptions.forEach((option: any, i) => {
|
||||
if (option.value === this.form.value.vaultTimeoutAction) {
|
||||
this.vaultTimeoutActionSelectRef.nativeElement.value =
|
||||
i + ": " + this.vaultTimeoutAction;
|
||||
i + ": " + this.form.value.vaultTimeoutAction;
|
||||
}
|
||||
});
|
||||
this.form.controls.vaultTimeoutAction.patchValue(VaultTimeoutAction.Lock, {
|
||||
emitEvent: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.vaultTimeout.hasError("policyError")) {
|
||||
if (this.form.controls.vaultTimeout.hasError("policyError")) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
@ -190,23 +251,22 @@ export class SettingsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.vaultTimeoutAction = newValue;
|
||||
await this.vaultTimeoutSettingsService.setVaultTimeoutOptions(
|
||||
this.vaultTimeout.value,
|
||||
this.vaultTimeoutAction
|
||||
this.form.value.vaultTimeout,
|
||||
newValue
|
||||
);
|
||||
}
|
||||
|
||||
async updatePin() {
|
||||
if (this.pin) {
|
||||
if (this.form.value.pin) {
|
||||
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
|
||||
|
||||
if (ref == null) {
|
||||
this.pin = false;
|
||||
this.form.controls.pin.setValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pin = await ref.onClosedPromise();
|
||||
this.form.controls.pin.setValue(await ref.onClosedPromise());
|
||||
} else {
|
||||
await this.cryptoService.clearPinProtectedKey();
|
||||
await this.vaultTimeoutSettingsService.clear();
|
||||
@ -214,7 +274,7 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async updateBiometric() {
|
||||
if (this.biometric && this.supportsBiometric) {
|
||||
if (this.form.value.biometric && this.supportsBiometric) {
|
||||
let granted;
|
||||
try {
|
||||
granted = await BrowserApi.requestPermission({ permissions: ["nativeMessaging"] });
|
||||
@ -229,7 +289,7 @@ export class SettingsComponent implements OnInit {
|
||||
this.i18nService.t("ok"),
|
||||
null
|
||||
);
|
||||
this.biometric = false;
|
||||
this.form.controls.biometric.setValue(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -241,7 +301,7 @@ export class SettingsComponent implements OnInit {
|
||||
this.i18nService.t("ok"),
|
||||
null
|
||||
);
|
||||
this.biometric = false;
|
||||
this.form.controls.biometric.setValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -264,17 +324,17 @@ export class SettingsComponent implements OnInit {
|
||||
await Promise.race([
|
||||
submitted.then(async (result) => {
|
||||
if (result.dismiss === Swal.DismissReason.cancel) {
|
||||
this.biometric = false;
|
||||
this.form.controls.biometric.setValue(false);
|
||||
await this.stateService.setBiometricAwaitingAcceptance(null);
|
||||
}
|
||||
}),
|
||||
this.platformUtilsService
|
||||
.authenticateBiometric()
|
||||
.then((result) => {
|
||||
this.biometric = result;
|
||||
this.form.controls.biometric.setValue(result);
|
||||
|
||||
Swal.close();
|
||||
if (this.biometric === false) {
|
||||
if (this.form.value.biometric === false) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorEnableBiometricTitle"),
|
||||
@ -284,7 +344,7 @@ export class SettingsComponent implements OnInit {
|
||||
})
|
||||
.catch((e) => {
|
||||
// Handle connection errors
|
||||
this.biometric = false;
|
||||
this.form.controls.biometric.setValue(false);
|
||||
|
||||
const error = BiometricErrors[e as BiometricErrorTypes];
|
||||
|
||||
@ -304,7 +364,9 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async updateAutoBiometricsPrompt() {
|
||||
await this.stateService.setDisableAutoBiometricsPrompt(!this.enableAutoBiometricsPrompt);
|
||||
await this.stateService.setDisableAutoBiometricsPrompt(
|
||||
!this.form.value.enableAutoBiometricsPrompt
|
||||
);
|
||||
}
|
||||
|
||||
async lock() {
|
||||
@ -314,7 +376,7 @@ export class SettingsComponent implements OnInit {
|
||||
async logOut() {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("logOutConfirmation"),
|
||||
this.i18nService.t("logOut"),
|
||||
this.i18nService.t(VaultTimeoutAction.LogOut),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("cancel")
|
||||
);
|
||||
@ -409,4 +471,9 @@ export class SettingsComponent implements OnInit {
|
||||
const deviceType = this.platformUtilsService.getDevice();
|
||||
BrowserApi.createNewTab((RateUrls as any)[deviceType]);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicy">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n : vaultTimeoutPolicyHours : vaultTimeoutPolicyMinutes }}
|
||||
</app-callout>
|
||||
|
||||
<div [formGroup]="form">
|
||||
<div class="box-content-row last display-block" appBoxRow>
|
||||
<label for="vaultTimeout">{{ "vaultTimeout" | i18n }}</label>
|
||||
@ -11,7 +7,7 @@
|
||||
formControlName="vaultTimeout"
|
||||
class="form-control"
|
||||
>
|
||||
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
|
||||
<option *ngFor="let o of vaultTimeoutOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-content-row last" *ngIf="showCustom">
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="settingsTitle">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body form">
|
||||
<div class="modal-body form" [formGroup]="form">
|
||||
<div class="box">
|
||||
<h1 class="box-header" id="settingsTitle">
|
||||
{{ "settingsTitle" | i18n : currentUserEmail }}
|
||||
@ -30,9 +30,29 @@
|
||||
</button>
|
||||
</h2>
|
||||
<ng-container *ngIf="showSecurity">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n
|
||||
: policy.timeout.hours
|
||||
: policy.timeout.minutes
|
||||
: (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyInEffect"
|
||||
| i18n : policy.timeout.hours : policy.timeout.minutes
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n : (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeouts]="vaultTimeouts"
|
||||
[formControl]="vaultTimeout"
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
></app-vault-timeout-input>
|
||||
<div class="form-group">
|
||||
@ -41,12 +61,10 @@
|
||||
<label for="vaultTimeoutActionLock">
|
||||
<input
|
||||
type="radio"
|
||||
name="VaultTimeoutAction"
|
||||
id="vaultTimeoutActionLock"
|
||||
value="lock"
|
||||
value="{{ VaultTimeoutAction.Lock }}"
|
||||
aria-describedby="vaultTimeoutActionLockHelp"
|
||||
[(ngModel)]="vaultTimeoutAction"
|
||||
(change)="saveVaultTimeoutOptions()"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
{{ "lock" | i18n }}
|
||||
</label>
|
||||
@ -58,12 +76,10 @@
|
||||
<label for="vaultTimeoutActionLogOut">
|
||||
<input
|
||||
type="radio"
|
||||
name="VaultTimeoutAction"
|
||||
id="vaultTimeoutActionLogOut"
|
||||
value="logOut"
|
||||
value="{{ VaultTimeoutAction.LogOut }}"
|
||||
aria-describedby="vaultTimeoutActionLogOutHelp"
|
||||
[(ngModel)]="vaultTimeoutAction"
|
||||
(change)="saveVaultTimeoutOptions()"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
{{ "logOut" | i18n }}
|
||||
</label>
|
||||
@ -75,13 +91,7 @@
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="pin">
|
||||
<input
|
||||
id="pin"
|
||||
type="checkbox"
|
||||
name="PIN"
|
||||
[(ngModel)]="pin"
|
||||
(change)="updatePin()"
|
||||
/>
|
||||
<input id="pin" type="checkbox" formControlName="pin" (change)="updatePin()" />
|
||||
{{ "unlockWithPin" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
@ -92,22 +102,20 @@
|
||||
<input
|
||||
id="biometric"
|
||||
type="checkbox"
|
||||
name="biometric"
|
||||
[ngModel]="biometric"
|
||||
(ngModelChange)="updateBiometric($event)"
|
||||
formControlName="biometric"
|
||||
(change)="updateBiometric()"
|
||||
/>
|
||||
{{ biometricText | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="supportsBiometric && biometric">
|
||||
<div class="form-group" *ngIf="supportsBiometric && this.form.value.biometric">
|
||||
<div class="checkbox">
|
||||
<label for="autoPromptBiometrics">
|
||||
<input
|
||||
id="autoPromptBiometrics"
|
||||
type="checkbox"
|
||||
name="autoPromptBiometrics"
|
||||
[(ngModel)]="autoPromptBiometrics"
|
||||
formControlName="autoPromptBiometrics"
|
||||
(change)="updateAutoPromptBiometrics()"
|
||||
/>
|
||||
{{ autoPromptBiometricsText | i18n }}
|
||||
@ -120,8 +128,7 @@
|
||||
<input
|
||||
id="approveLoginRequests"
|
||||
type="checkbox"
|
||||
name="approveLoginRequests"
|
||||
[(ngModel)]="approveLoginRequests"
|
||||
formControlName="approveLoginRequests"
|
||||
(change)="updateApproveLoginRequests()"
|
||||
/>
|
||||
{{ "approveLoginRequests" | i18n }}
|
||||
@ -159,9 +166,8 @@
|
||||
<label for="clearClipboard">{{ "clearClipboard" | i18n }}</label>
|
||||
<select
|
||||
id="clearClipboard"
|
||||
name="ClearClipboard"
|
||||
aria-describedby="clearClipboardHelp"
|
||||
[(ngModel)]="clearClipboard"
|
||||
formControlName="clearClipboard"
|
||||
(change)="saveClearClipboard()"
|
||||
>
|
||||
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">
|
||||
@ -178,9 +184,8 @@
|
||||
<input
|
||||
id="minimizeOnCopyToClipboard"
|
||||
type="checkbox"
|
||||
name="MinimizeOnCopyToClipboard"
|
||||
aria-describedby="minimizeOnCopyToClipboardHelp"
|
||||
[(ngModel)]="minimizeOnCopyToClipboard"
|
||||
formControlName="minimizeOnCopyToClipboard"
|
||||
(change)="saveMinOnCopyToClipboard()"
|
||||
/>
|
||||
{{ "minimizeOnCopyToClipboard" | i18n }}
|
||||
@ -196,9 +201,8 @@
|
||||
<input
|
||||
id="enableFavicons"
|
||||
type="checkbox"
|
||||
name="enableFavicons"
|
||||
aria-describedby="enableFaviconsHelp"
|
||||
[(ngModel)]="enableFavicons"
|
||||
formControlName="enableFavicons"
|
||||
(change)="saveFavicons()"
|
||||
/>
|
||||
{{ "enableFavicon" | i18n }}
|
||||
@ -238,9 +242,8 @@
|
||||
<input
|
||||
id="enableTray"
|
||||
type="checkbox"
|
||||
name="EnableTray"
|
||||
aria-describedby="enableTrayHelp"
|
||||
[(ngModel)]="enableTray"
|
||||
formControlName="enableTray"
|
||||
(change)="saveTray()"
|
||||
/>
|
||||
{{ enableTrayText }}
|
||||
@ -254,9 +257,8 @@
|
||||
<input
|
||||
id="enableMinToTray"
|
||||
type="checkbox"
|
||||
name="EnableMinToTray"
|
||||
aria-describedby="enableMinToTrayHelp"
|
||||
[(ngModel)]="enableMinToTray"
|
||||
formControlName="enableMinToTray"
|
||||
(change)="saveMinToTray()"
|
||||
/>
|
||||
{{ enableMinToTrayText }}
|
||||
@ -272,9 +274,8 @@
|
||||
<input
|
||||
id="enableCloseToTray"
|
||||
type="checkbox"
|
||||
name="EnableCloseToTray"
|
||||
aria-describedby="enableCloseToTrayHelp"
|
||||
[(ngModel)]="enableCloseToTray"
|
||||
formControlName="enableCloseToTray"
|
||||
(change)="saveCloseToTray()"
|
||||
/>
|
||||
{{ enableCloseToTrayText }}
|
||||
@ -290,9 +291,8 @@
|
||||
<input
|
||||
id="startToTray"
|
||||
type="checkbox"
|
||||
name="StartToTray"
|
||||
aria-describedby="startToTrayHelp"
|
||||
[(ngModel)]="startToTray"
|
||||
formControlName="startToTray"
|
||||
(change)="saveStartToTray()"
|
||||
/>
|
||||
{{ startToTrayText }}
|
||||
@ -306,9 +306,8 @@
|
||||
<input
|
||||
id="openAtLogin"
|
||||
type="checkbox"
|
||||
name="OpenAtLogin"
|
||||
aria-describedby="openAtLoginHelp"
|
||||
[(ngModel)]="openAtLogin"
|
||||
formControlName="openAtLogin"
|
||||
(change)="saveOpenAtLogin()"
|
||||
/>
|
||||
{{ "openAtLogin" | i18n }}
|
||||
@ -324,9 +323,8 @@
|
||||
<input
|
||||
id="alwaysShowDock"
|
||||
type="checkbox"
|
||||
name="AlwaysShowDock"
|
||||
aria-describedby="alwaysShowDockHelp"
|
||||
[(ngModel)]="alwaysShowDock"
|
||||
formControlName="alwaysShowDock"
|
||||
(change)="saveAlwaysShowDock()"
|
||||
/>
|
||||
{{ "alwaysShowDock" | i18n }}
|
||||
@ -342,9 +340,8 @@
|
||||
<input
|
||||
id="enableBrowserIntegration"
|
||||
type="checkbox"
|
||||
name="EnableBrowserIntegration"
|
||||
aria-describedby="enableBrowserIntegrationHelp"
|
||||
[(ngModel)]="enableBrowserIntegration"
|
||||
formControlName="enableBrowserIntegration"
|
||||
(change)="saveBrowserIntegration()"
|
||||
/>
|
||||
{{ "enableBrowserIntegration" | i18n }}
|
||||
@ -360,11 +357,9 @@
|
||||
<input
|
||||
id="enableBrowserIntegrationFingerprint"
|
||||
type="checkbox"
|
||||
name="EnableBrowserIntegrationFingerprint"
|
||||
aria-describedby="enableBrowserIntegrationFingerprintHelp"
|
||||
[(ngModel)]="enableBrowserIntegrationFingerprint"
|
||||
formControlName="enableBrowserIntegrationFingerprint"
|
||||
(change)="saveBrowserIntegrationFingerprint()"
|
||||
[disabled]="!enableBrowserIntegration"
|
||||
/>
|
||||
{{ "enableBrowserIntegrationFingerprint" | i18n }}
|
||||
</label>
|
||||
@ -379,8 +374,7 @@
|
||||
<input
|
||||
id="enableDuckDuckGoBrowserIntegration"
|
||||
type="checkbox"
|
||||
name="enableDuckDuckGoBrowserIntegration"
|
||||
[(ngModel)]="enableDuckDuckGoBrowserIntegration"
|
||||
formControlName="enableDuckDuckGoBrowserIntegration"
|
||||
(change)="saveDdgBrowserIntegration()"
|
||||
/>
|
||||
{{ "enableDuckDuckGoBrowserIntegration" | i18n }}
|
||||
@ -394,9 +388,8 @@
|
||||
<label for="theme">{{ "theme" | i18n }}</label>
|
||||
<select
|
||||
id="theme"
|
||||
name="Theme"
|
||||
aria-describedby="themeHelp"
|
||||
[(ngModel)]="theme"
|
||||
formControlName="theme"
|
||||
(change)="saveTheme()"
|
||||
>
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
@ -407,9 +400,8 @@
|
||||
<label for="locale">{{ "language" | i18n }}</label>
|
||||
<select
|
||||
id="locale"
|
||||
name="Locale"
|
||||
aria-describedby="localeHelp"
|
||||
[(ngModel)]="locale"
|
||||
formControlName="locale"
|
||||
(change)="saveLocale()"
|
||||
>
|
||||
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { UntypedFormControl } from "@angular/forms";
|
||||
import { debounceTime } from "rxjs/operators";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { concatMap, debounceTime, filter, map, takeUntil, tap } from "rxjs/operators";
|
||||
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||
@ -10,7 +11,10 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { DeviceType, ThemeType, StorageLocation } from "@bitwarden/common/enums";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
import { flagEnabled } from "../../flags";
|
||||
@ -23,36 +27,20 @@ import { SetPinComponent } from "../components/set-pin.component";
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class SettingsComponent implements OnInit {
|
||||
vaultTimeoutAction: string;
|
||||
pin: boolean = null;
|
||||
enableFavicons = false;
|
||||
enableBrowserIntegration = false;
|
||||
enableDuckDuckGoBrowserIntegration = false;
|
||||
enableBrowserIntegrationFingerprint = false;
|
||||
enableMinToTray = false;
|
||||
enableCloseToTray = false;
|
||||
enableTray = false;
|
||||
// For use in template
|
||||
protected readonly VaultTimeoutAction = VaultTimeoutAction;
|
||||
|
||||
showMinToTray = false;
|
||||
startToTray = false;
|
||||
minimizeOnCopyToClipboard = false;
|
||||
locale: string;
|
||||
vaultTimeouts: any[];
|
||||
vaultTimeoutOptions: any[];
|
||||
localeOptions: any[];
|
||||
theme: ThemeType;
|
||||
themeOptions: any[];
|
||||
clearClipboard: number;
|
||||
clearClipboardOptions: any[];
|
||||
supportsBiometric: boolean;
|
||||
biometric: boolean;
|
||||
biometricText: string;
|
||||
autoPromptBiometrics: boolean;
|
||||
autoPromptBiometricsText: string;
|
||||
alwaysShowDock: boolean;
|
||||
showAlwaysShowDock = false;
|
||||
openAtLogin: boolean;
|
||||
requireEnableTray = false;
|
||||
showDuckDuckGoIntegrationOption = false;
|
||||
approveLoginRequests = false;
|
||||
|
||||
enableTrayText: string;
|
||||
enableTrayDescText: string;
|
||||
@ -63,17 +51,52 @@ export class SettingsComponent implements OnInit {
|
||||
startToTrayText: string;
|
||||
startToTrayDescText: string;
|
||||
|
||||
vaultTimeout: UntypedFormControl = new UntypedFormControl(null);
|
||||
|
||||
showSecurity = true;
|
||||
showAccountPreferences = true;
|
||||
showAppPreferences = true;
|
||||
|
||||
currentUserEmail: string;
|
||||
|
||||
vaultTimeoutPolicyCallout: Observable<{
|
||||
timeout: { hours: number; minutes: number };
|
||||
action: "lock" | "logOut";
|
||||
}>;
|
||||
previousVaultTimeout: number = null;
|
||||
|
||||
form = this.formBuilder.group({
|
||||
// Security
|
||||
vaultTimeout: [null as number | null],
|
||||
vaultTimeoutAction: [VaultTimeoutAction.Lock],
|
||||
pin: [null as boolean | null],
|
||||
biometric: false,
|
||||
autoPromptBiometrics: false,
|
||||
approveLoginRequests: false,
|
||||
// Account Preferences
|
||||
clearClipboard: [null as number | null],
|
||||
minimizeOnCopyToClipboard: false,
|
||||
enableFavicons: false,
|
||||
// App Settings
|
||||
enableTray: false,
|
||||
enableMinToTray: false,
|
||||
enableCloseToTray: false,
|
||||
startToTray: false,
|
||||
openAtLogin: false,
|
||||
alwaysShowDock: false,
|
||||
enableBrowserIntegration: false,
|
||||
enableBrowserIntegrationFingerprint: this.formBuilder.control<boolean>({
|
||||
value: false,
|
||||
disabled: true,
|
||||
}),
|
||||
enableDuckDuckGoBrowserIntegration: false,
|
||||
theme: [null as ThemeType | null],
|
||||
locale: [null as string | null],
|
||||
});
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private policyService: PolicyService,
|
||||
private formBuilder: FormBuilder,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
@ -107,108 +130,158 @@ export class SettingsComponent implements OnInit {
|
||||
// DuckDuckGo browser is only for macos initially
|
||||
this.showDuckDuckGoIntegrationOption = flagEnabled("showDDGSetting") && isMac;
|
||||
|
||||
this.vaultTimeouts = [
|
||||
this.vaultTimeoutOptions = [
|
||||
// { name: i18nService.t('immediately'), value: 0 },
|
||||
{ name: i18nService.t("oneMinute"), value: 1 },
|
||||
{ name: i18nService.t("fiveMinutes"), value: 5 },
|
||||
{ name: i18nService.t("fifteenMinutes"), value: 15 },
|
||||
{ name: i18nService.t("thirtyMinutes"), value: 30 },
|
||||
{ name: i18nService.t("oneHour"), value: 60 },
|
||||
{ name: i18nService.t("fourHours"), value: 240 },
|
||||
{ name: i18nService.t("onIdle"), value: -4 },
|
||||
{ name: i18nService.t("onSleep"), value: -3 },
|
||||
{ name: this.i18nService.t("oneMinute"), value: 1 },
|
||||
{ name: this.i18nService.t("fiveMinutes"), value: 5 },
|
||||
{ name: this.i18nService.t("fifteenMinutes"), value: 15 },
|
||||
{ name: this.i18nService.t("thirtyMinutes"), value: 30 },
|
||||
{ name: this.i18nService.t("oneHour"), value: 60 },
|
||||
{ name: this.i18nService.t("fourHours"), value: 240 },
|
||||
{ name: this.i18nService.t("onIdle"), value: -4 },
|
||||
{ name: this.i18nService.t("onSleep"), value: -3 },
|
||||
];
|
||||
|
||||
if (this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop) {
|
||||
this.vaultTimeouts.push({ name: i18nService.t("onLocked"), value: -2 });
|
||||
this.vaultTimeoutOptions.push({ name: this.i18nService.t("onLocked"), value: -2 });
|
||||
}
|
||||
|
||||
this.vaultTimeouts = this.vaultTimeouts.concat([
|
||||
{ name: i18nService.t("onRestart"), value: -1 },
|
||||
{ name: i18nService.t("never"), value: null },
|
||||
this.vaultTimeoutOptions = this.vaultTimeoutOptions.concat([
|
||||
{ name: this.i18nService.t("onRestart"), value: -1 },
|
||||
{ name: this.i18nService.t("never"), value: null },
|
||||
]);
|
||||
|
||||
const localeOptions: any[] = [];
|
||||
i18nService.supportedTranslationLocales.forEach((locale) => {
|
||||
this.i18nService.supportedTranslationLocales.forEach((locale) => {
|
||||
let name = locale;
|
||||
if (i18nService.localeNames.has(locale)) {
|
||||
name += " - " + i18nService.localeNames.get(locale);
|
||||
if (this.i18nService.localeNames.has(locale)) {
|
||||
name += " - " + this.i18nService.localeNames.get(locale);
|
||||
}
|
||||
localeOptions.push({ name: name, value: locale });
|
||||
});
|
||||
localeOptions.sort(Utils.getSortFunction(i18nService, "name"));
|
||||
localeOptions.splice(0, 0, { name: i18nService.t("default"), value: null });
|
||||
localeOptions.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
localeOptions.splice(0, 0, { name: this.i18nService.t("default"), value: null });
|
||||
this.localeOptions = localeOptions;
|
||||
|
||||
this.themeOptions = [
|
||||
{ name: i18nService.t("default"), value: ThemeType.System },
|
||||
{ name: i18nService.t("light"), value: ThemeType.Light },
|
||||
{ name: i18nService.t("dark"), value: ThemeType.Dark },
|
||||
{ name: this.i18nService.t("default"), value: ThemeType.System },
|
||||
{ name: this.i18nService.t("light"), value: ThemeType.Light },
|
||||
{ name: this.i18nService.t("dark"), value: ThemeType.Dark },
|
||||
{ name: "Nord", value: ThemeType.Nord },
|
||||
];
|
||||
|
||||
this.clearClipboardOptions = [
|
||||
{ name: i18nService.t("never"), value: null },
|
||||
{ name: i18nService.t("tenSeconds"), value: 10 },
|
||||
{ name: i18nService.t("twentySeconds"), value: 20 },
|
||||
{ name: i18nService.t("thirtySeconds"), value: 30 },
|
||||
{ name: i18nService.t("oneMinute"), value: 60 },
|
||||
{ name: i18nService.t("twoMinutes"), value: 120 },
|
||||
{ name: i18nService.t("fiveMinutes"), value: 300 },
|
||||
{ name: this.i18nService.t("never"), value: null },
|
||||
{ name: this.i18nService.t("tenSeconds"), value: 10 },
|
||||
{ name: this.i18nService.t("twentySeconds"), value: 20 },
|
||||
{ name: this.i18nService.t("thirtySeconds"), value: 30 },
|
||||
{ name: this.i18nService.t("oneMinute"), value: 60 },
|
||||
{ name: this.i18nService.t("twoMinutes"), value: 120 },
|
||||
{ name: this.i18nService.t("fiveMinutes"), value: 300 },
|
||||
];
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// App preferences
|
||||
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
|
||||
this.enableMinToTray = await this.stateService.getEnableMinimizeToTray();
|
||||
this.enableCloseToTray = await this.stateService.getEnableCloseToTray();
|
||||
this.enableTray = await this.stateService.getEnableTray();
|
||||
this.startToTray = await this.stateService.getEnableStartToTray();
|
||||
|
||||
this.alwaysShowDock = await this.stateService.getAlwaysShowDock();
|
||||
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
this.openAtLogin = await this.stateService.getOpenAtLogin();
|
||||
|
||||
this.locale = (await this.stateService.getLocale()) ?? null;
|
||||
this.theme = await this.stateService.getTheme();
|
||||
|
||||
if ((await this.stateService.getUserId()) == null) {
|
||||
return;
|
||||
}
|
||||
this.currentUserEmail = await this.stateService.getEmail();
|
||||
|
||||
// Security
|
||||
this.vaultTimeout.setValue(await this.stateService.getVaultTimeout());
|
||||
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
||||
this.previousVaultTimeout = this.vaultTimeout.value;
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.vaultTimeout.valueChanges.pipe(debounceTime(500)).subscribe(() => {
|
||||
this.saveVaultTimeoutOptions();
|
||||
});
|
||||
// Load timeout policy
|
||||
this.vaultTimeoutPolicyCallout = this.policyService.get$(PolicyType.MaximumVaultTimeout).pipe(
|
||||
filter((policy) => policy != null),
|
||||
map((policy) => {
|
||||
let timeout;
|
||||
if (policy.data?.minutes) {
|
||||
timeout = {
|
||||
hours: Math.floor(policy.data?.minutes / 60),
|
||||
minutes: policy.data?.minutes % 60,
|
||||
};
|
||||
}
|
||||
return { timeout: timeout, action: policy.data?.action };
|
||||
}),
|
||||
tap((policy) => {
|
||||
if (policy.action) {
|
||||
this.form.controls.vaultTimeoutAction.disable({ emitEvent: false });
|
||||
} else {
|
||||
this.form.controls.vaultTimeoutAction.enable({ emitEvent: false });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Load initial values
|
||||
const pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||
this.pin = pinSet[0] || pinSet[1];
|
||||
this.approveLoginRequests = await this.stateService.getApproveLoginRequests();
|
||||
const initialValues = {
|
||||
vaultTimeout: await this.vaultTimeoutSettingsService.getVaultTimeout(),
|
||||
vaultTimeoutAction: await this.vaultTimeoutSettingsService.getVaultTimeoutAction(),
|
||||
pin: pinSet[0] || pinSet[1],
|
||||
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
|
||||
autoPromptBiometrics: !(await this.stateService.getNoAutoPromptBiometrics()),
|
||||
approveLoginRequests: (await this.stateService.getApproveLoginRequests()) ?? false,
|
||||
clearClipboard: await this.stateService.getClearClipboard(),
|
||||
minimizeOnCopyToClipboard: await this.stateService.getMinimizeOnCopyToClipboard(),
|
||||
enableFavicons: !(await this.stateService.getDisableFavicon()),
|
||||
enableTray: await this.stateService.getEnableTray(),
|
||||
enableMinToTray: await this.stateService.getEnableMinimizeToTray(),
|
||||
enableCloseToTray: await this.stateService.getEnableCloseToTray(),
|
||||
startToTray: await this.stateService.getEnableStartToTray(),
|
||||
openAtLogin: await this.stateService.getOpenAtLogin(),
|
||||
alwaysShowDock: await this.stateService.getAlwaysShowDock(),
|
||||
enableBrowserIntegration: await this.stateService.getEnableBrowserIntegration(),
|
||||
enableBrowserIntegrationFingerprint:
|
||||
await this.stateService.getEnableBrowserIntegrationFingerprint(),
|
||||
enableDuckDuckGoBrowserIntegration:
|
||||
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
|
||||
theme: await this.stateService.getTheme(),
|
||||
locale: (await this.stateService.getLocale()) ?? null,
|
||||
};
|
||||
this.form.setValue(initialValues, { emitEvent: false });
|
||||
|
||||
// Account preferences
|
||||
this.enableFavicons = !(await this.stateService.getDisableFavicon());
|
||||
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
|
||||
this.enableDuckDuckGoBrowserIntegration =
|
||||
await this.stateService.getEnableDuckDuckGoBrowserIntegration();
|
||||
this.enableBrowserIntegrationFingerprint =
|
||||
await this.stateService.getEnableBrowserIntegrationFingerprint();
|
||||
this.clearClipboard = await this.stateService.getClearClipboard();
|
||||
this.minimizeOnCopyToClipboard = await this.stateService.getMinimizeOnCopyToClipboard();
|
||||
if (this.form.value.enableBrowserIntegration) {
|
||||
this.form.controls.enableBrowserIntegrationFingerprint.enable();
|
||||
}
|
||||
|
||||
// Non-form values
|
||||
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
|
||||
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||
this.biometric = await this.vaultTimeoutSettingsService.isBiometricLockSet();
|
||||
this.biometricText = await this.stateService.getBiometricText();
|
||||
this.autoPromptBiometrics = !(await this.stateService.getNoAutoPromptBiometrics());
|
||||
this.autoPromptBiometricsText = await this.stateService.getNoAutoPromptBiometricsText();
|
||||
this.previousVaultTimeout = this.form.value.vaultTimeout;
|
||||
|
||||
// Form events
|
||||
this.form.controls.vaultTimeout.valueChanges
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
concatMap(async (value) => {
|
||||
await this.saveVaultTimeout(value);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.form.controls.vaultTimeoutAction.valueChanges
|
||||
.pipe(
|
||||
concatMap(async (action) => {
|
||||
await this.saveVaultTimeoutAction(action);
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.form.controls.enableBrowserIntegration.valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((enabled) => {
|
||||
if (enabled) {
|
||||
this.form.controls.enableBrowserIntegrationFingerprint.enable();
|
||||
} else {
|
||||
this.form.controls.enableBrowserIntegrationFingerprint.disable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async saveVaultTimeoutOptions() {
|
||||
if (this.vaultTimeout.value == null) {
|
||||
async saveVaultTimeout(newValue: number) {
|
||||
if (newValue == null) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("neverLockWarning"),
|
||||
"",
|
||||
@ -217,31 +290,17 @@ export class SettingsComponent implements OnInit {
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.vaultTimeout.setValue(this.previousVaultTimeout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.vaultTimeoutAction === "logOut") {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.vaultTimeoutAction = "lock";
|
||||
this.form.controls.vaultTimeout.setValue(this.previousVaultTimeout);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid saving 0 since it's useless as a timeout value.
|
||||
if (this.vaultTimeout.value === 0) {
|
||||
if (this.form.value.vaultTimeout === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.vaultTimeout.valid) {
|
||||
if (!this.form.controls.vaultTimeout.valid) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
@ -250,39 +309,71 @@ export class SettingsComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.previousVaultTimeout = this.vaultTimeout.value;
|
||||
this.previousVaultTimeout = this.form.value.vaultTimeout;
|
||||
|
||||
await this.vaultTimeoutSettingsService.setVaultTimeoutOptions(
|
||||
this.vaultTimeout.value,
|
||||
this.vaultTimeoutAction
|
||||
newValue,
|
||||
this.form.value.vaultTimeoutAction
|
||||
);
|
||||
}
|
||||
|
||||
async saveVaultTimeoutAction(newValue: VaultTimeoutAction) {
|
||||
if (newValue === "logOut") {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.form.controls.vaultTimeoutAction.patchValue(VaultTimeoutAction.Lock, {
|
||||
emitEvent: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.form.controls.vaultTimeout.hasError("policyError")) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("vaultTimeoutTooLarge")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.vaultTimeoutSettingsService.setVaultTimeoutOptions(
|
||||
this.form.value.vaultTimeout,
|
||||
newValue
|
||||
);
|
||||
}
|
||||
|
||||
async updatePin() {
|
||||
if (this.pin) {
|
||||
if (this.form.value.pin) {
|
||||
const ref = this.modalService.open(SetPinComponent, { allowMultipleModals: true });
|
||||
|
||||
if (ref == null) {
|
||||
this.pin = false;
|
||||
this.form.controls.pin.setValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pin = await ref.onClosedPromise();
|
||||
this.form.controls.pin.setValue(await ref.onClosedPromise());
|
||||
}
|
||||
if (!this.pin) {
|
||||
if (!this.form.value.pin) {
|
||||
await this.cryptoService.clearPinProtectedKey();
|
||||
await this.vaultTimeoutSettingsService.clear();
|
||||
}
|
||||
}
|
||||
|
||||
async updateBiometric(newValue: boolean) {
|
||||
async updateBiometric() {
|
||||
// NOTE: A bug in angular causes [ngModel] to not reflect the backing field value
|
||||
// causing the checkbox to remain checked even if authentication fails.
|
||||
// The bug should resolve itself once the angular issue is resolved.
|
||||
// See: https://github.com/angular/angular/issues/13063
|
||||
|
||||
if (!newValue || !this.supportsBiometric) {
|
||||
this.biometric = false;
|
||||
if (!this.form.value.biometric || !this.supportsBiometric) {
|
||||
this.form.controls.biometric.setValue(false);
|
||||
await this.stateService.setBiometricUnlock(null);
|
||||
await this.cryptoService.toggleKey();
|
||||
return;
|
||||
@ -291,17 +382,17 @@ export class SettingsComponent implements OnInit {
|
||||
const authResult = await this.platformUtilsService.authenticateBiometric();
|
||||
|
||||
if (!authResult) {
|
||||
this.biometric = false;
|
||||
this.form.controls.biometric.setValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.biometric = true;
|
||||
this.form.controls.biometric.setValue(true);
|
||||
await this.stateService.setBiometricUnlock(true);
|
||||
await this.cryptoService.toggleKey();
|
||||
}
|
||||
|
||||
async updateAutoPromptBiometrics() {
|
||||
if (this.autoPromptBiometrics) {
|
||||
if (this.form.value.autoPromptBiometrics) {
|
||||
await this.stateService.setNoAutoPromptBiometrics(null);
|
||||
} else {
|
||||
await this.stateService.setNoAutoPromptBiometrics(true);
|
||||
@ -309,31 +400,31 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async saveFavicons() {
|
||||
await this.stateService.setDisableFavicon(!this.enableFavicons);
|
||||
await this.stateService.setDisableFavicon(!this.enableFavicons, {
|
||||
await this.stateService.setDisableFavicon(!this.form.value.enableFavicons);
|
||||
await this.stateService.setDisableFavicon(!this.form.value.enableFavicons, {
|
||||
storageLocation: StorageLocation.Disk,
|
||||
});
|
||||
this.messagingService.send("refreshCiphers");
|
||||
}
|
||||
|
||||
async saveMinToTray() {
|
||||
await this.stateService.setEnableMinimizeToTray(this.enableMinToTray);
|
||||
await this.stateService.setEnableMinimizeToTray(this.form.value.enableMinToTray);
|
||||
}
|
||||
|
||||
async saveCloseToTray() {
|
||||
if (this.requireEnableTray) {
|
||||
this.enableTray = true;
|
||||
await this.stateService.setEnableTray(this.enableTray);
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
await this.stateService.setEnableTray(this.form.value.enableTray);
|
||||
}
|
||||
|
||||
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
|
||||
await this.stateService.setEnableCloseToTray(this.form.value.enableCloseToTray);
|
||||
}
|
||||
|
||||
async saveTray() {
|
||||
if (
|
||||
this.requireEnableTray &&
|
||||
!this.enableTray &&
|
||||
(this.startToTray || this.enableCloseToTray)
|
||||
!this.form.value.enableTray &&
|
||||
(this.form.value.startToTray || this.form.value.enableCloseToTray)
|
||||
) {
|
||||
const confirm = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("confirmTrayDesc"),
|
||||
@ -344,53 +435,55 @@ export class SettingsComponent implements OnInit {
|
||||
);
|
||||
|
||||
if (confirm) {
|
||||
this.startToTray = false;
|
||||
await this.stateService.setEnableStartToTray(this.startToTray);
|
||||
this.enableCloseToTray = false;
|
||||
await this.stateService.setEnableCloseToTray(this.enableCloseToTray);
|
||||
this.form.controls.startToTray.setValue(false, { emitEvent: false });
|
||||
await this.stateService.setEnableStartToTray(this.form.value.startToTray);
|
||||
this.form.controls.enableCloseToTray.setValue(false, { emitEvent: false });
|
||||
await this.stateService.setEnableCloseToTray(this.form.value.enableCloseToTray);
|
||||
} else {
|
||||
this.enableTray = true;
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.stateService.setEnableTray(this.enableTray);
|
||||
this.messagingService.send(this.enableTray ? "showTray" : "removeTray");
|
||||
await this.stateService.setEnableTray(this.form.value.enableTray);
|
||||
this.messagingService.send(this.form.value.enableTray ? "showTray" : "removeTray");
|
||||
}
|
||||
|
||||
async saveStartToTray() {
|
||||
if (this.requireEnableTray) {
|
||||
this.enableTray = true;
|
||||
await this.stateService.setEnableTray(this.enableTray);
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
await this.stateService.setEnableTray(this.form.value.enableTray);
|
||||
}
|
||||
|
||||
await this.stateService.setEnableStartToTray(this.startToTray);
|
||||
await this.stateService.setEnableStartToTray(this.form.value.startToTray);
|
||||
}
|
||||
|
||||
async saveLocale() {
|
||||
await this.stateService.setLocale(this.locale);
|
||||
await this.stateService.setLocale(this.form.value.locale);
|
||||
}
|
||||
|
||||
async saveTheme() {
|
||||
await this.themingService.updateConfiguredTheme(this.theme);
|
||||
await this.themingService.updateConfiguredTheme(this.form.value.theme);
|
||||
}
|
||||
|
||||
async saveMinOnCopyToClipboard() {
|
||||
await this.stateService.setMinimizeOnCopyToClipboard(this.minimizeOnCopyToClipboard);
|
||||
await this.stateService.setMinimizeOnCopyToClipboard(this.form.value.minimizeOnCopyToClipboard);
|
||||
}
|
||||
|
||||
async saveClearClipboard() {
|
||||
await this.stateService.setClearClipboard(this.clearClipboard);
|
||||
await this.stateService.setClearClipboard(this.form.value.clearClipboard);
|
||||
}
|
||||
|
||||
async saveAlwaysShowDock() {
|
||||
await this.stateService.setAlwaysShowDock(this.alwaysShowDock);
|
||||
await this.stateService.setAlwaysShowDock(this.form.value.alwaysShowDock);
|
||||
}
|
||||
|
||||
async saveOpenAtLogin() {
|
||||
this.stateService.setOpenAtLogin(this.openAtLogin);
|
||||
this.messagingService.send(this.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin");
|
||||
this.stateService.setOpenAtLogin(this.form.value.openAtLogin);
|
||||
this.messagingService.send(
|
||||
this.form.value.openAtLogin ? "addOpenAtLogin" : "removeOpenAtLogin"
|
||||
);
|
||||
}
|
||||
|
||||
async saveBrowserIntegration() {
|
||||
@ -403,7 +496,7 @@ export class SettingsComponent implements OnInit {
|
||||
"warning"
|
||||
);
|
||||
|
||||
this.enableBrowserIntegration = false;
|
||||
this.form.controls.enableBrowserIntegration.setValue(false);
|
||||
return;
|
||||
} else if (isWindowsStore()) {
|
||||
await this.platformUtilsService.showDialog(
|
||||
@ -414,7 +507,7 @@ export class SettingsComponent implements OnInit {
|
||||
"warning"
|
||||
);
|
||||
|
||||
this.enableBrowserIntegration = false;
|
||||
this.form.controls.enableBrowserIntegration.setValue(false);
|
||||
return;
|
||||
} else if (process.platform == "linux") {
|
||||
await this.platformUtilsService.showDialog(
|
||||
@ -425,32 +518,34 @@ export class SettingsComponent implements OnInit {
|
||||
"warning"
|
||||
);
|
||||
|
||||
this.enableBrowserIntegration = false;
|
||||
this.form.controls.enableBrowserIntegration.setValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.stateService.setEnableBrowserIntegration(this.enableBrowserIntegration);
|
||||
await this.stateService.setEnableBrowserIntegration(this.form.value.enableBrowserIntegration);
|
||||
this.messagingService.send(
|
||||
this.enableBrowserIntegration ? "enableBrowserIntegration" : "disableBrowserIntegration"
|
||||
this.form.value.enableBrowserIntegration
|
||||
? "enableBrowserIntegration"
|
||||
: "disableBrowserIntegration"
|
||||
);
|
||||
|
||||
if (!this.enableBrowserIntegration) {
|
||||
this.enableBrowserIntegrationFingerprint = false;
|
||||
if (!this.form.value.enableBrowserIntegration) {
|
||||
this.form.controls.enableBrowserIntegrationFingerprint.setValue(false);
|
||||
this.saveBrowserIntegrationFingerprint();
|
||||
}
|
||||
}
|
||||
|
||||
async saveDdgBrowserIntegration() {
|
||||
await this.stateService.setEnableDuckDuckGoBrowserIntegration(
|
||||
this.enableDuckDuckGoBrowserIntegration
|
||||
this.form.value.enableDuckDuckGoBrowserIntegration
|
||||
);
|
||||
|
||||
if (!this.enableBrowserIntegration) {
|
||||
if (!this.form.value.enableBrowserIntegration) {
|
||||
await this.stateService.setDuckDuckGoSharedKey(null);
|
||||
}
|
||||
|
||||
this.messagingService.send(
|
||||
this.enableDuckDuckGoBrowserIntegration
|
||||
this.form.value.enableDuckDuckGoBrowserIntegration
|
||||
? "enableDuckDuckGoBrowserIntegration"
|
||||
: "disableDuckDuckGoBrowserIntegration"
|
||||
);
|
||||
@ -458,11 +553,16 @@ export class SettingsComponent implements OnInit {
|
||||
|
||||
async saveBrowserIntegrationFingerprint() {
|
||||
await this.stateService.setEnableBrowserIntegrationFingerprint(
|
||||
this.enableBrowserIntegrationFingerprint
|
||||
this.form.value.enableBrowserIntegrationFingerprint
|
||||
);
|
||||
}
|
||||
|
||||
async updateApproveLoginRequests() {
|
||||
await this.stateService.setApproveLoginRequests(this.approveLoginRequests);
|
||||
await this.stateService.setApproveLoginRequests(this.form.value.approveLoginRequests);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
<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>
|
||||
@ -12,7 +8,7 @@
|
||||
formControlName="vaultTimeout"
|
||||
class="form-control"
|
||||
>
|
||||
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
|
||||
<option *ngFor="let o of vaultTimeoutOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small id="vaultTimeoutHelp" class="help-block">{{ "vaultTimeoutDesc" | i18n }}</small>
|
||||
</div>
|
||||
|
@ -1850,7 +1850,7 @@
|
||||
"message": "Minutes"
|
||||
},
|
||||
"vaultTimeoutPolicyInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)",
|
||||
"message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
@ -1862,6 +1862,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutPolicyWithActionInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "5"
|
||||
},
|
||||
"action": {
|
||||
"content": "$3",
|
||||
"example": "Lock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutActionPolicyInEffect": {
|
||||
"message": "Your organization policies have set your vault timeout action to $ACTION$.",
|
||||
"placeholders": {
|
||||
"action": {
|
||||
"content": "$1",
|
||||
"example": "Lock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutTooLarge": {
|
||||
"message": "Your vault timeout exceeds the restrictions set by your organization."
|
||||
},
|
||||
|
@ -2,12 +2,26 @@
|
||||
<h1>{{ "preferences" | i18n }}</h1>
|
||||
</div>
|
||||
<p>{{ "preferencesDesc" | i18n }}</p>
|
||||
<form (ngSubmit)="submit()" ngNativeValidate>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" ngNativeValidate>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<app-callout type="info" *ngIf="vaultTimeoutPolicyCallout | async as policy">
|
||||
<span *ngIf="policy.timeout && policy.action">
|
||||
{{
|
||||
"vaultTimeoutPolicyWithActionInEffect"
|
||||
| i18n : policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
|
||||
}}
|
||||
</span>
|
||||
<span *ngIf="policy.timeout && !policy.action">
|
||||
{{ "vaultTimeoutPolicyInEffect" | i18n : policy.timeout.hours : policy.timeout.minutes }}
|
||||
</span>
|
||||
<span *ngIf="!policy.timeout && policy.action">
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n : (policy.action | i18n) }}
|
||||
</span>
|
||||
</app-callout>
|
||||
<app-vault-timeout-input
|
||||
[vaultTimeouts]="vaultTimeouts"
|
||||
[formControl]="vaultTimeout"
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</app-vault-timeout-input>
|
||||
@ -21,8 +35,8 @@
|
||||
type="radio"
|
||||
name="vaultTimeoutAction"
|
||||
id="vaultTimeoutActionLock"
|
||||
value="lock"
|
||||
[(ngModel)]="vaultTimeoutAction"
|
||||
value="{{ VaultTimeoutAction.Lock }}"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
<label class="form-check-label" for="vaultTimeoutActionLock">
|
||||
{{ "lock" | i18n }}
|
||||
@ -35,9 +49,8 @@
|
||||
type="radio"
|
||||
name="vaultTimeoutAction"
|
||||
id="vaultTimeoutActionLogOut"
|
||||
value="logOut"
|
||||
[(ngModel)]="vaultTimeoutAction"
|
||||
(ngModelChange)="vaultTimeoutActionChanged($event)"
|
||||
value="{{ VaultTimeoutAction.LogOut }}"
|
||||
formControlName="vaultTimeoutAction"
|
||||
/>
|
||||
<label class="form-check-label" for="vaultTimeoutActionLogOut">
|
||||
{{ "logOut" | i18n }}
|
||||
@ -60,7 +73,7 @@
|
||||
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
<select id="locale" name="Locale" [(ngModel)]="locale" class="form-control">
|
||||
<select id="locale" name="Locale" formControlName="locale" class="form-control">
|
||||
<option *ngFor="let o of localeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "languageDesc" | i18n }}</small>
|
||||
@ -74,7 +87,7 @@
|
||||
type="checkbox"
|
||||
id="enableFavicons"
|
||||
name="enableFavicons"
|
||||
[(ngModel)]="enableFavicons"
|
||||
formControlName="enableFavicons"
|
||||
/>
|
||||
<label class="form-check-label" for="enableFavicons">
|
||||
{{ "enableFavicon" | i18n }}
|
||||
@ -97,7 +110,7 @@
|
||||
type="checkbox"
|
||||
id="enableFullWidth"
|
||||
name="enableFullWidth"
|
||||
[(ngModel)]="enableFullWidth"
|
||||
formControlName="enableFullWidth"
|
||||
/>
|
||||
<label class="form-check-label" for="enableFullWidth">
|
||||
{{ "enableFullWidth" | i18n }}
|
||||
@ -109,7 +122,7 @@
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label for="theme">{{ "theme" | i18n }}</label>
|
||||
<select id="theme" name="theme" [(ngModel)]="theme" class="form-control">
|
||||
<select id="theme" name="theme" formControlName="theme" class="form-control">
|
||||
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "themeDesc" | i18n }}</small>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { UntypedFormControl } from "@angular/forms";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { concatMap, filter, map, Observable, Subject, takeUntil, tap } from "rxjs";
|
||||
|
||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
@ -7,7 +8,10 @@ import { MessagingService } from "@bitwarden/common/abstractions/messaging.servi
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { ThemeType } from "@bitwarden/common/enums";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
@Component({
|
||||
@ -15,21 +19,33 @@ import { Utils } from "@bitwarden/common/misc/utils";
|
||||
templateUrl: "preferences.component.html",
|
||||
})
|
||||
export class PreferencesComponent implements OnInit {
|
||||
vaultTimeoutAction = "lock";
|
||||
enableFavicons: boolean;
|
||||
enableFullWidth: boolean;
|
||||
theme: ThemeType;
|
||||
locale: string;
|
||||
vaultTimeouts: { name: string; value: number }[];
|
||||
// For use in template
|
||||
protected readonly VaultTimeoutAction = VaultTimeoutAction;
|
||||
|
||||
vaultTimeoutPolicyCallout: Observable<{
|
||||
timeout: { hours: number; minutes: number };
|
||||
action: VaultTimeoutAction;
|
||||
}>;
|
||||
vaultTimeoutOptions: { name: string; value: number }[];
|
||||
localeOptions: any[];
|
||||
themeOptions: any[];
|
||||
|
||||
vaultTimeout: UntypedFormControl = new UntypedFormControl(null);
|
||||
|
||||
private startingLocale: string;
|
||||
private startingTheme: ThemeType;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
form = this.formBuilder.group({
|
||||
vaultTimeout: [null as number | null],
|
||||
vaultTimeoutAction: [VaultTimeoutAction.Lock],
|
||||
enableFavicons: true,
|
||||
enableFullWidth: false,
|
||||
theme: [ThemeType.Light],
|
||||
locale: [null as string | null],
|
||||
});
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private policyService: PolicyService,
|
||||
private stateService: StateService,
|
||||
private i18nService: I18nService,
|
||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
@ -37,7 +53,7 @@ export class PreferencesComponent implements OnInit {
|
||||
private messagingService: MessagingService,
|
||||
private themingService: AbstractThemingService
|
||||
) {
|
||||
this.vaultTimeouts = [
|
||||
this.vaultTimeoutOptions = [
|
||||
{ name: i18nService.t("oneMinute"), value: 1 },
|
||||
{ name: i18nService.t("fiveMinutes"), value: 5 },
|
||||
{ name: i18nService.t("fifteenMinutes"), value: 15 },
|
||||
@ -47,7 +63,7 @@ export class PreferencesComponent implements OnInit {
|
||||
{ name: i18nService.t("onRefresh"), value: -1 },
|
||||
];
|
||||
if (this.platformUtilsService.isDev()) {
|
||||
this.vaultTimeouts.push({ name: i18nService.t("never"), value: null });
|
||||
this.vaultTimeoutOptions.push({ name: i18nService.t("never"), value: null });
|
||||
}
|
||||
|
||||
const localeOptions: any[] = [];
|
||||
@ -69,20 +85,65 @@ export class PreferencesComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.vaultTimeout.setValue(await this.vaultTimeoutSettingsService.getVaultTimeout());
|
||||
this.vaultTimeoutAction = await this.stateService.getVaultTimeoutAction();
|
||||
this.enableFavicons = !(await this.stateService.getDisableFavicon());
|
||||
this.enableFullWidth = await this.stateService.getEnableFullWidth();
|
||||
this.vaultTimeoutPolicyCallout = this.policyService.get$(PolicyType.MaximumVaultTimeout).pipe(
|
||||
filter((policy) => policy != null),
|
||||
map((policy) => {
|
||||
let timeout;
|
||||
if (policy.data?.minutes) {
|
||||
timeout = {
|
||||
hours: Math.floor(policy.data?.minutes / 60),
|
||||
minutes: policy.data?.minutes % 60,
|
||||
};
|
||||
}
|
||||
return { timeout: timeout, action: policy.data?.action };
|
||||
}),
|
||||
tap((policy) => {
|
||||
if (policy.action) {
|
||||
this.form.controls.vaultTimeoutAction.disable({ emitEvent: false });
|
||||
} else {
|
||||
this.form.controls.vaultTimeoutAction.enable({ emitEvent: false });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.locale = (await this.stateService.getLocale()) ?? null;
|
||||
this.startingLocale = this.locale;
|
||||
this.form.controls.vaultTimeoutAction.valueChanges
|
||||
.pipe(
|
||||
concatMap(async (action) => {
|
||||
if (action === VaultTimeoutAction.LogOut) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.form.controls.vaultTimeoutAction.patchValue(VaultTimeoutAction.Lock, {
|
||||
emitEvent: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.theme = await this.stateService.getTheme();
|
||||
this.startingTheme = this.theme;
|
||||
const initialFormValues = {
|
||||
vaultTimeout: await this.vaultTimeoutSettingsService.getVaultTimeout(),
|
||||
vaultTimeoutAction: await this.vaultTimeoutSettingsService.getVaultTimeoutAction(),
|
||||
enableFavicons: !(await this.stateService.getDisableFavicon()),
|
||||
enableFullWidth: await this.stateService.getEnableFullWidth(),
|
||||
theme: await this.stateService.getTheme(),
|
||||
locale: (await this.stateService.getLocale()) ?? null,
|
||||
};
|
||||
this.startingLocale = initialFormValues.locale;
|
||||
this.startingTheme = initialFormValues.theme;
|
||||
this.form.setValue(initialFormValues, { emitEvent: false });
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!this.vaultTimeout.valid) {
|
||||
if (!this.form.controls.vaultTimeout.valid) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
@ -90,20 +151,21 @@ export class PreferencesComponent implements OnInit {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const values = this.form.value;
|
||||
|
||||
await this.vaultTimeoutSettingsService.setVaultTimeoutOptions(
|
||||
this.vaultTimeout.value,
|
||||
this.vaultTimeoutAction
|
||||
values.vaultTimeout,
|
||||
values.vaultTimeoutAction
|
||||
);
|
||||
await this.stateService.setDisableFavicon(!this.enableFavicons);
|
||||
await this.stateService.setEnableFullWidth(this.enableFullWidth);
|
||||
await this.stateService.setDisableFavicon(!values.enableFavicons);
|
||||
await this.stateService.setEnableFullWidth(values.enableFullWidth);
|
||||
this.messagingService.send("setFullWidth");
|
||||
if (this.theme !== this.startingTheme) {
|
||||
await this.themingService.updateConfiguredTheme(this.theme);
|
||||
this.startingTheme = this.theme;
|
||||
if (values.theme !== this.startingTheme) {
|
||||
await this.themingService.updateConfiguredTheme(values.theme);
|
||||
this.startingTheme = values.theme;
|
||||
}
|
||||
await this.stateService.setLocale(this.locale);
|
||||
if (this.locale !== this.startingLocale) {
|
||||
await this.stateService.setLocale(values.locale);
|
||||
if (values.locale !== this.startingLocale) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
this.platformUtilsService.showToast(
|
||||
@ -114,20 +176,8 @@ export class PreferencesComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
async vaultTimeoutActionChanged(newValue: string) {
|
||||
if (newValue === "logOut") {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmation"),
|
||||
this.i18nService.t("vaultTimeoutLogOutConfirmationTitle"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("cancel"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.vaultTimeoutAction = "lock";
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.vaultTimeoutAction = newValue;
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
<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>
|
||||
@ -11,7 +7,7 @@
|
||||
formControlName="vaultTimeout"
|
||||
class="form-control"
|
||||
>
|
||||
<option *ngFor="let o of vaultTimeouts" [ngValue]="o.value">{{ o.name }}</option>
|
||||
<option *ngFor="let o of vaultTimeoutOptions" [ngValue]="o.value">{{ o.name }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ "vaultTimeoutDesc" | i18n }}</small>
|
||||
</div>
|
||||
|
@ -4832,7 +4832,7 @@
|
||||
"message": "Minutes"
|
||||
},
|
||||
"vaultTimeoutPolicyInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s)",
|
||||
"message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
@ -4844,6 +4844,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutPolicyWithActionInEffect": {
|
||||
"message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "5"
|
||||
},
|
||||
"action": {
|
||||
"content": "$3",
|
||||
"example": "Lock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"vaultTimeoutActionPolicyInEffect": {
|
||||
"message": "Your organization policies have set your vault timeout action to $ACTION$.",
|
||||
"placeholders": {
|
||||
"action": {
|
||||
"content": "$1",
|
||||
"example": "Lock"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customVaultTimeout": {
|
||||
"message": "Custom vault timeout"
|
||||
},
|
||||
|
@ -44,4 +44,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label for="action">{{ "vaultTimeoutAction" | i18n }}</label>
|
||||
<select class="form-control" formControlName="action">
|
||||
<option *ngFor="let o of vaultTimeoutActionOptions" [ngValue]="o.value">
|
||||
{{ o.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { UntypedFormBuilder } from "@angular/forms";
|
||||
import { FormBuilder, FormControl } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
|
||||
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
|
||||
import {
|
||||
BasePolicy,
|
||||
BasePolicyComponent,
|
||||
@ -21,25 +22,30 @@ export class MaximumVaultTimeoutPolicy extends BasePolicy {
|
||||
templateUrl: "maximum-vault-timeout.component.html",
|
||||
})
|
||||
export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
|
||||
vaultTimeoutActionOptions: { name: string; value: string }[];
|
||||
data = this.formBuilder.group({
|
||||
hours: [null],
|
||||
minutes: [null],
|
||||
hours: new FormControl<number>(null),
|
||||
minutes: new FormControl<number>(null),
|
||||
action: new FormControl<string>(null),
|
||||
});
|
||||
|
||||
constructor(private formBuilder: UntypedFormBuilder, private i18nService: I18nService) {
|
||||
constructor(private formBuilder: FormBuilder, private i18nService: I18nService) {
|
||||
super();
|
||||
this.vaultTimeoutActionOptions = [
|
||||
{ name: i18nService.t("userPreference"), value: null },
|
||||
{ name: i18nService.t(VaultTimeoutAction.Lock), value: VaultTimeoutAction.Lock },
|
||||
{ name: i18nService.t(VaultTimeoutAction.LogOut), value: VaultTimeoutAction.LogOut },
|
||||
];
|
||||
}
|
||||
|
||||
loadData() {
|
||||
const minutes = this.policyResponse.data?.minutes;
|
||||
|
||||
if (minutes == null) {
|
||||
return;
|
||||
}
|
||||
const action = this.policyResponse.data?.action;
|
||||
|
||||
this.data.patchValue({
|
||||
hours: Math.floor(minutes / 60),
|
||||
minutes: minutes % 60,
|
||||
hours: minutes ? Math.floor(minutes / 60) : null,
|
||||
minutes: minutes ? minutes % 60 : null,
|
||||
action: action,
|
||||
});
|
||||
}
|
||||
|
||||
@ -50,6 +56,7 @@ export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent {
|
||||
|
||||
return {
|
||||
minutes: this.data.value.hours * 60 + this.data.value.minutes,
|
||||
action: this.data.value.action,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Directive, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Directive, Input, OnChanges, OnDestroy, OnInit } from "@angular/core";
|
||||
import {
|
||||
AbstractControl,
|
||||
ControlValueAccessor,
|
||||
@ -6,7 +6,7 @@ import {
|
||||
ValidationErrors,
|
||||
Validator,
|
||||
} from "@angular/forms";
|
||||
import { combineLatestWith, Subject, takeUntil } from "rxjs";
|
||||
import { filter, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@ -15,7 +15,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
|
||||
@Directive()
|
||||
export class VaultTimeoutInputComponent
|
||||
implements ControlValueAccessor, Validator, OnInit, OnDestroy
|
||||
implements ControlValueAccessor, Validator, OnInit, OnDestroy, OnChanges
|
||||
{
|
||||
get showCustom() {
|
||||
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
|
||||
@ -38,7 +38,7 @@ export class VaultTimeoutInputComponent
|
||||
}),
|
||||
});
|
||||
|
||||
@Input() vaultTimeouts: { name: string; value: number }[];
|
||||
@Input() vaultTimeoutOptions: { name: string; value: number }[];
|
||||
vaultTimeoutPolicy: Policy;
|
||||
vaultTimeoutPolicyHours: number;
|
||||
vaultTimeoutPolicyMinutes: number;
|
||||
@ -55,38 +55,37 @@ export class VaultTimeoutInputComponent
|
||||
|
||||
async ngOnInit() {
|
||||
this.policyService
|
||||
.policyAppliesToActiveUser$(PolicyType.MaximumVaultTimeout)
|
||||
.pipe(combineLatestWith(this.policyService.policies$), takeUntil(this.destroy$))
|
||||
.subscribe(([policyAppliesToActiveUser, policies]) => {
|
||||
if (policyAppliesToActiveUser) {
|
||||
const vaultTimeoutPolicy = policies.find(
|
||||
(policy) => policy.type === PolicyType.MaximumVaultTimeout && policy.enabled
|
||||
);
|
||||
|
||||
this.vaultTimeoutPolicy = vaultTimeoutPolicy;
|
||||
this.applyVaultTimeoutPolicy();
|
||||
}
|
||||
.get$(PolicyType.MaximumVaultTimeout)
|
||||
.pipe(
|
||||
filter((policy) => policy != null),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((policy) => {
|
||||
this.vaultTimeoutPolicy = policy;
|
||||
this.applyVaultTimeoutPolicy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
this.form.valueChanges.subscribe(async (value) => {
|
||||
this.onChange(this.getVaultTimeout(value));
|
||||
this.form.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
if (this.onChange) {
|
||||
this.onChange(this.getVaultTimeout(value));
|
||||
}
|
||||
});
|
||||
|
||||
// Assign the previous value to the custom fields
|
||||
this.form.get("vaultTimeout").valueChanges.subscribe((value) => {
|
||||
if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const current = Math.max(this.form.value.vaultTimeout, 0);
|
||||
this.form.patchValue({
|
||||
custom: {
|
||||
hours: Math.floor(current / 60),
|
||||
minutes: current % 60,
|
||||
},
|
||||
this.form.controls.vaultTimeout.valueChanges
|
||||
.pipe(
|
||||
filter((value) => value !== VaultTimeoutInputComponent.CUSTOM_VALUE),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe((_) => {
|
||||
const current = Math.max(this.form.value.vaultTimeout, 0);
|
||||
this.form.patchValue({
|
||||
custom: {
|
||||
hours: Math.floor(current / 60),
|
||||
minutes: current % 60,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -95,10 +94,14 @@ export class VaultTimeoutInputComponent
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.vaultTimeouts.push({
|
||||
name: this.i18nService.t("custom"),
|
||||
value: VaultTimeoutInputComponent.CUSTOM_VALUE,
|
||||
});
|
||||
if (
|
||||
!this.vaultTimeoutOptions.find((p) => p.value === VaultTimeoutInputComponent.CUSTOM_VALUE)
|
||||
) {
|
||||
this.vaultTimeoutOptions.push({
|
||||
name: this.i18nService.t("custom"),
|
||||
value: VaultTimeoutInputComponent.CUSTOM_VALUE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getVaultTimeout(value: any) {
|
||||
@ -114,7 +117,7 @@ export class VaultTimeoutInputComponent
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.vaultTimeouts.every((p) => p.value !== value)) {
|
||||
if (this.vaultTimeoutOptions.every((p) => p.value !== value)) {
|
||||
this.form.setValue({
|
||||
vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE,
|
||||
custom: {
|
||||
@ -166,7 +169,7 @@ export class VaultTimeoutInputComponent
|
||||
this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60);
|
||||
this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60;
|
||||
|
||||
this.vaultTimeouts = this.vaultTimeouts.filter(
|
||||
this.vaultTimeoutOptions = this.vaultTimeoutOptions.filter(
|
||||
(t) =>
|
||||
t.value <= this.vaultTimeoutPolicy.data.minutes &&
|
||||
(t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) &&
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
|
||||
export abstract class VaultTimeoutSettingsService {
|
||||
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
|
||||
setVaultTimeoutOptions: (
|
||||
vaultTimeout: number,
|
||||
vaultTimeoutAction: VaultTimeoutAction
|
||||
) => Promise<void>;
|
||||
getVaultTimeout: (userId?: string) => Promise<number>;
|
||||
getVaultTimeoutAction: (userId?: string) => Promise<VaultTimeoutAction>;
|
||||
isPinLockSet: () => Promise<[boolean, boolean]>;
|
||||
isBiometricLockSet: () => Promise<boolean>;
|
||||
clear: (userId?: string) => Promise<void>;
|
||||
|
@ -10,6 +10,7 @@ import { PolicyResponse } from "../../models/response/policy.response";
|
||||
|
||||
export abstract class PolicyService {
|
||||
policies$: Observable<Policy[]>;
|
||||
get$: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) => Observable<Policy>;
|
||||
masterPasswordPolicyOptions$: (policies?: Policy[]) => Observable<MasterPasswordPolicyOptions>;
|
||||
policyAppliesToActiveUser$: (
|
||||
policyType: PolicyType,
|
||||
|
@ -42,6 +42,28 @@ export class PolicyService implements InternalPolicyServiceAbstraction {
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first policy found that applies to the active user
|
||||
* @param policyType Policy type to search for
|
||||
* @param policyFilter Additional filter to apply to the policy
|
||||
*/
|
||||
get$(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean): Observable<Policy> {
|
||||
return this.policies$.pipe(
|
||||
concatMap(async (policies) => {
|
||||
const userId = await this.stateService.getUserId();
|
||||
const appliesToCurrentUser = await this.checkPoliciesThatApplyToUser(
|
||||
policies,
|
||||
policyType,
|
||||
policyFilter,
|
||||
userId
|
||||
);
|
||||
if (appliesToCurrentUser) {
|
||||
return policies.find((policy) => policy.type === policyType && policy.enabled);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Do not call this, use the policies$ observable collection
|
||||
*/
|
||||
|
4
libs/common/src/enums/vault-timeout-action.enum.ts
Normal file
4
libs/common/src/enums/vault-timeout-action.enum.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum VaultTimeoutAction {
|
||||
Lock = "lock",
|
||||
LogOut = "logOut",
|
||||
}
|
@ -18,6 +18,7 @@ import { CollectionView } from "../admin-console/models/view/collection.view";
|
||||
import { EnvironmentUrls } from "../auth/models/domain/environment-urls";
|
||||
import { KdfConfig } from "../auth/models/domain/kdf-config";
|
||||
import { HtmlStorageLocation, KdfType, StorageLocation, ThemeType, UriMatchType } from "../enums";
|
||||
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
||||
import { StateFactory } from "../factories/stateFactory";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { EventData } from "../models/data/event.data";
|
||||
@ -2571,7 +2572,10 @@ export class StateService<
|
||||
await this.storageService.remove(keys.tempAccountSettings);
|
||||
}
|
||||
account.settings.environmentUrls = environmentUrls;
|
||||
if (account.settings.vaultTimeoutAction === "logOut" && account.settings.vaultTimeout != null) {
|
||||
if (
|
||||
account.settings.vaultTimeoutAction === VaultTimeoutAction.LogOut &&
|
||||
account.settings.vaultTimeout != null
|
||||
) {
|
||||
account.tokens.accessToken = null;
|
||||
account.tokens.refreshToken = null;
|
||||
account.profile.apiKeyClientId = null;
|
||||
@ -2831,7 +2835,7 @@ export class StateService<
|
||||
const timeoutAction = await this.getVaultTimeoutAction({ userId: options?.userId });
|
||||
const timeout = await this.getVaultTimeout({ userId: options?.userId });
|
||||
const defaultOptions =
|
||||
timeoutAction === "logOut" && timeout != null
|
||||
timeoutAction === VaultTimeoutAction.LogOut && timeout != null
|
||||
? await this.defaultInMemoryOptions()
|
||||
: await this.defaultOnDiskOptions();
|
||||
return this.reconcileOptions(options, defaultOptions);
|
||||
|
@ -11,6 +11,7 @@ import { CollectionService } from "../../admin-console/abstractions/collection.s
|
||||
import { AuthService } from "../../auth/abstractions/auth.service";
|
||||
import { KeyConnectorService } from "../../auth/abstractions/key-connector.service";
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
||||
|
||||
@ -132,7 +133,9 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||
}
|
||||
|
||||
private async executeTimeoutAction(userId: string): Promise<void> {
|
||||
const timeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId });
|
||||
timeoutAction === "logOut" ? await this.logOut(userId) : await this.lock(userId);
|
||||
const timeoutAction = await this.vaultTimeoutSettingsService.getVaultTimeoutAction(userId);
|
||||
timeoutAction === VaultTimeoutAction.LogOut
|
||||
? await this.logOut(userId)
|
||||
: await this.lock(userId);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction }
|
||||
import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "../../admin-console/enums";
|
||||
import { TokenService } from "../../auth/abstractions/token.service";
|
||||
import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum";
|
||||
|
||||
export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceAbstraction {
|
||||
constructor(
|
||||
@ -13,7 +14,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
private stateService: StateService
|
||||
) {}
|
||||
|
||||
async setVaultTimeoutOptions(timeout: number, action: string): Promise<void> {
|
||||
async setVaultTimeoutOptions(timeout: number, action: VaultTimeoutAction): Promise<void> {
|
||||
await this.stateService.setVaultTimeout(timeout);
|
||||
|
||||
// We swap these tokens from being on disk for lock actions, and in memory for logout actions
|
||||
@ -24,7 +25,11 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
const clientSecret = await this.tokenService.getClientSecret();
|
||||
|
||||
const currentAction = await this.stateService.getVaultTimeoutAction();
|
||||
if ((timeout != null || timeout === 0) && action === "logOut" && action !== currentAction) {
|
||||
if (
|
||||
(timeout != null || timeout === 0) &&
|
||||
action === VaultTimeoutAction.LogOut &&
|
||||
action !== currentAction
|
||||
) {
|
||||
// if we have a vault timeout and the action is log out, reset tokens
|
||||
await this.tokenService.clearToken();
|
||||
}
|
||||
@ -74,6 +79,29 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
return vaultTimeout;
|
||||
}
|
||||
|
||||
async getVaultTimeoutAction(userId?: string): Promise<VaultTimeoutAction> {
|
||||
let vaultTimeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId });
|
||||
|
||||
if (
|
||||
await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId)
|
||||
) {
|
||||
const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId);
|
||||
const action = policy[0].data.action;
|
||||
|
||||
if (action) {
|
||||
// We really shouldn't need to set the value here, but multiple services relies on this value being correct.
|
||||
if (action && vaultTimeoutAction !== action) {
|
||||
await this.stateService.setVaultTimeoutAction(action, { userId: userId });
|
||||
}
|
||||
vaultTimeoutAction = action;
|
||||
}
|
||||
}
|
||||
|
||||
return vaultTimeoutAction === VaultTimeoutAction.LogOut
|
||||
? VaultTimeoutAction.LogOut
|
||||
: VaultTimeoutAction.Lock;
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<void> {
|
||||
await this.stateService.setEverBeenUnlocked(false, { userId: userId });
|
||||
await this.stateService.setDecryptedPinProtected(null, { userId: userId });
|
||||
|
Loading…
Reference in New Issue
Block a user