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