1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 14:23:32 +00:00

[PM-20644] [Vault] [Browser Extension] Front End Changes to Enforce "Remove card item type policy" (#15147)

* Added service to get restricted cipher and used that to hide in autofill settings

* Referenced files from the work done on web

* Fixed restrictedCardType$ observable

* Created resuseable cipher menu items type

(cherry picked from commit 34be7f7ffef135aea2449e11e45e638ebaf34ee8)

* Updated new item dropdown to filter out restricted type and also render the menu items dynamically

(cherry picked from commit 566099ba9f3dbd7f18077dbc5b8ed44f51a94bfc)

* Updated service to have cipher types as an observable

(cherry picked from commit 6848e5f75803eb45e2262c617c9805359861ad14)

* Refactored service to have use CIPHER MENU ITEMS type and filter restricted rypes and return an observable

(cherry picked from commit e25c4eb18af895deac762b9e2d7ae69cc235f224)

* Fixed type enum

* Referenced files from the work done on web

* Referenced change from the work done on web

* Remove comment

* Remove cipher type from autofill suggestion list when enabled

* revert autofillcipher$ change

* Fixed test

* Added sharereplay to restrictedCardType$ observable

* Added startwith operator

* Add organization exemptions to restricted filter
This commit is contained in:
SmithThe4th
2025-06-16 15:07:29 -04:00
committed by GitHub
parent 9ba593701a
commit fcd24a4d60
10 changed files with 184 additions and 100 deletions

View File

@@ -65,7 +65,10 @@
{{ "showInlineMenuIdentitiesLabel" | i18n }} {{ "showInlineMenuIdentitiesLabel" | i18n }}
</bit-label> </bit-label>
</bit-form-control> </bit-form-control>
<bit-form-control *ngIf="enableInlineMenu" class="tw-ml-5"> <bit-form-control
*ngIf="enableInlineMenu && !(restrictedCardType$ | async)"
class="tw-ml-5"
>
<input <input
bitCheckbox bitCheckbox
id="show-inline-menu-cards" id="show-inline-menu-cards"
@@ -114,7 +117,7 @@
</a> </a>
</bit-hint> </bit-hint>
</bit-form-control> </bit-form-control>
<bit-form-control> <bit-form-control *ngIf="!(restrictedCardType$ | async)">
<input <input
bitCheckbox bitCheckbox
id="showCardsSuggestions" id="showCardsSuggestions"

View File

@@ -11,7 +11,7 @@ import {
ReactiveFormsModule, ReactiveFormsModule,
} from "@angular/forms"; } from "@angular/forms";
import { RouterModule } from "@angular/router"; import { RouterModule } from "@angular/router";
import { filter, firstValueFrom, Observable, switchMap } from "rxjs"; import { filter, firstValueFrom, map, Observable, shareReplay, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
@@ -44,6 +44,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { import {
CardComponent, CardComponent,
CheckboxModule, CheckboxModule,
@@ -57,6 +58,7 @@ import {
SelectModule, SelectModule,
TypographyModule, TypographyModule,
} from "@bitwarden/components"; } from "@bitwarden/components";
import { RestrictedItemTypesService } from "@bitwarden/vault";
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
import { BrowserApi } from "../../../platform/browser/browser-api"; import { BrowserApi } from "../../../platform/browser/browser-api";
@@ -111,6 +113,11 @@ export class AutofillComponent implements OnInit {
this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id), this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id),
), ),
); );
protected restrictedCardType$: Observable<boolean> =
this.restrictedItemTypesService.restricted$.pipe(
map((restrictedTypes) => restrictedTypes.some((type) => type.cipherType === CipherType.Card)),
shareReplay({ bufferSize: 1, refCount: true }),
);
protected autofillOnPageLoadForm = new FormGroup({ protected autofillOnPageLoadForm = new FormGroup({
autofillOnPageLoad: new FormControl(), autofillOnPageLoad: new FormControl(),
@@ -156,6 +163,7 @@ export class AutofillComponent implements OnInit {
private nudgesService: NudgesService, private nudgesService: NudgesService,
private accountService: AccountService, private accountService: AccountService,
private autofillBrowserSettingsService: AutofillBrowserSettingsService, private autofillBrowserSettingsService: AutofillBrowserSettingsService,
private restrictedItemTypesService: RestrictedItemTypesService,
) { ) {
this.autofillOnPageLoadOptions = [ this.autofillOnPageLoadOptions = [
{ name: this.i18nService.t("autoFillOnPageLoadYes"), value: true }, { name: this.i18nService.t("autoFillOnPageLoadYes"), value: true },

View File

@@ -3,34 +3,12 @@
{{ "new" | i18n }} {{ "new" | i18n }}
</button> </button>
<bit-menu #itemOptions> <bit-menu #itemOptions>
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Login)"> @for (menuItem of cipherMenuItems$ | async; track menuItem.type) {
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i> <a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(menuItem.type)">
{{ "typeLogin" | i18n }} <i [class]="`bwi ${menuItem.icon}`" slot="start" aria-hidden="true"></i>
</a> {{ menuItem.labelKey | i18n }}
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Card)"> </a>
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i> }
{{ "typeCard" | i18n }}
</a>
<a
bitMenuItem
[routerLink]="['/add-cipher']"
[queryParams]="buildQueryParams(cipherType.Identity)"
>
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
{{ "typeIdentity" | i18n }}
</a>
<a
bitMenuItem
[routerLink]="['/add-cipher']"
[queryParams]="buildQueryParams(cipherType.SecureNote)"
>
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
{{ "note" | i18n }}
</a>
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.SshKey)">
<i class="bwi bwi-key" slot="start" aria-hidden="true"></i>
{{ "typeSshKey" | i18n }}
</a>
<bit-menu-divider></bit-menu-divider> <bit-menu-divider></bit-menu-divider>
<button type="button" bitMenuItem (click)="openFolderDialog()"> <button type="button" bitMenuItem (click)="openFolderDialog()">
<i class="bwi bwi-folder" slot="start" aria-hidden="true"></i> <i class="bwi bwi-folder" slot="start" aria-hidden="true"></i>

View File

@@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ActivatedRoute, RouterLink } from "@angular/router"; import { ActivatedRoute, RouterLink } from "@angular/router";
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -12,6 +13,7 @@ import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstraction
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";
import { RestrictedCipherType, RestrictedItemTypesService } from "@bitwarden/vault";
import { BrowserApi } from "../../../../../platform/browser/browser-api"; import { BrowserApi } from "../../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
@@ -23,6 +25,7 @@ describe("NewItemDropdownV2Component", () => {
let fixture: ComponentFixture<NewItemDropdownV2Component>; let fixture: ComponentFixture<NewItemDropdownV2Component>;
let dialogServiceMock: jest.Mocked<DialogService>; let dialogServiceMock: jest.Mocked<DialogService>;
let browserApiMock: jest.Mocked<typeof BrowserApi>; let browserApiMock: jest.Mocked<typeof BrowserApi>;
let restrictedItemTypesServiceMock: jest.Mocked<RestrictedItemTypesService>;
const mockTab = { url: "https://example.com" }; const mockTab = { url: "https://example.com" };
@@ -44,6 +47,9 @@ describe("NewItemDropdownV2Component", () => {
const folderServiceMock = mock<FolderService>(); const folderServiceMock = mock<FolderService>();
const folderApiServiceAbstractionMock = mock<FolderApiServiceAbstraction>(); const folderApiServiceAbstractionMock = mock<FolderApiServiceAbstraction>();
const accountServiceMock = mock<AccountService>(); const accountServiceMock = mock<AccountService>();
restrictedItemTypesServiceMock = {
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
} as any;
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ imports: [
@@ -65,6 +71,7 @@ describe("NewItemDropdownV2Component", () => {
{ provide: FolderService, useValue: folderServiceMock }, { provide: FolderService, useValue: folderServiceMock },
{ provide: FolderApiServiceAbstraction, useValue: folderApiServiceAbstractionMock }, { provide: FolderApiServiceAbstraction, useValue: folderApiServiceAbstractionMock },
{ provide: AccountService, useValue: accountServiceMock }, { provide: AccountService, useValue: accountServiceMock },
{ provide: RestrictedItemTypesService, useValue: restrictedItemTypesServiceMock },
], ],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -3,12 +3,14 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core"; import { Component, Input, OnInit } from "@angular/core";
import { RouterLink } from "@angular/router"; import { RouterLink } from "@angular/router";
import { map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherMenuItem, CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";
import { AddEditFolderDialogComponent } from "@bitwarden/vault"; import { AddEditFolderDialogComponent, RestrictedItemTypesService } from "@bitwarden/vault";
import { BrowserApi } from "../../../../../platform/browser/browser-api"; import { BrowserApi } from "../../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
@@ -34,7 +36,22 @@ export class NewItemDropdownV2Component implements OnInit {
@Input() @Input()
initialValues: NewItemInitialValues; initialValues: NewItemInitialValues;
constructor(private dialogService: DialogService) {} /**
* Observable of cipher menu items that are not restricted by policy
*/
readonly cipherMenuItems$: Observable<CipherMenuItem[]> =
this.restrictedItemTypeService.restricted$.pipe(
map((restrictedTypes) => {
const restrictedTypeArr = restrictedTypes.map((item) => item.cipherType);
return CIPHER_MENU_ITEMS.filter((menuItem) => !restrictedTypeArr.includes(menuItem.type));
}),
);
constructor(
private dialogService: DialogService,
private restrictedItemTypeService: RestrictedItemTypesService,
) {}
async ngOnInit() { async ngOnInit() {
this.tab = await BrowserApi.getTabFromCurrentWindow(); this.tab = await BrowserApi.getTabFromCurrentWindow();

View File

@@ -42,7 +42,7 @@
fullWidth fullWidth
placeholderIcon="bwi-list" placeholderIcon="bwi-list"
[placeholderText]="'type' | i18n" [placeholderText]="'type' | i18n"
[options]="cipherTypes" [options]="cipherTypes$ | async"
> >
</bit-chip-select> </bit-chip-select>
</form> </form>

View File

@@ -18,7 +18,7 @@ export class VaultListFiltersComponent {
protected organizations$ = this.vaultPopupListFiltersService.organizations$; protected organizations$ = this.vaultPopupListFiltersService.organizations$;
protected collections$ = this.vaultPopupListFiltersService.collections$; protected collections$ = this.vaultPopupListFiltersService.collections$;
protected folders$ = this.vaultPopupListFiltersService.folders$; protected folders$ = this.vaultPopupListFiltersService.folders$;
protected cipherTypes = this.vaultPopupListFiltersService.cipherTypes; protected cipherTypes$ = this.vaultPopupListFiltersService.cipherTypes$;
// Combine all filters into a single observable to eliminate the filters from loading separately in the UI. // Combine all filters into a single observable to eliminate the filters from loading separately in the UI.
protected allFilters$ = combineLatest([ protected allFilters$ = combineLatest([

View File

@@ -20,6 +20,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { RestrictedCipherType, RestrictedItemTypesService } from "@bitwarden/vault";
import { import {
CachedFilterState, CachedFilterState,
@@ -70,6 +71,10 @@ describe("VaultPopupListFiltersService", () => {
const state$ = new BehaviorSubject<boolean>(false); const state$ = new BehaviorSubject<boolean>(false);
const update = jest.fn().mockResolvedValue(undefined); const update = jest.fn().mockResolvedValue(undefined);
const restrictedItemTypesService = {
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
};
beforeEach(() => { beforeEach(() => {
_memberOrganizations$ = new BehaviorSubject<Organization[]>([]); // Fresh instance per test _memberOrganizations$ = new BehaviorSubject<Organization[]>([]); // Fresh instance per test
folderViews$ = new BehaviorSubject([]); // Fresh instance per test folderViews$ = new BehaviorSubject([]); // Fresh instance per test
@@ -125,21 +130,46 @@ describe("VaultPopupListFiltersService", () => {
provide: ViewCacheService, provide: ViewCacheService,
useValue: viewCacheService, useValue: viewCacheService,
}, },
{
provide: RestrictedItemTypesService,
useValue: restrictedItemTypesService,
},
], ],
}); });
service = TestBed.inject(VaultPopupListFiltersService); service = TestBed.inject(VaultPopupListFiltersService);
}); });
describe("cipherTypes", () => { describe("cipherTypes$", () => {
it("returns all cipher types", () => { it("returns all cipher types when no restrictions", (done) => {
expect(service.cipherTypes.map((c) => c.value)).toEqual([ restrictedItemTypesService.restricted$.next([]);
CipherType.Login,
CipherType.Card, service.cipherTypes$.subscribe((cipherTypes) => {
CipherType.Identity, expect(cipherTypes.map((c) => c.value)).toEqual([
CipherType.SecureNote, CipherType.Login,
CipherType.SshKey, CipherType.Card,
CipherType.Identity,
CipherType.SecureNote,
CipherType.SshKey,
]);
done();
});
});
it("filters out restricted cipher types", (done) => {
restrictedItemTypesService.restricted$.next([
{ cipherType: CipherType.Card, allowViewOrgIds: [] },
]); ]);
service.cipherTypes$.subscribe((cipherTypes) => {
expect(cipherTypes.map((c) => c.value)).toEqual([
CipherType.Login,
CipherType.Identity,
CipherType.SecureNote,
CipherType.SshKey,
]);
done();
});
}); });
}); });
@@ -452,6 +482,10 @@ describe("VaultPopupListFiltersService", () => {
{ type: CipherType.SecureNote, collectionIds: [], organizationId: null }, { type: CipherType.SecureNote, collectionIds: [], organizationId: null },
] as CipherView[]; ] as CipherView[];
beforeEach(() => {
restrictedItemTypesService.restricted$.next([]);
});
it("filters by cipherType", (done) => { it("filters by cipherType", (done) => {
service.filterFunction$.subscribe((filterFunction) => { service.filterFunction$.subscribe((filterFunction) => {
expect(filterFunction(ciphers)).toEqual([ciphers[0]]); expect(filterFunction(ciphers)).toEqual([ciphers[0]]);
@@ -690,6 +724,9 @@ function createSeededVaultPopupListFiltersService(
} as any; } as any;
const accountServiceMock = mockAccountServiceWith("userId" as UserId); const accountServiceMock = mockAccountServiceWith("userId" as UserId);
const restrictedItemTypesServiceMock = {
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
} as any;
const formBuilderInstance = new FormBuilder(); const formBuilderInstance = new FormBuilder();
const seededCachedSignal = createMockSignal<CachedFilterState>(cachedState); const seededCachedSignal = createMockSignal<CachedFilterState>(cachedState);
@@ -713,6 +750,7 @@ function createSeededVaultPopupListFiltersService(
stateProviderMock, stateProviderMock,
accountServiceMock, accountServiceMock,
viewCacheServiceMock, viewCacheServiceMock,
restrictedItemTypesServiceMock,
); );
}); });

View File

@@ -39,7 +39,9 @@ import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
import { CIPHER_MENU_ITEMS } from "@bitwarden/common/vault/types/cipher-menu-items";
import { ChipSelectOption } from "@bitwarden/components"; import { ChipSelectOption } from "@bitwarden/components";
import { RestrictedItemTypesService } from "@bitwarden/vault";
const FILTER_VISIBILITY_KEY = new KeyDefinition<boolean>(VAULT_SETTINGS_DISK, "filterVisibility", { const FILTER_VISIBILITY_KEY = new KeyDefinition<boolean>(VAULT_SETTINGS_DISK, "filterVisibility", {
deserializer: (obj) => obj, deserializer: (obj) => obj,
@@ -178,6 +180,7 @@ export class VaultPopupListFiltersService {
private stateProvider: StateProvider, private stateProvider: StateProvider,
private accountService: AccountService, private accountService: AccountService,
private viewCacheService: ViewCacheService, private viewCacheService: ViewCacheService,
private restrictedItemTypesService: RestrictedItemTypesService,
) { ) {
this.filterForm.controls.organization.valueChanges this.filterForm.controls.organization.valueChanges
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
@@ -210,74 +213,80 @@ export class VaultPopupListFiltersService {
/** /**
* Observable whose value is a function that filters an array of `CipherView` objects based on the current filters * Observable whose value is a function that filters an array of `CipherView` objects based on the current filters
*/ */
filterFunction$: Observable<(ciphers: CipherView[]) => CipherView[]> = this.filters$.pipe( filterFunction$: Observable<(ciphers: CipherView[]) => CipherView[]> = combineLatest([
this.filters$,
this.restrictedItemTypesService.restricted$.pipe(startWith([])),
]).pipe(
map( map(
(filters) => (ciphers: CipherView[]) => ([filters, restrictions]) =>
ciphers.filter((cipher) => { (ciphers: CipherView[]) =>
// Vault popup lists never shows deleted ciphers ciphers.filter((cipher) => {
if (cipher.isDeleted) { // Vault popup lists never shows deleted ciphers
return false; if (cipher.isDeleted) {
}
if (filters.cipherType !== null && cipher.type !== filters.cipherType) {
return false;
}
if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) {
return false;
}
if (filters.folder && cipher.folderId !== filters.folder.id) {
return false;
}
const isMyVault = filters.organization?.id === MY_VAULT_ID;
if (isMyVault) {
if (cipher.organizationId !== null) {
return false; return false;
} }
} else if (filters.organization) {
if (cipher.organizationId !== filters.organization.id) { // Check if cipher type is restricted (with organization exemptions)
if (restrictions && restrictions.length > 0) {
const isRestricted = restrictions.some(
(restrictedType) =>
restrictedType.cipherType === cipher.type &&
(cipher.organizationId
? !restrictedType.allowViewOrgIds.includes(cipher.organizationId)
: restrictedType.allowViewOrgIds.length === 0),
);
if (isRestricted) {
return false;
}
}
if (filters.cipherType !== null && cipher.type !== filters.cipherType) {
return false; return false;
} }
}
return true; if (filters.collection && !cipher.collectionIds?.includes(filters.collection.id)) {
}), return false;
}
if (filters.folder && cipher.folderId !== filters.folder.id) {
return false;
}
const isMyVault = filters.organization?.id === MY_VAULT_ID;
if (isMyVault) {
if (cipher.organizationId !== null) {
return false;
}
} else if (filters.organization) {
if (cipher.organizationId !== filters.organization.id) {
return false;
}
}
return true;
}),
), ),
); );
/** /**
* All available cipher types * All available cipher types (filtered by policy restrictions)
*/ */
readonly cipherTypes: ChipSelectOption<CipherType>[] = [ readonly cipherTypes$: Observable<ChipSelectOption<CipherType>[]> =
{ this.restrictedItemTypesService.restricted$.pipe(
value: CipherType.Login, map((restrictedTypes) => {
label: this.i18nService.t("typeLogin"), const restrictedCipherTypes = restrictedTypes.map((r) => r.cipherType);
icon: "bwi-globe",
}, return CIPHER_MENU_ITEMS.filter((item) => !restrictedCipherTypes.includes(item.type)).map(
{ (item) => ({
value: CipherType.Card, value: item.type,
label: this.i18nService.t("typeCard"), label: this.i18nService.t(item.labelKey),
icon: "bwi-credit-card", icon: item.icon,
}, }),
{ );
value: CipherType.Identity, }),
label: this.i18nService.t("typeIdentity"), );
icon: "bwi-id-card",
},
{
value: CipherType.SecureNote,
label: this.i18nService.t("note"),
icon: "bwi-sticky-note",
},
{
value: CipherType.SshKey,
label: this.i18nService.t("typeSshKey"),
icon: "bwi-key",
},
];
/** Resets `filterForm` to the original state */ /** Resets `filterForm` to the original state */
resetFilterForm(): void { resetFilterForm(): void {

View File

@@ -0,0 +1,24 @@
import { CipherType } from "../enums";
/**
* Represents a menu item for creating a new cipher of a specific type
*/
export type CipherMenuItem = {
/** The cipher type this menu item represents */
type: CipherType;
/** The icon class name (e.g., "bwi-globe") */
icon: string;
/** The i18n key for the label text */
labelKey: string;
};
/**
* All available cipher menu items with their associated icons and labels
*/
export const CIPHER_MENU_ITEMS = Object.freeze([
{ type: CipherType.Login, icon: "bwi-globe", labelKey: "typeLogin" },
{ type: CipherType.Card, icon: "bwi-credit-card", labelKey: "typeCard" },
{ type: CipherType.Identity, icon: "bwi-id-card", labelKey: "typeIdentity" },
{ type: CipherType.SecureNote, icon: "bwi-sticky-note", labelKey: "note" },
{ type: CipherType.SshKey, icon: "bwi-key", labelKey: "typeSshKey" },
] as const) satisfies readonly CipherMenuItem[];