mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +01:00
Auth/PM-13114 - WebEnvService Refactor + Unit Tests to support QA Env Selector (#11397)
* PM-13114 - WebEnvSvc - use hostname vs domain check for init and setEnv (tests TODO) * PM-13114 - WebEnvSvc + URLs webpack config - use expected string variable on process.env.URLS to ensure tests can properly mock the WebEnvSvc * PM-13114 - WebEnvSvc - setEnvironment - fix issue with returning currentRegion urls instead of currentEnv urls. * PM-13114 - WebEnvSvc - setEnv - refactor names to improve clarity. * PM-13114 - WebEnvSvc spec file - Test all prod scenarios * PM-13144 - Work with Justin to move process.env.Urls access into injection token and remove webpack string type conversion. * PM-13114 - WIP on getting additionalRegionConfigs injected via injection token to default env service. * PM-13114 - Update all background inits to pass process.env.ADDITIONAL_REGIONS as unknown as RegionConfig[] to env service. * PM-13114 - WebEnvSvc - adjust order of constructor deps * PM-13114 - WebEnvSvc - add WebRegionConfig to extend RegionConfig type and be accurate for what the WebEnvSvc uses. * PM-13114 - WebEnvSvc Tests - US QA tested * PM-13114 - WebEnvSvc tests - refactor QA naming to make it more clear. * PM-13114 - WebEnvSvc - test QA EU * PM-13114 - WebEnvSvc - remove promise resolve per PR feedback.
This commit is contained in:
parent
e6ff647343
commit
87cb45c520
@ -80,6 +80,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
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";
|
||||
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction";
|
||||
import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction";
|
||||
import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
@ -570,6 +571,7 @@ export default class MainBackground {
|
||||
this.logService,
|
||||
this.stateProvider,
|
||||
this.accountService,
|
||||
process.env.ADDITIONAL_REGIONS as unknown as RegionConfig[],
|
||||
);
|
||||
this.biometricStateService = new DefaultBiometricStateService(this.stateProvider);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { Region } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Region, RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
@ -14,8 +14,9 @@ export class BrowserEnvironmentService extends DefaultEnvironmentService {
|
||||
private logService: LogService,
|
||||
stateProvider: StateProvider,
|
||||
accountService: AccountService,
|
||||
additionalRegionConfigs: RegionConfig[] = [],
|
||||
) {
|
||||
super(stateProvider, accountService);
|
||||
super(stateProvider, accountService, additionalRegionConfigs);
|
||||
}
|
||||
|
||||
async hasManagedEnvironment(): Promise<boolean> {
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
DEFAULT_VAULT_TIMEOUT,
|
||||
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
CLIENT_TYPE,
|
||||
ENV_ADDITIONAL_REGIONS,
|
||||
} from "@bitwarden/angular/services/injection-tokens";
|
||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { AnonLayoutWrapperDataService, LockComponentService } from "@bitwarden/auth/angular";
|
||||
@ -197,7 +198,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: BrowserEnvironmentService,
|
||||
useClass: BrowserEnvironmentService,
|
||||
deps: [LogService, StateProvider, AccountServiceAbstraction],
|
||||
deps: [LogService, StateProvider, AccountServiceAbstraction, ENV_ADDITIONAL_REGIONS],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: I18nServiceAbstraction,
|
||||
|
@ -59,7 +59,10 @@ import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/bill
|
||||
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 {
|
||||
EnvironmentService,
|
||||
RegionConfig,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { KeySuffixOptions, LogLevelType } from "@bitwarden/common/platform/enums";
|
||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||
@ -346,6 +349,7 @@ export class ServiceContainer {
|
||||
this.environmentService = new DefaultEnvironmentService(
|
||||
this.stateProvider,
|
||||
this.accountService,
|
||||
process.env.ADDITIONAL_REGIONS as unknown as RegionConfig[],
|
||||
);
|
||||
|
||||
this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService);
|
||||
|
@ -5,6 +5,7 @@ import { Subject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Message, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
// eslint-disable-next-line no-restricted-imports -- For dependency creation
|
||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||
@ -152,7 +153,11 @@ export class Main {
|
||||
new DefaultDerivedStateProvider(),
|
||||
);
|
||||
|
||||
this.environmentService = new DefaultEnvironmentService(stateProvider, accountService);
|
||||
this.environmentService = new DefaultEnvironmentService(
|
||||
stateProvider,
|
||||
accountService,
|
||||
process.env.ADDITIONAL_REGIONS as unknown as RegionConfig[],
|
||||
);
|
||||
|
||||
this.migrationRunner = new MigrationRunner(
|
||||
this.storageService,
|
||||
|
@ -11,6 +11,7 @@ import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/sa
|
||||
import {
|
||||
CLIENT_TYPE,
|
||||
DEFAULT_VAULT_TIMEOUT,
|
||||
ENV_ADDITIONAL_REGIONS,
|
||||
LOCALES_DIRECTORY,
|
||||
MEMORY_STORAGE,
|
||||
OBSERVABLE_DISK_LOCAL_STORAGE,
|
||||
@ -42,7 +43,10 @@ import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.ser
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } 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 {
|
||||
EnvironmentService,
|
||||
Urls,
|
||||
} 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 } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@ -84,6 +88,7 @@ import { WebStorageServiceProvider } from "../platform/web-storage-service.provi
|
||||
|
||||
import { EventService } from "./event.service";
|
||||
import { InitService } from "./init.service";
|
||||
import { ENV_URLS } from "./injection-tokens";
|
||||
import { ModalService } from "./modal.service";
|
||||
import { RouterService } from "./router.service";
|
||||
import { WebFileDownloadService } from "./web-file-download.service";
|
||||
@ -173,10 +178,14 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: WebMigrationRunner,
|
||||
deps: [AbstractStorageService, LogService, MigrationBuilderService, WindowStorageService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ENV_URLS,
|
||||
useValue: process.env.URLS as Urls,
|
||||
}),
|
||||
safeProvider({
|
||||
provide: EnvironmentService,
|
||||
useClass: WebEnvironmentService,
|
||||
deps: [WINDOW, StateProvider, AccountService, Router],
|
||||
deps: [WINDOW, StateProvider, AccountService, ENV_ADDITIONAL_REGIONS, Router, ENV_URLS],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: BiometricsService,
|
||||
|
10
apps/web/src/app/core/injection-tokens.ts
Normal file
10
apps/web/src/app/core/injection-tokens.ts
Normal file
@ -0,0 +1,10 @@
|
||||
// Put web specific injection tokens here
|
||||
import { SafeInjectionToken } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { Urls } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
|
||||
/**
|
||||
* Injection token for injecting the NodeJS process.env urls into services.
|
||||
* Using an injection token allows services to be tested without needing to
|
||||
* mock the process.env.
|
||||
*/
|
||||
export const ENV_URLS = new SafeInjectionToken<Urls>("ENV_URLS");
|
457
apps/web/src/app/platform/web-environment.service.spec.ts
Normal file
457
apps/web/src/app/platform/web-environment.service.spec.ts
Normal file
@ -0,0 +1,457 @@
|
||||
import { Router } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { Region, Urls } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { PRODUCTION_REGIONS } from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import {
|
||||
FakeAccountService,
|
||||
FakeStateProvider,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/spec";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
WebCloudEnvironment,
|
||||
WebEnvironmentService,
|
||||
WebRegionConfig,
|
||||
} from "./web-environment.service";
|
||||
|
||||
describe("WebEnvironmentService", () => {
|
||||
let service: WebEnvironmentService;
|
||||
|
||||
let window: MockProxy<Window>;
|
||||
|
||||
let stateProvider: FakeStateProvider;
|
||||
let accountService: FakeAccountService;
|
||||
let router: MockProxy<Router>;
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
|
||||
describe("Production Environment", () => {
|
||||
describe("US Region", () => {
|
||||
const mockInitialProdUSUrls = {
|
||||
base: null,
|
||||
api: "https://api.bitwarden.com",
|
||||
identity: "https://identity.bitwarden.com",
|
||||
icons: "https://icons.bitwarden.net",
|
||||
webVault: "https://vault.bitwarden.com",
|
||||
notifications: "https://notifications.bitwarden.com",
|
||||
events: "https://events.bitwarden.com",
|
||||
scim: "https://scim.bitwarden.com",
|
||||
} as Urls;
|
||||
|
||||
const mockProdUSBaseUrl = "https://vault.bitwarden.com";
|
||||
|
||||
const expectedProdUSUrls: Urls = {
|
||||
...mockInitialProdUSUrls,
|
||||
base: mockProdUSBaseUrl,
|
||||
};
|
||||
|
||||
const expectedModifiedScimUrl = expectedProdUSUrls.scim + "/v2";
|
||||
const expectedSendUrl = "https://send.bitwarden.com/#";
|
||||
|
||||
const PROD_US_REGION = PRODUCTION_REGIONS.find((r) => r.key === Region.US);
|
||||
|
||||
const prodUSEnv = new WebCloudEnvironment(PROD_US_REGION, expectedProdUSUrls);
|
||||
|
||||
beforeEach(() => {
|
||||
window = mock<Window>();
|
||||
|
||||
window.location = {
|
||||
origin: mockProdUSBaseUrl,
|
||||
href: mockProdUSBaseUrl + "/#/example",
|
||||
} as Location;
|
||||
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
router = mock<Router>();
|
||||
|
||||
(router as any).url = "";
|
||||
|
||||
service = new WebEnvironmentService(
|
||||
window,
|
||||
stateProvider,
|
||||
accountService,
|
||||
[], // no additional region configs required for prod envs
|
||||
router,
|
||||
mockInitialProdUSUrls,
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes the environment with the US production urls", async () => {
|
||||
const env = await firstValueFrom(service.environment$);
|
||||
|
||||
expect(env).toEqual(prodUSEnv);
|
||||
|
||||
expect(env.getRegion()).toEqual(Region.US);
|
||||
expect(env.getUrls()).toEqual(expectedProdUSUrls);
|
||||
expect(env.isCloud()).toBeTruthy();
|
||||
|
||||
expect(env.getApiUrl()).toEqual(expectedProdUSUrls.api);
|
||||
expect(env.getIdentityUrl()).toEqual(expectedProdUSUrls.identity);
|
||||
expect(env.getIconsUrl()).toEqual(expectedProdUSUrls.icons);
|
||||
expect(env.getWebVaultUrl()).toEqual(expectedProdUSUrls.webVault);
|
||||
expect(env.getNotificationsUrl()).toEqual(expectedProdUSUrls.notifications);
|
||||
expect(env.getEventsUrl()).toEqual(expectedProdUSUrls.events);
|
||||
|
||||
expect(env.getScimUrl()).toEqual(expectedModifiedScimUrl);
|
||||
expect(env.getSendUrl()).toEqual(expectedSendUrl);
|
||||
|
||||
expect(env.getHostname()).toEqual(PROD_US_REGION.domain);
|
||||
});
|
||||
|
||||
describe("setEnvironment", () => {
|
||||
it("throws an error when trying to set the environment to self-hosted", async () => {
|
||||
await expect(service.setEnvironment(Region.SelfHosted)).rejects.toThrow(
|
||||
"setEnvironment does not work in web for self-hosted.",
|
||||
);
|
||||
});
|
||||
|
||||
it("only returns the current env's urls when trying to set the environment to the current region", async () => {
|
||||
const urls = await service.setEnvironment(Region.US);
|
||||
expect(urls).toEqual(expectedProdUSUrls);
|
||||
});
|
||||
|
||||
it("errors if the selected region is unknown", async () => {
|
||||
await expect(service.setEnvironment("unknown" as Region)).rejects.toThrow(
|
||||
"The selected region is not known as an available region.",
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the window location to a new region's web vault url and preserves any query params", async () => {
|
||||
const routeAndQueryParams = "/signup?example=1&another=2";
|
||||
(router as any).url = routeAndQueryParams;
|
||||
|
||||
const newRegion = Region.EU;
|
||||
const newRegionConfig = PRODUCTION_REGIONS.find((r) => r.key === newRegion);
|
||||
|
||||
await service.setEnvironment(newRegion);
|
||||
|
||||
expect(window.location.href).toEqual(
|
||||
newRegionConfig.urls.webVault + "/#" + routeAndQueryParams,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("EU Region", () => {
|
||||
const mockInitialProdEUUrls = {
|
||||
base: null,
|
||||
api: "https://api.bitwarden.eu",
|
||||
identity: "https://identity.bitwarden.eu",
|
||||
icons: "https://icons.bitwarden.eu",
|
||||
webVault: "https://vault.bitwarden.eu",
|
||||
notifications: "https://notifications.bitwarden.eu",
|
||||
events: "https://events.bitwarden.eu",
|
||||
scim: "https://scim.bitwarden.eu",
|
||||
} as Urls;
|
||||
|
||||
const mockProdEUBaseUrl = "https://vault.bitwarden.eu";
|
||||
|
||||
const expectedProdEUUrls: Urls = {
|
||||
...mockInitialProdEUUrls,
|
||||
base: mockProdEUBaseUrl,
|
||||
};
|
||||
|
||||
const expectedModifiedScimUrl = expectedProdEUUrls.scim + "/v2";
|
||||
const expectedSendUrl = expectedProdEUUrls.webVault + "/#/send/";
|
||||
|
||||
const prodEURegionConfig = PRODUCTION_REGIONS.find((r) => r.key === Region.EU);
|
||||
|
||||
const prodEUEnv = new WebCloudEnvironment(prodEURegionConfig, expectedProdEUUrls);
|
||||
|
||||
beforeEach(() => {
|
||||
window = mock<Window>();
|
||||
|
||||
window.location = {
|
||||
origin: mockProdEUBaseUrl,
|
||||
href: mockProdEUBaseUrl + "/#/example",
|
||||
} as Location;
|
||||
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
router = mock<Router>();
|
||||
|
||||
service = new WebEnvironmentService(
|
||||
window,
|
||||
stateProvider,
|
||||
accountService,
|
||||
[], // no additional region configs required for prod envs
|
||||
router,
|
||||
mockInitialProdEUUrls,
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes the environment to be the prod EU environment", async () => {
|
||||
const env = await firstValueFrom(service.environment$);
|
||||
|
||||
expect(env).toEqual(prodEUEnv);
|
||||
expect(env.getRegion()).toEqual(Region.EU);
|
||||
expect(env.getUrls()).toEqual(expectedProdEUUrls);
|
||||
expect(env.isCloud()).toBeTruthy();
|
||||
|
||||
expect(env.getApiUrl()).toEqual(expectedProdEUUrls.api);
|
||||
expect(env.getIdentityUrl()).toEqual(expectedProdEUUrls.identity);
|
||||
expect(env.getIconsUrl()).toEqual(expectedProdEUUrls.icons);
|
||||
expect(env.getWebVaultUrl()).toEqual(expectedProdEUUrls.webVault);
|
||||
expect(env.getNotificationsUrl()).toEqual(expectedProdEUUrls.notifications);
|
||||
expect(env.getEventsUrl()).toEqual(expectedProdEUUrls.events);
|
||||
|
||||
expect(env.getScimUrl()).toEqual(expectedModifiedScimUrl);
|
||||
expect(env.getSendUrl()).toEqual(expectedSendUrl);
|
||||
|
||||
expect(env.getHostname()).toEqual(prodEURegionConfig.domain);
|
||||
});
|
||||
|
||||
describe("setEnvironment", () => {
|
||||
it("throws an error when trying to set the environment to self-hosted", async () => {
|
||||
await expect(service.setEnvironment(Region.SelfHosted)).rejects.toThrow(
|
||||
"setEnvironment does not work in web for self-hosted.",
|
||||
);
|
||||
});
|
||||
|
||||
it("only returns the current env's urls when trying to set the environment to the current region", async () => {
|
||||
const urls = await service.setEnvironment(Region.EU);
|
||||
expect(urls).toEqual(expectedProdEUUrls);
|
||||
});
|
||||
|
||||
it("errors if the selected region is unknown", async () => {
|
||||
await expect(service.setEnvironment("unknown" as Region)).rejects.toThrow(
|
||||
"The selected region is not known as an available region.",
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the window location to a new region's web vault url and preserves any query params", async () => {
|
||||
const routeAndQueryParams = "/signup?example=1&another=2";
|
||||
(router as any).url = routeAndQueryParams;
|
||||
|
||||
const newRegion = Region.US;
|
||||
const newRegionConfig = PRODUCTION_REGIONS.find((r) => r.key === newRegion);
|
||||
|
||||
await service.setEnvironment(newRegion);
|
||||
|
||||
expect(window.location.href).toEqual(
|
||||
newRegionConfig.urls.webVault + "/#" + routeAndQueryParams,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("QA Environment", () => {
|
||||
const QA_US_REGION_KEY = "USQA";
|
||||
const QA_US_WEB_REGION_CONFIG = {
|
||||
key: QA_US_REGION_KEY,
|
||||
domain: "qa.bitwarden.pw",
|
||||
urls: {
|
||||
webVault: "https://vault.qa.bitwarden.pw",
|
||||
},
|
||||
} as WebRegionConfig;
|
||||
|
||||
const QA_EU_REGION_KEY = "EUQA";
|
||||
const QA_EU_WEB_REGION_CONFIG = {
|
||||
key: QA_EU_REGION_KEY,
|
||||
domain: "euqa.bitwarden.pw",
|
||||
urls: {
|
||||
webVault: "https://vault.euqa.bitwarden.pw",
|
||||
},
|
||||
} as WebRegionConfig;
|
||||
|
||||
const additionalRegionConfigs: WebRegionConfig[] = [
|
||||
QA_US_WEB_REGION_CONFIG,
|
||||
QA_EU_WEB_REGION_CONFIG,
|
||||
];
|
||||
|
||||
describe("US Region", () => {
|
||||
const initial_QA_US_Urls = {
|
||||
icons: "https://icons.qa.bitwarden.pw",
|
||||
notifications: "https://notifications.qa.bitwarden.pw",
|
||||
scim: "https://scim.qa.bitwarden.pw",
|
||||
} as Urls;
|
||||
|
||||
const mock_QA_US_BaseUrl = "https://vault.qa.bitwarden.pw";
|
||||
|
||||
const expected_QA_US_Urls: Urls = {
|
||||
...initial_QA_US_Urls,
|
||||
base: mock_QA_US_BaseUrl,
|
||||
};
|
||||
|
||||
const expectedModifiedScimUrl = expected_QA_US_Urls.scim + "/v2";
|
||||
|
||||
const expectedSendUrl = QA_US_WEB_REGION_CONFIG.urls.webVault + "/#/send/";
|
||||
|
||||
const QA_US_Env = new WebCloudEnvironment(QA_US_WEB_REGION_CONFIG, expected_QA_US_Urls);
|
||||
|
||||
beforeEach(() => {
|
||||
window = mock<Window>();
|
||||
window.location = {
|
||||
origin: mock_QA_US_BaseUrl,
|
||||
href: mock_QA_US_BaseUrl + "/#/example",
|
||||
} as Location;
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
router = mock<Router>();
|
||||
(router as any).url = "";
|
||||
service = new WebEnvironmentService(
|
||||
window,
|
||||
stateProvider,
|
||||
accountService,
|
||||
additionalRegionConfigs,
|
||||
router,
|
||||
initial_QA_US_Urls,
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes the environment to be the QA US environment", async () => {
|
||||
const env = await firstValueFrom(service.environment$);
|
||||
|
||||
expect(env).toEqual(QA_US_Env);
|
||||
expect(env.getRegion()).toEqual(QA_US_REGION_KEY);
|
||||
expect(env.getUrls()).toEqual(expected_QA_US_Urls);
|
||||
expect(env.isCloud()).toBeTruthy();
|
||||
|
||||
expect(env.getApiUrl()).toEqual(expected_QA_US_Urls.base + "/api");
|
||||
expect(env.getIdentityUrl()).toEqual(expected_QA_US_Urls.base + "/identity");
|
||||
expect(env.getIconsUrl()).toEqual(expected_QA_US_Urls.icons);
|
||||
|
||||
expect(env.getWebVaultUrl()).toEqual(QA_US_WEB_REGION_CONFIG.urls.webVault);
|
||||
|
||||
expect(env.getNotificationsUrl()).toEqual(expected_QA_US_Urls.notifications);
|
||||
expect(env.getEventsUrl()).toEqual(expected_QA_US_Urls.base + "/events");
|
||||
|
||||
expect(env.getScimUrl()).toEqual(expectedModifiedScimUrl);
|
||||
|
||||
expect(env.getSendUrl()).toEqual(expectedSendUrl);
|
||||
|
||||
expect(env.getHostname()).toEqual(QA_US_WEB_REGION_CONFIG.domain);
|
||||
});
|
||||
|
||||
describe("setEnvironment", () => {
|
||||
it("throws an error when trying to set the environment to self-hosted", async () => {
|
||||
await expect(service.setEnvironment(Region.SelfHosted)).rejects.toThrow(
|
||||
"setEnvironment does not work in web for self-hosted.",
|
||||
);
|
||||
});
|
||||
|
||||
it("only returns the current env's urls when trying to set the environment to the current region", async () => {
|
||||
const urls = await service.setEnvironment(QA_US_REGION_KEY);
|
||||
expect(urls).toEqual(expected_QA_US_Urls);
|
||||
});
|
||||
|
||||
it("errors if the selected region is unknown", async () => {
|
||||
await expect(service.setEnvironment("unknown" as Region)).rejects.toThrow(
|
||||
"The selected region is not known as an available region.",
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the window location to a new region's web vault url and preserves any query params", async () => {
|
||||
const routeAndQueryParams = "/signup?example=1&another=2";
|
||||
(router as any).url = routeAndQueryParams;
|
||||
|
||||
await service.setEnvironment(QA_EU_REGION_KEY);
|
||||
|
||||
expect(window.location.href).toEqual(
|
||||
QA_EU_WEB_REGION_CONFIG.urls.webVault + "/#" + routeAndQueryParams,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("EU Region", () => {
|
||||
const initial_QA_EU_Urls = {
|
||||
icons: "https://icons.euqa.bitwarden.pw",
|
||||
notifications: "https://notifications.euqa.bitwarden.pw",
|
||||
scim: "https://scim.euqa.bitwarden.pw",
|
||||
} as Urls;
|
||||
|
||||
const mock_QA_EU_BaseUrl = "https://vault.euqa.bitwarden.pw";
|
||||
|
||||
const expected_QA_EU_Urls: Urls = {
|
||||
...initial_QA_EU_Urls,
|
||||
base: mock_QA_EU_BaseUrl,
|
||||
};
|
||||
|
||||
const expectedModifiedScimUrl = expected_QA_EU_Urls.scim + "/v2";
|
||||
|
||||
const expectedSendUrl = QA_EU_WEB_REGION_CONFIG.urls.webVault + "/#/send/";
|
||||
|
||||
const QA_EU_Env = new WebCloudEnvironment(QA_EU_WEB_REGION_CONFIG, expected_QA_EU_Urls);
|
||||
|
||||
beforeEach(() => {
|
||||
window = mock<Window>();
|
||||
window.location = {
|
||||
origin: mock_QA_EU_BaseUrl,
|
||||
href: mock_QA_EU_BaseUrl + "/#/example",
|
||||
} as Location;
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
stateProvider = new FakeStateProvider(accountService);
|
||||
router = mock<Router>();
|
||||
(router as any).url = "";
|
||||
service = new WebEnvironmentService(
|
||||
window,
|
||||
stateProvider,
|
||||
accountService,
|
||||
additionalRegionConfigs,
|
||||
router,
|
||||
initial_QA_EU_Urls,
|
||||
);
|
||||
});
|
||||
|
||||
it("initializes the environment to be the QA US environment", async () => {
|
||||
const env = await firstValueFrom(service.environment$);
|
||||
|
||||
expect(env).toEqual(QA_EU_Env);
|
||||
expect(env.getRegion()).toEqual(QA_EU_REGION_KEY);
|
||||
expect(env.getUrls()).toEqual(expected_QA_EU_Urls);
|
||||
expect(env.isCloud()).toBeTruthy();
|
||||
|
||||
expect(env.getApiUrl()).toEqual(expected_QA_EU_Urls.base + "/api");
|
||||
expect(env.getIdentityUrl()).toEqual(expected_QA_EU_Urls.base + "/identity");
|
||||
expect(env.getIconsUrl()).toEqual(expected_QA_EU_Urls.icons);
|
||||
|
||||
expect(env.getWebVaultUrl()).toEqual(QA_EU_WEB_REGION_CONFIG.urls.webVault);
|
||||
|
||||
expect(env.getNotificationsUrl()).toEqual(expected_QA_EU_Urls.notifications);
|
||||
expect(env.getEventsUrl()).toEqual(expected_QA_EU_Urls.base + "/events");
|
||||
|
||||
expect(env.getScimUrl()).toEqual(expectedModifiedScimUrl);
|
||||
|
||||
expect(env.getSendUrl()).toEqual(expectedSendUrl);
|
||||
|
||||
expect(env.getHostname()).toEqual(QA_EU_WEB_REGION_CONFIG.domain);
|
||||
});
|
||||
|
||||
describe("setEnvironment", () => {
|
||||
it("throws an error when trying to set the environment to self-hosted", async () => {
|
||||
await expect(service.setEnvironment(Region.SelfHosted)).rejects.toThrow(
|
||||
"setEnvironment does not work in web for self-hosted.",
|
||||
);
|
||||
});
|
||||
|
||||
it("only returns the current env's urls when trying to set the environment to the current region", async () => {
|
||||
const urls = await service.setEnvironment(QA_EU_REGION_KEY);
|
||||
expect(urls).toEqual(expected_QA_EU_Urls);
|
||||
});
|
||||
|
||||
it("errors if the selected region is unknown", async () => {
|
||||
await expect(service.setEnvironment("unknown" as Region)).rejects.toThrow(
|
||||
"The selected region is not known as an available region.",
|
||||
);
|
||||
});
|
||||
|
||||
it("sets the window location to a new region's web vault url and preserves any query params", async () => {
|
||||
const routeAndQueryParams = "/signup?example=1&another=2";
|
||||
(router as any).url = routeAndQueryParams;
|
||||
|
||||
await service.setEnvironment(QA_US_REGION_KEY);
|
||||
|
||||
expect(window.location.href).toEqual(
|
||||
QA_US_WEB_REGION_CONFIG.urls.webVault + "/#" + routeAndQueryParams,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import { Router } from "@angular/router";
|
||||
import { ReplaySubject } from "rxjs";
|
||||
import { firstValueFrom, ReplaySubject } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import {
|
||||
@ -8,7 +8,6 @@ import {
|
||||
RegionConfig,
|
||||
Urls,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
CloudEnvironment,
|
||||
DefaultEnvironmentService,
|
||||
@ -16,6 +15,12 @@ import {
|
||||
} from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
|
||||
export type WebRegionConfig = RegionConfig & {
|
||||
key: Region | string; // strings are used for custom environments
|
||||
domain: string;
|
||||
urls: Urls;
|
||||
};
|
||||
|
||||
/**
|
||||
* Web specific environment service. Ensures that the urls are set from the window location.
|
||||
*/
|
||||
@ -24,23 +29,30 @@ export class WebEnvironmentService extends DefaultEnvironmentService {
|
||||
private win: Window,
|
||||
stateProvider: StateProvider,
|
||||
accountService: AccountService,
|
||||
additionalRegionConfigs: WebRegionConfig[] = [],
|
||||
private router: Router,
|
||||
private envUrls: Urls,
|
||||
) {
|
||||
super(stateProvider, accountService);
|
||||
super(stateProvider, accountService, additionalRegionConfigs);
|
||||
|
||||
// The web vault always uses the current location as the base url
|
||||
const urls = process.env.URLS as Urls;
|
||||
urls.base ??= this.win.location.origin;
|
||||
envUrls.base ??= this.win.location.origin;
|
||||
|
||||
// Find the region
|
||||
const domain = Utils.getDomain(this.win.location.href);
|
||||
const region = this.availableRegions().find((r) => Utils.getDomain(r.urls.webVault) === domain);
|
||||
const currentHostname = new URL(this.win.location.href).hostname;
|
||||
const availableRegions = this.availableRegions();
|
||||
const region = availableRegions.find((r) => {
|
||||
// We must use hostname as our QA envs use the same
|
||||
// domain (bitwarden.pw) but different subdomains (qa and euqa)
|
||||
const webVaultHostname = new URL(r.urls.webVault).hostname;
|
||||
return webVaultHostname === currentHostname;
|
||||
});
|
||||
|
||||
let environment: Environment;
|
||||
if (region) {
|
||||
environment = new WebCloudEnvironment(region, urls);
|
||||
environment = new WebCloudEnvironment(region, envUrls);
|
||||
} else {
|
||||
environment = new SelfHostedEnvironment(urls);
|
||||
environment = new SelfHostedEnvironment(envUrls);
|
||||
}
|
||||
|
||||
// Override the environment observable with a replay subject
|
||||
@ -50,37 +62,45 @@ export class WebEnvironmentService extends DefaultEnvironmentService {
|
||||
}
|
||||
|
||||
// Web setting env means navigating to a new location
|
||||
setEnvironment(region: Region, urls?: Urls): Promise<Urls> {
|
||||
async setEnvironment(region: Region | string, urls?: Urls): Promise<Urls> {
|
||||
if (region === Region.SelfHosted) {
|
||||
throw new Error("setEnvironment does not work in web for self-hosted.");
|
||||
}
|
||||
|
||||
const currentDomain = Utils.getDomain(this.win.location.href);
|
||||
const currentRegion = this.availableRegions().find(
|
||||
(r) => Utils.getDomain(r.urls.webVault) === currentDomain,
|
||||
);
|
||||
// Find the region
|
||||
const currentHostname = new URL(this.win.location.href).hostname;
|
||||
const availableRegions = this.availableRegions();
|
||||
const currentRegionConfig = availableRegions.find((r) => {
|
||||
// We must use hostname as our QA envs use the same
|
||||
// domain (bitwarden.pw) but different subdomains (qa and euqa)
|
||||
const webVaultHostname = new URL(r.urls.webVault).hostname;
|
||||
return webVaultHostname === currentHostname;
|
||||
});
|
||||
|
||||
if (currentRegion.key === region) {
|
||||
// They have selected the current region, nothing to do
|
||||
return Promise.resolve(currentRegion.urls);
|
||||
if (currentRegionConfig.key === region) {
|
||||
// They have selected the current region, return the current env urls
|
||||
// We can't return the region urls because the env base url is modified
|
||||
// in the constructor to match the current window.location.origin.
|
||||
const currentEnv = await firstValueFrom(this.environment$);
|
||||
return currentEnv.getUrls();
|
||||
}
|
||||
|
||||
const chosenRegion = this.availableRegions().find((r) => r.key === region);
|
||||
const chosenRegionConfig = this.availableRegions().find((r) => r.key === region);
|
||||
|
||||
if (chosenRegion == null) {
|
||||
if (chosenRegionConfig == null) {
|
||||
throw new Error("The selected region is not known as an available region.");
|
||||
}
|
||||
|
||||
// Preserve the current in app route + params in the new location
|
||||
const routeAndParams = `/#${this.router.url}`;
|
||||
this.win.location.href = chosenRegion.urls.webVault + routeAndParams;
|
||||
this.win.location.href = chosenRegionConfig.urls.webVault + routeAndParams;
|
||||
|
||||
// This return shouldn't matter as we are about to leave the current window
|
||||
return Promise.resolve(chosenRegion.urls);
|
||||
return chosenRegionConfig.urls;
|
||||
}
|
||||
}
|
||||
|
||||
class WebCloudEnvironment extends CloudEnvironment {
|
||||
export class WebCloudEnvironment extends CloudEnvironment {
|
||||
constructor(config: RegionConfig, urls: Urls) {
|
||||
super(config);
|
||||
// We override the urls to avoid CORS issues
|
||||
|
@ -3,6 +3,7 @@ import { Observable, Subject } from "rxjs";
|
||||
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
@ -58,3 +59,12 @@ export const CLIENT_TYPE = new SafeInjectionToken<ClientType>("CLIENT_TYPE");
|
||||
export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => void>(
|
||||
"REFRESH_ACCESS_TOKEN_ERROR_CALLBACK",
|
||||
);
|
||||
|
||||
/**
|
||||
* Injection token for injecting the NodeJS process.env additional regions into services.
|
||||
* Using an injection token allows services to be tested without needing to
|
||||
* mock the process.env.
|
||||
*/
|
||||
export const ENV_ADDITIONAL_REGIONS = new SafeInjectionToken<RegionConfig[]>(
|
||||
"ENV_ADDITIONAL_REGIONS",
|
||||
);
|
||||
|
@ -141,7 +141,10 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
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";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import {
|
||||
EnvironmentService,
|
||||
RegionConfig,
|
||||
} from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/platform/abstractions/file-upload/file-upload.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
@ -298,6 +301,7 @@ import {
|
||||
INTRAPROCESS_MESSAGING_SUBJECT,
|
||||
CLIENT_TYPE,
|
||||
REFRESH_ACCESS_TOKEN_ERROR_CALLBACK,
|
||||
ENV_ADDITIONAL_REGIONS,
|
||||
} from "./injection-tokens";
|
||||
import { ModalService } from "./modal.service";
|
||||
|
||||
@ -530,10 +534,14 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: CollectionService,
|
||||
deps: [CryptoServiceAbstraction, EncryptService, I18nServiceAbstraction, StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ENV_ADDITIONAL_REGIONS,
|
||||
useValue: process.env.ADDITIONAL_REGIONS as unknown as RegionConfig[],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: EnvironmentService,
|
||||
useClass: DefaultEnvironmentService,
|
||||
deps: [StateProvider, AccountServiceAbstraction],
|
||||
deps: [StateProvider, AccountServiceAbstraction, ENV_ADDITIONAL_REGIONS],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
|
@ -136,6 +136,7 @@ export class DefaultEnvironmentService implements EnvironmentService {
|
||||
constructor(
|
||||
private stateProvider: StateProvider,
|
||||
private accountService: AccountService,
|
||||
private additionalRegionConfigs: RegionConfig[] = [],
|
||||
) {
|
||||
this.globalState = this.stateProvider.getGlobal(GLOBAL_ENVIRONMENT_KEY);
|
||||
this.globalCloudRegionState = this.stateProvider.getGlobal(GLOBAL_CLOUD_REGION_KEY);
|
||||
@ -177,8 +178,7 @@ export class DefaultEnvironmentService implements EnvironmentService {
|
||||
}
|
||||
|
||||
availableRegions(): RegionConfig[] {
|
||||
const additionalRegions = (process.env.ADDITIONAL_REGIONS as unknown as RegionConfig[]) ?? [];
|
||||
return PRODUCTION_REGIONS.concat(additionalRegions);
|
||||
return PRODUCTION_REGIONS.concat(this.additionalRegionConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user