diff --git a/apps/browser/src/auth/popup/sso.component.ts b/apps/browser/src/auth/popup/sso.component.ts index 430bd855f1..228c7401fd 100644 --- a/apps/browser/src/auth/popup/sso.component.ts +++ b/apps/browser/src/auth/popup/sso.component.ts @@ -12,7 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -44,7 +44,7 @@ export class SsoComponent extends BaseSsoComponent { environmentService: EnvironmentService, logService: LogService, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, protected authService: AuthService, @Inject(WINDOW) private win: Window, ) { diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor.component.ts index da2c3482fd..94dfb5155b 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor.component.ts @@ -16,7 +16,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -59,7 +59,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { appIdService: AppIdService, loginService: LoginService, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, ssoLoginService: SsoLoginServiceAbstraction, private dialogService: DialogService, @Inject(WINDOW) protected win: Window, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 73c4356f69..c2c8c5be72 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -70,6 +70,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -93,6 +94,7 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; @@ -201,7 +203,6 @@ import { BrowserApi } from "../platform/browser/browser-api"; import { flagEnabled } from "../platform/flags"; import { UpdateBadge } from "../platform/listeners/update-badge"; import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service"; -import { BrowserConfigService } from "../platform/services/browser-config.service"; import { BrowserCryptoService } from "../platform/services/browser-crypto.service"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; import BrowserLocalStorageService from "../platform/services/browser-local-storage.service"; @@ -293,7 +294,7 @@ export default class MainBackground { avatarService: AvatarServiceAbstraction; mainContextMenuHandler: MainContextMenuHandler; cipherContextMenuHandler: CipherContextMenuHandler; - configService: BrowserConfigService; + configService: ConfigService; configApiService: ConfigApiServiceAbstraction; devicesApiService: DevicesApiServiceAbstraction; devicesService: DevicesServiceAbstraction; @@ -609,16 +610,13 @@ export default class MainBackground { this.userVerificationApiService = new UserVerificationApiService(this.apiService); - this.configApiService = new ConfigApiService(this.apiService, this.authService); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - this.configService = new BrowserConfigService( - this.stateService, + this.configService = new DefaultConfigService( this.configApiService, - this.authService, this.environmentService, this.logService, this.stateProvider, - true, ); this.cipherService = new CipherService( @@ -1005,7 +1003,6 @@ export default class MainBackground { this.filelessImporterBackground.init(); await this.commandsBackground.init(); - this.configService.init(); this.twoFactorService.init(); await this.overlayBackground.init(); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 0a94e0a79a..dd55c14fb2 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -3,7 +3,7 @@ import { firstValueFrom } from "rxjs"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -46,7 +46,7 @@ export default class RuntimeBackground { private environmentService: BrowserEnvironmentService, private messagingService: MessagingService, private logService: LogService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private fido2Service: Fido2Service, ) { // onInstalled listener must be wired up before anything else, so we do it in the ctor @@ -136,7 +136,7 @@ export default class RuntimeBackground { await this.main.refreshBadge(); await this.main.refreshMenu(); }, 2000); - this.configService.triggerServerConfigFetch(); + await this.configService.ensureConfigFetched(); } break; case "openPopup": diff --git a/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts b/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts index c0dbf1f475..3d7d508832 100644 --- a/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/config-api.service.factory.ts @@ -2,9 +2,9 @@ import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstract import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; import { - authServiceFactory, - AuthServiceInitOptions, -} from "../../../auth/background/service-factories/auth-service.factory"; + tokenServiceFactory, + TokenServiceInitOptions, +} from "../../../auth/background/service-factories/token-service.factory"; import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; import { FactoryOptions, CachedServices, factory } from "./factory-options"; @@ -13,7 +13,7 @@ type ConfigApiServiceFactoyOptions = FactoryOptions; export type ConfigApiServiceInitOptions = ConfigApiServiceFactoyOptions & ApiServiceInitOptions & - AuthServiceInitOptions; + TokenServiceInitOptions; export function configApiServiceFactory( cache: { configApiService?: ConfigApiServiceAbstraction } & CachedServices, @@ -26,7 +26,7 @@ export function configApiServiceFactory( async () => new ConfigApiService( await apiServiceFactory(cache, opts), - await authServiceFactory(cache, opts), + await tokenServiceFactory(cache, opts), ), ); } diff --git a/apps/browser/src/platform/background/service-factories/config-service.factory.ts b/apps/browser/src/platform/background/service-factories/config-service.factory.ts index 4e31fb3141..a899f8fd9a 100644 --- a/apps/browser/src/platform/background/service-factories/config-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/config-service.factory.ts @@ -1,10 +1,5 @@ -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; - -import { - authServiceFactory, - AuthServiceInitOptions, -} from "../../../auth/background/service-factories/auth-service.factory"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { configApiServiceFactory, ConfigApiServiceInitOptions } from "./config-api.service.factory"; import { @@ -13,39 +8,30 @@ import { } from "./environment-service.factory"; import { FactoryOptions, CachedServices, factory } from "./factory-options"; import { logServiceFactory, LogServiceInitOptions } from "./log-service.factory"; -import { stateProviderFactory } from "./state-provider.factory"; -import { stateServiceFactory, StateServiceInitOptions } from "./state-service.factory"; +import { stateProviderFactory, StateProviderInitOptions } from "./state-provider.factory"; -type ConfigServiceFactoryOptions = FactoryOptions & { - configServiceOptions?: { - subscribe?: boolean; - }; -}; +type ConfigServiceFactoryOptions = FactoryOptions; export type ConfigServiceInitOptions = ConfigServiceFactoryOptions & - StateServiceInitOptions & ConfigApiServiceInitOptions & - AuthServiceInitOptions & EnvironmentServiceInitOptions & - LogServiceInitOptions; + LogServiceInitOptions & + StateProviderInitOptions; export function configServiceFactory( - cache: { configService?: ConfigServiceAbstraction } & CachedServices, + cache: { configService?: ConfigService } & CachedServices, opts: ConfigServiceInitOptions, -): Promise { +): Promise { return factory( cache, "configService", opts, async () => - new ConfigService( - await stateServiceFactory(cache, opts), + new DefaultConfigService( await configApiServiceFactory(cache, opts), - await authServiceFactory(cache, opts), await environmentServiceFactory(cache, opts), await logServiceFactory(cache, opts), await stateProviderFactory(cache, opts), - opts.configServiceOptions?.subscribe ?? true, ), ); } diff --git a/apps/browser/src/platform/services/browser-config.service.ts b/apps/browser/src/platform/services/browser-config.service.ts deleted file mode 100644 index be8d087f3b..0000000000 --- a/apps/browser/src/platform/services/browser-config.service.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ReplaySubject } from "rxjs"; - -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; -import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; -import { StateProvider } from "@bitwarden/common/platform/state"; - -import { browserSession, sessionSync } from "../decorators/session-sync-observable"; - -@browserSession -export class BrowserConfigService extends ConfigService { - @sessionSync({ initializer: ServerConfig.fromJSON }) - protected _serverConfig: ReplaySubject; - - constructor( - stateService: StateService, - configApiService: ConfigApiServiceAbstraction, - authService: AuthService, - environmentService: EnvironmentService, - logService: LogService, - stateProvider: StateProvider, - subscribe = false, - ) { - super( - stateService, - configApiService, - authService, - environmentService, - logService, - stateProvider, - subscribe, - ); - } -} diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index b0e80ab960..4036ace31f 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -5,7 +5,6 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; @@ -19,7 +18,6 @@ export class InitService { private stateService: StateServiceAbstraction, private logService: LogServiceAbstraction, private themingService: AbstractThemingService, - private configService: ConfigService, @Inject(DOCUMENT) private document: Document, ) {} @@ -55,7 +53,6 @@ export class InitService { this.logService.info("Force redraw is on"); } - this.configService.init(); this.setupVaultPopupHeartbeat(); }; } diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 25db6f78ca..7ab04603e4 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -46,17 +46,13 @@ import { UserNotificationSettingsService, UserNotificationSettingsServiceAbstraction, } from "@bitwarden/common/autofill/services/user-notification-settings.service"; -import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { - LogService, - LogService as LogServiceAbstraction, -} from "@bitwarden/common/platform/abstractions/log.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; @@ -66,7 +62,6 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; @@ -95,7 +90,6 @@ import { Account } from "../../models/account"; import { BrowserApi } from "../../platform/browser/browser-api"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service"; -import { BrowserConfigService } from "../../platform/services/browser-config.service"; import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service"; import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service"; import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service"; @@ -186,7 +180,7 @@ function getBgService(service: keyof MainBackground) { i18nService, ); }, - deps: [LogServiceAbstraction, I18nServiceAbstraction], + deps: [LogService, I18nServiceAbstraction], }, { provide: CipherFileUploadService, @@ -205,7 +199,7 @@ function getBgService(service: keyof MainBackground) { deps: [], }, { - provide: LogServiceAbstraction, + provide: LogService, useFactory: (platformUtilsService: PlatformUtilsService) => new ConsoleLogService(platformUtilsService.isDev()), deps: [PlatformUtilsService], @@ -367,7 +361,7 @@ function getBgService(service: keyof MainBackground) { storageService: AbstractStorageService, secureStorageService: AbstractStorageService, memoryStorageService: AbstractMemoryStorageService, - logService: LogServiceAbstraction, + logService: LogService, accountService: AccountServiceAbstraction, environmentService: EnvironmentService, tokenService: TokenService, @@ -389,7 +383,7 @@ function getBgService(service: keyof MainBackground) { AbstractStorageService, SECURE_STORAGE, MEMORY_STORAGE, - LogServiceAbstraction, + LogService, AccountServiceAbstraction, EnvironmentService, TokenService, @@ -430,18 +424,6 @@ function getBgService(service: keyof MainBackground) { }, deps: [PlatformUtilsService], }, - { - provide: ConfigService, - useClass: BrowserConfigService, - deps: [ - StateServiceAbstraction, - ConfigApiServiceAbstraction, - AuthServiceAbstraction, - EnvironmentService, - StateProvider, - LogService, - ], - }, { provide: FilePopoutUtilsService, useFactory: (platformUtilsService: PlatformUtilsService) => { diff --git a/apps/browser/src/popup/settings/about.component.ts b/apps/browser/src/popup/settings/about.component.ts index 4cabb183ae..61b5749b51 100644 --- a/apps/browser/src/popup/settings/about.component.ts +++ b/apps/browser/src/popup/settings/about.component.ts @@ -3,7 +3,7 @@ import { Component } from "@angular/core"; import { combineLatest, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { ButtonModule, DialogModule } from "@bitwarden/components"; @@ -24,7 +24,7 @@ export class AboutComponent { ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); constructor( - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private environmentService: EnvironmentService, ) {} } diff --git a/apps/browser/src/tools/background/fileless-importer.background.spec.ts b/apps/browser/src/tools/background/fileless-importer.background.spec.ts index d3436099ef..858889b887 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.spec.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.spec.ts @@ -4,7 +4,7 @@ import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Importer, ImportResult, ImportServiceAbstraction } from "@bitwarden/importer/core"; diff --git a/apps/browser/src/tools/background/fileless-importer.background.ts b/apps/browser/src/tools/background/fileless-importer.background.ts index 3ddc7bd1b7..57c2faa930 100644 --- a/apps/browser/src/tools/background/fileless-importer.background.ts +++ b/apps/browser/src/tools/background/fileless-importer.background.ts @@ -5,7 +5,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ImportServiceAbstraction } from "@bitwarden/importer/core"; @@ -55,7 +55,7 @@ class FilelessImporterBackground implements FilelessImporterBackgroundInterface * @param syncService - Used to trigger a full sync after the import is completed. */ constructor( - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private authService: AuthService, private policyService: PolicyService, private notificationBackground: NotificationBackground, diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index 8e52d44069..b27a986231 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -11,7 +11,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve 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 { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -68,7 +68,7 @@ export class AddEditComponent extends BaseAddEditComponent { sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, - configService: ConfigServiceAbstraction, + configService: ConfigService, ) { super( cipherService, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index e610f39954..ce2152ffbf 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -47,6 +47,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { @@ -60,6 +61,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/services/broadcaster.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { CryptoService } from "@bitwarden/common/platform/services/crypto.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; @@ -131,7 +133,6 @@ import { VaultExportServiceAbstraction, } from "@bitwarden/vault-export-core"; -import { CliConfigService } from "./platform/services/cli-config.service"; import { CliPlatformUtilsService } from "./platform/services/cli-platform-utils.service"; import { ConsoleLogService } from "./platform/services/console-log.service"; import { I18nService } from "./platform/services/i18n.service"; @@ -214,7 +215,7 @@ export class Main { deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction; authRequestService: AuthRequestService; configApiService: ConfigApiServiceAbstraction; - configService: CliConfigService; + configService: ConfigService; accountService: AccountService; globalStateProvider: GlobalStateProvider; singleUserStateProvider: SingleUserStateProvider; @@ -504,16 +505,13 @@ export class Main { this.stateService, ); - this.configApiService = new ConfigApiService(this.apiService, this.authService); + this.configApiService = new ConfigApiService(this.apiService, this.tokenService); - this.configService = new CliConfigService( - this.stateService, + this.configService = new DefaultConfigService( this.configApiService, - this.authService, this.environmentService, this.logService, this.stateProvider, - true, ); this.cipherService = new CipherService( @@ -714,7 +712,6 @@ export class Main { this.containerService.attachToGlobal(global); await this.i18nService.init(); this.twoFactorService.init(); - this.configService.init(); const installedVersion = await this.stateService.getInstalledVersion(); const currentVersion = await this.platformUtilsService.getApplicationVersion(); diff --git a/apps/cli/src/platform/services/cli-config.service.ts b/apps/cli/src/platform/services/cli-config.service.ts deleted file mode 100644 index 6faa1b12e8..0000000000 --- a/apps/cli/src/platform/services/cli-config.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NEVER } from "rxjs"; - -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; - -export class CliConfigService extends ConfigService { - // The rxjs timer uses setTimeout/setInterval under the hood, which prevents the node process from exiting - // when the command is finished. Cli should never be alive long enough to use the timer, so we disable it. - protected refreshTimer$ = NEVER; -} diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index fa396ab313..196bebfcf7 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -31,7 +31,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -147,7 +147,7 @@ export class AppComponent implements OnInit, OnDestroy { private modalService: ModalService, private keyConnectorService: KeyConnectorService, private userVerificationService: UserVerificationService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, @@ -265,7 +265,7 @@ export class AppComponent implements OnInit, OnDestroy { // 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.updateAppMenu(); - this.configService.triggerServerConfigFetch(); + await this.configService.ensureConfigFetched(); } break; case "openSettings": diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index f45d530edd..bb7d4e7b52 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -11,7 +11,6 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt. import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; @@ -36,7 +35,6 @@ export class InitService { private nativeMessagingService: NativeMessagingService, private themingService: AbstractThemingService, private encryptService: EncryptService, - private configService: ConfigService, @Inject(DOCUMENT) private document: Document, ) {} @@ -70,8 +68,6 @@ export class InitService { const containerService = new ContainerService(this.cryptoService, this.encryptService); containerService.attachToGlobal(this.win); - - this.configService.init(); }; } } diff --git a/apps/desktop/src/auth/sso.component.ts b/apps/desktop/src/auth/sso.component.ts index 0268133192..210319b9ed 100644 --- a/apps/desktop/src/auth/sso.component.ts +++ b/apps/desktop/src/auth/sso.component.ts @@ -8,7 +8,7 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -38,7 +38,7 @@ export class SsoComponent extends BaseSsoComponent { passwordGenerationService: PasswordGenerationServiceAbstraction, logService: LogService, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, ) { super( ssoLoginService, diff --git a/apps/desktop/src/auth/two-factor.component.ts b/apps/desktop/src/auth/two-factor.component.ts index 9b862e7c9f..8b46f3d1b9 100644 --- a/apps/desktop/src/auth/two-factor.component.ts +++ b/apps/desktop/src/auth/two-factor.component.ts @@ -16,7 +16,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -59,7 +59,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { loginService: LoginService, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, @Inject(WINDOW) protected win: Window, ) { super( diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 8532b7462a..b89beebaa6 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -8,7 +8,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve 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 { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -49,7 +49,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnChanges, sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, - configService: ConfigServiceAbstraction, + configService: ConfigService, ) { super( cipherService, diff --git a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts b/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts index 33a3069e1d..63431cd6ab 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/group/group.service.ts @@ -3,7 +3,7 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CoreOrganizationModule } from "../../core-organization.module"; import { GroupView } from "../../views/group.view"; @@ -18,7 +18,7 @@ import { GroupDetailsResponse, GroupResponse } from "./responses/group.response" export class GroupService { constructor( protected apiService: ApiService, - protected configService: ConfigServiceAbstraction, + protected configService: ConfigService, ) {} async get(orgId: string, groupId: string): Promise { @@ -52,7 +52,7 @@ export class GroupService { export class InternalGroupService extends GroupService { constructor( protected apiService: ApiService, - protected configService: ConfigServiceAbstraction, + protected configService: ConfigService, ) { super(apiService, configService); } diff --git a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts index a1d1bc3e23..399140e3ea 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts @@ -6,7 +6,7 @@ import { OrganizationUserUpdateRequest, } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { OrganizationUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CoreOrganizationModule } from "../core-organization.module"; import { OrganizationUserAdminView } from "../views/organization-user-admin-view"; @@ -14,7 +14,7 @@ import { OrganizationUserAdminView } from "../views/organization-user-admin-view @Injectable({ providedIn: CoreOrganizationModule }) export class UserAdminService { constructor( - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private organizationUserService: OrganizationUserService, ) {} diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 90010160aa..1924476327 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -17,7 +17,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { BannerModule, IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components"; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 668b09eb7e..752122de00 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -24,7 +24,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; @@ -148,7 +148,7 @@ export class MemberDialogComponent implements OnDestroy { private userService: UserAdminService, private organizationUserService: OrganizationUserService, private dialogService: DialogService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private accountService: AccountService, organizationService: OrganizationService, ) { diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 8527aa1b17..b218e680e3 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -11,7 +11,7 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/ import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-update.request"; import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -95,7 +95,7 @@ export class AccountComponent { private organizationApiService: OrganizationApiServiceAbstraction, private dialogService: DialogService, private formBuilder: FormBuilder, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, ) {} async ngOnInit() { diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index a1b7456627..23b45618c6 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -16,7 +16,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -83,7 +83,7 @@ export class AppComponent implements OnDestroy, OnInit { private policyService: InternalPolicyService, protected policyListService: PolicyListService, private keyConnectorService: KeyConnectorService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, @@ -158,7 +158,7 @@ export class AppComponent implements OnDestroy, OnInit { break; case "syncCompleted": if (message.successfully) { - this.configService.triggerServerConfigFetch(); + await this.configService.ensureConfigFetched(); } break; case "upgradeOrganization": { diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts index 93ee857617..7eabbbb5c1 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts @@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; @@ -39,7 +39,7 @@ describe("KeyRotationService", () => { let mockCryptoService: MockProxy; let mockEncryptService: MockProxy; let mockStateService: MockProxy; - let mockConfigService: MockProxy; + let mockConfigService: MockProxy; beforeAll(() => { mockApiService = mock(); @@ -52,7 +52,7 @@ describe("KeyRotationService", () => { mockCryptoService = mock(); mockEncryptService = mock(); mockStateService = mock(); - mockConfigService = mock(); + mockConfigService = mock(); keyRotationService = new UserKeyRotationService( mockApiService, diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts index bb4c3494dd..b53c71cb2e 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts @@ -3,7 +3,7 @@ import { firstValueFrom } from "rxjs"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @@ -34,7 +34,7 @@ export class UserKeyRotationService { private cryptoService: CryptoService, private encryptService: EncryptService, private stateService: StateService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, ) {} /** diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts index d20f0cd1bd..9312ce5fc0 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts @@ -6,7 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve 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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -52,7 +52,7 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent { sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, - configService: ConfigServiceAbstraction, + configService: ConfigService, billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( diff --git a/apps/web/src/app/auth/sso.component.ts b/apps/web/src/app/auth/sso.component.ts index 2ef4f3eb15..cdd979aa89 100644 --- a/apps/web/src/app/auth/sso.component.ts +++ b/apps/web/src/app/auth/sso.component.ts @@ -13,7 +13,7 @@ import { OrganizationDomainSsoDetailsResponse } from "@bitwarden/common/admin-co import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -45,7 +45,7 @@ export class SsoComponent extends BaseSsoComponent { private orgDomainApiService: OrgDomainApiServiceAbstraction, private validationService: ValidationService, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, ) { super( ssoLoginService, diff --git a/apps/web/src/app/auth/two-factor.component.ts b/apps/web/src/app/auth/two-factor.component.ts index a47a7a2848..6760ab449f 100644 --- a/apps/web/src/app/auth/two-factor.component.ts +++ b/apps/web/src/app/auth/two-factor.component.ts @@ -15,7 +15,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -49,7 +49,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest loginService: LoginService, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, @Inject(WINDOW) protected win: Window, ) { super( diff --git a/apps/web/src/app/billing/shared/add-credit.component.ts b/apps/web/src/app/billing/shared/add-credit.component.ts index 25d49fac9e..71050a9a6e 100644 --- a/apps/web/src/app/billing/shared/add-credit.component.ts +++ b/apps/web/src/app/billing/shared/add-credit.component.ts @@ -13,7 +13,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @@ -57,7 +57,7 @@ export class AddCreditComponent implements OnInit { private platformUtilsService: PlatformUtilsService, private organizationService: OrganizationService, private logService: LogService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, ) { const payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig; this.ppButtonFormAction = payPalConfig.buttonAction; diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 60f3dea915..d5576d3bf7 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -10,7 +10,6 @@ import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/pla import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; @@ -28,7 +27,6 @@ export class InitService { private cryptoService: CryptoServiceAbstraction, private themingService: AbstractThemingService, private encryptService: EncryptService, - private configService: ConfigService, @Inject(DOCUMENT) private document: Document, ) {} @@ -46,8 +44,6 @@ export class InitService { this.themingService.applyThemeChangesTo(this.document); const containerService = new ContainerService(this.cryptoService, this.encryptService); containerService.attachToGlobal(this.win); - - this.configService.init(); }; } } diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index 2e1813697e..ee30bed0d6 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -9,7 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components"; diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index 357d2217e4..722ab972fc 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -18,7 +18,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -107,7 +107,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private organizationUserService: OrganizationUserService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, ) { diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts index 05659de073..ad80c9f4e5 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.stories.ts @@ -8,7 +8,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; @@ -92,7 +92,7 @@ export default { } as Partial, }, { - provide: ConfigServiceAbstraction, + provide: ConfigService, useValue: { getFeatureFlag() { // does not currently affect any display logic, default all to OFF diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 8332b7e95f..56f18c4a3b 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -9,7 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType, ProductType } from "@bitwarden/common/enums"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -65,7 +65,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, - configService: ConfigServiceAbstraction, + configService: ConfigService, private billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts index 3424717630..8967336f75 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.spec.ts @@ -5,7 +5,7 @@ import { Subject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateProvider } from "@bitwarden/common/platform/state"; @@ -21,7 +21,7 @@ describe("VaultOnboardingComponent", () => { let mockApiService: Partial; let mockPolicyService: MockProxy; let mockI18nService: MockProxy; - let mockConfigService: MockProxy; + let mockConfigService: MockProxy; let mockVaultOnboardingService: MockProxy; let mockStateProvider: Partial; let setInstallExtLinkSpy: any; @@ -34,7 +34,7 @@ describe("VaultOnboardingComponent", () => { mockApiService = { getProfile: jest.fn(), }; - mockConfigService = mock(); + mockConfigService = mock(); mockVaultOnboardingService = mock(); mockStateProvider = { getActive: jest.fn().mockReturnValue( @@ -56,7 +56,7 @@ describe("VaultOnboardingComponent", () => { { provide: VaultOnboardingServiceAbstraction, useValue: mockVaultOnboardingService }, { provide: I18nService, useValue: mockI18nService }, { provide: ApiService, useValue: mockApiService }, - { provide: ConfigServiceAbstraction, useValue: mockConfigService }, + { provide: ConfigService, useValue: mockConfigService }, { provide: StateProvider, useValue: mockStateProvider }, ], }).compileComponents(); diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts index 16f68d6111..dc3a41cf15 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/vault-onboarding.component.ts @@ -17,7 +17,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -55,7 +55,7 @@ export class VaultOnboardingComponent implements OnInit, OnChanges, OnDestroy { protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService, private apiService: ApiService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private vaultOnboardingService: VaultOnboardingServiceAbstraction, ) {} diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 1dc6fdaf1c..92d6e13020 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -41,7 +41,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -180,7 +180,7 @@ export class VaultComponent implements OnInit, OnDestroy { private eventCollectionService: EventCollectionService, private searchService: SearchService, private searchPipe: SearchPipe, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private apiService: ApiService, private userVerificationService: UserVerificationService, private billingAccountProfileStateService: BillingAccountProfileStateService, diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index ba0c65b107..c4213989c6 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -7,7 +7,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve 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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -54,7 +54,7 @@ export class AddEditComponent extends BaseAddEditComponent { sendApiService: SendApiService, dialogService: DialogService, datePipe: DatePipe, - configService: ConfigServiceAbstraction, + configService: ConfigService, billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( diff --git a/apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/bulk-collection-assignment-dialog.component.ts b/apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/bulk-collection-assignment-dialog.component.ts index 04edce8543..091c646178 100644 --- a/apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/bulk-collection-assignment-dialog.component.ts +++ b/apps/web/src/app/vault/org-vault/bulk-collection-assignment-dialog/bulk-collection-assignment-dialog.component.ts @@ -4,7 +4,7 @@ import { Subject } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; @@ -65,7 +65,7 @@ export class BulkCollectionAssignmentDialogComponent implements OnDestroy, OnIni private cipherService: CipherService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private organizationService: OrganizationService, ) {} diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 6691404b3d..028198723b 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -40,7 +40,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -184,7 +184,7 @@ export class VaultComponent implements OnInit, OnDestroy { private totpService: TotpService, private apiService: ApiService, private collectionService: CollectionService, - protected configService: ConfigServiceAbstraction, + protected configService: ConfigService, ) {} async ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts index 5f45379442..b8afe1c235 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts @@ -6,7 +6,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components"; import { ProviderPortalLogo } from "@bitwarden/web-vault/app/admin-console/icons/provider-portal-logo"; import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index 35e3a8bad3..b3d3112bf5 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -5,7 +5,7 @@ import { first } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index d5a1aebdd8..45cfc02a09 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -26,7 +26,7 @@ import { SsoConfigApi } from "@bitwarden/common/auth/models/api/sso-config.api"; import { OrganizationSsoRequest } from "@bitwarden/common/auth/models/request/organization-sso.request"; import { OrganizationSsoResponse } from "@bitwarden/common/auth/models/response/organization-sso.response"; import { SsoConfigView } from "@bitwarden/common/auth/models/view/sso-config.view"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -186,7 +186,7 @@ export class SsoComponent implements OnInit, OnDestroy { private i18nService: I18nService, private organizationService: OrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, ) {} async ngOnInit() { diff --git a/libs/angular/src/auth/components/sso.component.spec.ts b/libs/angular/src/auth/components/sso.component.spec.ts index 82650cb7f1..c5c062d9a7 100644 --- a/libs/angular/src/auth/components/sso.component.spec.ts +++ b/libs/angular/src/auth/components/sso.component.spec.ts @@ -16,7 +16,7 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -66,7 +66,7 @@ describe("SsoComponent", () => { let mockPasswordGenerationService: MockProxy; let mockLogService: MockProxy; let mockUserDecryptionOptionsService: MockProxy; - let mockConfigService: MockProxy; + let mockConfigService: MockProxy; // Mock authService.logIn params let code: string; @@ -107,16 +107,16 @@ describe("SsoComponent", () => { queryParams: mockQueryParams, } as any as ActivatedRoute; - mockSsoLoginService = mock(); - mockStateService = mock(); - mockPlatformUtilsService = mock(); - mockApiService = mock(); - mockCryptoFunctionService = mock(); - mockEnvironmentService = mock(); - mockPasswordGenerationService = mock(); - mockLogService = mock(); - mockUserDecryptionOptionsService = mock(); - mockConfigService = mock(); + mockSsoLoginService = mock(); + mockStateService = mock(); + mockPlatformUtilsService = mock(); + mockApiService = mock(); + mockCryptoFunctionService = mock(); + mockEnvironmentService = mock(); + mockPasswordGenerationService = mock(); + mockLogService = mock(); + mockUserDecryptionOptionsService = mock(); + mockConfigService = mock(); // Mock loginStrategyService.logIn params code = "code"; @@ -198,7 +198,7 @@ describe("SsoComponent", () => { useValue: mockUserDecryptionOptionsService, }, { provide: LogService, useValue: mockLogService }, - { provide: ConfigServiceAbstraction, useValue: mockConfigService }, + { provide: ConfigService, useValue: mockConfigService }, ], }); diff --git a/libs/angular/src/auth/components/sso.component.ts b/libs/angular/src/auth/components/sso.component.ts index f7d5504e08..68d6e72e8d 100644 --- a/libs/angular/src/auth/components/sso.component.ts +++ b/libs/angular/src/auth/components/sso.component.ts @@ -15,7 +15,7 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -65,7 +65,7 @@ export class SsoComponent { protected passwordGenerationService: PasswordGenerationServiceAbstraction, protected logService: LogService, protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - protected configService: ConfigServiceAbstraction, + protected configService: ConfigService, ) {} async ngOnInit() { diff --git a/libs/angular/src/auth/components/two-factor.component.spec.ts b/libs/angular/src/auth/components/two-factor.component.spec.ts index c27ba7082f..9703c7e703 100644 --- a/libs/angular/src/auth/components/two-factor.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor.component.spec.ts @@ -21,7 +21,7 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -62,7 +62,7 @@ describe("TwoFactorComponent", () => { let mockLoginService: MockProxy; let mockUserDecryptionOptionsService: MockProxy; let mockSsoLoginService: MockProxy; - let mockConfigService: MockProxy; + let mockConfigService: MockProxy; let mockUserDecryptionOpts: { noMasterPassword: UserDecryptionOptions; @@ -92,7 +92,7 @@ describe("TwoFactorComponent", () => { mockLoginService = mock(); mockUserDecryptionOptionsService = mock(); mockSsoLoginService = mock(); - mockConfigService = mock(); + mockConfigService = mock(); mockUserDecryptionOpts = { noMasterPassword: new UserDecryptionOptions({ @@ -169,7 +169,7 @@ describe("TwoFactorComponent", () => { useValue: mockUserDecryptionOptionsService, }, { provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService }, - { provide: ConfigServiceAbstraction, useValue: mockConfigService }, + { provide: ConfigService, useValue: mockConfigService }, ], }); diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index 78d1c020b8..f64e591fa2 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -25,7 +25,7 @@ import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -91,7 +91,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI protected loginService: LoginService, protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, protected ssoLoginService: SsoLoginServiceAbstraction, - protected configService: ConfigServiceAbstraction, + protected configService: ConfigService, ) { super(environmentService, i18nService, platformUtilsService); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); diff --git a/libs/angular/src/directives/if-feature.directive.spec.ts b/libs/angular/src/directives/if-feature.directive.spec.ts index 01364b2ada..944410be7d 100644 --- a/libs/angular/src/directives/if-feature.directive.spec.ts +++ b/libs/angular/src/directives/if-feature.directive.spec.ts @@ -4,7 +4,7 @@ import { By } from "@angular/platform-browser"; import { mock, MockProxy } from "jest-mock-extended"; import { FeatureFlag, FeatureFlagValue } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { IfFeatureDirective } from "./if-feature.directive"; @@ -39,7 +39,7 @@ class TestComponent { describe("IfFeatureDirective", () => { let fixture: ComponentFixture; let content: HTMLElement; - let mockConfigService: MockProxy; + let mockConfigService: MockProxy; const mockConfigFlagValue = (flag: FeatureFlag, flagValue: FeatureFlagValue) => { mockConfigService.getFeatureFlag.mockImplementation((f, defaultValue) => @@ -51,14 +51,14 @@ describe("IfFeatureDirective", () => { fixture.debugElement.query(By.css(`[data-testid="${testId}"]`))?.nativeElement; beforeEach(async () => { - mockConfigService = mock(); + mockConfigService = mock(); await TestBed.configureTestingModule({ declarations: [IfFeatureDirective, TestComponent], providers: [ { provide: LogService, useValue: mock() }, { - provide: ConfigServiceAbstraction, + provide: ConfigService, useValue: mockConfigService, }, ], diff --git a/libs/angular/src/directives/if-feature.directive.ts b/libs/angular/src/directives/if-feature.directive.ts index ff08125678..069f306a89 100644 --- a/libs/angular/src/directives/if-feature.directive.ts +++ b/libs/angular/src/directives/if-feature.directive.ts @@ -1,7 +1,7 @@ import { Directive, Input, OnInit, TemplateRef, ViewContainerRef } from "@angular/core"; import { FeatureFlag, FeatureFlagValue } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; /** @@ -30,7 +30,7 @@ export class IfFeatureDirective implements OnInit { constructor( private templateRef: TemplateRef, private viewContainer: ViewContainerRef, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private logService: LogService, ) {} diff --git a/libs/angular/src/platform/guard/feature-flag.guard.spec.ts b/libs/angular/src/platform/guard/feature-flag.guard.spec.ts index 95dd56cd50..88637dff97 100644 --- a/libs/angular/src/platform/guard/feature-flag.guard.spec.ts +++ b/libs/angular/src/platform/guard/feature-flag.guard.spec.ts @@ -5,7 +5,7 @@ import { RouterTestingModule } from "@angular/router/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -21,11 +21,11 @@ describe("canAccessFeature", () => { const featureRoute = "enabled-feature"; const redirectRoute = "redirect"; - let mockConfigService: MockProxy; + let mockConfigService: MockProxy; let mockPlatformUtilsService: MockProxy; const setup = (featureGuard: CanActivateFn, flagValue: any) => { - mockConfigService = mock(); + mockConfigService = mock(); mockPlatformUtilsService = mock(); // Mock the correct getter based on the type of flagValue; also mock default values if one is not provided @@ -56,7 +56,7 @@ describe("canAccessFeature", () => { ]), ], providers: [ - { provide: ConfigServiceAbstraction, useValue: mockConfigService }, + { provide: ConfigService, useValue: mockConfigService }, { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, { provide: LogService, useValue: mock() }, { diff --git a/libs/angular/src/platform/guard/feature-flag.guard.ts b/libs/angular/src/platform/guard/feature-flag.guard.ts index 8842f04152..bfcabc2b53 100644 --- a/libs/angular/src/platform/guard/feature-flag.guard.ts +++ b/libs/angular/src/platform/guard/feature-flag.guard.ts @@ -2,7 +2,7 @@ import { inject } from "@angular/core"; import { CanActivateFn, Router } from "@angular/router"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -23,7 +23,7 @@ export const canAccessFeature = ( redirectUrlOnDisabled?: string, ): CanActivateFn => { return async () => { - const configService = inject(ConfigServiceAbstraction); + const configService = inject(ConfigService); const platformUtilsService = inject(PlatformUtilsService); const router = inject(Router); const i18nService = inject(I18nService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b2aebe20f4..67d38d33de 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -111,7 +111,7 @@ import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -135,7 +135,7 @@ import { Account } from "@bitwarden/common/platform/models/domain/account"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { AppIdService } from "@bitwarden/common/platform/services/app-id.service"; import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service"; -import { ConfigService } from "@bitwarden/common/platform/services/config/config.service"; +import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { CryptoService } from "@bitwarden/common/platform/services/crypto.service"; import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; @@ -400,7 +400,7 @@ const typesafeProviders: Array = [ autofillSettingsService: AutofillSettingsServiceAbstraction, encryptService: EncryptService, fileUploadService: CipherFileUploadServiceAbstraction, - configService: ConfigServiceAbstraction, + configService: ConfigService, ) => new CipherService( cryptoService, @@ -424,7 +424,7 @@ const typesafeProviders: Array = [ AutofillSettingsServiceAbstraction, EncryptService, CipherFileUploadServiceAbstraction, - ConfigServiceAbstraction, + ConfigService, ], }), safeProvider({ @@ -851,25 +851,18 @@ const typesafeProviders: Array = [ deps: [], }), safeProvider({ - provide: ConfigService, - useClass: ConfigService, - deps: [ - StateServiceAbstraction, - ConfigApiServiceAbstraction, - AuthServiceAbstraction, - EnvironmentService, - LogService, - StateProvider, - ], + provide: DefaultConfigService, + useClass: DefaultConfigService, + deps: [ConfigApiServiceAbstraction, EnvironmentService, LogService, StateProvider], }), safeProvider({ - provide: ConfigServiceAbstraction, - useExisting: ConfigService, + provide: ConfigService, + useExisting: DefaultConfigService, }), safeProvider({ provide: ConfigApiServiceAbstraction, useClass: ConfigApiService, - deps: [ApiServiceAbstraction, AuthServiceAbstraction], + deps: [ApiServiceAbstraction, TokenServiceAbstraction], }), safeProvider({ provide: AnonymousHubServiceAbstraction, diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 83131f8fc5..4f5334d176 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -14,7 +14,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; -import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -119,7 +119,7 @@ export class AddEditComponent implements OnInit, OnDestroy { protected dialogService: DialogService, protected win: Window, protected datePipe: DatePipe, - protected configService: ConfigServiceAbstraction, + protected configService: ConfigService, ) { this.typeOptions = [ { name: i18nService.t("typeLogin"), value: CipherType.Login }, diff --git a/libs/common/spec/matchers/to-almost-equal.spec.ts b/libs/common/spec/matchers/to-almost-equal.spec.ts new file mode 100644 index 0000000000..5922545137 --- /dev/null +++ b/libs/common/spec/matchers/to-almost-equal.spec.ts @@ -0,0 +1,54 @@ +describe("toAlmostEqual custom matcher", () => { + it("matches identical Dates", () => { + const date = new Date(); + expect(date).toAlmostEqual(date); + }); + + it("matches when older but within default ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() - 5); + expect(date).toAlmostEqual(olderDate); + }); + + it("matches when newer but within default ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() + 5); + expect(date).toAlmostEqual(olderDate); + }); + + it("doesn't match if older than default ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() - 11); + expect(date).not.toAlmostEqual(olderDate); + }); + + it("doesn't match if newer than default ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() + 11); + expect(date).not.toAlmostEqual(olderDate); + }); + + it("matches when older but within custom ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() - 15); + expect(date).toAlmostEqual(olderDate, 20); + }); + + it("matches when newer but within custom ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() + 15); + expect(date).toAlmostEqual(olderDate, 20); + }); + + it("doesn't match if older than custom ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() - 21); + expect(date).not.toAlmostEqual(olderDate, 20); + }); + + it("doesn't match if newer than custom ms", () => { + const date = new Date(); + const olderDate = new Date(date.getTime() + 21); + expect(date).not.toAlmostEqual(olderDate, 20); + }); +}); diff --git a/libs/common/spec/matchers/to-almost-equal.ts b/libs/common/spec/matchers/to-almost-equal.ts new file mode 100644 index 0000000000..ba5aacc9b3 --- /dev/null +++ b/libs/common/spec/matchers/to-almost-equal.ts @@ -0,0 +1,20 @@ +/** + * Matches the expected date within an optional ms precision + * @param received The received date + * @param expected The expected date + * @param msPrecision The optional precision in milliseconds + */ +export const toAlmostEqual: jest.CustomMatcher = function ( + received: Date, + expected: Date, + msPrecision: number = 10, +) { + const receivedTime = received.getTime(); + const expectedTime = expected.getTime(); + const difference = Math.abs(receivedTime - expectedTime); + return { + pass: difference <= msPrecision, + message: () => + `expected ${received} to be within ${msPrecision}ms of ${expected} (actual difference: ${difference}ms)`, + }; +}; diff --git a/libs/common/spec/observable-tracker.ts b/libs/common/spec/observable-tracker.ts new file mode 100644 index 0000000000..a6f3e6a879 --- /dev/null +++ b/libs/common/spec/observable-tracker.ts @@ -0,0 +1,86 @@ +import { Observable, Subscription, firstValueFrom, throwError, timeout } from "rxjs"; + +/** Test class to enable async awaiting of observable emissions */ +export class ObservableTracker { + private subscription: Subscription; + emissions: T[] = []; + constructor(private observable: Observable) { + this.emissions = this.trackEmissions(observable); + } + + /** Unsubscribes from the observable */ + unsubscribe() { + this.subscription.unsubscribe(); + } + + /** + * Awaits the next emission from the observable, or throws if the timeout is exceeded + * @param msTimeout The maximum time to wait for another emission before throwing + */ + async expectEmission(msTimeout = 50) { + await firstValueFrom( + this.observable.pipe( + timeout({ + first: msTimeout, + with: () => throwError(() => new Error("Timeout exceeded waiting for another emission.")), + }), + ), + ); + } + + /** Awaits until the the total number of emissions observed by this tracker equals or exceeds {@link count} + * @param count The number of emissions to wait for + */ + async pauseUntilReceived(count: number, msTimeout = 50): Promise { + for (let i = 0; i < count - this.emissions.length; i++) { + await this.expectEmission(msTimeout); + } + return this.emissions; + } + + private trackEmissions(observable: Observable): T[] { + const emissions: T[] = []; + this.subscription = observable.subscribe((value) => { + switch (value) { + case undefined: + case null: + emissions.push(value); + return; + default: + // process by type + break; + } + + switch (typeof value) { + case "string": + case "number": + case "boolean": + emissions.push(value); + break; + case "symbol": + // Cheating types to make symbols work at all + emissions.push(value.toString() as T); + break; + default: { + emissions.push(clone(value)); + } + } + }); + return emissions; + } +} +function clone(value: any): any { + if (global.structuredClone != undefined) { + return structuredClone(value); + } else { + return JSON.parse(JSON.stringify(value)); + } +} + +/** A test helper that builds an @see{@link ObservableTracker}, which can be used to assert things about the + * emissions of the given observable + * @param observable The observable to track + */ +export function subscribeTo(observable: Observable) { + return new ObservableTracker(observable); +} diff --git a/libs/common/src/platform/abstractions/config/config-api.service.abstraction.ts b/libs/common/src/platform/abstractions/config/config-api.service.abstraction.ts index 2b25164e7c..63534becf3 100644 --- a/libs/common/src/platform/abstractions/config/config-api.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/config/config-api.service.abstraction.ts @@ -1,5 +1,9 @@ +import { UserId } from "../../../types/guid"; import { ServerConfigResponse } from "../../models/response/server-config.response"; export abstract class ConfigApiServiceAbstraction { - get: () => Promise; + /** + * Fetches the server configuration for the given user. If no user is provided, the configuration will not contain user-specific context. + */ + get: (userId: UserId | undefined) => Promise; } diff --git a/libs/common/src/platform/abstractions/config/config.service.abstraction.ts b/libs/common/src/platform/abstractions/config/config.service.abstraction.ts deleted file mode 100644 index 1e1de9155f..0000000000 --- a/libs/common/src/platform/abstractions/config/config.service.abstraction.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Observable } from "rxjs"; -import { SemVer } from "semver"; - -import { FeatureFlag } from "../../../enums/feature-flag.enum"; -import { Region } from "../environment.service"; - -import { ServerConfig } from "./server-config"; - -export abstract class ConfigServiceAbstraction { - serverConfig$: Observable; - cloudRegion$: Observable; - getFeatureFlag$: ( - key: FeatureFlag, - defaultValue?: T, - ) => Observable; - getFeatureFlag: ( - key: FeatureFlag, - defaultValue?: T, - ) => Promise; - checkServerMeetsVersionRequirement$: ( - minimumRequiredServerVersion: SemVer, - ) => Observable; - - /** - * Force ConfigService to fetch an updated config from the server and emit it from serverConfig$ - * @deprecated The service implementation should subscribe to an observable and use that to trigger a new fetch from - * server instead - */ - triggerServerConfigFetch: () => void; -} diff --git a/libs/common/src/platform/abstractions/config/config.service.ts b/libs/common/src/platform/abstractions/config/config.service.ts new file mode 100644 index 0000000000..9eca5891ac --- /dev/null +++ b/libs/common/src/platform/abstractions/config/config.service.ts @@ -0,0 +1,47 @@ +import { Observable } from "rxjs"; +import { SemVer } from "semver"; + +import { FeatureFlag } from "../../../enums/feature-flag.enum"; +import { Region } from "../environment.service"; + +import { ServerConfig } from "./server-config"; + +export abstract class ConfigService { + /** The server config of the currently active user */ + serverConfig$: Observable; + /** The cloud region of the currently active user */ + cloudRegion$: Observable; + /** + * Retrieves the value of a feature flag for the currently active user + * @param key The feature flag to retrieve + * @param defaultValue The default value to return if the feature flag is not set or the server's config is irretrievable + * @returns An observable that emits the value of the feature flag, updates as the server config changes + */ + getFeatureFlag$: ( + key: FeatureFlag, + defaultValue?: T, + ) => Observable; + /** + * Retrieves the value of a feature flag for the currently active user + * @param key The feature flag to retrieve + * @param defaultValue The default value to return if the feature flag is not set or the server's config is irretrievable + * @returns The value of the feature flag + */ + getFeatureFlag: ( + key: FeatureFlag, + defaultValue?: T, + ) => Promise; + /** + * Verifies whether the server version meets the minimum required version + * @param minimumRequiredServerVersion The minimum version required + * @returns True if the server version is greater than or equal to the minimum required version + */ + checkServerMeetsVersionRequirement$: ( + minimumRequiredServerVersion: SemVer, + ) => Observable; + + /** + * Triggers a check that the config for the currently active user is up-to-date. If it is not, it will be fetched from the server and stored. + */ + abstract ensureConfigFetched(): Promise; +} diff --git a/libs/common/src/platform/abstractions/config/server-config.ts b/libs/common/src/platform/abstractions/config/server-config.ts index 2fa250202e..287e359f18 100644 --- a/libs/common/src/platform/abstractions/config/server-config.ts +++ b/libs/common/src/platform/abstractions/config/server-config.ts @@ -7,7 +7,6 @@ import { } from "../../models/data/server-config.data"; const dayInMilliseconds = 24 * 3600 * 1000; -const eighteenHoursInMilliseconds = 18 * 3600 * 1000; export class ServerConfig { version: string; @@ -38,10 +37,6 @@ export class ServerConfig { return this.getAgeInMilliseconds() <= dayInMilliseconds; } - expiresSoon(): boolean { - return this.getAgeInMilliseconds() >= eighteenHoursInMilliseconds; - } - static fromJSON(obj: Jsonify): ServerConfig { if (obj == null) { return null; diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 514689313f..b4847279c3 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -16,7 +16,6 @@ import { LocalData } from "../../vault/models/data/local.data"; import { CipherView } from "../../vault/models/view/cipher.view"; import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info"; import { KdfType } from "../enums"; -import { ServerConfigData } from "../models/data/server-config.data"; import { Account } from "../models/domain/account"; import { EncString } from "../models/domain/enc-string"; import { StorageOptions } from "../models/domain/storage-options"; @@ -278,14 +277,6 @@ export abstract class StateService { setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise; getApproveLoginRequests: (options?: StorageOptions) => Promise; setApproveLoginRequests: (value: boolean, options?: StorageOptions) => Promise; - /** - * @deprecated Do not call this directly, use ConfigService - */ - getServerConfig: (options?: StorageOptions) => Promise; - /** - * @deprecated Do not call this directly, use ConfigService - */ - setServerConfig: (value: ServerConfigData, options?: StorageOptions) => Promise; /** * fetches string value of URL user tried to navigate to while unauthenticated. * @param options Defines the storage options for the URL; Defaults to session Storage. diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 2657467ae6..d01e9d5b8d 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -18,7 +18,6 @@ import { CipherView } from "../../../vault/models/view/cipher.view"; import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info"; import { KdfType } from "../../enums"; import { Utils } from "../../misc/utils"; -import { ServerConfigData } from "../../models/data/server-config.data"; import { EncryptedString, EncString } from "./enc-string"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; @@ -196,7 +195,6 @@ export class AccountSettings { protectedPin?: string; vaultTimeout?: number; vaultTimeoutAction?: string = "lock"; - serverConfig?: ServerConfigData; approveLoginRequests?: boolean; avatarColor?: string; trustDeviceChoiceForDecryption?: boolean; @@ -214,7 +212,6 @@ export class AccountSettings { obj?.pinProtected, EncString.fromJSON, ), - serverConfig: ServerConfigData.fromJSON(obj?.serverConfig), }); } } diff --git a/libs/common/src/platform/services/config/config-api.service.ts b/libs/common/src/platform/services/config/config-api.service.ts index 702c38f53c..f283410ace 100644 --- a/libs/common/src/platform/services/config/config-api.service.ts +++ b/libs/common/src/platform/services/config/config-api.service.ts @@ -1,18 +1,20 @@ import { ApiService } from "../../../abstractions/api.service"; -import { AuthService } from "../../../auth/abstractions/auth.service"; -import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; +import { TokenService } from "../../../auth/abstractions/token.service"; +import { UserId } from "../../../types/guid"; import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction"; import { ServerConfigResponse } from "../../models/response/server-config.response"; export class ConfigApiService implements ConfigApiServiceAbstraction { constructor( private apiService: ApiService, - private authService: AuthService, + private tokenService: TokenService, ) {} - async get(): Promise { + async get(userId: UserId | undefined): Promise { + // Authentication adds extra context to config responses, if the user has an access token, we want to use it + // We don't particularly care about ensuring the token is valid and not expired, just that it exists const authed: boolean = - (await this.authService.getAuthStatus()) !== AuthenticationStatus.LoggedOut; + userId == null ? false : (await this.tokenService.getAccessToken(userId)) != null; const r = await this.apiService.send("GET", "/config", null, authed, true); return new ServerConfigResponse(r); diff --git a/libs/common/src/platform/services/config/config.service.spec.ts b/libs/common/src/platform/services/config/config.service.spec.ts index 7f337f3322..d643311a26 100644 --- a/libs/common/src/platform/services/config/config.service.spec.ts +++ b/libs/common/src/platform/services/config/config.service.spec.ts @@ -1,200 +1,264 @@ -import { MockProxy, mock } from "jest-mock-extended"; -import { ReplaySubject, skip, take } from "rxjs"; +/** + * need to update test environment so structuredClone works appropriately + * @jest-environment ../../libs/shared/test.environment.ts + */ -import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; -import { AuthService } from "../../../auth/abstractions/auth.service"; -import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; +import { mock } from "jest-mock-extended"; +import { Subject, firstValueFrom, of } from "rxjs"; + +import { + FakeGlobalState, + FakeSingleUserState, + FakeStateProvider, + awaitAsync, + mockAccountServiceWith, +} from "../../../../spec"; +import { subscribeTo } from "../../../../spec/observable-tracker"; import { UserId } from "../../../types/guid"; import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction"; import { ServerConfig } from "../../abstractions/config/server-config"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { LogService } from "../../abstractions/log.service"; -import { StateService } from "../../abstractions/state.service"; +import { Utils } from "../../misc/utils"; import { ServerConfigData } from "../../models/data/server-config.data"; import { EnvironmentServerConfigResponse, ServerConfigResponse, ThirdPartyServerConfigResponse, } from "../../models/response/server-config.response"; -import { StateProvider } from "../../state"; -import { ConfigService } from "./config.service"; +import { + ApiUrl, + DefaultConfigService, + RETRIEVAL_INTERVAL, + GLOBAL_SERVER_CONFIGURATIONS, + USER_SERVER_CONFIG, +} from "./default-config.service"; describe("ConfigService", () => { - let stateService: MockProxy; - let configApiService: MockProxy; - let authService: MockProxy; - let environmentService: MockProxy; - let logService: MockProxy; - let replaySubject: ReplaySubject; - let stateProvider: StateProvider; - - let serverResponseCount: number; // increments to track distinct responses received from server - - // Observables will start emitting as soon as this is created, so only create it - // after everything is mocked - const configServiceFactory = () => { - const configService = new ConfigService( - stateService, - configApiService, - authService, - environmentService, - logService, - stateProvider, - ); - configService.init(); - return configService; - }; + const configApiService = mock(); + const environmentService = mock(); + const logService = mock(); + let stateProvider: FakeStateProvider; + let globalState: FakeGlobalState>; + let userState: FakeSingleUserState; + const activeApiUrl = apiUrl(0); + const userId = "userId" as UserId; + const accountService = mockAccountServiceWith(userId); + const tooOld = new Date(Date.now() - 1.1 * RETRIEVAL_INTERVAL); beforeEach(() => { - stateService = mock(); - configApiService = mock(); - authService = mock(); - environmentService = mock(); - logService = mock(); - replaySubject = new ReplaySubject(1); - const accountService = mockAccountServiceWith("0" as UserId); stateProvider = new FakeStateProvider(accountService); - - environmentService.environment$ = replaySubject.asObservable(); - - serverResponseCount = 1; - configApiService.get.mockImplementation(() => - Promise.resolve(serverConfigResponseFactory("server" + serverResponseCount++)), - ); - - jest.useFakeTimers(); + globalState = stateProvider.global.getFake(GLOBAL_SERVER_CONFIGURATIONS); + userState = stateProvider.singleUser.getFake(userId, USER_SERVER_CONFIG); }); afterEach(() => { - jest.useRealTimers(); + jest.resetAllMocks(); }); - it("Uses storage as fallback", (done) => { - const storedConfigData = serverConfigDataFactory("storedConfig"); - stateService.getServerConfig.mockResolvedValueOnce(storedConfigData); + describe.each([null, userId])("active user: %s", (activeUserId) => { + let sut: DefaultConfigService; - configApiService.get.mockRejectedValueOnce(new Error("Unable to fetch")); - - const configService = configServiceFactory(); - - configService.serverConfig$.pipe(take(1)).subscribe((config) => { - expect(config).toEqual(new ServerConfig(storedConfigData)); - expect(stateService.getServerConfig).toHaveBeenCalledTimes(1); - expect(stateService.setServerConfig).not.toHaveBeenCalled(); - done(); + beforeAll(async () => { + await accountService.switchAccount(activeUserId); }); - configService.triggerServerConfigFetch(); - }); - - it("Stream does not error out if fetch fails", (done) => { - const storedConfigData = serverConfigDataFactory("storedConfig"); - stateService.getServerConfig.mockResolvedValueOnce(storedConfigData); - - const configService = configServiceFactory(); - - configService.serverConfig$.pipe(skip(1), take(1)).subscribe((config) => { - try { - expect(config.gitHash).toEqual("server1"); - done(); - } catch (e) { - done(e); - } - }); - - configApiService.get.mockRejectedValueOnce(new Error("Unable to fetch")); - configService.triggerServerConfigFetch(); - - configApiService.get.mockResolvedValueOnce(serverConfigResponseFactory("server1")); - configService.triggerServerConfigFetch(); - }); - - describe("Fetches config from server", () => { beforeEach(() => { - stateService.getServerConfig.mockResolvedValueOnce(null); + environmentService.environment$ = of(environmentFactory(activeApiUrl)); + sut = new DefaultConfigService( + configApiService, + environmentService, + logService, + stateProvider, + ); }); - it.each([1, 2, 3])( - "after %p hour/s", - (hours: number, done: jest.DoneCallback) => { - const configService = configServiceFactory(); + describe("serverConfig$", () => { + it.each([{}, null])("handles null stored state", async (globalTestState) => { + globalState.stateSubject.next(globalTestState); + userState.nextState(null); + await expect(firstValueFrom(sut.serverConfig$)).resolves.not.toThrow(); + }); - // skip previous hours (if any) - configService.serverConfig$.pipe(skip(hours - 1), take(1)).subscribe((config) => { - try { - expect(config.gitHash).toEqual("server" + hours); - expect(configApiService.get).toHaveBeenCalledTimes(hours); - done(); - } catch (e) { - done(e); - } + describe.each(["stale", "missing"])("%s config", (configStateDescription) => { + const userStored = + configStateDescription === "missing" + ? null + : serverConfigFactory(activeApiUrl + userId, tooOld); + const globalStored = + configStateDescription === "missing" + ? {} + : { + [activeApiUrl]: serverConfigFactory(activeApiUrl, tooOld), + }; + + beforeEach(() => { + globalState.stateSubject.next(globalStored); + userState.nextState(userStored); }); - const oneHourInMs = 1000 * 3600; - jest.advanceTimersByTime(oneHourInMs * hours + 1); - }, - ); + // sanity check + test("authed and unauthorized state are different", () => { + expect(globalStored[activeApiUrl]).not.toEqual(userStored); + }); - it("when environment URLs change", (done) => { - const configService = configServiceFactory(); + describe("fail to fetch", () => { + beforeEach(() => { + configApiService.get.mockRejectedValue(new Error("Unable to fetch")); + }); - configService.serverConfig$.pipe(take(1)).subscribe((config) => { - try { - expect(config.gitHash).toEqual("server1"); - done(); - } catch (e) { - done(e); - } + it("uses storage as fallback", async () => { + const actual = await firstValueFrom(sut.serverConfig$); + expect(actual).toEqual(activeUserId ? userStored : globalStored[activeApiUrl]); + expect(configApiService.get).toHaveBeenCalledTimes(1); + }); + + it("does not error out when fetch fails", async () => { + await expect(firstValueFrom(sut.serverConfig$)).resolves.not.toThrow(); + expect(configApiService.get).toHaveBeenCalledTimes(1); + }); + + it("logs an error when unable to fetch", async () => { + await firstValueFrom(sut.serverConfig$); + + expect(logService.error).toHaveBeenCalledWith( + `Unable to fetch ServerConfig from ${activeApiUrl}: Unable to fetch`, + ); + }); + }); + + describe("fetch success", () => { + const response = serverConfigResponseFactory(); + const newConfig = new ServerConfig(new ServerConfigData(response)); + + it("should be a new config", async () => { + expect(newConfig).not.toEqual(activeUserId ? userStored : globalStored[activeApiUrl]); + }); + + it("fetches config from server when it's older than an hour", async () => { + await firstValueFrom(sut.serverConfig$); + + expect(configApiService.get).toHaveBeenCalledTimes(1); + }); + + it("returns the updated config", async () => { + configApiService.get.mockResolvedValue(response); + + const actual = await firstValueFrom(sut.serverConfig$); + + // This is the time the response is converted to a config + expect(actual.utcDate).toAlmostEqual(newConfig.utcDate, 1000); + delete actual.utcDate; + delete newConfig.utcDate; + + expect(actual).toEqual(newConfig); + }); + }); }); - replaySubject.next(null); - }); + describe("fresh configuration", () => { + const userStored = serverConfigFactory(activeApiUrl + userId); + const globalStored = { + [activeApiUrl]: serverConfigFactory(activeApiUrl), + }; + beforeEach(() => { + globalState.stateSubject.next(globalStored); + userState.nextState(userStored); + }); + it("does not fetch from server", async () => { + await firstValueFrom(sut.serverConfig$); - it("when triggerServerConfigFetch() is called", (done) => { - const configService = configServiceFactory(); + expect(configApiService.get).not.toHaveBeenCalled(); + }); - configService.serverConfig$.pipe(take(1)).subscribe((config) => { - try { - expect(config.gitHash).toEqual("server1"); - done(); - } catch (e) { - done(e); - } + it("uses stored value", async () => { + const actual = await firstValueFrom(sut.serverConfig$); + expect(actual).toEqual(activeUserId ? userStored : globalStored[activeApiUrl]); + }); + + it("does not complete after emit", async () => { + const emissions = []; + const subscription = sut.serverConfig$.subscribe((v) => emissions.push(v)); + await awaitAsync(); + expect(emissions.length).toBe(1); + expect(subscription.closed).toBe(false); + }); }); - - configService.triggerServerConfigFetch(); }); }); - it("Saves server config to storage when the user is logged in", (done) => { - stateService.getServerConfig.mockResolvedValueOnce(null); - authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); - const configService = configServiceFactory(); + describe("environment change", () => { + let sut: DefaultConfigService; + let environmentSubject: Subject; - configService.serverConfig$.pipe(take(1)).subscribe(() => { - try { - expect(stateService.setServerConfig).toHaveBeenCalledWith( - expect.objectContaining({ gitHash: "server1" }), - ); - done(); - } catch (e) { - done(e); - } + beforeAll(async () => { + // updating environment with an active account is undefined behavior + await accountService.switchAccount(null); }); - configService.triggerServerConfigFetch(); + beforeEach(() => { + environmentSubject = new Subject(); + environmentService.environment$ = environmentSubject; + sut = new DefaultConfigService( + configApiService, + environmentService, + logService, + stateProvider, + ); + }); + + describe("serverConfig$", () => { + it("emits a new config when the environment changes", async () => { + const globalStored = { + [apiUrl(0)]: serverConfigFactory(apiUrl(0)), + [apiUrl(1)]: serverConfigFactory(apiUrl(1)), + }; + globalState.stateSubject.next(globalStored); + + const spy = subscribeTo(sut.serverConfig$); + + environmentSubject.next(environmentFactory(apiUrl(0))); + environmentSubject.next(environmentFactory(apiUrl(1))); + + const expected = [globalStored[apiUrl(0)], globalStored[apiUrl(1)]]; + + const actual = await spy.pauseUntilReceived(2); + expect(actual.length).toBe(2); + + // validate dates this is done separately because the dates are created when ServerConfig is initialized + expect(actual[0].utcDate).toAlmostEqual(expected[0].utcDate, 1000); + expect(actual[1].utcDate).toAlmostEqual(expected[1].utcDate, 1000); + delete actual[0].utcDate; + delete actual[1].utcDate; + delete expected[0].utcDate; + delete expected[1].utcDate; + + expect(actual).toEqual(expected); + spy.unsubscribe(); + }); + }); }); }); -function serverConfigDataFactory(gitHash: string) { - return new ServerConfigData(serverConfigResponseFactory(gitHash)); +function apiUrl(count: number) { + return `https://api${count}.test.com`; } -function serverConfigResponseFactory(gitHash: string) { +function serverConfigFactory(hash: string, date: Date = new Date()) { + const config = new ServerConfig(serverConfigDataFactory(hash)); + config.utcDate = date; + return config; +} + +function serverConfigDataFactory(hash?: string) { + return new ServerConfigData(serverConfigResponseFactory(hash)); +} + +function serverConfigResponseFactory(hash?: string) { return new ServerConfigResponse({ version: "myConfigVersion", - gitHash: gitHash, + gitHash: hash ?? Utils.newGuid(), // Use optional git hash to store uniqueness value server: new ThirdPartyServerConfigResponse({ name: "myThirdPartyServer", url: "www.example.com", @@ -209,3 +273,9 @@ function serverConfigResponseFactory(gitHash: string) { }, }); } + +function environmentFactory(apiUrl: string) { + return { + getApiUrl: () => apiUrl, + } as Environment; +} diff --git a/libs/common/src/platform/services/config/config.service.ts b/libs/common/src/platform/services/config/config.service.ts deleted file mode 100644 index 86948fc1c0..0000000000 --- a/libs/common/src/platform/services/config/config.service.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { - ReplaySubject, - Subject, - catchError, - concatMap, - defer, - delayWhen, - firstValueFrom, - map, - merge, - timer, -} from "rxjs"; -import { SemVer } from "semver"; - -import { AuthService } from "../../../auth/abstractions/auth.service"; -import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; -import { FeatureFlag, FeatureFlagValue } from "../../../enums/feature-flag.enum"; -import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction"; -import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction"; -import { ServerConfig } from "../../abstractions/config/server-config"; -import { EnvironmentService, Region } from "../../abstractions/environment.service"; -import { LogService } from "../../abstractions/log.service"; -import { StateService } from "../../abstractions/state.service"; -import { ServerConfigData } from "../../models/data/server-config.data"; -import { StateProvider } from "../../state"; - -const ONE_HOUR_IN_MILLISECONDS = 1000 * 3600; - -export class ConfigService implements ConfigServiceAbstraction { - private inited = false; - - protected _serverConfig = new ReplaySubject(1); - serverConfig$ = this._serverConfig.asObservable(); - - private _forceFetchConfig = new Subject(); - protected refreshTimer$ = timer(ONE_HOUR_IN_MILLISECONDS, ONE_HOUR_IN_MILLISECONDS); // after 1 hour, then every hour - - cloudRegion$ = this.serverConfig$.pipe( - map((config) => config?.environment?.cloudRegion ?? Region.US), - ); - - constructor( - private stateService: StateService, - private configApiService: ConfigApiServiceAbstraction, - private authService: AuthService, - private environmentService: EnvironmentService, - private logService: LogService, - private stateProvider: StateProvider, - - // Used to avoid duplicate subscriptions, e.g. in browser between the background and popup - private subscribe = true, - ) {} - - init() { - if (!this.subscribe || this.inited) { - return; - } - - const latestServerConfig$ = defer(() => this.configApiService.get()).pipe( - map((response) => new ServerConfigData(response)), - delayWhen((data) => this.saveConfig(data)), - catchError((e: unknown) => { - // fall back to stored ServerConfig (if any) - this.logService.error("Unable to fetch ServerConfig: " + (e as Error)?.message); - return this.stateService.getServerConfig(); - }), - ); - - // If you need to fetch a new config when an event occurs, add an observable that emits on that event here - merge( - this.refreshTimer$, // an overridable interval - this.environmentService.environment$, // when environment URLs change (including when app is started) - this._forceFetchConfig, // manual - ) - .pipe( - concatMap(() => latestServerConfig$), - map((data) => (data == null ? null : new ServerConfig(data))), - ) - .subscribe((config) => this._serverConfig.next(config)); - - this.inited = true; - } - - getFeatureFlag$(key: FeatureFlag, defaultValue?: T) { - return this.serverConfig$.pipe( - map((serverConfig) => { - if (serverConfig?.featureStates == null || serverConfig.featureStates[key] == null) { - return defaultValue; - } - - return serverConfig.featureStates[key] as T; - }), - ); - } - - async getFeatureFlag(key: FeatureFlag, defaultValue?: T) { - return await firstValueFrom(this.getFeatureFlag$(key, defaultValue)); - } - - triggerServerConfigFetch() { - this._forceFetchConfig.next(); - } - - private async saveConfig(data: ServerConfigData) { - if ((await this.authService.getAuthStatus()) === AuthenticationStatus.LoggedOut) { - return; - } - - const userId = await firstValueFrom(this.stateProvider.activeUserId$); - await this.stateService.setServerConfig(data); - await this.environmentService.setCloudRegion(userId, data.environment?.cloudRegion); - } - - /** - * Verifies whether the server version meets the minimum required version - * @param minimumRequiredServerVersion The minimum version required - * @returns True if the server version is greater than or equal to the minimum required version - */ - checkServerMeetsVersionRequirement$(minimumRequiredServerVersion: SemVer) { - return this.serverConfig$.pipe( - map((serverConfig) => { - if (serverConfig == null) { - return false; - } - const serverVersion = new SemVer(serverConfig.version); - return serverVersion.compare(minimumRequiredServerVersion) >= 0; - }), - ); - } -} diff --git a/libs/common/src/platform/services/config/default-config.service.ts b/libs/common/src/platform/services/config/default-config.service.ts new file mode 100644 index 0000000000..9532b903d3 --- /dev/null +++ b/libs/common/src/platform/services/config/default-config.service.ts @@ -0,0 +1,177 @@ +import { + NEVER, + Observable, + Subject, + combineLatest, + firstValueFrom, + map, + mergeWith, + of, + shareReplay, + switchMap, + tap, +} from "rxjs"; +import { SemVer } from "semver"; + +import { FeatureFlag, FeatureFlagValue } from "../../../enums/feature-flag.enum"; +import { UserId } from "../../../types/guid"; +import { ConfigApiServiceAbstraction } from "../../abstractions/config/config-api.service.abstraction"; +import { ConfigService } from "../../abstractions/config/config.service"; +import { ServerConfig } from "../../abstractions/config/server-config"; +import { EnvironmentService, Region } from "../../abstractions/environment.service"; +import { LogService } from "../../abstractions/log.service"; +import { ServerConfigData } from "../../models/data/server-config.data"; +import { CONFIG_DISK, KeyDefinition, StateProvider, UserKeyDefinition } from "../../state"; + +export const RETRIEVAL_INTERVAL = 3_600_000; // 1 hour + +export type ApiUrl = string; + +export const USER_SERVER_CONFIG = new UserKeyDefinition(CONFIG_DISK, "serverConfig", { + deserializer: (data) => (data == null ? null : ServerConfig.fromJSON(data)), + clearOn: ["logout"], +}); + +// TODO MDG: When to clean these up? +export const GLOBAL_SERVER_CONFIGURATIONS = KeyDefinition.record( + CONFIG_DISK, + "byServer", + { + deserializer: (data) => (data == null ? null : ServerConfig.fromJSON(data)), + }, +); + +// FIXME: currently we are limited to api requests for active users. Update to accept a UserId and APIUrl once ApiService supports it. +export class DefaultConfigService implements ConfigService { + private failedFetchFallbackSubject = new Subject(); + + serverConfig$: Observable; + + cloudRegion$: Observable; + + constructor( + private configApiService: ConfigApiServiceAbstraction, + private environmentService: EnvironmentService, + private logService: LogService, + private stateProvider: StateProvider, + ) { + const apiUrl$ = this.environmentService.environment$.pipe( + map((environment) => environment.getApiUrl()), + ); + + this.serverConfig$ = combineLatest([this.stateProvider.activeUserId$, apiUrl$]).pipe( + switchMap(([userId, apiUrl]) => { + const config$ = + userId == null ? this.globalConfigFor$(apiUrl) : this.userConfigFor$(userId); + return config$.pipe(map((config) => [config, userId, apiUrl] as const)); + }), + tap(async (rec) => { + const [existingConfig, userId, apiUrl] = rec; + // Grab new config if older retrieval interval + if (!existingConfig || this.olderThanRetrievalInterval(existingConfig.utcDate)) { + await this.renewConfig(existingConfig, userId, apiUrl); + } + }), + switchMap(([existingConfig]) => { + // If we needed to fetch, stop this emit, we'll get a new one after update + // This is split up with the above tap because we need to return an observable from a failed promise, + // which isn't very doable since promises are converted to observables in switchMap + if (!existingConfig || this.olderThanRetrievalInterval(existingConfig.utcDate)) { + return NEVER; + } + return of(existingConfig); + }), + // If fetch fails, we'll emit on this subject to fallback to the existing config + mergeWith(this.failedFetchFallbackSubject), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + this.cloudRegion$ = this.serverConfig$.pipe( + map((config) => config?.environment?.cloudRegion ?? Region.US), + ); + } + getFeatureFlag$(key: FeatureFlag, defaultValue?: T) { + return this.serverConfig$.pipe( + map((serverConfig) => { + if (serverConfig?.featureStates == null || serverConfig.featureStates[key] == null) { + return defaultValue; + } + + return serverConfig.featureStates[key] as T; + }), + ); + } + + async getFeatureFlag(key: FeatureFlag, defaultValue?: T) { + return await firstValueFrom(this.getFeatureFlag$(key, defaultValue)); + } + + checkServerMeetsVersionRequirement$(minimumRequiredServerVersion: SemVer) { + return this.serverConfig$.pipe( + map((serverConfig) => { + if (serverConfig == null) { + return false; + } + const serverVersion = new SemVer(serverConfig.version); + return serverVersion.compare(minimumRequiredServerVersion) >= 0; + }), + ); + } + + async ensureConfigFetched() { + // Triggering a retrieval for the given user ensures that the config is less than RETRIEVAL_INTERVAL old + await firstValueFrom(this.serverConfig$); + } + + private olderThanRetrievalInterval(date: Date) { + return new Date().getTime() - date.getTime() > RETRIEVAL_INTERVAL; + } + + // Updates the on-disk configuration with a newly retrieved configuration + private async renewConfig( + existingConfig: ServerConfig, + userId: UserId, + apiUrl: string, + ): Promise { + try { + const response = await this.configApiService.get(userId); + const newConfig = new ServerConfig(new ServerConfigData(response)); + + // Update the environment region + if ( + newConfig?.environment?.cloudRegion != null && + existingConfig?.environment?.cloudRegion != newConfig.environment.cloudRegion + ) { + // Null userId sets global, otherwise sets to the given user + await this.environmentService.setCloudRegion(userId, newConfig?.environment?.cloudRegion); + } + + if (userId == null) { + // update global state with new pulled config + await this.stateProvider.getGlobal(GLOBAL_SERVER_CONFIGURATIONS).update((configs) => { + return { ...configs, [apiUrl]: newConfig }; + }); + } else { + // update state with new pulled config + await this.stateProvider.setUserState(USER_SERVER_CONFIG, newConfig, userId); + } + } catch (e) { + // mutate error to be handled by catchError + this.logService.error( + `Unable to fetch ServerConfig from ${apiUrl}: ${(e as Error)?.message}`, + ); + // Emit the existing config + this.failedFetchFallbackSubject.next(existingConfig); + } + } + + private globalConfigFor$(apiUrl: string): Observable { + return this.stateProvider + .getGlobal(GLOBAL_SERVER_CONFIGURATIONS) + .state$.pipe(map((configs) => configs?.[apiUrl])); + } + + private userConfigFor$(userId: UserId): Observable { + return this.stateProvider.getUser(userId, USER_SERVER_CONFIG).state$; + } +} diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 56fb91dd52..bbcc00e562 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -32,7 +32,6 @@ import { import { HtmlStorageLocation, KdfType, StorageLocation } from "../enums"; import { StateFactory } from "../factories/state-factory"; import { Utils } from "../misc/utils"; -import { ServerConfigData } from "../models/data/server-config.data"; import { Account, AccountData, AccountSettings } from "../models/domain/account"; import { EncString } from "../models/domain/enc-string"; import { GlobalState } from "../models/domain/global-state"; @@ -1377,23 +1376,6 @@ export class StateService< ); } - async setServerConfig(value: ServerConfigData, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()), - ); - account.settings.serverConfig = value; - return await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()), - ); - } - - async getServerConfig(options: StorageOptions): Promise { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) - )?.settings?.serverConfig; - } - async getDeepLinkRedirectUrl(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index bf0d162eee..b44c449c21 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -73,6 +73,9 @@ export const APPLICATION_ID_DISK = new StateDefinition("applicationId", "disk", }); export const BIOMETRIC_SETTINGS_DISK = new StateDefinition("biometricSettings", "disk"); export const CLEAR_EVENT_DISK = new StateDefinition("clearEvent", "disk"); +export const CONFIG_DISK = new StateDefinition("config", "disk", { + web: "disk-local", +}); export const CRYPTO_DISK = new StateDefinition("crypto", "disk"); export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory"); export const DESKTOP_SETTINGS_DISK = new StateDefinition("desktopSettings", "disk"); diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index 1b057fda4d..b932a7186e 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -44,6 +44,7 @@ import { MergeEnvironmentState } from "./migrations/45-merge-environment-state"; import { DeleteBiometricPromptCancelledData } from "./migrations/46-delete-orphaned-biometric-prompt-data"; import { MoveDesktopSettingsMigrator } from "./migrations/47-move-desktop-settings"; import { MoveDdgToStateProviderMigrator } from "./migrations/48-move-ddg-to-state-provider"; +import { AccountServerConfigMigrator } from "./migrations/49-move-account-server-configs"; import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys"; import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; @@ -52,8 +53,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 48; - +export const CURRENT_VERSION = 49; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -103,7 +103,8 @@ export function createMigrationBuilder() { .with(MergeEnvironmentState, 44, 45) .with(DeleteBiometricPromptCancelledData, 45, 46) .with(MoveDesktopSettingsMigrator, 46, 47) - .with(MoveDdgToStateProviderMigrator, 47, CURRENT_VERSION); + .with(MoveDdgToStateProviderMigrator, 47, 48) + .with(AccountServerConfigMigrator, 48, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/common/src/state-migrations/migrations/49-move-account-server-configs.spec.ts b/libs/common/src/state-migrations/migrations/49-move-account-server-configs.spec.ts new file mode 100644 index 0000000000..4533a754b6 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/49-move-account-server-configs.spec.ts @@ -0,0 +1,112 @@ +import { runMigrator } from "../migration-helper.spec"; + +import { AccountServerConfigMigrator } from "./49-move-account-server-configs"; + +describe("AccountServerConfigMigrator", () => { + const migrator = new AccountServerConfigMigrator(48, 49); + + describe("all data", () => { + function toMigrate() { + return { + authenticatedAccounts: ["user1", "user2"], + user1: { + settings: { + serverConfig: { + config: "user1 server config", + }, + }, + }, + user2: { + settings: { + serverConfig: { + config: "user2 server config", + }, + }, + }, + }; + } + + function migrated() { + return { + authenticatedAccounts: ["user1", "user2"], + + user1: { + settings: {}, + }, + user2: { + settings: {}, + }, + user_user1_config_serverConfig: { + config: "user1 server config", + }, + user_user2_config_serverConfig: { + config: "user2 server config", + }, + }; + } + + function rolledBack(previous: object) { + return { + ...previous, + user_user1_config_serverConfig: null as unknown, + user_user2_config_serverConfig: null as unknown, + }; + } + + it("migrates", async () => { + const output = await runMigrator(migrator, toMigrate(), "migrate"); + expect(output).toEqual(migrated()); + }); + + it("rolls back", async () => { + const output = await runMigrator(migrator, migrated(), "rollback"); + expect(output).toEqual(rolledBack(toMigrate())); + }); + }); + + describe("missing parts", () => { + function toMigrate() { + return { + authenticatedAccounts: ["user1", "user2"], + user1: { + settings: { + serverConfig: { + config: "user1 server config", + }, + }, + }, + user2: null as unknown, + }; + } + + function migrated() { + return { + authenticatedAccounts: ["user1", "user2"], + user1: { + settings: {}, + }, + user2: null as unknown, + user_user1_config_serverConfig: { + config: "user1 server config", + }, + }; + } + + function rollback(previous: object) { + return { + ...previous, + user_user1_config_serverConfig: null as unknown, + }; + } + + it("migrates", async () => { + const output = await runMigrator(migrator, toMigrate(), "migrate"); + expect(output).toEqual(migrated()); + }); + + it("rolls back", async () => { + const output = await runMigrator(migrator, migrated(), "rollback"); + expect(output).toEqual(rollback(toMigrate())); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/49-move-account-server-configs.ts b/libs/common/src/state-migrations/migrations/49-move-account-server-configs.ts new file mode 100644 index 0000000000..8cc25a322d --- /dev/null +++ b/libs/common/src/state-migrations/migrations/49-move-account-server-configs.ts @@ -0,0 +1,51 @@ +import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper"; +import { Migrator } from "../migrator"; + +const CONFIG_DISK: StateDefinitionLike = { name: "config" }; +export const USER_SERVER_CONFIG: KeyDefinitionLike = { + stateDefinition: CONFIG_DISK, + key: "serverConfig", +}; + +// Note: no need to migrate global configs, they don't currently exist + +type ExpectedAccountType = { + settings?: { + serverConfig?: unknown; + }; +}; + +export class AccountServerConfigMigrator extends Migrator<48, 49> { + async migrate(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function migrateAccount(userId: string, account: ExpectedAccountType): Promise { + if (account?.settings?.serverConfig != null) { + await helper.setToUser(userId, USER_SERVER_CONFIG, account.settings.serverConfig); + delete account.settings.serverConfig; + await helper.set(userId, account); + } + } + + await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]); + } + + async rollback(helper: MigrationHelper): Promise { + const accounts = await helper.getAccounts(); + + async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise { + const serverConfig = await helper.getFromUser(userId, USER_SERVER_CONFIG); + + if (serverConfig) { + account ??= {}; + account.settings ??= {}; + + account.settings.serverConfig = serverConfig; + await helper.setToUser(userId, USER_SERVER_CONFIG, null); + await helper.set(userId, account); + } + } + + await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]); + } +} diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index bcd4bb9836..c374724781 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -7,7 +7,7 @@ import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; import { UriMatchStrategy } from "../../models/domain/domain-service"; -import { ConfigServiceAbstraction } from "../../platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; @@ -108,7 +108,7 @@ describe("Cipher Service", () => { const i18nService = mock(); const searchService = mock(); const encryptService = mock(); - const configService = mock(); + const configService = mock(); let cipherService: CipherService; let cipherObj: Cipher; diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 829ee5ed4e..4a6e96ead7 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -9,7 +9,7 @@ import { UriMatchStrategySetting } from "../../models/domain/domain-service"; import { ErrorResponse } from "../../models/response/error.response"; import { ListResponse } from "../../models/response/list.response"; import { View } from "../../models/view/view"; -import { ConfigServiceAbstraction } from "../../platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "../../platform/abstractions/config/config.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; @@ -72,7 +72,7 @@ export class CipherService implements CipherServiceAbstraction { private autofillSettingsService: AutofillSettingsServiceAbstraction, private encryptService: EncryptService, private cipherFileUploadService: CipherFileUploadService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, ) {} async getDecryptedCipherCache(): Promise { diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts index 2f76c5043a..9757e24d8f 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts @@ -4,7 +4,7 @@ import { of } from "rxjs"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; -import { ConfigServiceAbstraction } from "../../../platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "../../../platform/abstractions/config/config.service"; import { Utils } from "../../../platform/misc/utils"; import { Fido2AuthenticatorError, @@ -30,7 +30,7 @@ const VaultUrl = "https://vault.bitwarden.com"; describe("FidoAuthenticatorService", () => { let authenticator!: MockProxy; - let configService!: MockProxy; + let configService!: MockProxy; let authService!: MockProxy; let vaultSettingsService: MockProxy; let domainSettingsService: MockProxy; @@ -39,7 +39,7 @@ describe("FidoAuthenticatorService", () => { beforeEach(async () => { authenticator = mock(); - configService = mock(); + configService = mock(); authService = mock(); vaultSettingsService = mock(); domainSettingsService = mock(); diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index c725b22637..bfc8cbe915 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -4,7 +4,7 @@ import { parse } from "tldts"; import { AuthService } from "../../../auth/abstractions/auth.service"; import { AuthenticationStatus } from "../../../auth/enums/authentication-status"; import { DomainSettingsService } from "../../../autofill/services/domain-settings.service"; -import { ConfigServiceAbstraction } from "../../../platform/abstractions/config/config.service.abstraction"; +import { ConfigService } from "../../../platform/abstractions/config/config.service"; import { LogService } from "../../../platform/abstractions/log.service"; import { Utils } from "../../../platform/misc/utils"; import { @@ -40,7 +40,7 @@ import { Fido2Utils } from "./fido2-utils"; export class Fido2ClientService implements Fido2ClientServiceAbstraction { constructor( private authenticator: Fido2AuthenticatorService, - private configService: ConfigServiceAbstraction, + private configService: ConfigService, private authService: AuthService, private vaultSettingsService: VaultSettingsService, private domainSettingsService: DomainSettingsService, diff --git a/libs/common/test.setup.ts b/libs/common/test.setup.ts index c50c7ca227..d857751b51 100644 --- a/libs/common/test.setup.ts +++ b/libs/common/test.setup.ts @@ -1,6 +1,7 @@ import { webcrypto } from "crypto"; import { toEqualBuffer } from "./spec"; +import { toAlmostEqual } from "./spec/matchers/to-almost-equal"; Object.defineProperty(window, "crypto", { value: webcrypto, @@ -10,8 +11,15 @@ Object.defineProperty(window, "crypto", { expect.extend({ toEqualBuffer: toEqualBuffer, + toAlmostEqual: toAlmostEqual, }); export interface CustomMatchers { toEqualBuffer(expected: Uint8Array | ArrayBuffer): R; + /** + * Matches the expected date within an optional ms precision + * @param expected The expected date + * @param msPrecision The optional precision in milliseconds + */ + toAlmostEqual(expected: Date, msPrecision?: number): R; }