1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[PM-5273] Migrate state in CipherService (#8314)

* PM-5273 Initial migration work for localData

* PM-5273 Encrypted and Decrypted ciphers migration to state provider

* pm-5273 Update references

* pm5273 Ensure prototype on cipher

* PM-5273 Add CipherId

* PM-5273 Remove migrated methods and updated references

* pm-5273 Fix versions

* PM-5273 Added missing options

* Conflict resolution

* Revert "Conflict resolution"

This reverts commit 0c0c2039ed.

* PM-5273 Fix PR comments

* Pm-5273 Fix comments

* PM-5273 Changed decryptedCiphers to use ActiveUserState

* PM-5273 Fix tests

* PM-5273 Fix pr comments
This commit is contained in:
Carlos Gonçalves
2024-04-16 17:37:03 +01:00
committed by GitHub
parent 62ed7e5abc
commit 06acdefa91
29 changed files with 525 additions and 305 deletions

View File

@@ -1,3 +1,5 @@
import { Observable } from "rxjs";
import { UriMatchStrategySetting } from "../../models/domain/domain-service";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { CipherId, CollectionId, OrganizationId } from "../../types/guid";
@@ -7,8 +9,13 @@ import { Cipher } from "../models/domain/cipher";
import { Field } from "../models/domain/field";
import { CipherView } from "../models/view/cipher.view";
import { FieldView } from "../models/view/field.view";
import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
export abstract class CipherService {
/**
* An observable monitoring the add/edit cipher info saved to memory.
*/
addEditCipherInfo$: Observable<AddEditCipherInfo>;
clearCache: (userId?: string) => Promise<void>;
encrypt: (
model: CipherView,
@@ -102,4 +109,5 @@ export abstract class CipherService {
asAdmin?: boolean,
) => Promise<void>;
getKeyForCipherKeyDecryption: (cipher: Cipher) => Promise<any>;
setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise<void>;
}

View File

@@ -1,3 +1,5 @@
import { Jsonify } from "type-fest";
import { CipherRepromptType } from "../../enums/cipher-reprompt-type";
import { CipherType } from "../../enums/cipher-type";
import { CipherResponse } from "../response/cipher.response";
@@ -84,4 +86,8 @@ export class CipherData {
this.passwordHistory = response.passwordHistory.map((ph) => new PasswordHistoryData(ph));
}
}
static fromJSON(obj: Jsonify<CipherData>) {
return Object.assign(new CipherData(), obj);
}
}

View File

@@ -1,6 +1,8 @@
import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service";
import { FakeStateProvider } from "../../../spec/fake-state-provider";
import { makeStaticByteArray } from "../../../spec/utils";
import { ApiService } from "../../abstractions/api.service";
import { SearchService } from "../../abstractions/search.service";
@@ -12,10 +14,12 @@ import { CryptoService } from "../../platform/abstractions/crypto.service";
import { EncryptService } from "../../platform/abstractions/encrypt.service";
import { I18nService } from "../../platform/abstractions/i18n.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
import { EncString } from "../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { ContainerService } from "../../platform/services/container.service";
import { UserId } from "../../types/guid";
import { CipherKey, OrgKey } from "../../types/key";
import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service";
import { FieldType } from "../enums";
@@ -97,6 +101,8 @@ const cipherData: CipherData = {
},
],
};
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
describe("Cipher Service", () => {
const cryptoService = mock<CryptoService>();
@@ -109,6 +115,8 @@ describe("Cipher Service", () => {
const searchService = mock<SearchService>();
const encryptService = mock<EncryptService>();
const configService = mock<ConfigService>();
accountService = mockAccountServiceWith(mockUserId);
const stateProvider = new FakeStateProvider(accountService);
let cipherService: CipherService;
let cipherObj: Cipher;
@@ -130,6 +138,7 @@ describe("Cipher Service", () => {
encryptService,
cipherFileUploadService,
configService,
stateProvider,
);
cipherObj = new Cipher(cipherData);

View File

@@ -1,4 +1,4 @@
import { firstValueFrom } from "rxjs";
import { Observable, firstValueFrom } from "rxjs";
import { SemVer } from "semver";
import { ApiService } from "../../abstractions/api.service";
@@ -21,13 +21,15 @@ import Domain from "../../platform/models/domain/domain-base";
import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer";
import { EncString } from "../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { ActiveUserState, StateProvider } from "../../platform/state";
import { CipherId, CollectionId, OrganizationId } from "../../types/guid";
import { OrgKey, UserKey } from "../../types/key";
import { UserKey, OrgKey } from "../../types/key";
import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service";
import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service";
import { FieldType } from "../enums";
import { CipherType } from "../enums/cipher-type";
import { CipherData } from "../models/data/cipher.data";
import { LocalData } from "../models/data/local.data";
import { Attachment } from "../models/domain/attachment";
import { Card } from "../models/domain/card";
import { Cipher } from "../models/domain/cipher";
@@ -54,6 +56,14 @@ import { AttachmentView } from "../models/view/attachment.view";
import { CipherView } from "../models/view/cipher.view";
import { FieldView } from "../models/view/field.view";
import { PasswordHistoryView } from "../models/view/password-history.view";
import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
import {
ENCRYPTED_CIPHERS,
LOCAL_DATA_KEY,
ADD_EDIT_CIPHER_INFO_KEY,
DECRYPTED_CIPHERS,
} from "./key-state/ciphers.state";
const CIPHER_KEY_ENC_MIN_SERVER_VER = new SemVer("2024.2.0");
@@ -62,6 +72,16 @@ export class CipherService implements CipherServiceAbstraction {
this.sortCiphersByLastUsed,
);
localData$: Observable<Record<CipherId, LocalData>>;
ciphers$: Observable<Record<CipherId, CipherData>>;
cipherViews$: Observable<Record<CipherId, CipherView>>;
addEditCipherInfo$: Observable<AddEditCipherInfo>;
private localDataState: ActiveUserState<Record<CipherId, LocalData>>;
private encryptedCiphersState: ActiveUserState<Record<CipherId, CipherData>>;
private decryptedCiphersState: ActiveUserState<Record<CipherId, CipherView>>;
private addEditCipherInfoState: ActiveUserState<AddEditCipherInfo>;
constructor(
private cryptoService: CryptoService,
private domainSettingsService: DomainSettingsService,
@@ -73,11 +93,17 @@ export class CipherService implements CipherServiceAbstraction {
private encryptService: EncryptService,
private cipherFileUploadService: CipherFileUploadService,
private configService: ConfigService,
) {}
private stateProvider: StateProvider,
) {
this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY);
this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS);
this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS);
this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY);
async getDecryptedCipherCache(): Promise<CipherView[]> {
const decryptedCiphers = await this.stateService.getDecryptedCiphers();
return decryptedCiphers;
this.localData$ = this.localDataState.state$;
this.ciphers$ = this.encryptedCiphersState.state$;
this.cipherViews$ = this.decryptedCiphersState.state$;
this.addEditCipherInfo$ = this.addEditCipherInfoState.state$;
}
async setDecryptedCipherCache(value: CipherView[]) {
@@ -85,7 +111,7 @@ export class CipherService implements CipherServiceAbstraction {
// if we cache it then we may accidentially return it when it's not right, we'd rather try decryption again.
// We still want to set null though, that is the indicator that the cache isn't valid and we should do decryption.
if (value == null || value.length !== 0) {
await this.stateService.setDecryptedCiphers(value);
await this.setDecryptedCiphers(value);
}
if (this.searchService != null) {
if (value == null) {
@@ -96,6 +122,14 @@ export class CipherService implements CipherServiceAbstraction {
}
}
private async setDecryptedCiphers(value: CipherView[]) {
const cipherViews: { [id: string]: CipherView } = {};
value?.forEach((c) => {
cipherViews[c.id] = c;
});
await this.decryptedCiphersState.update(() => cipherViews);
}
async clearCache(userId?: string): Promise<void> {
await this.clearDecryptedCiphersState(userId);
}
@@ -268,24 +302,27 @@ export class CipherService implements CipherServiceAbstraction {
}
async get(id: string): Promise<Cipher> {
const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await firstValueFrom(this.ciphers$);
// eslint-disable-next-line
if (ciphers == null || !ciphers.hasOwnProperty(id)) {
return null;
}
const localData = await this.stateService.getLocalData();
return new Cipher(ciphers[id], localData ? localData[id] : null);
const localData = await firstValueFrom(this.localData$);
const cipherId = id as CipherId;
return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null);
}
async getAll(): Promise<Cipher[]> {
const localData = await this.stateService.getLocalData();
const ciphers = await this.stateService.getEncryptedCiphers();
const localData = await firstValueFrom(this.localData$);
const ciphers = await firstValueFrom(this.ciphers$);
const response: Cipher[] = [];
for (const id in ciphers) {
// eslint-disable-next-line
if (ciphers.hasOwnProperty(id)) {
response.push(new Cipher(ciphers[id], localData ? localData[id] : null));
const cipherId = id as CipherId;
response.push(new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null));
}
}
return response;
@@ -293,12 +330,23 @@ export class CipherService implements CipherServiceAbstraction {
@sequentialize(() => "getAllDecrypted")
async getAllDecrypted(): Promise<CipherView[]> {
if ((await this.getDecryptedCipherCache()) != null) {
let decCiphers = await this.getDecryptedCiphers();
if (decCiphers != null && decCiphers.length !== 0) {
await this.reindexCiphers();
return await this.getDecryptedCipherCache();
return await this.getDecryptedCiphers();
}
const ciphers = await this.getAll();
decCiphers = await this.decryptCiphers(await this.getAll());
await this.setDecryptedCipherCache(decCiphers);
return decCiphers;
}
private async getDecryptedCiphers() {
return Object.values(await firstValueFrom(this.cipherViews$));
}
private async decryptCiphers(ciphers: Cipher[]) {
const orgKeys = await this.cryptoService.getOrgKeys();
const userKey = await this.cryptoService.getUserKeyWithLegacySupport();
if (Object.keys(orgKeys).length === 0 && userKey == null) {
@@ -326,7 +374,6 @@ export class CipherService implements CipherServiceAbstraction {
.flat()
.sort(this.getLocaleSortingFunction());
await this.setDecryptedCipherCache(decCiphers);
return decCiphers;
}
@@ -336,7 +383,7 @@ export class CipherService implements CipherServiceAbstraction {
this.searchService != null &&
((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId;
if (reindexRequired) {
await this.searchService.indexCiphers(await this.getDecryptedCipherCache(), userId);
await this.searchService.indexCiphers(await this.getDecryptedCiphers(), userId);
}
}
@@ -448,22 +495,24 @@ export class CipherService implements CipherServiceAbstraction {
}
async updateLastUsedDate(id: string): Promise<void> {
let ciphersLocalData = await this.stateService.getLocalData();
let ciphersLocalData = await firstValueFrom(this.localData$);
if (!ciphersLocalData) {
ciphersLocalData = {};
}
if (ciphersLocalData[id]) {
ciphersLocalData[id].lastUsedDate = new Date().getTime();
const cipherId = id as CipherId;
if (ciphersLocalData[cipherId]) {
ciphersLocalData[cipherId].lastUsedDate = new Date().getTime();
} else {
ciphersLocalData[id] = {
ciphersLocalData[cipherId] = {
lastUsedDate: new Date().getTime(),
};
}
await this.stateService.setLocalData(ciphersLocalData);
await this.localDataState.update(() => ciphersLocalData);
const decryptedCipherCache = await this.stateService.getDecryptedCiphers();
const decryptedCipherCache = await this.getDecryptedCiphers();
if (!decryptedCipherCache) {
return;
}
@@ -471,30 +520,32 @@ export class CipherService implements CipherServiceAbstraction {
for (let i = 0; i < decryptedCipherCache.length; i++) {
const cached = decryptedCipherCache[i];
if (cached.id === id) {
cached.localData = ciphersLocalData[id];
cached.localData = ciphersLocalData[id as CipherId];
break;
}
}
await this.stateService.setDecryptedCiphers(decryptedCipherCache);
await this.setDecryptedCiphers(decryptedCipherCache);
}
async updateLastLaunchedDate(id: string): Promise<void> {
let ciphersLocalData = await this.stateService.getLocalData();
let ciphersLocalData = await firstValueFrom(this.localData$);
if (!ciphersLocalData) {
ciphersLocalData = {};
}
if (ciphersLocalData[id]) {
ciphersLocalData[id].lastLaunched = new Date().getTime();
const cipherId = id as CipherId;
if (ciphersLocalData[cipherId]) {
ciphersLocalData[cipherId].lastLaunched = new Date().getTime();
} else {
ciphersLocalData[id] = {
ciphersLocalData[cipherId] = {
lastUsedDate: new Date().getTime(),
};
}
await this.stateService.setLocalData(ciphersLocalData);
await this.localDataState.update(() => ciphersLocalData);
const decryptedCipherCache = await this.stateService.getDecryptedCiphers();
const decryptedCipherCache = await this.getDecryptedCiphers();
if (!decryptedCipherCache) {
return;
}
@@ -502,11 +553,11 @@ export class CipherService implements CipherServiceAbstraction {
for (let i = 0; i < decryptedCipherCache.length; i++) {
const cached = decryptedCipherCache[i];
if (cached.id === id) {
cached.localData = ciphersLocalData[id];
cached.localData = ciphersLocalData[id as CipherId];
break;
}
}
await this.stateService.setDecryptedCiphers(decryptedCipherCache);
await this.setDecryptedCiphers(decryptedCipherCache);
}
async saveNeverDomain(domain: string): Promise<void> {
@@ -711,7 +762,7 @@ export class CipherService implements CipherServiceAbstraction {
await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false);
// Update the local state
const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await firstValueFrom(this.ciphers$);
for (const id of cipherIds) {
const cipher = ciphers[id];
@@ -728,30 +779,29 @@ export class CipherService implements CipherServiceAbstraction {
}
await this.clearCache();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update(() => ciphers);
}
async upsert(cipher: CipherData | CipherData[]): Promise<any> {
let ciphers = await this.stateService.getEncryptedCiphers();
if (ciphers == null) {
ciphers = {};
}
if (cipher instanceof CipherData) {
const c = cipher as CipherData;
ciphers[c.id] = c;
} else {
(cipher as CipherData[]).forEach((c) => {
ciphers[c.id] = c;
});
}
await this.replace(ciphers);
const ciphers = cipher instanceof CipherData ? [cipher] : cipher;
await this.updateEncryptedCipherState((current) => {
ciphers.forEach((c) => current[c.id as CipherId]);
return current;
});
}
async replace(ciphers: { [id: string]: CipherData }): Promise<any> {
await this.updateEncryptedCipherState(() => ciphers);
}
private async updateEncryptedCipherState(
update: (current: Record<CipherId, CipherData>) => Record<CipherId, CipherData>,
) {
await this.clearDecryptedCiphersState();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update((current) => {
const result = update(current ?? {});
return result;
});
}
async clear(userId?: string): Promise<any> {
@@ -762,7 +812,7 @@ export class CipherService implements CipherServiceAbstraction {
async moveManyWithServer(ids: string[], folderId: string): Promise<any> {
await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId));
let ciphers = await this.stateService.getEncryptedCiphers();
let ciphers = await firstValueFrom(this.ciphers$);
if (ciphers == null) {
ciphers = {};
}
@@ -770,33 +820,34 @@ export class CipherService implements CipherServiceAbstraction {
ids.forEach((id) => {
// eslint-disable-next-line
if (ciphers.hasOwnProperty(id)) {
ciphers[id].folderId = folderId;
ciphers[id as CipherId].folderId = folderId;
}
});
await this.clearCache();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update(() => ciphers);
}
async delete(id: string | string[]): Promise<any> {
const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await firstValueFrom(this.ciphers$);
if (ciphers == null) {
return;
}
if (typeof id === "string") {
if (ciphers[id] == null) {
const cipherId = id as CipherId;
if (ciphers[cipherId] == null) {
return;
}
delete ciphers[id];
delete ciphers[cipherId];
} else {
(id as string[]).forEach((i) => {
(id as CipherId[]).forEach((i) => {
delete ciphers[i];
});
}
await this.clearCache();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update(() => ciphers);
}
async deleteWithServer(id: string, asAdmin = false): Promise<any> {
@@ -820,21 +871,26 @@ export class CipherService implements CipherServiceAbstraction {
}
async deleteAttachment(id: string, attachmentId: string): Promise<void> {
const ciphers = await this.stateService.getEncryptedCiphers();
let ciphers = await firstValueFrom(this.ciphers$);
const cipherId = id as CipherId;
// eslint-disable-next-line
if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) {
if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) {
return;
}
for (let i = 0; i < ciphers[id].attachments.length; i++) {
if (ciphers[id].attachments[i].id === attachmentId) {
ciphers[id].attachments.splice(i, 1);
for (let i = 0; i < ciphers[cipherId].attachments.length; i++) {
if (ciphers[cipherId].attachments[i].id === attachmentId) {
ciphers[cipherId].attachments.splice(i, 1);
}
}
await this.clearCache();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update(() => {
if (ciphers == null) {
ciphers = {};
}
return ciphers;
});
}
async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<void> {
@@ -917,12 +973,12 @@ export class CipherService implements CipherServiceAbstraction {
}
async softDelete(id: string | string[]): Promise<any> {
const ciphers = await this.stateService.getEncryptedCiphers();
let ciphers = await firstValueFrom(this.ciphers$);
if (ciphers == null) {
return;
}
const setDeletedDate = (cipherId: string) => {
const setDeletedDate = (cipherId: CipherId) => {
if (ciphers[cipherId] == null) {
return;
}
@@ -930,13 +986,18 @@ export class CipherService implements CipherServiceAbstraction {
};
if (typeof id === "string") {
setDeletedDate(id);
setDeletedDate(id as CipherId);
} else {
(id as string[]).forEach(setDeletedDate);
}
await this.clearCache();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update(() => {
if (ciphers == null) {
ciphers = {};
}
return ciphers;
});
}
async softDeleteWithServer(id: string, asAdmin = false): Promise<any> {
@@ -963,17 +1024,18 @@ export class CipherService implements CipherServiceAbstraction {
async restore(
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
) {
const ciphers = await this.stateService.getEncryptedCiphers();
let ciphers = await firstValueFrom(this.ciphers$);
if (ciphers == null) {
return;
}
const clearDeletedDate = (c: { id: string; revisionDate: string }) => {
if (ciphers[c.id] == null) {
const cipherId = c.id as CipherId;
if (ciphers[cipherId] == null) {
return;
}
ciphers[c.id].deletedDate = null;
ciphers[c.id].revisionDate = c.revisionDate;
ciphers[cipherId].deletedDate = null;
ciphers[cipherId].revisionDate = c.revisionDate;
};
if (cipher.constructor.name === Array.name) {
@@ -983,7 +1045,12 @@ export class CipherService implements CipherServiceAbstraction {
}
await this.clearCache();
await this.stateService.setEncryptedCiphers(ciphers);
await this.encryptedCiphersState.update(() => {
if (ciphers == null) {
ciphers = {};
}
return ciphers;
});
}
async restoreWithServer(id: string, asAdmin = false): Promise<any> {
@@ -1025,6 +1092,10 @@ export class CipherService implements CipherServiceAbstraction {
);
}
async setAddEditCipherInfo(value: AddEditCipherInfo) {
await this.addEditCipherInfoState.update(() => value);
}
// Helpers
// In the case of a cipher that is being shared with an organization, we want to decrypt the
@@ -1350,11 +1421,11 @@ export class CipherService implements CipherServiceAbstraction {
}
private async clearEncryptedCiphersState(userId?: string) {
await this.stateService.setEncryptedCiphers(null, { userId: userId });
await this.encryptedCiphersState.update(() => ({}));
}
private async clearDecryptedCiphersState(userId?: string) {
await this.stateService.setDecryptedCiphers(null, { userId: userId });
await this.setDecryptedCiphers(null);
this.clearSortedCiphers();
}

View File

@@ -8,7 +8,6 @@ import { FakeStateProvider } from "../../../../spec/fake-state-provider";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { StateService } from "../../../platform/abstractions/state.service";
import { Utils } from "../../../platform/misc/utils";
import { EncString } from "../../../platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
@@ -27,7 +26,6 @@ describe("Folder Service", () => {
let encryptService: MockProxy<EncryptService>;
let i18nService: MockProxy<I18nService>;
let cipherService: MockProxy<CipherService>;
let stateService: MockProxy<StateService>;
let stateProvider: FakeStateProvider;
const mockUserId = Utils.newGuid() as UserId;
@@ -39,7 +37,6 @@ describe("Folder Service", () => {
encryptService = mock<EncryptService>();
i18nService = mock<I18nService>();
cipherService = mock<CipherService>();
stateService = mock<StateService>();
accountService = mockAccountServiceWith(mockUserId);
stateProvider = new FakeStateProvider(accountService);
@@ -52,13 +49,7 @@ describe("Folder Service", () => {
);
encryptService.decryptToUtf8.mockResolvedValue("DEC");
folderService = new FolderService(
cryptoService,
i18nService,
cipherService,
stateService,
stateProvider,
);
folderService = new FolderService(cryptoService, i18nService, cipherService, stateProvider);
folderState = stateProvider.activeUser.getFake(FOLDER_ENCRYPTED_FOLDERS);

View File

@@ -2,17 +2,16 @@ import { Observable, firstValueFrom, map } from "rxjs";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { StateService } from "../../../platform/abstractions/state.service";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { ActiveUserState, DerivedState, StateProvider } from "../../../platform/state";
import { UserId } from "../../../types/guid";
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 { Cipher } from "../../models/domain/cipher";
import { FOLDER_DECRYPTED_FOLDERS, FOLDER_ENCRYPTED_FOLDERS } from "../key-state/folder.state";
export class FolderService implements InternalFolderServiceAbstraction {
@@ -26,7 +25,6 @@ export class FolderService implements InternalFolderServiceAbstraction {
private cryptoService: CryptoService,
private i18nService: I18nService,
private cipherService: CipherService,
private stateService: StateService,
private stateProvider: StateProvider,
) {
this.encryptedFoldersState = this.stateProvider.getActive(FOLDER_ENCRYPTED_FOLDERS);
@@ -144,9 +142,9 @@ export class FolderService implements InternalFolderServiceAbstraction {
});
// Items in a deleted folder are re-assigned to "No Folder"
const ciphers = await this.stateService.getEncryptedCiphers();
const ciphers = await this.cipherService.getAll();
if (ciphers != null) {
const updates: CipherData[] = [];
const updates: Cipher[] = [];
for (const cId in ciphers) {
if (ciphers[cId].folderId === id) {
ciphers[cId].folderId = null;
@@ -156,7 +154,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
if (updates.length > 0) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.cipherService.upsert(updates);
this.cipherService.upsert(updates.map((c) => c.toCipherData()));
}
}
}

View File

@@ -0,0 +1,52 @@
import { Jsonify } from "type-fest";
import {
CIPHERS_DISK,
CIPHERS_DISK_LOCAL,
CIPHERS_MEMORY,
KeyDefinition,
} from "../../../platform/state";
import { CipherId } from "../../../types/guid";
import { CipherData } from "../../models/data/cipher.data";
import { LocalData } from "../../models/data/local.data";
import { CipherView } from "../../models/view/cipher.view";
import { AddEditCipherInfo } from "../../types/add-edit-cipher-info";
export const ENCRYPTED_CIPHERS = KeyDefinition.record<CipherData>(CIPHERS_DISK, "ciphers", {
deserializer: (obj: Jsonify<CipherData>) => CipherData.fromJSON(obj),
});
export const DECRYPTED_CIPHERS = KeyDefinition.record<CipherView>(
CIPHERS_MEMORY,
"decryptedCiphers",
{
deserializer: (cipher: Jsonify<CipherView>) => CipherView.fromJSON(cipher),
},
);
export const LOCAL_DATA_KEY = new KeyDefinition<Record<CipherId, LocalData>>(
CIPHERS_DISK_LOCAL,
"localData",
{
deserializer: (localData) => localData,
},
);
export const ADD_EDIT_CIPHER_INFO_KEY = new KeyDefinition<AddEditCipherInfo>(
CIPHERS_MEMORY,
"addEditCipherInfo",
{
deserializer: (addEditCipherInfo: AddEditCipherInfo) => {
if (addEditCipherInfo == null) {
return null;
}
const cipher =
addEditCipherInfo?.cipher.toJSON != null
? addEditCipherInfo.cipher
: CipherView.fromJSON(addEditCipherInfo?.cipher as Jsonify<CipherView>);
return { cipher, collectionIds: addEditCipherInfo.collectionIds };
},
},
);