mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 17:53:39 +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:
@@ -0,0 +1,15 @@
|
||||
import { VaultTimeout } from "../../vault-timeout";
|
||||
|
||||
export abstract class SessionTimeoutTypeService {
|
||||
/**
|
||||
* Is provided timeout type available on this client type, OS ?
|
||||
* @param timeout the timeout type
|
||||
*/
|
||||
abstract isAvailable(timeout: VaultTimeout): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Returns the highest available and permissive timeout type, that is higher than or equals the provided timeout type.
|
||||
* @param timeout the provided timeout type
|
||||
*/
|
||||
abstract getOrPromoteToAvailable(timeout: VaultTimeout): Promise<VaultTimeout>;
|
||||
}
|
||||
3
libs/common/src/key-management/session-timeout/index.ts
Normal file
3
libs/common/src/key-management/session-timeout/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { SessionTimeoutTypeService } from "./abstractions/session-timeout-type.service";
|
||||
export { MaximumSessionTimeoutPolicyData } from "./types/maximum-session-timeout-policy.type";
|
||||
export { SessionTimeoutAction, SessionTimeoutType } from "./types/session-timeout.type";
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SessionTimeoutAction, SessionTimeoutType } from "./session-timeout.type";
|
||||
|
||||
export interface MaximumSessionTimeoutPolicyData {
|
||||
type?: SessionTimeoutType;
|
||||
minutes: number;
|
||||
action?: SessionTimeoutAction;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export type SessionTimeoutAction = null | "lock" | "logOut";
|
||||
export type SessionTimeoutType =
|
||||
| null
|
||||
| "never"
|
||||
| "onAppRestart"
|
||||
| "onSystemLock"
|
||||
| "immediately"
|
||||
| "custom";
|
||||
@@ -4,8 +4,9 @@ export { VaultTimeoutService } from "./abstractions/vault-timeout.service";
|
||||
export { VaultTimeoutService as DefaultVaultTimeoutService } from "./services/vault-timeout.service";
|
||||
export { VaultTimeoutAction } from "./enums/vault-timeout-action.enum";
|
||||
export {
|
||||
isVaultTimeoutTypeNumeric,
|
||||
VaultTimeout,
|
||||
VaultTimeoutOption,
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "./types/vault-timeout.type";
|
||||
export { MaximumVaultTimeoutPolicyData } from "./types/maximum-vault-timeout-policy.type";
|
||||
|
||||
@@ -21,9 +21,14 @@ import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PinStateServiceAbstraction } from "../../pin/pin-state.service.abstraction";
|
||||
import { SessionTimeoutTypeService } from "../../session-timeout";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../abstractions/vault-timeout-settings.service";
|
||||
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
||||
import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type";
|
||||
import {
|
||||
VaultTimeout,
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "../types/vault-timeout.type";
|
||||
|
||||
import { VaultTimeoutSettingsService } from "./vault-timeout-settings.service";
|
||||
import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION } from "./vault-timeout-settings.state";
|
||||
@@ -40,9 +45,11 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
|
||||
let userDecryptionOptionsSubject: BehaviorSubject<UserDecryptionOptions>;
|
||||
|
||||
const defaultVaultTimeout: VaultTimeout = 15; // default web vault timeout
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let stateProvider: FakeStateProvider;
|
||||
let logService: MockProxy<LogService>;
|
||||
let sessionTimeoutTypeService: MockProxy<SessionTimeoutTypeService>;
|
||||
|
||||
beforeEach(() => {
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
@@ -67,8 +74,8 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
|
||||
logService = mock<LogService>();
|
||||
sessionTimeoutTypeService = mock<SessionTimeoutTypeService>();
|
||||
|
||||
const defaultVaultTimeout: VaultTimeout = 15; // default web vault timeout
|
||||
vaultTimeoutSettingsService = createVaultTimeoutSettingsService(defaultVaultTimeout);
|
||||
|
||||
biometricStateService.biometricUnlockEnabled$ = of(false);
|
||||
@@ -259,40 +266,276 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
// policy, vaultTimeout, expected
|
||||
[null, null, 15], // no policy, no vault timeout, falls back to default
|
||||
[30, 90, 30], // policy overrides vault timeout
|
||||
[30, 15, 15], // policy doesn't override vault timeout when it's within acceptable range
|
||||
[90, VaultTimeoutStringType.Never, 90], // policy overrides vault timeout when it's "never"
|
||||
[null, VaultTimeoutStringType.Never, VaultTimeoutStringType.Never], // no policy, persist "never" vault timeout
|
||||
[90, 0, 0], // policy doesn't override vault timeout when it's 0 (immediate)
|
||||
[null, 0, 0], // no policy, persist 0 (immediate) vault timeout
|
||||
[90, VaultTimeoutStringType.OnRestart, 90], // policy overrides vault timeout when it's "onRestart"
|
||||
[null, VaultTimeoutStringType.OnRestart, VaultTimeoutStringType.OnRestart], // no policy, persist "onRestart" vault timeout
|
||||
[90, VaultTimeoutStringType.OnLocked, 90], // policy overrides vault timeout when it's "onLocked"
|
||||
[null, VaultTimeoutStringType.OnLocked, VaultTimeoutStringType.OnLocked], // no policy, persist "onLocked" vault timeout
|
||||
[90, VaultTimeoutStringType.OnSleep, 90], // policy overrides vault timeout when it's "onSleep"
|
||||
[null, VaultTimeoutStringType.OnSleep, VaultTimeoutStringType.OnSleep], // no policy, persist "onSleep" vault timeout
|
||||
[90, VaultTimeoutStringType.OnIdle, 90], // policy overrides vault timeout when it's "onIdle"
|
||||
[null, VaultTimeoutStringType.OnIdle, VaultTimeoutStringType.OnIdle], // no policy, persist "onIdle" vault timeout
|
||||
])(
|
||||
"when policy is %s, and vault timeout is %s, returns %s",
|
||||
async (policy, vaultTimeout, expected) => {
|
||||
describe("no policy", () => {
|
||||
it("when vault timeout is null, returns default", async () => {
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of(policy === null ? [] : ([{ data: { minutes: policy } }] as unknown as Policy[])),
|
||||
policyService.policiesByType$.mockReturnValue(of([]));
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, null, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(result).toBe(defaultVaultTimeout);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
])("when vault timeout is %s, returns unchanged", async (vaultTimeout) => {
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(of([]));
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, vaultTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(result).toBe(expected);
|
||||
},
|
||||
);
|
||||
expect(result).toBe(vaultTimeout);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: custom", () => {
|
||||
const policyMinutes = 30;
|
||||
|
||||
it.each([
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
])(
|
||||
"when vault timeout is %s and exceeds policy max, returns policy minutes",
|
||||
async (vaultTimeout) => {
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "custom", minutes: policyMinutes } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, vaultTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(result).toBe(policyMinutes);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([VaultTimeoutNumberType.OnMinute, policyMinutes])(
|
||||
"when vault timeout is %s and within policy max, returns unchanged",
|
||||
async (vaultTimeout) => {
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "custom", minutes: policyMinutes } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, vaultTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(result).toBe(vaultTimeout);
|
||||
},
|
||||
);
|
||||
|
||||
it("when vault timeout is Immediately, returns Immediately", async () => {
|
||||
userDecryptionOptionsSubject.next(new UserDecryptionOptions({ hasMasterPassword: true }));
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "custom", minutes: policyMinutes } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(
|
||||
VAULT_TIMEOUT,
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
mockUserId,
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(result).toBe(VaultTimeoutNumberType.Immediately);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: immediately", () => {
|
||||
it.each([
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
])(
|
||||
"when current timeout is %s, returns immediately or promoted value",
|
||||
async (currentTimeout) => {
|
||||
const expectedTimeout = VaultTimeoutNumberType.Immediately;
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(expectedTimeout);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "immediately" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, currentTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
);
|
||||
expect(result).toBe(expectedTimeout);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("policy type: onSystemLock", () => {
|
||||
it.each([
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
])(
|
||||
"when current timeout is %s, returns onLocked or promoted value",
|
||||
async (currentTimeout) => {
|
||||
const expectedTimeout = VaultTimeoutStringType.OnLocked;
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(expectedTimeout);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "onSystemLock" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, currentTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
);
|
||||
expect(result).toBe(expectedTimeout);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
])("when current timeout is numeric %s, returns unchanged", async (currentTimeout) => {
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "onSystemLock" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, currentTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(result).toBe(currentTimeout);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: onAppRestart", () => {
|
||||
it.each([
|
||||
VaultTimeoutStringType.Never,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
])("when current timeout is %s, returns onRestart", async (currentTimeout) => {
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "onAppRestart" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, currentTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(result).toBe(VaultTimeoutStringType.OnRestart);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
])("when current timeout is %s, returns unchanged", async (currentTimeout) => {
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "onAppRestart" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, currentTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(result).toBe(currentTimeout);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy type: never", () => {
|
||||
it("when current timeout is never, returns never or promoted value", async () => {
|
||||
const expectedTimeout = VaultTimeoutStringType.Never;
|
||||
sessionTimeoutTypeService.getOrPromoteToAvailable.mockResolvedValue(expectedTimeout);
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "never" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, VaultTimeoutStringType.Never, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).toHaveBeenCalledWith(
|
||||
VaultTimeoutStringType.Never,
|
||||
);
|
||||
expect(result).toBe(expectedTimeout);
|
||||
});
|
||||
|
||||
it.each([
|
||||
VaultTimeoutStringType.OnRestart,
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
VaultTimeoutStringType.OnIdle,
|
||||
VaultTimeoutStringType.OnSleep,
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
VaultTimeoutNumberType.OnMinute,
|
||||
VaultTimeoutNumberType.EightHours,
|
||||
])("when current timeout is %s, returns unchanged", async (currentTimeout) => {
|
||||
policyService.policiesByType$.mockReturnValue(
|
||||
of([{ data: { type: "never" } }] as unknown as Policy[]),
|
||||
);
|
||||
|
||||
await stateProvider.setUserState(VAULT_TIMEOUT, currentTimeout, mockUserId);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$(mockUserId),
|
||||
);
|
||||
|
||||
expect(sessionTimeoutTypeService.getOrPromoteToAvailable).not.toHaveBeenCalled();
|
||||
expect(result).toBe(currentTimeout);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setVaultTimeoutOptions", () => {
|
||||
@@ -405,6 +648,7 @@ describe("VaultTimeoutSettingsService", () => {
|
||||
stateProvider,
|
||||
logService,
|
||||
defaultVaultTimeout,
|
||||
sessionTimeoutTypeService,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
EMPTY,
|
||||
Observable,
|
||||
catchError,
|
||||
combineLatest,
|
||||
defer,
|
||||
distinctUntilChanged,
|
||||
EMPTY,
|
||||
firstValueFrom,
|
||||
from,
|
||||
map,
|
||||
Observable,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
tap,
|
||||
@@ -23,7 +24,6 @@ import { BiometricStateService, KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { PolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "../../../admin-console/enums";
|
||||
import { Policy } from "../../../admin-console/models/domain/policy";
|
||||
import { getFirstPolicy } from "../../../admin-console/services/policy/default-policy.service";
|
||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { TokenService } from "../../../auth/abstractions/token.service";
|
||||
@@ -31,9 +31,15 @@ import { LogService } from "../../../platform/abstractions/log.service";
|
||||
import { StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { PinStateServiceAbstraction } from "../../pin/pin-state.service.abstraction";
|
||||
import { MaximumSessionTimeoutPolicyData, SessionTimeoutTypeService } from "../../session-timeout";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../abstractions/vault-timeout-settings.service";
|
||||
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
||||
import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type";
|
||||
import {
|
||||
isVaultTimeoutTypeNumeric,
|
||||
VaultTimeout,
|
||||
VaultTimeoutNumberType,
|
||||
VaultTimeoutStringType,
|
||||
} from "../types/vault-timeout.type";
|
||||
|
||||
import { VAULT_TIMEOUT, VAULT_TIMEOUT_ACTION } from "./vault-timeout-settings.state";
|
||||
|
||||
@@ -49,6 +55,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
private stateProvider: StateProvider,
|
||||
private logService: LogService,
|
||||
private defaultVaultTimeout: VaultTimeout,
|
||||
private sessionTimeoutTypeService: SessionTimeoutTypeService,
|
||||
) {}
|
||||
|
||||
async setVaultTimeoutOptions(
|
||||
@@ -131,11 +138,25 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
|
||||
return combineLatest([
|
||||
this.stateProvider.getUserState$(VAULT_TIMEOUT, userId),
|
||||
this.getMaxVaultTimeoutPolicyByUserId$(userId),
|
||||
this.getMaxSessionTimeoutPolicyDataByUserId$(userId),
|
||||
]).pipe(
|
||||
switchMap(([currentVaultTimeout, maxVaultTimeoutPolicy]) => {
|
||||
return from(this.determineVaultTimeout(currentVaultTimeout, maxVaultTimeoutPolicy)).pipe(
|
||||
switchMap(([currentVaultTimeout, maxSessionTimeoutPolicyData]) => {
|
||||
this.logService.debug(
|
||||
"[VaultTimeoutSettingsService] Current vault timeout is %o for user id %s, max session policy %o",
|
||||
currentVaultTimeout,
|
||||
userId,
|
||||
maxSessionTimeoutPolicyData,
|
||||
);
|
||||
return from(
|
||||
this.determineVaultTimeout(currentVaultTimeout, maxSessionTimeoutPolicyData),
|
||||
).pipe(
|
||||
tap((vaultTimeout: VaultTimeout) => {
|
||||
this.logService.debug(
|
||||
"[VaultTimeoutSettingsService] Determined vault timeout is %o for user id %s",
|
||||
vaultTimeout,
|
||||
userId,
|
||||
);
|
||||
|
||||
// As a side effect, set the new value determined by determineVaultTimeout into state if it's different from the current
|
||||
if (vaultTimeout !== currentVaultTimeout) {
|
||||
return this.stateProvider.setUserState(VAULT_TIMEOUT, vaultTimeout, userId);
|
||||
@@ -155,28 +176,63 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
|
||||
private async determineVaultTimeout(
|
||||
currentVaultTimeout: VaultTimeout | null,
|
||||
maxVaultTimeoutPolicy: Policy | null,
|
||||
maxSessionTimeoutPolicyData: MaximumSessionTimeoutPolicyData | null,
|
||||
): Promise<VaultTimeout | null> {
|
||||
// if current vault timeout is null, apply the client specific default
|
||||
currentVaultTimeout = currentVaultTimeout ?? this.defaultVaultTimeout;
|
||||
|
||||
// If no policy applies, return the current vault timeout
|
||||
if (!maxVaultTimeoutPolicy) {
|
||||
if (maxSessionTimeoutPolicyData == null) {
|
||||
return currentVaultTimeout;
|
||||
}
|
||||
|
||||
// User is subject to a max vault timeout policy
|
||||
const maxVaultTimeoutPolicyData = maxVaultTimeoutPolicy.data;
|
||||
|
||||
// If the current vault timeout is not numeric, change it to the policy compliant value
|
||||
if (typeof currentVaultTimeout === "string") {
|
||||
return maxVaultTimeoutPolicyData.minutes;
|
||||
switch (maxSessionTimeoutPolicyData.type) {
|
||||
case "immediately":
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(
|
||||
VaultTimeoutNumberType.Immediately,
|
||||
);
|
||||
case "custom":
|
||||
case null:
|
||||
case undefined:
|
||||
if (currentVaultTimeout === VaultTimeoutNumberType.Immediately) {
|
||||
return currentVaultTimeout;
|
||||
}
|
||||
if (isVaultTimeoutTypeNumeric(currentVaultTimeout)) {
|
||||
return Math.min(currentVaultTimeout as number, maxSessionTimeoutPolicyData.minutes);
|
||||
}
|
||||
return maxSessionTimeoutPolicyData.minutes;
|
||||
case "onSystemLock":
|
||||
if (
|
||||
currentVaultTimeout === VaultTimeoutStringType.Never ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnRestart ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnLocked ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnIdle ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnSleep
|
||||
) {
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(
|
||||
VaultTimeoutStringType.OnLocked,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "onAppRestart":
|
||||
if (
|
||||
currentVaultTimeout === VaultTimeoutStringType.Never ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnLocked ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnIdle ||
|
||||
currentVaultTimeout === VaultTimeoutStringType.OnSleep
|
||||
) {
|
||||
return VaultTimeoutStringType.OnRestart;
|
||||
}
|
||||
break;
|
||||
case "never":
|
||||
if (currentVaultTimeout === VaultTimeoutStringType.Never) {
|
||||
return await this.sessionTimeoutTypeService.getOrPromoteToAvailable(
|
||||
VaultTimeoutStringType.Never,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// For numeric vault timeouts, ensure they are smaller than maximum allowed value according to policy
|
||||
const policyCompliantTimeout = Math.min(currentVaultTimeout, maxVaultTimeoutPolicyData.minutes);
|
||||
|
||||
return policyCompliantTimeout;
|
||||
return currentVaultTimeout;
|
||||
}
|
||||
|
||||
private async setVaultTimeoutAction(userId: UserId, action: VaultTimeoutAction): Promise<void> {
|
||||
@@ -198,14 +254,14 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
|
||||
return combineLatest([
|
||||
this.stateProvider.getUserState$(VAULT_TIMEOUT_ACTION, userId),
|
||||
this.getMaxVaultTimeoutPolicyByUserId$(userId),
|
||||
this.getMaxSessionTimeoutPolicyDataByUserId$(userId),
|
||||
]).pipe(
|
||||
switchMap(([currentVaultTimeoutAction, maxVaultTimeoutPolicy]) => {
|
||||
switchMap(([currentVaultTimeoutAction, maxSessionTimeoutPolicyData]) => {
|
||||
return from(
|
||||
this.determineVaultTimeoutAction(
|
||||
userId,
|
||||
currentVaultTimeoutAction,
|
||||
maxVaultTimeoutPolicy,
|
||||
maxSessionTimeoutPolicyData,
|
||||
),
|
||||
).pipe(
|
||||
tap((vaultTimeoutAction: VaultTimeoutAction) => {
|
||||
@@ -235,7 +291,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
private async determineVaultTimeoutAction(
|
||||
userId: string,
|
||||
currentVaultTimeoutAction: VaultTimeoutAction | null,
|
||||
maxVaultTimeoutPolicy: Policy | null,
|
||||
maxSessionTimeoutPolicyData: MaximumSessionTimeoutPolicyData | null,
|
||||
): Promise<VaultTimeoutAction> {
|
||||
const availableVaultTimeoutActions = await this.getAvailableVaultTimeoutActions(userId);
|
||||
if (availableVaultTimeoutActions.length === 1) {
|
||||
@@ -243,11 +299,13 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
}
|
||||
|
||||
if (
|
||||
maxVaultTimeoutPolicy?.data?.action &&
|
||||
availableVaultTimeoutActions.includes(maxVaultTimeoutPolicy.data.action)
|
||||
maxSessionTimeoutPolicyData?.action &&
|
||||
availableVaultTimeoutActions.includes(
|
||||
maxSessionTimeoutPolicyData.action as VaultTimeoutAction,
|
||||
)
|
||||
) {
|
||||
// return policy defined vault timeout action
|
||||
return maxVaultTimeoutPolicy.data.action;
|
||||
// return policy defined session timeout action
|
||||
return maxSessionTimeoutPolicyData.action as VaultTimeoutAction;
|
||||
}
|
||||
|
||||
// No policy applies from here on
|
||||
@@ -262,14 +320,17 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA
|
||||
return currentVaultTimeoutAction;
|
||||
}
|
||||
|
||||
private getMaxVaultTimeoutPolicyByUserId$(userId: UserId): Observable<Policy | null> {
|
||||
private getMaxSessionTimeoutPolicyDataByUserId$(
|
||||
userId: UserId,
|
||||
): Observable<MaximumSessionTimeoutPolicyData | null> {
|
||||
if (!userId) {
|
||||
throw new Error("User id required. Cannot get max vault timeout policy.");
|
||||
throw new Error("User id required. Cannot get max session timeout policy.");
|
||||
}
|
||||
|
||||
return this.policyService
|
||||
.policiesByType$(PolicyType.MaximumVaultTimeout, userId)
|
||||
.pipe(getFirstPolicy);
|
||||
return this.policyService.policiesByType$(PolicyType.MaximumVaultTimeout, userId).pipe(
|
||||
getFirstPolicy,
|
||||
map((policy) => (policy?.data ?? null) as MaximumSessionTimeoutPolicyData | null),
|
||||
);
|
||||
}
|
||||
|
||||
private async getAvailableVaultTimeoutActions(userId?: string): Promise<VaultTimeoutAction[]> {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
||||
|
||||
export interface MaximumVaultTimeoutPolicyData {
|
||||
minutes: number;
|
||||
action?: VaultTimeoutAction;
|
||||
}
|
||||
@@ -5,13 +5,25 @@ export const VaultTimeoutStringType = {
|
||||
OnLocked: "onLocked", // -2
|
||||
OnSleep: "onSleep", // -3
|
||||
OnIdle: "onIdle", // -4
|
||||
Custom: "custom", // -100
|
||||
} as const;
|
||||
|
||||
export const VaultTimeoutNumberType = {
|
||||
Immediately: 0,
|
||||
OnMinute: 1,
|
||||
EightHours: 480,
|
||||
} as const;
|
||||
|
||||
export type VaultTimeout =
|
||||
| number // 0 or positive numbers only
|
||||
| (typeof VaultTimeoutNumberType)[keyof typeof VaultTimeoutNumberType]
|
||||
| number // 0 or positive numbers (in minutes). See VaultTimeoutNumberType for common numeric presets
|
||||
| (typeof VaultTimeoutStringType)[keyof typeof VaultTimeoutStringType];
|
||||
|
||||
export interface VaultTimeoutOption {
|
||||
name: string;
|
||||
value: VaultTimeout;
|
||||
}
|
||||
|
||||
export function isVaultTimeoutTypeNumeric(timeout: VaultTimeout): boolean {
|
||||
return typeof timeout === "number";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user