diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index cf83b6e78d1..56c81ec5add 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -247,7 +247,8 @@ export default class MainBackground { return promise.then((result) => result.response === "unlocked"); } - } + }, + window ); this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true); diff --git a/apps/browser/src/background/service_factories/autofill-service.factory.ts b/apps/browser/src/background/service_factories/autofill-service.factory.ts new file mode 100644 index 00000000000..1d67a2735be --- /dev/null +++ b/apps/browser/src/background/service_factories/autofill-service.factory.ts @@ -0,0 +1,37 @@ +import { AutofillService as AbstractAutoFillService } from "../../services/abstractions/autofill.service"; +import AutofillService from "../../services/autofill.service"; + +import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory"; +import { EventServiceInitOptions, eventServiceFactory } from "./event-service.factory"; +import { CachedServices, factory, FactoryOptions } from "./factory-options"; +import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; +import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; +import { totpServiceFacotry, TotpServiceInitOptions } from "./totp-service.factory"; + +type AutoFillServiceOptions = FactoryOptions; + +export type AutoFillServiceInitOptions = AutoFillServiceOptions & + CipherServiceInitOptions & + StateServiceInitOptions & + TotpServiceInitOptions & + EventServiceInitOptions & + LogServiceInitOptions; + +export function autofillServiceFactory( + cache: { autofillService?: AbstractAutoFillService } & CachedServices, + opts: AutoFillServiceInitOptions +): Promise { + return factory( + cache, + "autofillService", + opts, + async () => + new AutofillService( + await cipherServiceFactory(cache, opts), + await stateServiceFactory(cache, opts), + await totpServiceFacotry(cache, opts), + await eventServiceFactory(cache, opts), + await logServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/background/service_factories/cipher-service.factory.ts b/apps/browser/src/background/service_factories/cipher-service.factory.ts index 149ac54fc82..03141f2c84f 100644 --- a/apps/browser/src/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/background/service_factories/cipher-service.factory.ts @@ -44,7 +44,7 @@ export function cipherServiceFactory( await apiServiceFactory(cache, opts), await fileUploadServiceFactory(cache, opts), await i18nServiceFactory(cache, opts), - opts.cipherServiceOptions.searchServiceFactory === undefined + opts.cipherServiceOptions?.searchServiceFactory === undefined ? () => cache.searchService : opts.cipherServiceOptions.searchServiceFactory, await logServiceFactory(cache, opts), diff --git a/apps/browser/src/background/service_factories/event-service.factory.ts b/apps/browser/src/background/service_factories/event-service.factory.ts new file mode 100644 index 00000000000..61a82ebeb19 --- /dev/null +++ b/apps/browser/src/background/service_factories/event-service.factory.ts @@ -0,0 +1,40 @@ +import { EventService as AbstractEventService } from "@bitwarden/common/abstractions/event.service"; +import { EventService } from "@bitwarden/common/services/event.service"; + +import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; +import { cipherServiceFactory, CipherServiceInitOptions } from "./cipher-service.factory"; +import { FactoryOptions, CachedServices, factory } from "./factory-options"; +import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; +import { + organizationServiceFactory, + OrganizationServiceInitOptions, +} from "./organization-service.factory"; +import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; + +type EventServiceOptions = FactoryOptions; + +export type EventServiceInitOptions = EventServiceOptions & + ApiServiceInitOptions & + CipherServiceInitOptions & + StateServiceInitOptions & + LogServiceInitOptions & + OrganizationServiceInitOptions; + +export function eventServiceFactory( + cache: { eventService?: AbstractEventService } & CachedServices, + opts: EventServiceInitOptions +): Promise { + return factory( + cache, + "eventService", + opts, + async () => + new EventService( + await apiServiceFactory(cache, opts), + await cipherServiceFactory(cache, opts), + await stateServiceFactory(cache, opts), + await logServiceFactory(cache, opts), + await organizationServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/background/service_factories/platform-utils-service.factory.ts b/apps/browser/src/background/service_factories/platform-utils-service.factory.ts index 6d85f126361..da25e51ce0c 100644 --- a/apps/browser/src/background/service_factories/platform-utils-service.factory.ts +++ b/apps/browser/src/background/service_factories/platform-utils-service.factory.ts @@ -28,7 +28,8 @@ export function platformUtilsServiceFactory( new BrowserPlatformUtilsService( await messagingServiceFactory(cache, opts), opts.platformUtilsServiceOptions.clipboardWriteCallback, - opts.platformUtilsServiceOptions.biometricCallback + opts.platformUtilsServiceOptions.biometricCallback, + opts.platformUtilsServiceOptions.win ) ); } diff --git a/apps/browser/src/background/service_factories/totp-service.factory.ts b/apps/browser/src/background/service_factories/totp-service.factory.ts new file mode 100644 index 00000000000..fe2f5c74905 --- /dev/null +++ b/apps/browser/src/background/service_factories/totp-service.factory.ts @@ -0,0 +1,31 @@ +import { TotpService as AbstractTotpService } from "@bitwarden/common/abstractions/totp.service"; +import { TotpService } from "@bitwarden/common/services/totp.service"; + +import { + cryptoFunctionServiceFactory, + CryptoFunctionServiceInitOptions, +} from "./crypto-function-service.factory"; +import { CachedServices, factory, FactoryOptions } from "./factory-options"; +import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; + +type TotpServiceOptions = FactoryOptions; + +export type TotpServiceInitOptions = TotpServiceOptions & + CryptoFunctionServiceInitOptions & + LogServiceInitOptions; + +export function totpServiceFacotry( + cache: { totpService?: AbstractTotpService } & CachedServices, + opts: TotpServiceInitOptions +): Promise { + return factory( + cache, + "totpService", + opts, + async () => + new TotpService( + await cryptoFunctionServiceFactory(cache, opts), + await logServiceFactory(cache, opts) + ) + ); +} diff --git a/apps/browser/src/listeners/onCommandListener.ts b/apps/browser/src/listeners/onCommandListener.ts index 2a33e91e578..294ea51a963 100644 --- a/apps/browser/src/listeners/onCommandListener.ts +++ b/apps/browser/src/listeners/onCommandListener.ts @@ -1,27 +1,15 @@ +import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { GlobalState } from "@bitwarden/common/models/domain/globalState"; -import { AuthService } from "@bitwarden/common/services/auth.service"; -import { CipherService } from "@bitwarden/common/services/cipher.service"; -import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; -import { NoopEventService } from "@bitwarden/common/services/noopEvent.service"; -import { SearchService } from "@bitwarden/common/services/search.service"; -import { SettingsService } from "@bitwarden/common/services/settings.service"; -import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service"; -import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFunction.service"; +import { authServiceFactory } from "../background/service_factories/auth-service.factory"; +import { autofillServiceFactory } from "../background/service_factories/autofill-service.factory"; +import { CachedServices } from "../background/service_factories/factory-options"; +import { logServiceFactory } from "../background/service_factories/log-service.factory"; +import { BrowserApi } from "../browser/browserApi"; import { AutoFillActiveTabCommand } from "../commands/autoFillActiveTabCommand"; import { Account } from "../models/account"; -import { StateService as AbstractStateService } from "../services/abstractions/state.service"; -import AutofillService from "../services/autofill.service"; -import { BrowserCryptoService } from "../services/browserCrypto.service"; -import BrowserLocalStorageService from "../services/browserLocalStorage.service"; -import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; -import I18nService from "../services/i18n.service"; -import { KeyGenerationService } from "../services/keyGeneration.service"; -import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service"; -import { StateService } from "../services/state.service"; export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) => { switch (command) { @@ -32,100 +20,44 @@ export const onCommandListener = async (command: string, tab: chrome.tabs.Tab) = }; const doAutoFillLogin = async (tab: chrome.tabs.Tab): Promise => { - const logService = new ConsoleLogService(false); - - const cryptoFunctionService = new WebCryptoFunctionService(self); - - const storageService = new BrowserLocalStorageService(); - - const secureStorageService = new BrowserLocalStorageService(); - - const memoryStorageService = new LocalBackedSessionStorageService( - new EncryptService(cryptoFunctionService, logService, false), - new KeyGenerationService(cryptoFunctionService) - ); - - const stateFactory = new StateFactory(GlobalState, Account); - - const stateMigrationService = new StateMigrationService( - storageService, - secureStorageService, - stateFactory - ); - - const stateService: AbstractStateService = new StateService( - storageService, - secureStorageService, - memoryStorageService, // AbstractStorageService - logService, - stateMigrationService, - stateFactory - ); - - await stateService.init(); - - const platformUtils = new BrowserPlatformUtilsService( - null, // MessagingService - null, // clipboardWriteCallback - null // biometricCallback - ); - - const cryptoService = new BrowserCryptoService( - cryptoFunctionService, - null, // AbstractEncryptService - platformUtils, - logService, - stateService - ); - - const settingsService = new SettingsService(stateService); - - const i18nService = new I18nService(chrome.i18n.getUILanguage()); - - await i18nService.init(); - - // Don't love this pt.1 - let searchService: SearchService = null; - - const cipherService = new CipherService( - cryptoService, - settingsService, - null, // ApiService - null, // FileUploadService, - i18nService, - () => searchService, // Don't love this pt.2 - logService, - stateService - ); - - // Don't love this pt.3 - searchService = new SearchService(cipherService, logService, i18nService); - - // TODO: Remove this before we encourage anyone to start using this - const eventService = new NoopEventService(); - - const autofillService = new AutofillService( - cipherService, - stateService, - null, // TotpService - eventService, - logService - ); - - const authService = new AuthService( - cryptoService, // CryptoService - null, // ApiService - null, // TokenService - null, // AppIdService - platformUtils, - null, // MessagingService - logService, - null, // KeyConnectorService - null, // EnvironmentService - stateService, - null, // TwoFactorService - i18nService - ); + const cachedServices: CachedServices = {}; + const opts = { + cryptoFunctionServiceOptions: { + win: self, + }, + encryptServiceOptions: { + logMacFailures: true, + }, + logServiceOptions: { + isDev: false, + }, + platformUtilsServiceOptions: { + clipboardWriteCallback: () => Promise.resolve(), + biometricCallback: () => Promise.resolve(false), + win: self, + }, + stateServiceOptions: { + stateFactory: new StateFactory(GlobalState, Account), + }, + stateMigrationServiceOptions: { + stateFactory: new StateFactory(GlobalState, Account), + }, + apiServiceOptions: { + logoutCallback: () => Promise.resolve(), + }, + keyConnectorServiceOptions: { + logoutCallback: () => Promise.resolve(), + }, + i18nServiceOptions: { + systemLanguage: BrowserApi.getUILanguage(self), + }, + cipherServiceOptions: { + searchServiceFactory: null as () => SearchService, // No dependence on search service + }, + }; + const logService = await logServiceFactory(cachedServices, opts); + const authService = await authServiceFactory(cachedServices, opts); + const autofillService = await autofillServiceFactory(cachedServices, opts); const authStatus = await authService.getAuthStatus(); if (authStatus < AuthenticationStatus.Unlocked) { diff --git a/apps/browser/src/services/autofill.service.ts b/apps/browser/src/services/autofill.service.ts index ff8eca93475..b403a2a679c 100644 --- a/apps/browser/src/services/autofill.service.ts +++ b/apps/browser/src/services/autofill.service.ts @@ -172,14 +172,10 @@ export default class AutofillService implements AutofillServiceInterface { } else { cipher = await this.cipherService.getLastUsedForUrl(tab.url, true); } - - if (cipher == null) { - return null; - } } - if (cipher.reprompt !== CipherRepromptType.None) { - return; + if (cipher == null || cipher.reprompt !== CipherRepromptType.None) { + return null; } const totpCode = await this.doAutoFill({ diff --git a/apps/browser/src/services/browserPlatformUtils.service.spec.ts b/apps/browser/src/services/browserPlatformUtils.service.spec.ts index 21034bcfa47..1f557dc7426 100644 --- a/apps/browser/src/services/browserPlatformUtils.service.spec.ts +++ b/apps/browser/src/services/browserPlatformUtils.service.spec.ts @@ -16,7 +16,7 @@ describe("Browser Utils Service", () => { let browserPlatformUtilsService: BrowserPlatformUtilsService; beforeEach(() => { (window as any).matchMedia = jest.fn().mockReturnValueOnce({}); - browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null); + browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null, self); }); afterEach(() => { diff --git a/apps/browser/src/services/browserPlatformUtils.service.ts b/apps/browser/src/services/browserPlatformUtils.service.ts index a9f1c35567d..48c305a91e7 100644 --- a/apps/browser/src/services/browserPlatformUtils.service.ts +++ b/apps/browser/src/services/browserPlatformUtils.service.ts @@ -19,7 +19,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService constructor( private messagingService: MessagingService, private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void, - private biometricCallback: () => Promise + private biometricCallback: () => Promise, + private win: Window & typeof globalThis ) {} getDevice(): DeviceType { @@ -33,8 +34,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService ) { this.deviceCache = DeviceType.FirefoxExtension; } else if ( - (self.opr && self.opr.addons) || - self.opera || + (!!this.win.opr && !!opr.addons) || + !!this.win.opera || navigator.userAgent.indexOf(" OPR/") >= 0 ) { this.deviceCache = DeviceType.OperaExtension; @@ -42,7 +43,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService this.deviceCache = DeviceType.EdgeExtension; } else if (navigator.userAgent.indexOf(" Vivaldi/") !== -1) { this.deviceCache = DeviceType.VivaldiExtension; - } else if (window.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) { + } else if (this.win.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1) { this.deviceCache = DeviceType.ChromeExtension; } else if (navigator.userAgent.indexOf(" Safari/") !== -1) { this.deviceCache = DeviceType.SafariExtension; @@ -178,8 +179,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService } copyToClipboard(text: string, options?: any): void { - let win = window; - let doc = window.document; + let win = this.win; + let doc = this.win.document; if (options && (options.window || options.win)) { win = options.window || options.win; doc = win.document; @@ -238,8 +239,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService } async readFromClipboard(options?: any): Promise { - let win = window; - let doc = window.document; + let win = this.win; + let doc = this.win.document; if (options && (options.window || options.win)) { win = options.window || options.win; doc = win.document; @@ -335,7 +336,7 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService } sidebarViewName(): string { - if (window.chrome.sidebarAction && this.isFirefox()) { + if (this.win.chrome.sidebarAction && this.isFirefox()) { return "sidebar"; } else if (this.isOpera() && typeof opr !== "undefined" && opr.sidebarAction) { return "sidebar_panel"; diff --git a/libs/common/src/services/noopEvent.service.ts b/libs/common/src/services/noopEvent.service.ts deleted file mode 100644 index 9a49d5a8061..00000000000 --- a/libs/common/src/services/noopEvent.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EventService } from "../abstractions/event.service"; -import { EventType } from "../enums/eventType"; - -/** - * If you want to use this, don't. - * If you think you should use that after the warning, don't. - */ -export class NoopEventService implements EventService { - constructor() { - if (chrome.runtime.getManifest().manifest_version !== 3) { - throw new Error("You are not allowed to use this when not in manifest_version 3"); - } - } - - collect(eventType: EventType, cipherId?: string, uploadImmediately?: boolean) { - return Promise.resolve(); - } - uploadEvents(userId?: string) { - return Promise.resolve(); - } - clearEvents(userId?: string) { - return Promise.resolve(); - } -}