mirror of
https://github.com/bitwarden/browser
synced 2026-01-05 10:03:21 +00:00
* Extract files only used in cli out of libs/node Move commands from libs/node to cli Move program from libs/node to cli Move services from libs/node to cli Move specs from libs/node to cli Naming changes based on ADR 12 Rename commands Rename models/request Rename models/response Remove entries from whitelist-capital-letters.txt * Merge lowDbStorageService into base class Move logic from extended lowdbStorage.service.ts into base-lowdb-storage.service.ts Delete lowdb-storage.service.ts Rename base-lowdb-storage.service.ts to lowdb-storage.service.ts * Merge login.command with base class program.ts - changed import temporarily to make it easier to review Remove passing in clientId, set "cli" when constructing ssoRedirectUri call Remove setting callbacks, use private methods instead Remove i18nService from constructor params Add syncService, keyConnectorService and logoutCallback to constructor Merge successCallback with handleSuccessResponse Remove validatedParams callback and added private method Move options(program.OptionValues) and set in run() Delete login.command.ts * Rename base-login.command.ts to login.command.ts * Merge base.program.ts with program.ts
169 lines
5.0 KiB
TypeScript
169 lines
5.0 KiB
TypeScript
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 { LogService } from "@bitwarden/common/abstractions/log.service";
|
|
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
|
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
|
|
import { sequentialize } from "@bitwarden/common/misc/sequentialize";
|
|
import { Utils } from "@bitwarden/common/misc/utils";
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|