1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-05 18:13:26 +00:00

[PM-26056] Consolidated session timeout component (#16988)

* consolidated session timeout settings component

* rename preferences to appearance

* race condition bug on computed signal

* outdated header for browser

* unnecessary padding

* remove required on action, fix build

* rename localization key

* missing user id

* required

* cleanup task

* eslint fix signals rollback

* takeUntilDestroyed, null checks

* move browser specific logic outside shared component

* explicit input type

* input name

* takeUntilDestroyed, no toast

* unit tests

* cleanup

* cleanup, correct link to deprecation jira

* tech debt todo with jira

* missing web localization key when policy is on

* relative import

* extracting timeout options to component service

* duplicate localization key

* fix failing test

* subsequent timeout action selecting opening without dialog on first dialog cancellation

* default locale can be null

* unit tests failing

* rename, simplifications

* one if else feature flag

* timeout input component rendering before async pipe completion
This commit is contained in:
Maciej Zieniuk
2025-11-11 15:15:36 +01:00
committed by GitHub
parent a66227638e
commit 021d3e53aa
38 changed files with 1660 additions and 80 deletions

View File

@@ -31,36 +31,50 @@
</h2>
<ng-container *ngIf="showSecurity">
<bit-section disableMargin>
<bit-section-header>
<h2 bitTypography="h6">{{ "vaultTimeoutHeader" | i18n }}</h2>
</bit-section-header>
@if (consolidatedSessionTimeoutComponent$ | async) {
<bit-section-header>
<h2 bitTypography="h6">{{ "sessionTimeoutHeader" | i18n }}</h2>
</bit-section-header>
<auth-vault-timeout-input
[vaultTimeoutOptions]="vaultTimeoutOptions"
[formControl]="form.controls.vaultTimeout"
ngDefaultControl
>
</auth-vault-timeout-input>
<bit-session-timeout-settings
[refreshTimeoutActionSettings]="refreshTimeoutSettings$"
/>
} @else {
<bit-section-header>
<h2 bitTypography="h6">{{ "vaultTimeoutHeader" | i18n }}</h2>
</bit-section-header>
<bit-form-field disableMargin>
<bit-label for="vaultTimeoutAction">{{ "vaultTimeoutAction1" | i18n }}</bit-label>
<bit-select id="vaultTimeoutAction" formControlName="vaultTimeoutAction">
<bit-option
*ngFor="let action of availableVaultTimeoutActions"
[value]="action"
[label]="action | i18n"
<auth-vault-timeout-input
[vaultTimeoutOptions]="vaultTimeoutOptions"
[formControl]="form.controls.vaultTimeout"
ngDefaultControl
>
</auth-vault-timeout-input>
<bit-form-field disableMargin>
<bit-label for="vaultTimeoutAction">{{
"vaultTimeoutAction1" | i18n
}}</bit-label>
<bit-select id="vaultTimeoutAction" formControlName="vaultTimeoutAction">
<bit-option
*ngFor="let action of availableVaultTimeoutActions"
[value]="action"
[label]="action | i18n"
>
</bit-option>
</bit-select>
<bit-hint
*ngIf="!availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)"
>
</bit-option>
</bit-select>
{{ "unlockMethodNeededToChangeTimeoutActionDesc" | i18n }}<br />
</bit-hint>
</bit-form-field>
<bit-hint *ngIf="!availableVaultTimeoutActions.includes(VaultTimeoutAction.Lock)">
{{ "unlockMethodNeededToChangeTimeoutActionDesc" | i18n }}<br />
<bit-hint *ngIf="hasVaultTimeoutPolicy" class="tw-mt-4">
{{ "vaultTimeoutPolicyAffectingOptions" | i18n }}
</bit-hint>
</bit-form-field>
<bit-hint *ngIf="hasVaultTimeoutPolicy" class="tw-mt-4">
{{ "vaultTimeoutPolicyAffectingOptions" | i18n }}
</bit-hint>
}
</bit-section>
<div class="form-group tw-mt-4" *ngIf="(pinEnabled$ | async) || this.form.value.pin">
<div class="checkbox">

View File

@@ -191,7 +191,7 @@ describe("SettingsComponent", () => {
desktopAutotypeService.autotypeEnabledUserSetting$ = of(false);
desktopAutotypeService.autotypeKeyboardShortcut$ = of(["Control", "Shift", "B"]);
billingAccountProfileStateService.hasPremiumFromAnySource$.mockReturnValue(of(false));
configService.getFeatureFlag$.mockReturnValue(of(true));
configService.getFeatureFlag$.mockReturnValue(of(false));
});
afterEach(() => {

View File

@@ -55,6 +55,7 @@ import {
TypographyModule,
} from "@bitwarden/components";
import { KeyService, BiometricStateService, BiometricsStatus } from "@bitwarden/key-management";
import { SessionTimeoutSettingsComponent } from "@bitwarden/key-management-ui";
import { PermitCipherDetailsPopoverComponent } from "@bitwarden/vault";
import { SetPinComponent } from "../../auth/components/set-pin.component";
@@ -95,6 +96,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man
SelectModule,
TypographyModule,
VaultTimeoutInputComponent,
SessionTimeoutSettingsComponent,
PermitCipherDetailsPopoverComponent,
PremiumBadgeComponent,
],
@@ -146,6 +148,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
pinEnabled$: Observable<boolean> = of(true);
isWindowsV2BiometricsEnabled: boolean = false;
consolidatedSessionTimeoutComponent$: Observable<boolean>;
form = this.formBuilder.group({
// Security
vaultTimeout: [null as VaultTimeout | null],
@@ -184,7 +188,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
locale: [null as string | null],
});
private refreshTimeoutSettings$ = new BehaviorSubject<void>(undefined);
protected refreshTimeoutSettings$ = new BehaviorSubject<void>(undefined);
private destroy$ = new Subject<void>();
constructor(
@@ -282,12 +286,17 @@ export class SettingsComponent implements OnInit, OnDestroy {
value: SshAgentPromptType.RememberUntilLock,
},
];
this.consolidatedSessionTimeoutComponent$ = this.configService.getFeatureFlag$(
FeatureFlag.ConsolidatedSessionTimeoutComponent,
);
}
async ngOnInit() {
this.vaultTimeoutOptions = await this.generateVaultTimeoutOptions();
this.isWindowsV2BiometricsEnabled = await this.biometricsService.isWindowsV2BiometricsEnabled();
this.vaultTimeoutOptions = await this.generateVaultTimeoutOptions();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
// Autotype is for Windows initially

View File

@@ -109,7 +109,10 @@ import {
BiometricStateService,
BiometricsService,
} from "@bitwarden/key-management";
import { LockComponentService } from "@bitwarden/key-management-ui";
import {
LockComponentService,
SessionTimeoutSettingsComponentService,
} from "@bitwarden/key-management-ui";
import { SerializedMemoryStorageService } from "@bitwarden/storage-core";
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
@@ -125,6 +128,7 @@ import { DesktopBiometricsService } from "../../key-management/biometrics/deskto
import { RendererBiometricsService } from "../../key-management/biometrics/renderer-biometrics.service";
import { ElectronKeyService } from "../../key-management/electron-key.service";
import { DesktopLockComponentService } from "../../key-management/lock/services/desktop-lock-component.service";
import { DesktopSessionTimeoutSettingsComponentService } from "../../key-management/session-timeout/services/desktop-session-timeout-settings-component.service";
import { flagEnabled } from "../../platform/flags";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
@@ -480,6 +484,11 @@ const safeProviders: SafeProvider[] = [
useClass: DesktopAutotypeDefaultSettingPolicy,
deps: [AccountServiceAbstraction, AuthServiceAbstraction, InternalPolicyService, ConfigService],
}),
safeProvider({
provide: SessionTimeoutSettingsComponentService,
useClass: DesktopSessionTimeoutSettingsComponentService,
deps: [I18nServiceAbstraction],
}),
];
@NgModule({

View File

@@ -0,0 +1,48 @@
import { defer, from, map, Observable } from "rxjs";
import {
VaultTimeout,
VaultTimeoutOption,
VaultTimeoutStringType,
} from "@bitwarden/common/key-management/vault-timeout";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SessionTimeoutSettingsComponentService } from "@bitwarden/key-management-ui";
export class DesktopSessionTimeoutSettingsComponentService
implements SessionTimeoutSettingsComponentService
{
availableTimeoutOptions$: Observable<VaultTimeoutOption[]> = defer(() =>
from(ipc.platform.powermonitor.isLockMonitorAvailable()).pipe(
map((isLockMonitorAvailable) => {
const options: VaultTimeoutOption[] = [
{ name: this.i18nService.t("oneMinute"), value: 1 },
{ name: this.i18nService.t("fiveMinutes"), value: 5 },
{ name: this.i18nService.t("fifteenMinutes"), value: 15 },
{ name: this.i18nService.t("thirtyMinutes"), value: 30 },
{ name: this.i18nService.t("oneHour"), value: 60 },
{ name: this.i18nService.t("fourHours"), value: 240 },
{ name: this.i18nService.t("onIdle"), value: VaultTimeoutStringType.OnIdle },
{ name: this.i18nService.t("onSleep"), value: VaultTimeoutStringType.OnSleep },
];
if (isLockMonitorAvailable) {
options.push({
name: this.i18nService.t("onLocked"),
value: VaultTimeoutStringType.OnLocked,
});
}
options.push(
{ name: this.i18nService.t("onRestart"), value: VaultTimeoutStringType.OnRestart },
{ name: this.i18nService.t("never"), value: VaultTimeoutStringType.Never },
);
return options;
}),
),
);
constructor(private readonly i18nService: I18nService) {}
onTimeoutSave(_: VaultTimeout): void {}
}

View File

@@ -4220,5 +4220,11 @@
},
"upgradeToPremium": {
"message": "Upgrade to Premium"
},
"sessionTimeoutSettingsAction": {
"message": "Timeout action"
},
"sessionTimeoutHeader": {
"message": "Session timeout"
}
}