1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

Merge branch 'main' into auth/pm-14369/hide-account-switcher-if-on-login-page-and-not-logged-in

This commit is contained in:
Alec Rippberger
2024-11-04 14:44:22 -06:00
committed by GitHub
17 changed files with 447 additions and 388 deletions

View File

@@ -257,12 +257,9 @@ import { BrowserPlatformUtilsService } from "../platform/services/platform-utils
import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service"; import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service";
import { BrowserSdkClientFactory } from "../platform/services/sdk/browser-sdk-client-factory"; import { BrowserSdkClientFactory } from "../platform/services/sdk/browser-sdk-client-factory";
import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service"; import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service";
import { ForegroundTaskSchedulerService } from "../platform/services/task-scheduler/foreground-task-scheduler.service";
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service"; import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider"; import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service"; import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service";
import { ForegroundSyncService } from "../platform/sync/foreground-sync.service";
import { SyncServiceListener } from "../platform/sync/sync-service.listener"; import { SyncServiceListener } from "../platform/sync/sync-service.listener";
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
@@ -401,7 +398,7 @@ export default class MainBackground {
private popupViewCacheBackgroundService: PopupViewCacheBackgroundService; private popupViewCacheBackgroundService: PopupViewCacheBackgroundService;
constructor(public popupOnlyContext: boolean = false) { constructor() {
// Services // Services
const lockedCallback = async (userId?: string) => { const lockedCallback = async (userId?: string) => {
if (this.notificationsService != null) { if (this.notificationsService != null) {
@@ -460,45 +457,6 @@ export default class MainBackground {
this.offscreenDocumentService, this.offscreenDocumentService,
); );
// Creates a session key for mv3 storage of large memory items
const sessionKey = new Lazy(async () => {
// Key already in session storage
const sessionStorage = new BrowserMemoryStorageService();
const existingKey = await sessionStorage.get<SymmetricCryptoKey>("session-key");
if (existingKey) {
if (sessionStorage.valuesRequireDeserialization) {
return SymmetricCryptoKey.fromJSON(existingKey);
}
return existingKey;
}
// New key
const { derivedKey } = await this.keyGenerationService.createKeyWithPurpose(
128,
"ephemeral",
"bitwarden-ephemeral",
);
await sessionStorage.save("session-key", derivedKey);
return derivedKey;
});
const mv3MemoryStorageCreator = () => {
if (this.popupOnlyContext) {
return new ForegroundMemoryStorageService();
}
// For local backed session storage, we expect that the encrypted data on disk will persist longer than the encryption key in memory
// and failures to decrypt because of that are completely expected. For this reason, we pass in `false` to the `EncryptServiceImplementation`
// so that MAC failures are not logged.
return new LocalBackedSessionStorageService(
sessionKey,
this.storageService,
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
this.platformUtilsService,
this.logService,
);
};
this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used
if (BrowserApi.isManifestVersion(3)) { if (BrowserApi.isManifestVersion(3)) {
@@ -506,18 +464,47 @@ export default class MainBackground {
this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session
this.memoryStorageService = this.memoryStorageForStateProviders; this.memoryStorageService = this.memoryStorageForStateProviders;
} else { } else {
if (popupOnlyContext) { this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
this.memoryStorageForStateProviders = new ForegroundMemoryStorageService(); this.memoryStorageService = this.memoryStorageForStateProviders;
this.memoryStorageService = new ForegroundMemoryStorageService();
} else {
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
this.memoryStorageService = this.memoryStorageForStateProviders;
}
} }
this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3) if (BrowserApi.isManifestVersion(3)) {
? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage // Creates a session key for mv3 storage of large memory items
: this.memoryStorageForStateProviders; // mv2 stores to the same location const sessionKey = new Lazy(async () => {
// Key already in session storage
const sessionStorage = new BrowserMemoryStorageService();
const existingKey = await sessionStorage.get<SymmetricCryptoKey>("session-key");
if (existingKey) {
if (sessionStorage.valuesRequireDeserialization) {
return SymmetricCryptoKey.fromJSON(existingKey);
}
return existingKey;
}
// New key
const { derivedKey } = await this.keyGenerationService.createKeyWithPurpose(
128,
"ephemeral",
"bitwarden-ephemeral",
);
await sessionStorage.save("session-key", derivedKey);
return derivedKey;
});
this.largeObjectMemoryStorageForStateProviders = new LocalBackedSessionStorageService(
sessionKey,
this.storageService,
// For local backed session storage, we expect that the encrypted data on disk will persist longer than the encryption key in memory
// and failures to decrypt because of that are completely expected. For this reason, we pass in `false` to the `EncryptServiceImplementation`
// so that MAC failures are not logged.
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
this.platformUtilsService,
this.logService,
);
} else {
// mv2 stores to the same location
this.largeObjectMemoryStorageForStateProviders = this.memoryStorageForStateProviders;
}
const localStorageStorageService = BrowserApi.isManifestVersion(3) const localStorageStorageService = BrowserApi.isManifestVersion(3)
? new OffscreenStorageService(this.offscreenDocumentService) ? new OffscreenStorageService(this.offscreenDocumentService)
@@ -575,9 +562,10 @@ export default class MainBackground {
this.derivedStateProvider, this.derivedStateProvider,
); );
this.taskSchedulerService = this.popupOnlyContext this.taskSchedulerService = new BackgroundTaskSchedulerService(
? new ForegroundTaskSchedulerService(this.logService, this.stateProvider) this.logService,
: new BackgroundTaskSchedulerService(this.logService, this.stateProvider); this.stateProvider,
);
this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () => this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () =>
this.fullSync(), this.fullSync(),
); );
@@ -873,26 +861,24 @@ export default class MainBackground {
this.vaultSettingsService = new VaultSettingsService(this.stateProvider); this.vaultSettingsService = new VaultSettingsService(this.stateProvider);
if (!this.popupOnlyContext) { this.vaultTimeoutService = new VaultTimeoutService(
this.vaultTimeoutService = new VaultTimeoutService( this.accountService,
this.accountService, this.masterPasswordService,
this.masterPasswordService, this.cipherService,
this.cipherService, this.folderService,
this.folderService, this.collectionService,
this.collectionService, this.platformUtilsService,
this.platformUtilsService, this.messagingService,
this.messagingService, this.searchService,
this.searchService, this.stateService,
this.stateService, this.authService,
this.authService, this.vaultTimeoutSettingsService,
this.vaultTimeoutSettingsService, this.stateEventRunnerService,
this.stateEventRunnerService, this.taskSchedulerService,
this.taskSchedulerService, this.logService,
this.logService, lockedCallback,
lockedCallback, logoutCallback,
logoutCallback, );
);
}
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);
@@ -913,59 +899,41 @@ export default class MainBackground {
this.providerService = new ProviderService(this.stateProvider); this.providerService = new ProviderService(this.stateProvider);
if (this.popupOnlyContext) { this.syncService = new DefaultSyncService(
this.syncService = new ForegroundSyncService( this.masterPasswordService,
this.stateService, this.accountService,
this.folderService, this.apiService,
this.folderApiService, this.domainSettingsService,
this.messagingService, this.folderService,
this.logService, this.cipherService,
this.cipherService, this.keyService,
this.collectionService, this.collectionService,
this.apiService, this.messagingService,
this.accountService, this.policyService,
this.authService, this.sendService,
this.sendService, this.logService,
this.sendApiService, this.keyConnectorService,
messageListener, this.stateService,
this.stateProvider, this.providerService,
); this.folderApiService,
} else { this.organizationService,
this.syncService = new DefaultSyncService( this.sendApiService,
this.masterPasswordService, this.userDecryptionOptionsService,
this.accountService, this.avatarService,
this.apiService, logoutCallback,
this.domainSettingsService, this.billingAccountProfileStateService,
this.folderService, this.tokenService,
this.cipherService, this.authService,
this.keyService, this.stateProvider,
this.collectionService, );
this.messagingService,
this.policyService, this.syncServiceListener = new SyncServiceListener(
this.sendService, this.syncService,
this.logService, messageListener,
this.keyConnectorService, this.messagingService,
this.stateService, this.logService,
this.providerService, );
this.folderApiService,
this.organizationService,
this.sendApiService,
this.userDecryptionOptionsService,
this.avatarService,
logoutCallback,
this.billingAccountProfileStateService,
this.tokenService,
this.authService,
this.stateProvider,
);
this.syncServiceListener = new SyncServiceListener(
this.syncService,
messageListener,
this.messagingService,
this.logService,
);
}
this.eventUploadService = new EventUploadService( this.eventUploadService = new EventUploadService(
this.apiService, this.apiService,
this.stateProvider, this.stateProvider,
@@ -1112,122 +1080,128 @@ export default class MainBackground {
this.isSafari = this.platformUtilsService.isSafari(); this.isSafari = this.platformUtilsService.isSafari();
// Background // Background
if (!this.popupOnlyContext) {
this.fido2Background = new Fido2Background(
this.logService,
this.fido2ActiveRequestManager,
this.fido2ClientService,
this.vaultSettingsService,
this.scriptInjectorService,
this.configService,
this.authService,
);
const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService); this.fido2Background = new Fido2Background(
this.logService,
this.fido2ActiveRequestManager,
this.fido2ClientService,
this.vaultSettingsService,
this.scriptInjectorService,
this.configService,
this.authService,
);
this.runtimeBackground = new RuntimeBackground( const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService);
this,
this.autofillService,
this.platformUtilsService as BrowserPlatformUtilsService,
this.notificationsService,
this.autofillSettingsService,
this.processReloadService,
this.environmentService,
this.messagingService,
this.logService,
this.configService,
messageListener,
this.accountService,
lockService,
);
this.nativeMessagingBackground = new NativeMessagingBackground(
this.keyService,
this.encryptService,
this.cryptoFunctionService,
this.runtimeBackground,
this.messagingService,
this.appIdService,
this.platformUtilsService,
this.logService,
this.authService,
this.biometricStateService,
this.accountService,
);
this.commandsBackground = new CommandsBackground(
this,
this.platformUtilsService,
this.vaultTimeoutService,
this.authService,
() => this.generatePasswordToClipboard(),
);
this.notificationBackground = new NotificationBackground(
this.autofillService,
this.cipherService,
this.authService,
this.policyService,
this.folderService,
this.userNotificationSettingsService,
this.domainSettingsService,
this.environmentService,
this.logService,
this.themeStateService,
this.configService,
this.accountService,
);
this.overlayNotificationsBackground = new OverlayNotificationsBackground( this.runtimeBackground = new RuntimeBackground(
this.logService, this,
this.configService, this.autofillService,
this.notificationBackground, this.platformUtilsService as BrowserPlatformUtilsService,
); this.notificationsService,
this.autofillSettingsService,
this.processReloadService,
this.environmentService,
this.messagingService,
this.logService,
this.configService,
messageListener,
this.accountService,
lockService,
);
this.nativeMessagingBackground = new NativeMessagingBackground(
this.keyService,
this.encryptService,
this.cryptoFunctionService,
this.runtimeBackground,
this.messagingService,
this.appIdService,
this.platformUtilsService,
this.logService,
this.authService,
this.biometricStateService,
this.accountService,
);
this.commandsBackground = new CommandsBackground(
this,
this.platformUtilsService,
this.vaultTimeoutService,
this.authService,
() => this.generatePasswordToClipboard(),
);
this.notificationBackground = new NotificationBackground(
this.autofillService,
this.cipherService,
this.authService,
this.policyService,
this.folderService,
this.userNotificationSettingsService,
this.domainSettingsService,
this.environmentService,
this.logService,
this.themeStateService,
this.configService,
this.accountService,
);
this.filelessImporterBackground = new FilelessImporterBackground( this.overlayNotificationsBackground = new OverlayNotificationsBackground(
this.configService, this.logService,
this.authService, this.configService,
this.policyService, this.notificationBackground,
this.notificationBackground, );
this.importService,
this.syncService,
this.scriptInjectorService,
);
this.autoSubmitLoginBackground = new AutoSubmitLoginBackground( this.filelessImporterBackground = new FilelessImporterBackground(
this.logService, this.configService,
this.autofillService, this.authService,
this.scriptInjectorService, this.policyService,
this.authService, this.notificationBackground,
this.configService, this.importService,
this.platformUtilsService, this.syncService,
this.policyService, this.scriptInjectorService,
); );
const contextMenuClickedHandler = new ContextMenuClickedHandler( this.autoSubmitLoginBackground = new AutoSubmitLoginBackground(
(options) => this.platformUtilsService.copyToClipboard(options.text), this.logService,
async () => this.generatePasswordToClipboard(), this.autofillService,
async (tab, cipher) => { this.scriptInjectorService,
this.loginToAutoFill = cipher; this.authService,
if (tab == null) { this.configService,
return; this.platformUtilsService,
} this.policyService,
);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. const contextMenuClickedHandler = new ContextMenuClickedHandler(
// eslint-disable-next-line @typescript-eslint/no-floating-promises (options) => this.platformUtilsService.copyToClipboard(options.text),
BrowserApi.tabSendMessage(tab, { async (_tab) => {
command: "collectPageDetails", const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
tab: tab, const password = await this.passwordGenerationService.generatePassword(options);
sender: "contextMenu", this.platformUtilsService.copyToClipboard(password);
}); // 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
this.authService, this.passwordGenerationService.addHistory(password);
this.cipherService, },
this.totpService, async (tab, cipher) => {
this.eventCollectionService, this.loginToAutoFill = cipher;
this.userVerificationService, if (tab == null) {
this.accountService, return;
); }
this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler); // 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
BrowserApi.tabSendMessage(tab, {
command: "collectPageDetails",
tab: tab,
sender: "contextMenu",
});
},
this.authService,
this.cipherService,
this.totpService,
this.eventCollectionService,
this.userVerificationService,
this.accountService,
);
this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler);
this.idleBackground = new IdleBackground( this.idleBackground = new IdleBackground(
this.vaultTimeoutService, this.vaultTimeoutService,
@@ -1246,29 +1220,27 @@ export default class MainBackground {
this.stateProvider, this.stateProvider,
); );
if (!this.popupOnlyContext) { this.mainContextMenuHandler = new MainContextMenuHandler(
this.mainContextMenuHandler = new MainContextMenuHandler( this.stateService,
this.stateService, this.autofillSettingsService,
this.autofillSettingsService, this.i18nService,
this.i18nService, this.logService,
this.logService, this.billingAccountProfileStateService,
this.billingAccountProfileStateService, );
);
this.cipherContextMenuHandler = new CipherContextMenuHandler( this.cipherContextMenuHandler = new CipherContextMenuHandler(
this.mainContextMenuHandler, this.mainContextMenuHandler,
this.authService, this.authService,
this.cipherService,
);
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
this.webRequestBackground = new WebRequestBackground(
this.platformUtilsService,
this.cipherService, this.cipherService,
this.authService,
chrome.webRequest,
); );
if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) {
this.webRequestBackground = new WebRequestBackground(
this.platformUtilsService,
this.cipherService,
this.authService,
chrome.webRequest,
);
}
} }
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.keyService); this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.keyService);
@@ -1283,7 +1255,7 @@ export default class MainBackground {
this.containerService.attachToGlobal(self); this.containerService.attachToGlobal(self);
// Only the "true" background should run migrations // Only the "true" background should run migrations
await this.stateService.init({ runMigrations: !this.popupOnlyContext }); await this.stateService.init({ runMigrations: true });
// This is here instead of in in the InitService b/c we don't plan for // This is here instead of in in the InitService b/c we don't plan for
// side effects to run in the Browser InitService. // side effects to run in the Browser InitService.
@@ -1305,10 +1277,6 @@ export default class MainBackground {
this.popupViewCacheBackgroundService.startObservingTabChanges(); this.popupViewCacheBackgroundService.startObservingTabChanges();
if (this.popupOnlyContext) {
return;
}
await this.vaultTimeoutService.init(true); await this.vaultTimeoutService.init(true);
this.fido2Background.init(); this.fido2Background.init();
await this.runtimeBackground.init(); await this.runtimeBackground.init();
@@ -1637,7 +1605,6 @@ export default class MainBackground {
*/ */
async initOverlayAndTabsBackground() { async initOverlayAndTabsBackground() {
if ( if (
this.popupOnlyContext ||
this.overlayBackground || this.overlayBackground ||
this.tabsBackground || this.tabsBackground ||
(await firstValueFrom(this.authService.activeAccountStatus$)) === (await firstValueFrom(this.authService.activeAccountStatus$)) ===

View File

@@ -24,8 +24,8 @@ import {
LockComponentService, LockComponentService,
} from "@bitwarden/auth/angular"; } from "@bitwarden/auth/angular";
import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common"; import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
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 { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.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";
@@ -92,9 +92,15 @@ import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/imp
import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service"; import { PrimarySecondaryStorageService } from "@bitwarden/common/platform/storage/primary-secondary-storage.service";
import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service";
import { SyncService } from "@bitwarden/common/platform/sync"; import { SyncService } from "@bitwarden/common/platform/sync";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import {
FolderService as FolderServiceAbstraction,
InternalFolderService,
} from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import { DialogService, ToastService } from "@bitwarden/components"; import { DialogService, ToastService } from "@bitwarden/components";
@@ -107,7 +113,6 @@ import { ExtensionAnonLayoutWrapperDataService } from "../../auth/popup/extensio
import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service"; import { ExtensionLoginComponentService } from "../../auth/popup/login/extension-login-component.service";
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service"; import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
import AutofillService from "../../autofill/services/autofill.service"; import AutofillService from "../../autofill/services/autofill.service";
import MainBackground from "../../background/main.background";
import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics"; import { ForegroundBrowserBiometricsService } from "../../key-management/biometrics/foreground-browser-biometrics";
import { BrowserKeyService } from "../../key-management/browser-key.service"; import { BrowserKeyService } from "../../key-management/browser-key.service";
import { BrowserApi } from "../../platform/browser/browser-api"; import { BrowserApi } from "../../platform/browser/browser-api";
@@ -117,12 +122,12 @@ import { ChromeMessageSender } from "../../platform/messaging/chrome-message.sen
/* eslint-enable no-restricted-imports */ /* eslint-enable no-restricted-imports */
import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document"; import { OffscreenDocumentService } from "../../platform/offscreen-document/abstractions/offscreen-document";
import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service"; import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service"; import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
import { PopupViewCacheService } from "../../platform/popup/view-cache/popup-view-cache.service"; import { PopupViewCacheService } from "../../platform/popup/view-cache/popup-view-cache.service";
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
import BrowserMemoryStorageService from "../../platform/services/browser-memory-storage.service";
import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service";
import I18nService from "../../platform/services/i18n.service"; import I18nService from "../../platform/services/i18n.service";
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service"; import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
@@ -130,6 +135,7 @@ import { BrowserSdkClientFactory } from "../../platform/services/sdk/browser-sdk
import { ForegroundTaskSchedulerService } from "../../platform/services/task-scheduler/foreground-task-scheduler.service"; import { ForegroundTaskSchedulerService } from "../../platform/services/task-scheduler/foreground-task-scheduler.service";
import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider"; import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider";
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service"; import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
import { ForegroundSyncService } from "../../platform/sync/foreground-sync.service";
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
import { ExtensionLockComponentService } from "../../services/extension-lock-component.service"; import { ExtensionLockComponentService } from "../../services/extension-lock-component.service";
import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service"; import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service";
@@ -151,26 +157,6 @@ const DISK_BACKUP_LOCAL_STORAGE = new SafeInjectionToken<
AbstractStorageService & ObservableStorageService AbstractStorageService & ObservableStorageService
>("DISK_BACKUP_LOCAL_STORAGE"); >("DISK_BACKUP_LOCAL_STORAGE");
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
const mainBackground: MainBackground = needsBackgroundInit
? createLocalBgService()
: BrowserApi.getBackgroundPage().bitwardenMain;
function createLocalBgService() {
const localBgService = new MainBackground(true);
// 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
localBgService.bootstrap();
return localBgService;
}
/** @deprecated This method needs to be removed as part of MV3 conversion. Please do not add more and actively try to remove usages */
function getBgService<T>(service: keyof MainBackground) {
return (): T => {
return mainBackground ? (mainBackground[service] as any as T) : null;
};
}
/** /**
* Provider definitions used in the ngModule. * Provider definitions used in the ngModule.
* Add your provider definition here using the safeProvider function as a wrapper. This will give you type safety. * Add your provider definition here using the safeProvider function as a wrapper. This will give you type safety.
@@ -307,8 +293,23 @@ const safeProviders: SafeProvider[] = [
}), }),
safeProvider({ safeProvider({
provide: SyncService, provide: SyncService,
useFactory: getBgService<SyncService>("syncService"), useClass: ForegroundSyncService,
deps: [], deps: [
StateService,
InternalFolderService,
FolderApiServiceAbstraction,
MessageSender,
LogService,
CipherService,
CollectionService,
ApiService,
AccountServiceAbstraction,
AuthService,
InternalSendService,
SendApiService,
MessageListener,
StateProvider,
],
}), }),
safeProvider({ safeProvider({
provide: DomainSettingsService, provide: DomainSettingsService,
@@ -358,11 +359,6 @@ const safeProviders: SafeProvider[] = [
useClass: ForegroundVaultTimeoutService, useClass: ForegroundVaultTimeoutService,
deps: [MessagingServiceAbstraction], deps: [MessagingServiceAbstraction],
}), }),
safeProvider({
provide: NotificationsService,
useFactory: getBgService<NotificationsService>("notificationsService"),
deps: [],
}),
safeProvider({ safeProvider({
provide: VaultFilterService, provide: VaultFilterService,
useClass: VaultFilterService, useClass: VaultFilterService,
@@ -382,8 +378,8 @@ const safeProviders: SafeProvider[] = [
}), }),
safeProvider({ safeProvider({
provide: MEMORY_STORAGE, provide: MEMORY_STORAGE,
useFactory: getBgService<AbstractStorageService>("memoryStorageService"), useFactory: (memoryStorage: AbstractStorageService) => memoryStorage,
deps: [], deps: [OBSERVABLE_MEMORY_STORAGE],
}), }),
safeProvider({ safeProvider({
provide: OBSERVABLE_MEMORY_STORAGE, provide: OBSERVABLE_MEMORY_STORAGE,
@@ -392,9 +388,7 @@ const safeProviders: SafeProvider[] = [
return new ForegroundMemoryStorageService(); return new ForegroundMemoryStorageService();
} }
return getBgService<AbstractStorageService & ObservableStorageService>( return new BrowserMemoryStorageService();
"memoryStorageForStateProviders",
)();
}, },
deps: [], deps: [],
}), }),
@@ -407,9 +401,7 @@ const safeProviders: SafeProvider[] = [
return regularMemoryStorageService; return regularMemoryStorageService;
} }
return getBgService<AbstractStorageService & ObservableStorageService>( return new ForegroundMemoryStorageService();
"largeObjectMemoryStorageForStateProviders",
)();
}, },
deps: [OBSERVABLE_MEMORY_STORAGE], deps: [OBSERVABLE_MEMORY_STORAGE],
}), }),
@@ -494,15 +486,7 @@ const safeProviders: SafeProvider[] = [
}), }),
safeProvider({ safeProvider({
provide: INTRAPROCESS_MESSAGING_SUBJECT, provide: INTRAPROCESS_MESSAGING_SUBJECT,
useFactory: () => { useFactory: () => new Subject<Message<Record<string, unknown>>>(),
if (BrowserPopupUtils.backgroundInitializationRequired()) {
// There is no persistent main background which means we have one in memory,
// we need the same instance that our in memory background is utilizing.
return getBgService("intraprocessMessagingSubject")();
} else {
return new Subject<Message<Record<string, unknown>>>();
}
},
deps: [], deps: [],
}), }),
safeProvider({ safeProvider({
@@ -514,23 +498,6 @@ const safeProviders: SafeProvider[] = [
), ),
deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService], deps: [INTRAPROCESS_MESSAGING_SUBJECT, LogService],
}), }),
safeProvider({
provide: INTRAPROCESS_MESSAGING_SUBJECT,
useFactory: () => {
if (needsBackgroundInit) {
// We will have created a popup within this context, in that case
// we want to make sure we have the same subject as that context so we
// can message with it.
return getBgService("intraprocessMessagingSubject")();
} else {
// There isn't a locally created background so we will communicate with
// the true background through chrome apis, in that case, we can just create
// one for ourself.
return new Subject<Message<Record<string, unknown>>>();
}
},
deps: [],
}),
safeProvider({ safeProvider({
provide: DISK_BACKUP_LOCAL_STORAGE, provide: DISK_BACKUP_LOCAL_STORAGE,
useFactory: (diskStorage: AbstractStorageService & ObservableStorageService) => useFactory: (diskStorage: AbstractStorageService & ObservableStorageService) =>
@@ -572,13 +539,7 @@ const safeProviders: SafeProvider[] = [
}), }),
safeProvider({ safeProvider({
provide: ForegroundTaskSchedulerService, provide: ForegroundTaskSchedulerService,
useFactory: (logService: LogService, stateProvider: StateProvider) => { useClass: ForegroundTaskSchedulerService,
if (needsBackgroundInit) {
return getBgService<ForegroundTaskSchedulerService>("taskSchedulerService")();
}
return new ForegroundTaskSchedulerService(logService, stateProvider);
},
deps: [LogService, StateProvider], deps: [LogService, StateProvider],
}), }),
safeProvider({ safeProvider({

View File

@@ -486,7 +486,7 @@ export class MembersComponent extends BaseMembersComponent<OrganizationUserView>
const enableUpgradePasswordManagerSub = await firstValueFrom( const enableUpgradePasswordManagerSub = await firstValueFrom(
this.enableUpgradePasswordManagerSub$, this.enableUpgradePasswordManagerSub$,
); );
if (enableUpgradePasswordManagerSub) { if (enableUpgradePasswordManagerSub && this.organization.canEditSubscription) {
const reference = openChangePlanDialog(this.dialogService, { const reference = openChangePlanDialog(this.dialogService, {
data: { data: {
organizationId: this.organization.id, organizationId: this.organization.id,

View File

@@ -15,7 +15,7 @@
{{ "refresh" | i18n }} {{ "refresh" | i18n }}
</a> </a>
</div> </div>
<bit-tab-group [(selectedIndex)]="tabIndex"> <bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}"> <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
<tools-all-applications></tools-all-applications> <tools-all-applications></tools-all-applications>
</bit-tab> </bit-tab>

View File

@@ -1,8 +1,7 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components";
@@ -18,7 +17,7 @@ import { PasswordHealthComponent } from "./password-health.component";
export enum AccessIntelligenceTabType { export enum AccessIntelligenceTabType {
AllApps = 0, AllApps = 0,
PriorityApps = 1, CriticalApps = 1,
NotifiedMembers = 2, NotifiedMembers = 2,
} }
@@ -58,8 +57,19 @@ export class AccessIntelligenceComponent {
); );
} }
constructor(route: ActivatedRoute) { onTabChange = async (newIndex: number) => {
route.queryParams.pipe(takeUntilDestroyed(), first()).subscribe(({ tabIndex }) => { await this.router.navigate([], {
relativeTo: this.route,
queryParams: { tabIndex: newIndex },
queryParamsHandling: "merge",
});
};
constructor(
protected route: ActivatedRoute,
private router: Router,
) {
route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
this.tabIndex = !isNaN(tabIndex) ? tabIndex : AccessIntelligenceTabType.AllApps; this.tabIndex = !isNaN(tabIndex) ? tabIndex : AccessIntelligenceTabType.AllApps;
}); });
} }

View File

@@ -14,13 +14,15 @@
</h2> </h2>
</ng-container> </ng-container>
<ng-container slot="description"> <ng-container slot="description">
<p class="tw-text-muted"> <div class="tw-flex tw-flex-col tw-mb-2">
{{ "noAppsInOrgDescription" | i18n }} <span class="tw-text-muted">
{{ "noAppsInOrgDescription" | i18n }}
</span>
<a class="text-primary" routerLink="/login">{{ "learnMore" | i18n }}</a> <a class="text-primary" routerLink="/login">{{ "learnMore" | i18n }}</a>
</p> </div>
</ng-container> </ng-container>
<ng-container slot="button"> <ng-container slot="button">
<button bitButton buttonType="primary" type="button"> <button (click)="goToCreateNewLoginItem()" bitButton buttonType="primary" type="button">
{{ "createNewLoginItem" | i18n }} {{ "createNewLoginItem" | i18n }}
</button> </button>
</ng-container> </ng-container>
@@ -50,7 +52,15 @@
class="tw-grow" class="tw-grow"
[formControl]="searchControl" [formControl]="searchControl"
></bit-search> ></bit-search>
<button class="tw-rounded-lg" type="button" buttonType="secondary" bitButton> <button
class="tw-rounded-lg"
type="button"
buttonType="secondary"
bitButton
[disabled]="!selectedIds.size"
[loading]="markingAsCritical"
(click)="markAppsAsCritical()"
>
<i class="bwi bwi-star-f tw-mr-2"></i> <i class="bwi bwi-star-f tw-mr-2"></i>
{{ "markAppAsCritical" | i18n }} {{ "markAppAsCritical" | i18n }}
</button> </button>

View File

@@ -40,6 +40,7 @@ export class AllApplicationsComponent implements OnInit {
protected loading = false; protected loading = false;
protected organization: Organization; protected organization: Organization;
noItemsIcon = Icons.Security; noItemsIcon = Icons.Security;
protected markingAsCritical = false;
// MOCK DATA // MOCK DATA
protected mockData = applicationTableMockData; protected mockData = applicationTableMockData;
@@ -76,8 +77,18 @@ export class AllApplicationsComponent implements OnInit {
.subscribe((v) => (this.dataSource.filter = v)); .subscribe((v) => (this.dataSource.filter = v));
} }
goToCreateNewLoginItem = async () => {
// TODO: implement
this.toastService.showToast({
variant: "warning",
title: null,
message: "Not yet implemented",
});
};
markAppsAsCritical = async () => { markAppsAsCritical = async () => {
// TODO: Send to API once implemented // TODO: Send to API once implemented
this.markingAsCritical = true;
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
this.selectedIds.clear(); this.selectedIds.clear();
@@ -87,6 +98,7 @@ export class AllApplicationsComponent implements OnInit {
message: this.i18nService.t("appsMarkedAsCritical"), message: this.i18nService.t("appsMarkedAsCritical"),
}); });
resolve(true); resolve(true);
this.markingAsCritical = false;
}, 1000); }, 1000);
}); });
}; };

View File

@@ -19,7 +19,9 @@
</p> </p>
</ng-container> </ng-container>
<ng-container slot="button"> <ng-container slot="button">
<button bitButton buttonType="primary" type="button">{{ "markCriticalApps" | i18n }}</button> <button (click)="goToAllAppsTab()" bitButton buttonType="primary" type="button">
{{ "markCriticalApps" | i18n }}
</button>
</ng-container> </ng-container>
</bit-no-items> </bit-no-items>
</div> </div>

View File

@@ -1,7 +1,7 @@
import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { Component, DestroyRef, inject, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormControl } from "@angular/forms"; import { FormControl } from "@angular/forms";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { debounceTime, map } from "rxjs"; import { debounceTime, map } from "rxjs";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -12,6 +12,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
import { SharedModule } from "../../shared"; import { SharedModule } from "../../shared";
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module"; import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
import { AccessIntelligenceTabType } from "./access-intelligence.component";
import { applicationTableMockData } from "./application-table.mock"; import { applicationTableMockData } from "./application-table.mock";
@Component({ @Component({
@@ -26,8 +27,10 @@ export class CriticalApplicationsComponent implements OnInit {
protected searchControl = new FormControl("", { nonNullable: true }); protected searchControl = new FormControl("", { nonNullable: true });
private destroyRef = inject(DestroyRef); private destroyRef = inject(DestroyRef);
protected loading = false; protected loading = false;
protected organizationId: string;
noItemsIcon = Icons.Security; noItemsIcon = Icons.Security;
// MOCK DATA // MOCK DATA
protected mockData = applicationTableMockData;
protected mockAtRiskMembersCount = 0; protected mockAtRiskMembersCount = 0;
protected mockAtRiskAppsCount = 0; protected mockAtRiskAppsCount = 0;
protected mockTotalMembersCount = 0; protected mockTotalMembersCount = 0;
@@ -38,18 +41,26 @@ export class CriticalApplicationsComponent implements OnInit {
.pipe( .pipe(
takeUntilDestroyed(this.destroyRef), takeUntilDestroyed(this.destroyRef),
map(async (params) => { map(async (params) => {
// const organizationId = params.get("organizationId"); this.organizationId = params.get("organizationId");
// TODO: use organizationId to fetch data // TODO: use organizationId to fetch data
}), }),
) )
.subscribe(); .subscribe();
} }
goToAllAppsTab = async () => {
await this.router.navigate([`organizations/${this.organizationId}/access-intelligence`], {
queryParams: { tabIndex: AccessIntelligenceTabType.AllApps },
queryParamsHandling: "merge",
});
};
constructor( constructor(
protected i18nService: I18nService, protected i18nService: I18nService,
protected activatedRoute: ActivatedRoute, protected activatedRoute: ActivatedRoute,
protected router: Router,
) { ) {
this.dataSource.data = applicationTableMockData; this.dataSource.data = []; //applicationTableMockData;
this.searchControl.valueChanges this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed()) .pipe(debounceTime(200), takeUntilDestroyed())
.subscribe((v) => (this.dataSource.filter = v)); .subscribe((v) => (this.dataSource.filter = v));

View File

@@ -216,7 +216,7 @@ describe("UserVerificationService", () => {
}); });
it("returns if verification is successful", async () => { it("returns if verification is successful", async () => {
keyService.compareAndUpdateKeyHash.mockResolvedValueOnce(true); keyService.compareKeyHash.mockResolvedValueOnce(true);
const result = await sut.verifyUserByMasterPassword( const result = await sut.verifyUserByMasterPassword(
{ {
@@ -227,7 +227,7 @@ describe("UserVerificationService", () => {
"email", "email",
); );
expect(keyService.compareAndUpdateKeyHash).toHaveBeenCalled(); expect(keyService.compareKeyHash).toHaveBeenCalled();
expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith( expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith(
"localHash", "localHash",
mockUserId, mockUserId,
@@ -240,7 +240,7 @@ describe("UserVerificationService", () => {
}); });
it("throws if verification fails", async () => { it("throws if verification fails", async () => {
keyService.compareAndUpdateKeyHash.mockResolvedValueOnce(false); keyService.compareKeyHash.mockResolvedValueOnce(false);
await expect( await expect(
sut.verifyUserByMasterPassword( sut.verifyUserByMasterPassword(
@@ -253,7 +253,7 @@ describe("UserVerificationService", () => {
), ),
).rejects.toThrow("Invalid master password"); ).rejects.toThrow("Invalid master password");
expect(keyService.compareAndUpdateKeyHash).toHaveBeenCalled(); expect(keyService.compareKeyHash).toHaveBeenCalled();
expect(masterPasswordService.setMasterKeyHash).not.toHaveBeenCalledWith(); expect(masterPasswordService.setMasterKeyHash).not.toHaveBeenCalledWith();
expect(masterPasswordService.setMasterKey).not.toHaveBeenCalledWith(); expect(masterPasswordService.setMasterKey).not.toHaveBeenCalledWith();
}); });
@@ -285,7 +285,7 @@ describe("UserVerificationService", () => {
"email", "email",
); );
expect(keyService.compareAndUpdateKeyHash).not.toHaveBeenCalled(); expect(keyService.compareKeyHash).not.toHaveBeenCalled();
expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith( expect(masterPasswordService.setMasterKeyHash).toHaveBeenCalledWith(
"localHash", "localHash",
mockUserId, mockUserId,
@@ -318,7 +318,7 @@ describe("UserVerificationService", () => {
), ),
).rejects.toThrow("Invalid master password"); ).rejects.toThrow("Invalid master password");
expect(keyService.compareAndUpdateKeyHash).not.toHaveBeenCalled(); expect(keyService.compareKeyHash).not.toHaveBeenCalled();
expect(masterPasswordService.setMasterKeyHash).not.toHaveBeenCalledWith(); expect(masterPasswordService.setMasterKeyHash).not.toHaveBeenCalledWith();
expect(masterPasswordService.setMasterKey).not.toHaveBeenCalledWith(); expect(masterPasswordService.setMasterKey).not.toHaveBeenCalledWith();
}); });

View File

@@ -206,9 +206,10 @@ export class UserVerificationService implements UserVerificationServiceAbstracti
let policyOptions: MasterPasswordPolicyResponse | null; let policyOptions: MasterPasswordPolicyResponse | null;
// Client-side verification // Client-side verification
if (await this.hasMasterPasswordAndMasterKeyHash(userId)) { if (await this.hasMasterPasswordAndMasterKeyHash(userId)) {
const passwordValid = await this.keyService.compareAndUpdateKeyHash( const passwordValid = await this.keyService.compareKeyHash(
verification.secret, verification.secret,
masterKey, masterKey,
userId,
); );
if (!passwordValid) { if (!passwordValid) {
throw new Error(this.i18nService.t("invalidMasterPassword")); throw new Error(this.i18nService.t("invalidMasterPassword"));

View File

@@ -204,14 +204,18 @@ export abstract class KeyService {
hashPurpose?: HashPurpose, hashPurpose?: HashPurpose,
): Promise<string>; ): Promise<string>;
/** /**
* Compares the provided master password to the stored password hash and server password hash. * Compares the provided master password to the stored password hash.
* Updates the stored hash if outdated.
* @param masterPassword The user's master password * @param masterPassword The user's master password
* @param key The user's master key * @param key The user's master key
* @param userId The id of the user to do the operation for.
* @returns True if the provided master password matches either the stored * @returns True if the provided master password matches either the stored
* key hash or the server key hash * key hash or the server key hash
*/ */
abstract compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean>; abstract compareKeyHash(
masterPassword: string,
masterKey: MasterKey,
userId: UserId,
): Promise<boolean>;
/** /**
* Stores the encrypted organization keys and clears any decrypted * Stores the encrypted organization keys and clears any decrypted
* organization keys currently in memory * organization keys currently in memory

View File

@@ -733,4 +733,63 @@ describe("keyService", () => {
}); });
}); });
}); });
describe("compareKeyHash", () => {
type TestCase = {
masterKey: MasterKey;
masterPassword: string | null;
storedMasterKeyHash: string;
mockReturnedHash: string;
expectedToMatch: boolean;
};
const data: TestCase[] = [
{
masterKey: makeSymmetricCryptoKey(64),
masterPassword: "my_master_password",
storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: true,
},
{
masterKey: makeSymmetricCryptoKey(64),
masterPassword: null,
storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: false,
},
{
masterKey: makeSymmetricCryptoKey(64),
masterPassword: null,
storedMasterKeyHash: null,
mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: false,
},
];
it.each(data)(
"returns expected match value when calculated hash equals stored hash",
async ({
masterKey,
masterPassword,
storedMasterKeyHash,
mockReturnedHash,
expectedToMatch,
}) => {
masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash);
cryptoFunctionService.pbkdf2
.calledWith(masterKey.key, masterPassword, "sha256", 2)
.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash));
const actualDidMatch = await keyService.compareKeyHash(
masterPassword,
masterKey,
mockUserId,
);
expect(actualDidMatch).toBe(expectedToMatch);
},
);
});
}); });

View File

@@ -319,34 +319,43 @@ export class DefaultKeyService implements KeyServiceAbstraction {
} }
// TODO: move to MasterPasswordService // TODO: move to MasterPasswordService
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> { async compareKeyHash(
const userId = await firstValueFrom(this.stateProvider.activeUserId$); masterPassword: string,
masterKey: MasterKey,
userId: UserId,
): Promise<boolean> {
if (masterKey == null) {
throw new Error("'masterKey' is required to be non-null.");
}
if (masterPassword == null) {
// If they don't give us a master password, we can't hash it, and therefore
// it will never match what we have stored.
return false;
}
// Retrieve the current password hash
const storedPasswordHash = await firstValueFrom( const storedPasswordHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId), this.masterPasswordService.masterKeyHash$(userId),
); );
if (masterPassword != null && storedPasswordHash != null) {
const localKeyHash = await this.hashMasterKey(
masterPassword,
masterKey,
HashPurpose.LocalAuthorization,
);
if (localKeyHash != null && storedPasswordHash === localKeyHash) {
return true;
}
// TODO: remove serverKeyHash check in 1-2 releases after everyone's keyHash has been updated if (storedPasswordHash == null) {
const serverKeyHash = await this.hashMasterKey( return false;
masterPassword,
masterKey,
HashPurpose.ServerAuthorization,
);
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
return true;
}
} }
return false; // Hash the key for local use
const localKeyHash = await this.hashMasterKey(
masterPassword,
masterKey,
HashPurpose.LocalAuthorization,
);
// Check if the stored hash is already equal to the hash we create locally
if (localKeyHash == null || storedPasswordHash !== localKeyHash) {
return false;
}
return true;
} }
async setOrgKeys( async setOrgKeys(

View File

@@ -1,8 +1,10 @@
import { DialogRef } from "@angular/cdk/dialog"; import { DialogRef } from "@angular/cdk/dialog";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { import {
@@ -43,16 +45,25 @@ export class PasswordRepromptComponent {
protected i18nService: I18nService, protected i18nService: I18nService,
protected formBuilder: FormBuilder, protected formBuilder: FormBuilder,
protected dialogRef: DialogRef, protected dialogRef: DialogRef,
protected accountService: AccountService,
) {} ) {}
submit = async () => { submit = async () => {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id)));
if (userId == null) {
throw new Error("An active user is expected while doing password reprompt.");
}
const storedMasterKey = await this.keyService.getOrDeriveMasterKey( const storedMasterKey = await this.keyService.getOrDeriveMasterKey(
this.formGroup.value.masterPassword, this.formGroup.value.masterPassword,
userId,
); );
if ( if (
!(await this.keyService.compareAndUpdateKeyHash( !(await this.keyService.compareKeyHash(
this.formGroup.value.masterPassword, this.formGroup.value.masterPassword,
storedMasterKey, storedMasterKey,
userId,
)) ))
) { ) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(

10
package-lock.json generated
View File

@@ -69,7 +69,7 @@
"rxjs": "7.8.1", "rxjs": "7.8.1",
"tabbable": "6.2.0", "tabbable": "6.2.0",
"tldts": "6.1.58", "tldts": "6.1.58",
"utf-8-validate": "6.0.4", "utf-8-validate": "6.0.5",
"zone.js": "0.14.10", "zone.js": "0.14.10",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },
@@ -319,6 +319,7 @@
"license": "GPL-3.0" "license": "GPL-3.0"
}, },
"libs/tools/export/vault-export/vault-export-core": { "libs/tools/export/vault-export/vault-export-core": {
"name": "@bitwarden/vault-export-core",
"version": "0.0.0", "version": "0.0.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
@@ -34702,10 +34703,11 @@
} }
}, },
"node_modules/utf-8-validate": { "node_modules/utf-8-validate": {
"version": "6.0.4", "version": "6.0.5",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.5.tgz",
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==", "integrity": "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT",
"dependencies": { "dependencies": {
"node-gyp-build": "^4.3.0" "node-gyp-build": "^4.3.0"
}, },

View File

@@ -203,7 +203,7 @@
"rxjs": "7.8.1", "rxjs": "7.8.1",
"tabbable": "6.2.0", "tabbable": "6.2.0",
"tldts": "6.1.58", "tldts": "6.1.58",
"utf-8-validate": "6.0.4", "utf-8-validate": "6.0.5",
"zone.js": "0.14.10", "zone.js": "0.14.10",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },