1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 21:50:15 +00:00
Files
browser/libs/common/src/vault/services/folder/folder.service.ts
2023-10-02 16:54:35 -04:00

163 lines
5.5 KiB
TypeScript

import { Observable, firstValueFrom, map } from "rxjs";
import { ActiveUserStateProvider } from "../../../platform/abstractions/active-user-state.provider";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { StateService } from "../../../platform/abstractions/state.service";
import { ActiveUserState } from "../../../platform/interfaces/active-user-state";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { DerivedActiveUserState } from "../../../platform/services/default-active-user-state.provider";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { InternalFolderService as InternalFolderServiceAbstraction } from "../../../vault/abstractions/folder/folder.service.abstraction";
import { CipherData } from "../../../vault/models/data/cipher.data";
import { FolderData } from "../../../vault/models/data/folder.data";
import { Folder } from "../../../vault/models/domain/folder";
import { FolderView } from "../../../vault/models/view/folder.view";
import { FOLDERS } from "../../types/key-definitions";
export class FolderService implements InternalFolderServiceAbstraction {
folderState: ActiveUserState<Record<string, FolderData>>;
decryptedFolderState: DerivedActiveUserState<Record<string, FolderData>, FolderView[]>;
folders$: Observable<Folder[]>;
folderViews$: Observable<FolderView[]>;
constructor(
private cryptoService: CryptoService,
private i18nService: I18nService,
private cipherService: CipherService,
private activeUserStateProvider: ActiveUserStateProvider,
private stateService: StateService
) {
const derivedFoldersDefinition = FOLDERS.createDerivedDefinition(
"memory",
async (foldersMap) => {
const folders = this.flattenMap(foldersMap);
const decryptedFolders = await this.decryptFolders(folders);
return decryptedFolders;
}
);
this.folderState = this.activeUserStateProvider.create(FOLDERS);
this.folders$ = this.folderState.state$.pipe(
map((foldersMap) => {
return this.flattenMap(foldersMap);
})
);
this.decryptedFolderState = this.folderState.createDerived(derivedFoldersDefinition);
this.folderViews$ = this.decryptedFolderState.state$;
}
async clearCache(): Promise<void> {
// TODO: I don't really have a replacement for this right now
// this._folderViews.next([]);
}
// TODO: This should be moved to EncryptService or something
async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise<Folder> {
const folder = new Folder();
folder.id = model.id;
folder.name = await this.cryptoService.encrypt(model.name, key);
return folder;
}
async get(id: string): Promise<Folder> {
const folders = await firstValueFrom(this.folderState.state$);
return new Folder(folders[id]);
}
async getAllFromState(): Promise<Folder[]> {
const foldersMap = await this.folderState.getFromState();
return this.flattenMap(foldersMap);
}
/**
* @deprecated For the CLI only
* @param id id of the folder
*/
async getFromState(id: string): Promise<Folder> {
const foldersMap = await this.folderState.getFromState();
const folder = foldersMap[id];
if (folder == null) {
return null;
}
return new Folder(folder);
}
/**
* @deprecated Only use in CLI!
*/
async getAllDecryptedFromState(): Promise<FolderView[]> {
return await this.decryptedFolderState.getFromState();
}
async upsert(folder: FolderData | FolderData[]): Promise<void> {
await this.folderState.update((folders) => {
if (folder instanceof FolderData) {
folders[folder.id] = folder;
} else {
(folder as FolderData[]).forEach((f) => {
folders[f.id] = f;
});
}
});
}
async replace(folders: { [id: string]: FolderData }): Promise<void> {
await this.folderState.update((f) => (f = folders));
}
async clear(userId?: string): Promise<any> {
await this.folderState.update((f) => (f = null));
}
async delete(id: string | string[]): Promise<void> {
const folderIds = typeof id === "string" ? [id] : id;
await this.folderState.update((folders) => {
for (const folderId in folderIds) {
delete folders[folderId];
}
});
// Items in a deleted folder are re-assigned to "No Folder"
const ciphers = await this.stateService.getEncryptedCiphers();
if (ciphers != null) {
const updates: CipherData[] = [];
for (const cId in ciphers) {
if (ciphers[cId].folderId === id) {
ciphers[cId].folderId = null;
updates.push(ciphers[cId]);
}
}
if (updates.length > 0) {
this.cipherService.upsert(updates);
}
}
}
private async decryptFolders(folders: Folder[]) {
const decryptFolderPromises = folders.map((f) => f.decrypt());
const decryptedFolders = await Promise.all(decryptFolderPromises);
decryptedFolders.sort(Utils.getSortFunction(this.i18nService, "name"));
const noneFolder = new FolderView();
noneFolder.name = this.i18nService.t("noneFolder");
decryptedFolders.push(noneFolder);
return decryptedFolders;
}
private flattenMap(foldersMap: Record<string, FolderData>): Folder[] {
const folders: Folder[] = [];
for (const id in foldersMap) {
folders.push(new Folder(foldersMap[id]));
}
return folders;
}
}