-
+
-
+
-
+
{
const mockOrganizationId = "mockOrgId" as OrganizationId;
@@ -112,5 +115,34 @@ describe("ImportService", () => {
]),
);
});
+
+ it("should generate user report export items and include users with no access", async () => {
+ reportApiService.getMemberAccessData.mockImplementation(() =>
+ Promise.resolve(memberAccessWithoutAccessDetailsReportsMock),
+ );
+ const result =
+ await memberAccessReportService.generateUserReportExportItems(mockOrganizationId);
+
+ expect(result).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ email: "asmith@email.com",
+ name: "Alice Smith",
+ twoStepLogin: "memberAccessReportTwoFactorEnabledTrue",
+ accountRecovery: "memberAccessReportAuthenticationEnabledTrue",
+ group: "Alice Group 1",
+ totalItems: "10",
+ }),
+ expect.objectContaining({
+ email: "rbrown@email.com",
+ name: "Robert Brown",
+ twoStepLogin: "memberAccessReportTwoFactorEnabledFalse",
+ accountRecovery: "memberAccessReportAuthenticationEnabledFalse",
+ group: "memberAccessReportNoGroup",
+ totalItems: "0",
+ }),
+ ]),
+ );
+ });
});
});
diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts
index b7ff5551e2c..029dce8a404 100644
--- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts
+++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts
@@ -65,6 +65,26 @@ export class MemberAccessReportService {
}
const exportItems = memberAccessReports.flatMap((report) => {
+ // to include users without access details
+ // which means a user has no groups, collections or items
+ if (report.accessDetails.length === 0) {
+ return [
+ {
+ email: report.email,
+ name: report.userName,
+ twoStepLogin: report.twoFactorEnabled
+ ? this.i18nService.t("memberAccessReportTwoFactorEnabledTrue")
+ : this.i18nService.t("memberAccessReportTwoFactorEnabledFalse"),
+ accountRecovery: report.accountRecoveryEnabled
+ ? this.i18nService.t("memberAccessReportAuthenticationEnabledTrue")
+ : this.i18nService.t("memberAccessReportAuthenticationEnabledFalse"),
+ group: this.i18nService.t("memberAccessReportNoGroup"),
+ collection: this.i18nService.t("memberAccessReportNoCollection"),
+ collectionPermission: this.i18nService.t("memberAccessReportNoCollectionPermission"),
+ totalItems: "0",
+ },
+ ];
+ }
const userDetails = report.accessDetails.map((detail) => {
const collectionName = collectionNameMap.get(detail.collectionName.encryptedString);
return {
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 3cce9b5357e..8e2b3409593 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1255,6 +1255,7 @@ const safeProviders: SafeProvider[] = [
I18nServiceAbstraction,
OrganizationApiServiceAbstraction,
SyncService,
+ ConfigService,
],
}),
safeProvider({
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html
index f31a5500b43..1e16dba82cc 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.component.html
+++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html
@@ -10,7 +10,7 @@
[routerLink]="['/']"
class="tw-w-[128px] tw-block tw-mb-12 [&>*]:tw-align-top"
>
-
+
Promise
;
+
+ /**
+ * Determines if breadcrumbing policies is enabled for the organizations meeting certain criteria.
+ * @param organization
+ */
+ abstract isBreadcrumbingPoliciesEnabled$(organization: Organization): Observable;
}
diff --git a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts
index 40d8db03d3b..bfeecb4eb23 100644
--- a/libs/common/src/billing/models/request/preview-organization-invoice.request.ts
+++ b/libs/common/src/billing/models/request/preview-organization-invoice.request.ts
@@ -1,4 +1,4 @@
-import { PlanType } from "../../enums";
+import { PlanSponsorshipType, PlanType } from "../../enums";
export class PreviewOrganizationInvoiceRequest {
organizationId?: string;
@@ -21,6 +21,7 @@ export class PreviewOrganizationInvoiceRequest {
class PasswordManager {
plan: PlanType;
+ sponsoredPlan?: PlanSponsorshipType;
seats: number;
additionalStorage: number;
diff --git a/libs/common/src/billing/services/organization-billing.service.spec.ts b/libs/common/src/billing/services/organization-billing.service.spec.ts
new file mode 100644
index 00000000000..7b194dff637
--- /dev/null
+++ b/libs/common/src/billing/services/organization-billing.service.spec.ts
@@ -0,0 +1,149 @@
+import { mock } from "jest-mock-extended";
+import { firstValueFrom, of } from "rxjs";
+
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { OrganizationApiServiceAbstraction as OrganizationApiService } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
+import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
+import { ProductTierType } from "@bitwarden/common/billing/enums";
+import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { SyncService } from "@bitwarden/common/platform/sync";
+import { KeyService } from "@bitwarden/key-management";
+
+describe("BillingAccountProfileStateService", () => {
+ let apiService: jest.Mocked;
+ let billingApiService: jest.Mocked;
+ let keyService: jest.Mocked;
+ let encryptService: jest.Mocked;
+ let i18nService: jest.Mocked;
+ let organizationApiService: jest.Mocked;
+ let syncService: jest.Mocked;
+ let configService: jest.Mocked;
+
+ let sut: OrganizationBillingService;
+
+ beforeEach(() => {
+ apiService = mock();
+ billingApiService = mock();
+ keyService = mock();
+ encryptService = mock();
+ i18nService = mock();
+ organizationApiService = mock();
+ syncService = mock();
+ configService = mock();
+
+ sut = new OrganizationBillingService(
+ apiService,
+ billingApiService,
+ keyService,
+ encryptService,
+ i18nService,
+ organizationApiService,
+ syncService,
+ configService,
+ );
+ });
+
+ afterEach(() => {
+ return jest.resetAllMocks();
+ });
+
+ describe("isBreadcrumbingPoliciesEnabled", () => {
+ it("returns false when feature flag is disabled", async () => {
+ configService.getFeatureFlag$.mockReturnValue(of(false));
+ const org = {
+ isProviderUser: false,
+ canEditSubscription: true,
+ productTierType: ProductTierType.Teams,
+ } as Organization;
+
+ const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(actual).toBe(false);
+ expect(configService.getFeatureFlag$).toHaveBeenCalledWith(
+ FeatureFlag.PM12276_BreadcrumbEventLogs,
+ );
+ });
+
+ it("returns false when organization belongs to a provider", async () => {
+ configService.getFeatureFlag$.mockReturnValue(of(true));
+ const org = {
+ isProviderUser: true,
+ canEditSubscription: true,
+ productTierType: ProductTierType.Teams,
+ } as Organization;
+
+ const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(actual).toBe(false);
+ });
+
+ it("returns false when cannot edit subscription", async () => {
+ configService.getFeatureFlag$.mockReturnValue(of(true));
+ const org = {
+ isProviderUser: false,
+ canEditSubscription: false,
+ productTierType: ProductTierType.Teams,
+ } as Organization;
+
+ const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(actual).toBe(false);
+ });
+
+ it.each([
+ ["Teams", ProductTierType.Teams],
+ ["TeamsStarter", ProductTierType.TeamsStarter],
+ ])("returns true when all conditions are met with %s tier", async (_, productTierType) => {
+ configService.getFeatureFlag$.mockReturnValue(of(true));
+ const org = {
+ isProviderUser: false,
+ canEditSubscription: true,
+ productTierType: productTierType,
+ } as Organization;
+
+ const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(actual).toBe(true);
+ expect(configService.getFeatureFlag$).toHaveBeenCalledWith(
+ FeatureFlag.PM12276_BreadcrumbEventLogs,
+ );
+ });
+
+ it("returns false when product tier is not supported", async () => {
+ configService.getFeatureFlag$.mockReturnValue(of(true));
+ const org = {
+ isProviderUser: false,
+ canEditSubscription: true,
+ productTierType: ProductTierType.Enterprise,
+ } as Organization;
+
+ const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(actual).toBe(false);
+ });
+
+ it("handles all conditions false correctly", async () => {
+ configService.getFeatureFlag$.mockReturnValue(of(false));
+ const org = {
+ isProviderUser: true,
+ canEditSubscription: false,
+ productTierType: ProductTierType.Free,
+ } as Organization;
+
+ const actual = await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(actual).toBe(false);
+ });
+
+ it("verifies feature flag is only called once", async () => {
+ configService.getFeatureFlag$.mockReturnValue(of(false));
+ const org = {
+ isProviderUser: false,
+ canEditSubscription: true,
+ productTierType: ProductTierType.Teams,
+ } as Organization;
+
+ await firstValueFrom(sut.isBreadcrumbingPoliciesEnabled$(org));
+ expect(configService.getFeatureFlag$).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts
index 83efbf0a30c..6622cdcdce3 100644
--- a/libs/common/src/billing/services/organization-billing.service.ts
+++ b/libs/common/src/billing/services/organization-billing.service.ts
@@ -1,5 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
+import { Observable, of, switchMap } from "rxjs";
+
+import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { KeyService } from "@bitwarden/key-management";
import { ApiService } from "../../abstractions/api.service";
@@ -20,7 +25,7 @@ import {
PlanInformation,
SubscriptionInformation,
} from "../abstractions";
-import { PlanType } from "../enums";
+import { PlanType, ProductTierType } from "../enums";
import { OrganizationNoPaymentMethodCreateRequest } from "../models/request/organization-no-payment-method-create-request";
import { PaymentSourceResponse } from "../models/response/payment-source.response";
@@ -40,6 +45,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
private i18nService: I18nService,
private organizationApiService: OrganizationApiService,
private syncService: SyncService,
+ private configService: ConfigService,
) {}
async getPaymentSource(organizationId: string): Promise {
@@ -220,4 +226,29 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
this.setPaymentInformation(request, subscription.payment);
await this.billingApiService.restartSubscription(organizationId, request);
}
+
+ isBreadcrumbingPoliciesEnabled$(organization: Organization): Observable {
+ if (organization === null || organization === undefined) {
+ return of(false);
+ }
+
+ return this.configService.getFeatureFlag$(FeatureFlag.PM12276_BreadcrumbEventLogs).pipe(
+ switchMap((featureFlagEnabled) => {
+ if (!featureFlagEnabled) {
+ return of(false);
+ }
+
+ if (organization.isProviderUser || !organization.canEditSubscription) {
+ return of(false);
+ }
+
+ const supportedProducts = [ProductTierType.Teams, ProductTierType.TeamsStarter];
+ const isSupportedProduct = supportedProducts.some(
+ (product) => product === organization.productTierType,
+ );
+
+ return of(isSupportedProduct);
+ }),
+ );
+ }
}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index f9e2d9757bc..1d0b1521db6 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -46,7 +46,6 @@ export enum FeatureFlag {
CriticalApps = "pm-14466-risk-insights-critical-application",
EnableRiskInsightsNotifications = "enable-risk-insights-notifications",
DesktopSendUIRefresh = "desktop-send-ui-refresh",
- ExportAttachments = "export-attachments",
/* Vault */
PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge",
@@ -97,7 +96,6 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.CriticalApps]: FALSE,
[FeatureFlag.EnableRiskInsightsNotifications]: FALSE,
[FeatureFlag.DesktopSendUIRefresh]: FALSE,
- [FeatureFlag.ExportAttachments]: FALSE,
/* Vault */
[FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE,
diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts
index a2211753f4e..c82efa0c571 100644
--- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts
+++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts
@@ -209,9 +209,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
devices.data
.filter((device) => device.isTrusted)
.map(async (device) => {
- const deviceWithKeys = await this.devicesApiService.getDeviceKeys(device.identifier);
const publicKey = await this.encryptService.decryptToBytes(
- deviceWithKeys.encryptedPublicKey,
+ new EncString(device.encryptedPublicKey),
oldUserKey,
);
diff --git a/libs/common/src/models/view/view.ts b/libs/common/src/models/view/view.ts
index 1f16b3d5958..2869617dca5 100644
--- a/libs/common/src/models/view/view.ts
+++ b/libs/common/src/models/view/view.ts
@@ -1 +1,5 @@
+// See https://contributing.bitwarden.com/architecture/clients/data-model/#view for proper use.
+// View models represent the decrypted state of a corresponding Domain model.
+// They typically match the Domain model but contains a decrypted string for any EncString fields.
+// Don't use this to represent arbitrary component view data as that isn't what it is for.
export class View {}
diff --git a/libs/common/src/platform/misc/utils.spec.ts b/libs/common/src/platform/misc/utils.spec.ts
index 964a2a19413..818138863fb 100644
--- a/libs/common/src/platform/misc/utils.spec.ts
+++ b/libs/common/src/platform/misc/utils.spec.ts
@@ -706,4 +706,73 @@ describe("Utils Service", () => {
});
});
});
+
+ describe("fromUtf8ToB64(...)", () => {
+ const originalIsNode = Utils.isNode;
+
+ afterEach(() => {
+ Utils.isNode = originalIsNode;
+ });
+
+ runInBothEnvironments("should handle empty string", () => {
+ const str = Utils.fromUtf8ToB64("");
+ expect(str).toBe("");
+ });
+
+ runInBothEnvironments("should convert a normal b64 string", () => {
+ const str = Utils.fromUtf8ToB64(asciiHelloWorld);
+ expect(str).toBe(b64HelloWorldString);
+ });
+
+ runInBothEnvironments("should convert various special characters", () => {
+ const cases = [
+ { input: "»", output: "wrs=" },
+ { input: "¦", output: "wqY=" },
+ { input: "£", output: "wqM=" },
+ { input: "é", output: "w6k=" },
+ { input: "ö", output: "w7Y=" },
+ { input: "»»", output: "wrvCuw==" },
+ ];
+ cases.forEach((c) => {
+ const utfStr = c.input;
+ const str = Utils.fromUtf8ToB64(utfStr);
+ expect(str).toBe(c.output);
+ });
+ });
+ });
+
+ describe("fromB64ToUtf8(...)", () => {
+ const originalIsNode = Utils.isNode;
+
+ afterEach(() => {
+ Utils.isNode = originalIsNode;
+ });
+
+ runInBothEnvironments("should handle empty string", () => {
+ const str = Utils.fromB64ToUtf8("");
+ expect(str).toBe("");
+ });
+
+ runInBothEnvironments("should convert a normal b64 string", () => {
+ const str = Utils.fromB64ToUtf8(b64HelloWorldString);
+ expect(str).toBe(asciiHelloWorld);
+ });
+
+ runInBothEnvironments("should handle various special characters", () => {
+ const cases = [
+ { input: "wrs=", output: "»" },
+ { input: "wqY=", output: "¦" },
+ { input: "wqM=", output: "£" },
+ { input: "w6k=", output: "é" },
+ { input: "w7Y=", output: "ö" },
+ { input: "wrvCuw==", output: "»»" },
+ ];
+
+ cases.forEach((c) => {
+ const b64Str = c.input;
+ const str = Utils.fromB64ToUtf8(b64Str);
+ expect(str).toBe(c.output);
+ });
+ });
+ });
});
diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts
index ef65d2130a0..203a04851c5 100644
--- a/libs/common/src/platform/misc/utils.ts
+++ b/libs/common/src/platform/misc/utils.ts
@@ -233,7 +233,7 @@ export class Utils {
if (Utils.isNode) {
return Buffer.from(utfStr, "utf8").toString("base64");
} else {
- return decodeURIComponent(escape(Utils.global.btoa(utfStr)));
+ return BufferLib.from(utfStr, "utf8").toString("base64");
}
}
@@ -245,7 +245,7 @@ export class Utils {
if (Utils.isNode) {
return Buffer.from(b64Str, "base64").toString("utf8");
} else {
- return decodeURIComponent(escape(Utils.global.atob(b64Str)));
+ return BufferLib.from(b64Str, "base64").toString("utf8");
}
}
diff --git a/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts b/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts
index 74981b6782f..a1143d14d1d 100644
--- a/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts
+++ b/libs/common/src/platform/notifications/internal/worker-webpush-connection.service.ts
@@ -134,7 +134,7 @@ class MyWebPushConnector implements WebPushConnector {
private async pushManagerSubscribe(key: string) {
return await this.serviceWorkerRegistration.pushManager.subscribe({
- userVisibleOnly: true,
+ userVisibleOnly: false,
applicationServerKey: key,
});
}
diff --git a/libs/components/src/icon/icon.component.ts b/libs/components/src/icon/icon.component.ts
index 2382d197bec..08fa25956d0 100644
--- a/libs/components/src/icon/icon.component.ts
+++ b/libs/components/src/icon/icon.component.ts
@@ -1,19 +1,23 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, HostBinding, Input } from "@angular/core";
+import { Component, Input } from "@angular/core";
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
import { Icon, isIcon } from "./icon";
@Component({
selector: "bit-icon",
+ host: {
+ "[attr.aria-hidden]": "!ariaLabel",
+ "[attr.aria-label]": "ariaLabel",
+ "[innerHtml]": "innerHtml",
+ },
template: ``,
standalone: true,
})
export class BitIconComponent {
+ innerHtml: SafeHtml | null = null;
+
@Input() set icon(icon: Icon) {
if (!isIcon(icon)) {
- this.innerHtml = "";
return;
}
@@ -21,7 +25,7 @@ export class BitIconComponent {
this.innerHtml = this.domSanitizer.bypassSecurityTrustHtml(svg);
}
- @HostBinding() innerHtml: SafeHtml;
+ @Input() ariaLabel: string | undefined = undefined;
constructor(private domSanitizer: DomSanitizer) {}
}
diff --git a/libs/components/src/icon/icon.mdx b/libs/components/src/icon/icon.mdx
index fc1c4cd3d57..6435fc24948 100644
--- a/libs/components/src/icon/icon.mdx
+++ b/libs/components/src/icon/icon.mdx
@@ -98,9 +98,19 @@ import * as stories from "./icon.stories";
```
- **HTML:**
+
+ > NOTE: SVG icons are treated as decorative by default and will be `aria-hidden` unless an
+ > `ariaLabel` is explicitly provided to the `` component
+
```html
```
+ With `ariaLabel`
+
+ ```html
+
+ ```
+
8. **Ensure your SVG renders properly** according to Figma in both light and dark modes on a client
which supports multiple style modes.
diff --git a/libs/components/src/icon/icon.stories.ts b/libs/components/src/icon/icon.stories.ts
index 53454567b7f..7892bdd3ec1 100644
--- a/libs/components/src/icon/icon.stories.ts
+++ b/libs/components/src/icon/icon.stories.ts
@@ -26,5 +26,9 @@ export const Default: Story = {
mapping: GenericIcons,
control: { type: "select" },
},
+ ariaLabel: {
+ control: "text",
+ description: "the text used by a screen reader to describe the icon",
+ },
},
};
diff --git a/libs/key-management-ui/src/index.ts b/libs/key-management-ui/src/index.ts
index 2f98538caad..b330e390d36 100644
--- a/libs/key-management-ui/src/index.ts
+++ b/libs/key-management-ui/src/index.ts
@@ -7,3 +7,4 @@ export { LockComponentService, UnlockOptions } from "./lock/services/lock-compon
export { KeyRotationTrustInfoComponent } from "./key-rotation/key-rotation-trust-info.component";
export { AccountRecoveryTrustComponent } from "./trust/account-recovery-trust.component";
export { EmergencyAccessTrustComponent } from "./trust/emergency-access-trust.component";
+export { RemovePasswordComponent } from "./key-connector/remove-password.component";
diff --git a/libs/angular/src/auth/components/remove-password.component.ts b/libs/key-management-ui/src/key-connector/remove-password.component.ts
similarity index 100%
rename from libs/angular/src/auth/components/remove-password.component.ts
rename to libs/key-management-ui/src/key-connector/remove-password.component.ts
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
index 69f77c6ca32..71599c19ae0 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
@@ -39,8 +39,6 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { EventType } from "@bitwarden/common/enums";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -184,10 +182,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
private onlyManagedCollections = true;
private onGenerate$ = new Subject();
- private isExportAttachmentsEnabled$ = this.configService.getFeatureFlag$(
- FeatureFlag.ExportAttachments,
- );
-
constructor(
protected i18nService: I18nService,
protected toastService: ToastService,
@@ -202,7 +196,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
protected organizationService: OrganizationService,
private accountService: AccountService,
private collectionService: CollectionService,
- private configService: ConfigService,
) {}
async ngOnInit() {
@@ -225,17 +218,14 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
),
);
- combineLatest([
- this.exportForm.controls.vaultSelector.valueChanges,
- this.isExportAttachmentsEnabled$,
- ])
+ this.exportForm.controls.vaultSelector.valueChanges
.pipe(takeUntil(this.destroy$))
- .subscribe(([value, isExportAttachmentsEnabled]) => {
+ .subscribe((value) => {
this.organizationId = value !== "myVault" ? value : undefined;
this.formatOptions = this.formatOptions.filter((option) => option.value !== "zip");
this.exportForm.get("format").setValue("json");
- if (value === "myVault" && isExportAttachmentsEnabled) {
+ if (value === "myVault") {
this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" });
}
});
diff --git a/libs/vault/src/icons/login-cards.ts b/libs/vault/src/icons/login-cards.ts
index 01baf308412..6e066a0d924 100644
--- a/libs/vault/src/icons/login-cards.ts
+++ b/libs/vault/src/icons/login-cards.ts
@@ -2,10 +2,10 @@ import { svgIcon } from "@bitwarden/components";
export const LoginCards = svgIcon`
`;
diff --git a/libs/vault/src/icons/secure-devices.ts b/libs/vault/src/icons/secure-devices.ts
index ee3a6ea6b90..4e123afad40 100644
--- a/libs/vault/src/icons/secure-devices.ts
+++ b/libs/vault/src/icons/secure-devices.ts
@@ -3,14 +3,14 @@ import { svgIcon } from "@bitwarden/components";
export const SecureDevices = svgIcon`
`;
diff --git a/libs/vault/src/icons/secure-user.ts b/libs/vault/src/icons/secure-user.ts
index f8f126adbac..39d9957030c 100644
--- a/libs/vault/src/icons/secure-user.ts
+++ b/libs/vault/src/icons/secure-user.ts
@@ -2,8 +2,8 @@ import { svgIcon } from "@bitwarden/components";
export const SecureUser = svgIcon`
diff --git a/libs/vault/src/icons/security-handshake.ts b/libs/vault/src/icons/security-handshake.ts
index 5a598fd180d..d68f8a948d3 100644
--- a/libs/vault/src/icons/security-handshake.ts
+++ b/libs/vault/src/icons/security-handshake.ts
@@ -2,11 +2,11 @@ import { svgIcon } from "@bitwarden/components";
export const SecurityHandshake = svgIcon`
`;
diff --git a/package-lock.json b/package-lock.json
index cb9baf4fafe..3e16fd7ba68 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,7 @@
"@angular/platform-browser": "18.2.13",
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
- "@bitwarden/sdk-internal": "0.2.0-main.133",
+ "@bitwarden/sdk-internal": "0.2.0-main.137",
"@electron/fuses": "1.8.0",
"@emotion/css": "11.13.5",
"@koa/multer": "3.0.2",
@@ -48,7 +48,7 @@
"jquery": "3.7.1",
"jsdom": "26.0.0",
"jszip": "3.10.1",
- "koa": "2.15.4",
+ "koa": "2.16.1",
"koa-bodyparser": "4.4.1",
"koa-json": "2.0.2",
"lit": "3.2.1",
@@ -191,11 +191,11 @@
},
"apps/browser": {
"name": "@bitwarden/browser",
- "version": "2025.3.2"
+ "version": "2025.4.0"
},
"apps/cli": {
"name": "@bitwarden/cli",
- "version": "2025.3.0",
+ "version": "2025.4.0",
"license": "SEE LICENSE IN LICENSE.txt",
"dependencies": {
"@koa/multer": "3.0.2",
@@ -210,7 +210,7 @@
"inquirer": "8.2.6",
"jsdom": "26.0.0",
"jszip": "3.10.1",
- "koa": "2.15.4",
+ "koa": "2.16.1",
"koa-bodyparser": "4.4.1",
"koa-json": "2.0.2",
"lowdb": "1.0.0",
@@ -231,7 +231,7 @@
},
"apps/desktop": {
"name": "@bitwarden/desktop",
- "version": "2025.4.1",
+ "version": "2025.4.2",
"hasInstallScript": true,
"license": "GPL-3.0"
},
@@ -245,7 +245,7 @@
},
"apps/web": {
"name": "@bitwarden/web-vault",
- "version": "2025.4.0"
+ "version": "2025.4.1"
},
"libs/admin-console": {
"name": "@bitwarden/admin-console",
@@ -4700,9 +4700,9 @@
"link": true
},
"node_modules/@bitwarden/sdk-internal": {
- "version": "0.2.0-main.133",
- "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.133.tgz",
- "integrity": "sha512-KzKJGf9cKlcQzfRmqkAwVGBN1kDpcRFkTMm7nrphZSrjfaWJWI1lBEJ0DhnkbMMHJXhQavGyoVk5TIn/Y8ylmw==",
+ "version": "0.2.0-main.137",
+ "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.137.tgz",
+ "integrity": "sha512-Df0pB5tOEc4WiMjskunTrqHulPzenFv8C61sqsBhHfy80xcf5kU5JyPd4asbf3e4uNS6QGXptd8imp09AuiFEA==",
"license": "GPL-3.0"
},
"node_modules/@bitwarden/send-ui": {
@@ -24789,9 +24789,9 @@
}
},
"node_modules/koa": {
- "version": "2.15.4",
- "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.4.tgz",
- "integrity": "sha512-7fNBIdrU2PEgLljXoPWoyY4r1e+ToWCmzS/wwMPbUNs7X+5MMET1ObhJBlUkF5uZG9B6QhM2zS1TsH6adegkiQ==",
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz",
+ "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==",
"license": "MIT",
"dependencies": {
"accepts": "^1.3.5",
diff --git a/package.json b/package.json
index 28d243e1c32..c78decb9827 100644
--- a/package.json
+++ b/package.json
@@ -156,7 +156,7 @@
"@angular/platform-browser": "18.2.13",
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
- "@bitwarden/sdk-internal": "0.2.0-main.133",
+ "@bitwarden/sdk-internal": "0.2.0-main.137",
"@electron/fuses": "1.8.0",
"@emotion/css": "11.13.5",
"@koa/multer": "3.0.2",
@@ -180,7 +180,7 @@
"jquery": "3.7.1",
"jsdom": "26.0.0",
"jszip": "3.10.1",
- "koa": "2.15.4",
+ "koa": "2.16.1",
"koa-bodyparser": "4.4.1",
"koa-json": "2.0.2",
"lit": "3.2.1",