diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 4ba6c6999ac..e730122d1c4 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -582,8 +582,8 @@
"archiveItem": {
"message": "Archive item"
},
- "archiveItemConfirmDesc": {
- "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
+ "archiveItemDialogContent": {
+ "message": "Once archived, this item will be excluded from search results and autofill suggestions."
},
"archived": {
"message": "Archived"
diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts
index ce797d9755e..f881b07282b 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts
@@ -376,7 +376,8 @@ export class ItemMoreOptionsComponent {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "archiveItem" },
- content: { key: "archiveItemConfirmDesc" },
+ content: { key: "archiveItemDialogContent" },
+ acceptButtonText: { key: "archiveVerb" },
type: "info",
});
diff --git a/apps/browser/src/vault/popup/settings/archive.component.html b/apps/browser/src/vault/popup/settings/archive.component.html
index 16afab4384b..01ac799ba29 100644
--- a/apps/browser/src/vault/popup/settings/archive.component.html
+++ b/apps/browser/src/vault/popup/settings/archive.component.html
@@ -42,9 +42,23 @@
{{ cipher.name }}
- @if (CipherViewLikeUtils.hasAttachments(cipher)) {
-
- }
+
+ @if (cipher.organizationId) {
+
+ }
+ @if (CipherViewLikeUtils.hasAttachments(cipher)) {
+
+ }
+
{{ CipherViewLikeUtils.subtitle(cipher) }}
diff --git a/apps/browser/src/vault/popup/settings/archive.component.ts b/apps/browser/src/vault/popup/settings/archive.component.ts
index ecf091a7322..8a81d733039 100644
--- a/apps/browser/src/vault/popup/settings/archive.component.ts
+++ b/apps/browser/src/vault/popup/settings/archive.component.ts
@@ -1,11 +1,13 @@
import { CommonModule } from "@angular/common";
import { Component, inject } from "@angular/core";
+import { toSignal } from "@angular/core/rxjs-interop";
import { Router } from "@angular/router";
import { combineLatest, firstValueFrom, map, Observable, startWith, switchMap } from "rxjs";
import { CollectionService } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
+import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -33,6 +35,7 @@ import {
import {
CanDeleteCipherDirective,
DecryptionFailureDialogComponent,
+ OrgIconDirective,
PasswordRepromptService,
} from "@bitwarden/vault";
@@ -60,6 +63,7 @@ import { ROUTES_AFTER_EDIT_DELETION } from "../components/vault-v2/add-edit/add-
SectionComponent,
SectionHeaderComponent,
TypographyModule,
+ OrgIconDirective,
CardComponent,
ButtonComponent,
],
@@ -79,6 +83,26 @@ export class ArchiveComponent {
private userId$: Observable = this.accountService.activeAccount$.pipe(getUserId);
+ private readonly orgMap = toSignal(
+ this.userId$.pipe(
+ switchMap((userId) =>
+ this.organizationService.organizations$(userId).pipe(
+ map((orgs) => {
+ const map = new Map();
+ for (const org of orgs) {
+ map.set(org.id, org);
+ }
+ return map;
+ }),
+ ),
+ ),
+ ),
+ );
+
+ private readonly collections = toSignal(
+ this.userId$.pipe(switchMap((userId) => this.collectionService.decryptedCollections$(userId))),
+ );
+
protected archivedCiphers$ = this.userId$.pipe(
switchMap((userId) => this.cipherArchiveService.archivedCiphers$(userId)),
);
@@ -242,4 +266,22 @@ export class ArchiveComponent {
return this.passwordRepromptService.passwordRepromptCheck(cipher);
}
+
+ /**
+ * Get the organization tier type for the given cipher.
+ */
+ orgTierType({ organizationId }: CipherViewLike) {
+ return this.orgMap()?.get(organizationId as string)?.productTierType;
+ }
+
+ /**
+ * Get the organization icon tooltip for the given cipher.
+ */
+ orgIconTooltip({ collectionIds }: CipherViewLike) {
+ if (collectionIds.length !== 1) {
+ return this.i18nService.t("nCollections", collectionIds.length);
+ }
+
+ return this.collections()?.find((c) => c.id === collectionIds[0])?.name;
+ }
}
diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock
index 879c5621105..b75275107eb 100644
--- a/apps/desktop/desktop_native/Cargo.lock
+++ b/apps/desktop/desktop_native/Cargo.lock
@@ -2949,9 +2949,9 @@ dependencies = [
[[package]]
name = "sha2"
-version = "0.10.8"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml
index fec4dc41982..facd9554af1 100644
--- a/apps/desktop/desktop_native/Cargo.toml
+++ b/apps/desktop/desktop_native/Cargo.toml
@@ -58,7 +58,7 @@ security-framework = "=3.5.1"
security-framework-sys = "=2.15.0"
serde = "=1.0.209"
serde_json = "=1.0.127"
-sha2 = "=0.10.8"
+sha2 = "=0.10.9"
ssh-encoding = "=0.2.0"
ssh-key = { version = "=0.6.7", default-features = false }
sysinfo = "=0.37.2"
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 56eacc94e50..f9947e16692 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -4397,8 +4397,8 @@
"archiveItem": {
"message": "Archive item"
},
- "archiveItemConfirmDesc": {
- "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
+ "archiveItemDialogContent": {
+ "message": "Once archived, this item will be excluded from search results and autofill suggestions."
},
"unArchiveAndSave": {
"message": "Unarchive and save"
diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/collection-filter.component.ts b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/collection-filter.component.ts
index e23d215aef1..6a801e78ec2 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/collection-filter.component.ts
+++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/collection-filter.component.ts
@@ -12,7 +12,7 @@ import { VaultFilter, CollectionFilter } from "@bitwarden/vault";
imports: [A11yTitleDirective, NavigationModule],
})
export class CollectionFilterComponent {
- protected readonly collection = input>();
+ protected readonly collection = input.required>();
protected readonly activeFilter = input();
protected readonly displayName = computed(() => {
diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.ts b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.ts
index 0f24fe7aecf..dd2d5b504c8 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.ts
+++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.ts
@@ -13,7 +13,7 @@ import { VaultFilter, FolderFilter } from "@bitwarden/vault";
imports: [A11yTitleDirective, NavigationModule, IconButtonModule, I18nPipe],
})
export class FolderFilterComponent {
- protected readonly folder = input>();
+ protected readonly folder = input.required>();
protected readonly activeFilter = input();
protected onEditFolder = output();
diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/organization-filter.component.ts
index fa91816577a..520c29833e3 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/organization-filter.component.ts
+++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/organization-filter.component.ts
@@ -1,3 +1,5 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
import { Component, computed, input, inject } from "@angular/core";
import { DisplayMode } from "@bitwarden/angular/vault/vault-filter/models/display-mode";
@@ -20,7 +22,7 @@ export class OrganizationFilterComponent {
private vaultFilterService: VaultFilterServiceAbstraction = inject(VaultFilterServiceAbstraction);
protected readonly hide = input(false);
- protected readonly organizations = input>();
+ protected readonly organizations = input.required>();
protected readonly activeFilter = input();
protected readonly activeOrganizationDataOwnership = input(false);
protected readonly activeSingleOrganizationPolicy = input(false);
@@ -56,7 +58,6 @@ export class OrganizationFilterComponent {
if (!organization.node.enabled) {
this.toastService.showToast({
variant: "error",
- title: null,
message: this.i18nService.t("disabledOrganizationFilterError"),
});
return;
diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/status-filter.component.html b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/status-filter.component.html
index aef9a4d41b4..b6b22a3c68d 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/status-filter.component.html
+++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/status-filter.component.html
@@ -6,10 +6,13 @@
[text]="archiveFilter.name | i18n"
[attr.aria-pressed]="activeFilter()?.isArchived"
[appA11yTitle]="archiveFilter.name | i18n"
- />
- @if (!(canArchive$ | async)) {
-
- }
+ >
+ @if (!(canArchive$ | async)) {
+
+
+
+ }
+
}
();
+ protected readonly activeFilter = input.required();
+
+ private readonly premiumBadgeComponent = viewChild(PremiumBadgeComponent);
+
protected readonly archiveFilter: CipherTypeFilter = {
id: "archive",
name: "archiveNoun",
@@ -38,7 +43,7 @@ export class StatusFilterComponent {
};
protected applyFilter(filterType: CipherStatus) {
- let filter: CipherTypeFilter = null;
+ let filter: CipherTypeFilter | null = null;
if (filterType === "archive") {
filter = this.archiveFilter;
} else if (filterType === "trash") {
@@ -50,8 +55,6 @@ export class StatusFilterComponent {
}
}
- private readonly premiumBadgeComponent = viewChild.required(PremiumBadgeComponent);
-
private userId$ = this.accountService.activeAccount$.pipe(getUserId);
protected canArchive$ = this.userId$.pipe(
switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)),
@@ -71,7 +74,7 @@ export class StatusFilterComponent {
if (canArchive || hasArchivedCiphers) {
this.applyFilter("archive");
} else {
- await this.premiumBadgeComponent().promptForPremium(event);
+ await this.premiumBadgeComponent()?.promptForPremium(event);
}
}
}
diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/type-filter.component.ts b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/type-filter.component.ts
index 40755b25253..e5e7a7691e4 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/type-filter.component.ts
+++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/type-filter.component.ts
@@ -20,7 +20,7 @@ export class TypeFilterComponent {
RestrictedItemTypesService,
);
- protected readonly cipherTypes = input>();
+ protected readonly cipherTypes = input.required>();
protected readonly activeFilter = input();
protected applyTypeFilter(event: Event, cipherType: TreeNode) {
diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.ts b/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.ts
index aa54c736024..a858e40bf5e 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.ts
+++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.ts
@@ -1,3 +1,5 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, inject, OnInit, output, computed, signal } from "@angular/core";
import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs";
diff --git a/apps/desktop/src/vault/app/vault-v3/vault.component.ts b/apps/desktop/src/vault/app/vault-v3/vault.component.ts
index 64f850826a3..c104f76ff2d 100644
--- a/apps/desktop/src/vault/app/vault-v3/vault.component.ts
+++ b/apps/desktop/src/vault/app/vault-v3/vault.component.ts
@@ -1,3 +1,5 @@
+// FIXME: Update this file to be type safe and remove this and next line
+// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts
index cad2c97557b..527df0b5370 100644
--- a/apps/web/src/app/vault/individual-vault/vault.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault.component.ts
@@ -746,7 +746,8 @@ export class VaultComponent implements OnInit, OnDestr
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "archiveItem" },
- content: { key: "archiveItemConfirmDesc" },
+ content: { key: "archiveItemDialogContent" },
+ acceptButtonText: { key: "archiveVerb" },
type: "info",
});
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index de77354efb6..e296e76e983 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -11707,8 +11707,8 @@
"message": "Archive item",
"description": "Verb"
},
- "archiveItemConfirmDesc": {
- "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?"
+ "archiveItemDialogContent": {
+ "message": "Once archived, this item will be excluded from search results and autofill suggestions."
},
"archiveBulkItems": {
"message": "Archive items",
diff --git a/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts
index 71d228ff822..cbaece1b442 100644
--- a/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts
+++ b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts
@@ -264,6 +264,13 @@ export abstract class OrganizationUserApiService {
ids: string[],
): Promise>;
+ /**
+ * Revoke the current user's access to the organization
+ * if they decline an item transfer under the Organization Data Ownership policy.
+ * @param organizationId - Identifier for the organization the user belongs to
+ */
+ abstract revokeSelf(organizationId: string): Promise;
+
/**
* Restore an organization user's access to the organization
* @param organizationId - Identifier for the organization the user belongs to
diff --git a/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts
index 869d84a8c8e..536afd2b3f6 100644
--- a/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts
+++ b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts
@@ -339,6 +339,16 @@ export class DefaultOrganizationUserApiService implements OrganizationUserApiSer
return new ListResponse(r, OrganizationUserBulkResponse);
}
+ revokeSelf(organizationId: string): Promise {
+ return this.apiService.send(
+ "PUT",
+ "/organizations/" + organizationId + "/users/revoke-self",
+ null,
+ true,
+ false,
+ );
+ }
+
restoreOrganizationUser(organizationId: string, id: string): Promise {
return this.apiService.send(
"PUT",
diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts
index bc065155fdb..db8eacb5fa4 100644
--- a/libs/key-management/src/abstractions/key.service.ts
+++ b/libs/key-management/src/abstractions/key.service.ts
@@ -280,8 +280,7 @@ export abstract class KeyService {
* encrypted private key at all.
*
* @param userId The user id of the user to get the data for.
- * @returns An observable stream of the decrypted private key or null.
- * @throws Error when decryption of the encrypted private key fails.
+ * @returns An observable stream of the decrypted private key or null if the private key is not present or fails to decrypt
*/
abstract userPrivateKey$(userId: UserId): Observable;
diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts
index c0a0ab62347..9d96d7c09b1 100644
--- a/libs/key-management/src/key.service.spec.ts
+++ b/libs/key-management/src/key.service.spec.ts
@@ -437,14 +437,13 @@ describe("keyService", () => {
);
});
- it("throws an error if unwrapping encrypted private key fails", async () => {
+ it("emits null if unwrapping encrypted private key fails", async () => {
encryptService.unwrapDecapsulationKey.mockImplementationOnce(() => {
throw new Error("Unwrapping failed");
});
- await expect(firstValueFrom(keyService.userPrivateKey$(mockUserId))).rejects.toThrow(
- "Unwrapping failed",
- );
+ const result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
+ expect(result).toBeNull();
});
it("returns null if user key is not set", async () => {
diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts
index 752a89e5fcd..4c749e9f6c4 100644
--- a/libs/key-management/src/key.service.ts
+++ b/libs/key-management/src/key.service.ts
@@ -791,7 +791,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
}
- private userPrivateKeyHelper$(userId: UserId) {
+ private userPrivateKeyHelper$(userId: UserId): Observable<{
+ userKey: UserKey;
+ userPrivateKey: UserPrivateKey | null;
+ } | null> {
const userKey$ = this.userKey$(userId);
return userKey$.pipe(
switchMap((userKey) => {
@@ -801,18 +804,20 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$.pipe(
switchMap(async (encryptedPrivateKey) => {
- try {
- return await this.decryptPrivateKey(encryptedPrivateKey, userKey);
- } catch (e) {
- this.logService.error("Failed to decrypt private key for user ", userId, e);
- throw e;
- }
+ return await this.decryptPrivateKey(encryptedPrivateKey, userKey);
}),
// Combine outerscope info with user private key
map((userPrivateKey) => ({
userKey,
userPrivateKey,
})),
+ catchError((err: unknown) => {
+ this.logService.error(`Failed to decrypt private key for user ${userId}`);
+ return of({
+ userKey,
+ userPrivateKey: null,
+ });
+ }),
);
}),
);
diff --git a/libs/tools/send/send-ui/src/send-list/send-list.component.ts b/libs/tools/send/send-ui/src/send-list/send-list.component.ts
index d90f77913aa..17fc006feef 100644
--- a/libs/tools/send/send-ui/src/send-list/send-list.component.ts
+++ b/libs/tools/send/send-ui/src/send-list/send-list.component.ts
@@ -1,17 +1,8 @@
import { CommonModule } from "@angular/common";
-import {
- ChangeDetectionStrategy,
- Component,
- computed,
- effect,
- inject,
- input,
- output,
-} from "@angular/core";
+import { ChangeDetectionStrategy, Component, computed, effect, input, output } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { NoResults, NoSendsIcon } from "@bitwarden/assets/svg";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import {
ButtonModule,
@@ -57,8 +48,6 @@ export class SendListComponent {
protected readonly noResultsIcon = NoResults;
protected readonly sendListState = SendListState;
- private i18nService = inject(I18nService);
-
readonly sends = input.required();
readonly loading = input(false);
readonly disableSend = input(false);
@@ -70,7 +59,7 @@ export class SendListComponent {
);
protected readonly noSearchResults = computed(
- () => this.showSearchBar() && (this.sends().length === 0 || this.searchText().length > 0),
+ () => this.showSearchBar() && this.sends().length === 0,
);
// Reusable data source instance - updated reactively when sends change
diff --git a/libs/vault/src/services/archive-cipher-utilities.service.ts b/libs/vault/src/services/archive-cipher-utilities.service.ts
index 5d3c5c33236..93e752b57dd 100644
--- a/libs/vault/src/services/archive-cipher-utilities.service.ts
+++ b/libs/vault/src/services/archive-cipher-utilities.service.ts
@@ -41,7 +41,8 @@ export class ArchiveCipherUtilitiesService {
const confirmed = await this.dialogService.openSimpleDialog({
title: { key: "archiveItem" },
- content: { key: "archiveItemConfirmDesc" },
+ content: { key: "archiveItemDialogContent" },
+ acceptButtonText: { key: "archiveVerb" },
type: "info",
});
diff --git a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts
index 4ad9c53c6f0..51154c3cee9 100644
--- a/libs/vault/src/services/default-vault-items-transfer.service.spec.ts
+++ b/libs/vault/src/services/default-vault-items-transfer.service.spec.ts
@@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, of, Subject } from "rxjs";
// eslint-disable-next-line no-restricted-imports
-import { CollectionService } from "@bitwarden/admin-console/common";
+import { CollectionService, OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -42,6 +42,7 @@ describe("DefaultVaultItemsTransferService", () => {
let mockToastService: MockProxy;
let mockEventCollectionService: MockProxy;
let mockConfigService: MockProxy;
+ let mockOrganizationUserApiService: MockProxy;
const userId = "user-id" as UserId;
const organizationId = "org-id" as OrganizationId;
@@ -77,6 +78,7 @@ describe("DefaultVaultItemsTransferService", () => {
mockToastService = mock();
mockEventCollectionService = mock();
mockConfigService = mock();
+ mockOrganizationUserApiService = mock();
mockI18nService.t.mockImplementation((key) => key);
transferInProgressValues = [];
@@ -92,6 +94,7 @@ describe("DefaultVaultItemsTransferService", () => {
mockToastService,
mockEventCollectionService,
mockConfigService,
+ mockOrganizationUserApiService,
);
});
@@ -632,9 +635,15 @@ describe("DefaultVaultItemsTransferService", () => {
mockDialogService.open
.mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined))
.mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed));
+ mockOrganizationUserApiService.revokeSelf.mockResolvedValue(undefined);
await service.enforceOrganizationDataOwnership(userId);
+ expect(mockOrganizationUserApiService.revokeSelf).toHaveBeenCalledWith(organizationId);
+ expect(mockToastService.showToast).toHaveBeenCalledWith({
+ variant: "success",
+ message: "leftOrganization",
+ });
expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled();
});
diff --git a/libs/vault/src/services/default-vault-items-transfer.service.ts b/libs/vault/src/services/default-vault-items-transfer.service.ts
index c184b2c902e..6009fc97e7c 100644
--- a/libs/vault/src/services/default-vault-items-transfer.service.ts
+++ b/libs/vault/src/services/default-vault-items-transfer.service.ts
@@ -10,7 +10,7 @@ import {
} from "rxjs";
// eslint-disable-next-line no-restricted-imports
-import { CollectionService } from "@bitwarden/admin-console/common";
+import { CollectionService, OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -53,6 +53,7 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
private toastService: ToastService,
private eventCollectionService: EventCollectionService,
private configService: ConfigService,
+ private organizationUserApiService: OrganizationUserApiService,
) {}
private _transferInProgressSubject = new BehaviorSubject(false);
@@ -162,7 +163,12 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
);
if (!userAcceptedTransfer) {
- // TODO: Revoke user from organization if they decline migration and show toast PM-29465
+ await this.organizationUserApiService.revokeSelf(migrationInfo.enforcingOrganization.id);
+
+ this.toastService.showToast({
+ variant: "success",
+ message: this.i18nService.t("leftOrganization"),
+ });
await this.eventCollectionService.collect(
EventType.Organization_ItemOrganization_Declined,