-
-
-
-
- {{
- "vaultTimeoutPolicyWithActionInEffect"
- | i18n: policy.timeout.hours : policy.timeout.minutes : (policy.action | i18n)
- }}
-
-
- {{ "vaultTimeoutPolicyInEffect" | i18n: policy.timeout.hours : policy.timeout.minutes }}
-
-
- {{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
{{ "unlockWithPin" | i18n }}
+
+
+
+ {{
+ "lockWithMasterPassOnRestart1" | i18n
+ }}
+
+
+
+
+
+ {{ "vaultTimeoutHeader" | i18n }}
+
+
+
+
+
+
+ {{ "vaultTimeoutAction1" | i18n }}
+
+
+ {{ "vaultTimeoutActionDesc" | i18n }}
+
+
+ {{ "unlockMethodNeededToChangeTimeoutActionDesc" | i18n }}
+
+
+
+ {{ "vaultTimeoutPolicyAffectingOptions" | i18n }}
+
+
+
+
+
+ {{ "otherOptions" | i18n }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ "lockNow" | i18n }}
-
-
-
+
+
{{ "logOut" | i18n }}
-
-
-
+
+
-
+
diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts
index 25401f06f38..8e0acc7d641 100644
--- a/apps/browser/src/auth/popup/settings/account-security.component.ts
+++ b/apps/browser/src/auth/popup/settings/account-security.component.ts
@@ -1,15 +1,15 @@
import { DialogRef } from "@angular/cdk/dialog";
+import { CommonModule } from "@angular/common";
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from "@angular/core";
-import { FormBuilder } from "@angular/forms";
+import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { RouterModule } from "@angular/router";
import {
BehaviorSubject,
combineLatest,
concatMap,
distinctUntilChanged,
- filter,
firstValueFrom,
map,
- Observable,
pairwise,
startWith,
Subject,
@@ -17,7 +17,8 @@ import {
takeUntil,
} from "rxjs";
-import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
@@ -39,30 +40,67 @@ import {
VaultTimeoutOption,
VaultTimeoutStringType,
} from "@bitwarden/common/types/vault-timeout.type";
-import { DialogService, ToastService } from "@bitwarden/components";
+import {
+ CardComponent,
+ CheckboxModule,
+ DialogService,
+ FormFieldModule,
+ IconButtonModule,
+ ItemModule,
+ LinkModule,
+ SectionComponent,
+ SectionHeaderComponent,
+ SelectModule,
+ TypographyModule,
+ ToastService,
+} from "@bitwarden/components";
import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors";
import { BrowserApi } from "../../../platform/browser/browser-api";
import { enableAccountSwitching } from "../../../platform/flags";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
+import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
+import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component";
+import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
+import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
import { SetPinComponent } from "../components/set-pin.component";
import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component";
@Component({
- selector: "auth-account-security",
templateUrl: "account-security.component.html",
+ standalone: true,
+ imports: [
+ CardComponent,
+ CheckboxModule,
+ CommonModule,
+ FormFieldModule,
+ FormsModule,
+ ReactiveFormsModule,
+ IconButtonModule,
+ ItemModule,
+ JslibModule,
+ LinkModule,
+ PopOutComponent,
+ PopupFooterComponent,
+ PopupHeaderComponent,
+ PopupPageComponent,
+ RouterModule,
+ SectionComponent,
+ SectionHeaderComponent,
+ SelectModule,
+ TypographyModule,
+ VaultTimeoutInputComponent,
+ ],
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class AccountSecurityComponent implements OnInit, OnDestroy {
protected readonly VaultTimeoutAction = VaultTimeoutAction;
+ showMasterPasswordOnClientRestartOption = true;
availableVaultTimeoutActions: VaultTimeoutAction[] = [];
- vaultTimeoutOptions: VaultTimeoutOption[];
- vaultTimeoutPolicyCallout: Observable<{
- timeout: { hours: number; minutes: number };
- action: VaultTimeoutAction;
- }>;
+ vaultTimeoutOptions: VaultTimeoutOption[] = [];
+ hasVaultTimeoutPolicy = false;
supportsBiometric: boolean;
showChangeMasterPass = true;
accountSwitcherEnabled = false;
@@ -71,6 +109,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
vaultTimeout: [null as VaultTimeout | null],
vaultTimeoutAction: [VaultTimeoutAction.Lock],
pin: [null as boolean | null],
+ pinLockWithMasterPassword: false,
biometric: false,
enableAutoBiometricsPrompt: true,
});
@@ -102,20 +141,12 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
}
async ngOnInit() {
+ const hasMasterPassword = await this.userVerificationService.hasMasterPassword();
+ this.showMasterPasswordOnClientRestartOption = hasMasterPassword;
const maximumVaultTimeoutPolicy = this.policyService.get$(PolicyType.MaximumVaultTimeout);
- this.vaultTimeoutPolicyCallout = maximumVaultTimeoutPolicy.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 };
- }),
- );
+ if ((await firstValueFrom(this.policyService.get$(PolicyType.MaximumVaultTimeout))) != null) {
+ this.hasVaultTimeoutPolicy = true;
+ }
const showOnLocked =
!this.platformUtilsService.isFirefox() && !this.platformUtilsService.isSafari();
@@ -161,6 +192,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
this.vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$(activeAccount.id),
),
pin: await this.pinService.isPinSet(activeAccount.id),
+ pinLockWithMasterPassword:
+ (await this.pinService.getPinLockType(activeAccount.id)) == "EPHEMERAL",
biometric: await this.vaultTimeoutSettingsService.isBiometricLockSet(),
enableAutoBiometricsPrompt: await firstValueFrom(
this.biometricStateService.promptAutomatically$,
@@ -185,9 +218,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
this.form.controls.vaultTimeoutAction.valueChanges
.pipe(
startWith(initialValues.vaultTimeoutAction), // emit to init pairwise
- pairwise(),
- concatMap(async ([previousValue, newValue]) => {
- await this.saveVaultTimeoutAction(previousValue, newValue);
+ map(async (value) => {
+ await this.saveVaultTimeoutAction(value);
}),
takeUntil(this.destroy$),
)
@@ -203,6 +235,22 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
)
.subscribe();
+ this.form.controls.pinLockWithMasterPassword.valueChanges
+ .pipe(
+ concatMap(async (value) => {
+ const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
+ const pinKeyEncryptedUserKey =
+ (await this.pinService.getPinKeyEncryptedUserKeyPersistent(userId)) ||
+ (await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId));
+ await this.pinService.clearPinKeyEncryptedUserKeyPersistent(userId);
+ await this.pinService.clearPinKeyEncryptedUserKeyEphemeral(userId);
+ await this.pinService.storePinKeyEncryptedUserKey(pinKeyEncryptedUserKey, value, userId);
+ this.refreshTimeoutSettings$.next();
+ }),
+ takeUntil(this.destroy$),
+ )
+ .subscribe();
+
this.form.controls.biometric.valueChanges
.pipe(
distinctUntilChanged(),
@@ -219,6 +267,15 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
)
.subscribe();
+ this.form.controls.enableAutoBiometricsPrompt.valueChanges
+ .pipe(
+ concatMap(async (enabled) => {
+ await this.biometricStateService.setPromptAutomatically(enabled);
+ }),
+ takeUntil(this.destroy$),
+ )
+ .subscribe();
+
this.refreshTimeoutSettings$
.pipe(
switchMap(() =>
@@ -272,17 +329,6 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
}
}
- // The minTimeoutError does not apply to browser because it supports Immediately
- // So only check for the policyError
- if (this.form.controls.vaultTimeout.hasError("policyError")) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("vaultTimeoutTooLarge"),
- });
- return;
- }
-
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
const vaultTimeoutAction = await firstValueFrom(
@@ -299,8 +345,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
}
}
- async saveVaultTimeoutAction(previousValue: VaultTimeoutAction, newValue: VaultTimeoutAction) {
- if (newValue === VaultTimeoutAction.LogOut) {
+ async saveVaultTimeoutAction(value: VaultTimeoutAction) {
+ if (value === VaultTimeoutAction.LogOut) {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "vaultTimeoutLogOutConfirmationTitle" },
content: { key: "vaultTimeoutLogOutConfirmation" },
@@ -308,7 +354,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
});
if (!confirmed) {
- this.form.controls.vaultTimeoutAction.setValue(previousValue, {
+ this.form.controls.vaultTimeoutAction.setValue(VaultTimeoutAction.Lock, {
emitEvent: false,
});
return;
@@ -329,7 +375,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
await this.vaultTimeoutSettingsService.setVaultTimeoutOptions(
activeAccount.id,
this.form.value.vaultTimeout,
- newValue,
+ value,
);
this.refreshTimeoutSettings$.next();
}
@@ -343,8 +389,13 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
return;
}
+ const userId = await firstValueFrom(
+ this.accountService.activeAccount$.pipe(map((account) => account.id)),
+ );
const userHasPinSet = await firstValueFrom(dialogRef.closed);
this.form.controls.pin.setValue(userHasPinSet, { emitEvent: false });
+ const requireReprompt = (await this.pinService.getPinLockType(userId)) == "EPHEMERAL";
+ this.form.controls.pinLockWithMasterPassword.setValue(requireReprompt, { emitEvent: false });
} else {
await this.vaultTimeoutSettingsService.clear();
}
@@ -386,77 +437,91 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
return;
}
- let awaitDesktopDialogRef: DialogRef
| undefined;
- let biometricsResponseReceived = false;
-
await this.cryptoService.refreshAdditionalKeys();
- const waitForUserDialogPromise = async () => {
- // only show waiting dialog if we have waited for 200 msec to prevent double dialog
- // the os will respond instantly if the dialog shows successfully, and the desktop app will respond instantly if something is wrong
- await new Promise((resolve) => setTimeout(resolve, 200));
- if (biometricsResponseReceived) {
+ const successful = await this.trySetupBiometrics();
+ this.form.controls.biometric.setValue(successful);
+ if (!successful) {
+ await this.biometricStateService.setBiometricUnlockEnabled(false);
+ await this.biometricStateService.setFingerprintValidated(false);
+ }
+ }
+ }
+
+ async trySetupBiometrics(): Promise {
+ let awaitDesktopDialogRef: DialogRef | undefined;
+ let biometricsResponseReceived = false;
+ let setupResult = false;
+
+ const waitForUserDialogPromise = async () => {
+ // only show waiting dialog if we have waited for 500 msec to prevent double dialog
+ // the os will respond instantly if the dialog shows successfully, and the desktop app will respond instantly if something is wrong
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ if (biometricsResponseReceived) {
+ return;
+ }
+
+ awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService);
+ await firstValueFrom(awaitDesktopDialogRef.closed);
+ if (!biometricsResponseReceived) {
+ setupResult = false;
+ }
+ return;
+ };
+
+ const biometricsPromise = async () => {
+ try {
+ const result = await this.biometricsService.authenticateBiometric();
+
+ // prevent duplicate dialog
+ biometricsResponseReceived = true;
+ if (awaitDesktopDialogRef) {
+ awaitDesktopDialogRef.close(result);
+ }
+
+ if (!result) {
+ this.platformUtilsService.showToast(
+ "error",
+ this.i18nService.t("errorEnableBiometricTitle"),
+ this.i18nService.t("errorEnableBiometricDesc"),
+ );
+ }
+ setupResult = true;
+ } catch (e) {
+ // prevent duplicate dialog
+ biometricsResponseReceived = true;
+ if (awaitDesktopDialogRef) {
+ awaitDesktopDialogRef.close(true);
+ }
+
+ if (e.message == "canceled") {
+ setupResult = false;
return;
}
- awaitDesktopDialogRef = AwaitDesktopDialogComponent.open(this.dialogService);
- const result = await firstValueFrom(awaitDesktopDialogRef.closed);
- if (result !== true) {
- this.form.controls.biometric.setValue(false);
+ const error = BiometricErrors[e.message as BiometricErrorTypes];
+ const shouldRetry = await this.dialogService.openSimpleDialog({
+ title: { key: error.title },
+ content: { key: error.description },
+ acceptButtonText: { key: "retry" },
+ cancelButtonText: null,
+ type: "danger",
+ });
+ if (shouldRetry) {
+ setupResult = await this.trySetupBiometrics();
+ } else {
+ setupResult = false;
+ return;
}
- };
-
- const biometricsPromise = async () => {
- try {
- const result = await this.biometricsService.authenticateBiometric();
-
- // prevent duplicate dialog
- biometricsResponseReceived = true;
- if (awaitDesktopDialogRef) {
- awaitDesktopDialogRef.close(true);
- }
-
- this.form.controls.biometric.setValue(result);
- if (!result) {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorEnableBiometricTitle"),
- message: this.i18nService.t("errorEnableBiometricDesc"),
- });
- }
- } catch (e) {
- // prevent duplicate dialog
- biometricsResponseReceived = true;
- if (awaitDesktopDialogRef) {
- awaitDesktopDialogRef.close(true);
- }
-
- this.form.controls.biometric.setValue(false);
-
- if (e.message == "canceled") {
- return;
- }
-
- const error = BiometricErrors[e.message as BiometricErrorTypes];
- await this.dialogService.openSimpleDialog({
- title: { key: error.title },
- content: { key: error.description },
- acceptButtonText: { key: "ok" },
- cancelButtonText: null,
- type: "danger",
- });
- } finally {
- if (awaitDesktopDialogRef) {
- awaitDesktopDialogRef.close(true);
- }
+ } finally {
+ if (awaitDesktopDialogRef) {
+ awaitDesktopDialogRef.close(true);
}
- };
+ }
+ };
- await Promise.race([waitForUserDialogPromise(), biometricsPromise()]);
- } else {
- await this.biometricStateService.setBiometricUnlockEnabled(false);
- await this.biometricStateService.setFingerprintValidated(false);
- }
+ await Promise.all([waitForUserDialogPromise(), biometricsPromise()]);
+ return setupResult;
}
async updateAutoBiometricsPrompt() {
@@ -471,6 +536,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
content: { key: "changeMasterPasswordOnWebConfirmation" },
type: "info",
acceptButtonText: { key: "continue" },
+ cancelButtonText: { key: "cancel" },
});
if (confirmed) {
const env = await firstValueFrom(this.environmentService.environment$);
@@ -480,9 +546,11 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
async twoStep() {
const confirmed = await this.dialogService.openSimpleDialog({
- title: { key: "twoStepLogin" },
- content: { key: "twoStepLoginConfirmation" },
+ title: { key: "twoStepLoginConfirmationTitle" },
+ content: { key: "twoStepLoginConfirmationContent" },
type: "info",
+ acceptButtonText: { key: "continue" },
+ cancelButtonText: { key: "cancel" },
});
if (confirmed) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts
index 0f6a9d9248d..14f35a78fb3 100644
--- a/apps/browser/src/popup/app-routing.module.ts
+++ b/apps/browser/src/popup/app-routing.module.ts
@@ -41,6 +41,7 @@ import { LoginComponent } from "../auth/popup/login.component";
import { RegisterComponent } from "../auth/popup/register.component";
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
+import { AccountSecurityComponent as AccountSecurityV1Component } from "../auth/popup/settings/account-security-v1.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { SsoComponent } from "../auth/popup/sso.component";
import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component";
@@ -296,12 +297,11 @@ const routes: Routes = [
canActivate: [authGuard],
data: { state: "autofill" },
}),
- {
+ ...extensionRefreshSwap(AccountSecurityV1Component, AccountSecurityComponent, {
path: "account-security",
- component: AccountSecurityComponent,
canActivate: [authGuard],
data: { state: "account-security" },
- },
+ }),
...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, {
path: "notifications",
canActivate: [authGuard],
diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts
index f14dafacb70..d5777215b19 100644
--- a/apps/browser/src/popup/app.module.ts
+++ b/apps/browser/src/popup/app.module.ts
@@ -15,7 +15,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe";
import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe";
import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
-import { AvatarModule, ButtonModule, ToastModule } from "@bitwarden/components";
+import { AvatarModule, ButtonModule, FormFieldModule, ToastModule } from "@bitwarden/components";
import { AccountComponent } from "../auth/popup/account-switching/account.component";
import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component";
@@ -30,6 +30,7 @@ import { LoginComponent } from "../auth/popup/login.component";
import { RegisterComponent } from "../auth/popup/register.component";
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
+import { AccountSecurityComponent as AccountSecurityComponentV1 } from "../auth/popup/settings/account-security-v1.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
import { SsoComponent } from "../auth/popup/sso.component";
@@ -98,6 +99,7 @@ import "../platform/popup/locales";
A11yModule,
AppRoutingModule,
AutofillComponent,
+ AccountSecurityComponent,
ToastModule.forRoot({
maxOpened: 2,
autoDismiss: true,
@@ -132,6 +134,7 @@ import "../platform/popup/locales";
HeaderComponent,
UserVerificationDialogComponent,
CurrentAccountComponent,
+ FormFieldModule,
ExtensionAnonLayoutWrapperComponent,
],
declarations: [
@@ -171,7 +174,6 @@ import "../platform/popup/locales";
SendListComponent,
SendTypeComponent,
SetPasswordComponent,
- AccountSecurityComponent,
SettingsComponent,
VaultSettingsComponent,
ShareComponent,
@@ -183,6 +185,7 @@ import "../platform/popup/locales";
TwoFactorOptionsComponent,
UpdateTempPasswordComponent,
UserVerificationComponent,
+ AccountSecurityComponentV1,
VaultTimeoutInputComponent,
ViewComponent,
ViewCustomFieldsComponent,
diff --git a/apps/desktop/src/auth/components/set-pin.component.html b/apps/desktop/src/auth/components/set-pin.component.html
index 50e7aca75f3..cadd5340bb2 100644
--- a/apps/desktop/src/auth/components/set-pin.component.html
+++ b/apps/desktop/src/auth/components/set-pin.component.html
@@ -22,7 +22,7 @@
bitCheckbox
formControlName="requireMasterPasswordOnClientRestart"
/>
- {{ "lockWithMasterPassOnRestart" | i18n }}
+ {{ "lockWithMasterPassOnRestart1" | i18n }}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 721faa25675..bd9ad5075b4 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -942,6 +942,9 @@
"vaultTimeout": {
"message": "Vault timeout"
},
+ "vaultTimeout1": {
+ "message": "Timeout"
+ },
"vaultTimeoutDesc": {
"message": "Choose when your vault will take the vault timeout action."
},
@@ -1567,7 +1570,7 @@
"recommendedForSecurity": {
"message": "Recommended for security."
},
- "lockWithMasterPassOnRestart": {
+ "lockWithMasterPassOnRestart1": {
"message": "Lock with master password on restart"
},
"deleteAccount": {
@@ -2099,8 +2102,8 @@
"minutes": {
"message": "Minutes"
},
- "vaultTimeoutPolicyInEffect": {
- "message": "Your organization policies have set your maximum allowed vault timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
+ "vaultTimeoutPolicyInEffect1": {
+ "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.",
"placeholders": {
"hours": {
"content": "$1",
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 340acc8efb5..7b518e4899d 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -1024,8 +1024,8 @@
"unexpectedError": {
"message": "An unexpected error has occurred."
},
- "expirationDateError" : {
- "message":"Please select an expiration date that is in the future."
+ "expirationDateError": {
+ "message": "Please select an expiration date that is in the future."
},
"emailAddress": {
"message": "Email address"
@@ -1033,8 +1033,8 @@
"yourVaultIsLockedV2": {
"message": "Your vault is locked"
},
- "uuid":{
- "message" : "UUID"
+ "uuid": {
+ "message": "UUID"
},
"unlock": {
"message": "Unlock"
@@ -1270,10 +1270,10 @@
"copyUuid": {
"message": "Copy UUID"
},
- "errorRefreshingAccessToken":{
+ "errorRefreshingAccessToken": {
"message": "Access Token Refresh Error"
},
- "errorRefreshingAccessTokenDesc":{
+ "errorRefreshingAccessTokenDesc": {
"message": "No refresh token or API keys found. Please try logging out and logging back in."
},
"warning": {
@@ -3993,6 +3993,9 @@
"vaultTimeout": {
"message": "Vault timeout"
},
+ "vaultTimeout1": {
+ "message": "Timeout"
+ },
"vaultTimeoutDesc": {
"message": "Choose when your vault will take the vault timeout action."
},
@@ -4997,7 +5000,7 @@
"youNeedApprovalFromYourAdminToTrySecretsManager": {
"message": "You need approval from your administrator to try Secrets Manager."
},
- "smAccessRequestEmailSent" : {
+ "smAccessRequestEmailSent": {
"message": "Access request for secrets manager email sent to admins."
},
"requestAccessSMDefaultEmailContent": {
@@ -5006,8 +5009,8 @@
"giveMembersAccess": {
"message": "Give members access:"
},
- "viewAndSelectTheMembers" : {
- "message" :"view and select the members you want to give access to Secrets Manager."
+ "viewAndSelectTheMembers": {
+ "message": "view and select the members you want to give access to Secrets Manager."
},
"openYourOrganizations": {
"message": "Open your organization's"
@@ -5471,6 +5474,19 @@
}
}
},
+ "vaultTimeoutPolicyInEffect1": {
+ "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.",
+ "placeholders": {
+ "hours": {
+ "content": "$1",
+ "example": "5"
+ },
+ "minutes": {
+ "content": "$2",
+ "example": "5"
+ }
+ }
+ },
"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": {
@@ -5497,9 +5513,6 @@
}
}
},
- "customVaultTimeout": {
- "message": "Custom vault timeout"
- },
"vaultTimeoutToLarge": {
"message": "Your vault timeout exceeds the restriction set by your organization."
},
@@ -5944,10 +5957,10 @@
"selfHostedBaseUrlHint": {
"message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com"
},
- "selfHostedCustomEnvHeader" :{
+ "selfHostedCustomEnvHeader": {
"message": "For advanced configuration, you can specify the base URL of each service independently."
},
- "selfHostedEnvFormInvalid" :{
+ "selfHostedEnvFormInvalid": {
"message": "You must add either the base Server URL or at least one custom environment."
},
"apiUrl": {
@@ -7709,7 +7722,7 @@
}
}
},
- "verificationRequired" : {
+ "verificationRequired": {
"message": "Verification required",
"description": "Default title for the user verification dialog."
},
@@ -8501,7 +8514,7 @@
"deleteProviderRecoverConfirmDesc": {
"message": "You have requested to delete this Provider. Use the button below to confirm."
},
- "deleteProviderWarning": {
+ "deleteProviderWarning": {
"message": "Deleting your provider is permanent. It cannot be undone."
},
"errorAssigningTargetCollection": {
@@ -8514,7 +8527,7 @@
"message": "Integrations & SDKs",
"description": "The title for the section that deals with integrations and SDKs."
},
- "integrations":{
+ "integrations": {
"message": "Integrations"
},
"integrationsDesc": {
@@ -8585,7 +8598,7 @@
},
"createdNewClient": {
"message": "Successfully created new client"
- },
+ },
"noAccess": {
"message": "No access"
},
@@ -8821,11 +8834,11 @@
"placeholders": {
"value": {
"content": "$1",
- "example":"increments of 100,000"
+ "example": "increments of 100,000"
}
}
},
- "providerReinstate":{
+ "providerReinstate": {
"message": " Contact Customer Support to reinstate your subscription."
},
"secretPeopleDescription": {
diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html
index 9a534d461c4..d6005c970f7 100644
--- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html
+++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html
@@ -1,8 +1,10 @@
- {{ "yourAccountsFingerprint" | i18n }}:
+ {{ "yourAccountsFingerprint" | i18n }}:
- {{ data.fingerprint.join("-") }}
+ {{ data.fingerprint.join("-") }}
-
- {{ "vaultTimeout" | i18n }}
+
diff --git a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts
index 7c760835603..d42e7d7d15b 100644
--- a/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts
+++ b/libs/auth/src/angular/vault-timeout-input/vault-timeout-input.component.ts
@@ -55,16 +55,41 @@ type VaultTimeoutFormValue = VaultTimeoutForm["value"];
export class VaultTimeoutInputComponent
implements ControlValueAccessor, Validator, OnInit, OnDestroy, OnChanges
{
+ protected readonly VaultTimeoutAction = VaultTimeoutAction;
+
get showCustom() {
return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE;
}
- get exceedsMinimumTimout(): boolean {
+ get exceedsMinimumTimeout(): boolean {
return (
!this.showCustom || this.customTimeInMinutes() > VaultTimeoutInputComponent.MIN_CUSTOM_MINUTES
);
}
+ get exceedsMaximumTimeout(): boolean {
+ return (
+ this.showCustom &&
+ this.customTimeInMinutes() >
+ this.vaultTimeoutPolicyMinutes + 60 * this.vaultTimeoutPolicyHours
+ );
+ }
+
+ get filteredVaultTimeoutOptions(): VaultTimeoutOption[] {
+ // by policy max value
+ if (this.vaultTimeoutPolicy == null || this.vaultTimeoutPolicy.data == null) {
+ return this.vaultTimeoutOptions;
+ }
+
+ return this.vaultTimeoutOptions.filter((option) => {
+ if (typeof option.value === "number") {
+ return option.value <= this.vaultTimeoutPolicy.data.minutes;
+ }
+
+ return false;
+ });
+ }
+
static CUSTOM_VALUE = -100;
static MIN_CUSTOM_MINUTES = 0;
@@ -77,6 +102,7 @@ export class VaultTimeoutInputComponent
});
@Input() vaultTimeoutOptions: VaultTimeoutOption[];
+
vaultTimeoutPolicy: Policy;
vaultTimeoutPolicyHours: number;
vaultTimeoutPolicyMinutes: number;
@@ -207,7 +233,7 @@ export class VaultTimeoutInputComponent
return { policyError: true };
}
- if (!this.exceedsMinimumTimout) {
+ if (!this.exceedsMinimumTimeout) {
return { minTimeoutError: true };
}