1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00

[PM-12047] Remove usage of ActiveUserState from cipher.service (#12814)

* Cipher service web changes

* Updated browser client to pass user id to cipher service observable changes

* Cli changes

* desktop changes

* Fixed test

* Libs changes

* Fixed merge conflicts

* Fixed merge conflicts

* removed duplicate reference fixed conflict

* Fixed test

* Fixed test

* Fixed test

* Fixed desturcturing issue on failed to decrypt ciphers cipher service

* Updated abstraction to use method syntax

* Fixed conflicts

* Fixed test on add edit v2

Passed active userId to delete function

* Used getUserId utility function

* made vault changes

* made suggestion changes

* made suggestion changes

* made suggestion changes

* Replace getUserId function calls with pipe operator syntax for better consistency

* fixed merge conflicts

* revert mistake made of usinf account activity during merge conflict fix

* fixed conflicts

* fixed tests
This commit is contained in:
SmithThe4th
2025-02-12 08:53:31 -05:00
committed by GitHub
parent e45ef6b924
commit a2945203f4
98 changed files with 1174 additions and 725 deletions

View File

@@ -151,11 +151,15 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract
await this.syncService.syncUpsertCipher(
notification.payload as SyncCipherNotification,
notification.type === NotificationType.SyncCipherUpdate,
payloadUserId,
);
break;
case NotificationType.SyncCipherDelete:
case NotificationType.SyncLoginDelete:
await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification);
await this.syncService.syncDeleteCipher(
notification.payload as SyncCipherNotification,
payloadUserId,
);
break;
case NotificationType.SyncFolderCreate:
case NotificationType.SyncFolderUpdate:

View File

@@ -3,7 +3,8 @@ import { TextEncoder } from "util";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject, of } from "rxjs";
import { Account, AccountService } from "../../../auth/abstractions/account.service";
import { mockAccountServiceWith } from "../../../../spec";
import { Account } from "../../../auth/abstractions/account.service";
import { UserId } from "../../../types/guid";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
@@ -46,7 +47,6 @@ describe("FidoAuthenticatorService", () => {
let userInterface!: MockProxy<Fido2UserInterfaceService<ParentWindowReference>>;
let userInterfaceSession!: MockProxy<Fido2UserInterfaceSession>;
let syncService!: MockProxy<SyncService>;
let accountService!: MockProxy<AccountService>;
let authenticator!: Fido2AuthenticatorService<ParentWindowReference>;
let windowReference!: ParentWindowReference;
@@ -58,7 +58,7 @@ describe("FidoAuthenticatorService", () => {
syncService = mock<SyncService>({
activeUserLastSync$: () => of(new Date()),
});
accountService = mock<AccountService>();
const accountService = mockAccountServiceWith("testId" as UserId);
authenticator = new Fido2AuthenticatorService(
cipherService,
userInterface,

View File

@@ -1,8 +1,9 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom, map } from "rxjs";
import { firstValueFrom } from "rxjs";
import { AccountService } from "../../../auth/abstractions/account.service";
import { getUserId } from "../../../auth/services/account.service";
import { CipherService } from "../../../vault/abstractions/cipher.service";
import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction";
import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type";
@@ -145,10 +146,10 @@ export class Fido2AuthenticatorService<ParentWindowReference>
try {
keyPair = await createKeyPair();
pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
const encrypted = await this.cipherService.get(cipherId);
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
this.accountService.activeAccount$.pipe(getUserId),
);
const encrypted = await this.cipherService.get(cipherId, activeUserId);
cipher = await encrypted.decrypt(
await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId),
@@ -309,7 +310,7 @@ export class Fido2AuthenticatorService<ParentWindowReference>
if (selectedFido2Credential.counter > 0) {
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
this.accountService.activeAccount$.pipe(getUserId),
);
const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId);
await this.cipherService.updateWithServer(encrypted);
@@ -400,7 +401,8 @@ export class Fido2AuthenticatorService<ParentWindowReference>
return [];
}
const ciphers = await this.cipherService.getAllDecrypted();
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
return ciphers
.filter(
(cipher) =>
@@ -421,7 +423,8 @@ export class Fido2AuthenticatorService<ParentWindowReference>
return [];
}
const ciphers = await this.cipherService.getAllDecrypted();
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
return ciphers.filter(
(cipher) =>
!cipher.isDeleted &&
@@ -438,7 +441,8 @@ export class Fido2AuthenticatorService<ParentWindowReference>
}
private async findCredentialsByRp(rpId: string): Promise<CipherView[]> {
const ciphers = await this.cipherService.getAllDecrypted();
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
const ciphers = await this.cipherService.getAllDecrypted(activeUserId);
return ciphers.filter(
(cipher) =>
!cipher.isDeleted &&

View File

@@ -129,12 +129,18 @@ export abstract class CoreSyncService implements SyncService {
return this.syncCompleted(false);
}
async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise<boolean> {
async syncUpsertCipher(
notification: SyncCipherNotification,
isEdit: boolean,
userId: UserId,
): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId));
if (authStatus >= AuthenticationStatus.Locked) {
try {
let shouldUpdate = true;
const localCipher = await this.cipherService.get(notification.id);
const localCipher = await this.cipherService.get(notification.id, userId);
if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) {
shouldUpdate = false;
}
@@ -182,7 +188,7 @@ export abstract class CoreSyncService implements SyncService {
}
} catch (e) {
if (e != null && e.statusCode === 404 && isEdit) {
await this.cipherService.delete(notification.id);
await this.cipherService.delete(notification.id, userId);
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}
@@ -191,10 +197,12 @@ export abstract class CoreSyncService implements SyncService {
return this.syncCompleted(false);
}
async syncDeleteCipher(notification: SyncCipherNotification): Promise<boolean> {
async syncDeleteCipher(notification: SyncCipherNotification, userId: UserId): Promise<boolean> {
this.syncStarted();
if (await this.stateService.getIsAuthenticated()) {
await this.cipherService.delete(notification.id);
const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId));
if (authStatus >= AuthenticationStatus.Locked) {
await this.cipherService.delete(notification.id, userId);
this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id });
return this.syncCompleted(true);
}

View File

@@ -62,8 +62,9 @@ export abstract class SyncService {
abstract syncUpsertCipher(
notification: SyncCipherNotification,
isEdit: boolean,
userId: UserId,
): Promise<boolean>;
abstract syncDeleteCipher(notification: SyncFolderNotification): Promise<boolean>;
abstract syncDeleteCipher(notification: SyncFolderNotification, userId: UserId): Promise<boolean>;
abstract syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise<boolean>;
abstract syncDeleteSend(notification: SyncSendNotification): Promise<boolean>;
}

View File

@@ -6,6 +6,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/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 { UserId } from "@bitwarden/common/types/guid";
import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service";
import { EventUploadService } from "../../abstractions/event/event-upload.service";
@@ -46,7 +47,7 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
if (!(await this.shouldUpdate(null, eventType, ciphers))) {
if (!(await this.shouldUpdate(userId, null, eventType, ciphers))) {
return;
}
@@ -91,7 +92,7 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION);
if (!(await this.shouldUpdate(organizationId, eventType, undefined, cipherId))) {
if (!(await this.shouldUpdate(userId, organizationId, eventType, undefined, cipherId))) {
return;
}
@@ -113,18 +114,18 @@ export class EventCollectionService implements EventCollectionServiceAbstraction
}
/** Verifies if the event collection should be updated for the provided information
* @param userId the active user's id
* @param cipherId the cipher for the event
* @param organizationId the organization for the event
*/
private async shouldUpdate(
userId: UserId,
organizationId: string = null,
eventType: EventType = null,
ciphers: CipherView[] = [],
cipherId?: string,
): Promise<boolean> {
const cipher$ = from(this.cipherService.get(cipherId));
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
const cipher$ = from(this.cipherService.get(cipherId, userId));
const orgIds$ = this.organizationService
.organizations$(userId)

View File

@@ -19,57 +19,70 @@ import { FieldView } from "../models/view/field.view";
import { AddEditCipherInfo } from "../types/add-edit-cipher-info";
export abstract class CipherService implements UserKeyRotationDataProvider<CipherWithIdRequest> {
cipherViews$: Observable<CipherView[]>;
ciphers$: Observable<Record<CipherId, CipherData>>;
localData$: Observable<Record<CipherId, LocalData>>;
abstract cipherViews$(userId: UserId): Observable<CipherView[]>;
abstract ciphers$(userId: UserId): Observable<Record<CipherId, CipherData>>;
abstract localData$(userId: UserId): Observable<Record<CipherId, LocalData>>;
/**
* An observable monitoring the add/edit cipher info saved to memory.
*/
addEditCipherInfo$: Observable<AddEditCipherInfo>;
abstract addEditCipherInfo$(userId: UserId): Observable<AddEditCipherInfo>;
/**
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
*
* An empty array indicates that all ciphers were successfully decrypted.
*/
failedToDecryptCiphers$: Observable<CipherView[]>;
clearCache: (userId?: string) => Promise<void>;
encrypt: (
abstract failedToDecryptCiphers$(userId: UserId): Observable<CipherView[]>;
abstract clearCache(userId: UserId): Promise<void>;
abstract encrypt(
model: CipherView,
userId: UserId,
keyForEncryption?: SymmetricCryptoKey,
keyForCipherKeyDecryption?: SymmetricCryptoKey,
originalCipher?: Cipher,
) => Promise<Cipher>;
encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise<Field[]>;
encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise<Field>;
get: (id: string) => Promise<Cipher>;
getAll: () => Promise<Cipher[]>;
getAllDecrypted: () => Promise<CipherView[]>;
getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise<CipherView[]>;
getAllDecryptedForUrl: (
): Promise<Cipher>;
abstract encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise<Field[]>;
abstract encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise<Field>;
abstract get(id: string, userId: UserId): Promise<Cipher>;
abstract getAll(userId: UserId): Promise<Cipher[]>;
abstract getAllDecrypted(userId: UserId): Promise<CipherView[]>;
abstract getAllDecryptedForGrouping(
groupingId: string,
userId: UserId,
folder?: boolean,
): Promise<CipherView[]>;
abstract getAllDecryptedForUrl(
url: string,
userId: UserId,
includeOtherTypes?: CipherType[],
defaultMatch?: UriMatchStrategySetting,
) => Promise<CipherView[]>;
filterCiphersForUrl: (
): Promise<CipherView[]>;
abstract filterCiphersForUrl(
ciphers: CipherView[],
url: string,
includeOtherTypes?: CipherType[],
defaultMatch?: UriMatchStrategySetting,
) => Promise<CipherView[]>;
getAllFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
): Promise<CipherView[]>;
abstract getAllFromApiForOrganization(organizationId: string): Promise<CipherView[]>;
/**
* Gets ciphers belonging to the specified organization that the user has explicit collection level access to.
* Ciphers that are not assigned to any collections are only included for users with admin access.
*/
getManyFromApiForOrganization: (organizationId: string) => Promise<CipherView[]>;
getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise<CipherView>;
getNextCipherForUrl: (url: string) => Promise<CipherView>;
updateLastUsedIndexForUrl: (url: string) => void;
updateLastUsedDate: (id: string) => Promise<void>;
updateLastLaunchedDate: (id: string) => Promise<void>;
saveNeverDomain: (domain: string) => Promise<void>;
abstract getManyFromApiForOrganization(organizationId: string): Promise<CipherView[]>;
abstract getLastUsedForUrl(
url: string,
userId: UserId,
autofillOnPageLoad: boolean,
): Promise<CipherView>;
abstract getLastLaunchedForUrl(
url: string,
userId: UserId,
autofillOnPageLoad: boolean,
): Promise<CipherView>;
abstract getNextCipherForUrl(url: string, userId: UserId): Promise<CipherView>;
abstract updateLastUsedIndexForUrl(url: string): void;
abstract updateLastUsedDate(id: string, userId: UserId): Promise<void>;
abstract updateLastLaunchedDate(id: string, userId: UserId): Promise<void>;
abstract saveNeverDomain(domain: string): Promise<void>;
/**
* Create a cipher with the server
*
@@ -78,7 +91,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
*
* @returns A promise that resolves to the created cipher
*/
createWithServer: (cipher: Cipher, orgAdmin?: boolean) => Promise<Cipher>;
abstract createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise<Cipher>;
/**
* Update a cipher with the server
* @param cipher The cipher to update
@@ -87,88 +100,105 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
*
* @returns A promise that resolves to the updated cipher
*/
updateWithServer: (cipher: Cipher, orgAdmin?: boolean, isNotClone?: boolean) => Promise<Cipher>;
shareWithServer: (
abstract updateWithServer(
cipher: Cipher,
orgAdmin?: boolean,
isNotClone?: boolean,
): Promise<Cipher>;
abstract shareWithServer(
cipher: CipherView,
organizationId: string,
collectionIds: string[],
userId: UserId,
) => Promise<Cipher>;
shareManyWithServer: (
): Promise<Cipher>;
abstract shareManyWithServer(
ciphers: CipherView[],
organizationId: string,
collectionIds: string[],
userId: UserId,
) => Promise<any>;
saveAttachmentWithServer: (
): Promise<any>;
abstract saveAttachmentWithServer(
cipher: Cipher,
unencryptedFile: any,
userId: UserId,
admin?: boolean,
) => Promise<Cipher>;
saveAttachmentRawWithServer: (
): Promise<Cipher>;
abstract saveAttachmentRawWithServer(
cipher: Cipher,
filename: string,
data: ArrayBuffer,
userId: UserId,
admin?: boolean,
) => Promise<Cipher>;
): Promise<Cipher>;
/**
* Save the collections for a cipher with the server
*
* @param cipher The cipher to save collections for
* @param userId The user ID
*
* @returns A promise that resolves when the collections have been saved
*/
saveCollectionsWithServer: (cipher: Cipher) => Promise<Cipher>;
abstract saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise<Cipher>;
/**
* Save the collections for a cipher with the server as an admin.
* Used for Unassigned ciphers or when the user only has admin access to the cipher (not assigned normally).
* @param cipher
*/
saveCollectionsWithServerAdmin: (cipher: Cipher) => Promise<Cipher>;
abstract saveCollectionsWithServerAdmin(cipher: Cipher): Promise<Cipher>;
/**
* Bulk update collections for many ciphers with the server
* @param orgId
* @param userId
* @param cipherIds
* @param collectionIds
* @param removeCollections - If true, the collections will be removed from the ciphers, otherwise they will be added
*/
bulkUpdateCollectionsWithServer: (
abstract bulkUpdateCollectionsWithServer(
orgId: OrganizationId,
userId: UserId,
cipherIds: CipherId[],
collectionIds: CollectionId[],
removeCollections: boolean,
) => Promise<void>;
): Promise<void>;
/**
* Update the local store of CipherData with the provided data. Values are upserted into the existing store.
*
* @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects.
* @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated
*/
upsert: (cipher: CipherData | CipherData[]) => Promise<Record<CipherId, CipherData>>;
replace: (ciphers: { [id: string]: CipherData }, userId: UserId) => Promise<any>;
clear: (userId?: string) => Promise<void>;
moveManyWithServer: (ids: string[], folderId: string) => Promise<any>;
delete: (id: string | string[]) => Promise<any>;
deleteWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
deleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise<any>;
deleteAttachment: (id: string, revisionDate: string, attachmentId: string) => Promise<CipherData>;
deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise<CipherData>;
sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number;
sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number;
getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number;
softDelete: (id: string | string[]) => Promise<any>;
softDeleteWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
softDeleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise<any>;
restore: (
abstract upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>>;
abstract replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise<any>;
abstract clear(userId?: string): Promise<void>;
abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any>;
abstract delete(id: string | string[], userId: UserId): Promise<any>;
abstract deleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
abstract deleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise<any>;
abstract deleteAttachment(
id: string,
revisionDate: string,
attachmentId: string,
userId: UserId,
): Promise<CipherData>;
abstract deleteAttachmentWithServer(
id: string,
attachmentId: string,
userId: UserId,
): Promise<CipherData>;
abstract sortCiphersByLastUsed(a: CipherView, b: CipherView): number;
abstract sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number;
abstract getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number;
abstract softDelete(id: string | string[], userId: UserId): Promise<any>;
abstract softDeleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
abstract softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise<any>;
abstract restore(
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
) => Promise<any>;
restoreWithServer: (id: string, asAdmin?: boolean) => Promise<any>;
restoreManyWithServer: (ids: string[], orgId?: string) => Promise<void>;
getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise<any>;
setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise<void>;
userId: UserId,
): Promise<any>;
abstract restoreWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise<any>;
abstract restoreManyWithServer(ids: string[], orgId?: string): Promise<void>;
abstract getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<any>;
abstract setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId): Promise<void>;
/**
* Returns user ciphers re-encrypted with the new user key.
* @param originalUserKey the original user key
@@ -177,11 +207,11 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
* @throws Error if new user key is null
* @returns a list of user ciphers that have been re-encrypted with the new user key
*/
getRotatedData: (
abstract getRotatedData(
originalUserKey: UserKey,
newUserKey: UserKey,
userId: UserId,
) => Promise<CipherWithIdRequest[]>;
getNextCardCipher: () => Promise<CipherView>;
getNextIdentityCipher: () => Promise<CipherView>;
): Promise<CipherWithIdRequest[]>;
abstract getNextCardCipher(userId: UserId): Promise<CipherView>;
abstract getNextIdentityCipher(userId: UserId): Promise<CipherView>;
}

View File

@@ -382,8 +382,16 @@ describe("Cipher Service", () => {
Cipher1: cipher1,
Cipher2: cipher2,
});
cipherService.cipherViews$ = decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers)));
cipherService.failedToDecryptCiphers$ = failedCiphers = new BehaviorSubject<CipherView[]>([]);
jest
.spyOn(cipherService, "cipherViews$")
.mockImplementation((userId: UserId) =>
decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))),
);
failedCiphers = new BehaviorSubject<CipherView[]>([]);
jest
.spyOn(cipherService, "failedToDecryptCiphers$")
.mockImplementation((userId: UserId) => failedCiphers);
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32));
encryptedKey = new EncString("Re-encrypted Cipher Key");

View File

@@ -37,7 +37,7 @@ 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 { StateProvider } from "../../platform/state";
import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid";
import { OrgKey, UserKey } from "../../types/key";
import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service";
@@ -97,33 +97,6 @@ export class CipherService implements CipherServiceAbstraction {
*/
private forceCipherViews$: Subject<CipherView[]> = new Subject<CipherView[]>();
localData$: Observable<Record<CipherId, LocalData>>;
ciphers$: Observable<Record<CipherId, CipherData>>;
/**
* Observable that emits an array of decrypted ciphers for the active user.
* This observable will not emit until the encrypted ciphers have either been loaded from state or after sync.
*
* A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that
* decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete.
*
*/
cipherViews$: Observable<CipherView[] | null>;
addEditCipherInfo$: Observable<AddEditCipherInfo>;
/**
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
*
* An empty array indicates that all ciphers were successfully decrypted.
*/
failedToDecryptCiphers$: Observable<CipherView[]>;
private localDataState: ActiveUserState<Record<CipherId, LocalData>>;
private encryptedCiphersState: ActiveUserState<Record<CipherId, CipherData>>;
private decryptedCiphersState: ActiveUserState<Record<CipherId, CipherView>>;
private failedToDecryptCiphersState: ActiveUserState<CipherView[]>;
private addEditCipherInfoState: ActiveUserState<AddEditCipherInfo>;
constructor(
private keyService: KeyService,
private domainSettingsService: DomainSettingsService,
@@ -138,30 +111,49 @@ export class CipherService implements CipherServiceAbstraction {
private configService: ConfigService,
private stateProvider: StateProvider,
private accountService: AccountService,
) {
this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY);
this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS);
this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS);
this.failedToDecryptCiphersState = this.stateProvider.getActive(FAILED_DECRYPTED_CIPHERS);
this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY);
) {}
this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {}));
this.ciphers$ = this.encryptedCiphersState.state$.pipe(map((ciphers) => ciphers ?? {}));
localData$(userId: UserId): Observable<Record<CipherId, LocalData>> {
return this.localDataState(userId).state$.pipe(map((data) => data ?? {}));
}
// Decrypted ciphers depend on both ciphers and local data and need to be updated when either changes
this.cipherViews$ = combineLatest([this.encryptedCiphersState.state$, this.localData$]).pipe(
/**
* Observable that emits an object of encrypted ciphers for the active user.
*/
ciphers$(userId: UserId): Observable<Record<CipherId, CipherData>> {
return this.encryptedCiphersState(userId).state$.pipe(map((ciphers) => ciphers ?? {}));
}
/**
* Observable that emits an array of decrypted ciphers for the active user.
* This observable will not emit until the encrypted ciphers have either been loaded from state or after sync.
*
* A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that
* decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete.
*/
cipherViews$(userId: UserId): Observable<CipherView[] | null> {
return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe(
filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet
switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted())),
switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted(userId))),
shareReplay({ bufferSize: 1, refCount: true }),
);
}
this.failedToDecryptCiphers$ = this.failedToDecryptCiphersState.state$.pipe(
addEditCipherInfo$(userId: UserId): Observable<AddEditCipherInfo> {
return this.addEditCipherInfoState(userId).state$;
}
/**
* Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed.
*
* An empty array indicates that all ciphers were successfully decrypted.
*/
failedToDecryptCiphers$(userId: UserId): Observable<CipherView[]> {
return this.failedToDecryptCiphersState(userId).state$.pipe(
filter((ciphers) => ciphers != null),
switchMap((ciphers) => merge(this.forceCipherViews$, of(ciphers))),
shareReplay({ bufferSize: 1, refCount: true }),
);
this.addEditCipherInfo$ = this.addEditCipherInfoState.state$;
}
async setDecryptedCipherCache(value: CipherView[], userId: UserId) {
@@ -212,7 +204,7 @@ export class CipherService implements CipherServiceAbstraction {
): Promise<Cipher> {
if (model.id != null) {
if (originalCipher == null) {
originalCipher = await this.get(model.id);
originalCipher = await this.get(model.id, userId);
}
if (originalCipher != null) {
await this.updateModelfromExistingCipher(model, originalCipher, userId);
@@ -366,22 +358,22 @@ export class CipherService implements CipherServiceAbstraction {
return ph;
}
async get(id: string): Promise<Cipher> {
const ciphers = await firstValueFrom(this.ciphers$);
async get(id: string, userId: UserId): Promise<Cipher> {
const ciphers = await firstValueFrom(this.ciphers$(userId));
// eslint-disable-next-line
if (ciphers == null || !ciphers.hasOwnProperty(id)) {
return null;
}
const localData = await firstValueFrom(this.localData$);
const localData = await firstValueFrom(this.localData$(userId));
const cipherId = id as CipherId;
return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null);
}
async getAll(): Promise<Cipher[]> {
const localData = await firstValueFrom(this.localData$);
const ciphers = await firstValueFrom(this.ciphers$);
async getAll(userId: UserId): Promise<Cipher[]> {
const localData = await firstValueFrom(this.localData$(userId));
const ciphers = await firstValueFrom(this.ciphers$(userId));
const response: Cipher[] = [];
for (const id in ciphers) {
// eslint-disable-next-line
@@ -399,33 +391,27 @@ export class CipherService implements CipherServiceAbstraction {
* @deprecated Use `cipherViews$` observable instead
*/
@sequentialize(() => "getAllDecrypted")
async getAllDecrypted(): Promise<CipherView[]> {
const decCiphers = await this.getDecryptedCiphers();
async getAllDecrypted(userId: UserId): Promise<CipherView[]> {
const decCiphers = await this.getDecryptedCiphers(userId);
if (decCiphers != null && decCiphers.length !== 0) {
await this.reindexCiphers();
return await this.getDecryptedCiphers();
}
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
if (activeUserId == null) {
return [];
await this.reindexCiphers(userId);
return await this.getDecryptedCiphers(userId);
}
const [newDecCiphers, failedCiphers] = await this.decryptCiphers(
await this.getAll(),
activeUserId,
await this.getAll(userId),
userId,
);
await this.setDecryptedCipherCache(newDecCiphers, activeUserId);
await this.setFailedDecryptedCiphers(failedCiphers, activeUserId);
await this.setDecryptedCipherCache(newDecCiphers, userId);
await this.setFailedDecryptedCiphers(failedCiphers, userId);
return newDecCiphers;
}
private async getDecryptedCiphers() {
private async getDecryptedCiphers(userId: UserId) {
return Object.values(
await firstValueFrom(this.decryptedCiphersState.state$.pipe(map((c) => c ?? {}))),
await firstValueFrom(this.decryptedCiphersState(userId).state$.pipe(map((c) => c ?? {}))),
);
}
@@ -491,18 +477,21 @@ export class CipherService implements CipherServiceAbstraction {
);
}
private async reindexCiphers() {
const userId = await this.stateService.getUserId();
private async reindexCiphers(userId: UserId) {
const reindexRequired =
this.searchService != null &&
((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId;
if (reindexRequired) {
await this.searchService.indexCiphers(await this.getDecryptedCiphers(), userId);
await this.searchService.indexCiphers(await this.getDecryptedCiphers(userId), userId);
}
}
async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise<CipherView[]> {
const ciphers = await this.getAllDecrypted();
async getAllDecryptedForGrouping(
groupingId: string,
userId: UserId,
folder = true,
): Promise<CipherView[]> {
const ciphers = await this.getAllDecrypted(userId);
return ciphers.filter((cipher) => {
if (cipher.isDeleted) {
@@ -524,10 +513,11 @@ export class CipherService implements CipherServiceAbstraction {
async getAllDecryptedForUrl(
url: string,
userId: UserId,
includeOtherTypes?: CipherType[],
defaultMatch: UriMatchStrategySetting = null,
): Promise<CipherView[]> {
const ciphers = await this.getAllDecrypted();
const ciphers = await this.getAllDecrypted(userId);
return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch);
}
@@ -569,8 +559,11 @@ export class CipherService implements CipherServiceAbstraction {
});
}
private async getAllDecryptedCiphersOfType(type: CipherType[]): Promise<CipherView[]> {
const ciphers = await this.getAllDecrypted();
private async getAllDecryptedCiphersOfType(
type: CipherType[],
userId: UserId,
): Promise<CipherView[]> {
const ciphers = await this.getAllDecrypted(userId);
return ciphers
.filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type))
.sort((a, b) => this.sortCiphersByLastUsedThenName(a, b));
@@ -613,23 +606,31 @@ export class CipherService implements CipherServiceAbstraction {
return decCiphers;
}
async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise<CipherView> {
return this.getCipherForUrl(url, true, false, autofillOnPageLoad);
async getLastUsedForUrl(
url: string,
userId: UserId,
autofillOnPageLoad = false,
): Promise<CipherView> {
return this.getCipherForUrl(url, userId, true, false, autofillOnPageLoad);
}
async getLastLaunchedForUrl(url: string, autofillOnPageLoad = false): Promise<CipherView> {
return this.getCipherForUrl(url, false, true, autofillOnPageLoad);
async getLastLaunchedForUrl(
url: string,
userId: UserId,
autofillOnPageLoad = false,
): Promise<CipherView> {
return this.getCipherForUrl(url, userId, false, true, autofillOnPageLoad);
}
async getNextCipherForUrl(url: string): Promise<CipherView> {
return this.getCipherForUrl(url, false, false, false);
async getNextCipherForUrl(url: string, userId: UserId): Promise<CipherView> {
return this.getCipherForUrl(url, userId, false, false, false);
}
async getNextCardCipher(): Promise<CipherView> {
async getNextCardCipher(userId: UserId): Promise<CipherView> {
const cacheKey = "cardCiphers";
if (!this.sortedCiphersCache.isCached(cacheKey)) {
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card]);
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card], userId);
if (!ciphers?.length) {
return null;
}
@@ -640,11 +641,11 @@ export class CipherService implements CipherServiceAbstraction {
return this.sortedCiphersCache.getNext(cacheKey);
}
async getNextIdentityCipher(): Promise<CipherView> {
async getNextIdentityCipher(userId: UserId): Promise<CipherView> {
const cacheKey = "identityCiphers";
if (!this.sortedCiphersCache.isCached(cacheKey)) {
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity]);
const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity], userId);
if (!ciphers?.length) {
return null;
}
@@ -659,9 +660,8 @@ export class CipherService implements CipherServiceAbstraction {
this.sortedCiphersCache.updateLastUsedIndex(url);
}
async updateLastUsedDate(id: string): Promise<void> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
let ciphersLocalData = await firstValueFrom(this.localData$);
async updateLastUsedDate(id: string, userId: UserId): Promise<void> {
let ciphersLocalData = await firstValueFrom(this.localData$(userId));
if (!ciphersLocalData) {
ciphersLocalData = {};
@@ -676,9 +676,9 @@ export class CipherService implements CipherServiceAbstraction {
};
}
await this.localDataState.update(() => ciphersLocalData);
await this.localDataState(userId).update(() => ciphersLocalData);
const decryptedCipherCache = await this.getDecryptedCiphers();
const decryptedCipherCache = await this.getDecryptedCiphers(userId);
if (!decryptedCipherCache) {
return;
}
@@ -693,9 +693,8 @@ export class CipherService implements CipherServiceAbstraction {
await this.setDecryptedCiphers(decryptedCipherCache, userId);
}
async updateLastLaunchedDate(id: string): Promise<void> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
let ciphersLocalData = await firstValueFrom(this.localData$);
async updateLastLaunchedDate(id: string, userId: UserId): Promise<void> {
let ciphersLocalData = await firstValueFrom(this.localData$(userId));
if (!ciphersLocalData) {
ciphersLocalData = {};
@@ -707,9 +706,9 @@ export class CipherService implements CipherServiceAbstraction {
lastUsedDate: currentTime,
};
await this.localDataState.update(() => ciphersLocalData);
await this.localDataState(userId).update(() => ciphersLocalData);
const decryptedCipherCache = await this.getDecryptedCiphers();
const decryptedCipherCache = await this.getDecryptedCiphers(userId);
if (!decryptedCipherCache) {
return;
}
@@ -914,13 +913,13 @@ export class CipherService implements CipherServiceAbstraction {
return new Cipher(cData);
}
async saveCollectionsWithServer(cipher: Cipher): Promise<Cipher> {
async saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise<Cipher> {
const request = new CipherCollectionsRequest(cipher.collectionIds);
const response = await this.apiService.putCipherCollections(cipher.id, request);
// The response will now check for an unavailable value. This value determines whether
// the user still has Can Manage access to the item after updating.
if (response.unavailable) {
await this.delete(cipher.id);
await this.delete(cipher.id, userId);
return;
}
const data = new CipherData(response.cipher);
@@ -944,6 +943,7 @@ export class CipherService implements CipherServiceAbstraction {
*/
async bulkUpdateCollectionsWithServer(
orgId: OrganizationId,
userId: UserId,
cipherIds: CipherId[],
collectionIds: CollectionId[],
removeCollections: boolean = false,
@@ -958,7 +958,7 @@ export class CipherService implements CipherServiceAbstraction {
await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false);
// Update the local state
const ciphers = await firstValueFrom(this.ciphers$);
const ciphers = await firstValueFrom(this.ciphers$(userId));
for (const id of cipherIds) {
const cipher = ciphers[id];
@@ -975,7 +975,7 @@ export class CipherService implements CipherServiceAbstraction {
}
await this.clearCache();
await this.encryptedCiphersState.update(() => ciphers);
await this.encryptedCiphersState(userId).update(() => ciphers);
}
async upsert(cipher: CipherData | CipherData[]): Promise<Record<CipherId, CipherData>> {
@@ -1016,10 +1016,10 @@ export class CipherService implements CipherServiceAbstraction {
await this.clearCache(userId);
}
async moveManyWithServer(ids: string[], folderId: string): Promise<any> {
async moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise<any> {
await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId));
let ciphers = await firstValueFrom(this.ciphers$);
let ciphers = await firstValueFrom(this.ciphers$(userId));
if (ciphers == null) {
ciphers = {};
}
@@ -1032,11 +1032,11 @@ export class CipherService implements CipherServiceAbstraction {
});
await this.clearCache();
await this.encryptedCiphersState.update(() => ciphers);
await this.encryptedCiphersState(userId).update(() => ciphers);
}
async delete(id: string | string[]): Promise<any> {
const ciphers = await firstValueFrom(this.ciphers$);
async delete(id: string | string[], userId: UserId): Promise<any> {
const ciphers = await firstValueFrom(this.ciphers$(userId));
if (ciphers == null) {
return;
}
@@ -1054,35 +1054,36 @@ export class CipherService implements CipherServiceAbstraction {
}
await this.clearCache();
await this.encryptedCiphersState.update(() => ciphers);
await this.encryptedCiphersState(userId).update(() => ciphers);
}
async deleteWithServer(id: string, asAdmin = false): Promise<any> {
async deleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
if (asAdmin) {
await this.apiService.deleteCipherAdmin(id);
} else {
await this.apiService.deleteCipher(id);
}
await this.delete(id);
await this.delete(id, userId);
}
async deleteManyWithServer(ids: string[], asAdmin = false): Promise<any> {
async deleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise<any> {
const request = new CipherBulkDeleteRequest(ids);
if (asAdmin) {
await this.apiService.deleteManyCiphersAdmin(request);
} else {
await this.apiService.deleteManyCiphers(request);
}
await this.delete(ids);
await this.delete(ids, userId);
}
async deleteAttachment(
id: string,
revisionDate: string,
attachmentId: string,
userId: UserId,
): Promise<CipherData> {
let ciphers = await firstValueFrom(this.ciphers$);
let ciphers = await firstValueFrom(this.ciphers$(userId));
const cipherId = id as CipherId;
// eslint-disable-next-line
if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) {
@@ -1100,7 +1101,7 @@ export class CipherService implements CipherServiceAbstraction {
ciphers[cipherId].revisionDate = revisionDate;
await this.clearCache();
await this.encryptedCiphersState.update(() => {
await this.encryptedCiphersState(userId).update(() => {
if (ciphers == null) {
ciphers = {};
}
@@ -1110,7 +1111,11 @@ export class CipherService implements CipherServiceAbstraction {
return ciphers[cipherId];
}
async deleteAttachmentWithServer(id: string, attachmentId: string): Promise<CipherData> {
async deleteAttachmentWithServer(
id: string,
attachmentId: string,
userId: UserId,
): Promise<CipherData> {
let cipherResponse = null;
try {
cipherResponse = await this.apiService.deleteCipherAttachment(id, attachmentId);
@@ -1119,7 +1124,7 @@ export class CipherService implements CipherServiceAbstraction {
}
const cipherData = CipherData.fromJSON(cipherResponse?.cipher);
return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId);
return await this.deleteAttachment(id, cipherData.revisionDate, attachmentId, userId);
}
sortCiphersByLastUsed(a: CipherView, b: CipherView): number {
@@ -1192,8 +1197,8 @@ export class CipherService implements CipherServiceAbstraction {
};
}
async softDelete(id: string | string[]): Promise<any> {
let ciphers = await firstValueFrom(this.ciphers$);
async softDelete(id: string | string[], userId: UserId): Promise<any> {
let ciphers = await firstValueFrom(this.ciphers$(userId));
if (ciphers == null) {
return;
}
@@ -1212,7 +1217,7 @@ export class CipherService implements CipherServiceAbstraction {
}
await this.clearCache();
await this.encryptedCiphersState.update(() => {
await this.encryptedCiphersState(userId).update(() => {
if (ciphers == null) {
ciphers = {};
}
@@ -1220,17 +1225,17 @@ export class CipherService implements CipherServiceAbstraction {
});
}
async softDeleteWithServer(id: string, asAdmin = false): Promise<any> {
async softDeleteWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
if (asAdmin) {
await this.apiService.putDeleteCipherAdmin(id);
} else {
await this.apiService.putDeleteCipher(id);
}
await this.softDelete(id);
await this.softDelete(id, userId);
}
async softDeleteManyWithServer(ids: string[], asAdmin = false): Promise<any> {
async softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise<any> {
const request = new CipherBulkDeleteRequest(ids);
if (asAdmin) {
await this.apiService.putDeleteManyCiphersAdmin(request);
@@ -1238,13 +1243,14 @@ export class CipherService implements CipherServiceAbstraction {
await this.apiService.putDeleteManyCiphers(request);
}
await this.softDelete(ids);
await this.softDelete(ids, userId);
}
async restore(
cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[],
userId: UserId,
) {
let ciphers = await firstValueFrom(this.ciphers$);
let ciphers = await firstValueFrom(this.ciphers$(userId));
if (ciphers == null) {
return;
}
@@ -1265,7 +1271,7 @@ export class CipherService implements CipherServiceAbstraction {
}
await this.clearCache();
await this.encryptedCiphersState.update(() => {
await this.encryptedCiphersState(userId).update(() => {
if (ciphers == null) {
ciphers = {};
}
@@ -1273,7 +1279,7 @@ export class CipherService implements CipherServiceAbstraction {
});
}
async restoreWithServer(id: string, asAdmin = false): Promise<any> {
async restoreWithServer(id: string, userId: UserId, asAdmin = false): Promise<any> {
let response;
if (asAdmin) {
response = await this.apiService.putRestoreCipherAdmin(id);
@@ -1281,14 +1287,14 @@ export class CipherService implements CipherServiceAbstraction {
response = await this.apiService.putRestoreCipher(id);
}
await this.restore({ id: id, revisionDate: response.revisionDate });
await this.restore({ id: id, revisionDate: response.revisionDate }, userId);
}
/**
* No longer using an asAdmin Param. Org Vault bulkRestore will assess if an item is unassigned or editable
* The Org Vault will pass those ids an array as well as the orgId when calling bulkRestore
*/
async restoreManyWithServer(ids: string[], orgId: string = null): Promise<void> {
async restoreManyWithServer(ids: string[], userId: UserId, orgId: string = null): Promise<void> {
let response;
if (orgId) {
@@ -1303,7 +1309,7 @@ export class CipherService implements CipherServiceAbstraction {
for (const cipher of response.data) {
restores.push({ id: cipher.id, revisionDate: cipher.revisionDate });
}
await this.restore(restores);
await this.restore(restores, userId);
}
async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise<UserKey | OrgKey> {
@@ -1313,8 +1319,8 @@ export class CipherService implements CipherServiceAbstraction {
);
}
async setAddEditCipherInfo(value: AddEditCipherInfo) {
await this.addEditCipherInfoState.update(() => value, {
async setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId) {
await this.addEditCipherInfoState(userId).update(() => value, {
shouldUpdate: (current) => !(current == null && value == null),
});
}
@@ -1333,8 +1339,8 @@ export class CipherService implements CipherServiceAbstraction {
let encryptedCiphers: CipherWithIdRequest[] = [];
const ciphers = await firstValueFrom(this.cipherViews$);
const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$);
const ciphers = await firstValueFrom(this.cipherViews$(userId));
const failedCiphers = await firstValueFrom(this.failedToDecryptCiphers$(userId));
if (!ciphers) {
return encryptedCiphers;
}
@@ -1357,6 +1363,41 @@ export class CipherService implements CipherServiceAbstraction {
return encryptedCiphers;
}
/**
* @returns a SingleUserState
*/
private localDataState(userId: UserId) {
return this.stateProvider.getUser(userId, LOCAL_DATA_KEY);
}
/**
* @returns a SingleUserState for the encrypted ciphers
*/
private encryptedCiphersState(userId: UserId) {
return this.stateProvider.getUser(userId, ENCRYPTED_CIPHERS);
}
/**
* @returns a SingleUserState for the decrypted ciphers
*/
private decryptedCiphersState(userId: UserId) {
return this.stateProvider.getUser(userId, DECRYPTED_CIPHERS);
}
/**
* @returns a SingleUserState for the add/edit cipher info
*/
private addEditCipherInfoState(userId: UserId) {
return this.stateProvider.getUser(userId, ADD_EDIT_CIPHER_INFO_KEY);
}
/**
* @returns a SingleUserState for the failed to decrypt ciphers
*/
private failedToDecryptCiphersState(userId: UserId) {
return this.stateProvider.getUser(userId, FAILED_DECRYPTED_CIPHERS);
}
// Helpers
// In the case of a cipher that is being shared with an organization, we want to decrypt the
@@ -1660,6 +1701,7 @@ export class CipherService implements CipherServiceAbstraction {
private async getCipherForUrl(
url: string,
userId: UserId,
lastUsed: boolean,
lastLaunched: boolean,
autofillOnPageLoad: boolean,
@@ -1667,7 +1709,7 @@ export class CipherService implements CipherServiceAbstraction {
const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url;
if (!this.sortedCiphersCache.isCached(cacheKey)) {
let ciphers = await this.getAllDecryptedForUrl(url);
let ciphers = await this.getAllDecryptedForUrl(url, userId);
if (!ciphers) {
return null;
}

View File

@@ -190,7 +190,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
});
// Items in a deleted folder are re-assigned to "No Folder"
const ciphers = await this.cipherService.getAll();
const ciphers = await this.cipherService.getAll(userId);
if (ciphers != null) {
const updates: Cipher[] = [];
for (const cId in ciphers) {