From 4cf6e19b305c693e250f9e4a29008e8f3d06c464 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Tue, 13 May 2025 09:28:25 -0400 Subject: [PATCH 01/11] Estimate tax for trial initiation flow when trial length is 0 (#14674) --- .../trial-billing-step.component.html | 41 +++++++- .../trial-billing-step.component.ts | 94 ++++++++++++++++++- .../abstractions/tax.service.abstraction.ts | 6 ++ .../src/billing/models/request/tax/index.ts | 1 + ...x-amount-for-organization-trial.request.ts | 11 +++ .../src/billing/models/response/tax/index.ts | 1 + .../tax/preview-tax-amount.response.ts | 11 +++ .../src/billing/services/tax.service.ts | 15 +++ 8 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 libs/common/src/billing/models/request/tax/index.ts create mode 100644 libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts create mode 100644 libs/common/src/billing/models/response/tax/index.ts create mode 100644 libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html index d947ea96dfb..64a9781b7cf 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html @@ -51,8 +51,38 @@
+ {{ "total" | i18n }}:
+ @if (fetchingTaxAmount) {
+
", "Two-step login code.")
- .option("--sso", "Log in with Single-Sign On.")
+ .option(
+ "--sso [identifier]",
+ "Log in with Single-Sign On with optional organization identifier.",
+ )
.option("--apikey", "Log in with an Api Key.")
.option("--passwordenv ", "Environment variable storing your password")
.option(
diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts
index a91a8ed20e9..968a05bf850 100644
--- a/libs/auth/src/angular/sso/sso.component.ts
+++ b/libs/auth/src/angular/sso/sso.component.ts
@@ -155,7 +155,14 @@ export class SsoComponent implements OnInit {
return;
}
- // Detect if we have landed here but only have an SSO identifier in the URL.
+ // Detect if we are on the first portion of the SSO flow
+ // and have been sent here from another client with the info in query params.
+ // If so, we want to initialize the SSO flow with those values.
+ if (this.hasParametersFromOtherClientRedirect(qParams)) {
+ this.initializeFromRedirectFromOtherClient(qParams);
+ }
+
+ // Detect if we have landed here with an SSO identifier in the URL.
// This is used by integrations that want to "short-circuit" the login to send users
// directly to their IdP to simulate IdP-initiated SSO, so we submit automatically.
if (qParams.identifier != null) {
@@ -165,13 +172,6 @@ export class SsoComponent implements OnInit {
return;
}
- // Detect if we are on the first portion of the SSO flow
- // and have been sent here from another client with the info in query params.
- // If so, we want to initialize the SSO flow with those values.
- if (this.hasParametersFromOtherClientRedirect(qParams)) {
- this.initializeFromRedirectFromOtherClient(qParams);
- }
-
// Try to determine the identifier using claimed domain or local state
// persisted from the user's last login attempt.
await this.initializeIdentifierFromEmailOrStorage();
diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts
index 074c3a1e0b1..632a2812cfe 100644
--- a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts
+++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts
@@ -92,4 +92,27 @@ describe("SsoUrlService", () => {
);
expect(result).toBe(expectedUrl);
});
+
+ it("should build CLI SSO URL with Org SSO Identifier correctly", () => {
+ const baseUrl = "https://web-vault.bitwarden.com";
+ const clientType = ClientType.Cli;
+ const redirectUri = "https://localhost:1000";
+ const state = "abc123";
+ const codeChallenge = "xyz789";
+ const email = "test@bitwarden.com";
+ const orgSsoIdentifier = "test-org";
+
+ const expectedUrl = `${baseUrl}/#/sso?clientId=cli&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}&identifier=${encodeURIComponent(orgSsoIdentifier)}`;
+
+ const result = service.buildSsoUrl(
+ baseUrl,
+ clientType,
+ redirectUri,
+ state,
+ codeChallenge,
+ email,
+ orgSsoIdentifier,
+ );
+ expect(result).toBe(expectedUrl);
+ });
});
diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts
index 667a27ad598..b2d6231db7c 100644
--- a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts
+++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts
@@ -11,6 +11,7 @@ export class SsoUrlService {
* @param state A state value that will be peristed through the SSO flow
* @param codeChallenge A challenge value that will be used to verify the SSO code after authentication
* @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier
+ * @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO
* @returns The URL for redirecting users to the web app SSO component
*/
buildSsoUrl(
@@ -20,6 +21,7 @@ export class SsoUrlService {
state: string,
codeChallenge: string,
email?: string,
+ orgSsoIdentifier?: string,
): string {
let url =
webAppUrl +
@@ -36,6 +38,10 @@ export class SsoUrlService {
url += "&email=" + encodeURIComponent(email);
}
+ if (orgSsoIdentifier) {
+ url += "&identifier=" + encodeURIComponent(orgSsoIdentifier);
+ }
+
return url;
}
}
From 9a7089594efac32006eacd1e013488447f1f4214 Mon Sep 17 00:00:00 2001
From: Oscar Hinton
Date: Tue, 13 May 2025 16:59:56 +0200
Subject: [PATCH 08/11] [PM-19878] Add core-js to cli (#14696)
* Add core-js to cli
* Add core-js to bit-cli
---
apps/cli/package.json | 1 +
bitwarden_license/bit-cli/src/bw.ts | 2 ++
package-lock.json | 2 +-
3 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 82faa7d40e6..b01c96b23d1 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -71,6 +71,7 @@
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "11.1.0",
+ "core-js": "3.40.0",
"form-data": "4.0.1",
"https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
diff --git a/bitwarden_license/bit-cli/src/bw.ts b/bitwarden_license/bit-cli/src/bw.ts
index ffbc186d9e0..2e4e945b9c8 100644
--- a/bitwarden_license/bit-cli/src/bw.ts
+++ b/bitwarden_license/bit-cli/src/bw.ts
@@ -1,3 +1,5 @@
+import "core-js/proposals/explicit-resource-management";
+
import { program } from "commander";
import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs";
diff --git a/package-lock.json b/package-lock.json
index 8466a4ba4c6..d1378d63ec3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -207,6 +207,7 @@
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "11.1.0",
+ "core-js": "3.40.0",
"form-data": "4.0.1",
"https-proxy-agent": "7.0.6",
"inquirer": "8.2.6",
@@ -16960,7 +16961,6 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
"integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
"hasInstallScript": true,
- "license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
From 0750ff38f580e85e7ec6296e3883ac7c38676349 Mon Sep 17 00:00:00 2001
From: SmithThe4th
Date: Tue, 13 May 2025 11:21:07 -0400
Subject: [PATCH 09/11] Route to vault page when user doesn't have access
(#14633)
---
apps/browser/src/_locales/en/messages.json | 3 +++
.../src/vault/guards/at-risk-passwords.guard.ts | 12 ++++++++----
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 361093b12ef..e1a3234a61f 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -5321,5 +5321,8 @@
"message": "Learn more about SSH agent",
"description": "Two part message",
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
+ },
+ "noPermissionsViewPage": {
+ "message": "You do not have permissions to view this page. Try logging in with a different account."
}
}
diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts
index 6bcdddfde81..fc302dd6c36 100644
--- a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts
+++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts
@@ -1,6 +1,6 @@
import { inject } from "@angular/core";
-import { CanActivateFn } from "@angular/router";
-import { switchMap, tap } from "rxjs";
+import { CanActivateFn, Router } from "@angular/router";
+import { map, switchMap } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => {
const taskService = inject(TaskService);
const toastService = inject(ToastService);
const i18nService = inject(I18nService);
+ const router = inject(Router);
return accountService.activeAccount$.pipe(
filterOutNullish(),
switchMap((user) => taskService.tasksEnabled$(user.id)),
- tap((tasksEnabled) => {
+ map((tasksEnabled) => {
if (!tasksEnabled) {
toastService.showToast({
variant: "error",
title: "",
- message: i18nService.t("accessDenied"),
+ message: i18nService.t("noPermissionsViewPage"),
});
+
+ return router.createUrlTree(["/tabs/vault"]);
}
+ return true;
}),
);
};
From 1f72dd7710a160058cd39316ac00d97cdbfe5aad Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Tue, 13 May 2025 11:30:36 -0400
Subject: [PATCH 10/11] PM-21286 add aria label to nudge settings badge
(#14750)
---
apps/browser/src/_locales/en/messages.json | 3 +++
.../src/tools/popup/settings/settings-v2.component.html | 3 +++
2 files changed, 6 insertions(+)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index e1a3234a61f..dcdfe7df4d6 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -5258,6 +5258,9 @@
"secureDevicesBody": {
"message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps."
},
+ "nudgeBadgeAria": {
+ "message": "1 notification"
+ },
"emptyVaultNudgeTitle": {
"message": "Import existing passwords"
},
diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html
index 22e2d9a28d0..8d31ccf8371 100644
--- a/apps/browser/src/tools/popup/settings/settings-v2.component.html
+++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html
@@ -23,6 +23,7 @@
*ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)"
bitBadge
variant="notification"
+ [attr.aria-label]="'nudgeBadgeAria' | i18n"
>1
@@ -53,6 +54,7 @@
*ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed"
bitBadge
variant="notification"
+ [attr.aria-label]="'nudgeBadgeAria' | i18n"
>1
@@ -83,6 +85,7 @@
*ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false"
bitBadge
variant="notification"
+ [attr.aria-label]="'nudgeBadgeAria' | i18n"
>1
From 5fb46df3415aefced0b52f2db86c873962255448 Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Tue, 13 May 2025 16:49:06 +0100
Subject: [PATCH 11/11] [PM 21106]Remove button not responsive for admin
Console Remove Sponorship (#14743)
* Resolve the remove button inactive
* Resolve the lint error
---
.../free-bitwarden-families.component.ts | 2 +-
.../settings/sponsoring-org-row.component.ts | 2 +-
.../src/services/jslib-services.module.ts | 2 +-
libs/common/src/abstractions/api.service.ts | 1 -
...ion-sponsorship-api.service.abstraction.ts | 5 ++++
.../organization-sponsorship-api.service.ts | 23 ++++++++++++++++++-
libs/common/src/services/api.service.ts | 12 ----------
7 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts
index eb6bfdf368b..b482007e30b 100644
--- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts
+++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts
@@ -179,7 +179,7 @@ export class FreeBitwardenFamiliesComponent implements OnInit {
return;
}
- await this.apiService.deleteRevokeSponsorship(this.organizationId);
+ await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.organizationId, true);
this.toastService.showToast({
variant: "success",
diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts
index 33e6334c577..39a7531082a 100644
--- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts
+++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts
@@ -109,7 +109,7 @@ export class SponsoringOrgRowComponent implements OnInit {
return;
}
- await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id);
+ await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.sponsoringOrg.id);
this.toastService.showToast({
variant: "success",
title: null,
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 470115ae3f0..3ffca776034 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1079,7 +1079,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: OrganizationSponsorshipApiServiceAbstraction,
useClass: OrganizationSponsorshipApiService,
- deps: [ApiServiceAbstraction],
+ deps: [ApiServiceAbstraction, PlatformUtilsServiceAbstraction],
}),
safeProvider({
provide: OrganizationBillingApiServiceAbstraction,
diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts
index 1e13a3064f4..e4453359015 100644
--- a/libs/common/src/abstractions/api.service.ts
+++ b/libs/common/src/abstractions/api.service.ts
@@ -475,7 +475,6 @@ export abstract class ApiService {
getSponsorshipSyncStatus: (
sponsoredOrgId: string,
) => Promise;
- deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise;
deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise;
postPreValidateSponsorshipToken: (
sponsorshipToken: string,
diff --git a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts
index 7bd6aac17bd..5e69f57ca19 100644
--- a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts
+++ b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts
@@ -10,4 +10,9 @@ export abstract class OrganizationSponsorshipApiServiceAbstraction {
sponsoringOrgId: string,
friendlyName?: string,
): Promise;
+
+ abstract deleteRevokeSponsorship: (
+ sponsoringOrganizationId: string,
+ isAdminInitiated?: boolean,
+ ) => Promise;
}
diff --git a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts
index 22ab3ec86ee..99440b10dec 100644
--- a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts
+++ b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts
@@ -1,12 +1,16 @@
import { ApiService } from "../../../abstractions/api.service";
import { ListResponse } from "../../../models/response/list.response";
+import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service";
import { OrganizationSponsorshipApiServiceAbstraction } from "../../abstractions/organizations/organization-sponsorship-api.service.abstraction";
import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response";
export class OrganizationSponsorshipApiService
implements OrganizationSponsorshipApiServiceAbstraction
{
- constructor(private apiService: ApiService) {}
+ constructor(
+ private apiService: ApiService,
+ private platformUtilsService: PlatformUtilsService,
+ ) {}
async getOrganizationSponsorship(
sponsoredOrgId: string,
): Promise> {
@@ -33,4 +37,21 @@ export class OrganizationSponsorshipApiService
return await this.apiService.send("POST", url, null, true, false);
}
+
+ async deleteRevokeSponsorship(
+ sponsoringOrganizationId: string,
+ isAdminInitiated: boolean = false,
+ ): Promise {
+ const basePath = "/organization/sponsorship/";
+ const hostPath = this.platformUtilsService.isSelfHost() ? "self-hosted/" : "";
+ const queryParam = `?isAdminInitiated=${isAdminInitiated}`;
+
+ return await this.apiService.send(
+ "DELETE",
+ basePath + hostPath + sponsoringOrganizationId + queryParam,
+ null,
+ true,
+ false,
+ );
+ }
}
diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts
index 5c4bcdedb26..639daa7c658 100644
--- a/libs/common/src/services/api.service.ts
+++ b/libs/common/src/services/api.service.ts
@@ -1621,18 +1621,6 @@ export class ApiService implements ApiServiceAbstraction {
return new OrganizationSponsorshipSyncStatusResponse(response);
}
- async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise {
- return await this.send(
- "DELETE",
- "/organization/sponsorship/" +
- (this.platformUtilsService.isSelfHost() ? "self-hosted/" : "") +
- sponsoringOrganizationId,
- null,
- true,
- false,
- );
- }
-
async deleteRemoveSponsorship(sponsoringOrgId: string): Promise {
return await this.send(
"DELETE",