1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 21:50:15 +00:00

Use update for to clear storage for non active users

This commit is contained in:
Matt Gibson
2023-10-04 20:15:33 -04:00
parent 0f5fbb8837
commit c13cc39da0
5 changed files with 49 additions and 30 deletions

View File

@@ -14,6 +14,7 @@ export class TestUserState<T> implements UserState<T> {
getFromState = jest.fn();
update = jest.fn().mockImplementation(this.minimalUpdate);
updateFor = jest.fn();
createDerived = jest.fn();
next(next: T) {

View File

@@ -1,5 +1,6 @@
import { Observable } from "rxjs";
import { UserId } from "../../types/guid";
import { DerivedUserState } from "../services/default-user-state.provider";
import { DerivedStateDefinition } from "../types/derived-state-definition";
@@ -7,11 +8,24 @@ export interface UserState<T> {
readonly state$: Observable<T>;
readonly getFromState: () => Promise<T>;
/**
*
* Updates backing stores for the active user.
* @param configureState function that takes the current state and returns the new state
* @returns The new state
*/
readonly update: (configureState: (state: T) => T) => Promise<T>;
/**
* Updates backing stores for the given userId, which may or may not be active.
* @param userId the UserId to target the update for
* @param configureState function that takes the current state for the targeted user and returns the new state
* @returns The new state
*/
readonly updateFor: (userId: UserId, configureState: (state: T) => T) => Promise<T>;
/**
* Creates a derives state from the current state. Derived states are always tied to the active user.
* @param derivedStateDefinition
* @returns
*/
createDerived: <TTo>(
derivedStateDefinition: DerivedStateDefinition<T, TTo>
) => DerivedUserState<T, TTo>;

View File

@@ -11,6 +11,7 @@ import {
import { Jsonify } from "type-fest";
import { AccountService } from "../../auth/abstractions/account.service";
import { UserId } from "../../types/guid";
import { EncryptService } from "../abstractions/encrypt.service";
import {
AbstractMemoryStorageService,
@@ -142,6 +143,20 @@ class DefaultUserState<T> implements UserState<T> {
return newState;
}
async updateFor(userId: UserId, configureState: (state: T) => T): Promise<T> {
if (userId == null) {
throw new Error("Attempting to update user state, but no userId has been supplied.");
}
const key = userKeyBuilder(userId, this.keyDefinition);
const currentStore = await this.chosenStorageLocation.get<Jsonify<T>>(key);
const currentState = this.keyDefinition.deserializer(currentStore);
const newState = configureState(currentState);
await this.chosenStorageLocation.save(key, newState);
return newState;
}
async getFromState(): Promise<T> {
const activeUser = await firstValueFrom(this.accountService.activeAccount$);
if (activeUser == null || activeUser.id == null) {

View File

@@ -3,7 +3,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { mock } from "jest-mock-extended";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { TestUserState } from "../../../../spec/test-active-user-state";
import { TestUserState } from "../../../../spec/test-user-state";
import { CryptoService } from "../../../platform/abstractions/crypto.service";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
@@ -123,17 +123,7 @@ describe("Folder Service", () => {
test("replace", async () => {
await folderService.replace({ "2": folderData("2", "test 2") });
expect(await firstValueFrom(folderService.folders$)).toEqual([
{
id: "2",
name: {
decryptedValue: [],
encryptedString: "test 2",
encryptionType: 0,
},
revisionDate: null,
},
]);
expect(await firstValueFrom(folderService.folders$)).toEqual([folder("2", "test 2")]);
});
test("delete", async () => {
@@ -159,29 +149,24 @@ describe("Folder Service", () => {
it("null userId", async () => {
await folderService.clear();
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
expect(userState.update).toHaveBeenCalled();
expect(await firstValueFrom(folderService.folders$)).toEqual(expect.arrayContaining([]));
});
it("matching userId", async () => {
stateService.getUserId().resolves("1");
it("active userId", async () => {
await folderService.clear("1");
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
expect((await firstValueFrom(folderService.folders$)).length).toBe(0);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(0);
expect(userState.updateFor).toHaveBeenCalled();
const updateCallback = userState.updateFor.mock.calls[0][1];
expect(updateCallback({ "2": folderData("2", "test") })).toEqual(expect.objectContaining({}));
});
it("missmatching userId", async () => {
it("inactive userId", async () => {
await folderService.clear("12");
stateService.received(1).setEncryptedFolders(Arg.any(), Arg.any());
expect((await firstValueFrom(folderService.folders$)).length).toBe(1);
expect((await firstValueFrom(folderService.folderViews$)).length).toBe(2);
expect(userState.updateFor).toHaveBeenCalled();
const updateCallback = userState.updateFor.mock.calls[0][1];
expect(updateCallback({ "2": folderData("2", "test") })).toEqual(expect.objectContaining({}));
});
});

View File

@@ -8,6 +8,7 @@ import { UserState } from "../../../platform/interfaces/user-state";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { DerivedUserState } from "../../../platform/services/default-user-state.provider";
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";
@@ -115,8 +116,11 @@ export class FolderService implements InternalFolderServiceAbstraction {
}
async clear(userId?: string): Promise<any> {
// TODO: handle clear for non-active users
await this.folderState.update((_) => null);
if (userId == null) {
await this.folderState.update((_) => ({}));
} else {
await this.folderState.updateFor(userId as UserId, (_) => ({}));
}
}
async delete(id: string | string[]): Promise<void> {