1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-21 16:18:28 +01:00

[PM-2475][PM-2536] Clicking "US" in region selector sets base URL (#5604)

This commit is contained in:
André Bispo 2023-06-16 14:09:16 +01:00 committed by GitHub
parent 5cd51374d7
commit 1052f00b87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 177 additions and 41 deletions

View File

@ -151,6 +151,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
// eslint-disable-next-line rxjs/no-async-subscribe // eslint-disable-next-line rxjs/no-async-subscribe
childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => { childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => {
modal.close(); modal.close();
this.environmentSelector.updateEnvironmentInfo();
await this.getLoginWithDevice(this.loggedEmail); await this.getLoginWithDevice(this.loggedEmail);
}); });
} }

View File

@ -1,6 +1,6 @@
<router-outlet></router-outlet> <router-outlet></router-outlet>
<div class="container my-5 text-muted text-center"> <div class="container my-5 text-muted text-center">
<div class="tw-mb-1"> <div class="tw-mb-1" *ngIf="!isSelfHosted">
<bit-menu #environmentOptions> <bit-menu #environmentOptions>
<a bitMenuItem href="https://vault.bitwarden.com" class="pr-4"> <a bitMenuItem href="https://vault.bitwarden.com" class="pr-4">
<i <i

View File

@ -11,9 +11,10 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
}) })
export class FrontendLayoutComponent implements OnInit, OnDestroy { export class FrontendLayoutComponent implements OnInit, OnDestroy {
version: string; version: string;
isSelfHosted: boolean;
euServerFlagEnabled: boolean;
year = "2015"; year = "2015";
isEuServer = true; isEuServer = true;
euServerFlagEnabled: boolean;
constructor( constructor(
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
@ -23,6 +24,7 @@ export class FrontendLayoutComponent implements OnInit, OnDestroy {
async ngOnInit() { async ngOnInit() {
this.year = new Date().getFullYear().toString(); this.year = new Date().getFullYear().toString();
this.version = await this.platformUtilsService.getApplicationVersion(); this.version = await this.platformUtilsService.getApplicationVersion();
this.isSelfHosted = this.platformUtilsService.isSelfHost();
this.euServerFlagEnabled = await this.configService.getFeatureFlagBool( this.euServerFlagEnabled = await this.configService.getFeatureFlagBool(
FeatureFlag.DisplayEuEnvironmentFlag FeatureFlag.DisplayEuEnvironmentFlag
); );

View File

@ -6,7 +6,10 @@ import { Subject, takeUntil } from "rxjs";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction"; import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import {
EnvironmentService as EnvironmentServiceAbstraction,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
@Component({ @Component({
selector: "environment-selector", selector: "environment-selector",
@ -37,8 +40,8 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
euServerFlagEnabled: boolean; euServerFlagEnabled: boolean;
isOpen = false; isOpen = false;
showingModal = false; showingModal = false;
selectedEnvironment: ServerEnvironment; selectedEnvironment: Region;
ServerEnvironmentType = ServerEnvironment; ServerEnvironmentType = Region;
overlayPostition: ConnectedPosition[] = [ overlayPostition: ConnectedPosition[] = [
{ {
originX: "start", originX: "start",
@ -50,7 +53,7 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
protected componentDestroyed$: Subject<void> = new Subject(); protected componentDestroyed$: Subject<void> = new Subject();
constructor( constructor(
protected environmentService: EnvironmentService, protected environmentService: EnvironmentServiceAbstraction,
protected configService: ConfigServiceAbstraction, protected configService: ConfigServiceAbstraction,
protected router: Router protected router: Router
) {} ) {}
@ -67,18 +70,20 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
this.componentDestroyed$.complete(); this.componentDestroyed$.complete();
} }
async toggle(option: ServerEnvironment) { async toggle(option: Region) {
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
if (option === null) { if (option === null) {
return; return;
} }
if (option === ServerEnvironment.EU) {
await this.environmentService.setUrls({ base: "https://vault.bitwarden.eu" }); this.updateEnvironmentInfo();
} else if (option === ServerEnvironment.US) {
await this.environmentService.setUrls({ base: "https://vault.bitwarden.com" }); if (option === Region.SelfHosted) {
} else if (option === ServerEnvironment.SelfHosted) {
this.onOpenSelfHostedSettings.emit(); this.onOpenSelfHostedSettings.emit();
return;
} }
await this.environmentService.setRegion(option);
this.updateEnvironmentInfo(); this.updateEnvironmentInfo();
} }
@ -86,14 +91,8 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
this.euServerFlagEnabled = await this.configService.getFeatureFlagBool( this.euServerFlagEnabled = await this.configService.getFeatureFlagBool(
FeatureFlag.DisplayEuEnvironmentFlag FeatureFlag.DisplayEuEnvironmentFlag
); );
const webvaultUrl = this.environmentService.getWebVaultUrl();
if (this.environmentService.isSelfHosted()) { this.selectedEnvironment = this.environmentService.selectedRegion;
this.selectedEnvironment = ServerEnvironment.SelfHosted;
} else if (webvaultUrl != null && webvaultUrl.includes("bitwarden.eu")) {
this.selectedEnvironment = ServerEnvironment.EU;
} else {
this.selectedEnvironment = ServerEnvironment.US;
}
} }
close() { close() {
@ -101,9 +100,3 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy {
this.updateEnvironmentInfo(); this.updateEnvironmentInfo();
} }
} }
enum ServerEnvironment {
US = "US",
EU = "EU",
SelfHosted = "Self-hosted",
}

View File

@ -1,6 +1,9 @@
import { Directive, EventEmitter, Output } from "@angular/core"; import { Directive, EventEmitter, Output } from "@angular/core";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -25,6 +28,9 @@ export class EnvironmentComponent {
private modalService: ModalService private modalService: ModalService
) { ) {
const urls = this.environmentService.getUrls(); const urls = this.environmentService.getUrls();
if (this.environmentService.selectedRegion != Region.SelfHosted) {
return;
}
this.baseUrl = urls.base || ""; this.baseUrl = urls.base || "";
this.webVaultUrl = urls.webVault || ""; this.webVaultUrl = urls.webVault || "";

View File

@ -17,8 +17,17 @@ export type PayPalConfig = {
buttonAction?: string; buttonAction?: string;
}; };
export enum Region {
US = "US",
EU = "EU",
SelfHosted = "Self-hosted",
}
export abstract class EnvironmentService { export abstract class EnvironmentService {
urls: Observable<void>; urls: Observable<void>;
usUrls: Urls;
euUrls: Urls;
selectedRegion?: Region;
hasBaseUrl: () => boolean; hasBaseUrl: () => boolean;
getNotificationsUrl: () => string; getNotificationsUrl: () => string;
@ -32,8 +41,10 @@ export abstract class EnvironmentService {
getScimUrl: () => string; getScimUrl: () => string;
setUrlsFromStorage: () => Promise<void>; setUrlsFromStorage: () => Promise<void>;
setUrls: (urls: Urls) => Promise<Urls>; setUrls: (urls: Urls) => Promise<Urls>;
setRegion: (region: Region) => Promise<void>;
getUrls: () => Urls; getUrls: () => Urls;
isCloud: () => boolean; isCloud: () => boolean;
isEmpty: () => boolean;
/** /**
* @remarks For desktop and browser use only. * @remarks For desktop and browser use only.
* For web, use PlatformUtilsService.isSelfHost() * For web, use PlatformUtilsService.isSelfHost()

View File

@ -263,6 +263,8 @@ export abstract class StateService<T extends Account = Account> {
setEntityType: (value: string, options?: StorageOptions) => Promise<void>; setEntityType: (value: string, options?: StorageOptions) => Promise<void>;
getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>; getEnvironmentUrls: (options?: StorageOptions) => Promise<EnvironmentUrls>;
setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>; setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise<void>;
getRegion: (options?: StorageOptions) => Promise<string>;
setRegion: (value: string, options?: StorageOptions) => Promise<void>;
getEquivalentDomains: (options?: StorageOptions) => Promise<string[][]>; getEquivalentDomains: (options?: StorageOptions) => Promise<string[][]>;
setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>; setEquivalentDomains: (value: string, options?: StorageOptions) => Promise<void>;
getEventCollection: (options?: StorageOptions) => Promise<EventData[]>; getEventCollection: (options?: StorageOptions) => Promise<EventData[]>;

View File

@ -233,6 +233,7 @@ export class AccountSettings {
approveLoginRequests?: boolean; approveLoginRequests?: boolean;
avatarColor?: string; avatarColor?: string;
activateAutoFillOnPageLoadFromPolicy?: boolean; activateAutoFillOnPageLoadFromPolicy?: boolean;
region?: string;
smOnboardingTasks?: Record<string, Record<string, boolean>>; smOnboardingTasks?: Record<string, Record<string, boolean>>;
static fromJSON(obj: Jsonify<AccountSettings>): AccountSettings { static fromJSON(obj: Jsonify<AccountSettings>): AccountSettings {

View File

@ -36,4 +36,5 @@ export class GlobalState {
enableBrowserIntegration?: boolean; enableBrowserIntegration?: boolean;
enableBrowserIntegrationFingerprint?: boolean; enableBrowserIntegrationFingerprint?: boolean;
enableDuckDuckGoBrowserIntegration?: boolean; enableDuckDuckGoBrowserIntegration?: boolean;
region?: string;
} }

View File

@ -3,6 +3,7 @@ import { concatMap, Observable, Subject } from "rxjs";
import { EnvironmentUrls } from "../../auth/models/domain/environment-urls"; import { EnvironmentUrls } from "../../auth/models/domain/environment-urls";
import { import {
EnvironmentService as EnvironmentServiceAbstraction, EnvironmentService as EnvironmentServiceAbstraction,
Region,
Urls, Urls,
} from "../abstractions/environment.service"; } from "../abstractions/environment.service";
import { StateService } from "../abstractions/state.service"; import { StateService } from "../abstractions/state.service";
@ -10,6 +11,7 @@ import { StateService } from "../abstractions/state.service";
export class EnvironmentService implements EnvironmentServiceAbstraction { export class EnvironmentService implements EnvironmentServiceAbstraction {
private readonly urlsSubject = new Subject<void>(); private readonly urlsSubject = new Subject<void>();
urls: Observable<void> = this.urlsSubject.asObservable(); urls: Observable<void> = this.urlsSubject.asObservable();
selectedRegion?: Region;
protected baseUrl: string; protected baseUrl: string;
protected webVaultUrl: string; protected webVaultUrl: string;
@ -21,6 +23,28 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
private keyConnectorUrl: string; private keyConnectorUrl: string;
private scimUrl: string = null; private scimUrl: string = null;
readonly usUrls: Urls = {
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/v2",
};
readonly euUrls: Urls = {
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/v2",
};
constructor(private stateService: StateService) { constructor(private stateService: StateService) {
this.stateService.activeAccount$ this.stateService.activeAccount$
.pipe( .pipe(
@ -127,20 +151,42 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
} }
async setUrlsFromStorage(): Promise<void> { async setUrlsFromStorage(): Promise<void> {
const urls: any = await this.stateService.getEnvironmentUrls(); const region = await this.stateService.getRegion();
const savedUrls = await this.stateService.getEnvironmentUrls();
const envUrls = new EnvironmentUrls(); const envUrls = new EnvironmentUrls();
this.baseUrl = envUrls.base = urls.base; // fix environment urls for old users
this.webVaultUrl = urls.webVault; if (savedUrls.base === "https://vault.bitwarden.com") {
this.apiUrl = envUrls.api = urls.api; this.setRegion(Region.US);
this.identityUrl = envUrls.identity = urls.identity; return;
this.iconsUrl = urls.icons; }
this.notificationsUrl = urls.notifications; if (savedUrls.base === "https://vault.bitwarden.eu") {
this.eventsUrl = envUrls.events = urls.events; this.setRegion(Region.EU);
this.keyConnectorUrl = urls.keyConnector; return;
// scimUrl is not saved to storage }
this.urlsSubject.next(); switch (region) {
case Region.EU:
this.setRegion(Region.EU);
return;
case Region.US:
this.setRegion(Region.US);
return;
case Region.SelfHosted:
default:
this.baseUrl = envUrls.base = savedUrls.base;
this.webVaultUrl = savedUrls.webVault;
this.apiUrl = envUrls.api = savedUrls.api;
this.identityUrl = envUrls.identity = savedUrls.identity;
this.iconsUrl = savedUrls.icons;
this.notificationsUrl = savedUrls.notifications;
this.eventsUrl = envUrls.events = savedUrls.events;
this.keyConnectorUrl = savedUrls.keyConnector;
// scimUrl is not saved to storage
this.urlsSubject.next();
this.setRegion(Region.SelfHosted);
break;
}
} }
async setUrls(urls: Urls): Promise<Urls> { async setUrls(urls: Urls): Promise<Urls> {
@ -178,6 +224,8 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
this.keyConnectorUrl = urls.keyConnector; this.keyConnectorUrl = urls.keyConnector;
this.scimUrl = urls.scim; this.scimUrl = urls.scim;
await this.setRegion(Region.SelfHosted);
this.urlsSubject.next(); this.urlsSubject.next();
return urls; return urls;
@ -197,6 +245,52 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
}; };
} }
isEmpty(): boolean {
return (
this.baseUrl == null &&
this.webVaultUrl == null &&
this.apiUrl == null &&
this.identityUrl == null &&
this.iconsUrl == null &&
this.notificationsUrl == null &&
this.eventsUrl == null
);
}
async setRegion(region: Region) {
this.selectedRegion = region;
await this.stateService.setRegion(region);
switch (region) {
case Region.EU:
this.setUrlsInternal(this.euUrls);
break;
case Region.US:
this.setUrlsInternal(this.usUrls);
break;
case Region.SelfHosted:
// if user saves with empty fields, default to US
if (this.isEmpty()) {
this.setRegion(Region.US);
}
break;
}
}
private setUrlsInternal(urls: Urls) {
this.baseUrl = this.formatUrl(urls.base);
this.webVaultUrl = this.formatUrl(urls.webVault);
this.apiUrl = this.formatUrl(urls.api);
this.identityUrl = this.formatUrl(urls.identity);
this.iconsUrl = this.formatUrl(urls.icons);
this.notificationsUrl = this.formatUrl(urls.notifications);
this.eventsUrl = this.formatUrl(urls.events);
this.keyConnectorUrl = this.formatUrl(urls.keyConnector);
// scimUrl cannot be cleared
this.scimUrl = this.formatUrl(urls.scim) ?? this.scimUrl;
this.urlsSubject.next();
}
private formatUrl(url: string): string { private formatUrl(url: string): string {
if (url == null || url === "") { if (url == null || url === "") {
return null; return null;
@ -211,9 +305,12 @@ export class EnvironmentService implements EnvironmentServiceAbstraction {
} }
isCloud(): boolean { isCloud(): boolean {
return ["https://api.bitwarden.com", "https://vault.bitwarden.com/api"].includes( return [
this.getApiUrl() "https://api.bitwarden.com",
); "https://vault.bitwarden.com/api",
"https://api.bitwarden.eu",
"https://vault.bitwarden.eu/api",
].includes(this.getApiUrl());
} }
isSelfHosted(): boolean { isSelfHosted(): boolean {

View File

@ -1598,6 +1598,28 @@ export class StateService<
); );
} }
async getRegion(options?: StorageOptions): Promise<string> {
if ((await this.state())?.activeUserId == null) {
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
return (await this.getGlobals(options)).region ?? null;
}
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());
return (await this.getAccount(options))?.settings?.region ?? null;
}
async setRegion(value: string, options?: StorageOptions): Promise<void> {
// Global values are set on each change and the current global settings are passed to any newly authed accounts.
// This is to allow setting region values before an account is active, while still allowing individual accounts to have their own region.
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
globals.region = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
}
async getEquivalentDomains(options?: StorageOptions): Promise<string[][]> { async getEquivalentDomains(options?: StorageOptions): Promise<string[][]> {
return ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))