mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[PM-25206] Inject service instead of passing as param (#16801)
* Inject service instead of passing as param * [PM-25206] Move locking logic to LockService (#16802) * Move locking logic to lock service * Fix tests * Fix CLI * Fix test * FIx safari build * Update call to lock service * Remove locked callback * Clean up lock service logic * Add tests * Fix cli build * Add extension lock service * Fix cli build * Fix build * Undo ac changes * Undo ac changes * Run prettier * Fix build * Remove duplicate call * [PM-25206] Remove VaultTimeoutService lock logic (#16804) * Move consumers off of vaulttimeoutsettingsservice lock * Fix build * Fix build * Fix build * Fix firefox build * Fix test * Fix ts strict errors * Fix ts strict error * Undo AC changes * Cleanup * Fix * Fix missing service
This commit is contained in:
@@ -122,10 +122,8 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
async lock(userId: string) {
|
async lock(userId: string) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
await this.vaultTimeoutService.lock(userId);
|
await this.lockService.lock(userId as UserId);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await this.router.navigate(["lock"]);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
this.router.navigate(["lock"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async lockAll() {
|
async lockAll() {
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ import {
|
|||||||
MessageListener,
|
MessageListener,
|
||||||
MessageSender,
|
MessageSender,
|
||||||
} from "@bitwarden/common/platform/messaging";
|
} from "@bitwarden/common/platform/messaging";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { newGuid } from "@bitwarden/guid";
|
||||||
|
import { UserId } from "@bitwarden/user-core";
|
||||||
|
|
||||||
const LOCK_ALL_FINISHED = new CommandDefinition<{ requestId: string }>("lockAllFinished");
|
const LOCK_ALL_FINISHED = new CommandDefinition<{ requestId: string }>("lockAllFinished");
|
||||||
const LOCK_ALL = new CommandDefinition<{ requestId: string }>("lockAll");
|
const LOCK_ALL = new CommandDefinition<{ requestId: string }>("lockAll");
|
||||||
|
const LOCK_USER_FINISHED = new CommandDefinition<{ requestId: string }>("lockUserFinished");
|
||||||
|
const LOCK_USER = new CommandDefinition<{ requestId: string; userId: UserId }>("lockUser");
|
||||||
|
|
||||||
export class ForegroundLockService implements LockService {
|
export class ForegroundLockService implements LockService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -18,7 +21,7 @@ export class ForegroundLockService implements LockService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async lockAll(): Promise<void> {
|
async lockAll(): Promise<void> {
|
||||||
const requestId = Utils.newGuid();
|
const requestId = newGuid();
|
||||||
const finishMessage = firstValueFrom(
|
const finishMessage = firstValueFrom(
|
||||||
this.messageListener
|
this.messageListener
|
||||||
.messages$(LOCK_ALL_FINISHED)
|
.messages$(LOCK_ALL_FINISHED)
|
||||||
@@ -29,4 +32,19 @@ export class ForegroundLockService implements LockService {
|
|||||||
|
|
||||||
await finishMessage;
|
await finishMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async lock(userId: UserId): Promise<void> {
|
||||||
|
const requestId = newGuid();
|
||||||
|
const finishMessage = firstValueFrom(
|
||||||
|
this.messageListener
|
||||||
|
.messages$(LOCK_USER_FINISHED)
|
||||||
|
.pipe(filter((m) => m.requestId === requestId)),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.messageSender.send(LOCK_USER, { requestId, userId });
|
||||||
|
|
||||||
|
await finishMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
async runPlatformOnLockActions(): Promise<void> {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { mock } from "jest-mock-extended";
|
|||||||
import { firstValueFrom, of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
|
import { LockService } from "@bitwarden/auth/common";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
@@ -16,7 +17,6 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
|
|||||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||||
import {
|
import {
|
||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
VaultTimeoutService,
|
|
||||||
VaultTimeoutStringType,
|
VaultTimeoutStringType,
|
||||||
VaultTimeoutAction,
|
VaultTimeoutAction,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
@@ -63,6 +63,7 @@ describe("AccountSecurityComponent", () => {
|
|||||||
const validationService = mock<ValidationService>();
|
const validationService = mock<ValidationService>();
|
||||||
const dialogService = mock<DialogService>();
|
const dialogService = mock<DialogService>();
|
||||||
const platformUtilsService = mock<PlatformUtilsService>();
|
const platformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
const lockService = mock<LockService>();
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
@@ -83,7 +84,6 @@ describe("AccountSecurityComponent", () => {
|
|||||||
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() },
|
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() },
|
||||||
{ provide: ToastService, useValue: mock<ToastService>() },
|
{ provide: ToastService, useValue: mock<ToastService>() },
|
||||||
{ provide: UserVerificationService, useValue: mock<UserVerificationService>() },
|
{ provide: UserVerificationService, useValue: mock<UserVerificationService>() },
|
||||||
{ provide: VaultTimeoutService, useValue: mock<VaultTimeoutService>() },
|
|
||||||
{ provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService },
|
{ provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService },
|
||||||
{ provide: StateProvider, useValue: mock<StateProvider>() },
|
{ provide: StateProvider, useValue: mock<StateProvider>() },
|
||||||
{ provide: CipherService, useValue: mock<CipherService>() },
|
{ provide: CipherService, useValue: mock<CipherService>() },
|
||||||
@@ -92,6 +92,7 @@ describe("AccountSecurityComponent", () => {
|
|||||||
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
|
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
|
||||||
{ provide: CollectionService, useValue: mock<CollectionService>() },
|
{ provide: CollectionService, useValue: mock<CollectionService>() },
|
||||||
{ provide: ValidationService, useValue: validationService },
|
{ provide: ValidationService, useValue: validationService },
|
||||||
|
{ provide: LockService, useValue: lockService },
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideComponent(AccountSecurityComponent, {
|
.overrideComponent(AccountSecurityComponent, {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|||||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||||
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
||||||
import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular";
|
import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular";
|
||||||
|
import { LockService } from "@bitwarden/auth/common";
|
||||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
|
import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service";
|
||||||
@@ -36,7 +37,6 @@ import {
|
|||||||
VaultTimeout,
|
VaultTimeout,
|
||||||
VaultTimeoutAction,
|
VaultTimeoutAction,
|
||||||
VaultTimeoutOption,
|
VaultTimeoutOption,
|
||||||
VaultTimeoutService,
|
|
||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
VaultTimeoutStringType,
|
VaultTimeoutStringType,
|
||||||
} from "@bitwarden/common/key-management/vault-timeout";
|
} from "@bitwarden/common/key-management/vault-timeout";
|
||||||
@@ -143,7 +143,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
|
|||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
private lockService: LockService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
public messagingService: MessagingService,
|
public messagingService: MessagingService,
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
@@ -695,7 +695,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async lock() {
|
async lock() {
|
||||||
await this.vaultTimeoutService.lock();
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
await this.lockService.lock(activeUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logOut() {
|
async logOut() {
|
||||||
|
|||||||
58
apps/browser/src/auth/services/extension-lock.service.ts
Normal file
58
apps/browser/src/auth/services/extension-lock.service.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { DefaultLockService, LogoutService } from "@bitwarden/auth/common";
|
||||||
|
import MainBackground from "@bitwarden/browser/background/main.background";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
|
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
|
||||||
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
|
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||||
|
import { BiometricsService, KeyService } from "@bitwarden/key-management";
|
||||||
|
import { LogService } from "@bitwarden/logging";
|
||||||
|
import { StateEventRunnerService } from "@bitwarden/state";
|
||||||
|
|
||||||
|
export class ExtensionLockService extends DefaultLockService {
|
||||||
|
constructor(
|
||||||
|
accountService: AccountService,
|
||||||
|
biometricService: BiometricsService,
|
||||||
|
vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
|
logoutService: LogoutService,
|
||||||
|
messagingService: MessagingService,
|
||||||
|
searchService: SearchService,
|
||||||
|
folderService: FolderService,
|
||||||
|
masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
|
stateEventRunnerService: StateEventRunnerService,
|
||||||
|
cipherService: CipherService,
|
||||||
|
authService: AuthService,
|
||||||
|
systemService: SystemService,
|
||||||
|
processReloadService: ProcessReloadServiceAbstraction,
|
||||||
|
logService: LogService,
|
||||||
|
keyService: KeyService,
|
||||||
|
private readonly main: MainBackground,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
accountService,
|
||||||
|
biometricService,
|
||||||
|
vaultTimeoutSettingsService,
|
||||||
|
logoutService,
|
||||||
|
messagingService,
|
||||||
|
searchService,
|
||||||
|
folderService,
|
||||||
|
masterPasswordService,
|
||||||
|
stateEventRunnerService,
|
||||||
|
cipherService,
|
||||||
|
authService,
|
||||||
|
systemService,
|
||||||
|
processReloadService,
|
||||||
|
logService,
|
||||||
|
keyService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async runPlatformOnLockActions(): Promise<void> {
|
||||||
|
await this.main.refreshMenu(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
|
import { LockService } from "@bitwarden/auth/common";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { ExtensionCommand, ExtensionCommandType } from "@bitwarden/common/autofill/constants";
|
import { ExtensionCommand, ExtensionCommandType } from "@bitwarden/common/autofill/constants";
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
|
||||||
// FIXME (PM-22628): Popup imports are forbidden in background
|
// FIXME (PM-22628): Popup imports are forbidden in background
|
||||||
@@ -21,9 +25,10 @@ export default class CommandsBackground {
|
|||||||
constructor(
|
constructor(
|
||||||
private main: MainBackground,
|
private main: MainBackground,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private generatePasswordToClipboard: () => Promise<void>,
|
private generatePasswordToClipboard: () => Promise<void>,
|
||||||
|
private accountService: AccountService,
|
||||||
|
private lockService: LockService,
|
||||||
) {
|
) {
|
||||||
this.isSafari = this.platformUtilsService.isSafari();
|
this.isSafari = this.platformUtilsService.isSafari();
|
||||||
this.isVivaldi = this.platformUtilsService.isVivaldi();
|
this.isVivaldi = this.platformUtilsService.isVivaldi();
|
||||||
@@ -72,9 +77,11 @@ export default class CommandsBackground {
|
|||||||
case "open_popup":
|
case "open_popup":
|
||||||
await this.openPopup();
|
await this.openPopup();
|
||||||
break;
|
break;
|
||||||
case "lock_vault":
|
case "lock_vault": {
|
||||||
await this.vaultTimeoutService.lock();
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
|
await this.lockService.lock(activeUserId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { LogoutService } from "@bitwarden/auth/common";
|
import { LockService, LogoutService } from "@bitwarden/auth/common";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import {
|
import {
|
||||||
VaultTimeoutAction,
|
VaultTimeoutAction,
|
||||||
@@ -23,6 +23,7 @@ export default class IdleBackground {
|
|||||||
private serverNotificationsService: ServerNotificationsService,
|
private serverNotificationsService: ServerNotificationsService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
|
private lockService: LockService,
|
||||||
private logoutService: LogoutService,
|
private logoutService: LogoutService,
|
||||||
) {
|
) {
|
||||||
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
this.idle = chrome.idle || (browser != null ? browser.idle : null);
|
||||||
@@ -66,7 +67,7 @@ export default class IdleBackground {
|
|||||||
if (action === VaultTimeoutAction.LogOut) {
|
if (action === VaultTimeoutAction.LogOut) {
|
||||||
await this.logoutService.logout(userId as UserId, "vaultTimeout");
|
await this.logoutService.logout(userId as UserId, "vaultTimeout");
|
||||||
} else {
|
} else {
|
||||||
await this.vaultTimeoutService.lock(userId);
|
await this.lockService.lock(userId as UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ import {
|
|||||||
AuthRequestService,
|
AuthRequestService,
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
DefaultAuthRequestApiService,
|
DefaultAuthRequestApiService,
|
||||||
DefaultLockService,
|
|
||||||
DefaultLogoutService,
|
DefaultLogoutService,
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
LockService,
|
||||||
LoginEmailServiceAbstraction,
|
LoginEmailServiceAbstraction,
|
||||||
LogoutReason,
|
LogoutReason,
|
||||||
UserDecryptionOptionsService,
|
UserDecryptionOptionsService,
|
||||||
@@ -270,6 +270,7 @@ import {
|
|||||||
} from "@bitwarden/vault-export-core";
|
} from "@bitwarden/vault-export-core";
|
||||||
|
|
||||||
import { AuthStatusBadgeUpdaterService } from "../auth/services/auth-status-badge-updater.service";
|
import { AuthStatusBadgeUpdaterService } from "../auth/services/auth-status-badge-updater.service";
|
||||||
|
import { ExtensionLockService } from "../auth/services/extension-lock.service";
|
||||||
import { OverlayNotificationsBackground as OverlayNotificationsBackgroundInterface } from "../autofill/background/abstractions/overlay-notifications.background";
|
import { OverlayNotificationsBackground as OverlayNotificationsBackgroundInterface } from "../autofill/background/abstractions/overlay-notifications.background";
|
||||||
import { OverlayBackground as OverlayBackgroundInterface } from "../autofill/background/abstractions/overlay.background";
|
import { OverlayBackground as OverlayBackgroundInterface } from "../autofill/background/abstractions/overlay.background";
|
||||||
import { AutoSubmitLoginBackground } from "../autofill/background/auto-submit-login.background";
|
import { AutoSubmitLoginBackground } from "../autofill/background/auto-submit-login.background";
|
||||||
@@ -363,6 +364,7 @@ export default class MainBackground {
|
|||||||
folderService: InternalFolderServiceAbstraction;
|
folderService: InternalFolderServiceAbstraction;
|
||||||
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
|
userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction;
|
||||||
collectionService: CollectionService;
|
collectionService: CollectionService;
|
||||||
|
lockService: LockService;
|
||||||
vaultTimeoutService?: VaultTimeoutService;
|
vaultTimeoutService?: VaultTimeoutService;
|
||||||
vaultTimeoutSettingsService: VaultTimeoutSettingsService;
|
vaultTimeoutSettingsService: VaultTimeoutSettingsService;
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction;
|
passwordGenerationService: PasswordGenerationServiceAbstraction;
|
||||||
@@ -496,16 +498,6 @@ export default class MainBackground {
|
|||||||
private phishingDataService: PhishingDataService;
|
private phishingDataService: PhishingDataService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Services
|
|
||||||
const lockedCallback = async (userId: UserId) => {
|
|
||||||
await this.refreshMenu(true);
|
|
||||||
if (this.systemService != null) {
|
|
||||||
await this.systemService.clearPendingClipboard();
|
|
||||||
await this.biometricsService.setShouldAutopromptNow(false);
|
|
||||||
await this.processReloadService.startProcessReload(this.authService);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) =>
|
const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) =>
|
||||||
await this.logout(logoutReason, userId);
|
await this.logout(logoutReason, userId);
|
||||||
|
|
||||||
@@ -987,27 +979,6 @@ export default class MainBackground {
|
|||||||
this.restrictedItemTypesService,
|
this.restrictedItemTypesService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const logoutService = new DefaultLogoutService(this.messagingService);
|
|
||||||
this.vaultTimeoutService = new VaultTimeoutService(
|
|
||||||
this.accountService,
|
|
||||||
this.masterPasswordService,
|
|
||||||
this.cipherService,
|
|
||||||
this.folderService,
|
|
||||||
this.collectionService,
|
|
||||||
this.platformUtilsService,
|
|
||||||
this.messagingService,
|
|
||||||
this.searchService,
|
|
||||||
this.stateService,
|
|
||||||
this.tokenService,
|
|
||||||
this.authService,
|
|
||||||
this.vaultTimeoutSettingsService,
|
|
||||||
this.stateEventRunnerService,
|
|
||||||
this.taskSchedulerService,
|
|
||||||
this.logService,
|
|
||||||
this.biometricsService,
|
|
||||||
lockedCallback,
|
|
||||||
logoutService,
|
|
||||||
);
|
|
||||||
this.containerService = new ContainerService(this.keyService, this.encryptService);
|
this.containerService = new ContainerService(this.keyService, this.encryptService);
|
||||||
|
|
||||||
this.sendStateProvider = new SendStateProvider(this.stateProvider);
|
this.sendStateProvider = new SendStateProvider(this.stateProvider);
|
||||||
@@ -1271,6 +1242,7 @@ export default class MainBackground {
|
|||||||
this.biometricStateService,
|
this.biometricStateService,
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.logService,
|
this.logService,
|
||||||
|
this.authService,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
@@ -1284,7 +1256,36 @@ export default class MainBackground {
|
|||||||
this.authService,
|
this.authService,
|
||||||
);
|
);
|
||||||
|
|
||||||
const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService);
|
const logoutService = new DefaultLogoutService(this.messagingService);
|
||||||
|
this.lockService = new ExtensionLockService(
|
||||||
|
this.accountService,
|
||||||
|
this.biometricsService,
|
||||||
|
this.vaultTimeoutSettingsService,
|
||||||
|
logoutService,
|
||||||
|
this.messagingService,
|
||||||
|
this.searchService,
|
||||||
|
this.folderService,
|
||||||
|
this.masterPasswordService,
|
||||||
|
this.stateEventRunnerService,
|
||||||
|
this.cipherService,
|
||||||
|
this.authService,
|
||||||
|
this.systemService,
|
||||||
|
this.processReloadService,
|
||||||
|
this.logService,
|
||||||
|
this.keyService,
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.vaultTimeoutService = new VaultTimeoutService(
|
||||||
|
this.accountService,
|
||||||
|
this.platformUtilsService,
|
||||||
|
this.authService,
|
||||||
|
this.vaultTimeoutSettingsService,
|
||||||
|
this.taskSchedulerService,
|
||||||
|
this.logService,
|
||||||
|
this.lockService,
|
||||||
|
logoutService,
|
||||||
|
);
|
||||||
|
|
||||||
this.runtimeBackground = new RuntimeBackground(
|
this.runtimeBackground = new RuntimeBackground(
|
||||||
this,
|
this,
|
||||||
@@ -1298,7 +1299,7 @@ export default class MainBackground {
|
|||||||
this.configService,
|
this.configService,
|
||||||
messageListener,
|
messageListener,
|
||||||
this.accountService,
|
this.accountService,
|
||||||
lockService,
|
this.lockService,
|
||||||
this.billingAccountProfileStateService,
|
this.billingAccountProfileStateService,
|
||||||
this.browserInitialInstallService,
|
this.browserInitialInstallService,
|
||||||
);
|
);
|
||||||
@@ -1318,9 +1319,10 @@ export default class MainBackground {
|
|||||||
this.commandsBackground = new CommandsBackground(
|
this.commandsBackground = new CommandsBackground(
|
||||||
this,
|
this,
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
this.vaultTimeoutService,
|
|
||||||
this.authService,
|
this.authService,
|
||||||
() => this.generatePasswordToClipboard(),
|
() => this.generatePasswordToClipboard(),
|
||||||
|
this.accountService,
|
||||||
|
this.lockService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.taskService = new DefaultTaskService(
|
this.taskService = new DefaultTaskService(
|
||||||
@@ -1405,6 +1407,7 @@ export default class MainBackground {
|
|||||||
this.serverNotificationsService,
|
this.serverNotificationsService,
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
|
this.lockService,
|
||||||
logoutService,
|
logoutService,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1752,7 +1755,7 @@ export default class MainBackground {
|
|||||||
}
|
}
|
||||||
await this.mainContextMenuHandler?.noAccess();
|
await this.mainContextMenuHandler?.noAccess();
|
||||||
await this.systemService.clearPendingClipboard();
|
await this.systemService.clearPendingClipboard();
|
||||||
await this.processReloadService.startProcessReload(this.authService);
|
await this.processReloadService.startProcessReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async needsStorageReseed(userId: UserId): Promise<boolean> {
|
private async needsStorageReseed(userId: UserId): Promise<boolean> {
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export default class RuntimeBackground {
|
|||||||
this.lockedVaultPendingNotifications.push(msg.data);
|
this.lockedVaultPendingNotifications.push(msg.data);
|
||||||
break;
|
break;
|
||||||
case "lockVault":
|
case "lockVault":
|
||||||
await this.main.vaultTimeoutService.lock(msg.userId);
|
await this.lockService.lock(msg.userId);
|
||||||
break;
|
break;
|
||||||
case "lockAll":
|
case "lockAll":
|
||||||
{
|
{
|
||||||
@@ -265,6 +265,14 @@ export default class RuntimeBackground {
|
|||||||
this.messagingService.send("lockAllFinished", { requestId: msg.requestId });
|
this.messagingService.send("lockAllFinished", { requestId: msg.requestId });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "lockUser":
|
||||||
|
{
|
||||||
|
await this.lockService.lock(msg.userId);
|
||||||
|
this.messagingService.send("lockUserFinished", {
|
||||||
|
requestId: msg.requestId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "logout":
|
case "logout":
|
||||||
await this.main.logout(msg.expired, msg.userId);
|
await this.main.logout(msg.expired, msg.userId);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,15 +2,10 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout/abstractions/vault-timeout.service";
|
import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout/abstractions/vault-timeout.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
|
||||||
|
|
||||||
export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService {
|
export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService {
|
||||||
constructor(protected messagingService: MessagingService) {}
|
constructor(protected messagingService: MessagingService) {}
|
||||||
|
|
||||||
// should only ever run in background
|
// should only ever run in background
|
||||||
async checkVaultTimeout(): Promise<void> {}
|
async checkVaultTimeout(): Promise<void> {}
|
||||||
|
|
||||||
async lock(userId?: UserId): Promise<void> {
|
|
||||||
this.messagingService.send("lockVault", { userId });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
import { firstValueFrom } from "rxjs";
|
||||||
// @ts-strict-ignore
|
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
import { LockService } from "@bitwarden/auth/common";
|
||||||
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
|
|
||||||
import { Response } from "../../models/response";
|
import { Response } from "../../models/response";
|
||||||
import { MessageResponse } from "../../models/response/message.response";
|
import { MessageResponse } from "../../models/response/message.response";
|
||||||
|
|
||||||
export class LockCommand {
|
export class LockCommand {
|
||||||
constructor(private vaultTimeoutService: VaultTimeoutService) {}
|
constructor(
|
||||||
|
private lockService: LockService,
|
||||||
|
private accountService: AccountService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
await this.vaultTimeoutService.lock();
|
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||||
process.env.BW_SESSION = null;
|
await this.lockService.lock(activeUserId);
|
||||||
|
process.env.BW_SESSION = undefined;
|
||||||
const res = new MessageResponse("Your vault is locked.", null);
|
const res = new MessageResponse("Your vault is locked.", null);
|
||||||
return Response.success(res);
|
return Response.success(res);
|
||||||
}
|
}
|
||||||
|
|||||||
10
apps/cli/src/key-management/cli-process-reload.service.ts
Normal file
10
apps/cli/src/key-management/cli-process-reload.service.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI implementation of ProcessReloadServiceAbstraction.
|
||||||
|
* This is NOOP since there is no effective way to process reload the CLI.
|
||||||
|
*/
|
||||||
|
export class CliProcessReloadService extends ProcessReloadServiceAbstraction {
|
||||||
|
async startProcessReload(): Promise<void> {}
|
||||||
|
async cancelProcessReload(): Promise<void> {}
|
||||||
|
}
|
||||||
@@ -160,7 +160,10 @@ export class OssServeConfigurator {
|
|||||||
this.serviceContainer.cipherService,
|
this.serviceContainer.cipherService,
|
||||||
this.serviceContainer.accountService,
|
this.serviceContainer.accountService,
|
||||||
);
|
);
|
||||||
this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService);
|
this.lockCommand = new LockCommand(
|
||||||
|
serviceContainer.lockService,
|
||||||
|
serviceContainer.accountService,
|
||||||
|
);
|
||||||
this.unlockCommand = new UnlockCommand(
|
this.unlockCommand = new UnlockCommand(
|
||||||
this.serviceContainer.accountService,
|
this.serviceContainer.accountService,
|
||||||
this.serviceContainer.masterPasswordService,
|
this.serviceContainer.masterPasswordService,
|
||||||
|
|||||||
10
apps/cli/src/platform/services/cli-system.service.ts
Normal file
10
apps/cli/src/platform/services/cli-system.service.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI implementation of SystemService.
|
||||||
|
* The implementation is NOOP since these functions are meant for GUI clients.
|
||||||
|
*/
|
||||||
|
export class CliSystemService extends SystemService {
|
||||||
|
async clearClipboard(clipboardValue: string, timeoutMs?: number): Promise<void> {}
|
||||||
|
async clearPendingClipboard(): Promise<any> {}
|
||||||
|
}
|
||||||
@@ -250,7 +250,10 @@ export class Program extends BaseProgram {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = new LockCommand(this.serviceContainer.vaultTimeoutService);
|
const command = new LockCommand(
|
||||||
|
this.serviceContainer.lockService,
|
||||||
|
this.serviceContainer.accountService,
|
||||||
|
);
|
||||||
const response = await command.run();
|
const response = await command.run();
|
||||||
this.processResponse(response);
|
this.processResponse(response);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import {
|
|||||||
SsoUrlService,
|
SsoUrlService,
|
||||||
AuthRequestApiServiceAbstraction,
|
AuthRequestApiServiceAbstraction,
|
||||||
DefaultAuthRequestApiService,
|
DefaultAuthRequestApiService,
|
||||||
|
DefaultLockService,
|
||||||
|
DefaultLogoutService,
|
||||||
|
LockService,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
@@ -199,9 +202,11 @@ import {
|
|||||||
} from "@bitwarden/vault-export-core";
|
} from "@bitwarden/vault-export-core";
|
||||||
|
|
||||||
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
|
import { CliBiometricsService } from "../key-management/cli-biometrics-service";
|
||||||
|
import { CliProcessReloadService } from "../key-management/cli-process-reload.service";
|
||||||
import { flagEnabled } from "../platform/flags";
|
import { flagEnabled } from "../platform/flags";
|
||||||
import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service";
|
import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service";
|
||||||
import { CliSdkLoadService } from "../platform/services/cli-sdk-load.service";
|
import { CliSdkLoadService } from "../platform/services/cli-sdk-load.service";
|
||||||
|
import { CliSystemService } from "../platform/services/cli-system.service";
|
||||||
import { ConsoleLogService } from "../platform/services/console-log.service";
|
import { ConsoleLogService } from "../platform/services/console-log.service";
|
||||||
import { I18nService } from "../platform/services/i18n.service";
|
import { I18nService } from "../platform/services/i18n.service";
|
||||||
import { LowdbStorageService } from "../platform/services/lowdb-storage.service";
|
import { LowdbStorageService } from "../platform/services/lowdb-storage.service";
|
||||||
@@ -318,6 +323,7 @@ export class ServiceContainer {
|
|||||||
securityStateService: SecurityStateService;
|
securityStateService: SecurityStateService;
|
||||||
masterPasswordUnlockService: MasterPasswordUnlockService;
|
masterPasswordUnlockService: MasterPasswordUnlockService;
|
||||||
cipherArchiveService: CipherArchiveService;
|
cipherArchiveService: CipherArchiveService;
|
||||||
|
lockService: LockService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
let p = null;
|
let p = null;
|
||||||
@@ -778,9 +784,6 @@ export class ServiceContainer {
|
|||||||
|
|
||||||
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
this.folderApiService = new FolderApiService(this.folderService, this.apiService);
|
||||||
|
|
||||||
const lockedCallback = async (userId: UserId) =>
|
|
||||||
await this.keyService.clearStoredUserKey(userId);
|
|
||||||
|
|
||||||
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
|
||||||
|
|
||||||
this.userVerificationService = new UserVerificationService(
|
this.userVerificationService = new UserVerificationService(
|
||||||
@@ -796,25 +799,35 @@ export class ServiceContainer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const biometricService = new CliBiometricsService();
|
const biometricService = new CliBiometricsService();
|
||||||
|
const logoutService = new DefaultLogoutService(this.messagingService);
|
||||||
|
const processReloadService = new CliProcessReloadService();
|
||||||
|
const systemService = new CliSystemService();
|
||||||
|
this.lockService = new DefaultLockService(
|
||||||
|
this.accountService,
|
||||||
|
biometricService,
|
||||||
|
this.vaultTimeoutSettingsService,
|
||||||
|
logoutService,
|
||||||
|
this.messagingService,
|
||||||
|
this.searchService,
|
||||||
|
this.folderService,
|
||||||
|
this.masterPasswordService,
|
||||||
|
this.stateEventRunnerService,
|
||||||
|
this.cipherService,
|
||||||
|
this.authService,
|
||||||
|
systemService,
|
||||||
|
processReloadService,
|
||||||
|
this.logService,
|
||||||
|
this.keyService,
|
||||||
|
);
|
||||||
|
|
||||||
this.vaultTimeoutService = new DefaultVaultTimeoutService(
|
this.vaultTimeoutService = new DefaultVaultTimeoutService(
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.masterPasswordService,
|
|
||||||
this.cipherService,
|
|
||||||
this.folderService,
|
|
||||||
this.collectionService,
|
|
||||||
this.platformUtilsService,
|
this.platformUtilsService,
|
||||||
this.messagingService,
|
|
||||||
this.searchService,
|
|
||||||
this.stateService,
|
|
||||||
this.tokenService,
|
|
||||||
this.authService,
|
this.authService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
this.stateEventRunnerService,
|
|
||||||
this.taskSchedulerService,
|
this.taskSchedulerService,
|
||||||
this.logService,
|
this.logService,
|
||||||
biometricService,
|
this.lockService,
|
||||||
lockedCallback,
|
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
|
|||||||
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||||
import {
|
import {
|
||||||
DESKTOP_SSO_CALLBACK,
|
DESKTOP_SSO_CALLBACK,
|
||||||
|
LockService,
|
||||||
LogoutReason,
|
LogoutReason,
|
||||||
UserDecryptionOptionsServiceAbstraction,
|
UserDecryptionOptionsServiceAbstraction,
|
||||||
} from "@bitwarden/auth/common";
|
} from "@bitwarden/auth/common";
|
||||||
@@ -195,6 +196,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private pinService: PinServiceAbstraction,
|
private pinService: PinServiceAbstraction,
|
||||||
private readonly tokenService: TokenService,
|
private readonly tokenService: TokenService,
|
||||||
private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy,
|
private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy,
|
||||||
|
private readonly lockService: LockService,
|
||||||
) {
|
) {
|
||||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||||
|
|
||||||
@@ -245,7 +247,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.updateAppMenu();
|
this.updateAppMenu();
|
||||||
await this.systemService.clearPendingClipboard();
|
await this.systemService.clearPendingClipboard();
|
||||||
await this.processReloadService.startProcessReload(this.authService);
|
await this.processReloadService.startProcessReload();
|
||||||
break;
|
break;
|
||||||
case "authBlocked":
|
case "authBlocked":
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
@@ -258,21 +260,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
break;
|
break;
|
||||||
case "lockVault":
|
case "lockVault":
|
||||||
await this.vaultTimeoutService.lock(message.userId);
|
await this.lockService.lock(message.userId);
|
||||||
break;
|
break;
|
||||||
case "lockAllVaults": {
|
case "lockAllVaults": {
|
||||||
const currentUser = await firstValueFrom(
|
await this.lockService.lockAll();
|
||||||
this.accountService.activeAccount$.pipe(map((a) => a.id)),
|
|
||||||
);
|
|
||||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
|
||||||
await this.vaultTimeoutService.lock(currentUser);
|
|
||||||
for (const account of Object.keys(accounts)) {
|
|
||||||
if (account === currentUser) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.vaultTimeoutService.lock(account);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "locked":
|
case "locked":
|
||||||
@@ -286,12 +277,12 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
await this.updateAppMenu();
|
await this.updateAppMenu();
|
||||||
await this.systemService.clearPendingClipboard();
|
await this.systemService.clearPendingClipboard();
|
||||||
await this.processReloadService.startProcessReload(this.authService);
|
await this.processReloadService.startProcessReload();
|
||||||
break;
|
break;
|
||||||
case "startProcessReload":
|
case "startProcessReload":
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.processReloadService.startProcessReload(this.authService);
|
this.processReloadService.startProcessReload();
|
||||||
break;
|
break;
|
||||||
case "cancelProcessReload":
|
case "cancelProcessReload":
|
||||||
this.processReloadService.cancelProcessReload();
|
this.processReloadService.cancelProcessReload();
|
||||||
@@ -812,11 +803,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
const options = await this.getVaultTimeoutOptions(userId);
|
const options = await this.getVaultTimeoutOptions(userId);
|
||||||
if (options[0] === timeout) {
|
if (options[0] === timeout) {
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
options[1] === "logOut"
|
options[1] === "logOut"
|
||||||
? this.logOut("vaultTimeout", userId as UserId)
|
? await this.logOut("vaultTimeout", userId as UserId)
|
||||||
: await this.vaultTimeoutService.lock(userId);
|
: await this.lockService.lock(userId as UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,6 +262,7 @@ const safeProviders: SafeProvider[] = [
|
|||||||
BiometricStateService,
|
BiometricStateService,
|
||||||
AccountServiceAbstraction,
|
AccountServiceAbstraction,
|
||||||
LogService,
|
LogService,
|
||||||
|
AuthServiceAbstraction,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Subject, filter, firstValueFrom, map, timeout } from "rxjs";
|
|||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||||
import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
|
import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
|
||||||
|
import { LockService } from "@bitwarden/auth/common";
|
||||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
@@ -16,7 +17,6 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
|
|||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@@ -58,8 +58,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService,
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private vaultTimeoutService: VaultTimeoutService,
|
|
||||||
private keyService: KeyService,
|
private keyService: KeyService,
|
||||||
|
private lockService: LockService,
|
||||||
private collectionService: CollectionService,
|
private collectionService: CollectionService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private serverNotificationsService: ServerNotificationsService,
|
private serverNotificationsService: ServerNotificationsService,
|
||||||
@@ -113,11 +113,13 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
// note: the message.logoutReason isn't consumed anymore because of the process reload clearing any toasts.
|
// note: the message.logoutReason isn't consumed anymore because of the process reload clearing any toasts.
|
||||||
await this.logOut(message.redirect);
|
await this.logOut(message.redirect);
|
||||||
break;
|
break;
|
||||||
case "lockVault":
|
case "lockVault": {
|
||||||
await this.vaultTimeoutService.lock();
|
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||||
|
await this.lockService.lock(userId);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "locked":
|
case "locked":
|
||||||
await this.processReloadService.startProcessReload(this.authService);
|
await this.processReloadService.startProcessReload();
|
||||||
break;
|
break;
|
||||||
case "lockedUrl":
|
case "lockedUrl":
|
||||||
break;
|
break;
|
||||||
@@ -267,7 +269,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
await this.router.navigate(["/"]);
|
await this.router.navigate(["/"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.processReloadService.startProcessReload(this.authService);
|
await this.processReloadService.startProcessReload();
|
||||||
|
|
||||||
// Normally we would need to reset the loading state to false or remove the layout_frontend
|
// Normally we would need to reset the loading state to false or remove the layout_frontend
|
||||||
// class from the body here, but the process reload completely reloads the app so
|
// class from the body here, but the process reload completely reloads the app so
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
|
import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory";
|
||||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { IpcService } from "@bitwarden/common/platform/ipc";
|
import { IpcService } from "@bitwarden/common/platform/ipc";
|
||||||
// eslint-disable-next-line no-restricted-imports -- Needed for DI
|
// eslint-disable-next-line no-restricted-imports -- Needed for DI
|
||||||
import {
|
import {
|
||||||
@@ -143,6 +144,7 @@ import { WebEnvironmentService } from "../platform/web-environment.service";
|
|||||||
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
||||||
import { WebSdkLoadService } from "../platform/web-sdk-load.service";
|
import { WebSdkLoadService } from "../platform/web-sdk-load.service";
|
||||||
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
|
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
|
||||||
|
import { WebSystemService } from "../platform/web-system.service";
|
||||||
|
|
||||||
import { EventService } from "./event.service";
|
import { EventService } from "./event.service";
|
||||||
import { InitService } from "./init.service";
|
import { InitService } from "./init.service";
|
||||||
@@ -428,6 +430,11 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: WebPremiumInterestStateService,
|
useClass: WebPremiumInterestStateService,
|
||||||
deps: [StateProvider],
|
deps: [StateProvider],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: SystemService,
|
||||||
|
useClass: WebSystemService,
|
||||||
|
deps: [],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
|
||||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
|
|
||||||
export class WebProcessReloadService implements ProcessReloadServiceAbstraction {
|
export class WebProcessReloadService implements ProcessReloadServiceAbstraction {
|
||||||
constructor(private window: Window) {}
|
constructor(private window: Window) {}
|
||||||
|
|
||||||
async startProcessReload(authService: AuthService): Promise<void> {
|
async startProcessReload(): Promise<void> {
|
||||||
this.window.location.reload();
|
this.window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
apps/web/src/app/platform/web-system.service.ts
Normal file
10
apps/web/src/app/platform/web-system.service.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web implementation of SystemService.
|
||||||
|
* The implementation is NOOP since these functions are not supported on web.
|
||||||
|
*/
|
||||||
|
export class WebSystemService extends SystemService {
|
||||||
|
async clearClipboard(clipboardValue: string, timeoutMs?: number): Promise<void> {}
|
||||||
|
async clearPendingClipboard(): Promise<any> {}
|
||||||
|
}
|
||||||
@@ -35,9 +35,6 @@ export const SECURE_STORAGE = new SafeInjectionToken<AbstractStorageService>("SE
|
|||||||
export const LOGOUT_CALLBACK = new SafeInjectionToken<
|
export const LOGOUT_CALLBACK = new SafeInjectionToken<
|
||||||
(logoutReason: LogoutReason, userId?: string) => Promise<void>
|
(logoutReason: LogoutReason, userId?: string) => Promise<void>
|
||||||
>("LOGOUT_CALLBACK");
|
>("LOGOUT_CALLBACK");
|
||||||
export const LOCKED_CALLBACK = new SafeInjectionToken<(userId?: string) => Promise<void>>(
|
|
||||||
"LOCKED_CALLBACK",
|
|
||||||
);
|
|
||||||
export const SUPPORTS_SECURE_STORAGE = new SafeInjectionToken<boolean>("SUPPORTS_SECURE_STORAGE");
|
export const SUPPORTS_SECURE_STORAGE = new SafeInjectionToken<boolean>("SUPPORTS_SECURE_STORAGE");
|
||||||
export const LOCALES_DIRECTORY = new SafeInjectionToken<string>("LOCALES_DIRECTORY");
|
export const LOCALES_DIRECTORY = new SafeInjectionToken<string>("LOCALES_DIRECTORY");
|
||||||
export const SYSTEM_LANGUAGE = new SafeInjectionToken<string>("SYSTEM_LANGUAGE");
|
export const SYSTEM_LANGUAGE = new SafeInjectionToken<string>("SYSTEM_LANGUAGE");
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ import {
|
|||||||
AuthRequestService,
|
AuthRequestService,
|
||||||
AuthRequestServiceAbstraction,
|
AuthRequestServiceAbstraction,
|
||||||
DefaultAuthRequestApiService,
|
DefaultAuthRequestApiService,
|
||||||
|
DefaultLockService,
|
||||||
DefaultLoginSuccessHandlerService,
|
DefaultLoginSuccessHandlerService,
|
||||||
DefaultLogoutService,
|
DefaultLogoutService,
|
||||||
InternalUserDecryptionOptionsServiceAbstraction,
|
InternalUserDecryptionOptionsServiceAbstraction,
|
||||||
|
LockService,
|
||||||
LoginEmailService,
|
LoginEmailService,
|
||||||
LoginEmailServiceAbstraction,
|
LoginEmailServiceAbstraction,
|
||||||
LoginStrategyService,
|
LoginStrategyService,
|
||||||
@@ -161,6 +163,7 @@ import { OrganizationSponsorshipApiService } from "@bitwarden/common/billing/ser
|
|||||||
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
||||||
import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service";
|
import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service";
|
||||||
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
|
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
|
||||||
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
import {
|
import {
|
||||||
DefaultKeyGenerationService,
|
DefaultKeyGenerationService,
|
||||||
KeyGenerationService,
|
KeyGenerationService,
|
||||||
@@ -219,6 +222,7 @@ import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sd
|
|||||||
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
|
import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { ActionsService } from "@bitwarden/common/platform/actions";
|
import { ActionsService } from "@bitwarden/common/platform/actions";
|
||||||
import { UnsupportedActionsService } from "@bitwarden/common/platform/actions/unsupported-actions.service";
|
import { UnsupportedActionsService } from "@bitwarden/common/platform/actions/unsupported-actions.service";
|
||||||
@@ -403,7 +407,6 @@ import {
|
|||||||
HTTP_OPERATIONS,
|
HTTP_OPERATIONS,
|
||||||
INTRAPROCESS_MESSAGING_SUBJECT,
|
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||||
LOCALES_DIRECTORY,
|
LOCALES_DIRECTORY,
|
||||||
LOCKED_CALLBACK,
|
|
||||||
LOG_MAC_FAILURES,
|
LOG_MAC_FAILURES,
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
OBSERVABLE_DISK_STORAGE,
|
OBSERVABLE_DISK_STORAGE,
|
||||||
@@ -459,10 +462,6 @@ const safeProviders: SafeProvider[] = [
|
|||||||
},
|
},
|
||||||
deps: [MessagingServiceAbstraction],
|
deps: [MessagingServiceAbstraction],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
|
||||||
provide: LOCKED_CALLBACK,
|
|
||||||
useValue: null,
|
|
||||||
}),
|
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: LOG_MAC_FAILURES,
|
provide: LOG_MAC_FAILURES,
|
||||||
useValue: true,
|
useValue: true,
|
||||||
@@ -889,22 +888,12 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DefaultVaultTimeoutService,
|
useClass: DefaultVaultTimeoutService,
|
||||||
deps: [
|
deps: [
|
||||||
AccountServiceAbstraction,
|
AccountServiceAbstraction,
|
||||||
InternalMasterPasswordServiceAbstraction,
|
|
||||||
CipherServiceAbstraction,
|
|
||||||
FolderServiceAbstraction,
|
|
||||||
CollectionService,
|
|
||||||
PlatformUtilsServiceAbstraction,
|
PlatformUtilsServiceAbstraction,
|
||||||
MessagingServiceAbstraction,
|
|
||||||
SearchServiceAbstraction,
|
|
||||||
StateServiceAbstraction,
|
|
||||||
TokenServiceAbstraction,
|
|
||||||
AuthServiceAbstraction,
|
AuthServiceAbstraction,
|
||||||
VaultTimeoutSettingsService,
|
VaultTimeoutSettingsService,
|
||||||
StateEventRunnerService,
|
|
||||||
TaskSchedulerService,
|
TaskSchedulerService,
|
||||||
LogService,
|
LogService,
|
||||||
BiometricsService,
|
LockService,
|
||||||
LOCKED_CALLBACK,
|
|
||||||
LogoutService,
|
LogoutService,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@@ -1718,6 +1707,27 @@ const safeProviders: SafeProvider[] = [
|
|||||||
InternalMasterPasswordServiceAbstraction,
|
InternalMasterPasswordServiceAbstraction,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: LockService,
|
||||||
|
useClass: DefaultLockService,
|
||||||
|
deps: [
|
||||||
|
AccountService,
|
||||||
|
BiometricsService,
|
||||||
|
VaultTimeoutSettingsService,
|
||||||
|
LogoutService,
|
||||||
|
MessagingServiceAbstraction,
|
||||||
|
SearchServiceAbstraction,
|
||||||
|
FolderServiceAbstraction,
|
||||||
|
InternalMasterPasswordServiceAbstraction,
|
||||||
|
StateEventRunnerService,
|
||||||
|
CipherServiceAbstraction,
|
||||||
|
AuthServiceAbstraction,
|
||||||
|
SystemService,
|
||||||
|
ProcessReloadServiceAbstraction,
|
||||||
|
LogService,
|
||||||
|
KeyService,
|
||||||
|
],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: CipherArchiveService,
|
provide: CipherArchiveService,
|
||||||
useClass: DefaultCipherArchiveService,
|
useClass: DefaultCipherArchiveService,
|
||||||
|
|||||||
@@ -1,20 +1,55 @@
|
|||||||
import { combineLatest, firstValueFrom, map } from "rxjs";
|
import { combineLatest, filter, firstValueFrom, map, timeout } from "rxjs";
|
||||||
|
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||||
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
|
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
|
||||||
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
|
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||||
|
import { BiometricsService, KeyService } from "@bitwarden/key-management";
|
||||||
|
import { LogService } from "@bitwarden/logging";
|
||||||
|
import { StateEventRunnerService } from "@bitwarden/state";
|
||||||
|
|
||||||
|
import { LogoutService } from "../../abstractions";
|
||||||
|
|
||||||
export abstract class LockService {
|
export abstract class LockService {
|
||||||
/**
|
/**
|
||||||
* Locks all accounts.
|
* Locks all accounts.
|
||||||
*/
|
*/
|
||||||
abstract lockAll(): Promise<void>;
|
abstract lockAll(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Performs lock for a user.
|
||||||
|
* @param userId The user id to lock
|
||||||
|
*/
|
||||||
|
abstract lock(userId: UserId): Promise<void>;
|
||||||
|
|
||||||
|
abstract runPlatformOnLockActions(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DefaultLockService implements LockService {
|
export class DefaultLockService implements LockService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
private readonly vaultTimeoutService: VaultTimeoutService,
|
private readonly biometricService: BiometricsService,
|
||||||
|
private readonly vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
|
private readonly logoutService: LogoutService,
|
||||||
|
private readonly messagingService: MessagingService,
|
||||||
|
private readonly searchService: SearchService,
|
||||||
|
private readonly folderService: FolderService,
|
||||||
|
private readonly masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||||
|
private readonly stateEventRunnerService: StateEventRunnerService,
|
||||||
|
private readonly cipherService: CipherService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
private readonly systemService: SystemService,
|
||||||
|
private readonly processReloadService: ProcessReloadServiceAbstraction,
|
||||||
|
private readonly logService: LogService,
|
||||||
|
private readonly keyService: KeyService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async lockAll() {
|
async lockAll() {
|
||||||
@@ -36,14 +71,88 @@ export class DefaultLockService implements LockService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const otherAccount of accounts.otherAccounts) {
|
for (const otherAccount of accounts.otherAccounts) {
|
||||||
await this.vaultTimeoutService.lock(otherAccount);
|
await this.lock(otherAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the active account last in case we ever try to route the user on lock
|
// Do the active account last in case we ever try to route the user on lock
|
||||||
// that way this whole operation will be complete before that routing
|
// that way this whole operation will be complete before that routing
|
||||||
// could take place.
|
// could take place.
|
||||||
if (accounts.activeAccount != null) {
|
if (accounts.activeAccount != null) {
|
||||||
await this.vaultTimeoutService.lock(accounts.activeAccount);
|
await this.lock(accounts.activeAccount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async lock(userId: UserId): Promise<void> {
|
||||||
|
assertNonNullish(userId, "userId", "LockService");
|
||||||
|
|
||||||
|
this.logService.info(`[LockService] Locking user ${userId}`);
|
||||||
|
|
||||||
|
// If user already logged out, then skip locking
|
||||||
|
if (
|
||||||
|
(await firstValueFrom(this.authService.authStatusFor$(userId))) ===
|
||||||
|
AuthenticationStatus.LoggedOut
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user cannot lock, then logout instead
|
||||||
|
if (!(await this.vaultTimeoutSettingsService.canLock(userId))) {
|
||||||
|
// Logout should perform the same steps
|
||||||
|
await this.logoutService.logout(userId, "vaultTimeout");
|
||||||
|
this.logService.info(`[LockService] User ${userId} cannot lock, logging out instead.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.wipeDecryptedState(userId);
|
||||||
|
await this.waitForLockedStatus(userId);
|
||||||
|
await this.systemService.clearPendingClipboard();
|
||||||
|
await this.runPlatformOnLockActions();
|
||||||
|
|
||||||
|
this.logService.info(`[LockService] Locked user ${userId}`);
|
||||||
|
|
||||||
|
// Subscribers navigate the client to the lock screen based on this lock message.
|
||||||
|
// We need to disable auto-prompting as we are just entering a locked state now.
|
||||||
|
await this.biometricService.setShouldAutopromptNow(false);
|
||||||
|
this.messagingService.send("locked", { userId });
|
||||||
|
|
||||||
|
// Wipe the current process to clear active secrets in memory.
|
||||||
|
await this.processReloadService.startProcessReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async wipeDecryptedState(userId: UserId) {
|
||||||
|
// Manually clear state
|
||||||
|
await this.searchService.clearIndex(userId);
|
||||||
|
//! DO NOT REMOVE folderService.clearDecryptedFolderState ! For more information see PM-25660
|
||||||
|
await this.folderService.clearDecryptedFolderState(userId);
|
||||||
|
await this.masterPasswordService.clearMasterKey(userId);
|
||||||
|
await this.cipherService.clearCache(userId);
|
||||||
|
// Clear CLI unlock state
|
||||||
|
await this.keyService.clearStoredUserKey(userId);
|
||||||
|
|
||||||
|
// This will clear ephemeral state such as the user's user key based on the key definition's clear-on
|
||||||
|
await this.stateEventRunnerService.handleEvent("lock", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForLockedStatus(userId: UserId): Promise<void> {
|
||||||
|
// HACK: Start listening for the transition of the locking user from something to the locked state.
|
||||||
|
// This is very much a hack to ensure that the authentication status to retrievable right after
|
||||||
|
// it does its work. Particularly and `"locked"` message. Instead the message should be deprecated
|
||||||
|
// and people should subscribe and react to `authStatusFor$` themselves.
|
||||||
|
await firstValueFrom(
|
||||||
|
this.authService.authStatusFor$(userId).pipe(
|
||||||
|
filter((authStatus) => authStatus === AuthenticationStatus.Locked),
|
||||||
|
timeout({
|
||||||
|
first: 5_000,
|
||||||
|
with: () => {
|
||||||
|
throw new Error("The lock process did not complete in a reasonable amount of time.");
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async runPlatformOnLockActions(): Promise<void> {
|
||||||
|
// No platform specific actions to run for this platform.
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||||
|
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||||
|
import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout";
|
||||||
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
import { mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||||
|
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||||
|
import { BiometricsService, KeyService } from "@bitwarden/key-management";
|
||||||
|
import { LogService } from "@bitwarden/logging";
|
||||||
|
import { StateEventRunnerService } from "@bitwarden/state";
|
||||||
|
|
||||||
|
import { LogoutService } from "../../abstractions";
|
||||||
|
|
||||||
import { DefaultLockService } from "./lock.service";
|
import { DefaultLockService } from "./lock.service";
|
||||||
|
|
||||||
@@ -12,10 +27,57 @@ describe("DefaultLockService", () => {
|
|||||||
const mockUser3 = "user3" as UserId;
|
const mockUser3 = "user3" as UserId;
|
||||||
|
|
||||||
const accountService = mockAccountServiceWith(mockUser1);
|
const accountService = mockAccountServiceWith(mockUser1);
|
||||||
const vaultTimeoutService = mock<VaultTimeoutService>();
|
const biometricsService = mock<BiometricsService>();
|
||||||
|
const vaultTimeoutSettingsService = mock<VaultTimeoutSettingsService>();
|
||||||
|
const logoutService = mock<LogoutService>();
|
||||||
|
const messagingService = mock<MessagingService>();
|
||||||
|
const searchService = mock<SearchService>();
|
||||||
|
const folderService = mock<FolderService>();
|
||||||
|
const masterPasswordService = mock<InternalMasterPasswordServiceAbstraction>();
|
||||||
|
const stateEventRunnerService = mock<StateEventRunnerService>();
|
||||||
|
const cipherService = mock<CipherService>();
|
||||||
|
const authService = mock<AuthService>();
|
||||||
|
const systemService = mock<SystemService>();
|
||||||
|
const processReloadService = mock<ProcessReloadServiceAbstraction>();
|
||||||
|
const logService = mock<LogService>();
|
||||||
|
const keyService = mock<KeyService>();
|
||||||
|
const sut = new DefaultLockService(
|
||||||
|
accountService,
|
||||||
|
biometricsService,
|
||||||
|
vaultTimeoutSettingsService,
|
||||||
|
logoutService,
|
||||||
|
messagingService,
|
||||||
|
searchService,
|
||||||
|
folderService,
|
||||||
|
masterPasswordService,
|
||||||
|
stateEventRunnerService,
|
||||||
|
cipherService,
|
||||||
|
authService,
|
||||||
|
systemService,
|
||||||
|
processReloadService,
|
||||||
|
logService,
|
||||||
|
keyService,
|
||||||
|
);
|
||||||
|
|
||||||
const sut = new DefaultLockService(accountService, vaultTimeoutService);
|
|
||||||
describe("lockAll", () => {
|
describe("lockAll", () => {
|
||||||
|
const sut = new DefaultLockService(
|
||||||
|
accountService,
|
||||||
|
biometricsService,
|
||||||
|
vaultTimeoutSettingsService,
|
||||||
|
logoutService,
|
||||||
|
messagingService,
|
||||||
|
searchService,
|
||||||
|
folderService,
|
||||||
|
masterPasswordService,
|
||||||
|
stateEventRunnerService,
|
||||||
|
cipherService,
|
||||||
|
authService,
|
||||||
|
systemService,
|
||||||
|
processReloadService,
|
||||||
|
logService,
|
||||||
|
keyService,
|
||||||
|
);
|
||||||
|
|
||||||
it("locks the active account last", async () => {
|
it("locks the active account last", async () => {
|
||||||
await accountService.addAccount(mockUser2, {
|
await accountService.addAccount(mockUser2, {
|
||||||
name: "name2",
|
name: "name2",
|
||||||
@@ -25,19 +87,49 @@ describe("DefaultLockService", () => {
|
|||||||
|
|
||||||
await accountService.addAccount(mockUser3, {
|
await accountService.addAccount(mockUser3, {
|
||||||
name: "name3",
|
name: "name3",
|
||||||
email: "email3@example.com",
|
email: "name3@example.com",
|
||||||
emailVerified: false,
|
emailVerified: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lockSpy = jest.spyOn(sut, "lock").mockResolvedValue(undefined);
|
||||||
|
|
||||||
await sut.lockAll();
|
await sut.lockAll();
|
||||||
|
|
||||||
expect(vaultTimeoutService.lock).toHaveBeenCalledTimes(3);
|
|
||||||
// Non-Active users should be called first
|
// Non-Active users should be called first
|
||||||
expect(vaultTimeoutService.lock).toHaveBeenNthCalledWith(1, mockUser2);
|
expect(lockSpy).toHaveBeenNthCalledWith(1, mockUser2);
|
||||||
expect(vaultTimeoutService.lock).toHaveBeenNthCalledWith(2, mockUser3);
|
expect(lockSpy).toHaveBeenNthCalledWith(2, mockUser3);
|
||||||
|
|
||||||
// Active user should be called last
|
// Active user should be called last
|
||||||
expect(vaultTimeoutService.lock).toHaveBeenNthCalledWith(3, mockUser1);
|
expect(lockSpy).toHaveBeenNthCalledWith(3, mockUser1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("lock", () => {
|
||||||
|
const userId = mockUser1;
|
||||||
|
|
||||||
|
it("returns early if user is already logged out", async () => {
|
||||||
|
authService.authStatusFor$.mockReturnValue(of(AuthenticationStatus.LoggedOut));
|
||||||
|
await sut.lock(userId);
|
||||||
|
// Should return early, not call logoutService.logout
|
||||||
|
expect(logoutService.logout).not.toHaveBeenCalled();
|
||||||
|
expect(stateEventRunnerService.handleEvent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs out if user cannot lock", async () => {
|
||||||
|
authService.authStatusFor$.mockReturnValue(of(AuthenticationStatus.Unlocked));
|
||||||
|
vaultTimeoutSettingsService.canLock.mockResolvedValue(false);
|
||||||
|
await sut.lock(userId);
|
||||||
|
expect(logoutService.logout).toHaveBeenCalledWith(userId, "vaultTimeout");
|
||||||
|
expect(stateEventRunnerService.handleEvent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("locks user", async () => {
|
||||||
|
authService.authStatusFor$.mockReturnValue(of(AuthenticationStatus.Locked));
|
||||||
|
logoutService.logout.mockClear();
|
||||||
|
vaultTimeoutSettingsService.canLock.mockResolvedValue(true);
|
||||||
|
await sut.lock(userId);
|
||||||
|
expect(logoutService.logout).not.toHaveBeenCalled();
|
||||||
|
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", userId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { AuthService } from "../../auth/abstractions/auth.service";
|
|
||||||
|
|
||||||
export abstract class ProcessReloadServiceAbstraction {
|
export abstract class ProcessReloadServiceAbstraction {
|
||||||
abstract startProcessReload(authService: AuthService): Promise<void>;
|
abstract startProcessReload(): Promise<void>;
|
||||||
abstract cancelProcessReload(): void;
|
abstract cancelProcessReload(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract
|
|||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
|
private authService: AuthService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async startProcessReload(authService: AuthService): Promise<void> {
|
async startProcessReload(): Promise<void> {
|
||||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||||
if (accounts != null) {
|
if (accounts != null) {
|
||||||
const keys = Object.keys(accounts);
|
const keys = Object.keys(accounts);
|
||||||
if (keys.length > 0) {
|
if (keys.length > 0) {
|
||||||
for (const userId of keys) {
|
for (const userId of keys) {
|
||||||
let status = await firstValueFrom(authService.authStatusFor$(userId as UserId));
|
let status = await firstValueFrom(this.authService.authStatusFor$(userId as UserId));
|
||||||
status = await authService.getAuthStatus(userId);
|
status = await this.authService.getAuthStatus(userId);
|
||||||
if (status === AuthenticationStatus.Unlocked) {
|
if (status === AuthenticationStatus.Unlocked) {
|
||||||
this.logService.info(
|
this.logService.info(
|
||||||
"[Process Reload Service] User unlocked, preventing process reload",
|
"[Process Reload Service] User unlocked, preventing process reload",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export abstract class VaultTimeoutService {
|
export abstract class VaultTimeoutService {
|
||||||
abstract checkVaultTimeout(): Promise<void>;
|
abstract checkVaultTimeout(): Promise<void>;
|
||||||
abstract lock(userId?: string): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,31 +5,17 @@ import { BehaviorSubject, from, of } from "rxjs";
|
|||||||
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { LockService, LogoutService } from "@bitwarden/auth/common";
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { LogoutService } from "@bitwarden/auth/common";
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { BiometricsService } from "@bitwarden/key-management";
|
|
||||||
import { StateService } from "@bitwarden/state";
|
|
||||||
|
|
||||||
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec";
|
import { FakeAccountService, mockAccountServiceWith } from "../../../../spec";
|
||||||
import { AccountInfo } from "../../../auth/abstractions/account.service";
|
import { AccountInfo } from "../../../auth/abstractions/account.service";
|
||||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||||
import { TokenService } from "../../../auth/abstractions/token.service";
|
|
||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
import { LogService } from "../../../platform/abstractions/log.service";
|
import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
|
||||||
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "../../../platform/misc/utils";
|
import { Utils } from "../../../platform/misc/utils";
|
||||||
import { TaskSchedulerService } from "../../../platform/scheduling";
|
import { TaskSchedulerService } from "../../../platform/scheduling";
|
||||||
import { StateEventRunnerService } from "../../../platform/state";
|
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
|
||||||
import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction";
|
|
||||||
import { SearchService } from "../../../vault/abstractions/search.service";
|
|
||||||
import { FakeMasterPasswordService } from "../../master-password/services/fake-master-password.service";
|
|
||||||
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
||||||
import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type";
|
import { VaultTimeout, VaultTimeoutStringType } from "../types/vault-timeout.type";
|
||||||
|
|
||||||
@@ -38,23 +24,13 @@ import { VaultTimeoutService } from "./vault-timeout.service";
|
|||||||
|
|
||||||
describe("VaultTimeoutService", () => {
|
describe("VaultTimeoutService", () => {
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
let masterPasswordService: FakeMasterPasswordService;
|
|
||||||
let cipherService: MockProxy<CipherService>;
|
|
||||||
let folderService: MockProxy<FolderService>;
|
|
||||||
let collectionService: MockProxy<CollectionService>;
|
|
||||||
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||||
let messagingService: MockProxy<MessagingService>;
|
|
||||||
let searchService: MockProxy<SearchService>;
|
|
||||||
let stateService: MockProxy<StateService>;
|
|
||||||
let tokenService: MockProxy<TokenService>;
|
|
||||||
let authService: MockProxy<AuthService>;
|
let authService: MockProxy<AuthService>;
|
||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
let stateEventRunnerService: MockProxy<StateEventRunnerService>;
|
|
||||||
let taskSchedulerService: MockProxy<TaskSchedulerService>;
|
let taskSchedulerService: MockProxy<TaskSchedulerService>;
|
||||||
let logService: MockProxy<LogService>;
|
let logService: MockProxy<LogService>;
|
||||||
let biometricsService: MockProxy<BiometricsService>;
|
let lockService: MockProxy<LockService>;
|
||||||
let logoutService: MockProxy<LogoutService>;
|
let logoutService: MockProxy<LogoutService>;
|
||||||
let lockedCallback: jest.Mock<Promise<void>, [userId: string]>;
|
|
||||||
|
|
||||||
let vaultTimeoutActionSubject: BehaviorSubject<VaultTimeoutAction>;
|
let vaultTimeoutActionSubject: BehaviorSubject<VaultTimeoutAction>;
|
||||||
let availableVaultTimeoutActionsSubject: BehaviorSubject<VaultTimeoutAction[]>;
|
let availableVaultTimeoutActionsSubject: BehaviorSubject<VaultTimeoutAction[]>;
|
||||||
@@ -65,25 +41,14 @@ describe("VaultTimeoutService", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
accountService = mockAccountServiceWith(userId);
|
accountService = mockAccountServiceWith(userId);
|
||||||
masterPasswordService = new FakeMasterPasswordService();
|
|
||||||
cipherService = mock();
|
|
||||||
folderService = mock();
|
|
||||||
collectionService = mock();
|
|
||||||
platformUtilsService = mock();
|
platformUtilsService = mock();
|
||||||
messagingService = mock();
|
|
||||||
searchService = mock();
|
|
||||||
stateService = mock();
|
|
||||||
tokenService = mock();
|
|
||||||
authService = mock();
|
authService = mock();
|
||||||
vaultTimeoutSettingsService = mock();
|
vaultTimeoutSettingsService = mock();
|
||||||
stateEventRunnerService = mock();
|
|
||||||
taskSchedulerService = mock<TaskSchedulerService>();
|
taskSchedulerService = mock<TaskSchedulerService>();
|
||||||
|
lockService = mock<LockService>();
|
||||||
logService = mock<LogService>();
|
logService = mock<LogService>();
|
||||||
biometricsService = mock<BiometricsService>();
|
|
||||||
logoutService = mock<LogoutService>();
|
logoutService = mock<LogoutService>();
|
||||||
|
|
||||||
lockedCallback = jest.fn();
|
|
||||||
|
|
||||||
vaultTimeoutActionSubject = new BehaviorSubject(VaultTimeoutAction.Lock);
|
vaultTimeoutActionSubject = new BehaviorSubject(VaultTimeoutAction.Lock);
|
||||||
|
|
||||||
vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue(
|
vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue(
|
||||||
@@ -94,22 +59,12 @@ describe("VaultTimeoutService", () => {
|
|||||||
|
|
||||||
vaultTimeoutService = new VaultTimeoutService(
|
vaultTimeoutService = new VaultTimeoutService(
|
||||||
accountService,
|
accountService,
|
||||||
masterPasswordService,
|
|
||||||
cipherService,
|
|
||||||
folderService,
|
|
||||||
collectionService,
|
|
||||||
platformUtilsService,
|
platformUtilsService,
|
||||||
messagingService,
|
|
||||||
searchService,
|
|
||||||
stateService,
|
|
||||||
tokenService,
|
|
||||||
authService,
|
authService,
|
||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
stateEventRunnerService,
|
|
||||||
taskSchedulerService,
|
taskSchedulerService,
|
||||||
logService,
|
logService,
|
||||||
biometricsService,
|
lockService,
|
||||||
lockedCallback,
|
|
||||||
logoutService,
|
logoutService,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -145,9 +100,6 @@ describe("VaultTimeoutService", () => {
|
|||||||
authService.getAuthStatus.mockImplementation((userId) => {
|
authService.getAuthStatus.mockImplementation((userId) => {
|
||||||
return Promise.resolve(accounts[userId]?.authStatus);
|
return Promise.resolve(accounts[userId]?.authStatus);
|
||||||
});
|
});
|
||||||
tokenService.hasAccessToken$.mockImplementation((userId) => {
|
|
||||||
return of(accounts[userId]?.isAuthenticated ?? false);
|
|
||||||
});
|
|
||||||
|
|
||||||
vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockImplementation((userId) => {
|
vaultTimeoutSettingsService.getVaultTimeoutByUserId$.mockImplementation((userId) => {
|
||||||
return new BehaviorSubject<VaultTimeout>(accounts[userId]?.vaultTimeout);
|
return new BehaviorSubject<VaultTimeout>(accounts[userId]?.vaultTimeout);
|
||||||
@@ -203,13 +155,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const expectUserToHaveLocked = (userId: string) => {
|
const expectUserToHaveLocked = (userId: string) => {
|
||||||
// This does NOT assert all the things that the lock process does
|
expect(lockService.lock).toHaveBeenCalledWith(userId);
|
||||||
expect(tokenService.hasAccessToken$).toHaveBeenCalledWith(userId);
|
|
||||||
expect(vaultTimeoutSettingsService.availableVaultTimeoutActions$).toHaveBeenCalledWith(userId);
|
|
||||||
expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { userId: userId });
|
|
||||||
expect(masterPasswordService.mock.clearMasterKey).toHaveBeenCalledWith(userId);
|
|
||||||
expect(cipherService.clearCache).toHaveBeenCalledWith(userId);
|
|
||||||
expect(lockedCallback).toHaveBeenCalledWith(userId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectUserToHaveLoggedOut = (userId: string) => {
|
const expectUserToHaveLoggedOut = (userId: string) => {
|
||||||
@@ -217,7 +163,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const expectNoAction = (userId: string) => {
|
const expectNoAction = (userId: string) => {
|
||||||
expect(lockedCallback).not.toHaveBeenCalledWith(userId);
|
expect(lockService.lock).not.toHaveBeenCalledWith(userId);
|
||||||
expect(logoutService.logout).not.toHaveBeenCalledWith(userId, "vaultTimeout");
|
expect(logoutService.logout).not.toHaveBeenCalledWith(userId, "vaultTimeout");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -347,12 +293,8 @@ describe("VaultTimeoutService", () => {
|
|||||||
expectNoAction("1");
|
expectNoAction("1");
|
||||||
expectUserToHaveLocked("2");
|
expectUserToHaveLocked("2");
|
||||||
|
|
||||||
// Active users should have additional steps ran
|
|
||||||
expect(searchService.clearIndex).toHaveBeenCalled();
|
|
||||||
expect(folderService.clearDecryptedFolderState).toHaveBeenCalled();
|
|
||||||
|
|
||||||
expectUserToHaveLoggedOut("3"); // They have chosen logout as their action and it's available, log them out
|
expectUserToHaveLoggedOut("3"); // They have chosen logout as their action and it's available, log them out
|
||||||
expectUserToHaveLoggedOut("4"); // They may have had lock as their chosen action but it's not available to them so logout
|
expectUserToHaveLocked("4"); // They don't have lock available. But this is handled in lock service so we do not check for logout here
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should lock an account if they haven't been active passed their vault timeout even if a view is open when they are not the active user.", async () => {
|
it("should lock an account if they haven't been active passed their vault timeout even if a view is open when they are not the active user.", async () => {
|
||||||
@@ -392,70 +334,4 @@ describe("VaultTimeoutService", () => {
|
|||||||
expectNoAction("1");
|
expectNoAction("1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("lock", () => {
|
|
||||||
const setupLock = () => {
|
|
||||||
setupAccounts(
|
|
||||||
{
|
|
||||||
user1: {
|
|
||||||
authStatus: AuthenticationStatus.Unlocked,
|
|
||||||
isAuthenticated: true,
|
|
||||||
},
|
|
||||||
user2: {
|
|
||||||
authStatus: AuthenticationStatus.Unlocked,
|
|
||||||
isAuthenticated: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: "user1",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
it("should call state event runner with currently active user if no user passed into lock", async () => {
|
|
||||||
setupLock();
|
|
||||||
|
|
||||||
await vaultTimeoutService.lock();
|
|
||||||
|
|
||||||
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", "user1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call locked callback with the locking user if no userID is passed in.", async () => {
|
|
||||||
setupLock();
|
|
||||||
|
|
||||||
await vaultTimeoutService.lock();
|
|
||||||
|
|
||||||
expect(lockedCallback).toHaveBeenCalledWith("user1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call state event runner with user passed into lock", async () => {
|
|
||||||
setupLock();
|
|
||||||
|
|
||||||
const user2 = "user2" as UserId;
|
|
||||||
|
|
||||||
await vaultTimeoutService.lock(user2);
|
|
||||||
|
|
||||||
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", user2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call messaging service locked message with user passed into lock", async () => {
|
|
||||||
setupLock();
|
|
||||||
|
|
||||||
const user2 = "user2" as UserId;
|
|
||||||
|
|
||||||
await vaultTimeoutService.lock(user2);
|
|
||||||
|
|
||||||
expect(messagingService.send).toHaveBeenCalledWith("locked", { userId: user2 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should call locked callback with user passed into lock", async () => {
|
|
||||||
setupLock();
|
|
||||||
|
|
||||||
const user2 = "user2" as UserId;
|
|
||||||
|
|
||||||
await vaultTimeoutService.lock(user2);
|
|
||||||
|
|
||||||
expect(lockedCallback).toHaveBeenCalledWith(user2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,32 +1,18 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { combineLatest, concatMap, filter, firstValueFrom, map, timeout } from "rxjs";
|
import { combineLatest, concatMap, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { LockService, LogoutService } from "@bitwarden/auth/common";
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { LogoutService } from "@bitwarden/auth/common";
|
|
||||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
|
||||||
// eslint-disable-next-line no-restricted-imports
|
|
||||||
import { BiometricsService } from "@bitwarden/key-management";
|
|
||||||
|
|
||||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||||
import { AuthService } from "../../../auth/abstractions/auth.service";
|
import { AuthService } from "../../../auth/abstractions/auth.service";
|
||||||
import { TokenService } from "../../../auth/abstractions/token.service";
|
|
||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
import { LogService } from "../../../platform/abstractions/log.service";
|
import { LogService } from "../../../platform/abstractions/log.service";
|
||||||
import { MessagingService } from "../../../platform/abstractions/messaging.service";
|
|
||||||
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../../platform/abstractions/state.service";
|
|
||||||
import { TaskSchedulerService, ScheduledTaskNames } from "../../../platform/scheduling";
|
import { TaskSchedulerService, ScheduledTaskNames } from "../../../platform/scheduling";
|
||||||
import { StateEventRunnerService } from "../../../platform/state";
|
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { CipherService } from "../../../vault/abstractions/cipher.service";
|
|
||||||
import { FolderService } from "../../../vault/abstractions/folder/folder.service.abstraction";
|
|
||||||
import { SearchService } from "../../../vault/abstractions/search.service";
|
|
||||||
import { InternalMasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction";
|
|
||||||
import { VaultTimeoutSettingsService } from "../abstractions/vault-timeout-settings.service";
|
import { VaultTimeoutSettingsService } from "../abstractions/vault-timeout-settings.service";
|
||||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vault-timeout.service";
|
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vault-timeout.service";
|
||||||
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum";
|
||||||
@@ -36,22 +22,12 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
|
||||||
private cipherService: CipherService,
|
|
||||||
private folderService: FolderService,
|
|
||||||
private collectionService: CollectionService,
|
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
private messagingService: MessagingService,
|
|
||||||
private searchService: SearchService,
|
|
||||||
private stateService: StateService,
|
|
||||||
private tokenService: TokenService,
|
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
private stateEventRunnerService: StateEventRunnerService,
|
|
||||||
private taskSchedulerService: TaskSchedulerService,
|
private taskSchedulerService: TaskSchedulerService,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
private biometricService: BiometricsService,
|
private lockService: LockService,
|
||||||
private lockedCallback: (userId: UserId) => Promise<void> = null,
|
|
||||||
private logoutService: LogoutService,
|
private logoutService: LogoutService,
|
||||||
) {
|
) {
|
||||||
this.taskSchedulerService.registerTaskHandler(
|
this.taskSchedulerService.registerTaskHandler(
|
||||||
@@ -104,64 +80,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async lock(userId?: UserId): Promise<void> {
|
|
||||||
await this.biometricService.setShouldAutopromptNow(false);
|
|
||||||
|
|
||||||
const lockingUserId =
|
|
||||||
userId ?? (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))));
|
|
||||||
|
|
||||||
const authed = await firstValueFrom(this.tokenService.hasAccessToken$(lockingUserId));
|
|
||||||
if (!authed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableActions = await firstValueFrom(
|
|
||||||
this.vaultTimeoutSettingsService.availableVaultTimeoutActions$(userId),
|
|
||||||
);
|
|
||||||
const supportsLock = availableActions.includes(VaultTimeoutAction.Lock);
|
|
||||||
if (!supportsLock) {
|
|
||||||
await this.logoutService.logout(userId, "vaultTimeout");
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK: Start listening for the transition of the locking user from something to the locked state.
|
|
||||||
// This is very much a hack to ensure that the authentication status to retrievable right after
|
|
||||||
// it does its work. Particularly the `lockedCallback` and `"locked"` message. Instead
|
|
||||||
// lockedCallback should be deprecated and people should subscribe and react to `authStatusFor$` themselves.
|
|
||||||
const lockPromise = firstValueFrom(
|
|
||||||
this.authService.authStatusFor$(lockingUserId).pipe(
|
|
||||||
filter((authStatus) => authStatus === AuthenticationStatus.Locked),
|
|
||||||
timeout({
|
|
||||||
first: 5_000,
|
|
||||||
with: () => {
|
|
||||||
throw new Error("The lock process did not complete in a reasonable amount of time.");
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.searchService.clearIndex(lockingUserId);
|
|
||||||
|
|
||||||
await this.folderService.clearDecryptedFolderState(lockingUserId);
|
|
||||||
await this.masterPasswordService.clearMasterKey(lockingUserId);
|
|
||||||
|
|
||||||
await this.stateService.setUserKeyAutoUnlock(null, { userId: lockingUserId });
|
|
||||||
|
|
||||||
await this.cipherService.clearCache(lockingUserId);
|
|
||||||
|
|
||||||
await this.stateEventRunnerService.handleEvent("lock", lockingUserId);
|
|
||||||
|
|
||||||
// HACK: Sit here and wait for the the auth status to transition to `Locked`
|
|
||||||
// to ensure the message and lockedCallback will get the correct status
|
|
||||||
// if/when they call it.
|
|
||||||
await lockPromise;
|
|
||||||
|
|
||||||
this.messagingService.send("locked", { userId: lockingUserId });
|
|
||||||
|
|
||||||
if (this.lockedCallback != null) {
|
|
||||||
await this.lockedCallback(lockingUserId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async shouldLock(
|
private async shouldLock(
|
||||||
userId: string,
|
userId: string,
|
||||||
lastActive: Date,
|
lastActive: Date,
|
||||||
@@ -206,6 +124,6 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
);
|
);
|
||||||
timeoutAction === VaultTimeoutAction.LogOut
|
timeoutAction === VaultTimeoutAction.LogOut
|
||||||
? await this.logoutService.logout(userId, "vaultTimeout")
|
? await this.logoutService.logout(userId, "vaultTimeout")
|
||||||
: await this.lock(userId);
|
: await this.lockService.lock(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user