From c41ec09aba8fcd656478fa2cff9b00d12ecf9a44 Mon Sep 17 00:00:00 2001 From: dfunkt Date: Fri, 2 Aug 2024 16:34:28 +0300 Subject: [PATCH 1/4] Update Github Actions & Dockerfile - switch to latest version of Dockerfile to enable linter - fix FromAsCasing warning --- .github/workflows/release.yml | 8 ++++---- Dockerfile | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1fa1407..dffd084 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: HAVE_GHCR_LOGIN: ${{ vars.GHCR_REPO != '' && github.repository_owner != '' && secrets.GITHUB_TOKEN != '' }} steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # Determine Docker Tag - name: Init Variables @@ -34,7 +34,7 @@ jobs: # Login to Docker Hub - name: Login to Docker Hub - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -49,7 +49,7 @@ jobs: # Login to GitHub Container Registry - name: Login to GitHub Container Registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -64,7 +64,7 @@ jobs: | tee -a "${GITHUB_ENV}" - name: Build and push - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 with: context: . push: true diff --git a/Dockerfile b/Dockerfile index 7731e93..aa98588 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + # Compile the web vault using docker # Usage: # Quick and easy: @@ -15,7 +17,7 @@ # Note: you can use --build-arg to specify the version to build: # docker build -t web_vault_build --build-arg VAULT_VERSION=main . -FROM node:18-bookworm as build +FROM node:18-bookworm AS build RUN node --version && npm --version # Prepare the folder to enable non-root, otherwise npm will refuse to run the postinstall From 36439a9baf6b34311b494eb1198176a9587a7b8c Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Fri, 5 Jul 2024 04:36:04 +0200 Subject: [PATCH 2/4] update node to v20 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aa98588..49c4e6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # Note: you can use --build-arg to specify the version to build: # docker build -t web_vault_build --build-arg VAULT_VERSION=main . -FROM node:18-bookworm AS build +FROM node:20-bookworm AS build RUN node --version && npm --version # Prepare the folder to enable non-root, otherwise npm will refuse to run the postinstall From 7b86da5d5875b447d2446f4d41447136751a350a Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Wed, 19 Jun 2024 12:18:11 +0200 Subject: [PATCH 3/4] build without the bitwarden_license/ directory --- scripts/apply_patches.sh | 3 +++ scripts/generate_patch_file.sh | 1 + 2 files changed, 4 insertions(+) diff --git a/scripts/apply_patches.sh b/scripts/apply_patches.sh index f7c92d3..1ee001a 100755 --- a/scripts/apply_patches.sh +++ b/scripts/apply_patches.sh @@ -49,6 +49,9 @@ replace_embedded_svg_icon \ ../resources/vaultwarden-password-manager-logo.svg \ ./apps/web/src/app/layouts/password-manager-logo.ts +echo "Remove non-free bitwarden_license/ code" +rm -rf ./bitwarden_license/ + echo "Using patch: ${PATCH_NAME}" git apply "../patches/${PATCH_NAME}" --reject diff --git a/scripts/generate_patch_file.sh b/scripts/generate_patch_file.sh index b62fddd..af71868 100755 --- a/scripts/generate_patch_file.sh +++ b/scripts/generate_patch_file.sh @@ -38,6 +38,7 @@ if [ "$(git status --porcelain | wc -l)" -ge 1 ]; then ':!apps/web/src/images/icons/safari-pinned-tab.svg' \ ':!apps/web/src/app/admin-console/icons/admin-console-logo.ts' \ ':!apps/web/src/app/layouts/password-manager-logo.ts' \ + ':!bitwarden_license/' \ > "../patches/${PATCH_FILENAME}" echo "Patch has been created here: patches/${PATCH_FILENAME}" else From 3a2220d76dc0df4423f658ef6e08676c4ff7ae97 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Tue, 6 Aug 2024 18:32:15 +0200 Subject: [PATCH 4/4] update web-vault to v2024.6.2 --- Dockerfile | 4 +- patches/v2024.6.2.patch | 1123 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1125 insertions(+), 2 deletions(-) create mode 100644 patches/v2024.6.2.patch diff --git a/Dockerfile b/Dockerfile index 49c4e6c..c129351 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,8 +28,8 @@ USER node # Can be a tag, release, but prefer a commit hash because it's not changeable # https://github.com/bitwarden/clients/commit/${VAULT_VERSION} # -# Using https://github.com/bitwarden/clients/releases/tag/web-v2024.5.1 -ARG VAULT_VERSION=9823f69c9d17e2d94de1cc005e01202dd95f0647 +# Using https://github.com/bitwarden/clients/releases/tag/web-v2024.6.2 +ARG VAULT_VERSION=e2354e8694ab5e532d04f275e4bd6bf560c7509b WORKDIR /vault RUN git -c init.defaultBranch=main init && \ diff --git a/patches/v2024.6.2.patch b/patches/v2024.6.2.patch new file mode 100644 index 0000000..873840e --- /dev/null +++ b/patches/v2024.6.2.patch @@ -0,0 +1,1123 @@ +diff --git a/apps/web/package.json b/apps/web/package.json +index 82c447c9b4..4ae2970707 100644 +--- a/apps/web/package.json ++++ b/apps/web/package.json +@@ -3,11 +3,8 @@ + "version": "2024.6.2", + "scripts": { + "build:oss": "webpack", +- "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:oss:watch": "webpack serve", +- "build:bit:watch": "webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:bit:dev": "cross-env ENV=development npm run build:bit", +- "build:bit:dev:analyze": "cross-env LOGGING=false webpack -c ../../bitwarden_license/bit-web/webpack.config.js --profile --json > stats.json && npx webpack-bundle-analyzer stats.json build/", + "build:bit:dev:watch": "cross-env ENV=development NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:bit:watch", + "build:bit:qa": "cross-env NODE_ENV=production ENV=qa npm run build:bit", + "build:bit:euprd": "cross-env NODE_ENV=production ENV=euprd npm run build:bit", +diff --git a/apps/web/src/app/admin-console/organizations/create/organization-information.component.html b/apps/web/src/app/admin-console/organizations/create/organization-information.component.html +index e0a8006081..789efd9264 100644 +--- a/apps/web/src/app/admin-console/organizations/create/organization-information.component.html ++++ b/apps/web/src/app/admin-console/organizations/create/organization-information.component.html +@@ -12,7 +12,7 @@ + + + +- {{ "billingEmail" | i18n }} ++ {{ "email" | i18n }} + + + +diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +index 4383656bee..eb2f6a154e 100644 +--- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts ++++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +@@ -147,6 +147,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { + } + + canShowBillingTab(organization: Organization): boolean { ++ return false; // disable billing tab in Vaultwarden + return canAccessBillingTab(organization); + } + +diff --git a/apps/web/src/app/admin-console/organizations/members/people.component.ts b/apps/web/src/app/admin-console/organizations/members/people.component.ts +index a47e0acd0c..06f21a5361 100644 +--- a/apps/web/src/app/admin-console/organizations/members/people.component.ts ++++ b/apps/web/src/app/admin-console/organizations/members/people.component.ts +@@ -182,11 +182,7 @@ export class PeopleComponent extends NewBasePeopleComponent p.organizationId === this.organization.id); + this.orgResetPasswordPolicyEnabled = resetPasswordPolicy?.enabled; + +- const billingMetadata = await this.billingApiService.getOrganizationBillingMetadata( +- this.organization.id, +- ); +- +- this.orgIsOnSecretsManagerStandalone = billingMetadata.isOnSecretsManagerStandalone; ++ this.orgIsOnSecretsManagerStandalone = false; // don't get billing metadata + + await this.load(); + +diff --git a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +index 7abee6b0d0..2e3b789b23 100644 +--- a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts ++++ b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +@@ -68,13 +68,6 @@ const routes: Routes = [ + (m) => m.OrganizationReportingModule, + ), + }, +- { +- path: "billing", +- loadChildren: () => +- import("../../billing/organizations/organization-billing.module").then( +- (m) => m.OrganizationBillingModule, +- ), +- }, + ], + }, + ]; +diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html +index ae27df1ce9..8a644c63e3 100644 +--- a/apps/web/src/app/admin-console/organizations/settings/account.component.html ++++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html +@@ -17,7 +17,7 @@ + + + +- {{ "billingEmail" | i18n }} ++ {{ "email" | i18n }} + + + +diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts +index 41cf9b9e8f..4d7f6426fd 100644 +--- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts ++++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts +@@ -87,7 +87,7 @@ export class AccountComponent { + ) {} + + async ngOnInit() { +- this.selfHosted = this.platformUtilsService.isSelfHost(); ++ this.selfHosted = false; // set to false so we can rename organizations + + this.route.params + .pipe( +@@ -169,6 +169,7 @@ export class AccountComponent { + }; + + submitCollectionManagement = async () => { ++ return; // flexible collections are not supported by Vaultwarden + // Early exit if self-hosted + if (this.selfHosted) { + return; +diff --git a/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts b/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts +index b228a4d135..e262fa51ff 100644 +--- a/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts ++++ b/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts +@@ -2,6 +2,8 @@ import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; + import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; + import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; + import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; ++import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; ++import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; + import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +@@ -26,6 +28,7 @@ export class EnrollMasterPasswordReset { + i18nService: I18nService, + syncService: SyncService, + logService: LogService, ++ userVerificationService: UserVerificationService, + ) { + const result = await UserVerificationDialogComponent.open(dialogService, { + title: "enrollAccountRecovery", +@@ -33,36 +36,42 @@ export class EnrollMasterPasswordReset { + text: "resetPasswordEnrollmentWarning", + type: "warning", + }, ++ verificationType: { ++ type: "custom", ++ verificationFn: async (secret: VerificationWithSecret) => { ++ const request = ++ await userVerificationService.buildRequest( ++ secret, ++ ); ++ request.resetPasswordKey = await resetPasswordService.buildRecoveryKey( ++ data.organization.id, ++ ); ++ ++ // Process the enrollment request, which is an endpoint that is ++ // gated by a server-side check of the master password hash ++ await organizationUserService.putOrganizationUserResetPasswordEnrollment( ++ data.organization.id, ++ data.organization.userId, ++ request, ++ ); ++ return true; ++ }, ++ }, + }); + +- // Handle the result of the dialog based on user action and verification success ++ // User canceled enrollment + if (result.userAction === "cancel") { + return; + } + +- // User confirmed the dialog so check verification success ++ // Enrollment failed + if (!result.verificationSuccess) { +- // verification failed + return; + } + +- // Verification succeeded ++ // Enrollment succeeded + try { +- // This object is missing most of the properties in the +- // `OrganizationUserResetPasswordEnrollmentRequest()`, but those +- // properties don't carry over to the server model anyway and are +- // never used by this flow. +- const request = new OrganizationUserResetPasswordEnrollmentRequest(); +- request.resetPasswordKey = await resetPasswordService.buildRecoveryKey(data.organization.id); +- +- await organizationUserService.putOrganizationUserResetPasswordEnrollment( +- data.organization.id, +- data.organization.userId, +- request, +- ); +- + platformUtilsService.showToast("success", null, i18nService.t("enrollPasswordResetSuccess")); +- + await syncService.fullSync(true); + } catch (e) { + logService.error(e); +diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts +index 254f23eeb2..133eb4c059 100644 +--- a/apps/web/src/app/app.component.ts ++++ b/apps/web/src/app/app.component.ts +@@ -219,6 +219,10 @@ export class AppComponent implements OnDestroy, OnInit { + break; + } + case "showToast": ++ if (typeof message.text === "string" && typeof crypto.subtle === "undefined") { ++ message.title = "This browser requires HTTPS to use the web vault"; ++ message.text = "Check the Vaultwarden wiki for details on how to enable it"; ++ } + this.toastService._showToast(message); + break; + case "convertAccountToKeyConnector": +diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html +index 4690a4e63a..9d297671d2 100644 +--- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html ++++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html +@@ -1,6 +1,6 @@ +
+
+- ++ +

+ +

+- ++ +

+ {{ "loginOrCreateNewAccount" | i18n }} +

+@@ -51,7 +51,7 @@ + +
+ +-
++
+

{{ "or" | i18n }}

+ + + +- +- +diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +index 88b695eb72..f9aafc450a 100644 +--- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts ++++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +@@ -112,11 +112,11 @@ export class TwoFactorAuthenticatorComponent + new window.QRious({ + element: document.getElementById("qr"), + value: +- "otpauth://totp/Bitwarden:" + ++ "otpauth://totp/Vaultwarden:" + + Utils.encodeRFC3986URIComponent(email) + + "?secret=" + + encodeURIComponent(this.key) + +- "&issuer=Bitwarden", ++ "&issuer=Vaultwarden", + size: 160, + }); + }, 100); +diff --git a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts +index 78872aa6a9..eed953b91a 100644 +--- a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts ++++ b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts +@@ -44,7 +44,7 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy { + return; + } + this.loading = true; +- this.billing = await this.organizationApiService.getBilling(this.organizationId); ++ this.billing = null; + this.loading = false; + } + } +diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html +index 1bd6b99dd1..ed391eb94c 100644 +--- a/apps/web/src/app/billing/organizations/organization-plans.component.html ++++ b/apps/web/src/app/billing/organizations/organization-plans.component.html +@@ -6,7 +6,7 @@ + >
+ {{ "loading" | i18n }} + +- ++ +

{{ "uploadLicenseFileOrg" | i18n }}

+
+ +@@ -33,12 +33,7 @@ + + +
+-
++ + + !!plan.PasswordManager); +@@ -192,6 +193,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + this.plan = providerDefaultPlan.type; + this.product = providerDefaultPlan.product; + } ++ end of asking /api/plans in Vaultwarden */ + + if (!this.createOrganization) { + this.upgradeFlowPrefillForm(); +@@ -263,6 +265,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + } + + get selectableProducts() { ++ return null; // there are no products to select in Vaultwarden + if (this.acceptingSponsorship) { + const familyPlan = this.passwordManagerPlans.find( + (plan) => plan.type === PlanType.FamiliesAnnually, +@@ -294,6 +297,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + } + + get selectablePlans() { ++ return null; // no plans to select in Vaultwarden + const selectedProductType = this.formGroup.controls.product.value; + const result = + this.passwordManagerPlans?.filter( +@@ -435,6 +439,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + } + + get planOffersSecretsManager() { ++ return false; // no support for secrets manager in Vaultwarden + return this.selectedSecretsManagerPlan != null; + } + +@@ -443,6 +448,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + } + + changedProduct() { ++ return; // no choice of products in Vaultwarden + const selectedPlan = this.selectablePlans[0]; + + this.setPlanType(selectedPlan.type); +@@ -562,11 +568,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + const collectionCt = collection.encryptedString; + const orgKeys = await this.cryptoService.makeKeyPair(orgKey[1]); + +- if (this.selfHosted) { +- orgId = await this.createSelfHosted(key, collectionCt, orgKeys); +- } else { +- orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); +- } ++ // always use createCloudHosted() to disable license file upload ++ orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1]); + + this.platformUtilsService.showToast( + "success", +@@ -659,7 +662,9 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + request.billingEmail = this.formGroup.controls.billingEmail.value; + request.initiationPath = "New organization creation in-product"; + request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); ++ request.planType = PlanType.Free; // always select the free plan in Vaultwarden + ++ /* there is no plan to select in Vaultwarden + if (this.selectedPlan.type === PlanType.Free) { + request.planType = PlanType.Free; + } else { +@@ -686,6 +691,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + + // Secrets Manager + this.buildSecretsManagerRequest(request); ++ end plan selection and no support for secret manager in Vaultwarden */ + + if (this.hasProvider) { + const providerRequest = new ProviderOrganizationCreateRequest( +@@ -765,6 +771,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { + } + + private upgradeFlowPrefillForm() { ++ return; // Vaultwarden only supports free plan + if (this.acceptingSponsorship) { + this.formGroup.controls.product.setValue(ProductType.Families); + this.changedProduct(); +diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts +index c0c1ec2640..fe4422a3b0 100644 +--- a/apps/web/src/app/core/router.service.ts ++++ b/apps/web/src/app/core/router.service.ts +@@ -45,7 +45,7 @@ export class RouterService { + .subscribe((event: NavigationEnd) => { + this.currentUrl = event.url; + +- let title = i18nService.t("bitWebVault"); ++ let title = "Vaultwarden Web"; + + if (this.currentUrl.includes("/sm/")) { + title = i18nService.t("bitSecretsManager"); +diff --git a/apps/web/src/app/core/web-platform-utils.service.ts b/apps/web/src/app/core/web-platform-utils.service.ts +index 02c7c29e34..9fd100024a 100644 +--- a/apps/web/src/app/core/web-platform-utils.service.ts ++++ b/apps/web/src/app/core/web-platform-utils.service.ts +@@ -133,14 +133,17 @@ export class WebPlatformUtilsService implements PlatformUtilsService { + } + + isDev(): boolean { ++ return false; // treat Vaultwarden as production ready + return process.env.NODE_ENV === "development"; + } + + isSelfHost(): boolean { ++ return true; // treat Vaultwarden as self hosted + return WebPlatformUtilsService.isSelfHost(); + } + + static isSelfHost(): boolean { ++ return true; // treat Vaultwarden as self hosted + return process.env.ENV.toString() === "selfhosted"; + } + +diff --git a/apps/web/src/app/layouts/frontend-layout.component.html b/apps/web/src/app/layouts/frontend-layout.component.html +index 72f0f1f1da..cea0867131 100644 +--- a/apps/web/src/app/layouts/frontend-layout.component.html ++++ b/apps/web/src/app/layouts/frontend-layout.component.html +@@ -1,6 +1,11 @@ + +
+- +- © {{ year }} Bitwarden Inc.
++ Vaultwarden Web
+ {{ "versionNumber" | i18n: version }} ++

++
++ A modified version of the Bitwarden® Web Vault for Vaultwarden (an unofficial rewrite of the ++ Bitwarden® server).
++ Vaultwarden is not associated with the Bitwarden® project nor Bitwarden Inc. ++
+
+diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html +index e2b3e7910a..fda32e9257 100644 +--- a/apps/web/src/app/layouts/header/web-header.component.html ++++ b/apps/web/src/app/layouts/header/web-header.component.html +@@ -89,7 +89,12 @@ + + {{ "accountSettings" | i18n }} +
+- ++ + + {{ "getHelp" | i18n }} + +diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html +index 17698367bf..e1ea9cbab4 100644 +--- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html ++++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html +@@ -11,7 +11,7 @@ + + +
+ {{ "moreFromBitwarden" | i18n }} +diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html +index 62d8b6a075..ec8a14c115 100644 +--- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html ++++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html +@@ -30,7 +30,7 @@ + + +
+ {{ "moreFromBitwarden" | i18n }} +diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts +index 1ce8d4d227..ca8268adcc 100644 +--- a/apps/web/src/app/layouts/user-layout.component.ts ++++ b/apps/web/src/app/layouts/user-layout.component.ts +@@ -1,7 +1,7 @@ + import { CommonModule } from "@angular/common"; + import { Component, OnInit } from "@angular/core"; + import { RouterModule } from "@angular/router"; +-import { Observable, combineLatest, concatMap } from "rxjs"; ++import { Observable, of } from "rxjs"; + + import { JslibModule } from "@bitwarden/angular/jslib.module"; + import { ApiService } from "@bitwarden/common/abstractions/api.service"; +@@ -58,26 +58,7 @@ export class UserLayoutComponent implements OnInit { + + await this.syncService.fullSync(false); + +- this.hasFamilySponsorshipAvailable$ = this.organizationService.canManageSponsorships$; +- +- // We want to hide the subscription menu for organizations that provide premium. +- // Except if the user has premium personally or has a billing history. +- this.showSubscription$ = combineLatest([ +- this.billingAccountProfileStateService.hasPremiumPersonally$, +- this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$, +- ]).pipe( +- concatMap(async ([hasPremiumPersonally, hasPremiumFromOrg]) => { +- const isCloud = !this.platformUtilsService.isSelfHost(); +- +- let billing = null; +- if (isCloud) { +- // TODO: We should remove the need to call this! +- billing = await this.apiService.getUserBillingHistory(); +- } +- +- const cloudAndBillingHistory = isCloud && !billing?.hasNoHistory; +- return hasPremiumPersonally || !hasPremiumFromOrg || cloudAndBillingHistory; +- }), +- ); ++ this.hasFamilySponsorshipAvailable$ = of(false); // disable family Sponsorships in Vaultwarden ++ this.showSubscription$ = of(false); // always hide subscriptions in Vaultwarden + } + } +diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts +index e543a6f083..343933b043 100644 +--- a/apps/web/src/app/oss-routing.module.ts ++++ b/apps/web/src/app/oss-routing.module.ts +@@ -236,13 +236,6 @@ const routes: Routes = [ + component: DomainRulesComponent, + data: { titleId: "domainRules" }, + }, +- { +- path: "subscription", +- loadChildren: () => +- import("./billing/individual/individual-billing.module").then( +- (m) => m.IndividualBillingModule, +- ), +- }, + { + path: "emergency-access", + children: [ +diff --git a/apps/web/src/app/platform/web-environment.service.ts b/apps/web/src/app/platform/web-environment.service.ts +index c2eb37eea5..2b5ac93392 100644 +--- a/apps/web/src/app/platform/web-environment.service.ts ++++ b/apps/web/src/app/platform/web-environment.service.ts +@@ -27,8 +27,17 @@ export class WebEnvironmentService extends DefaultEnvironmentService { + super(stateProvider, accountService); + + // 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; ++ // If the base URL is `https://vaultwarden.example.com/base/path/`, ++ // `window.location.href` should have one of the following forms: ++ // ++ // - `https://vaultwarden.example.com/base/path/` ++ // - `https://vaultwarden.example.com/base/path/#/some/route[?queryParam=...]` ++ // ++ // We want to get to just `https://vaultwarden.example.com/base/path`. ++ let baseUrl = this.win.location.href; ++ baseUrl = baseUrl.replace(/#.*/, ""); // Strip off `#` and everything after. ++ baseUrl = baseUrl.replace(/\/+$/, ""); // Trim any trailing `/` chars. ++ const urls = { base: baseUrl }; + + // Find the region + const domain = Utils.getDomain(this.win.location.href); +diff --git a/apps/web/src/app/tools/send/access.component.html b/apps/web/src/app/tools/send/access.component.html +index 6fef7d361d..1deb1164ff 100644 +--- a/apps/web/src/app/tools/send/access.component.html ++++ b/apps/web/src/app/tools/send/access.component.html +@@ -2,7 +2,7 @@ +
+- ++ +
+

View Send

+
+@@ -66,19 +66,6 @@ +
+

+ {{ "sendAccessTaglineProductDesc" | i18n }} +- {{ "sendAccessTaglineLearnMore" | i18n }} +- Bitwarden Send +- {{ "sendAccessTaglineOr" | i18n }} +- {{ +- "sendAccessTaglineSignUp" | i18n +- }} +- {{ "sendAccessTaglineTryToday" | i18n }} +

+
+
+diff --git a/apps/web/src/app/tools/send/add-edit.component.html b/apps/web/src/app/tools/send/add-edit.component.html +index 3225b61350..2a192514bf 100644 +--- a/apps/web/src/app/tools/send/add-edit.component.html ++++ b/apps/web/src/app/tools/send/add-edit.component.html +@@ -227,7 +227,12 @@ + {{ "password" | i18n }} + {{ "newPassword" | i18n }} + +- ++ + + {{ "sendPasswordDesc" | i18n }} + +diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +index 8dd63e62dd..1a1e45cc35 100644 +--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts ++++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +@@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli + import { PolicyType } from "@bitwarden/common/admin-console/enums"; + import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; + import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; ++import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; + import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +@@ -48,6 +49,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private dialogService: DialogService, + private resetPasswordService: OrganizationUserResetPasswordService, ++ private userVerificationService: UserVerificationService, + ) {} + + async ngOnInit() { +@@ -155,6 +157,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { + this.i18nService, + this.syncService, + this.logService, ++ this.userVerificationService, + ); + } else { + // Remove reset password +diff --git a/apps/web/src/index.html b/apps/web/src/index.html +index c3a2c03ed9..1a326771a6 100644 +--- a/apps/web/src/index.html ++++ b/apps/web/src/index.html +@@ -5,7 +5,7 @@ + + + +- Bitwarden Web Vault ++ Vaultwarden Web + + + +@@ -17,7 +17,7 @@ + +
+
+- ++ +

+ form:nth-child(1) > div:nth-child(3) { ++ @extend %vw-hide; ++} ++ ++/* Hide the `This account is owned by a business` checkbox and label */ ++#ownedBusiness, ++label[for^="ownedBusiness"] { ++ @extend %vw-hide; ++} ++ ++/* Hide the radio button and label for the `Custom` org user type */ ++#userTypeCustom, ++label[for^="userTypeCustom"] { ++ @extend %vw-hide; ++} ++ ++/* Hide Business Name */ ++app-org-account form div bit-form-field.tw-block:nth-child(3) { ++ @extend %vw-hide; ++} ++ ++/* Hide organization plans */ ++app-organization-plans > form > h2.mt-5 { ++ @extend %vw-hide; ++} ++ ++/* Hide Device Verification form at the Two Step Login screen */ ++app-security > app-two-factor-setup > form { ++ @extend %vw-hide; ++} ++ ++/* Replace the Bitwarden Shield at the top left with a Vaultwarden icon */ ++.bwi-shield:before { ++ content: "" !important; ++ width: 32px !important; ++ height: 40px !important; ++ display: block !important; ++ background-image: url(../images/icon-white.png) !important; ++ background-repeat: no-repeat; ++ background-position-y: bottom; ++} ++/**** END Vaultwarden CHANGES ****/ +diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js +index 08673c3f9a..db1dd55694 100644 +--- a/apps/web/tailwind.config.js ++++ b/apps/web/tailwind.config.js +@@ -6,7 +6,6 @@ config.content = [ + "../../libs/components/src/**/*.{html,ts}", + "../../libs/auth/src/**/*.{html,ts}", + "../../libs/angular/src/**/*.{html,ts}", +- "../../bitwarden_license/bit-web/src/**/*.{html,ts}", + ]; + + module.exports = config; +diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js +index f22d98f081..a9904a2ecf 100644 +--- a/apps/web/webpack.config.js ++++ b/apps/web/webpack.config.js +@@ -138,8 +138,6 @@ const plugins = [ + { from: "./src/favicon.ico" }, + { from: "./src/browserconfig.xml" }, + { from: "./src/app-id.json" }, +- { from: "./src/404.html" }, +- { from: "./src/404", to: "404" }, + { from: "./src/images", to: "images" }, + { from: "./src/locales", to: "locales" }, + { from: "../../node_modules/qrious/dist/qrious.min.js", to: "scripts" }, +diff --git a/clients.code-workspace b/clients.code-workspace +index a424f91eeb..72f7c59185 100644 +--- a/clients.code-workspace ++++ b/clients.code-workspace +@@ -8,18 +8,10 @@ + "name": "web vault", + "path": "apps/web", + }, +- { +- "name": "web vault (bit)", +- "path": "bitwarden_license/bit-web", +- }, + { + "name": "cli", + "path": "apps/cli", + }, +- { +- "name": "cli (bit)", +- "path": "bitwarden_license/bit-cli", +- }, + { + "name": "desktop", + "path": "apps/desktop", +@@ -32,10 +24,6 @@ + "name": "libs", + "path": "libs", + }, +- { +- "name": "common (bit)", +- "path": "bitwarden_license/bit-common", +- }, + ], + "settings": { + "eslint.options": { +diff --git a/jest.config.js b/jest.config.js +index f4e97262a3..12e11b35a3 100644 +--- a/jest.config.js ++++ b/jest.config.js +@@ -20,9 +20,6 @@ module.exports = { + "/apps/cli/jest.config.js", + "/apps/desktop/jest.config.js", + "/apps/web/jest.config.js", +- "/bitwarden_license/bit-web/jest.config.js", +- "/bitwarden_license/bit-cli/jest.config.js", +- "/bitwarden_license/bit-common/jest.config.js", + + "/libs/admin-console/jest.config.js", + "/libs/angular/jest.config.js", +diff --git a/libs/angular/src/auth/components/register.component.ts b/libs/angular/src/auth/components/register.component.ts +index e3197355dc..e3004ebdf7 100644 +--- a/libs/angular/src/auth/components/register.component.ts ++++ b/libs/angular/src/auth/components/register.component.ts +@@ -110,6 +110,14 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn + } + + async submit(showToast = true) { ++ if (typeof crypto.subtle === "undefined") { ++ this.platformUtilsService.showToast( ++ "error", ++ "This browser requires HTTPS to use the web vault", ++ "Check the Vaultwarden wiki for details on how to enable it", ++ ); ++ return; ++ } + let email = this.formGroup.value.email; + email = email.trim().toLowerCase(); + let name = this.formGroup.value.name; +diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.html b/libs/auth/src/angular/user-verification/user-verification-dialog.component.html +index aa4d26ae61..fb3b7cf4cb 100644 +--- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.html ++++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.html +@@ -9,8 +9,8 @@ + + +@@ -29,7 +29,7 @@ + + +@@ -41,7 +41,7 @@ + +@@ -50,8 +50,8 @@ + + +@@ -85,10 +85,12 @@ + +- ++ + +diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +index 7b2c869e3a..f8746b5b24 100644 +--- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts ++++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +@@ -142,6 +142,31 @@ export class UserVerificationDialogComponent { + * return; + * } + * ++ * ---------------------------------------------------------- ++ * ++ * @example ++ * // Example 4: Custom user verification validation ++ * ++ * const result = await UserVerificationDialogComponent.open(dialogService, { ++ * verificationType: { ++ * type: "custom", ++ * // Pass in a function that will be used to validate the input of the ++ * // verification dialog, returning true when finished. ++ * verificationFn: async (secret: VerificationWithSecret) => { ++ * const request = await userVerificationService.buildRequest(secret); ++ * ++ * // ... Do something with the custom request type ++ * ++ * await someServicer.sendMyRequestThatVerfiesUserIdentity( ++ * // ... Some other data ++ * request, ++ * ); ++ * return true; ++ * }, ++ * }, ++ * }); ++ * ++ * // ... Evaluate the result as usual + */ + static async open( + dialogService: DialogService, +@@ -202,6 +227,18 @@ export class UserVerificationDialogComponent { + } + + try { ++ if ( ++ typeof this.dialogOptions.verificationType === "object" && ++ this.dialogOptions.verificationType.type === "custom" ++ ) { ++ const success = await this.dialogOptions.verificationType.verificationFn(this.secret.value); ++ this.close({ ++ userAction: "confirm", ++ verificationSuccess: success, ++ }); ++ return; ++ } ++ + // TODO: once we migrate all user verification scenarios to use this new implementation, + // we should consider refactoring the user verification service handling of the + // OTP and MP flows to not throw errors on verification failure. +diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts +index f4637c770a..cb03f4e18f 100644 +--- a/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts ++++ b/libs/auth/src/angular/user-verification/user-verification-dialog.types.ts +@@ -1,3 +1,4 @@ ++import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; + import { ButtonType } from "@bitwarden/components"; + + /** +@@ -60,12 +61,27 @@ export type UserVerificationDialogOptions = { + */ + confirmButtonOptions?: UserVerificationConfirmButtonOptions; + +- /** +- * Indicates whether the verification is only performed client-side. Includes local MP verification, PIN, and Biometrics. +- * Optional. +- * **Important:** Only for use on desktop and browser platforms as when there are no client verification methods, the user is instructed to set a pin (which is not supported on web) ++ /** The validation method used to verify the secret. ++ * ++ * Possible values: ++ * ++ * - "default": Perform the default validation operation for the determined ++ * secret type. This would, for example, validate master passwords ++ * locally but OTPs on the server. ++ * - "client": Only do a client-side verification with no possible server ++ * request. Includes local MP verification, PIN, and Biometrics. ++ * **Important:** This option is only for use on desktop and browser ++ * platforms. When there are no client verification methods the user is ++ * instructed to set a pin, and this is not supported on web. ++ * - "custom": Custom validation is done to verify the secret. This is ++ * passed in from callers when opening the dialog. The custom type is ++ * meant to provide a mechanism where users can call a secured endpoint ++ * that performs user verification server side. + */ +- clientSideOnlyVerification?: boolean; ++ verificationType?: ++ | "default" ++ | "client" ++ | { type: "custom"; verificationFn: (secret: VerificationWithSecret) => Promise }; + }; + + /** +diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css +index 00ab2ff717..0950b9d787 100644 +--- a/libs/components/src/tw-theme.css ++++ b/libs/components/src/tw-theme.css +@@ -14,16 +14,16 @@ + --color-background: 255 255 255; + --color-background-alt: 251 251 251; + --color-background-alt2: 23 92 219; +- --color-background-alt3: 18 82 163; +- --color-background-alt4: 13 60 119; ++ --color-background-alt3: 33 37 41; /* bg of menu panel */ ++ --color-background-alt4: 16 18 21; /* bg of active menu item */ + + /* Can only be used behind the extension refresh flag */ + --color-primary-100: 200 217 249; +- --color-primary-300: 103 149 232; ++ --color-primary-300: 108 117 125; /* hover of menu items */ + /* Can only be used behind the extension refresh flag */ + --color-primary-500: 23 93 220; +- --color-primary-600: 23 93 220; +- --color-primary-700: 18 82 163; ++ --color-primary-600: 18 82 163; /* color of links and buttons */ ++ --color-primary-700: 13 60 119; /* hover of links and buttons */ + + --color-secondary-100: 240 240 240; + --color-secondary-300: 206 212 220; +diff --git a/libs/components/tailwind.config.js b/libs/components/tailwind.config.js +index 7a53c82ec5..9d0a337bd2 100644 +--- a/libs/components/tailwind.config.js ++++ b/libs/components/tailwind.config.js +@@ -6,7 +6,6 @@ config.content = [ + "libs/auth/src/**/*.{html,ts,mdx}", + "apps/web/src/**/*.{html,ts,mdx}", + "apps/browser/src/**/*.{html,ts,mdx}", +- "bitwarden_license/bit-web/src/**/*.{html,ts,mdx}", + ".storybook/preview.tsx", + ]; + config.safelist = [ +diff --git a/tailwind.config.js b/tailwind.config.js +index 50d82bf7d8..9b543ed950 100644 +--- a/tailwind.config.js ++++ b/tailwind.config.js +@@ -9,7 +9,6 @@ config.content = [ + "./libs/platform/src/**/*.{html,ts,mdx}", + "./libs/vault/src/**/*.{html,ts,mdx}", + "./apps/web/src/**/*.{html,ts,mdx}", +- "./bitwarden_license/bit-web/src/**/*.{html,ts,mdx}", + "./.storybook/preview.js", + ]; + config.safelist = [ +diff --git a/tsconfig.json b/tsconfig.json +index 8085b9f832..81a954ec84 100644 +--- a/tsconfig.json ++++ b/tsconfig.json +@@ -33,8 +33,7 @@ + "@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"], + "@bitwarden/node/*": ["./libs/node/src/*"], + "@bitwarden/web-vault/*": ["./apps/web/src/*"], +- "@bitwarden/vault": ["./libs/vault/src"], +- "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] ++ "@bitwarden/vault": ["./libs/vault/src"] + }, + "plugins": [ + { +@@ -43,13 +42,7 @@ + ], + "useDefineForClassFields": false + }, +- "include": [ +- "apps/web/src/**/*", +- "apps/browser/src/**/*", +- "libs/*/src/**/*", +- "bitwarden_license/bit-web/src/**/*", +- "bitwarden_license/bit-common/src/**/*" +- ], ++ "include": ["apps/web/src/**/*", "apps/browser/src/**/*", "libs/*/src/**/*"], + "exclude": [ + "apps/web/src/**/*.spec.ts", + "apps/browser/src/**/*.spec.ts",