mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-26057] Enforce session timeout policy (#17424)
* enforce session timeout policy * better angular validation * lint fix * missing switch break * fallback when timeout not supported with highest available timeout * failing unit tests * incorrect policy message * vault timeout type adjustments * fallback to "on browser refresh" for browser, when policy is set to "on system locked", but not available (Safari) * docs, naming improvements * fallback for current user session timeout to "on refresh", when policy is set to "on system locked", but not available. * don't display policy message when the policy does not affect available timeout options * 8 hours default when changing from non-numeric timeout to Custom. * failing unit test * missing locales, changing functions access to private, docs * removal of redundant magic number * missing await * await once for available timeout options * adjusted messaging * unit test coverage * vault timeout numeric module exports * unit test coverage
This commit is contained in:
@@ -5881,5 +5881,49 @@
|
||||
},
|
||||
"sessionTimeoutSettingsAction": {
|
||||
"message": "Timeout action"
|
||||
},
|
||||
"sessionTimeoutSettingsManagedByOrganization": {
|
||||
"message": "This setting is managed by your organization."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": {
|
||||
"message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "8"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetDefaultTimeoutToImmediately": {
|
||||
"message": "Your organization has set the default session timeout to Immediately."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": {
|
||||
"message": "Your organization has set the default session timeout to On system lock."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": {
|
||||
"message": "Your organization has set the default session timeout to On browser restart."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicyMaximumError": {
|
||||
"message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeoutOnRestart": {
|
||||
"message": "On browser restart"
|
||||
},
|
||||
"sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": {
|
||||
"message": "Set an unlock method to change your timeout action"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,12 +86,12 @@
|
||||
</bit-section-header>
|
||||
|
||||
<bit-card>
|
||||
<bit-session-timeout-input
|
||||
<bit-session-timeout-input-legacy
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</bit-session-timeout-input>
|
||||
</bit-session-timeout-input-legacy>
|
||||
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label for="vaultTimeoutAction">{{ "vaultTimeoutAction1" | i18n }}</bit-label>
|
||||
|
||||
@@ -70,7 +70,7 @@ import {
|
||||
BiometricsStatus,
|
||||
} from "@bitwarden/key-management";
|
||||
import {
|
||||
SessionTimeoutInputComponent,
|
||||
SessionTimeoutInputLegacyComponent,
|
||||
SessionTimeoutSettingsComponent,
|
||||
} from "@bitwarden/key-management-ui";
|
||||
|
||||
@@ -109,7 +109,7 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component";
|
||||
SessionTimeoutSettingsComponent,
|
||||
SpotlightComponent,
|
||||
TypographyModule,
|
||||
SessionTimeoutInputComponent,
|
||||
SessionTimeoutInputLegacyComponent,
|
||||
],
|
||||
})
|
||||
export class AccountSecurityComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -297,6 +297,7 @@ import { SafariApp } from "../browser/safariApp";
|
||||
import { PhishingDataService } from "../dirt/phishing-detection/services/phishing-data.service";
|
||||
import { PhishingDetectionService } from "../dirt/phishing-detection/services/phishing-detection.service";
|
||||
import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service";
|
||||
import { BrowserSessionTimeoutTypeService } from "../key-management/session-timeout/services/browser-session-timeout-type.service";
|
||||
import VaultTimeoutService from "../key-management/vault-timeout/vault-timeout.service";
|
||||
import { BrowserActionsService } from "../platform/actions/browser-actions.service";
|
||||
import { DefaultBadgeBrowserApi } from "../platform/badge/badge-browser-api";
|
||||
@@ -738,6 +739,10 @@ export default class MainBackground {
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
const sessionTimeoutTypeService = new BrowserSessionTimeoutTypeService(
|
||||
this.platformUtilsService,
|
||||
);
|
||||
|
||||
this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService(
|
||||
this.accountService,
|
||||
pinStateService,
|
||||
@@ -749,6 +754,7 @@ export default class MainBackground {
|
||||
this.stateProvider,
|
||||
this.logService,
|
||||
VaultTimeoutStringType.OnRestart, // default vault timeout
|
||||
sessionTimeoutTypeService,
|
||||
);
|
||||
|
||||
this.apiService = new ApiService(
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
|
||||
import { BrowserSessionTimeoutSettingsComponentService } from "./browser-session-timeout-settings-component.service";
|
||||
|
||||
describe("BrowserSessionTimeoutSettingsComponentService", () => {
|
||||
let service: BrowserSessionTimeoutSettingsComponentService;
|
||||
let mockI18nService: jest.Mocked<I18nService>;
|
||||
let mockSessionTimeoutTypeService: jest.Mocked<SessionTimeoutTypeService>;
|
||||
let mockPolicyService: jest.Mocked<PolicyService>;
|
||||
let mockMessagingService: jest.Mocked<MessagingService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockI18nService = mock<I18nService>();
|
||||
mockSessionTimeoutTypeService = mock<SessionTimeoutTypeService>();
|
||||
mockPolicyService = mock<PolicyService>();
|
||||
mockMessagingService = mock<MessagingService>();
|
||||
|
||||
service = new BrowserSessionTimeoutSettingsComponentService(
|
||||
mockI18nService,
|
||||
mockSessionTimeoutTypeService,
|
||||
mockPolicyService,
|
||||
mockMessagingService,
|
||||
);
|
||||
});
|
||||
|
||||
describe("onTimeoutSave", () => {
|
||||
it("should call messagingService.send with 'bgReseedStorage' when timeout is Never", () => {
|
||||
service.onTimeoutSave(VaultTimeoutStringType.Never);
|
||||
|
||||
expect(mockMessagingService.send).toHaveBeenCalledWith("bgReseedStorage");
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.Custom,
|
||||
])("should not call messagingService.send when timeout is %s", (timeoutValue) => {
|
||||
service.onTimeoutSave(timeoutValue);
|
||||
|
||||
expect(mockMessagingService.send).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,56 +1,24 @@
|
||||
import { defer, Observable, of } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
VaultTimeout,
|
||||
VaultTimeoutOption,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SessionTimeoutSettingsComponentService } from "@bitwarden/key-management-ui";
|
||||
|
||||
export class BrowserSessionTimeoutSettingsComponentService
|
||||
implements SessionTimeoutSettingsComponentService
|
||||
{
|
||||
availableTimeoutOptions$: Observable<VaultTimeoutOption[]> = defer(() => {
|
||||
const options: VaultTimeoutOption[] = [
|
||||
{ name: this.i18nService.t("immediately"), value: 0 },
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
const showOnLocked =
|
||||
!this.platformUtilsService.isFirefox() &&
|
||||
!this.platformUtilsService.isSafari() &&
|
||||
!(this.platformUtilsService.isOpera() && navigator.platform === "MacIntel");
|
||||
|
||||
if (showOnLocked) {
|
||||
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 of(options);
|
||||
});
|
||||
|
||||
export class BrowserSessionTimeoutSettingsComponentService extends SessionTimeoutSettingsComponentService {
|
||||
constructor(
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
sessionTimeoutTypeService: SessionTimeoutTypeService,
|
||||
policyService: PolicyService,
|
||||
private readonly messagingService: MessagingService,
|
||||
) {}
|
||||
) {
|
||||
super(i18nService, sessionTimeoutTypeService, policyService);
|
||||
}
|
||||
|
||||
onTimeoutSave(timeout: VaultTimeout): void {
|
||||
override onTimeoutSave(timeout: VaultTimeout): void {
|
||||
if (timeout === VaultTimeoutStringType.Never) {
|
||||
this.messagingService.send("bgReseedStorage");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import {
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { BrowserSessionTimeoutTypeService } from "./browser-session-timeout-type.service";
|
||||
|
||||
describe("BrowserSessionTimeoutTypeService", () => {
|
||||
let service: BrowserSessionTimeoutTypeService;
|
||||
let mockPlatformUtilsService: jest.Mocked<PlatformUtilsService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlatformUtilsService = mock<PlatformUtilsService>();
|
||||
service = new BrowserSessionTimeoutTypeService(mockPlatformUtilsService);
|
||||
});
|
||||
|
||||
describe("isAvailable", () => {
|
||||
it.each([
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.Custom,
|
||||
])("should return true for always available type: %s", async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it.each([VaultTimeoutNumberType.OnMinute, VaultTimeoutNumberType.EightHours])(
|
||||
"should return true for numeric timeout type: %s",
|
||||
async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
describe("OnLocked availability", () => {
|
||||
const mockNavigatorPlatform = (platform: string) => {
|
||||
Object.defineProperty(navigator, "platform", {
|
||||
value: platform,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockNavigatorPlatform("Linux x86_64");
|
||||
mockPlatformUtilsService.isFirefox.mockReturnValue(false);
|
||||
mockPlatformUtilsService.isSafari.mockReturnValue(false);
|
||||
mockPlatformUtilsService.isOpera.mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("should return true when not Firefox, Safari, or Opera on Mac", async () => {
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true when Opera on non-Mac platform", async () => {
|
||||
mockNavigatorPlatform("Win32");
|
||||
mockPlatformUtilsService.isOpera.mockReturnValue(true);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when Opera on Mac", async () => {
|
||||
mockNavigatorPlatform("MacIntel");
|
||||
mockPlatformUtilsService.isOpera.mockReturnValue(true);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when Firefox", async () => {
|
||||
mockPlatformUtilsService.isFirefox.mockReturnValue(true);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when Safari", async () => {
|
||||
mockPlatformUtilsService.isSafari.mockReturnValue(true);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([VaultTimeoutStringType.OnIdle, VaultTimeoutStringType.OnSleep])(
|
||||
"should return false for unavailable timeout type: %s",
|
||||
async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("getOrPromoteToAvailable", () => {
|
||||
it.each([
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.Custom,
|
||||
])("should return the original type when it is available: %s", async (timeoutType) => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(true);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(timeoutType);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(timeoutType);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
5,
|
||||
])("should return OnRestart when type is not available: %s", async (timeoutType) => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(false);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(VaultTimeoutStringType.OnRestart);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(timeoutType);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
isVaultTimeoutTypeNumeric,
|
||||
VaultTimeout,
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
export class BrowserSessionTimeoutTypeService implements SessionTimeoutTypeService {
|
||||
constructor(private readonly platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
async isAvailable(type: VaultTimeout): Promise<boolean> {
|
||||
switch (type) {
|
||||
case VaultTimeoutNumberType.Immediately:
|
||||
case VaultTimeoutStringType.OnRestart:
|
||||
case VaultTimeoutStringType.Never:
|
||||
case VaultTimeoutStringType.Custom:
|
||||
return true;
|
||||
case VaultTimeoutStringType.OnLocked:
|
||||
return (
|
||||
!this.platformUtilsService.isFirefox() &&
|
||||
!this.platformUtilsService.isSafari() &&
|
||||
!(this.platformUtilsService.isOpera() && navigator.platform === "MacIntel")
|
||||
);
|
||||
default:
|
||||
if (isVaultTimeoutTypeNumeric(type)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async getOrPromoteToAvailable(type: VaultTimeout): Promise<VaultTimeout> {
|
||||
const available = await this.isAvailable(type);
|
||||
if (!available) {
|
||||
return VaultTimeoutStringType.OnRestart;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ import {
|
||||
InternalMasterPasswordServiceAbstraction,
|
||||
MasterPasswordServiceAbstraction,
|
||||
} from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
VaultTimeoutService,
|
||||
VaultTimeoutStringType,
|
||||
@@ -170,6 +171,7 @@ import { InlineMenuFieldQualificationService } from "../../autofill/services/inl
|
||||
import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics";
|
||||
import { ExtensionLockComponentService } from "../../key-management/lock/services/extension-lock-component.service";
|
||||
import { BrowserSessionTimeoutSettingsComponentService } from "../../key-management/session-timeout/services/browser-session-timeout-settings-component.service";
|
||||
import { BrowserSessionTimeoutTypeService } from "../../key-management/session-timeout/services/browser-session-timeout-type.service";
|
||||
import { ForegroundVaultTimeoutService } from "../../key-management/vault-timeout/foreground-vault-timeout.service";
|
||||
import { BrowserActionsService } from "../../platform/actions/browser-actions.service";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
@@ -723,10 +725,20 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: ExtensionNewDeviceVerificationComponentService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutTypeService,
|
||||
useClass: BrowserSessionTimeoutTypeService,
|
||||
deps: [PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutSettingsComponentService,
|
||||
useClass: BrowserSessionTimeoutSettingsComponentService,
|
||||
deps: [I18nServiceAbstraction, PlatformUtilsService, MessagingServiceAbstraction],
|
||||
deps: [
|
||||
I18nServiceAbstraction,
|
||||
SessionTimeoutTypeService,
|
||||
PolicyService,
|
||||
MessagingServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
VaultTimeout,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
|
||||
export class CliSessionTimeoutTypeService implements SessionTimeoutTypeService {
|
||||
async isAvailable(timeout: VaultTimeout): Promise<boolean> {
|
||||
return timeout === VaultTimeoutStringType.Never;
|
||||
}
|
||||
|
||||
async getOrPromoteToAvailable(_: VaultTimeout): Promise<VaultTimeout> {
|
||||
return VaultTimeoutStringType.Never;
|
||||
}
|
||||
}
|
||||
@@ -211,6 +211,7 @@ import {
|
||||
|
||||
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
|
||||
import { CliProcessReloadService } from "../key-management/cli-process-reload.service";
|
||||
import { CliSessionTimeoutTypeService } from "../key-management/session-timeout/services/cli-session-timeout-type.service";
|
||||
import { flagEnabled } from "../platform/flags";
|
||||
import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service";
|
||||
import { CliSdkLoadService } from "../platform/services/cli-sdk-load.service";
|
||||
@@ -529,6 +530,8 @@ export class ServiceContainer {
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
const sessionTimeoutTypeService = new CliSessionTimeoutTypeService();
|
||||
|
||||
this.vaultTimeoutSettingsService = new DefaultVaultTimeoutSettingsService(
|
||||
this.accountService,
|
||||
pinStateService,
|
||||
@@ -540,6 +543,7 @@ export class ServiceContainer {
|
||||
this.stateProvider,
|
||||
this.logService,
|
||||
VaultTimeoutStringType.Never, // default vault timeout
|
||||
sessionTimeoutTypeService,
|
||||
);
|
||||
|
||||
const refreshAccessTokenErrorCallback = () => {
|
||||
|
||||
@@ -44,12 +44,12 @@
|
||||
<h2 bitTypography="h6">{{ "vaultTimeoutHeader" | i18n }}</h2>
|
||||
</bit-section-header>
|
||||
|
||||
<bit-session-timeout-input
|
||||
<bit-session-timeout-input-legacy
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</bit-session-timeout-input>
|
||||
</bit-session-timeout-input-legacy>
|
||||
|
||||
<bit-form-field disableMargin>
|
||||
<bit-label for="vaultTimeoutAction">{{
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
} from "@bitwarden/components";
|
||||
import { KeyService, BiometricStateService, BiometricsStatus } from "@bitwarden/key-management";
|
||||
import {
|
||||
SessionTimeoutInputComponent,
|
||||
SessionTimeoutInputLegacyComponent,
|
||||
SessionTimeoutSettingsComponent,
|
||||
} from "@bitwarden/key-management-ui";
|
||||
import { PermitCipherDetailsPopoverComponent } from "@bitwarden/vault";
|
||||
@@ -97,7 +97,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man
|
||||
SectionHeaderComponent,
|
||||
SelectModule,
|
||||
TypographyModule,
|
||||
SessionTimeoutInputComponent,
|
||||
SessionTimeoutInputLegacyComponent,
|
||||
SessionTimeoutSettingsComponent,
|
||||
PermitCipherDetailsPopoverComponent,
|
||||
PremiumBadgeComponent,
|
||||
|
||||
@@ -62,6 +62,7 @@ import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypt
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service";
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
VaultTimeoutSettingsService,
|
||||
VaultTimeoutStringType,
|
||||
@@ -128,7 +129,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 { DesktopSessionTimeoutTypeService } from "../../key-management/session-timeout/services/desktop-session-timeout-type.service";
|
||||
import { flagEnabled } from "../../platform/flags";
|
||||
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
|
||||
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
|
||||
@@ -484,10 +485,15 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: DesktopAutotypeDefaultSettingPolicy,
|
||||
deps: [AccountServiceAbstraction, AuthServiceAbstraction, InternalPolicyService, ConfigService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutTypeService,
|
||||
useClass: DesktopSessionTimeoutTypeService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutSettingsComponentService,
|
||||
useClass: DesktopSessionTimeoutSettingsComponentService,
|
||||
deps: [I18nServiceAbstraction],
|
||||
useClass: SessionTimeoutSettingsComponentService,
|
||||
deps: [I18nServiceAbstraction, SessionTimeoutTypeService, PolicyServiceAbstraction],
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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 {}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
|
||||
import { DesktopSessionTimeoutTypeService } from "./desktop-session-timeout-type.service";
|
||||
|
||||
describe("DesktopSessionTimeoutTypeService", () => {
|
||||
let service: DesktopSessionTimeoutTypeService;
|
||||
let mockIsLockMonitorAvailable: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockIsLockMonitorAvailable = jest.fn();
|
||||
|
||||
(global as any).ipc = {
|
||||
platform: {
|
||||
powermonitor: {
|
||||
isLockMonitorAvailable: mockIsLockMonitorAvailable,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
service = new DesktopSessionTimeoutTypeService();
|
||||
});
|
||||
|
||||
describe("isAvailable", () => {
|
||||
it("should return false for Immediately", async () => {
|
||||
const result = await service.isAvailable(VaultTimeoutNumberType.Immediately);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.Custom,
|
||||
])("should return true for always available type: %s", async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it.each([VaultTimeoutNumberType.OnMinute, VaultTimeoutNumberType.EightHours])(
|
||||
"should return true for numeric timeout type: %s",
|
||||
async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
describe("OnLocked availability", () => {
|
||||
it("should return true when lock monitor is available", async () => {
|
||||
mockIsLockMonitorAvailable.mockResolvedValue(true);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockIsLockMonitorAvailable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return false when lock monitor is not available", async () => {
|
||||
mockIsLockMonitorAvailable.mockResolvedValue(false);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockIsLockMonitorAvailable).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOrPromoteToAvailable", () => {
|
||||
it.each([
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.Custom,
|
||||
])("should return the original type when it is available: %s", async (timeoutType) => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(true);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(timeoutType);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(timeoutType);
|
||||
});
|
||||
|
||||
it("should return OnMinute when Immediately is not available", async () => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(false);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(VaultTimeoutNumberType.Immediately);
|
||||
|
||||
expect(result).toBe(VaultTimeoutNumberType.OnMinute);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(VaultTimeoutNumberType.Immediately);
|
||||
});
|
||||
|
||||
it("should return OnSleep when OnLocked is not available", async () => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(false);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(VaultTimeoutStringType.OnLocked);
|
||||
|
||||
expect(result).toBe(VaultTimeoutStringType.OnSleep);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(VaultTimeoutStringType.OnLocked);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
5,
|
||||
])("should return OnRestart when type is not available: %s", async (timeoutType) => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(false);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(VaultTimeoutStringType.OnRestart);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(timeoutType);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
isVaultTimeoutTypeNumeric,
|
||||
VaultTimeout,
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
|
||||
export class DesktopSessionTimeoutTypeService implements SessionTimeoutTypeService {
|
||||
async isAvailable(type: VaultTimeout): Promise<boolean> {
|
||||
switch (type) {
|
||||
case VaultTimeoutNumberType.Immediately:
|
||||
return false;
|
||||
case VaultTimeoutStringType.OnIdle:
|
||||
case VaultTimeoutStringType.OnSleep:
|
||||
case VaultTimeoutStringType.OnRestart:
|
||||
case VaultTimeoutStringType.Never:
|
||||
case VaultTimeoutStringType.Custom:
|
||||
return true;
|
||||
case VaultTimeoutStringType.OnLocked:
|
||||
return await ipc.platform.powermonitor.isLockMonitorAvailable();
|
||||
default:
|
||||
if (isVaultTimeoutTypeNumeric(type)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async getOrPromoteToAvailable(type: VaultTimeout): Promise<VaultTimeout> {
|
||||
const available = await this.isAvailable(type);
|
||||
if (!available) {
|
||||
switch (type) {
|
||||
case VaultTimeoutNumberType.Immediately:
|
||||
return VaultTimeoutNumberType.OnMinute;
|
||||
case VaultTimeoutStringType.OnLocked:
|
||||
return VaultTimeoutStringType.OnSleep;
|
||||
default:
|
||||
return VaultTimeoutStringType.OnRestart;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -4255,5 +4255,46 @@
|
||||
},
|
||||
"sessionTimeoutHeader": {
|
||||
"message": "Session timeout"
|
||||
},
|
||||
"sessionTimeoutSettingsManagedByOrganization": {
|
||||
"message": "This setting is managed by your organization."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": {
|
||||
"message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "8"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetDefaultTimeoutToOnLocked": {
|
||||
"message": "Your organization has set the default session timeout to On system lock."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": {
|
||||
"message": "Your organization has set the default session timeout to On restart."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicyMaximumError": {
|
||||
"message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeoutOnRestart": {
|
||||
"message": "On restart"
|
||||
},
|
||||
"sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": {
|
||||
"message": "Set an unlock method to change your timeout action"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-managemen
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
VaultTimeout,
|
||||
VaultTimeoutStringType,
|
||||
@@ -124,7 +125,6 @@ import {
|
||||
import { SerializedMemoryStorageService } from "@bitwarden/storage-core";
|
||||
import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault";
|
||||
import { WebOrganizationInviteService } from "@bitwarden/web-vault/app/auth/core/services/organization-invite/web-organization-invite.service";
|
||||
import { WebSessionTimeoutSettingsComponentService } from "@bitwarden/web-vault/app/key-management/session-timeout/services/web-session-timeout-settings-component.service";
|
||||
import { WebVaultPremiumUpgradePromptService } from "@bitwarden/web-vault/app/vault/services/web-premium-upgrade-prompt.service";
|
||||
|
||||
import { flagEnabled } from "../../utils/flags";
|
||||
@@ -149,6 +149,7 @@ import { WebFileDownloadService } from "../core/web-file-download.service";
|
||||
import { UserKeyRotationService } from "../key-management/key-rotation/user-key-rotation.service";
|
||||
import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service";
|
||||
import { WebProcessReloadService } from "../key-management/services/web-process-reload.service";
|
||||
import { WebSessionTimeoutTypeService } from "../key-management/session-timeout/services/web-session-timeout-type.service";
|
||||
import { WebBiometricsService } from "../key-management/web-biometric.service";
|
||||
import { WebIpcService } from "../platform/ipc/web-ipc.service";
|
||||
import { WebEnvironmentService } from "../platform/web-environment.service";
|
||||
@@ -469,10 +470,15 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: WebSystemService,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutTypeService,
|
||||
useClass: WebSessionTimeoutTypeService,
|
||||
deps: [PlatformUtilsService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SessionTimeoutSettingsComponentService,
|
||||
useClass: WebSessionTimeoutSettingsComponentService,
|
||||
deps: [I18nServiceAbstraction, PlatformUtilsService],
|
||||
useClass: SessionTimeoutSettingsComponentService,
|
||||
deps: [I18nServiceAbstraction, SessionTimeoutTypeService, PolicyService],
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import { defer, Observable, of } from "rxjs";
|
||||
|
||||
import {
|
||||
VaultTimeout,
|
||||
VaultTimeoutOption,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { SessionTimeoutSettingsComponentService } from "@bitwarden/key-management-ui";
|
||||
|
||||
export class WebSessionTimeoutSettingsComponentService
|
||||
implements SessionTimeoutSettingsComponentService
|
||||
{
|
||||
availableTimeoutOptions$: Observable<VaultTimeoutOption[]> = defer(() => {
|
||||
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("onRefresh"), value: VaultTimeoutStringType.OnRestart },
|
||||
];
|
||||
|
||||
if (this.platformUtilsService.isDev()) {
|
||||
options.push({ name: this.i18nService.t("never"), value: VaultTimeoutStringType.Never });
|
||||
}
|
||||
|
||||
return of(options);
|
||||
});
|
||||
|
||||
constructor(
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
) {}
|
||||
|
||||
onTimeoutSave(_: VaultTimeout): void {}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
|
||||
import {
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
import { WebSessionTimeoutTypeService } from "./web-session-timeout-type.service";
|
||||
|
||||
describe("WebSessionTimeoutTypeService", () => {
|
||||
let service: WebSessionTimeoutTypeService;
|
||||
let mockPlatformUtilsService: jest.Mocked<PlatformUtilsService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPlatformUtilsService = mock<PlatformUtilsService>();
|
||||
service = new WebSessionTimeoutTypeService(mockPlatformUtilsService);
|
||||
});
|
||||
|
||||
describe("isAvailable", () => {
|
||||
it("should return false for Immediately", async () => {
|
||||
const result = await service.isAvailable(VaultTimeoutNumberType.Immediately);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it.each([VaultTimeoutStringType.OnRestart, VaultTimeoutStringType.Custom])(
|
||||
"should return true for always available type: %s",
|
||||
async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([VaultTimeoutNumberType.OnMinute, VaultTimeoutNumberType.EightHours])(
|
||||
"should return true for numeric timeout type: %s",
|
||||
async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
])("should return false for unavailable timeout type: %s", async (timeoutType) => {
|
||||
const result = await service.isAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
describe("Never availability", () => {
|
||||
it("should return true when in dev mode", async () => {
|
||||
mockPlatformUtilsService.isDev.mockReturnValue(true);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.Never);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockPlatformUtilsService.isDev).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return false when not in dev mode", async () => {
|
||||
mockPlatformUtilsService.isDev.mockReturnValue(false);
|
||||
|
||||
const result = await service.isAvailable(VaultTimeoutStringType.Never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockPlatformUtilsService.isDev).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOrPromoteToAvailable", () => {
|
||||
it.each([
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.Custom,
|
||||
])("should return the original type when it is available: %s", async (timeoutType) => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(true);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(timeoutType);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(timeoutType);
|
||||
});
|
||||
|
||||
it("should return OnMinute when Immediately is not available", async () => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(false);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(VaultTimeoutNumberType.Immediately);
|
||||
|
||||
expect(result).toBe(VaultTimeoutNumberType.OnMinute);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(VaultTimeoutNumberType.Immediately);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.Never,
|
||||
])("should return OnRestart when type is not available: %s", async (timeoutType) => {
|
||||
jest.spyOn(service, "isAvailable").mockResolvedValue(false);
|
||||
|
||||
const result = await service.getOrPromoteToAvailable(timeoutType);
|
||||
|
||||
expect(result).toBe(VaultTimeoutStringType.OnRestart);
|
||||
expect(service.isAvailable).toHaveBeenCalledWith(timeoutType);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { SessionTimeoutTypeService } from "@bitwarden/common/key-management/session-timeout";
|
||||
import {
|
||||
isVaultTimeoutTypeNumeric,
|
||||
VaultTimeout,
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
||||
export class WebSessionTimeoutTypeService implements SessionTimeoutTypeService {
|
||||
constructor(private readonly platformUtilsService: PlatformUtilsService) {}
|
||||
|
||||
async isAvailable(type: VaultTimeout): Promise<boolean> {
|
||||
switch (type) {
|
||||
case VaultTimeoutNumberType.Immediately:
|
||||
return false;
|
||||
case VaultTimeoutStringType.OnRestart:
|
||||
case VaultTimeoutStringType.Custom:
|
||||
return true;
|
||||
case VaultTimeoutStringType.Never:
|
||||
return this.platformUtilsService.isDev();
|
||||
default:
|
||||
if (isVaultTimeoutTypeNumeric(type)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async getOrPromoteToAvailable(type: VaultTimeout): Promise<VaultTimeout> {
|
||||
const available = await this.isAvailable(type);
|
||||
if (!available) {
|
||||
switch (type) {
|
||||
case VaultTimeoutNumberType.Immediately:
|
||||
return VaultTimeoutNumberType.OnMinute;
|
||||
default:
|
||||
return VaultTimeoutStringType.OnRestart;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,12 @@
|
||||
{{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }}
|
||||
</span>
|
||||
</bit-callout>
|
||||
<bit-session-timeout-input
|
||||
<bit-session-timeout-input-legacy
|
||||
[vaultTimeoutOptions]="vaultTimeoutOptions"
|
||||
[formControl]="form.controls.vaultTimeout"
|
||||
ngDefaultControl
|
||||
>
|
||||
</bit-session-timeout-input>
|
||||
</bit-session-timeout-input-legacy>
|
||||
<ng-container *ngIf="availableVaultTimeoutActions$ | async as availableVaultTimeoutActions">
|
||||
<bit-radio-group
|
||||
formControlName="vaultTimeoutAction"
|
||||
|
||||
@@ -33,7 +33,7 @@ import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { SessionTimeoutInputComponent } from "@bitwarden/key-management-ui";
|
||||
import { SessionTimeoutInputLegacyComponent } from "@bitwarden/key-management-ui";
|
||||
import { PermitCipherDetailsPopoverComponent } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../layouts/header/header.module";
|
||||
@@ -52,8 +52,8 @@ import { SharedModule } from "../shared";
|
||||
imports: [
|
||||
SharedModule,
|
||||
HeaderModule,
|
||||
SessionTimeoutInputComponent,
|
||||
PermitCipherDetailsPopoverComponent,
|
||||
SessionTimeoutInputLegacyComponent,
|
||||
],
|
||||
})
|
||||
export class PreferencesComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -12214,5 +12214,43 @@
|
||||
},
|
||||
"userVerificationFailed": {
|
||||
"message": "User verification failed."
|
||||
},
|
||||
"sessionTimeoutSettingsManagedByOrganization": {
|
||||
"message": "This setting is managed by your organization."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetMaximumTimeoutToHoursMinutes": {
|
||||
"message": "Your organization has set the maximum session timeout to $HOURS$ hour(s) and $MINUTES$ minute(s).",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "8"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeoutSettingsPolicySetDefaultTimeoutToOnRestart": {
|
||||
"message": "Your organization has set the default session timeout to On browser refresh."
|
||||
},
|
||||
"sessionTimeoutSettingsPolicyMaximumError": {
|
||||
"message": "Maximum timeout cannot exceed $HOURS$ hour(s) and $MINUTES$ minute(s)",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "5"
|
||||
},
|
||||
"minutes": {
|
||||
"content": "$2",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeoutOnRestart": {
|
||||
"message": "On browser refresh"
|
||||
},
|
||||
"sessionTimeoutSettingsSetUnlockMethodToChangeTimeoutAction": {
|
||||
"message": "Set an unlock method to change your timeout action"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user