Overview
The CollectionService manages collections within organizations. Collections are organization-level groupings of vault items that allow teams to share and organize ciphers. Unlike folders (which are personal), collections are shared across organization members with specific access permissions.
Source: libs/admin-console/src/common/collections/services/default-collection.service.ts
Key Features
- Manage organization collections
- Encrypt and decrypt collection data
- Handle collection permissions (read, edit, manage)
- Transform collections into nested tree structures
- Group collections by organization
- Observable-based reactive state management
Observables
encryptedCollections$
Observable that emits encrypted collections for a user.
encryptedCollections$(userId: UserId): Observable<Collection[] | null>
Parameters:
userId: UserId - The user ID to get collections for
Returns: Observable of encrypted collection objects, or null if not loaded
decryptedCollections$
Observable that emits decrypted collection views for a user.
decryptedCollections$(userId: UserId): Observable<CollectionView[]>
Parameters:
userId: UserId - The user ID to get collection views for
Returns: Observable of decrypted collection views sorted by name
Example:
const collections$ = collectionService.decryptedCollections$(userId);
collections$.subscribe(collections => {
console.log(`User has access to ${collections.length} collections`);
collections.forEach(c => {
console.log(`- ${c.name} (Org: ${c.organizationId})`);
});
});
defaultUserCollection$
Observable for the default collection of a user in an organization.
defaultUserCollection$(
userId: UserId,
orgId: OrganizationId,
): Observable<CollectionView | undefined>
Parameters:
userId: UserId - The user ID
orgId: OrganizationId - The organization ID
Returns: Observable that emits the default collection or undefined
Example:
const defaultCollection$ = collectionService.defaultUserCollection$(userId, orgId);
defaultCollection$.subscribe(collection => {
if (collection) {
console.log(`Default collection: ${collection.name}`);
}
});
Core Methods
encrypt
Encrypts a collection view for storage.
encrypt(model: CollectionView, userId: UserId): Promise<Collection>
Parameters:
model: CollectionView - The collection view to encrypt
userId: UserId - The user ID (used to get organization key)
Returns: Promise resolving to encrypted collection
Example:
const collectionView = new CollectionView({
id: "collection-id",
organizationId: "org-id",
name: "Engineering Team"
});
const encrypted = await collectionService.encrypt(collectionView, userId);
decryptMany$
Decrypts multiple collections using organization keys.
decryptMany$(
collections: Collection[],
orgKeys: Record<OrganizationId, OrgKey>,
): Observable<CollectionView[]>
Parameters:
collections: Collection[] - Array of encrypted collections
orgKeys: Record<OrganizationId, OrgKey> - Map of organization IDs to org keys
Returns: Observable of decrypted collection views sorted by name
This method will soon be made private. Use decryptedCollections$ observable instead.
CRUD Operations
upsert
Inserts or updates a collection in storage.
upsert(collection: CollectionData, userId: UserId): Promise<any>
Parameters:
collection: CollectionData - The collection data to upsert
userId: UserId - The user ID
Behavior:
- Updates both encrypted and decrypted state
- Automatically decrypts the collection using organization key
- Merges with existing collections
Example:
const collectionData = new CollectionData(response);
await collectionService.upsert(collectionData, userId);
replace
Replaces all collections for a user.
replace(
collections: { [id: string]: CollectionData },
userId: UserId
): Promise<any>
Parameters:
collections: { [id: string]: CollectionData } - Object mapping collection IDs to collection data
userId: UserId - The user ID
Note: This is typically used during sync operations. It clears decrypted state, forcing re-decryption on next access.
delete
Deletes one or more collections.
delete(ids: CollectionId[], userId: UserId): Promise<any>
Parameters:
ids: CollectionId[] - Array of collection IDs to delete
userId: UserId - The user ID
Behavior:
- Removes from both encrypted and decrypted state
- Does not affect ciphers (they may become unassigned)
Example:
await collectionService.delete(
["collection-1", "collection-2"] as CollectionId[],
userId
);
Tree Structure Methods
getAllNested
Transforms collections into a nested tree structure.
getAllNested(collections: CollectionView[]): TreeNode<CollectionView>[]
Parameters:
collections: CollectionView[] - Flat array of collections
Returns: Array of tree nodes with nested structure based on collection names
Nesting Behavior:
- Collections are nested based on
/ delimiter in names
- Example:
"Team/Engineering/Backend" creates a 3-level tree
- Collections are grouped by organization first
Example:
const collections = await firstValueFrom(
collectionService.decryptedCollections$(userId)
);
const tree = collectionService.getAllNested(collections);
function printTree(nodes: TreeNode<CollectionView>[], depth = 0) {
nodes.forEach(node => {
console.log(`${' '.repeat(depth)}${node.node.name}`);
if (node.children) {
printTree(node.children, depth + 1);
}
});
}
printTree(tree);
// Output:
// Engineering
// Backend
// Frontend
// Marketing
getNested
Retrieves a specific collection as a tree node with its hierarchy.
getNested(
collections: CollectionView[],
id: string
): TreeNode<CollectionView>
Parameters:
collections: CollectionView[] - Flat array of collections
id: string - The collection ID to find
Returns: Tree node containing the collection and its position in the hierarchy
Deprecated as of August 30, 2022. Moved to Vault Filter Service. Will be removed when Desktop and Browser are updated.
groupByOrganization
Groups collections by their organization ID.
groupByOrganization(
collections: CollectionView[]
): Map<OrganizationId, CollectionView[]>
Parameters:
collections: CollectionView[] - Array of collections to group
Returns: Map with organization IDs as keys and arrays of collections as values
Example:
const collections = await firstValueFrom(
collectionService.decryptedCollections$(userId)
);
const grouped = collectionService.groupByOrganization(collections);
grouped.forEach((collections, orgId) => {
console.log(`Organization ${orgId}: ${collections.length} collections`);
});
Collection Types
CollectionView
Decrypted collection view with permissions.
class CollectionView {
id: CollectionId;
organizationId: OrganizationId;
externalId?: string;
name: string;
readOnly: boolean;
hidePasswords: boolean;
manage: boolean;
assigned: boolean;
type: CollectionType;
defaultUserCollectionEmail?: string;
// Permission methods
canEditItems(org: Organization): boolean;
canEdit(org: Organization | undefined): boolean;
canDelete(org: Organization | undefined): boolean;
}
Properties:
id: CollectionId - Unique collection identifier
organizationId: OrganizationId - Parent organization ID
name: string - Collection name (supports / for nesting)
readOnly: boolean - If true, items cannot be edited
hidePasswords: boolean - If true, passwords are hidden from view
manage: boolean - If true, user can manage the collection
assigned: boolean - If true, user is directly assigned to collection
type: CollectionType - Collection type (SharedCollection, DefaultUserCollection, etc.)
Permission Methods:
canEditItems
Checks if user can edit items within the collection.
canEditItems(org: Organization): boolean
Returns: true if user can edit items based on:
- Organization
canEditAllCiphers permission
- Collection
manage permission
- Collection
assigned and not readOnly
canEdit
Checks if user can edit the collection itself (permissions, name, etc.).
canEdit(org: Organization | undefined): boolean
Returns: true if user has manage permission and it’s not a default collection
canDelete
Checks if user can delete the collection from individual vault.
canDelete(org: Organization | undefined): boolean
Note: Does not include admin permissions. See CollectionAdminView.canDelete for admin access.
Collection
Encrypted collection domain object.
class Collection {
id: CollectionId;
organizationId: OrganizationId;
name: EncString;
externalId?: string;
readOnly: boolean;
hidePasswords: boolean;
static fromCollectionData(data: CollectionData): Collection;
}
CollectionData
Raw collection data for storage.
class CollectionData {
id: CollectionId;
organizationId: OrganizationId;
name: string; // Encrypted string representation
externalId?: string;
readOnly: boolean;
hidePasswords: boolean;
manage: boolean;
}
Collection Types Enum
enum CollectionTypes {
SharedCollection = "SharedCollection",
DefaultUserCollection = "DefaultUserCollection"
}
SharedCollection: Standard organization collection shared among members
DefaultUserCollection: Special collection automatically created for new organization members
Usage Examples
Listing User Collections
import { firstValueFrom } from 'rxjs';
// Get all decrypted collections
const collections = await firstValueFrom(
collectionService.decryptedCollections$(userId)
);
// Filter to specific organization
const orgCollections = collections.filter(
c => c.organizationId === targetOrgId
);
console.log(`Found ${orgCollections.length} collections`);
Creating Nested Collection Structure
// Collections with nested names
const collections = [
{ name: "Engineering" },
{ name: "Engineering/Backend" },
{ name: "Engineering/Frontend" },
{ name: "Marketing" },
];
// Get nested tree
const tree = collectionService.getAllNested(collectionViews);
// Tree structure:
// - Engineering
// - Backend
// - Frontend
// - Marketing
Checking Collection Permissions
const collection = collections.find(c => c.id === collectionId);
const org = await organizationService.get(collection.organizationId);
if (collection.canEditItems(org)) {
console.log("User can edit items in this collection");
}
if (collection.canEdit(org)) {
console.log("User can edit the collection itself");
}
if (collection.manage) {
console.log("User can manage collection permissions");
}
Filtering Collections by Organization
const grouped = collectionService.groupByOrganization(collections);
for (const [orgId, orgCollections] of grouped) {
console.log(`\nOrganization: ${orgId}`);
orgCollections.forEach(c => {
const access = c.readOnly ? "read-only" : "read-write";
console.log(` - ${c.name} (${access})`);
});
}
Assigning Cipher to Collections
// Get cipher
const cipher = await cipherService.get(cipherId, userId);
// Assign to collections
cipher.collectionIds = [collection1Id, collection2Id];
// Save collections
await cipherService.saveCollectionsWithServer(cipher, userId);
Implementation Notes
Nesting Delimiter
The service uses / as the nesting delimiter:
const NestingDelimiter = "/";
Collection names like "Parent/Child/Grandchild" create a 3-level hierarchy.
Observable Caching
The service caches decrypted collection observables per user:
private collectionViewCache = new Map<UserId, Observable<CollectionView[]>>();
This prevents redundant decryption operations.
Automatic Decryption
When accessing decryptedCollections$, the service:
- Checks if decrypted state exists
- If not, fetches encrypted collections and org keys
- Decrypts all collections with appropriate org keys
- Caches the result
- Emits sorted collection views
- Cipher Service - Manages items that can be assigned to collections
- Folder Service - Personal organization (vs. org collections)
- Organization Service - Manages organization data and permissions
See Also
- Collection Admin Service - Extended permissions for organization admins
- Vault Filter Service - Filtering and searching across collections