diff --git a/.eslintrc.json b/.eslintrc.json index f21e2b0872..671e7b2fab 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -67,7 +67,7 @@ "pathGroupsExcludedImportTypes": ["builtin"] } ], - "rxjs-angular/prefer-takeuntil": "error", + "rxjs-angular/prefer-takeuntil": ["error", { "alias": ["takeUntilDestroyed"] }], "rxjs/no-exposed-subjects": ["error", { "allowProtected": true }], "no-restricted-syntax": [ "error", diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index cf0ce67980..0defc8aa7c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2386,12 +2386,6 @@ "message": "EU", "description": "European Union" }, - "usDomain": { - "message": "bitwarden.com" - }, - "euDomain": { - "message": "bitwarden.eu" - }, "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, diff --git a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts index cf78f2ff91..32ebee7c75 100644 --- a/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts +++ b/apps/browser/src/auth/popup/account-switching/services/account-switcher.service.ts @@ -65,7 +65,7 @@ export class AccountSwitcherService { name: account.name ?? account.email, email: account.email, id: id, - server: await this.environmentService.getHost(id), + server: (await this.environmentService.getEnvironment(id))?.getHostname(), status: account.status, isActive: id === activeAccount?.id, avatarColor: await firstValueFrom( diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index b5598f13f7..1360e6c8a6 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -94,10 +94,6 @@ export class HomeComponent implements OnInit, OnDestroy { this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } }); } - get selfHostedDomain() { - return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null; - } - setFormValues() { this.loginService.setEmail(this.formGroup.value.email); this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); diff --git a/apps/browser/src/auth/popup/login.component.ts b/apps/browser/src/auth/popup/login.component.ts index c1dd952658..5c302455e6 100644 --- a/apps/browser/src/auth/popup/login.component.ts +++ b/apps/browser/src/auth/popup/login.component.ts @@ -1,6 +1,7 @@ import { Component, NgZone } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; @@ -114,7 +115,8 @@ export class LoginComponent extends BaseLoginComponent { await this.ssoLoginService.setCodeVerifier(codeVerifier); await this.ssoLoginService.setSsoState(state); - let url = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + let url = env.getWebVaultUrl(); if (url == null) { url = "https://vault.bitwarden.com"; } diff --git a/apps/browser/src/auth/popup/sso.component.ts b/apps/browser/src/auth/popup/sso.component.ts index 7b61a04bfd..430bd855f1 100644 --- a/apps/browser/src/auth/popup/sso.component.ts +++ b/apps/browser/src/auth/popup/sso.component.ts @@ -1,4 +1,5 @@ import { Component, Inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component"; @@ -64,9 +65,9 @@ export class SsoComponent extends BaseSsoComponent { configService, ); - const url = this.environmentService.getWebVaultUrl(); - - this.redirectUri = url + "/sso-connector.html"; + environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { + this.redirectUri = env.getWebVaultUrl() + "/sso-connector.html"; + }); this.clientId = "browser"; super.onSuccessfulLogin = async () => { diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor.component.ts index 0a950d6c1b..da2c3482fd 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor.component.ts @@ -1,6 +1,6 @@ import { Component, Inject } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, Subscription } from "rxjs"; +import { Subject, Subscription, firstValueFrom } from "rxjs"; import { filter, first, takeUntil } from "rxjs/operators"; import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; @@ -225,7 +225,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { } } - override launchDuoFrameless() { + override async launchDuoFrameless() { const duoHandOffMessage = { title: this.i18nService.t("youSuccessfullyLoggedIn"), message: this.i18nService.t("youMayCloseThisWindow"), @@ -234,8 +234,9 @@ export class TwoFactorComponent extends BaseTwoFactorComponent { // we're using the connector here as a way to set a cookie with translations // before continuing to the duo frameless url + const env = await firstValueFrom(this.environmentService.environment$); const launchUrl = - this.environmentService.getWebVaultUrl() + + env.getWebVaultUrl() + "/duo-redirect-connector.html" + "?duoFramelessUrl=" + encodeURIComponent(this.duoFramelessUrl) + diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index ab704a1d37..ac40bb315b 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -113,7 +113,7 @@ type NotificationBackgroundExtensionMessageHandlers = { bgGetEnableChangedPasswordPrompt: () => Promise; bgGetEnableAddedLoginPrompt: () => Promise; bgGetExcludedDomains: () => Promise; - getWebVaultUrlForNotification: () => string; + getWebVaultUrlForNotification: () => Promise; }; export { diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 0d8d52df30..3b05cf57a9 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1,13 +1,14 @@ import { mock } from "jest-mock-extended"; -import { firstValueFrom } from "rxjs"; +import { BehaviorSubject, 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 { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { UserNotificationSettingsService } from "@bitwarden/common/autofill/services/user-notification-settings.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -1348,16 +1349,21 @@ describe("NotificationBackground", () => { const message: NotificationBackgroundExtensionMessage = { command: "getWebVaultUrlForNotification", }; - const webVaultUrl = "https://example.com"; + const env = new SelfHostedEnvironment({ webVault: "https://example.com" }); + + Object.defineProperty(environmentService, "environment$", { + configurable: true, + get: () => null, + }); + const environmentServiceSpy = jest - .spyOn(environmentService, "getWebVaultUrl") - .mockReturnValueOnce(webVaultUrl); + .spyOn(environmentService as any, "environment$", "get") + .mockReturnValue(new BehaviorSubject(env).asObservable()); sendExtensionRuntimeMessage(message); await flushPromises(); expect(environmentServiceSpy).toHaveBeenCalled(); - expect(environmentServiceSpy).toHaveReturnedWith(webVaultUrl); }); }); }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index ddabb01158..c14531ee74 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -165,6 +165,7 @@ export default class NotificationBackground { notificationQueueMessage: NotificationQueueMessageItem, ) { const notificationType = notificationQueueMessage.type; + const typeData: Record = { isVaultLocked: notificationQueueMessage.wasVaultLocked, theme: await firstValueFrom(this.themeStateService.selectedTheme$), @@ -655,8 +656,9 @@ export default class NotificationBackground { return await firstValueFrom(this.folderService.folderViews$); } - private getWebVaultUrl(): string { - return this.environmentService.getWebVaultUrl(); + private async getWebVaultUrl(): Promise { + const env = await firstValueFrom(this.environmentService.environment$); + return env.getWebVaultUrl(); } private async removeIndividualVault(): Promise { diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 2b1156dc41..274f354cff 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -1,5 +1,5 @@ import { mock, mockReset } from "jest-mock-extended"; -import { of } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; @@ -12,9 +12,13 @@ import { DefaultDomainSettingsService, DomainSettingsService, } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { ThemeType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { CloudEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { I18nService } from "@bitwarden/common/platform/services/i18n.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { @@ -48,8 +52,6 @@ import { import OverlayBackground from "./overlay.background"; -const iconServerUrl = "https://icons.bitwarden.com/"; - describe("OverlayBackground", () => { const mockUserId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -61,9 +63,15 @@ describe("OverlayBackground", () => { const cipherService = mock(); const autofillService = mock(); const authService = mock(); - const environmentService = mock({ - getIconsUrl: () => iconServerUrl, - }); + + const environmentService = mock(); + environmentService.environment$ = new BehaviorSubject( + new CloudEnvironment({ + key: Region.US, + domain: "bitwarden.com", + urls: { icons: "https://icons.bitwarden.com/" }, + }), + ); const stateService = mock(); const autofillSettingsService = mock(); const i18nService = mock(); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index b685d69106..afa6180576 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -59,7 +59,7 @@ class OverlayBackground implements OverlayBackgroundInterface { private isFieldCurrentlyFocused: boolean; private isCurrentlyFilling: boolean; private overlayPageTranslations: Record; - private readonly iconsServerUrl: string; + private iconsServerUrl: string; private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = { openAutofillOverlay: () => this.openOverlay(false), closeAutofillOverlay: ({ message, sender }) => this.closeOverlay(sender, message), @@ -116,9 +116,7 @@ class OverlayBackground implements OverlayBackgroundInterface { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private themeStateService: ThemeStateService, - ) { - this.iconsServerUrl = this.environmentService.getIconsUrl(); - } + ) {} private async checkIsInlineMenuButtonVisible(sender: chrome.runtime.MessageSender) { const value = await BrowserApi.tabSendMessage( @@ -161,6 +159,8 @@ class OverlayBackground implements OverlayBackgroundInterface { */ async init() { this.setupExtensionMessageListeners(); + const env = await firstValueFrom(this.environmentService.environment$); + this.iconsServerUrl = env.getIconsUrl(); await this.getOverlayVisibility(); await this.getAuthStatus(); } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index b34f07f657..3e84b7544b 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -613,6 +613,7 @@ export default class MainBackground { this.authService, this.environmentService, this.logService, + this.stateProvider, true, ); @@ -1032,10 +1033,6 @@ export default class MainBackground { return new Promise((resolve) => { setTimeout(async () => { - await this.environmentService.setUrlsFromStorage(); - // Workaround to ignore stateService.activeAccount until URLs are set - // TODO: Remove this when implementing ticket PM-2637 - this.environmentService.initialized = true; if (!this.isPrivateMode) { await this.refreshBadge(); } diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index f422fc8550..0a94e0a79a 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -1,3 +1,5 @@ +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"; @@ -220,7 +222,8 @@ export default class RuntimeBackground { } break; case "authResult": { - const vaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { return; @@ -241,7 +244,8 @@ export default class RuntimeBackground { break; } case "webAuthnResult": { - const vaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) { return; @@ -364,7 +368,8 @@ export default class RuntimeBackground { async sendBwInstalledMessageToVault() { try { - const vaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const vaultUrl = env.getWebVaultUrl(); const urlObj = new URL(vaultUrl); const tabs = await BrowserApi.tabsQuery({ url: `${urlObj.href}*` }); 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 9c8b485c2a..4e31fb3141 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 @@ -13,6 +13,7 @@ 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"; type ConfigServiceFactoryOptions = FactoryOptions & { @@ -43,6 +44,7 @@ export function configServiceFactory( 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 index 557db4f33c..be8d087f3b 100644 --- a/apps/browser/src/platform/services/browser-config.service.ts +++ b/apps/browser/src/platform/services/browser-config.service.ts @@ -7,6 +7,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi 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"; @@ -21,8 +22,17 @@ export class BrowserConfigService extends ConfigService { authService: AuthService, environmentService: EnvironmentService, logService: LogService, + stateProvider: StateProvider, subscribe = false, ) { - super(stateService, configApiService, authService, environmentService, logService, subscribe); + super( + stateService, + configApiService, + authService, + environmentService, + logService, + stateProvider, + subscribe, + ); } } diff --git a/apps/browser/src/platform/services/browser-environment.service.ts b/apps/browser/src/platform/services/browser-environment.service.ts index 77cf7de8ea..d7e22cf747 100644 --- a/apps/browser/src/platform/services/browser-environment.service.ts +++ b/apps/browser/src/platform/services/browser-environment.service.ts @@ -1,12 +1,15 @@ +import { firstValueFrom } from "rxjs"; + import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Region } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { GroupPolicyEnvironment } from "../../admin-console/types/group-policy-environment"; import { devFlagEnabled, devFlagValue } from "../flags"; -export class BrowserEnvironmentService extends EnvironmentService { +export class BrowserEnvironmentService extends DefaultEnvironmentService { constructor( private logService: LogService, stateProvider: StateProvider, @@ -29,16 +32,18 @@ export class BrowserEnvironmentService extends EnvironmentService { return false; } - const env = await this.getManagedEnvironment(); + const managedEnv = await this.getManagedEnvironment(); + const env = await firstValueFrom(this.environment$); + const urls = env.getUrls(); return ( - env.base != this.baseUrl || - env.webVault != this.webVaultUrl || - env.api != this.webVaultUrl || - env.identity != this.identityUrl || - env.icons != this.iconsUrl || - env.notifications != this.notificationsUrl || - env.events != this.eventsUrl + managedEnv.base != urls.base || + managedEnv.webVault != urls.webVault || + managedEnv.api != urls.api || + managedEnv.identity != urls.identity || + managedEnv.icons != urls.icons || + managedEnv.notifications != urls.notifications || + managedEnv.events != urls.events ); } @@ -62,7 +67,7 @@ export class BrowserEnvironmentService extends EnvironmentService { async setUrlsToManagedEnvironment() { const env = await this.getManagedEnvironment(); - await this.setUrls({ + await this.setEnvironment(Region.SelfHosted, { base: env.base, webVault: env.webVault, api: env.api, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 5e080adf16..33fe6a52af 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -222,12 +222,12 @@ function getBgService(service: keyof MainBackground) { }, { provide: BrowserEnvironmentService, - useExisting: EnvironmentService, + useClass: BrowserEnvironmentService, + deps: [LogService, StateProvider, AccountServiceAbstraction], }, { provide: EnvironmentService, - useFactory: getBgService("environmentService"), - deps: [], + useExisting: BrowserEnvironmentService, }, { provide: TotpService, useFactory: getBgService("totpService"), deps: [] }, { @@ -480,6 +480,7 @@ function getBgService(service: keyof MainBackground) { ConfigApiServiceAbstraction, AuthServiceAbstraction, EnvironmentService, + StateProvider, LogService, ], }, diff --git a/apps/browser/src/popup/settings/about.component.html b/apps/browser/src/popup/settings/about.component.html index b408617834..a4ad0ba801 100644 --- a/apps/browser/src/popup/settings/about.component.html +++ b/apps/browser/src/popup/settings/about.component.html @@ -6,33 +6,33 @@

© Bitwarden Inc. 2015-{{ year }}

{{ "version" | i18n }}: {{ version }}

- -

- {{ "serverVersion" | i18n }}: {{ this.serverConfig?.version }} - - ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }}) + +

+ {{ "serverVersion" | i18n }}: {{ data.serverConfig?.version }} + + ({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})

- - + +

{{ "serverVersion" | i18n }} ({{ "thirdParty" | i18n }}): - {{ this.serverConfig?.version }} - - ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }}) + {{ data.serverConfig?.version }} + + ({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})

- {{ "thirdPartyServerMessage" | i18n: serverConfig.server?.name }} + {{ "thirdPartyServerMessage" | i18n: data.serverConfig.server?.name }}
-

+

{{ "serverVersion" | i18n }} ({{ "selfHostedServer" | i18n }}): - {{ this.serverConfig?.version }} - - ({{ "lastSeenOn" | i18n: (serverConfig.utcDate | date: "mediumDate") }}) + {{ data.serverConfig?.version }} + + ({{ "lastSeenOn" | i18n: (data.serverConfig.utcDate | date: "mediumDate") }})

diff --git a/apps/browser/src/popup/settings/about.component.ts b/apps/browser/src/popup/settings/about.component.ts index d6f6f000b6..4cabb183ae 100644 --- a/apps/browser/src/popup/settings/about.component.ts +++ b/apps/browser/src/popup/settings/about.component.ts @@ -1,10 +1,9 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { Observable } from "rxjs"; +import { combineLatest, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; -import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { ButtonModule, DialogModule } from "@bitwarden/components"; @@ -16,11 +15,13 @@ import { BrowserApi } from "../../platform/browser/browser-api"; imports: [CommonModule, JslibModule, DialogModule, ButtonModule], }) export class AboutComponent { - protected serverConfig$: Observable = this.configService.serverConfig$; - protected year = new Date().getFullYear(); protected version = BrowserApi.getApplicationVersion(); - protected isCloud = this.environmentService.isCloud(); + + protected data$ = combineLatest([ + this.configService.serverConfig$, + this.environmentService.environment$.pipe(map((env) => env.isCloud())), + ]).pipe(map(([serverConfig, isCloud]) => ({ serverConfig, isCloud }))); constructor( private configService: ConfigServiceAbstraction, diff --git a/apps/browser/src/popup/settings/settings.component.ts b/apps/browser/src/popup/settings/settings.component.ts index 05f633d424..52e44a2531 100644 --- a/apps/browser/src/popup/settings/settings.component.ts +++ b/apps/browser/src/popup/settings/settings.component.ts @@ -446,9 +446,8 @@ export class SettingsComponent implements OnInit { type: "info", }); if (confirmed) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab(this.environmentService.getWebVaultUrl()); + const env = await firstValueFrom(this.environmentService.environment$); + await BrowserApi.createNewTab(env.getWebVaultUrl()); } } @@ -479,10 +478,9 @@ export class SettingsComponent implements OnInit { } async webVault() { - const url = this.environmentService.getWebVaultUrl(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.createNewTab(url); + const env = await firstValueFrom(this.environmentService.environment$); + const url = env.getWebVaultUrl(); + await BrowserApi.createNewTab(url); } async import() { diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 97c0974ac6..a91e876e92 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -690,6 +690,8 @@ export class LoginCommand { codeChallenge: string, state: string, ): Promise<{ ssoCode: string; orgIdentifier: string }> { + const env = await firstValueFrom(this.environmentService.environment$); + return new Promise((resolve, reject) => { const callbackServer = http.createServer((req, res) => { const urlString = "http://localhost" + req.url; @@ -724,7 +726,7 @@ export class LoginCommand { } }); let foundPort = false; - const webUrl = this.environmentService.getWebVaultUrl(); + const webUrl = env.getWebVaultUrl(); for (let port = 8065; port <= 8070; port++) { try { this.ssoRedirectUri = "http://localhost:" + port; diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index 55bc46e41e..7af40b1ebd 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 { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { BiometricStateService, @@ -62,7 +63,7 @@ import { ConfigApiService } from "@bitwarden/common/platform/services/config/con 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"; -import { EnvironmentService } from "@bitwarden/common/platform/services/environment.service"; +import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service"; import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; @@ -312,7 +313,10 @@ export class Main { this.derivedStateProvider, ); - this.environmentService = new EnvironmentService(this.stateProvider, this.accountService); + this.environmentService = new DefaultEnvironmentService( + this.stateProvider, + this.accountService, + ); this.tokenService = new TokenService( this.singleUserStateProvider, @@ -504,6 +508,7 @@ export class Main { this.authService, this.environmentService, this.logService, + this.stateProvider, true, ); @@ -703,7 +708,6 @@ export class Main { await this.storageService.init(); await this.stateService.init(); this.containerService.attachToGlobal(global); - await this.environmentService.setUrlsFromStorage(); await this.i18nService.init(); this.twoFactorService.init(); this.configService.init(); diff --git a/apps/cli/src/commands/config.command.ts b/apps/cli/src/commands/config.command.ts index 209f75a836..eb6559443d 100644 --- a/apps/cli/src/commands/config.command.ts +++ b/apps/cli/src/commands/config.command.ts @@ -1,6 +1,10 @@ import { OptionValues } from "commander"; +import { firstValueFrom } from "rxjs"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { Response } from "../models/response"; import { MessageResponse } from "../models/response/message.response"; @@ -29,16 +33,15 @@ export class ConfigCommand { !options.notifications && !options.events ) { + const env = await firstValueFrom(this.environmentService.environment$); const stringRes = new StringResponse( - this.environmentService.hasBaseUrl() - ? this.environmentService.getUrls().base - : "https://bitwarden.com", + env.hasBaseUrl() ? env.getUrls().base : "https://bitwarden.com", ); return Response.success(stringRes); } url = url === "null" || url === "bitwarden.com" || url === "https://bitwarden.com" ? null : url; - await this.environmentService.setUrls({ + await this.environmentService.setEnvironment(Region.SelfHosted, { base: url, webVault: options.webVault || null, api: options.api || null, diff --git a/apps/cli/src/commands/convert-to-key-connector.command.ts b/apps/cli/src/commands/convert-to-key-connector.command.ts index de9565dfb6..654606dc06 100644 --- a/apps/cli/src/commands/convert-to-key-connector.command.ts +++ b/apps/cli/src/commands/convert-to-key-connector.command.ts @@ -1,8 +1,12 @@ import * as inquirer from "inquirer"; +import { firstValueFrom } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Response } from "../models/response"; @@ -67,9 +71,10 @@ export class ConvertToKeyConnectorCommand { await this.keyConnectorService.setUsesKeyConnector(true); // Update environment URL - required for api key login - const urls = this.environmentService.getUrls(); + const env = await firstValueFrom(this.environmentService.environment$); + const urls = env.getUrls(); urls.keyConnector = organization.keyConnectorUrl; - await this.environmentService.setUrls(urls); + await this.environmentService.setEnvironment(Region.SelfHosted, urls); return Response.success(); } else if (answer.convert === "leave") { diff --git a/apps/cli/src/commands/status.command.ts b/apps/cli/src/commands/status.command.ts index d7bc17b643..32b93a7e40 100644 --- a/apps/cli/src/commands/status.command.ts +++ b/apps/cli/src/commands/status.command.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -17,7 +19,7 @@ export class StatusCommand { async run(): Promise { try { - const baseUrl = this.baseUrl(); + const baseUrl = await this.baseUrl(); const status = await this.status(); const lastSync = await this.syncService.getLastSync(); const userId = await this.stateService.getUserId(); @@ -37,8 +39,9 @@ export class StatusCommand { } } - private baseUrl(): string { - return this.envService.getUrls().base; + private async baseUrl(): Promise { + const env = await firstValueFrom(this.envService.environment$); + return env.getUrls().base; } private async status(): Promise<"unauthenticated" | "locked" | "unlocked"> { diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index 1f6f8059e1..a3f4b5c086 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -127,7 +127,8 @@ export class SendCreateCommand { await this.sendApiService.save([encSend, fileData]); const newSend = await this.sendService.getFromState(encSend.id); const decSend = await newSend.decrypt(); - const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl()); + const env = await firstValueFrom(this.environmentService.environment$); + const res = new SendResponse(decSend, env.getWebVaultUrl()); return Response.success(res); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/tools/send/commands/get.command.ts b/apps/cli/src/tools/send/commands/get.command.ts index 9e80c350b7..2ffe242317 100644 --- a/apps/cli/src/tools/send/commands/get.command.ts +++ b/apps/cli/src/tools/send/commands/get.command.ts @@ -1,4 +1,5 @@ import { OptionValues } from "commander"; +import { firstValueFrom } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -32,7 +33,8 @@ export class SendGetCommand extends DownloadCommand { return Response.notFound(); } - const webVaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); let filter = (s: SendView) => true; let selector = async (s: SendView): Promise => Response.success(new SendResponse(s, webVaultUrl)); diff --git a/apps/cli/src/tools/send/commands/list.command.ts b/apps/cli/src/tools/send/commands/list.command.ts index e169a26ea1..ab8a4dcb1c 100644 --- a/apps/cli/src/tools/send/commands/list.command.ts +++ b/apps/cli/src/tools/send/commands/list.command.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -21,7 +23,8 @@ export class SendListCommand { sends = this.searchService.searchSends(sends, normalizedOptions.search); } - const webVaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); const res = new ListResponse(sends.map((s) => new SendResponse(s, webVaultUrl))); return Response.success(res); } diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index 8dfbf01fa9..dc662f0272 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -1,5 +1,6 @@ import { OptionValues } from "commander"; import * as inquirer from "inquirer"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -46,7 +47,7 @@ export class SendReceiveCommand extends DownloadCommand { return Response.badRequest("Failed to parse the provided Send url"); } - const apiUrl = this.getApiUrl(urlObject); + const apiUrl = await this.getApiUrl(urlObject); const [id, key] = this.getIdAndKey(urlObject); if (Utils.isNullOrWhitespace(id) || Utils.isNullOrWhitespace(key)) { @@ -108,8 +109,9 @@ export class SendReceiveCommand extends DownloadCommand { return [result[0], result[1]]; } - private getApiUrl(url: URL) { - const urls = this.environmentService.getUrls(); + private async getApiUrl(url: URL) { + const env = await firstValueFrom(this.environmentService.environment$); + const urls = env.getUrls(); if (url.origin === "https://send.bitwarden.com") { return "https://api.bitwarden.com"; } else if (url.origin === urls.api) { diff --git a/apps/cli/src/tools/send/commands/remove-password.command.ts b/apps/cli/src/tools/send/commands/remove-password.command.ts index a25ca4c07e..1c7289bf08 100644 --- a/apps/cli/src/tools/send/commands/remove-password.command.ts +++ b/apps/cli/src/tools/send/commands/remove-password.command.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { SendService } from "@bitwarden/common/tools/send/services//send.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; @@ -18,7 +20,8 @@ export class SendRemovePasswordCommand { const updatedSend = await this.sendService.get(id); const decSend = await updatedSend.decrypt(); - const webVaultUrl = this.environmentService.getWebVaultUrl(); + const env = await firstValueFrom(this.environmentService.environment$); + const webVaultUrl = env.getWebVaultUrl(); const res = new SendResponse(decSend, webVaultUrl); return Response.success(res); } catch (e) { diff --git a/apps/desktop/src/app/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index b35d3ed225..ae0409cdbc 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -30,7 +30,7 @@ - + {{ "vaultTimeoutPolicyWithActionInEffect" @@ -46,7 +46,7 @@ {{ "vaultTimeoutActionPolicyInEffect" | i18n: (policy.action | i18n) }} - + { this.nativeMessagingService.init(); await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process - await this.environmentService.setUrlsFromStorage(); - // Workaround to ignore stateService.activeAccount until URLs are set - // TODO: Remove this when implementing ticket PM-2637 - this.environmentService.initialized = true; // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.syncService.fullSync(true); diff --git a/apps/desktop/src/app/tools/export/export.component.html b/apps/desktop/src/app/tools/export/export.component.html index a6ed098181..0058a0925c 100644 --- a/apps/desktop/src/app/tools/export/export.component.html +++ b/apps/desktop/src/app/tools/export/export.component.html @@ -2,13 +2,13 @@