1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 09:13:33 +00:00

Platform/pm 19/platform team file moves (#5460)

* Rename service-factory folder

* Move cryptographic service factories

* Move crypto models

* Move crypto services

* Move domain base class

* Platform code owners

* Move desktop log services

* Move log files

* Establish component library ownership

* Move background listeners

* Move background background

* Move localization to Platform

* Move browser alarms to Platform

* Move browser state to Platform

* Move CLI state to Platform

* Move Desktop native concerns to Platform

* Move flag and misc to Platform

* Lint fixes

* Move electron state to platform

* Move web state to Platform

* Move lib state to Platform

* Fix broken tests

* Rename interface to idiomatic TS

* `npm run prettier` 🤖

* Resolve review feedback

* Set platform as owners of web core and shared

* Expand moved services

* Fix test types

---------

Co-authored-by: Hinton <hinton@users.noreply.github.com>
This commit is contained in:
Matt Gibson
2023-06-06 15:34:53 -05:00
committed by GitHub
parent ce4fc31efd
commit 78248db590
869 changed files with 2840 additions and 2746 deletions

View File

@@ -0,0 +1,27 @@
import {
flagEnabled as baseFlagEnabled,
devFlagEnabled as baseDevFlagEnabled,
devFlagValue as baseDevFlagValue,
SharedFlags,
SharedDevFlags,
} from "@bitwarden/common/platform/misc/flags";
// required to avoid linting errors when there are no flags
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type Flags = {} & SharedFlags;
// required to avoid linting errors when there are no flags
/* eslint-disable-next-line @typescript-eslint/ban-types */
export type DevFlags = {} & SharedDevFlags;
export function flagEnabled(flag: keyof Flags): boolean {
return baseFlagEnabled<Flags>(flag);
}
export function devFlagEnabled(flag: keyof DevFlags) {
return baseDevFlagEnabled<DevFlags>(flag);
}
export function devFlagValue(flag: keyof DevFlags) {
return baseDevFlagValue(flag);
}

View File

@@ -0,0 +1,146 @@
import * as child_process from "child_process";
import { ClientType, DeviceType } from "@bitwarden/common/enums";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
// eslint-disable-next-line
const open = require("open");
export class CliPlatformUtilsService implements PlatformUtilsService {
clientType: ClientType;
private deviceCache: DeviceType = null;
constructor(clientType: ClientType, private packageJson: any) {
this.clientType = clientType;
}
getDevice(): DeviceType {
if (!this.deviceCache) {
switch (process.platform) {
case "win32":
this.deviceCache = DeviceType.WindowsDesktop;
break;
case "darwin":
this.deviceCache = DeviceType.MacOsDesktop;
break;
case "linux":
default:
this.deviceCache = DeviceType.LinuxDesktop;
break;
}
}
return this.deviceCache;
}
getDeviceString(): string {
const device = DeviceType[this.getDevice()].toLowerCase();
return device.replace("desktop", "");
}
getClientType() {
return this.clientType;
}
isFirefox() {
return false;
}
isChrome() {
return false;
}
isEdge() {
return false;
}
isOpera() {
return false;
}
isVivaldi() {
return false;
}
isSafari() {
return false;
}
isMacAppStore() {
return false;
}
isViewOpen() {
return Promise.resolve(false);
}
launchUri(uri: string, options?: any): void {
if (process.platform === "linux") {
child_process.spawnSync("xdg-open", [uri]);
} else {
open(uri);
}
}
getApplicationVersion(): Promise<string> {
return Promise.resolve(this.packageJson.version);
}
async getApplicationVersionNumber(): Promise<string> {
return (await this.getApplicationVersion()).split(RegExp("[+|-]"))[0].trim();
}
getApplicationVersionSync(): string {
return this.packageJson.version;
}
supportsWebAuthn(win: Window) {
return false;
}
supportsDuo(): boolean {
return false;
}
showToast(
type: "error" | "success" | "warning" | "info",
title: string,
text: string | string[],
options?: any
): void {
throw new Error("Not implemented.");
}
isDev(): boolean {
return process.env.BWCLI_ENV === "development";
}
isSelfHost(): boolean {
return false;
}
copyToClipboard(text: string, options?: any): void {
throw new Error("Not implemented.");
}
readFromClipboard(options?: any): Promise<string> {
throw new Error("Not implemented.");
}
supportsBiometric(): Promise<boolean> {
return Promise.resolve(false);
}
authenticateBiometric(): Promise<boolean> {
return Promise.resolve(false);
}
supportsSecureStorage(): boolean {
return false;
}
getAutofillKeyboardShortcut(): Promise<string> {
return null;
}
}

View File

@@ -0,0 +1,42 @@
import { interceptConsole, restoreConsole } from "@bitwarden/common/spec";
import { ConsoleLogService } from "./console-log.service";
let caughtMessage: any = {};
describe("CLI Console log service", () => {
let logService: ConsoleLogService;
beforeEach(() => {
caughtMessage = {};
interceptConsole(caughtMessage);
logService = new ConsoleLogService(true);
});
afterAll(() => {
restoreConsole();
});
it("should redirect all console to error if BW_RESPONSE env is true", () => {
process.env.BW_RESPONSE = "true";
logService.debug("this is a debug message");
expect(caughtMessage).toMatchObject({
error: { 0: "this is a debug message" },
});
});
it("should not redirect console to error if BW_RESPONSE != true", () => {
process.env.BW_RESPONSE = "false";
logService.debug("debug");
logService.info("info");
logService.warning("warning");
logService.error("error");
expect(caughtMessage).toMatchObject({
log: { 0: "info" },
warn: { 0: "warning" },
error: { 0: "error" },
});
});
});

View File

@@ -0,0 +1,22 @@
import { LogLevelType } from "@bitwarden/common/enums";
import { ConsoleLogService as BaseConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
export class ConsoleLogService extends BaseConsoleLogService {
constructor(isDev: boolean, filter: (level: LogLevelType) => boolean = null) {
super(isDev, filter);
}
write(level: LogLevelType, message: string) {
if (this.filter != null && this.filter(level)) {
return;
}
if (process.env.BW_RESPONSE === "true") {
// eslint-disable-next-line
console.error(message);
return;
}
super.write(level, message);
}
}

View File

@@ -0,0 +1,20 @@
import * as fs from "fs";
import * as path from "path";
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
export class I18nService extends BaseI18nService {
constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
const filePath = path.join(
__dirname,
this.localesDirectory + "/" + formattedLocale + "/messages.json"
);
const localesJson = fs.readFileSync(filePath, "utf8");
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
return Promise.resolve(locales);
});
this.supportedTranslationLocales = ["en"];
}
}

View File

@@ -0,0 +1,168 @@
import * as fs from "fs";
import * as path from "path";
import * as lowdb from "lowdb";
import * as FileSync from "lowdb/adapters/FileSync";
import * as lock from "proper-lockfile";
import { OperationOptions } from "retry";
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { sequentialize } from "@bitwarden/common/platform/misc/sequentialize";
import { Utils } from "@bitwarden/common/platform/misc/utils";
const retries: OperationOptions = {
retries: 50,
minTimeout: 100,
maxTimeout: 250,
factor: 2,
};
export class LowdbStorageService implements AbstractStorageService {
protected dataFilePath: string;
private db: lowdb.LowdbSync<any>;
private defaults: any;
private ready = false;
constructor(
protected logService: LogService,
defaults?: any,
private dir?: string,
private allowCache = false,
private requireLock = false
) {
this.defaults = defaults;
}
@sequentialize(() => "lowdbStorageInit")
async init() {
if (this.ready) {
return;
}
this.logService.info("Initializing lowdb storage service.");
let adapter: lowdb.AdapterSync<any>;
if (Utils.isNode && this.dir != null) {
if (!fs.existsSync(this.dir)) {
this.logService.warning(`Could not find dir, "${this.dir}"; creating it instead.`);
NodeUtils.mkdirpSync(this.dir, "700");
this.logService.info(`Created dir "${this.dir}".`);
}
this.dataFilePath = path.join(this.dir, "data.json");
if (!fs.existsSync(this.dataFilePath)) {
this.logService.warning(
`Could not find data file, "${this.dataFilePath}"; creating it instead.`
);
fs.writeFileSync(this.dataFilePath, "", { mode: 0o600 });
fs.chmodSync(this.dataFilePath, 0o600);
this.logService.info(`Created data file "${this.dataFilePath}" with chmod 600.`);
} else {
this.logService.info(`db file "${this.dataFilePath} already exists"; using existing db`);
}
await this.lockDbFile(() => {
adapter = new FileSync(this.dataFilePath);
});
}
try {
this.logService.info("Attempting to create lowdb storage adapter.");
this.db = lowdb(adapter);
this.logService.info("Successfully created lowdb storage adapter.");
} catch (e) {
if (e instanceof SyntaxError) {
this.logService.warning(
`Error creating lowdb storage adapter, "${e.message}"; emptying data file.`
);
if (fs.existsSync(this.dataFilePath)) {
const backupPath = this.dataFilePath + ".bak";
this.logService.warning(`Writing backup of data file to ${backupPath}`);
await fs.copyFile(this.dataFilePath, backupPath, () => {
this.logService.warning(
`Error while creating data file backup, "${e.message}". No backup may have been created.`
);
});
}
adapter.write({});
this.db = lowdb(adapter);
} else {
this.logService.error(`Error creating lowdb storage adapter, "${e.message}".`);
throw e;
}
}
if (this.defaults != null) {
this.lockDbFile(() => {
this.logService.info("Writing defaults.");
this.readForNoCache();
this.db.defaults(this.defaults).write();
this.logService.info("Successfully wrote defaults to db.");
});
}
this.ready = true;
}
async get<T>(key: string): Promise<T> {
await this.waitForReady();
return this.lockDbFile(() => {
this.readForNoCache();
const val = this.db.get(key).value();
this.logService.debug(`Successfully read ${key} from db`);
if (val == null) {
return null;
}
return val as T;
});
}
has(key: string): Promise<boolean> {
return this.get(key).then((v) => v != null);
}
async save(key: string, obj: any): Promise<any> {
await this.waitForReady();
return this.lockDbFile(() => {
this.readForNoCache();
this.db.set(key, obj).write();
this.logService.debug(`Successfully wrote ${key} to db`);
return;
});
}
async remove(key: string): Promise<any> {
await this.waitForReady();
return this.lockDbFile(() => {
this.readForNoCache();
this.db.unset(key).write();
this.logService.debug(`Successfully removed ${key} from db`);
return;
});
}
protected async lockDbFile<T>(action: () => T): Promise<T> {
if (this.requireLock && !Utils.isNullOrWhitespace(this.dataFilePath)) {
this.logService.info("acquiring db file lock");
return await lock.lock(this.dataFilePath, { retries: retries }).then((release) => {
try {
return action();
} finally {
release();
}
});
} else {
return action();
}
}
private readForNoCache() {
if (!this.allowCache) {
this.db.read();
}
}
private async waitForReady() {
if (!this.ready) {
await this.init();
}
}
}

View File

@@ -0,0 +1,43 @@
import * as FormData from "form-data";
import { HttpsProxyAgent } from "https-proxy-agent";
import * as fe from "node-fetch";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ApiService } from "@bitwarden/common/services/api.service";
(global as any).fetch = fe.default;
(global as any).Request = fe.Request;
(global as any).Response = fe.Response;
(global as any).Headers = fe.Headers;
(global as any).FormData = FormData;
export class NodeApiService extends ApiService {
constructor(
tokenService: TokenService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
appIdService: AppIdService,
logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null
) {
super(
tokenService,
platformUtilsService,
environmentService,
appIdService,
logoutCallback,
customUserAgent
);
}
nativeFetch(request: Request): Promise<Response> {
const proxy = process.env.http_proxy || process.env.https_proxy;
if (proxy) {
(request as any).agent = new HttpsProxyAgent(proxy);
}
return fetch(request);
}
}

View File

@@ -0,0 +1,102 @@
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
export class NodeEnvSecureStorageService implements AbstractStorageService {
constructor(
private storageService: AbstractStorageService,
private logService: LogService,
private cryptoService: () => CryptoService
) {}
async get<T>(key: string): Promise<T> {
const value = await this.storageService.get<string>(this.makeProtectedStorageKey(key));
if (value == null) {
return null;
}
const obj = await this.decrypt(value);
return obj as any;
}
async has(key: string): Promise<boolean> {
return (await this.get(key)) != null;
}
async save(key: string, obj: any): Promise<any> {
if (obj == null) {
return this.remove(key);
}
if (obj !== null && typeof obj !== "string") {
throw new Error("Only string storage is allowed.");
}
const protectedObj = await this.encrypt(obj);
await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj);
}
remove(key: string): Promise<any> {
return this.storageService.remove(this.makeProtectedStorageKey(key));
}
private async encrypt(plainValue: string): Promise<string> {
const sessionKey = this.getSessionKey();
if (sessionKey == null) {
throw new Error("No session key available.");
}
const encValue = await this.cryptoService().encryptToBytes(
Utils.fromB64ToArray(plainValue).buffer,
sessionKey
);
if (encValue == null) {
throw new Error("Value didn't encrypt.");
}
return Utils.fromBufferToB64(encValue.buffer);
}
private async decrypt(encValue: string): Promise<string> {
try {
const sessionKey = this.getSessionKey();
if (sessionKey == null) {
return null;
}
const encBuf = EncArrayBuffer.fromB64(encValue);
const decValue = await this.cryptoService().decryptFromBytes(encBuf, sessionKey);
if (decValue == null) {
this.logService.info("Failed to decrypt.");
return null;
}
return Utils.fromBufferToB64(decValue);
} catch (e) {
this.logService.info("Decrypt error.");
return null;
}
}
private getSessionKey() {
try {
if (process.env.BW_SESSION != null) {
const sessionBuffer = Utils.fromB64ToArray(process.env.BW_SESSION).buffer;
if (sessionBuffer != null) {
const sessionKey = new SymmetricCryptoKey(sessionBuffer);
if (sessionBuffer != null) {
return sessionKey;
}
}
}
} catch (e) {
this.logService.info("Session key is invalid.");
}
return null;
}
private makeProtectedStorageKey(key: string) {
return "__PROTECTED__" + key;
}
}