From 876761c30085b63086395c4f7774ab2d8630ff80 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 15 Jul 2025 20:37:31 -0400 Subject: [PATCH] PM-23733 - Refactor guards and extend env to be able to determine self hosted and prod --- apps/web/src/app/oss-routing.module.ts | 5 +++-- .../guard/prevent-prod-access.guard.ts | 8 +------ .../prevent-self-hosted-access.guard.spec.ts | 1 + .../guard/prevent-self-hosted-access.guard.ts | 22 +++++++++++++++++++ .../abstractions/environment.service.ts | 14 ++++++++++++ .../services/default-environment.service.ts | 8 +++++++ 6 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 libs/angular/src/platform/guard/prevent-self-hosted-access.guard.spec.ts create mode 100644 libs/angular/src/platform/guard/prevent-self-hosted-access.guard.ts diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 8b7ddb07eae..901376e0591 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -14,6 +14,7 @@ import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-manage import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { preventProdAccessGuard } from "@bitwarden/angular/platform/guard/prevent-prod-access.guard"; +import { preventSelfHostedAccessGuard } from "@bitwarden/angular/platform/guard/prevent-self-hosted-access.guard"; import { PasswordHintComponent, RegistrationFinishComponent, @@ -179,7 +180,7 @@ const routes: Routes = [ children: [ { path: "feature-flags", - canMatch: [preventProdAccessGuard], + canMatch: [preventProdAccessGuard, preventSelfHostedAccessGuard], data: { pageTitle: { key: "featureFlags", @@ -718,7 +719,7 @@ const routes: Routes = [ { path: "developer-tools", data: { titleId: "developerTools" } satisfies RouteDataProperties, - canMatch: [preventProdAccessGuard], + canMatch: [preventProdAccessGuard, preventSelfHostedAccessGuard], loadComponent: () => import("./platform/settings/developer-tools").then((m) => m.DeveloperToolsComponent), children: [ diff --git a/libs/angular/src/platform/guard/prevent-prod-access.guard.ts b/libs/angular/src/platform/guard/prevent-prod-access.guard.ts index b5b1e8847e0..a9a67ea8170 100644 --- a/libs/angular/src/platform/guard/prevent-prod-access.guard.ts +++ b/libs/angular/src/platform/guard/prevent-prod-access.guard.ts @@ -3,9 +3,7 @@ import { CanMatchFn } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { PRODUCTION_REGIONS } from "@bitwarden/common/platform/services/default-environment.service"; -// TODO: consider moving logic to service and also not showing when self hosted. /** * Guard to prevent matching routes in production environments. * Allows for developer tooling that should only be accessible in non-production environments. @@ -15,11 +13,7 @@ export const preventProdAccessGuard: CanMatchFn = async (): Promise => const environment = await firstValueFrom(environmentService.environment$); - const region = environment.getRegion(); - - const prodRegions = PRODUCTION_REGIONS.map((regionConfig) => regionConfig.key); - - if (prodRegions.includes(region)) { + if (environment.isProduction()) { return false; } diff --git a/libs/angular/src/platform/guard/prevent-self-hosted-access.guard.spec.ts b/libs/angular/src/platform/guard/prevent-self-hosted-access.guard.spec.ts new file mode 100644 index 00000000000..3514a5dd418 --- /dev/null +++ b/libs/angular/src/platform/guard/prevent-self-hosted-access.guard.spec.ts @@ -0,0 +1 @@ +// TODO: test diff --git a/libs/angular/src/platform/guard/prevent-self-hosted-access.guard.ts b/libs/angular/src/platform/guard/prevent-self-hosted-access.guard.ts new file mode 100644 index 00000000000..5e3bf6a8c8f --- /dev/null +++ b/libs/angular/src/platform/guard/prevent-self-hosted-access.guard.ts @@ -0,0 +1,22 @@ +import { inject } from "@angular/core"; +import { CanMatchFn } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; + +// TODO: should we have a devTools guard instead of prod env and self hosted env checks? +/** + * Guard to prevent matching routes in self-hosted environments. + * Allows for developer tooling that should only be accessible in non-self-hosted environments. + */ +export const preventSelfHostedAccessGuard: CanMatchFn = async (): Promise => { + const environmentService = inject(EnvironmentService); + + const environment = await firstValueFrom(environmentService.environment$); + + if (environment.isSelfHosted()) { + return false; + } + + return true; +}; diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index b8931656848..b36a512c75b 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -60,6 +60,20 @@ export interface Environment { */ isCloud(): boolean; + /** + * Identify if the region is a self-hosted environment. + * + * @returns true if the environment is self-hosted, false otherwise. + */ + isSelfHosted(): boolean; + + /** + * Identify if the environment is a production environment. + * + * @returns true if the environment is production, false otherwise. + */ + isProduction(): boolean; + getApiUrl(): string; getEventsUrl(): string; getIconsUrl(): string; diff --git a/libs/common/src/platform/services/default-environment.service.ts b/libs/common/src/platform/services/default-environment.service.ts index df55693ba0b..9f1445a4e9f 100644 --- a/libs/common/src/platform/services/default-environment.service.ts +++ b/libs/common/src/platform/services/default-environment.service.ts @@ -410,6 +410,14 @@ abstract class UrlEnvironment implements Environment { return this.region !== Region.SelfHosted; } + isSelfHosted(): boolean { + return this.region === Region.SelfHosted; + } + + isProduction(): boolean { + return PRODUCTION_REGIONS.some((regionConfig) => regionConfig.key === this.region); + } + /** * Helper for getting an URL. *