import { defer } from "rxjs"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "../../abstractions/vault-timeout/vault-timeout-settings.service"; import { PolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "../../admin-console/enums"; import { TokenService } from "../../auth/abstractions/token.service"; import { UserVerificationService } from "../../auth/abstractions/user-verification/user-verification.service.abstraction"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { StateService } from "../../platform/abstractions/state.service"; /** * - DISABLED: No Pin set * - PERSISTENT: Pin is set and survives client reset * - TRANSIENT: Pin is set and requires password unlock after client reset */ export type PinLockType = "DISABLED" | "PERSISTANT" | "TRANSIENT"; export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceAbstraction { constructor( private cryptoService: CryptoService, private tokenService: TokenService, private policyService: PolicyService, private stateService: StateService, private userVerificationService: UserVerificationService, ) {} async setVaultTimeoutOptions(timeout: number, action: VaultTimeoutAction): Promise { // We swap these tokens from being on disk for lock actions, and in memory for logout actions // Get them here to set them to their new location after changing the timeout action and clearing if needed const token = await this.tokenService.getToken(); const refreshToken = await this.tokenService.getRefreshToken(); const clientId = await this.tokenService.getClientId(); const clientSecret = await this.tokenService.getClientSecret(); await this.stateService.setVaultTimeout(timeout); const currentAction = await this.stateService.getVaultTimeoutAction(); if ( (timeout != null || timeout === 0) && action === VaultTimeoutAction.LogOut && action !== currentAction ) { // if we have a vault timeout and the action is log out, reset tokens await this.tokenService.clearToken(); } await this.stateService.setVaultTimeoutAction(action); await this.tokenService.setToken(token); await this.tokenService.setRefreshToken(refreshToken); await this.tokenService.setClientId(clientId); await this.tokenService.setClientSecret(clientSecret); await this.cryptoService.refreshAdditionalKeys(); } availableVaultTimeoutActions$(userId?: string) { return defer(() => this.getAvailableVaultTimeoutActions(userId)); } async isPinLockSet(userId?: string): Promise { // we can't check the protected pin for both because old accounts only // used it for MP on Restart const pinIsEnabled = !!(await this.stateService.getProtectedPin({ userId })); const aUserKeyPinIsSet = !!(await this.stateService.getPinKeyEncryptedUserKey({ userId })); const anOldUserKeyPinIsSet = !!(await this.stateService.getEncryptedPinProtected({ userId })); if (aUserKeyPinIsSet || anOldUserKeyPinIsSet) { return "PERSISTANT"; } else if (pinIsEnabled && !aUserKeyPinIsSet && !anOldUserKeyPinIsSet) { return "TRANSIENT"; } else { return "DISABLED"; } } async isBiometricLockSet(userId?: string): Promise { return await this.stateService.getBiometricUnlock({ userId }); } async getVaultTimeout(userId?: string): Promise { const vaultTimeout = await this.stateService.getVaultTimeout({ userId }); if ( await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId) ) { const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId); // Remove negative values, and ensure it's smaller than maximum allowed value according to policy let timeout = Math.min(vaultTimeout, policy[0].data.minutes); if (vaultTimeout == null || timeout < 0) { timeout = policy[0].data.minutes; } // TODO @jlf0dev: Can we move this somwhere else? Maybe add it to the initialization process? // ( Apparently I'm the one that reviewed the original PR that added this :) ) // We really shouldn't need to set the value here, but multiple services relies on this value being correct. if (vaultTimeout !== timeout) { await this.stateService.setVaultTimeout(timeout, { userId }); } return timeout; } return vaultTimeout; } vaultTimeoutAction$(userId?: string) { return defer(() => this.getVaultTimeoutAction(userId)); } async getVaultTimeoutAction(userId?: string): Promise { const availableActions = await this.getAvailableVaultTimeoutActions(); if (availableActions.length === 1) { return availableActions[0]; } const vaultTimeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId }); if ( await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId) ) { const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId); const action = policy[0].data.action; // We really shouldn't need to set the value here, but multiple services relies on this value being correct. if (action && vaultTimeoutAction !== action) { await this.stateService.setVaultTimeoutAction(action, { userId: userId }); } if (action && availableActions.includes(action)) { return action; } } if (vaultTimeoutAction == null) { // Depends on whether or not the user has a master password const defaultValue = (await this.userVerificationService.hasMasterPassword()) ? VaultTimeoutAction.Lock : VaultTimeoutAction.LogOut; // We really shouldn't need to set the value here, but multiple services relies on this value being correct. await this.stateService.setVaultTimeoutAction(defaultValue, { userId: userId }); return defaultValue; } return vaultTimeoutAction === VaultTimeoutAction.LogOut ? VaultTimeoutAction.LogOut : VaultTimeoutAction.Lock; } private async getAvailableVaultTimeoutActions(userId?: string): Promise { const availableActions = [VaultTimeoutAction.LogOut]; const canLock = (await this.userVerificationService.hasMasterPassword(userId)) || (await this.isPinLockSet(userId)) !== "DISABLED" || (await this.isBiometricLockSet(userId)); if (canLock) { availableActions.push(VaultTimeoutAction.Lock); } return availableActions; } async clear(userId?: string): Promise { await this.stateService.setEverBeenUnlocked(false, { userId: userId }); await this.cryptoService.clearPinKeys(userId); } }