PS-813 Add memory storage to state service (#2892)
* Use abstract methods and generics in StorageService * Prepend `Abstract` to abstract classes * Create session browser storage service * Use memory storage service for state memory * Inject memory storage service * Maintain filename extensions to help ide formatting * Preserve state if it's still in memory * Use jslib's memory storage service * linter * Create prototypes on stored objects * standardize package scripts * Add type safety to `withPrototype` decorators * webpack notify manifest version * Fix desktop * linter * Fix script * Improve prototye application * do not change prototype if it already matches desired * fix error with object values prototype application * Handle null state * Apply prototypes to browser-specific state * Add angular language server to recommended extensions * Improve browser state service tests * Start testing state Service * Fix abstract returns * Move test setup files to not be picked up by default glob matchers * Add key generation service * Add low-dependency encrypt service * Back crypto service with encrypt service. We'll want to work items that don't require state over to encrypt service * Add new storage service and tests * Properly init more stored values * Fix reload issues when state service is recovering state from session storage Co-authored-by: Thomas Avery <Thomas-Avery@users.noreply.github.com> Co-authored-by: Justin Baur <admin@justinbaur.com> * Simplify encrypt service * Do not log mac failures for local-backed session storage * `content` changed to `main` in #2245 * Fix CLI * Remove loggin * PR feedback * Merge remote-tracking branch 'origin/master' into add-memory-storage-to-state-service * Fix desktop * Fix decrypt method signature * Minify if not development * Key is required Co-authored-by: Thomas Avery <Thomas-Avery@users.noreply.github.com> Co-authored-by: Justin Baur <admin@justinbaur.com>
This commit is contained in:
parent
c044c5c770
commit
399b8c2b34
|
@ -4,7 +4,7 @@
|
|||
"types": ["node"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["../src/test.ts", "../src/**/*.spec.ts", "../projects/**/*.spec.ts"],
|
||||
"exclude": ["../src/test.setup.ts", "../src/**/*.spec.ts", "../projects/**/*.spec.ts"],
|
||||
"include": ["../src/**/*", "../projects/**/*"],
|
||||
"files": ["./typings.d.ts"]
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ module.exports = {
|
|||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
preset: "jest-preset-angular",
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/",
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
|
||||
"build:watch": "webpack --watch",
|
||||
"build:watch:MV3": "cross-env MANIFEST_VERSION=3 webpack --watch",
|
||||
"build:watch:mv3": "cross-env MANIFEST_VERSION=3 webpack --watch",
|
||||
"build:prod": "cross-env NODE_ENV=production webpack",
|
||||
"build:prod:watch": "cross-env NODE_ENV=production webpack --watch",
|
||||
"dist": "npm run build:prod && gulp dist",
|
||||
"dist:chromeMV3": "cross-env MANIFEST_VERSION=3 npm run build:prod && gulp dist:chrome",
|
||||
"dist:chrome": "npm run build:prod && gulp dist:chrome",
|
||||
"dist:chrome:mv3": "cross-env MANIFEST_VERSION=3 npm run build:prod && gulp dist:chrome",
|
||||
"dist:firefox": "npm run build:prod && gulp dist:firefox",
|
||||
"dist:opera": "npm run build:prod && gulp dist:opera",
|
||||
"dist:safari": "npm run build:prod && gulp dist:safari",
|
||||
|
|
|
@ -24,7 +24,7 @@ import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common
|
|||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||
import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service";
|
||||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { StorageService as StorageServiceAbstraction } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync.service";
|
||||
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
|
||||
|
@ -47,12 +47,14 @@ import { CipherService } from "@bitwarden/common/services/cipher.service";
|
|||
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
||||
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
|
||||
import { EventService } from "@bitwarden/common/services/event.service";
|
||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
|
||||
import { FolderService } from "@bitwarden/common/services/folder.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
|
||||
import { OrganizationService } from "@bitwarden/common/services/organization.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
|
||||
|
@ -79,11 +81,13 @@ import { AutofillService as AutofillServiceAbstraction } from "../services/abstr
|
|||
import { StateService as StateServiceAbstraction } from "../services/abstractions/state.service";
|
||||
import AutofillService from "../services/autofill.service";
|
||||
import { BrowserCryptoService } from "../services/browserCrypto.service";
|
||||
import BrowserLocalStorageService from "../services/browserLocalStorage.service";
|
||||
import BrowserMessagingService from "../services/browserMessaging.service";
|
||||
import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service";
|
||||
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||
import BrowserStorageService from "../services/browserStorage.service";
|
||||
import I18nService from "../services/i18n.service";
|
||||
import { KeyGenerationService } from "../services/keyGeneration.service";
|
||||
import { LocalBackedSessionStorageService } from "../services/localBackedSessionStorage.service";
|
||||
import { StateService } from "../services/state.service";
|
||||
import { VaultFilterService } from "../services/vaultFilter.service";
|
||||
import VaultTimeoutService from "../services/vaultTimeout.service";
|
||||
|
@ -100,8 +104,9 @@ import WebRequestBackground from "./webRequest.background";
|
|||
|
||||
export default class MainBackground {
|
||||
messagingService: MessagingServiceAbstraction;
|
||||
storageService: StorageServiceAbstraction;
|
||||
secureStorageService: StorageServiceAbstraction;
|
||||
storageService: AbstractStorageService;
|
||||
secureStorageService: AbstractStorageService;
|
||||
memoryStorageService: AbstractStorageService;
|
||||
i18nService: I18nServiceAbstraction;
|
||||
platformUtilsService: PlatformUtilsServiceAbstraction;
|
||||
logService: LogServiceAbstraction;
|
||||
|
@ -141,6 +146,7 @@ export default class MainBackground {
|
|||
twoFactorService: TwoFactorServiceAbstraction;
|
||||
vaultFilterService: VaultFilterService;
|
||||
usernameGenerationService: UsernameGenerationServiceAbstraction;
|
||||
encryptService: EncryptService;
|
||||
|
||||
onUpdatedRan: boolean;
|
||||
onReplacedRan: boolean;
|
||||
|
@ -181,9 +187,17 @@ export default class MainBackground {
|
|||
this.messagingService = isPrivateMode
|
||||
? new BrowserMessagingPrivateModeBackgroundService()
|
||||
: new BrowserMessagingService();
|
||||
this.storageService = new BrowserStorageService();
|
||||
this.secureStorageService = new BrowserStorageService();
|
||||
this.logService = new ConsoleLogService(false);
|
||||
this.cryptoFunctionService = new WebCryptoFunctionService(window);
|
||||
this.storageService = new BrowserLocalStorageService();
|
||||
this.secureStorageService = new BrowserLocalStorageService();
|
||||
this.memoryStorageService =
|
||||
chrome.runtime.getManifest().manifest_version == 3
|
||||
? new LocalBackedSessionStorageService(
|
||||
new EncryptService(this.cryptoFunctionService, this.logService, false),
|
||||
new KeyGenerationService(this.cryptoFunctionService)
|
||||
)
|
||||
: new MemoryStorageService();
|
||||
this.stateMigrationService = new StateMigrationService(
|
||||
this.storageService,
|
||||
this.secureStorageService,
|
||||
|
@ -192,6 +206,7 @@ export default class MainBackground {
|
|||
this.stateService = new StateService(
|
||||
this.storageService,
|
||||
this.secureStorageService,
|
||||
this.memoryStorageService,
|
||||
this.logService,
|
||||
this.stateMigrationService,
|
||||
new StateFactory(GlobalState, Account)
|
||||
|
@ -219,9 +234,10 @@ export default class MainBackground {
|
|||
}
|
||||
);
|
||||
this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
|
||||
this.cryptoFunctionService = new WebCryptoFunctionService(window);
|
||||
this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true);
|
||||
this.cryptoService = new BrowserCryptoService(
|
||||
this.cryptoFunctionService,
|
||||
this.encryptService,
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
this.stateService
|
||||
|
|
|
@ -64,9 +64,7 @@
|
|||
"unlimitedStorage",
|
||||
"clipboardRead",
|
||||
"clipboardWrite",
|
||||
"idle",
|
||||
"webRequest",
|
||||
"declarativeNetRequest"
|
||||
"idle"
|
||||
],
|
||||
"optional_permissions": ["nativeMessaging"],
|
||||
"host_permissions": ["http://*/*", "https://*/*"],
|
|
@ -30,12 +30,12 @@ export class PopupUtilsService {
|
|||
return this.privateMode;
|
||||
}
|
||||
|
||||
getContentScrollY(win: Window, scrollingContainer = "content"): number {
|
||||
getContentScrollY(win: Window, scrollingContainer = "main"): number {
|
||||
const content = win.document.getElementsByTagName(scrollingContainer)[0];
|
||||
return content.scrollTop;
|
||||
}
|
||||
|
||||
setContentScrollY(win: Window, scrollY: number, scrollingContainer = "content"): void {
|
||||
setContentScrollY(win: Window, scrollY: number, scrollingContainer = "main"): void {
|
||||
if (scrollY != null) {
|
||||
const content = win.document.getElementsByTagName(scrollingContainer)[0];
|
||||
content.scrollTop = scrollY;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { LockGuard as BaseLockGuardService } from "@bitwarden/angular/guards/loc
|
|||
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/guards/unauth.guard";
|
||||
import {
|
||||
JslibServicesModule,
|
||||
MEMORY_STORAGE,
|
||||
SECURE_STORAGE,
|
||||
} from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
|
@ -34,7 +35,7 @@ import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abs
|
|||
import { SendService } from "@bitwarden/common/abstractions/send.service";
|
||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||
import { StorageService as StorageServiceAbstraction } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync.service";
|
||||
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||
|
@ -185,8 +186,8 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: StorageServiceAbstraction,
|
||||
useFactory: getBgService<StorageServiceAbstraction>("storageService"),
|
||||
provide: AbstractStorageService,
|
||||
useFactory: getBgService<AbstractStorageService>("storageService"),
|
||||
deps: [],
|
||||
},
|
||||
{ provide: AppIdService, useFactory: getBgService<AppIdService>("appIdService"), deps: [] },
|
||||
|
@ -249,9 +250,13 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||
},
|
||||
{
|
||||
provide: SECURE_STORAGE,
|
||||
useFactory: getBgService<StorageServiceAbstraction>("secureStorageService"),
|
||||
useFactory: getBgService<AbstractStorageService>("secureStorageService"),
|
||||
deps: [],
|
||||
},
|
||||
{
|
||||
provide: MEMORY_STORAGE,
|
||||
useFactory: getBgService<AbstractStorageService>("memoryStorageService"),
|
||||
},
|
||||
{
|
||||
provide: StateServiceAbstraction,
|
||||
useFactory: getBgService<StateServiceAbstraction>("stateService"),
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
|
||||
export default class BrowserStorageService implements StorageService {
|
||||
private chromeStorageApi: any;
|
||||
|
||||
constructor() {
|
||||
this.chromeStorageApi = chrome.storage.local;
|
||||
}
|
||||
export default abstract class AbstractChromeStorageService implements AbstractStorageService {
|
||||
protected abstract chromeStorageApi: any;
|
||||
|
||||
async get<T>(key: string): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -23,7 +19,7 @@ export default class BrowserStorageService implements StorageService {
|
|||
return (await this.get(key)) != null;
|
||||
}
|
||||
|
||||
async save(key: string, obj: any): Promise<any> {
|
||||
async save(key: string, obj: any): Promise<void> {
|
||||
if (obj == null) {
|
||||
// Fix safari not liking null in set
|
||||
return new Promise<void>((resolve) => {
|
||||
|
@ -45,7 +41,7 @@ export default class BrowserStorageService implements StorageService {
|
|||
});
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<any> {
|
||||
async remove(key: string): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
this.chromeStorageApi.remove(key, () => {
|
||||
resolve();
|
|
@ -0,0 +1,5 @@
|
|||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
export interface AbstractKeyGenerationService {
|
||||
makeEphemeralKey(numBytes?: number): Promise<SymmetricCryptoKey>;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
||||
|
||||
export default class BrowserLocalStorageService extends AbstractChromeStorageService {
|
||||
protected chromeStorageApi: any = chrome.storage.local;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
|
||||
|
||||
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
||||
protected chromeStorageApi: any = (chrome.storage as any).session;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { AbstractKeyGenerationService } from "./abstractions/abstractKeyGeneration.service";
|
||||
|
||||
export class KeyGenerationService implements AbstractKeyGenerationService {
|
||||
constructor(private cryptoFunctionService: CryptoFunctionService) {}
|
||||
|
||||
async makeEphemeralKey(numBytes = 16): Promise<SymmetricCryptoKey> {
|
||||
const keyMaterial = await this.cryptoFunctionService.randomBytes(numBytes);
|
||||
const key = await this.cryptoFunctionService.hkdf(
|
||||
keyMaterial,
|
||||
"bitwarden-ephemeral",
|
||||
"ephemeral",
|
||||
64,
|
||||
"sha256"
|
||||
);
|
||||
return new SymmetricCryptoKey(key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
import { EncryptService } from "@bitwarden/common/src/services/encrypt.service";
|
||||
|
||||
import BrowserLocalStorageService from "./browserLocalStorage.service";
|
||||
import BrowserMemoryStorageService from "./browserMemoryStorage.service";
|
||||
import { KeyGenerationService } from "./keyGeneration.service";
|
||||
import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service";
|
||||
|
||||
describe("Browser Session Storage Service", () => {
|
||||
let encryptService: SubstituteOf<EncryptService>;
|
||||
let keyGenerationService: SubstituteOf<KeyGenerationService>;
|
||||
|
||||
let cache: Map<string, any>;
|
||||
const testObj = { a: 1, b: 2 };
|
||||
|
||||
let localStorage: BrowserLocalStorageService;
|
||||
let sessionStorage: BrowserMemoryStorageService;
|
||||
|
||||
const key = new SymmetricCryptoKey(
|
||||
Utils.fromUtf8ToArray("00000000000000000000000000000000").buffer
|
||||
);
|
||||
let getSessionKeySpy: jest.SpyInstance;
|
||||
const mockEnc = (input: string) => Promise.resolve(new EncString("ENCRYPTED" + input));
|
||||
|
||||
let sut: LocalBackedSessionStorageService;
|
||||
|
||||
beforeEach(() => {
|
||||
encryptService = Substitute.for();
|
||||
keyGenerationService = Substitute.for();
|
||||
|
||||
sut = new LocalBackedSessionStorageService(encryptService, keyGenerationService);
|
||||
|
||||
cache = sut["cache"];
|
||||
localStorage = sut["localStorage"];
|
||||
sessionStorage = sut["sessionStorage"];
|
||||
getSessionKeySpy = jest.spyOn(sut, "getSessionEncKey");
|
||||
getSessionKeySpy.mockResolvedValue(key);
|
||||
});
|
||||
|
||||
it("should exist", () => {
|
||||
expect(sut).toBeInstanceOf(LocalBackedSessionStorageService);
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it("should return from cache", async () => {
|
||||
cache.set("test", testObj);
|
||||
const result = await sut.get("test");
|
||||
expect(result).toStrictEqual(testObj);
|
||||
});
|
||||
|
||||
describe("not in cache", () => {
|
||||
const session = { test: testObj };
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(sut, "getSessionEncKey").mockResolvedValue(key);
|
||||
});
|
||||
|
||||
describe("no session retrieved", () => {
|
||||
let result: any;
|
||||
let spy: jest.SpyInstance;
|
||||
beforeEach(async () => {
|
||||
spy = jest.spyOn(sut, "getLocalSession").mockResolvedValue(null);
|
||||
result = await sut.get("test");
|
||||
});
|
||||
|
||||
it("should grab from session if not in cache", async () => {
|
||||
expect(spy).toHaveBeenCalledWith(key);
|
||||
});
|
||||
|
||||
it("should return null if session is null", async () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("session retrieved from storage", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(session);
|
||||
});
|
||||
|
||||
it("should return null if session does not have the key", async () => {
|
||||
const result = await sut.get("DNE");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return the value retrieved from session", async () => {
|
||||
const result = await sut.get("test");
|
||||
expect(result).toEqual(session.test);
|
||||
});
|
||||
|
||||
it("should set retrieved values in cache", async () => {
|
||||
await sut.get("test");
|
||||
expect(cache.has("test")).toBe(true);
|
||||
expect(cache.get("test")).toEqual(session.test);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("has", () => {
|
||||
it("should be false if `get` returns null", async () => {
|
||||
const spy = jest.spyOn(sut, "get");
|
||||
spy.mockResolvedValue(null);
|
||||
expect(await sut.has("test")).toBe(false);
|
||||
expect(spy).toHaveBeenCalledWith("test");
|
||||
});
|
||||
|
||||
it("should be true if `get` returns non-null", async () => {
|
||||
const spy = jest.spyOn(sut, "get");
|
||||
spy.mockResolvedValue({});
|
||||
expect(await sut.has("test")).toBe(true);
|
||||
expect(spy).toHaveBeenCalledWith("test");
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it("should save null", async () => {
|
||||
const spy = jest.spyOn(sut, "save");
|
||||
spy.mockResolvedValue(null);
|
||||
await sut.remove("test");
|
||||
expect(spy).toHaveBeenCalledWith("test", null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("save", () => {
|
||||
describe("caching", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(localStorage, "get").mockResolvedValue(null);
|
||||
jest.spyOn(sessionStorage, "get").mockResolvedValue(null);
|
||||
jest.spyOn(localStorage, "save").mockResolvedValue();
|
||||
jest.spyOn(sessionStorage, "save").mockResolvedValue();
|
||||
});
|
||||
|
||||
it("should remove key from cache if value is null", async () => {
|
||||
cache.set("test", {});
|
||||
const deleteSpy = jest.spyOn(cache, "delete");
|
||||
expect(cache.has("test")).toBe(true);
|
||||
await sut.save("test", null);
|
||||
expect(cache.has("test")).toBe(false);
|
||||
expect(deleteSpy).toHaveBeenCalledWith("test");
|
||||
});
|
||||
|
||||
it("should set cache if value is non-null", async () => {
|
||||
expect(cache.has("test")).toBe(false);
|
||||
const setSpy = jest.spyOn(cache, "set");
|
||||
await sut.save("test", testObj);
|
||||
expect(cache.get("test")).toBe(testObj);
|
||||
expect(setSpy).toHaveBeenCalledWith("test", testObj);
|
||||
});
|
||||
});
|
||||
|
||||
describe("local storing", () => {
|
||||
let setSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
setSpy = jest.spyOn(sut, "setLocalSession").mockResolvedValue();
|
||||
});
|
||||
|
||||
it("should store a new session", async () => {
|
||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(null);
|
||||
await sut.save("test", testObj);
|
||||
|
||||
expect(setSpy).toHaveBeenCalledWith({ test: testObj }, key);
|
||||
});
|
||||
|
||||
it("should update an existing session", async () => {
|
||||
const existingObj = { test: testObj };
|
||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(existingObj);
|
||||
await sut.save("test2", testObj);
|
||||
|
||||
expect(setSpy).toHaveBeenCalledWith({ test2: testObj, ...existingObj }, key);
|
||||
});
|
||||
|
||||
it("should overwrite an existing item in session", async () => {
|
||||
const existingObj = { test: {} };
|
||||
jest.spyOn(sut, "getLocalSession").mockResolvedValue(existingObj);
|
||||
await sut.save("test", testObj);
|
||||
|
||||
expect(setSpy).toHaveBeenCalledWith({ test: testObj }, key);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSessionKey", () => {
|
||||
beforeEach(() => {
|
||||
getSessionKeySpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should return the stored symmetric crypto key", async () => {
|
||||
jest.spyOn(sessionStorage, "get").mockResolvedValue({ ...key });
|
||||
const result = await sut.getSessionEncKey();
|
||||
|
||||
expect(result).toStrictEqual(key);
|
||||
});
|
||||
|
||||
describe("new key creation", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(sessionStorage, "get").mockResolvedValue(null);
|
||||
keyGenerationService.makeEphemeralKey().resolves(key);
|
||||
jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
|
||||
});
|
||||
|
||||
it("should create a symmetric crypto key", async () => {
|
||||
const result = await sut.getSessionEncKey();
|
||||
|
||||
expect(result).toStrictEqual(key);
|
||||
keyGenerationService.received(1).makeEphemeralKey();
|
||||
});
|
||||
|
||||
it("should store a symmetric crypto key if it makes one", async () => {
|
||||
const spy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
|
||||
await sut.getSessionEncKey();
|
||||
|
||||
expect(spy).toBeCalledWith(key);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getLocalSession", () => {
|
||||
it("should return null if session is null", async () => {
|
||||
const spy = jest.spyOn(localStorage, "get").mockResolvedValue(null);
|
||||
const result = await sut.getLocalSession(key);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(spy).toBeCalledWith("session");
|
||||
});
|
||||
|
||||
describe("non-null sessions", () => {
|
||||
const session = { test: "test" };
|
||||
const encSession = new EncString(JSON.stringify(session));
|
||||
const decryptedSession = JSON.stringify(session);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(localStorage, "get").mockResolvedValue(encSession.encryptedString);
|
||||
});
|
||||
|
||||
it("should decrypt returned sessions", async () => {
|
||||
encryptService.decryptToUtf8(encSession, key).resolves(decryptedSession);
|
||||
await sut.getLocalSession(key);
|
||||
encryptService.received(1).decryptToUtf8(encSession, key);
|
||||
});
|
||||
|
||||
it("should parse session", async () => {
|
||||
encryptService.decryptToUtf8(encSession, key).resolves(decryptedSession);
|
||||
const result = await sut.getLocalSession(key);
|
||||
expect(result).toEqual(session);
|
||||
});
|
||||
|
||||
it("should remove state if decryption fails", async () => {
|
||||
encryptService.decryptToUtf8(Arg.any(), Arg.any()).resolves(null);
|
||||
const setSessionEncKeySpy = jest.spyOn(sut, "setSessionEncKey").mockResolvedValue();
|
||||
const removeLocalSessionSpy = jest.spyOn(localStorage, "remove").mockResolvedValue();
|
||||
|
||||
const result = await sut.getLocalSession(key);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(setSessionEncKeySpy).toHaveBeenCalledWith(null);
|
||||
expect(removeLocalSessionSpy).toHaveBeenCalledWith("session");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("setLocalSession", () => {
|
||||
const testSession = { test: "a" };
|
||||
const testJSON = JSON.stringify(testSession);
|
||||
|
||||
it("should encrypt a stringified session", async () => {
|
||||
encryptService.encrypt(Arg.any(), Arg.any()).mimicks(mockEnc);
|
||||
jest.spyOn(localStorage, "save").mockResolvedValue();
|
||||
await sut.setLocalSession(testSession, key);
|
||||
|
||||
encryptService.received(1).encrypt(testJSON, key);
|
||||
});
|
||||
|
||||
it("should remove local session if null", async () => {
|
||||
encryptService.encrypt(Arg.any(), Arg.any()).resolves(null);
|
||||
const spy = jest.spyOn(localStorage, "remove").mockResolvedValue();
|
||||
await sut.setLocalSession(null, key);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith("session");
|
||||
});
|
||||
|
||||
it("should save encrypted string", async () => {
|
||||
encryptService.encrypt(Arg.any(), Arg.any()).mimicks(mockEnc);
|
||||
const spy = jest.spyOn(localStorage, "save").mockResolvedValue();
|
||||
await sut.setLocalSession(testSession, key);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith("session", (await mockEnc(testJSON)).encryptedString);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setSessionKey", () => {
|
||||
it("should remove if null", async () => {
|
||||
const spy = jest.spyOn(sessionStorage, "remove").mockResolvedValue();
|
||||
await sut.setSessionEncKey(null);
|
||||
expect(spy).toHaveBeenCalledWith("localEncryptionKey");
|
||||
});
|
||||
|
||||
it("should save key when not null", async () => {
|
||||
const spy = jest.spyOn(sessionStorage, "save").mockResolvedValue();
|
||||
await sut.setSessionEncKey(key);
|
||||
expect(spy).toHaveBeenCalledWith("localEncryptionKey", key);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,107 @@
|
|||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { AbstractKeyGenerationService } from "./abstractions/abstractKeyGeneration.service";
|
||||
import BrowserLocalStorageService from "./browserLocalStorage.service";
|
||||
import BrowserMemoryStorageService from "./browserMemoryStorage.service";
|
||||
|
||||
const keys = {
|
||||
encKey: "localEncryptionKey",
|
||||
sessionKey: "session",
|
||||
};
|
||||
|
||||
export class LocalBackedSessionStorageService extends AbstractStorageService {
|
||||
private cache = new Map<string, any>();
|
||||
private localStorage = new BrowserLocalStorageService();
|
||||
private sessionStorage = new BrowserMemoryStorageService();
|
||||
|
||||
constructor(
|
||||
private encryptService: AbstractEncryptService,
|
||||
private keyGenerationService: AbstractKeyGenerationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T> {
|
||||
if (this.cache.has(key)) {
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
const session = await this.getLocalSession(await this.getSessionEncKey());
|
||||
if (session == null || !Object.keys(session).includes(key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.cache.set(key, session[key]);
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return (await this.get(key)) != null;
|
||||
}
|
||||
|
||||
async save(key: string, obj: any): Promise<void> {
|
||||
if (obj == null) {
|
||||
this.cache.delete(key);
|
||||
} else {
|
||||
this.cache.set(key, obj);
|
||||
}
|
||||
|
||||
const sessionEncKey = await this.getSessionEncKey();
|
||||
const localSession = (await this.getLocalSession(sessionEncKey)) ?? {};
|
||||
localSession[key] = obj;
|
||||
await this.setLocalSession(localSession, sessionEncKey);
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<void> {
|
||||
await this.save(key, null);
|
||||
}
|
||||
|
||||
async getLocalSession(encKey: SymmetricCryptoKey): Promise<any> {
|
||||
const local = await this.localStorage.get<string>(keys.sessionKey);
|
||||
|
||||
if (local == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionJson = await this.encryptService.decryptToUtf8(new EncString(local), encKey);
|
||||
if (sessionJson == null) {
|
||||
// Error with decryption -- session is lost, delete state and key and start over
|
||||
await this.setSessionEncKey(null);
|
||||
await this.localStorage.remove(keys.sessionKey);
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(sessionJson);
|
||||
}
|
||||
|
||||
async setLocalSession(session: any, key: SymmetricCryptoKey) {
|
||||
const jsonSession = JSON.stringify(session);
|
||||
const encSession = await this.encryptService.encrypt(jsonSession, key);
|
||||
|
||||
if (encSession == null) {
|
||||
return await this.localStorage.remove(keys.sessionKey);
|
||||
}
|
||||
await this.localStorage.save(keys.sessionKey, encSession.encryptedString);
|
||||
}
|
||||
|
||||
async getSessionEncKey(): Promise<SymmetricCryptoKey> {
|
||||
let storedKey = (await this.sessionStorage.get(keys.encKey)) as SymmetricCryptoKey;
|
||||
if (storedKey == null || Object.keys(storedKey).length == 0) {
|
||||
storedKey = await this.keyGenerationService.makeEphemeralKey();
|
||||
await this.setSessionEncKey(storedKey);
|
||||
}
|
||||
return SymmetricCryptoKey.initFromJson(
|
||||
Object.create(SymmetricCryptoKey.prototype, Object.getOwnPropertyDescriptors(storedKey))
|
||||
);
|
||||
}
|
||||
|
||||
async setSessionEncKey(input: SymmetricCryptoKey): Promise<void> {
|
||||
if (input == null) {
|
||||
await this.sessionStorage.remove(keys.encKey);
|
||||
} else {
|
||||
await this.sessionStorage.save(keys.encKey, input);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||
import { State } from "@bitwarden/common/models/domain/state";
|
||||
import { SendView } from "@bitwarden/common/models/view/sendView";
|
||||
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
|
||||
|
||||
import { Account } from "../models/account";
|
||||
import { BrowserComponentState } from "../models/browserComponentState";
|
||||
import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState";
|
||||
import { BrowserSendComponentState } from "../models/browserSendComponentState";
|
||||
|
||||
import { StateService } from "./state.service";
|
||||
|
||||
describe("Browser State Service", () => {
|
||||
let secureStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let diskStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let memoryStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let stateMigrationService: SubstituteOf<StateMigrationService>;
|
||||
let stateFactory: SubstituteOf<StateFactory<GlobalState, Account>>;
|
||||
let useAccountCache: boolean;
|
||||
|
||||
let state: State<GlobalState, Account>;
|
||||
const userId = "userId";
|
||||
|
||||
let sut: StateService;
|
||||
|
||||
beforeEach(() => {
|
||||
secureStorageService = Substitute.for();
|
||||
diskStorageService = Substitute.for();
|
||||
memoryStorageService = Substitute.for();
|
||||
logService = Substitute.for();
|
||||
stateMigrationService = Substitute.for();
|
||||
stateFactory = Substitute.for();
|
||||
useAccountCache = true;
|
||||
|
||||
state = new State(new GlobalState());
|
||||
state.accounts[userId] = new Account({
|
||||
profile: { userId: userId },
|
||||
});
|
||||
state.activeUserId = userId;
|
||||
const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state)));
|
||||
memoryStorageService.get("state").mimicks(stateGetter);
|
||||
|
||||
sut = new StateService(
|
||||
diskStorageService,
|
||||
secureStorageService,
|
||||
memoryStorageService,
|
||||
logService,
|
||||
stateMigrationService,
|
||||
stateFactory,
|
||||
useAccountCache
|
||||
);
|
||||
});
|
||||
|
||||
describe("getBrowserGroupingComponentState", () => {
|
||||
it("should return a BrowserGroupingsComponentState", async () => {
|
||||
state.accounts[userId].groupings = new BrowserGroupingsComponentState();
|
||||
|
||||
const actual = await sut.getBrowserGroupingComponentState();
|
||||
expect(actual).toBeInstanceOf(BrowserGroupingsComponentState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBrowserCipherComponentState", () => {
|
||||
it("should return a BrowserComponentState", async () => {
|
||||
const componentState = new BrowserComponentState();
|
||||
componentState.scrollY = 0;
|
||||
componentState.searchText = "test";
|
||||
state.accounts[userId].ciphers = componentState;
|
||||
|
||||
const actual = await sut.getBrowserCipherComponentState();
|
||||
expect(actual).toStrictEqual(componentState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBrowserSendComponentState", () => {
|
||||
it("should return a BrowserSendComponentState", async () => {
|
||||
const sendState = new BrowserSendComponentState();
|
||||
sendState.sends = [new SendView(), new SendView()];
|
||||
sendState.typeCounts = new Map<SendType, number>([
|
||||
[SendType.File, 3],
|
||||
[SendType.Text, 5],
|
||||
]);
|
||||
state.accounts[userId].send = sendState;
|
||||
|
||||
const actual = await sut.getBrowserSendComponentState();
|
||||
expect(actual).toBeInstanceOf(BrowserSendComponentState);
|
||||
expect(actual).toMatchObject(sendState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBrowserSendTypeComponentState", () => {
|
||||
it("should return a BrowserComponentState", async () => {
|
||||
const componentState = new BrowserComponentState();
|
||||
componentState.scrollY = 0;
|
||||
componentState.searchText = "test";
|
||||
state.accounts[userId].sendType = componentState;
|
||||
|
||||
const actual = await sut.getBrowserSendTypeComponentState();
|
||||
expect(actual).toStrictEqual(componentState);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,9 @@
|
|||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
|
||||
import { StateService as BaseStateService } from "@bitwarden/common/services/state.service";
|
||||
import {
|
||||
StateService as BaseStateService,
|
||||
withPrototype,
|
||||
} from "@bitwarden/common/services/state.service";
|
||||
|
||||
import { Account } from "../models/account";
|
||||
import { BrowserComponentState } from "../models/browserComponentState";
|
||||
|
@ -24,15 +27,17 @@ export class StateService
|
|||
// Check that there is an account in memory before considering the user authenticated
|
||||
return (
|
||||
(await super.getIsAuthenticated(options)) &&
|
||||
(await this.getAccount(this.defaultInMemoryOptions)) != null
|
||||
(await this.getAccount(await this.defaultInMemoryOptions())) != null
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserGroupingsComponentState)
|
||||
async getBrowserGroupingComponentState(
|
||||
options?: StorageOptions
|
||||
): Promise<BrowserGroupingsComponentState> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||
?.groupings;
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.groupings;
|
||||
}
|
||||
|
||||
async setBrowserGroupingComponentState(
|
||||
|
@ -40,15 +45,20 @@ export class StateService
|
|||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
account.groupings = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserComponentState)
|
||||
async getBrowserCipherComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||
?.ciphers;
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.ciphers;
|
||||
}
|
||||
|
||||
async setBrowserCipherComponentState(
|
||||
|
@ -56,15 +66,20 @@ export class StateService
|
|||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
account.ciphers = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserSendComponentState)
|
||||
async getBrowserSendComponentState(options?: StorageOptions): Promise<BrowserSendComponentState> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||
?.send;
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.send;
|
||||
}
|
||||
|
||||
async setBrowserSendComponentState(
|
||||
|
@ -72,14 +87,20 @@ export class StateService
|
|||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
account.send = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
}
|
||||
|
||||
@withPrototype(BrowserComponentState)
|
||||
async getBrowserSendTypeComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
|
||||
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))
|
||||
?.sendType;
|
||||
return (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||
)?.sendType;
|
||||
}
|
||||
|
||||
async setBrowserSendTypeComponentState(
|
||||
|
@ -87,9 +108,12 @@ export class StateService
|
|||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, this.defaultInMemoryOptions)
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
account.sendType = value;
|
||||
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions));
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultInMemoryOptions())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Add chrome storage api
|
||||
const get = jest.fn();
|
||||
const set = jest.fn();
|
||||
const has = jest.fn();
|
||||
const remove = jest.fn();
|
||||
const QUOTA_BYTES = 10;
|
||||
const getBytesInUse = jest.fn();
|
||||
const clear = jest.fn();
|
||||
global.chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
set,
|
||||
get,
|
||||
remove,
|
||||
QUOTA_BYTES,
|
||||
getBytesInUse,
|
||||
clear,
|
||||
},
|
||||
session: {
|
||||
set,
|
||||
get,
|
||||
has,
|
||||
remove,
|
||||
},
|
||||
},
|
||||
} as any;
|
|
@ -11,6 +11,9 @@ if (process.env.NODE_ENV == null) {
|
|||
process.env.NODE_ENV = "development";
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
const manifestVersion = process.env.MANIFEST_VERSION == 3 ? 3 : 2;
|
||||
|
||||
console.log(`Building Manifest Version ${manifestVersion} app`);
|
||||
|
||||
const moduleRules = [
|
||||
{
|
||||
|
@ -72,8 +75,8 @@ const plugins = [
|
|||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
process.env.MANIFEST_VERSION == 3
|
||||
? { from: "./src/manifest.json.v3", to: "manifest.json" }
|
||||
manifestVersion == 3
|
||||
? { from: "./src/manifest.v3.json", to: "manifest.json" }
|
||||
: "./src/manifest.json",
|
||||
{ from: "./src/_locales", to: "_locales" },
|
||||
{ from: "./src/images", to: "images" },
|
||||
|
@ -123,7 +126,7 @@ const config = {
|
|||
"notification/bar": "./src/notification/bar.js",
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimize: ENV !== "development",
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
exclude: [/content\/.*/, /notification\/.*/],
|
||||
|
|
|
@ -5,7 +5,7 @@ const { compilerOptions } = require("./tsconfig");
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
|
|
@ -17,12 +17,14 @@ import { CipherService } from "@bitwarden/common/services/cipher.service";
|
|||
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||
import { CryptoService } from "@bitwarden/common/services/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
|
||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
|
||||
import { FolderService } from "@bitwarden/common/services/folder.service";
|
||||
import { ImportService } from "@bitwarden/common/services/import.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
import { NoopMessagingService } from "@bitwarden/common/services/noopMessaging.service";
|
||||
import { OrganizationService } from "@bitwarden/common/services/organization.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
|
||||
|
@ -61,6 +63,7 @@ export class Main {
|
|||
messagingService: NoopMessagingService;
|
||||
storageService: LowdbStorageService;
|
||||
secureStorageService: NodeEnvSecureStorageService;
|
||||
memoryStorageService: MemoryStorageService;
|
||||
i18nService: I18nService;
|
||||
platformUtilsService: CliPlatformUtilsService;
|
||||
cryptoService: CryptoService;
|
||||
|
@ -82,6 +85,7 @@ export class Main {
|
|||
exportService: ExportService;
|
||||
searchService: SearchService;
|
||||
cryptoFunctionService: NodeCryptoFunctionService;
|
||||
encryptService: EncryptService;
|
||||
authService: AuthService;
|
||||
policyService: PolicyService;
|
||||
program: Program;
|
||||
|
@ -122,6 +126,7 @@ export class Main {
|
|||
(level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info
|
||||
);
|
||||
this.cryptoFunctionService = new NodeCryptoFunctionService();
|
||||
this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true);
|
||||
this.storageService = new LowdbStorageService(this.logService, null, p, false, true);
|
||||
this.secureStorageService = new NodeEnvSecureStorageService(
|
||||
this.storageService,
|
||||
|
@ -129,6 +134,8 @@ export class Main {
|
|||
() => this.cryptoService
|
||||
);
|
||||
|
||||
this.memoryStorageService = new MemoryStorageService();
|
||||
|
||||
this.stateMigrationService = new StateMigrationService(
|
||||
this.storageService,
|
||||
this.secureStorageService,
|
||||
|
@ -138,6 +145,7 @@ export class Main {
|
|||
this.stateService = new StateService(
|
||||
this.storageService,
|
||||
this.secureStorageService,
|
||||
this.memoryStorageService,
|
||||
this.logService,
|
||||
this.stateMigrationService,
|
||||
new StateFactory(GlobalState, Account)
|
||||
|
@ -145,6 +153,7 @@ export class Main {
|
|||
|
||||
this.cryptoService = new CryptoService(
|
||||
this.cryptoFunctionService,
|
||||
this.encryptService,
|
||||
this.platformUtilsService,
|
||||
this.logService,
|
||||
this.stateService
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
export class NodeEnvSecureStorageService implements StorageService {
|
||||
export class NodeEnvSecureStorageService implements AbstractStorageService {
|
||||
constructor(
|
||||
private storageService: StorageService,
|
||||
private storageService: AbstractStorageService,
|
||||
private logService: LogService,
|
||||
private cryptoService: () => CryptoService
|
||||
) {}
|
||||
|
|
|
@ -8,8 +8,10 @@ import {
|
|||
CLIENT_TYPE,
|
||||
LOCALES_DIRECTORY,
|
||||
SYSTEM_LANGUAGE,
|
||||
MEMORY_STORAGE,
|
||||
} from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
|
@ -23,11 +25,12 @@ import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@
|
|||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||
import { StorageService as StorageServiceAbstraction } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
|
||||
import { ClientType } from "@bitwarden/common/enums/clientType";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
import { SystemService } from "@bitwarden/common/services/system.service";
|
||||
import { ElectronCryptoService } from "@bitwarden/electron/services/electronCrypto.service";
|
||||
import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service";
|
||||
|
@ -96,13 +99,15 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||
useClass: ElectronRendererMessagingService,
|
||||
deps: [BroadcasterServiceAbstraction],
|
||||
},
|
||||
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService },
|
||||
{ provide: AbstractStorageService, useClass: ElectronRendererStorageService },
|
||||
{ provide: SECURE_STORAGE, useClass: ElectronRendererSecureStorageService },
|
||||
{ provide: MEMORY_STORAGE, useClass: MemoryStorageService },
|
||||
{
|
||||
provide: CryptoServiceAbstraction,
|
||||
useClass: ElectronCryptoService,
|
||||
deps: [
|
||||
CryptoFunctionServiceAbstraction,
|
||||
AbstractEncryptService,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
LogServiceAbstraction,
|
||||
StateServiceAbstraction,
|
||||
|
@ -123,8 +128,9 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||
provide: StateServiceAbstraction,
|
||||
useClass: StateService,
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
AbstractStorageService,
|
||||
SECURE_STORAGE,
|
||||
MEMORY_STORAGE,
|
||||
LogService,
|
||||
StateMigrationServiceAbstraction,
|
||||
STATE_FACTORY,
|
||||
|
|
|
@ -4,6 +4,7 @@ import { app } from "electron";
|
|||
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
import { StateService } from "@bitwarden/common/services/state.service";
|
||||
import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service";
|
||||
import { ElectronMainMessagingService } from "@bitwarden/electron/services/electronMainMessaging.service";
|
||||
|
@ -25,6 +26,7 @@ export class Main {
|
|||
logService: ElectronLogService;
|
||||
i18nService: I18nService;
|
||||
storageService: ElectronStorageService;
|
||||
memoryStorageService: MemoryStorageService;
|
||||
messagingService: ElectronMainMessagingService;
|
||||
stateService: StateService;
|
||||
desktopCredentialStorageListener: DesktopCredentialStorageListener;
|
||||
|
@ -74,6 +76,7 @@ export class Main {
|
|||
storageDefaults["global.vaultTimeout"] = -1;
|
||||
storageDefaults["global.vaultTimeoutAction"] = "lock";
|
||||
this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults);
|
||||
this.memoryStorageService = new MemoryStorageService();
|
||||
|
||||
// TODO: this state service will have access to on disk storage, but not in memory storage.
|
||||
// If we could get this to work using the stateService singleton that the rest of the app uses we could save
|
||||
|
@ -81,6 +84,7 @@ export class Main {
|
|||
this.stateService = new StateService(
|
||||
this.storageService,
|
||||
null,
|
||||
this.memoryStorageService,
|
||||
this.logService,
|
||||
null,
|
||||
new StateFactory(GlobalState, Account),
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
STATE_SERVICE_USE_CACHE,
|
||||
LOCALES_DIRECTORY,
|
||||
SYSTEM_LANGUAGE,
|
||||
MEMORY_STORAGE,
|
||||
} from "@bitwarden/angular/services/jslib-services.module";
|
||||
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
|
@ -23,9 +24,10 @@ import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@
|
|||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||
import { StorageService as StorageServiceAbstraction } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { ImportService } from "@bitwarden/common/services/import.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
|
||||
import { StateService as StateServiceAbstraction } from "../../abstractions/state.service";
|
||||
import { Account } from "../../models/account";
|
||||
|
@ -33,7 +35,6 @@ import { GlobalState } from "../../models/globalState";
|
|||
import { BroadcasterMessagingService } from "../../services/broadcasterMessaging.service";
|
||||
import { HtmlStorageService } from "../../services/htmlStorage.service";
|
||||
import { I18nService } from "../../services/i18n.service";
|
||||
import { MemoryStorageService } from "../../services/memoryStorage.service";
|
||||
import { PasswordRepromptService } from "../../services/passwordReprompt.service";
|
||||
import { StateService } from "../../services/state.service";
|
||||
import { StateMigrationService } from "../../services/stateMigration.service";
|
||||
|
@ -77,13 +78,17 @@ import { RouterService } from "./router.service";
|
|||
useClass: I18nService,
|
||||
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
|
||||
},
|
||||
{ provide: StorageServiceAbstraction, useClass: HtmlStorageService },
|
||||
{ provide: AbstractStorageService, useClass: HtmlStorageService },
|
||||
{
|
||||
provide: SECURE_STORAGE,
|
||||
// TODO: platformUtilsService.isDev has a helper for this, but using that service here results in a circular dependency.
|
||||
// We have a tech debt item in the backlog to break up platformUtilsService, but in the meantime simply checking the environement here is less cumbersome.
|
||||
useClass: process.env.NODE_ENV === "development" ? HtmlStorageService : MemoryStorageService,
|
||||
},
|
||||
{
|
||||
provide: MEMORY_STORAGE,
|
||||
useClass: MemoryStorageService,
|
||||
},
|
||||
{
|
||||
provide: PlatformUtilsServiceAbstraction,
|
||||
useClass: WebPlatformUtilsService,
|
||||
|
@ -106,14 +111,15 @@ import { RouterService } from "./router.service";
|
|||
{
|
||||
provide: StateMigrationServiceAbstraction,
|
||||
useClass: StateMigrationService,
|
||||
deps: [StorageServiceAbstraction, SECURE_STORAGE, STATE_FACTORY],
|
||||
deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
|
||||
},
|
||||
{
|
||||
provide: StateServiceAbstraction,
|
||||
useClass: StateService,
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
AbstractStorageService,
|
||||
SECURE_STORAGE,
|
||||
MEMORY_STORAGE,
|
||||
LogService,
|
||||
StateMigrationServiceAbstraction,
|
||||
STATE_FACTORY,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "@bitwarden/common/enums/htmlStorageLocation";
|
||||
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
|
||||
|
||||
@Injectable()
|
||||
export class HtmlStorageService implements StorageService {
|
||||
export class HtmlStorageService implements AbstractStorageService {
|
||||
get defaultOptions(): StorageOptions {
|
||||
return { htmlStorageLocation: HtmlStorageLocation.Session };
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export class StateService
|
|||
}
|
||||
|
||||
async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.getEncryptedCiphers(options);
|
||||
}
|
||||
|
||||
|
@ -45,14 +45,14 @@ export class StateService
|
|||
value: { [id: string]: CipherData },
|
||||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.setEncryptedCiphers(value, options);
|
||||
}
|
||||
|
||||
async getEncryptedCollections(
|
||||
options?: StorageOptions
|
||||
): Promise<{ [id: string]: CollectionData }> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.getEncryptedCollections(options);
|
||||
}
|
||||
|
||||
|
@ -60,12 +60,12 @@ export class StateService
|
|||
value: { [id: string]: CollectionData },
|
||||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.setEncryptedCollections(value, options);
|
||||
}
|
||||
|
||||
async getEncryptedFolders(options?: StorageOptions): Promise<{ [id: string]: FolderData }> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.getEncryptedFolders(options);
|
||||
}
|
||||
|
||||
|
@ -73,12 +73,12 @@ export class StateService
|
|||
value: { [id: string]: FolderData },
|
||||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.setEncryptedFolders(value, options);
|
||||
}
|
||||
|
||||
async getEncryptedSends(options?: StorageOptions): Promise<{ [id: string]: SendData }> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.getEncryptedSends(options);
|
||||
}
|
||||
|
||||
|
@ -86,17 +86,17 @@ export class StateService
|
|||
value: { [id: string]: SendData },
|
||||
options?: StorageOptions
|
||||
): Promise<void> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.setEncryptedSends(value, options);
|
||||
}
|
||||
|
||||
override async getLastSync(options?: StorageOptions): Promise<string> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.getLastSync(options);
|
||||
}
|
||||
|
||||
override async setLastSync(value: string, options?: StorageOptions): Promise<void> {
|
||||
options = this.reconcileOptions(options, this.defaultInMemoryOptions);
|
||||
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
|
||||
return await super.setLastSync(value, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,18 +38,23 @@
|
|||
}
|
||||
},
|
||||
"jest.disabledWorkspaceFolders": [
|
||||
"root",
|
||||
"browser",
|
||||
"cli",
|
||||
"desktop",
|
||||
"libs",
|
||||
"web vault",
|
||||
"web vault (bit)",
|
||||
"desktop"
|
||||
],
|
||||
"jest.jestCommandLine": "npx jest"
|
||||
},
|
||||
"extensions": {
|
||||
"jest.jestCommandLine": "npx jest",
|
||||
"angular.enable-strict-mode-prompt": false
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"orta.vscode-jest",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"Angular.ng-template"
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = {
|
|||
displayName: "libs/angular tests",
|
||||
preset: "jest-preset-angular",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
|
|
@ -2,6 +2,7 @@ import { InjectionToken, Injector, LOCALE_ID, NgModule } from "@angular/core";
|
|||
|
||||
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
|
||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
|
@ -32,7 +33,7 @@ import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstrac
|
|||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||
import { StorageService as StorageServiceAbstraction } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync.service";
|
||||
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||
|
@ -51,6 +52,7 @@ import { CipherService } from "@bitwarden/common/services/cipher.service";
|
|||
import { CollectionService } from "@bitwarden/common/services/collection.service";
|
||||
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||
import { CryptoService } from "@bitwarden/common/services/crypto.service";
|
||||
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/services/environment.service";
|
||||
import { EventService } from "@bitwarden/common/services/event.service";
|
||||
import { ExportService } from "@bitwarden/common/services/export.service";
|
||||
|
@ -86,7 +88,8 @@ import { PasswordRepromptService } from "./passwordReprompt.service";
|
|||
import { ValidationService } from "./validation.service";
|
||||
|
||||
export const WINDOW = new InjectionToken<Window>("WINDOW");
|
||||
export const SECURE_STORAGE = new InjectionToken<StorageServiceAbstraction>("SECURE_STORAGE");
|
||||
export const MEMORY_STORAGE = new InjectionToken<AbstractStorageService>("MEMORY_STORAGE");
|
||||
export const SECURE_STORAGE = new InjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
||||
export const STATE_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
|
||||
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
|
||||
export const LOGOUT_CALLBACK = new InjectionToken<(expired: boolean, userId?: string) => void>(
|
||||
|
@ -142,7 +145,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||
{
|
||||
provide: AppIdServiceAbstraction,
|
||||
useClass: AppIdService,
|
||||
deps: [StorageServiceAbstraction],
|
||||
deps: [AbstractStorageService],
|
||||
},
|
||||
{
|
||||
provide: AuditServiceAbstraction,
|
||||
|
@ -233,6 +236,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||
useClass: CryptoService,
|
||||
deps: [
|
||||
CryptoFunctionServiceAbstraction,
|
||||
AbstractEncryptService,
|
||||
PlatformUtilsServiceAbstraction,
|
||||
LogService,
|
||||
StateServiceAbstraction,
|
||||
|
@ -315,8 +319,9 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||
provide: StateServiceAbstraction,
|
||||
useClass: StateService,
|
||||
deps: [
|
||||
StorageServiceAbstraction,
|
||||
AbstractStorageService,
|
||||
SECURE_STORAGE,
|
||||
MEMORY_STORAGE,
|
||||
LogService,
|
||||
StateMigrationServiceAbstraction,
|
||||
STATE_FACTORY,
|
||||
|
@ -326,7 +331,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||
{
|
||||
provide: StateMigrationServiceAbstraction,
|
||||
useClass: StateMigrationService,
|
||||
deps: [StorageServiceAbstraction, SECURE_STORAGE, STATE_FACTORY],
|
||||
deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
|
||||
},
|
||||
{
|
||||
provide: ExportServiceAbstraction,
|
||||
|
@ -362,6 +367,11 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
|
|||
useClass: WebCryptoFunctionService,
|
||||
deps: [WINDOW],
|
||||
},
|
||||
{
|
||||
provide: AbstractEncryptService,
|
||||
useClass: EncryptService,
|
||||
deps: [CryptoFunctionServiceAbstraction, LogService, true], // Log mac failures = true
|
||||
},
|
||||
{
|
||||
provide: EventServiceAbstraction,
|
||||
useClass: EventService,
|
||||
|
|
|
@ -8,7 +8,7 @@ module.exports = {
|
|||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { Account } from "@bitwarden/common/models/domain/account";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||
import { State } from "@bitwarden/common/models/domain/state";
|
||||
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
import { StateService } from "@bitwarden/common/services/state.service";
|
||||
import { StateMigrationService } from "@bitwarden/common/services/stateMigration.service";
|
||||
|
||||
describe("Browser State Service backed by chrome.storage api", () => {
|
||||
let secureStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let diskStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let memoryStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let logService: SubstituteOf<LogService>;
|
||||
let stateMigrationService: SubstituteOf<StateMigrationService>;
|
||||
let stateFactory: SubstituteOf<StateFactory<GlobalState, Account>>;
|
||||
let useAccountCache: boolean;
|
||||
|
||||
let state: State<GlobalState, Account>;
|
||||
const userId = "userId";
|
||||
|
||||
let sut: StateService;
|
||||
|
||||
beforeEach(() => {
|
||||
secureStorageService = Substitute.for();
|
||||
diskStorageService = Substitute.for();
|
||||
memoryStorageService = Substitute.for();
|
||||
logService = Substitute.for();
|
||||
stateMigrationService = Substitute.for();
|
||||
stateFactory = Substitute.for();
|
||||
useAccountCache = true;
|
||||
|
||||
state = new State(new GlobalState());
|
||||
const stateGetter = (key: string) => Promise.resolve(JSON.parse(JSON.stringify(state)));
|
||||
memoryStorageService.get("state").mimicks(stateGetter);
|
||||
memoryStorageService
|
||||
.save("state", Arg.any(), Arg.any())
|
||||
.mimicks((key: string, obj: any, options: StorageOptions) => {
|
||||
return new Promise(() => {
|
||||
state = obj;
|
||||
});
|
||||
});
|
||||
|
||||
sut = new StateService(
|
||||
diskStorageService,
|
||||
secureStorageService,
|
||||
memoryStorageService,
|
||||
logService,
|
||||
stateMigrationService,
|
||||
stateFactory,
|
||||
useAccountCache
|
||||
);
|
||||
});
|
||||
|
||||
describe("account state getters", () => {
|
||||
beforeEach(() => {
|
||||
state.accounts[userId] = createAccount(userId);
|
||||
state.activeUserId = userId;
|
||||
});
|
||||
|
||||
describe("getCryptoMasterKey", () => {
|
||||
it("should return the stored SymmetricCryptoKey", async () => {
|
||||
const key = new SymmetricCryptoKey(new Uint8Array(32).buffer);
|
||||
state.accounts[userId].keys.cryptoMasterKey = key;
|
||||
|
||||
const actual = await sut.getCryptoMasterKey();
|
||||
expect(actual).toBeInstanceOf(SymmetricCryptoKey);
|
||||
expect(actual).toMatchObject(key);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createAccount(userId: string): Account {
|
||||
return new Account({
|
||||
profile: { userId: userId },
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { StateVersion } from "@bitwarden/common/enums/stateVersion";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { Account } from "@bitwarden/common/models/domain/account";
|
||||
|
@ -10,15 +10,15 @@ import { StateMigrationService } from "@bitwarden/common/services/stateMigration
|
|||
const userId = "USER_ID";
|
||||
|
||||
describe("State Migration Service", () => {
|
||||
let storageService: SubstituteOf<StorageService>;
|
||||
let secureStorageService: SubstituteOf<StorageService>;
|
||||
let storageService: SubstituteOf<AbstractStorageService>;
|
||||
let secureStorageService: SubstituteOf<AbstractStorageService>;
|
||||
let stateFactory: SubstituteOf<StateFactory>;
|
||||
|
||||
let stateMigrationService: StateMigrationService;
|
||||
|
||||
beforeEach(() => {
|
||||
storageService = Substitute.for<StorageService>();
|
||||
secureStorageService = Substitute.for<StorageService>();
|
||||
storageService = Substitute.for<AbstractStorageService>();
|
||||
secureStorageService = Substitute.for<AbstractStorageService>();
|
||||
stateFactory = Substitute.for<StateFactory>();
|
||||
|
||||
stateMigrationService = new StateMigrationService(
|
||||
|
@ -28,7 +28,7 @@ describe("State Migration Service", () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe("StateVersion 3 to 4 migration", async () => {
|
||||
describe("StateVersion 3 to 4 migration", () => {
|
||||
beforeEach(() => {
|
||||
const globalVersion3: Partial<GlobalState> = {
|
||||
stateVersion: StateVersion.Three,
|
|
@ -0,0 +1,7 @@
|
|||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
export abstract class AbstractEncryptService {
|
||||
abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString>;
|
||||
abstract decryptToUtf8(encString: EncString, key: SymmetricCryptoKey): Promise<string>;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import { StorageOptions } from "../models/domain/storageOptions";
|
||||
|
||||
export abstract class StorageService {
|
||||
get: <T>(key: string, options?: StorageOptions) => Promise<T>;
|
||||
has: (key: string, options?: StorageOptions) => Promise<boolean>;
|
||||
save: (key: string, obj: any, options?: StorageOptions) => Promise<any>;
|
||||
remove: (key: string, options?: StorageOptions) => Promise<any>;
|
||||
export abstract class AbstractStorageService {
|
||||
abstract get<T>(key: string, options?: StorageOptions): Promise<T>;
|
||||
abstract has(key: string, options?: StorageOptions): Promise<boolean>;
|
||||
abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;
|
||||
abstract remove(key: string, options?: StorageOptions): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { SymmetricCryptoKey } from "./symmetricCryptoKey";
|
|||
export class EncryptionPair<TEncrypted, TDecrypted> {
|
||||
encrypted?: TEncrypted;
|
||||
decrypted?: TDecrypted;
|
||||
decryptedSerialized?: string;
|
||||
}
|
||||
|
||||
export class DataEncryptionPair<TEncrypted, TDecrypted> {
|
||||
|
@ -76,6 +77,7 @@ export class AccountKeys {
|
|||
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
|
||||
legacyEtmKey?: SymmetricCryptoKey;
|
||||
publicKey?: ArrayBuffer;
|
||||
publicKeySerialized?: string;
|
||||
apiKeyClientSecret?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,4 +54,22 @@ export class SymmetricCryptoKey {
|
|||
this.macKeyB64 = Utils.fromBufferToB64(this.macKey);
|
||||
}
|
||||
}
|
||||
|
||||
static initFromJson(jsonResult: SymmetricCryptoKey): SymmetricCryptoKey {
|
||||
if (jsonResult == null) {
|
||||
return jsonResult;
|
||||
}
|
||||
|
||||
if (jsonResult.keyB64 != null) {
|
||||
jsonResult.key = Utils.fromB64ToArray(jsonResult.keyB64).buffer;
|
||||
}
|
||||
if (jsonResult.encKeyB64 != null) {
|
||||
jsonResult.encKey = Utils.fromB64ToArray(jsonResult.encKeyB64).buffer;
|
||||
}
|
||||
if (jsonResult.macKeyB64 != null) {
|
||||
jsonResult.macKey = Utils.fromB64ToArray(jsonResult.macKeyB64).buffer;
|
||||
}
|
||||
|
||||
return jsonResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { AppIdService as AppIdServiceAbstraction } from "../abstractions/appId.service";
|
||||
import { StorageService } from "../abstractions/storage.service";
|
||||
import { AbstractStorageService } from "../abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
|
||||
import { Utils } from "../misc/utils";
|
||||
|
||||
export class AppIdService implements AppIdServiceAbstraction {
|
||||
constructor(private storageService: StorageService) {}
|
||||
constructor(private storageService: AbstractStorageService) {}
|
||||
|
||||
getAppId(): Promise<string> {
|
||||
return this.makeAndGetAppId("appId");
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as bigInt from "big-integer";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
||||
import { LogService } from "../abstractions/log.service";
|
||||
|
@ -23,6 +24,7 @@ import { ProfileProviderResponse } from "../models/response/profileProviderRespo
|
|||
export class CryptoService implements CryptoServiceAbstraction {
|
||||
constructor(
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private encryptService: AbstractEncryptService,
|
||||
protected platformUtilService: PlatformUtilsService,
|
||||
protected logService: LogService,
|
||||
protected stateService: StateService
|
||||
|
@ -503,23 +505,15 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||
return this.buildEncKey(key, encKey.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated June 22 2022: This method has been moved to encryptService.
|
||||
* All callers should use this service to grab the relevant key and use encryptService for encryption instead.
|
||||
* This method will be removed once all existing code has been refactored to use encryptService.
|
||||
*/
|
||||
async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncString> {
|
||||
if (plainValue == null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
key = await this.getKeyForEncryption(key);
|
||||
|
||||
let plainBuf: ArrayBuffer;
|
||||
if (typeof plainValue === "string") {
|
||||
plainBuf = Utils.fromUtf8ToArray(plainValue).buffer;
|
||||
} else {
|
||||
plainBuf = plainValue;
|
||||
}
|
||||
|
||||
const encObj = await this.aesEncrypt(plainBuf, key);
|
||||
const iv = Utils.fromBufferToB64(encObj.iv);
|
||||
const data = Utils.fromBufferToB64(encObj.data);
|
||||
const mac = encObj.mac != null ? Utils.fromBufferToB64(encObj.mac) : null;
|
||||
return new EncString(encObj.key.encType, data, iv, mac);
|
||||
return await this.encryptService.encrypt(plainValue, key);
|
||||
}
|
||||
|
||||
async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
|
@ -618,13 +612,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||
}
|
||||
|
||||
async decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise<string> {
|
||||
return await this.aesDecryptToUtf8(
|
||||
encString.encryptionType,
|
||||
encString.data,
|
||||
encString.iv,
|
||||
encString.mac,
|
||||
key
|
||||
);
|
||||
key = await this.getKeyForEncryption(key);
|
||||
key = await this.resolveLegacyKey(encString.encryptionType, key);
|
||||
return await this.encryptService.decryptToUtf8(encString, key);
|
||||
}
|
||||
|
||||
async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
|
@ -754,6 +744,10 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||
: await this.stateService.getCryptoMasterKeyBiometric({ userId: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated June 22 2022: This method has been moved to encryptService.
|
||||
* All callers should use encryptService instead. This method will be removed once all existing code has been refactored to use encryptService.
|
||||
*/
|
||||
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
const obj = new EncryptedObject();
|
||||
obj.key = await this.getKeyForEncryption(key);
|
||||
|
@ -770,43 +764,6 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||
return obj;
|
||||
}
|
||||
|
||||
private async aesDecryptToUtf8(
|
||||
encType: EncryptionType,
|
||||
data: string,
|
||||
iv: string,
|
||||
mac: string,
|
||||
key: SymmetricCryptoKey
|
||||
): Promise<string> {
|
||||
const keyForEnc = await this.getKeyForEncryption(key);
|
||||
const theKey = await this.resolveLegacyKey(encType, keyForEnc);
|
||||
|
||||
if (theKey.macKey != null && mac == null) {
|
||||
this.logService.error("mac required.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (theKey.encType !== encType) {
|
||||
this.logService.error("encType unavailable.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(data, iv, mac, theKey);
|
||||
if (fastParams.macKey != null && fastParams.mac != null) {
|
||||
const computedMac = await this.cryptoFunctionService.hmacFast(
|
||||
fastParams.macData,
|
||||
fastParams.macKey,
|
||||
"sha256"
|
||||
);
|
||||
const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac);
|
||||
if (!macsEqual) {
|
||||
this.logService.error("mac failed.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.aesDecryptFast(fastParams);
|
||||
}
|
||||
|
||||
private async aesDecryptToBytes(
|
||||
encType: EncryptionType,
|
||||
data: ArrayBuffer,
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||
import { EncryptedObject } from "@bitwarden/common/models/domain/encryptedObject";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||
|
||||
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
|
||||
|
||||
export class EncryptService implements AbstractEncryptService {
|
||||
constructor(
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private logService: LogService,
|
||||
private logMacFailures: boolean
|
||||
) {}
|
||||
|
||||
async encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString> {
|
||||
if (key == null) {
|
||||
throw new Error("no encryption key provided.");
|
||||
}
|
||||
|
||||
if (plainValue == null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let plainBuf: ArrayBuffer;
|
||||
if (typeof plainValue === "string") {
|
||||
plainBuf = Utils.fromUtf8ToArray(plainValue).buffer;
|
||||
} else {
|
||||
plainBuf = plainValue;
|
||||
}
|
||||
|
||||
const encObj = await this.aesEncrypt(plainBuf, key);
|
||||
const iv = Utils.fromBufferToB64(encObj.iv);
|
||||
const data = Utils.fromBufferToB64(encObj.data);
|
||||
const mac = encObj.mac != null ? Utils.fromBufferToB64(encObj.mac) : null;
|
||||
return new EncString(encObj.key.encType, data, iv, mac);
|
||||
}
|
||||
|
||||
async decryptToUtf8(encString: EncString, key: SymmetricCryptoKey): Promise<string> {
|
||||
if (key?.macKey != null && encString?.mac == null) {
|
||||
this.logService.error("mac required.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key.encType !== encString.encryptionType) {
|
||||
this.logService.error("encType unavailable.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(
|
||||
encString.data,
|
||||
encString.iv,
|
||||
encString.mac,
|
||||
key
|
||||
);
|
||||
if (fastParams.macKey != null && fastParams.mac != null) {
|
||||
const computedMac = await this.cryptoFunctionService.hmacFast(
|
||||
fastParams.macData,
|
||||
fastParams.macKey,
|
||||
"sha256"
|
||||
);
|
||||
const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac);
|
||||
if (!macsEqual) {
|
||||
this.logMacFailed("mac failed.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.aesDecryptFast(fastParams);
|
||||
}
|
||||
|
||||
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
|
||||
const obj = new EncryptedObject();
|
||||
obj.key = key;
|
||||
obj.iv = await this.cryptoFunctionService.randomBytes(16);
|
||||
obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey);
|
||||
|
||||
if (obj.key.macKey != null) {
|
||||
const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength);
|
||||
macData.set(new Uint8Array(obj.iv), 0);
|
||||
macData.set(new Uint8Array(obj.data), obj.iv.byteLength);
|
||||
obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
private logMacFailed(msg: string) {
|
||||
if (this.logMacFailures) {
|
||||
this.logService.error(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
|
||||
export class MemoryStorageService implements StorageService {
|
||||
export class MemoryStorageService implements AbstractStorageService {
|
||||
private store = new Map<string, any>();
|
||||
|
||||
get<T>(key: string): Promise<T> {
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
import { StorageService } from "../abstractions/storage.service";
|
||||
import { AbstractStorageService } from "../abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { StateVersion } from "../enums/stateVersion";
|
||||
|
@ -132,8 +132,8 @@ export class StateMigrationService<
|
|||
TAccount extends Account = Account
|
||||
> {
|
||||
constructor(
|
||||
protected storageService: StorageService,
|
||||
protected secureStorageService: StorageService,
|
||||
protected storageService: AbstractStorageService,
|
||||
protected secureStorageService: AbstractStorageService,
|
||||
protected stateFactory: StateFactory<TGlobalState, TAccount>
|
||||
) {}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = {
|
|||
displayName: "libs/components tests",
|
||||
preset: "jest-preset-angular",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
|
|
@ -6,7 +6,7 @@ module.exports = {
|
|||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
@ -9,11 +10,12 @@ import { CryptoService } from "@bitwarden/common/services/crypto.service";
|
|||
export class ElectronCryptoService extends CryptoService {
|
||||
constructor(
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
encryptService: AbstractEncryptService,
|
||||
platformUtilService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
stateService: StateService
|
||||
) {
|
||||
super(cryptoFunctionService, platformUtilService, logService, stateService);
|
||||
super(cryptoFunctionService, encryptService, platformUtilService, logService, stateService);
|
||||
}
|
||||
|
||||
async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ipcRenderer } from "electron";
|
||||
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
|
||||
|
||||
export class ElectronRendererSecureStorageService implements StorageService {
|
||||
export class ElectronRendererSecureStorageService implements AbstractStorageService {
|
||||
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
const val = ipcRenderer.sendSync("keytar", {
|
||||
action: "getPassword",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { ipcRenderer } from "electron";
|
||||
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
|
||||
export class ElectronRendererStorageService implements StorageService {
|
||||
export class ElectronRendererStorageService implements AbstractStorageService {
|
||||
get<T>(key: string): Promise<T> {
|
||||
return ipcRenderer.invoke("storageService", {
|
||||
action: "get",
|
||||
|
|
|
@ -2,13 +2,13 @@ import * as fs from "fs";
|
|||
|
||||
import { ipcMain } from "electron";
|
||||
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
|
||||
|
||||
// eslint-disable-next-line
|
||||
const Store = require("electron-store");
|
||||
|
||||
export class ElectronStorageService implements StorageService {
|
||||
export class ElectronStorageService implements AbstractStorageService {
|
||||
private store: any;
|
||||
|
||||
constructor(dir: string, defaults = {}) {
|
||||
|
|
|
@ -5,7 +5,7 @@ const { compilerOptions } = require("../shared/tsconfig.libs");
|
|||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
|
||||
setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
|
||||
collectCoverage: true,
|
||||
coverageReporters: ["html", "lcov"],
|
||||
coverageDirectory: "coverage",
|
||||
|
|
|
@ -5,12 +5,12 @@ import * as lowdb from "lowdb";
|
|||
import * as FileSync from "lowdb/adapters/FileSync";
|
||||
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { StorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
|
||||
import { sequentialize } from "@bitwarden/common/misc/sequentialize";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
export class LowdbStorageService implements StorageService {
|
||||
export class LowdbStorageService implements AbstractStorageService {
|
||||
protected dataFilePath: string;
|
||||
private db: lowdb.LowdbSync<any>;
|
||||
private defaults: any;
|
||||
|
|
Loading…
Reference in New Issue