1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 07:13:32 +00:00

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:
Matt Gibson
2022-06-27 13:38:12 -04:00
committed by GitHub
parent c044c5c770
commit 399b8c2b34
57 changed files with 1575 additions and 370 deletions

View File

@@ -4,7 +4,7 @@
"types": ["node"], "types": ["node"],
"allowSyntheticDefaultImports": true "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/**/*"], "include": ["../src/**/*", "../projects/**/*"],
"files": ["./typings.d.ts"] "files": ["./typings.d.ts"]
} }

View File

@@ -6,6 +6,7 @@ module.exports = {
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
preset: "jest-preset-angular", preset: "jest-preset-angular",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, { moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/", prefix: "<rootDir>/",

View File

@@ -3,12 +3,14 @@
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"build": "webpack", "build": "webpack",
"build:mv3": "cross-env MANIFEST_VERSION=3 webpack",
"build:watch": "webpack --watch", "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": "cross-env NODE_ENV=production webpack",
"build:prod:watch": "cross-env NODE_ENV=production webpack --watch", "build:prod:watch": "cross-env NODE_ENV=production webpack --watch",
"dist": "npm run build:prod && gulp dist", "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:firefox": "npm run build:prod && gulp dist:firefox",
"dist:opera": "npm run build:prod && gulp dist:opera", "dist:opera": "npm run build:prod && gulp dist:opera",
"dist:safari": "npm run build:prod && gulp dist:safari", "dist:safari": "npm run build:prod && gulp dist:safari",

View File

@@ -24,7 +24,7 @@ import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service"; import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service"; import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service";
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.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 { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync.service";
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.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 { CollectionService } from "@bitwarden/common/services/collection.service";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
import { ContainerService } from "@bitwarden/common/services/container.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 { EnvironmentService } from "@bitwarden/common/services/environment.service";
import { EventService } from "@bitwarden/common/services/event.service"; import { EventService } from "@bitwarden/common/services/event.service";
import { ExportService } from "@bitwarden/common/services/export.service"; import { ExportService } from "@bitwarden/common/services/export.service";
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
import { FolderService } from "@bitwarden/common/services/folder.service"; import { FolderService } from "@bitwarden/common/services/folder.service";
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.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 { NotificationsService } from "@bitwarden/common/services/notifications.service";
import { OrganizationService } from "@bitwarden/common/services/organization.service"; import { OrganizationService } from "@bitwarden/common/services/organization.service";
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.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 { StateService as StateServiceAbstraction } from "../services/abstractions/state.service";
import AutofillService from "../services/autofill.service"; import AutofillService from "../services/autofill.service";
import { BrowserCryptoService } from "../services/browserCrypto.service"; import { BrowserCryptoService } from "../services/browserCrypto.service";
import BrowserLocalStorageService from "../services/browserLocalStorage.service";
import BrowserMessagingService from "../services/browserMessaging.service"; import BrowserMessagingService from "../services/browserMessaging.service";
import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service"; import BrowserMessagingPrivateModeBackgroundService from "../services/browserMessagingPrivateModeBackground.service";
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service"; import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
import BrowserStorageService from "../services/browserStorage.service";
import I18nService from "../services/i18n.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 { StateService } from "../services/state.service";
import { VaultFilterService } from "../services/vaultFilter.service"; import { VaultFilterService } from "../services/vaultFilter.service";
import VaultTimeoutService from "../services/vaultTimeout.service"; import VaultTimeoutService from "../services/vaultTimeout.service";
@@ -100,8 +104,9 @@ import WebRequestBackground from "./webRequest.background";
export default class MainBackground { export default class MainBackground {
messagingService: MessagingServiceAbstraction; messagingService: MessagingServiceAbstraction;
storageService: StorageServiceAbstraction; storageService: AbstractStorageService;
secureStorageService: StorageServiceAbstraction; secureStorageService: AbstractStorageService;
memoryStorageService: AbstractStorageService;
i18nService: I18nServiceAbstraction; i18nService: I18nServiceAbstraction;
platformUtilsService: PlatformUtilsServiceAbstraction; platformUtilsService: PlatformUtilsServiceAbstraction;
logService: LogServiceAbstraction; logService: LogServiceAbstraction;
@@ -141,6 +146,7 @@ export default class MainBackground {
twoFactorService: TwoFactorServiceAbstraction; twoFactorService: TwoFactorServiceAbstraction;
vaultFilterService: VaultFilterService; vaultFilterService: VaultFilterService;
usernameGenerationService: UsernameGenerationServiceAbstraction; usernameGenerationService: UsernameGenerationServiceAbstraction;
encryptService: EncryptService;
onUpdatedRan: boolean; onUpdatedRan: boolean;
onReplacedRan: boolean; onReplacedRan: boolean;
@@ -181,9 +187,17 @@ export default class MainBackground {
this.messagingService = isPrivateMode this.messagingService = isPrivateMode
? new BrowserMessagingPrivateModeBackgroundService() ? new BrowserMessagingPrivateModeBackgroundService()
: new BrowserMessagingService(); : new BrowserMessagingService();
this.storageService = new BrowserStorageService();
this.secureStorageService = new BrowserStorageService();
this.logService = new ConsoleLogService(false); 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.stateMigrationService = new StateMigrationService(
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
@@ -192,6 +206,7 @@ export default class MainBackground {
this.stateService = new StateService( this.stateService = new StateService(
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
this.memoryStorageService,
this.logService, this.logService,
this.stateMigrationService, this.stateMigrationService,
new StateFactory(GlobalState, Account) new StateFactory(GlobalState, Account)
@@ -219,9 +234,10 @@ export default class MainBackground {
} }
); );
this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); 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.cryptoService = new BrowserCryptoService(
this.cryptoFunctionService, this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService, this.platformUtilsService,
this.logService, this.logService,
this.stateService this.stateService

View File

@@ -64,9 +64,7 @@
"unlimitedStorage", "unlimitedStorage",
"clipboardRead", "clipboardRead",
"clipboardWrite", "clipboardWrite",
"idle", "idle"
"webRequest",
"declarativeNetRequest"
], ],
"optional_permissions": ["nativeMessaging"], "optional_permissions": ["nativeMessaging"],
"host_permissions": ["http://*/*", "https://*/*"], "host_permissions": ["http://*/*", "https://*/*"],

View File

@@ -30,12 +30,12 @@ export class PopupUtilsService {
return this.privateMode; return this.privateMode;
} }
getContentScrollY(win: Window, scrollingContainer = "content"): number { getContentScrollY(win: Window, scrollingContainer = "main"): number {
const content = win.document.getElementsByTagName(scrollingContainer)[0]; const content = win.document.getElementsByTagName(scrollingContainer)[0];
return content.scrollTop; return content.scrollTop;
} }
setContentScrollY(win: Window, scrollY: number, scrollingContainer = "content"): void { setContentScrollY(win: Window, scrollY: number, scrollingContainer = "main"): void {
if (scrollY != null) { if (scrollY != null) {
const content = win.document.getElementsByTagName(scrollingContainer)[0]; const content = win.document.getElementsByTagName(scrollingContainer)[0];
content.scrollTop = scrollY; content.scrollTop = scrollY;

View File

@@ -4,6 +4,7 @@ import { LockGuard as BaseLockGuardService } from "@bitwarden/angular/guards/loc
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/guards/unauth.guard"; import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/guards/unauth.guard";
import { import {
JslibServicesModule, JslibServicesModule,
MEMORY_STORAGE,
SECURE_STORAGE, SECURE_STORAGE,
} from "@bitwarden/angular/services/jslib-services.module"; } from "@bitwarden/angular/services/jslib-services.module";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 { SendService } from "@bitwarden/common/abstractions/send.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service"; import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.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 { SyncService } from "@bitwarden/common/abstractions/sync.service";
import { TokenService } from "@bitwarden/common/abstractions/token.service"; import { TokenService } from "@bitwarden/common/abstractions/token.service";
import { TotpService } from "@bitwarden/common/abstractions/totp.service"; import { TotpService } from "@bitwarden/common/abstractions/totp.service";
@@ -185,8 +186,8 @@ function getBgService<T>(service: keyof MainBackground) {
deps: [], deps: [],
}, },
{ {
provide: StorageServiceAbstraction, provide: AbstractStorageService,
useFactory: getBgService<StorageServiceAbstraction>("storageService"), useFactory: getBgService<AbstractStorageService>("storageService"),
deps: [], deps: [],
}, },
{ provide: AppIdService, useFactory: getBgService<AppIdService>("appIdService"), deps: [] }, { provide: AppIdService, useFactory: getBgService<AppIdService>("appIdService"), deps: [] },
@@ -249,9 +250,13 @@ function getBgService<T>(service: keyof MainBackground) {
}, },
{ {
provide: SECURE_STORAGE, provide: SECURE_STORAGE,
useFactory: getBgService<StorageServiceAbstraction>("secureStorageService"), useFactory: getBgService<AbstractStorageService>("secureStorageService"),
deps: [], deps: [],
}, },
{
provide: MEMORY_STORAGE,
useFactory: getBgService<AbstractStorageService>("memoryStorageService"),
},
{ {
provide: StateServiceAbstraction, provide: StateServiceAbstraction,
useFactory: getBgService<StateServiceAbstraction>("stateService"), useFactory: getBgService<StateServiceAbstraction>("stateService"),

View File

@@ -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 { export default abstract class AbstractChromeStorageService implements AbstractStorageService {
private chromeStorageApi: any; protected abstract chromeStorageApi: any;
constructor() {
this.chromeStorageApi = chrome.storage.local;
}
async get<T>(key: string): Promise<T> { async get<T>(key: string): Promise<T> {
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -23,7 +19,7 @@ export default class BrowserStorageService implements StorageService {
return (await this.get(key)) != null; 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) { if (obj == null) {
// Fix safari not liking null in set // Fix safari not liking null in set
return new Promise<void>((resolve) => { 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) => { return new Promise<void>((resolve) => {
this.chromeStorageApi.remove(key, () => { this.chromeStorageApi.remove(key, () => {
resolve(); resolve();

View File

@@ -0,0 +1,5 @@
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
export interface AbstractKeyGenerationService {
makeEphemeralKey(numBytes?: number): Promise<SymmetricCryptoKey>;
}

View File

@@ -0,0 +1,5 @@
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
export default class BrowserLocalStorageService extends AbstractChromeStorageService {
protected chromeStorageApi: any = chrome.storage.local;
}

View File

@@ -0,0 +1,5 @@
import AbstractChromeStorageService from "./abstractChromeStorageApi.service";
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
protected chromeStorageApi: any = (chrome.storage as any).session;
}

View File

@@ -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);
}
}

View File

@@ -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);
});
});
});

View File

@@ -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);
}
}
}

View File

@@ -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);
});
});
});

View File

@@ -1,6 +1,9 @@
import { GlobalState } from "@bitwarden/common/models/domain/globalState"; import { GlobalState } from "@bitwarden/common/models/domain/globalState";
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions"; 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 { Account } from "../models/account";
import { BrowserComponentState } from "../models/browserComponentState"; 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 // Check that there is an account in memory before considering the user authenticated
return ( return (
(await super.getIsAuthenticated(options)) && (await super.getIsAuthenticated(options)) &&
(await this.getAccount(this.defaultInMemoryOptions)) != null (await this.getAccount(await this.defaultInMemoryOptions())) != null
); );
} }
@withPrototype(BrowserGroupingsComponentState)
async getBrowserGroupingComponentState( async getBrowserGroupingComponentState(
options?: StorageOptions options?: StorageOptions
): Promise<BrowserGroupingsComponentState> { ): Promise<BrowserGroupingsComponentState> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.groupings; await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.groupings;
} }
async setBrowserGroupingComponentState( async setBrowserGroupingComponentState(
@@ -40,15 +45,20 @@ export class StateService
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
account.groupings = value; 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> { async getBrowserCipherComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.ciphers; await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.ciphers;
} }
async setBrowserCipherComponentState( async setBrowserCipherComponentState(
@@ -56,15 +66,20 @@ export class StateService
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
account.ciphers = value; 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> { async getBrowserSendComponentState(options?: StorageOptions): Promise<BrowserSendComponentState> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.send; await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.send;
} }
async setBrowserSendComponentState( async setBrowserSendComponentState(
@@ -72,14 +87,20 @@ export class StateService
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
account.send = value; 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> { async getBrowserSendTypeComponentState(options?: StorageOptions): Promise<BrowserComponentState> {
return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) return (
?.sendType; await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.sendType;
} }
async setBrowserSendTypeComponentState( async setBrowserSendTypeComponentState(
@@ -87,9 +108,12 @@ export class StateService
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, this.defaultInMemoryOptions) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
account.sendType = value; account.sendType = value;
await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
} }
} }

View File

@@ -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;

View File

@@ -11,6 +11,9 @@ if (process.env.NODE_ENV == null) {
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
} }
const ENV = (process.env.ENV = process.env.NODE_ENV); 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 = [ const moduleRules = [
{ {
@@ -72,8 +75,8 @@ const plugins = [
}), }),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [
process.env.MANIFEST_VERSION == 3 manifestVersion == 3
? { from: "./src/manifest.json.v3", to: "manifest.json" } ? { from: "./src/manifest.v3.json", to: "manifest.json" }
: "./src/manifest.json", : "./src/manifest.json",
{ from: "./src/_locales", to: "_locales" }, { from: "./src/_locales", to: "_locales" },
{ from: "./src/images", to: "images" }, { from: "./src/images", to: "images" },
@@ -123,7 +126,7 @@ const config = {
"notification/bar": "./src/notification/bar.js", "notification/bar": "./src/notification/bar.js",
}, },
optimization: { optimization: {
minimize: true, minimize: ENV !== "development",
minimizer: [ minimizer: [
new TerserPlugin({ new TerserPlugin({
exclude: [/content\/.*/, /notification\/.*/], exclude: [/content\/.*/, /notification\/.*/],

View File

@@ -5,7 +5,7 @@ const { compilerOptions } = require("./tsconfig");
module.exports = { module.exports = {
preset: "ts-jest", preset: "ts-jest",
testMatch: ["**/+(*.)+(spec).+(ts)"], testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"], setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",

View File

@@ -17,12 +17,14 @@ import { CipherService } from "@bitwarden/common/services/cipher.service";
import { CollectionService } from "@bitwarden/common/services/collection.service"; import { CollectionService } from "@bitwarden/common/services/collection.service";
import { ContainerService } from "@bitwarden/common/services/container.service"; import { ContainerService } from "@bitwarden/common/services/container.service";
import { CryptoService } from "@bitwarden/common/services/crypto.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 { EnvironmentService } from "@bitwarden/common/services/environment.service";
import { ExportService } from "@bitwarden/common/services/export.service"; import { ExportService } from "@bitwarden/common/services/export.service";
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
import { FolderService } from "@bitwarden/common/services/folder.service"; import { FolderService } from "@bitwarden/common/services/folder.service";
import { ImportService } from "@bitwarden/common/services/import.service"; import { ImportService } from "@bitwarden/common/services/import.service";
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.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 { NoopMessagingService } from "@bitwarden/common/services/noopMessaging.service";
import { OrganizationService } from "@bitwarden/common/services/organization.service"; import { OrganizationService } from "@bitwarden/common/services/organization.service";
import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service"; import { PasswordGenerationService } from "@bitwarden/common/services/passwordGeneration.service";
@@ -61,6 +63,7 @@ export class Main {
messagingService: NoopMessagingService; messagingService: NoopMessagingService;
storageService: LowdbStorageService; storageService: LowdbStorageService;
secureStorageService: NodeEnvSecureStorageService; secureStorageService: NodeEnvSecureStorageService;
memoryStorageService: MemoryStorageService;
i18nService: I18nService; i18nService: I18nService;
platformUtilsService: CliPlatformUtilsService; platformUtilsService: CliPlatformUtilsService;
cryptoService: CryptoService; cryptoService: CryptoService;
@@ -82,6 +85,7 @@ export class Main {
exportService: ExportService; exportService: ExportService;
searchService: SearchService; searchService: SearchService;
cryptoFunctionService: NodeCryptoFunctionService; cryptoFunctionService: NodeCryptoFunctionService;
encryptService: EncryptService;
authService: AuthService; authService: AuthService;
policyService: PolicyService; policyService: PolicyService;
program: Program; program: Program;
@@ -122,6 +126,7 @@ export class Main {
(level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info (level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info
); );
this.cryptoFunctionService = new NodeCryptoFunctionService(); this.cryptoFunctionService = new NodeCryptoFunctionService();
this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true);
this.storageService = new LowdbStorageService(this.logService, null, p, false, true); this.storageService = new LowdbStorageService(this.logService, null, p, false, true);
this.secureStorageService = new NodeEnvSecureStorageService( this.secureStorageService = new NodeEnvSecureStorageService(
this.storageService, this.storageService,
@@ -129,6 +134,8 @@ export class Main {
() => this.cryptoService () => this.cryptoService
); );
this.memoryStorageService = new MemoryStorageService();
this.stateMigrationService = new StateMigrationService( this.stateMigrationService = new StateMigrationService(
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
@@ -138,6 +145,7 @@ export class Main {
this.stateService = new StateService( this.stateService = new StateService(
this.storageService, this.storageService,
this.secureStorageService, this.secureStorageService,
this.memoryStorageService,
this.logService, this.logService,
this.stateMigrationService, this.stateMigrationService,
new StateFactory(GlobalState, Account) new StateFactory(GlobalState, Account)
@@ -145,6 +153,7 @@ export class Main {
this.cryptoService = new CryptoService( this.cryptoService = new CryptoService(
this.cryptoFunctionService, this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService, this.platformUtilsService,
this.logService, this.logService,
this.stateService this.stateService

View File

@@ -1,12 +1,12 @@
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { LogService } from "@bitwarden/common/abstractions/log.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 { Utils } from "@bitwarden/common/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
export class NodeEnvSecureStorageService implements StorageService { export class NodeEnvSecureStorageService implements AbstractStorageService {
constructor( constructor(
private storageService: StorageService, private storageService: AbstractStorageService,
private logService: LogService, private logService: LogService,
private cryptoService: () => CryptoService private cryptoService: () => CryptoService
) {} ) {}

View File

@@ -8,8 +8,10 @@ import {
CLIENT_TYPE, CLIENT_TYPE,
LOCALES_DIRECTORY, LOCALES_DIRECTORY,
SYSTEM_LANGUAGE, SYSTEM_LANGUAGE,
MEMORY_STORAGE,
} from "@bitwarden/angular/services/jslib-services.module"; } from "@bitwarden/angular/services/jslib-services.module";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; 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 { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.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 { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.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 { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
import { ClientType } from "@bitwarden/common/enums/clientType"; import { ClientType } from "@bitwarden/common/enums/clientType";
import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/globalState"; 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 { SystemService } from "@bitwarden/common/services/system.service";
import { ElectronCryptoService } from "@bitwarden/electron/services/electronCrypto.service"; import { ElectronCryptoService } from "@bitwarden/electron/services/electronCrypto.service";
import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service"; import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service";
@@ -96,13 +99,15 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
useClass: ElectronRendererMessagingService, useClass: ElectronRendererMessagingService,
deps: [BroadcasterServiceAbstraction], deps: [BroadcasterServiceAbstraction],
}, },
{ provide: StorageServiceAbstraction, useClass: ElectronRendererStorageService }, { provide: AbstractStorageService, useClass: ElectronRendererStorageService },
{ provide: SECURE_STORAGE, useClass: ElectronRendererSecureStorageService }, { provide: SECURE_STORAGE, useClass: ElectronRendererSecureStorageService },
{ provide: MEMORY_STORAGE, useClass: MemoryStorageService },
{ {
provide: CryptoServiceAbstraction, provide: CryptoServiceAbstraction,
useClass: ElectronCryptoService, useClass: ElectronCryptoService,
deps: [ deps: [
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
AbstractEncryptService,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
LogServiceAbstraction, LogServiceAbstraction,
StateServiceAbstraction, StateServiceAbstraction,
@@ -123,8 +128,9 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
provide: StateServiceAbstraction, provide: StateServiceAbstraction,
useClass: StateService, useClass: StateService,
deps: [ deps: [
StorageServiceAbstraction, AbstractStorageService,
SECURE_STORAGE, SECURE_STORAGE,
MEMORY_STORAGE,
LogService, LogService,
StateMigrationServiceAbstraction, StateMigrationServiceAbstraction,
STATE_FACTORY, STATE_FACTORY,

View File

@@ -4,6 +4,7 @@ import { app } from "electron";
import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { GlobalState } from "@bitwarden/common/models/domain/globalState"; 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 { StateService } from "@bitwarden/common/services/state.service";
import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service"; import { ElectronLogService } from "@bitwarden/electron/services/electronLog.service";
import { ElectronMainMessagingService } from "@bitwarden/electron/services/electronMainMessaging.service"; import { ElectronMainMessagingService } from "@bitwarden/electron/services/electronMainMessaging.service";
@@ -25,6 +26,7 @@ export class Main {
logService: ElectronLogService; logService: ElectronLogService;
i18nService: I18nService; i18nService: I18nService;
storageService: ElectronStorageService; storageService: ElectronStorageService;
memoryStorageService: MemoryStorageService;
messagingService: ElectronMainMessagingService; messagingService: ElectronMainMessagingService;
stateService: StateService; stateService: StateService;
desktopCredentialStorageListener: DesktopCredentialStorageListener; desktopCredentialStorageListener: DesktopCredentialStorageListener;
@@ -74,6 +76,7 @@ export class Main {
storageDefaults["global.vaultTimeout"] = -1; storageDefaults["global.vaultTimeout"] = -1;
storageDefaults["global.vaultTimeoutAction"] = "lock"; storageDefaults["global.vaultTimeoutAction"] = "lock";
this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults); 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. // 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 // 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.stateService = new StateService(
this.storageService, this.storageService,
null, null,
this.memoryStorageService,
this.logService, this.logService,
null, null,
new StateFactory(GlobalState, Account), new StateFactory(GlobalState, Account),

View File

@@ -8,6 +8,7 @@ import {
STATE_SERVICE_USE_CACHE, STATE_SERVICE_USE_CACHE,
LOCALES_DIRECTORY, LOCALES_DIRECTORY,
SYSTEM_LANGUAGE, SYSTEM_LANGUAGE,
MEMORY_STORAGE,
} from "@bitwarden/angular/services/jslib-services.module"; } from "@bitwarden/angular/services/jslib-services.module";
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.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 { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.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 { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { ImportService } from "@bitwarden/common/services/import.service"; 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 { StateService as StateServiceAbstraction } from "../../abstractions/state.service";
import { Account } from "../../models/account"; import { Account } from "../../models/account";
@@ -33,7 +35,6 @@ import { GlobalState } from "../../models/globalState";
import { BroadcasterMessagingService } from "../../services/broadcasterMessaging.service"; import { BroadcasterMessagingService } from "../../services/broadcasterMessaging.service";
import { HtmlStorageService } from "../../services/htmlStorage.service"; import { HtmlStorageService } from "../../services/htmlStorage.service";
import { I18nService } from "../../services/i18n.service"; import { I18nService } from "../../services/i18n.service";
import { MemoryStorageService } from "../../services/memoryStorage.service";
import { PasswordRepromptService } from "../../services/passwordReprompt.service"; import { PasswordRepromptService } from "../../services/passwordReprompt.service";
import { StateService } from "../../services/state.service"; import { StateService } from "../../services/state.service";
import { StateMigrationService } from "../../services/stateMigration.service"; import { StateMigrationService } from "../../services/stateMigration.service";
@@ -77,13 +78,17 @@ import { RouterService } from "./router.service";
useClass: I18nService, useClass: I18nService,
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY], deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
}, },
{ provide: StorageServiceAbstraction, useClass: HtmlStorageService }, { provide: AbstractStorageService, useClass: HtmlStorageService },
{ {
provide: SECURE_STORAGE, provide: SECURE_STORAGE,
// TODO: platformUtilsService.isDev has a helper for this, but using that service here results in a circular dependency. // 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. // 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, useClass: process.env.NODE_ENV === "development" ? HtmlStorageService : MemoryStorageService,
}, },
{
provide: MEMORY_STORAGE,
useClass: MemoryStorageService,
},
{ {
provide: PlatformUtilsServiceAbstraction, provide: PlatformUtilsServiceAbstraction,
useClass: WebPlatformUtilsService, useClass: WebPlatformUtilsService,
@@ -106,14 +111,15 @@ import { RouterService } from "./router.service";
{ {
provide: StateMigrationServiceAbstraction, provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService, useClass: StateMigrationService,
deps: [StorageServiceAbstraction, SECURE_STORAGE, STATE_FACTORY], deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
}, },
{ {
provide: StateServiceAbstraction, provide: StateServiceAbstraction,
useClass: StateService, useClass: StateService,
deps: [ deps: [
StorageServiceAbstraction, AbstractStorageService,
SECURE_STORAGE, SECURE_STORAGE,
MEMORY_STORAGE,
LogService, LogService,
StateMigrationServiceAbstraction, StateMigrationServiceAbstraction,
STATE_FACTORY, STATE_FACTORY,

View File

@@ -1,11 +1,11 @@
import { Injectable } from "@angular/core"; 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 { HtmlStorageLocation } from "@bitwarden/common/enums/htmlStorageLocation";
import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions"; import { StorageOptions } from "@bitwarden/common/models/domain/storageOptions";
@Injectable() @Injectable()
export class HtmlStorageService implements StorageService { export class HtmlStorageService implements AbstractStorageService {
get defaultOptions(): StorageOptions { get defaultOptions(): StorageOptions {
return { htmlStorageLocation: HtmlStorageLocation.Session }; return { htmlStorageLocation: HtmlStorageLocation.Session };
} }

View File

@@ -37,7 +37,7 @@ export class StateService
} }
async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> { 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); return await super.getEncryptedCiphers(options);
} }
@@ -45,14 +45,14 @@ export class StateService
value: { [id: string]: CipherData }, value: { [id: string]: CipherData },
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
options = this.reconcileOptions(options, this.defaultInMemoryOptions); options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
return await super.setEncryptedCiphers(value, options); return await super.setEncryptedCiphers(value, options);
} }
async getEncryptedCollections( async getEncryptedCollections(
options?: StorageOptions options?: StorageOptions
): Promise<{ [id: string]: CollectionData }> { ): Promise<{ [id: string]: CollectionData }> {
options = this.reconcileOptions(options, this.defaultInMemoryOptions); options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
return await super.getEncryptedCollections(options); return await super.getEncryptedCollections(options);
} }
@@ -60,12 +60,12 @@ export class StateService
value: { [id: string]: CollectionData }, value: { [id: string]: CollectionData },
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
options = this.reconcileOptions(options, this.defaultInMemoryOptions); options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
return await super.setEncryptedCollections(value, options); return await super.setEncryptedCollections(value, options);
} }
async getEncryptedFolders(options?: StorageOptions): Promise<{ [id: string]: FolderData }> { 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); return await super.getEncryptedFolders(options);
} }
@@ -73,12 +73,12 @@ export class StateService
value: { [id: string]: FolderData }, value: { [id: string]: FolderData },
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
options = this.reconcileOptions(options, this.defaultInMemoryOptions); options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
return await super.setEncryptedFolders(value, options); return await super.setEncryptedFolders(value, options);
} }
async getEncryptedSends(options?: StorageOptions): Promise<{ [id: string]: SendData }> { 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); return await super.getEncryptedSends(options);
} }
@@ -86,17 +86,17 @@ export class StateService
value: { [id: string]: SendData }, value: { [id: string]: SendData },
options?: StorageOptions options?: StorageOptions
): Promise<void> { ): Promise<void> {
options = this.reconcileOptions(options, this.defaultInMemoryOptions); options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
return await super.setEncryptedSends(value, options); return await super.setEncryptedSends(value, options);
} }
override async getLastSync(options?: StorageOptions): Promise<string> { 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); return await super.getLastSync(options);
} }
override async setLastSync(value: string, options?: StorageOptions): Promise<void> { 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); return await super.setLastSync(value, options);
} }
} }

View File

@@ -36,20 +36,25 @@
"webpack://?:*/*": "${workspaceFolder}/*", "webpack://?:*/*": "${workspaceFolder}/*",
"webpack://@bitwarden/cli/*": "${workspaceFolder}/*" "webpack://@bitwarden/cli/*": "${workspaceFolder}/*"
} }
},
"jest.disabledWorkspaceFolders": [
"browser",
"cli",
"desktop",
"libs",
"web vault",
"web vault (bit)",
"desktop"
],
"jest.jestCommandLine": "npx jest",
"angular.enable-strict-mode-prompt": false
}, },
"jest.disabledWorkspaceFolders": [ "extensions": {
"root", "recommendations": [
"web vault", "orta.vscode-jest",
"web vault (bit)",
"desktop"
],
"jest.jestCommandLine": "npx jest"
},
"extensions": {
"recommendations": [
"orta.vscode-jest",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode" "esbenp.prettier-vscode",
"Angular.ng-template"
], ],
} }
} }

View File

@@ -7,7 +7,7 @@ module.exports = {
displayName: "libs/angular tests", displayName: "libs/angular tests",
preset: "jest-preset-angular", preset: "jest-preset-angular",
testMatch: ["**/+(*.)+(spec).+(ts)"], testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"], setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",

View File

@@ -2,6 +2,7 @@ import { InjectionToken, Injector, LOCALE_ID, NgModule } from "@angular/core";
import { ThemingService } from "@bitwarden/angular/services/theming/theming.service"; import { ThemingService } from "@bitwarden/angular/services/theming/theming.service";
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; 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 { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.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 { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.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 { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync.service";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/abstractions/token.service";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.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 { CollectionService } from "@bitwarden/common/services/collection.service";
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
import { CryptoService } from "@bitwarden/common/services/crypto.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 { EnvironmentService } from "@bitwarden/common/services/environment.service";
import { EventService } from "@bitwarden/common/services/event.service"; import { EventService } from "@bitwarden/common/services/event.service";
import { ExportService } from "@bitwarden/common/services/export.service"; import { ExportService } from "@bitwarden/common/services/export.service";
@@ -86,7 +88,8 @@ import { PasswordRepromptService } from "./passwordReprompt.service";
import { ValidationService } from "./validation.service"; import { ValidationService } from "./validation.service";
export const WINDOW = new InjectionToken<Window>("WINDOW"); 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_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE"); export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
export const LOGOUT_CALLBACK = new InjectionToken<(expired: boolean, userId?: string) => void>( 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, provide: AppIdServiceAbstraction,
useClass: AppIdService, useClass: AppIdService,
deps: [StorageServiceAbstraction], deps: [AbstractStorageService],
}, },
{ {
provide: AuditServiceAbstraction, provide: AuditServiceAbstraction,
@@ -233,6 +236,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
useClass: CryptoService, useClass: CryptoService,
deps: [ deps: [
CryptoFunctionServiceAbstraction, CryptoFunctionServiceAbstraction,
AbstractEncryptService,
PlatformUtilsServiceAbstraction, PlatformUtilsServiceAbstraction,
LogService, LogService,
StateServiceAbstraction, StateServiceAbstraction,
@@ -315,8 +319,9 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
provide: StateServiceAbstraction, provide: StateServiceAbstraction,
useClass: StateService, useClass: StateService,
deps: [ deps: [
StorageServiceAbstraction, AbstractStorageService,
SECURE_STORAGE, SECURE_STORAGE,
MEMORY_STORAGE,
LogService, LogService,
StateMigrationServiceAbstraction, StateMigrationServiceAbstraction,
STATE_FACTORY, STATE_FACTORY,
@@ -326,7 +331,7 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
{ {
provide: StateMigrationServiceAbstraction, provide: StateMigrationServiceAbstraction,
useClass: StateMigrationService, useClass: StateMigrationService,
deps: [StorageServiceAbstraction, SECURE_STORAGE, STATE_FACTORY], deps: [AbstractStorageService, SECURE_STORAGE, STATE_FACTORY],
}, },
{ {
provide: ExportServiceAbstraction, provide: ExportServiceAbstraction,
@@ -362,6 +367,11 @@ export const SYSTEM_LANGUAGE = new InjectionToken<string>("SYSTEM_LANGUAGE");
useClass: WebCryptoFunctionService, useClass: WebCryptoFunctionService,
deps: [WINDOW], deps: [WINDOW],
}, },
{
provide: AbstractEncryptService,
useClass: EncryptService,
deps: [CryptoFunctionServiceAbstraction, LogService, true], // Log mac failures = true
},
{ {
provide: EventServiceAbstraction, provide: EventServiceAbstraction,
useClass: EventService, useClass: EventService,

View File

@@ -8,7 +8,7 @@ module.exports = {
preset: "ts-jest", preset: "ts-jest",
testEnvironment: "jsdom", testEnvironment: "jsdom",
testMatch: ["**/+(*.)+(spec).+(ts)"], testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"], setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",

View File

@@ -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 },
});
}
});

View File

@@ -1,6 +1,6 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; 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 { StateVersion } from "@bitwarden/common/enums/stateVersion";
import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { StateFactory } from "@bitwarden/common/factories/stateFactory";
import { Account } from "@bitwarden/common/models/domain/account"; import { Account } from "@bitwarden/common/models/domain/account";
@@ -10,15 +10,15 @@ import { StateMigrationService } from "@bitwarden/common/services/stateMigration
const userId = "USER_ID"; const userId = "USER_ID";
describe("State Migration Service", () => { describe("State Migration Service", () => {
let storageService: SubstituteOf<StorageService>; let storageService: SubstituteOf<AbstractStorageService>;
let secureStorageService: SubstituteOf<StorageService>; let secureStorageService: SubstituteOf<AbstractStorageService>;
let stateFactory: SubstituteOf<StateFactory>; let stateFactory: SubstituteOf<StateFactory>;
let stateMigrationService: StateMigrationService; let stateMigrationService: StateMigrationService;
beforeEach(() => { beforeEach(() => {
storageService = Substitute.for<StorageService>(); storageService = Substitute.for<AbstractStorageService>();
secureStorageService = Substitute.for<StorageService>(); secureStorageService = Substitute.for<AbstractStorageService>();
stateFactory = Substitute.for<StateFactory>(); stateFactory = Substitute.for<StateFactory>();
stateMigrationService = new StateMigrationService( 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(() => { beforeEach(() => {
const globalVersion3: Partial<GlobalState> = { const globalVersion3: Partial<GlobalState> = {
stateVersion: StateVersion.Three, stateVersion: StateVersion.Three,

View File

@@ -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>;
}

View File

@@ -1,8 +1,8 @@
import { StorageOptions } from "../models/domain/storageOptions"; import { StorageOptions } from "../models/domain/storageOptions";
export abstract class StorageService { export abstract class AbstractStorageService {
get: <T>(key: string, options?: StorageOptions) => Promise<T>; abstract get<T>(key: string, options?: StorageOptions): Promise<T>;
has: (key: string, options?: StorageOptions) => Promise<boolean>; abstract has(key: string, options?: StorageOptions): Promise<boolean>;
save: (key: string, obj: any, options?: StorageOptions) => Promise<any>; abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;
remove: (key: string, options?: StorageOptions) => Promise<any>; abstract remove(key: string, options?: StorageOptions): Promise<void>;
} }

View File

@@ -23,6 +23,7 @@ import { SymmetricCryptoKey } from "./symmetricCryptoKey";
export class EncryptionPair<TEncrypted, TDecrypted> { export class EncryptionPair<TEncrypted, TDecrypted> {
encrypted?: TEncrypted; encrypted?: TEncrypted;
decrypted?: TDecrypted; decrypted?: TDecrypted;
decryptedSerialized?: string;
} }
export class DataEncryptionPair<TEncrypted, TDecrypted> { export class DataEncryptionPair<TEncrypted, TDecrypted> {
@@ -76,6 +77,7 @@ export class AccountKeys {
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>(); privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
legacyEtmKey?: SymmetricCryptoKey; legacyEtmKey?: SymmetricCryptoKey;
publicKey?: ArrayBuffer; publicKey?: ArrayBuffer;
publicKeySerialized?: string;
apiKeyClientSecret?: string; apiKeyClientSecret?: string;
} }

View File

@@ -54,4 +54,22 @@ export class SymmetricCryptoKey {
this.macKeyB64 = Utils.fromBufferToB64(this.macKey); 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;
}
} }

View File

@@ -1,10 +1,10 @@
import { AppIdService as AppIdServiceAbstraction } from "../abstractions/appId.service"; 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 { HtmlStorageLocation } from "../enums/htmlStorageLocation";
import { Utils } from "../misc/utils"; import { Utils } from "../misc/utils";
export class AppIdService implements AppIdServiceAbstraction { export class AppIdService implements AppIdServiceAbstraction {
constructor(private storageService: StorageService) {} constructor(private storageService: AbstractStorageService) {}
getAppId(): Promise<string> { getAppId(): Promise<string> {
return this.makeAndGetAppId("appId"); return this.makeAndGetAppId("appId");

View File

@@ -1,5 +1,6 @@
import * as bigInt from "big-integer"; import * as bigInt from "big-integer";
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service"; import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service";
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
import { LogService } from "../abstractions/log.service"; import { LogService } from "../abstractions/log.service";
@@ -23,6 +24,7 @@ import { ProfileProviderResponse } from "../models/response/profileProviderRespo
export class CryptoService implements CryptoServiceAbstraction { export class CryptoService implements CryptoServiceAbstraction {
constructor( constructor(
private cryptoFunctionService: CryptoFunctionService, private cryptoFunctionService: CryptoFunctionService,
private encryptService: AbstractEncryptService,
protected platformUtilService: PlatformUtilsService, protected platformUtilService: PlatformUtilsService,
protected logService: LogService, protected logService: LogService,
protected stateService: StateService protected stateService: StateService
@@ -503,23 +505,15 @@ export class CryptoService implements CryptoServiceAbstraction {
return this.buildEncKey(key, encKey.key); 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> { async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncString> {
if (plainValue == null) { key = await this.getKeyForEncryption(key);
return Promise.resolve(null);
}
let plainBuf: ArrayBuffer; return await this.encryptService.encrypt(plainValue, key);
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 encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> { 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> { async decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise<string> {
return await this.aesDecryptToUtf8( key = await this.getKeyForEncryption(key);
encString.encryptionType, key = await this.resolveLegacyKey(encString.encryptionType, key);
encString.data, return await this.encryptService.decryptToUtf8(encString, key);
encString.iv,
encString.mac,
key
);
} }
async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> { async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
@@ -754,6 +744,10 @@ export class CryptoService implements CryptoServiceAbstraction {
: await this.stateService.getCryptoMasterKeyBiometric({ userId: userId }); : 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> { private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
const obj = new EncryptedObject(); const obj = new EncryptedObject();
obj.key = await this.getKeyForEncryption(key); obj.key = await this.getKeyForEncryption(key);
@@ -770,43 +764,6 @@ export class CryptoService implements CryptoServiceAbstraction {
return obj; 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( private async aesDecryptToBytes(
encType: EncryptionType, encType: EncryptionType,
data: ArrayBuffer, data: ArrayBuffer,

View File

@@ -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);
}
}
}

View File

@@ -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>(); private store = new Map<string, any>();
get<T>(key: string): Promise<T> { get<T>(key: string): Promise<T> {

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { StorageService } from "../abstractions/storage.service"; import { AbstractStorageService } from "../abstractions/storage.service";
import { HtmlStorageLocation } from "../enums/htmlStorageLocation"; import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
import { KdfType } from "../enums/kdfType"; import { KdfType } from "../enums/kdfType";
import { StateVersion } from "../enums/stateVersion"; import { StateVersion } from "../enums/stateVersion";
@@ -132,8 +132,8 @@ export class StateMigrationService<
TAccount extends Account = Account TAccount extends Account = Account
> { > {
constructor( constructor(
protected storageService: StorageService, protected storageService: AbstractStorageService,
protected secureStorageService: StorageService, protected secureStorageService: AbstractStorageService,
protected stateFactory: StateFactory<TGlobalState, TAccount> protected stateFactory: StateFactory<TGlobalState, TAccount>
) {} ) {}

View File

@@ -7,7 +7,7 @@ module.exports = {
displayName: "libs/components tests", displayName: "libs/components tests",
preset: "jest-preset-angular", preset: "jest-preset-angular",
testMatch: ["**/+(*.)+(spec).+(ts)"], testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"], setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",

View File

@@ -6,7 +6,7 @@ module.exports = {
preset: "ts-jest", preset: "ts-jest",
testEnvironment: "jsdom", testEnvironment: "jsdom",
testMatch: ["**/+(*.)+(spec).+(ts)"], testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"], setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",

View File

@@ -1,3 +1,4 @@
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { LogService } from "@bitwarden/common/abstractions/log.service"; import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.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 { export class ElectronCryptoService extends CryptoService {
constructor( constructor(
cryptoFunctionService: CryptoFunctionService, cryptoFunctionService: CryptoFunctionService,
encryptService: AbstractEncryptService,
platformUtilService: PlatformUtilsService, platformUtilService: PlatformUtilsService,
logService: LogService, logService: LogService,
stateService: StateService stateService: StateService
) { ) {
super(cryptoFunctionService, platformUtilService, logService, stateService); super(cryptoFunctionService, encryptService, platformUtilService, logService, stateService);
} }
async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> { async hasKeyStored(keySuffix: KeySuffixOptions): Promise<boolean> {

View File

@@ -1,9 +1,9 @@
import { ipcRenderer } from "electron"; 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"; 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> { async get<T>(key: string, options?: StorageOptions): Promise<T> {
const val = ipcRenderer.sendSync("keytar", { const val = ipcRenderer.sendSync("keytar", {
action: "getPassword", action: "getPassword",

View File

@@ -1,8 +1,8 @@
import { ipcRenderer } from "electron"; 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> { get<T>(key: string): Promise<T> {
return ipcRenderer.invoke("storageService", { return ipcRenderer.invoke("storageService", {
action: "get", action: "get",

View File

@@ -2,13 +2,13 @@ import * as fs from "fs";
import { ipcMain } from "electron"; 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"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
// eslint-disable-next-line // eslint-disable-next-line
const Store = require("electron-store"); const Store = require("electron-store");
export class ElectronStorageService implements StorageService { export class ElectronStorageService implements AbstractStorageService {
private store: any; private store: any;
constructor(dir: string, defaults = {}) { constructor(dir: string, defaults = {}) {

View File

@@ -5,7 +5,7 @@ const { compilerOptions } = require("../shared/tsconfig.libs");
module.exports = { module.exports = {
preset: "ts-jest", preset: "ts-jest",
testMatch: ["**/+(*.)+(spec).+(ts)"], testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"], setupFilesAfterEnv: ["<rootDir>/spec/test.setup.ts"],
collectCoverage: true, collectCoverage: true,
coverageReporters: ["html", "lcov"], coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage", coverageDirectory: "coverage",

View File

@@ -5,12 +5,12 @@ import * as lowdb from "lowdb";
import * as FileSync from "lowdb/adapters/FileSync"; import * as FileSync from "lowdb/adapters/FileSync";
import { LogService } from "@bitwarden/common/abstractions/log.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 { NodeUtils } from "@bitwarden/common/misc/nodeUtils"; import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
import { sequentialize } from "@bitwarden/common/misc/sequentialize"; import { sequentialize } from "@bitwarden/common/misc/sequentialize";
import { Utils } from "@bitwarden/common/misc/utils"; import { Utils } from "@bitwarden/common/misc/utils";
export class LowdbStorageService implements StorageService { export class LowdbStorageService implements AbstractStorageService {
protected dataFilePath: string; protected dataFilePath: string;
private db: lowdb.LowdbSync<any>; private db: lowdb.LowdbSync<any>;
private defaults: any; private defaults: any;