1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 23:33:31 +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,6 +457,18 @@ export default class MainBackground {
this.offscreenDocumentService, this.offscreenDocumentService,
); );
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)) {
// manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3
this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session
this.memoryStorageService = this.memoryStorageForStateProviders;
} else {
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
this.memoryStorageService = this.memoryStorageForStateProviders;
}
if (BrowserApi.isManifestVersion(3)) {
// Creates a session key for mv3 storage of large memory items // Creates a session key for mv3 storage of large memory items
const sessionKey = new Lazy(async () => { const sessionKey = new Lazy(async () => {
// Key already in session storage // Key already in session storage
@@ -482,42 +491,20 @@ export default class MainBackground {
return derivedKey; return derivedKey;
}); });
const mv3MemoryStorageCreator = () => { this.largeObjectMemoryStorageForStateProviders = new LocalBackedSessionStorageService(
if (this.popupOnlyContext) { sessionKey,
return new ForegroundMemoryStorageService(); this.storageService,
}
// For local backed session storage, we expect that the encrypted data on disk will persist longer than the encryption key in memory // 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` // 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. // so that MAC failures are not logged.
return new LocalBackedSessionStorageService(
sessionKey,
this.storageService,
new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false),
this.platformUtilsService, this.platformUtilsService,
this.logService, 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
if (BrowserApi.isManifestVersion(3)) {
// manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3
this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session
this.memoryStorageService = this.memoryStorageForStateProviders;
} else { } else {
if (popupOnlyContext) { // mv2 stores to the same location
this.memoryStorageForStateProviders = new ForegroundMemoryStorageService(); this.largeObjectMemoryStorageForStateProviders = this.memoryStorageForStateProviders;
this.memoryStorageService = new ForegroundMemoryStorageService();
} else {
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
this.memoryStorageService = this.memoryStorageForStateProviders;
} }
}
this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
? mv3MemoryStorageCreator() // mv3 stores to local-backed session storage
: this.memoryStorageForStateProviders; // mv2 stores to the same location
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,7 +861,6 @@ 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,
@@ -892,7 +879,6 @@ export default class MainBackground {
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,24 +899,6 @@ export default class MainBackground {
this.providerService = new ProviderService(this.stateProvider); this.providerService = new ProviderService(this.stateProvider);
if (this.popupOnlyContext) {
this.syncService = new ForegroundSyncService(
this.stateService,
this.folderService,
this.folderApiService,
this.messagingService,
this.logService,
this.cipherService,
this.collectionService,
this.apiService,
this.accountService,
this.authService,
this.sendService,
this.sendApiService,
messageListener,
this.stateProvider,
);
} else {
this.syncService = new DefaultSyncService( this.syncService = new DefaultSyncService(
this.masterPasswordService, this.masterPasswordService,
this.accountService, this.accountService,
@@ -965,7 +933,7 @@ export default class MainBackground {
this.messagingService, this.messagingService,
this.logService, this.logService,
); );
}
this.eventUploadService = new EventUploadService( this.eventUploadService = new EventUploadService(
this.apiService, this.apiService,
this.stateProvider, this.stateProvider,
@@ -1112,7 +1080,7 @@ export default class MainBackground {
this.isSafari = this.platformUtilsService.isSafari(); this.isSafari = this.platformUtilsService.isSafari();
// Background // Background
if (!this.popupOnlyContext) {
this.fido2Background = new Fido2Background( this.fido2Background = new Fido2Background(
this.logService, this.logService,
this.fido2ActiveRequestManager, this.fido2ActiveRequestManager,
@@ -1203,7 +1171,14 @@ export default class MainBackground {
const contextMenuClickedHandler = new ContextMenuClickedHandler( const contextMenuClickedHandler = new ContextMenuClickedHandler(
(options) => this.platformUtilsService.copyToClipboard(options.text), (options) => this.platformUtilsService.copyToClipboard(options.text),
async () => this.generatePasswordToClipboard(), async (_tab) => {
const options = (await this.passwordGenerationService.getOptions())?.[0] ?? {};
const password = await this.passwordGenerationService.generatePassword(options);
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.passwordGenerationService.addHistory(password);
},
async (tab, cipher) => { async (tab, cipher) => {
this.loginToAutoFill = cipher; this.loginToAutoFill = cipher;
if (tab == null) { if (tab == null) {
@@ -1227,7 +1202,6 @@ export default class MainBackground {
); );
this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler); this.contextMenusBackground = new ContextMenusBackground(contextMenuClickedHandler);
}
this.idleBackground = new IdleBackground( this.idleBackground = new IdleBackground(
this.vaultTimeoutService, this.vaultTimeoutService,
@@ -1246,7 +1220,6 @@ 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,
@@ -1269,7 +1242,6 @@ export default class MainBackground {
chrome.webRequest, 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">
<span class="tw-text-muted">
{{ "noAppsInOrgDescription" | i18n }} {{ "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,36 +319,45 @@ 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) {
if (storedPasswordHash == null) {
return false;
}
// Hash the key for local use
const localKeyHash = await this.hashMasterKey( const localKeyHash = await this.hashMasterKey(
masterPassword, masterPassword,
masterKey, masterKey,
HashPurpose.LocalAuthorization, HashPurpose.LocalAuthorization,
); );
if (localKeyHash != null && storedPasswordHash === localKeyHash) {
return true;
}
// TODO: remove serverKeyHash check in 1-2 releases after everyone's keyHash has been updated
const serverKeyHash = await this.hashMasterKey(
masterPassword,
masterKey,
HashPurpose.ServerAuthorization,
);
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
return true;
}
}
// Check if the stored hash is already equal to the hash we create locally
if (localKeyHash == null || storedPasswordHash !== localKeyHash) {
return false; return false;
} }
return true;
}
async setOrgKeys( async setOrgKeys(
orgs: ProfileOrganizationResponse[], orgs: ProfileOrganizationResponse[],
providerOrgs: ProfileProviderOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[],

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"
}, },