serve command (#451)

This commit is contained in:
Kyle Spearrin 2022-01-19 10:45:14 -05:00 committed by GitHub
parent 84a0bfb07c
commit 922cd1dc54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2091 additions and 332 deletions

1412
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -51,10 +51,12 @@
"assets": "./build/**/*"
},
"devDependencies": {
"@types/express": "^4.17.13",
"@types/inquirer": "^7.3.1",
"@types/jsdom": "^16.2.10",
"@types/lowdb": "^1.0.10",
"@types/lunr": "^2.3.3",
"@types/multer": "^1.4.7",
"@types/node": "^16.11.12",
"@types/node-fetch": "^2.5.10",
"@types/node-forge": "^0.9.7",
@ -83,12 +85,14 @@
"browser-hrtime": "^1.1.8",
"chalk": "^4.1.1",
"commander": "7.2.0",
"express": "^4.17.1",
"form-data": "4.0.0",
"https-proxy-agent": "5.0.0",
"inquirer": "8.0.0",
"jsdom": "^16.5.3",
"lowdb": "1.0.0",
"lunr": "^2.3.9",
"multer": "^1.4.3",
"node-fetch": "^2.6.1",
"node-forge": "0.10.0",
"open": "^8.0.8",

View File

@ -23,7 +23,7 @@ export class CompletionCommand {
const shell: typeof validShells[number] = options.shell;
if (!shell) {
return Response.badRequest("`shell` was not provided.");
return Response.badRequest("`shell` option was not provided.");
}
if (!validShells.includes(shell)) {

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
@ -12,35 +10,36 @@ import { Utils } from "jslib-common/misc/utils";
export class ConfirmCommand {
constructor(private apiService: ApiService, private cryptoService: CryptoService) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
const normalizedOptions = new Options(cmdOptions);
switch (object.toLowerCase()) {
case "org-member":
return await this.confirmOrganizationMember(id, cmd);
return await this.confirmOrganizationMember(id, normalizedOptions);
default:
return Response.badRequest("Unknown object.");
}
}
private async confirmOrganizationMember(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === "") {
private async confirmOrganizationMember(id: string, options: Options) {
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("--organizationid <organizationid> required.");
}
if (!Utils.isGuid(id)) {
return Response.error("`" + id + "` is not a GUID.");
return Response.badRequest("`" + id + "` is not a GUID.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
try {
const orgKey = await this.cryptoService.getOrgKey(options.organizationid);
const orgKey = await this.cryptoService.getOrgKey(options.organizationId);
if (orgKey == null) {
throw new Error("No encryption key for this organization.");
}
const orgUser = await this.apiService.getOrganizationUser(options.organizationid, id);
const orgUser = await this.apiService.getOrganizationUser(options.organizationId, id);
if (orgUser == null) {
throw new Error("Member id does not exist for this organization.");
}
@ -49,10 +48,18 @@ export class ConfirmCommand {
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString;
await this.apiService.postOrganizationUserConfirm(options.organizationid, id, req);
await this.apiService.postOrganizationUserConfirm(options.organizationId, id, req);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
}
class Options {
organizationId: string;
constructor(passedOptions: Record<string, any>) {
this.organizationId = passedOptions.organizationid || passedOptions.organizationId;
}
}

View File

@ -1,4 +1,3 @@
import * as program from "commander";
import * as fs from "fs";
import * as path from "path";
@ -36,10 +35,15 @@ export class CreateCommand {
private apiService: ApiService
) {}
async run(object: string, requestJson: string, cmd: program.Command): Promise<Response> {
async run(
object: string,
requestJson: string,
cmdOptions: Record<string, any>,
additionalData: any = null
): Promise<Response> {
let req: any = null;
if (object !== "attachment") {
if (requestJson == null || requestJson === "") {
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
requestJson = await CliUtils.readStdin();
}
@ -47,23 +51,28 @@ export class CreateCommand {
return Response.badRequest("`requestJson` was not provided.");
}
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
if (typeof requestJson !== "string") {
req = requestJson;
} else {
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
}
}
const normalizedOptions = new Options(cmdOptions);
switch (object.toLowerCase()) {
case "item":
return await this.createCipher(req);
case "attachment":
return await this.createAttachment(cmd);
return await this.createAttachment(normalizedOptions, additionalData);
case "folder":
return await this.createFolder(req);
case "org-collection":
return await this.createOrganizationCollection(req, cmd);
return await this.createOrganizationCollection(req, normalizedOptions);
default:
return Response.badRequest("Unknown object.");
}
@ -82,19 +91,35 @@ export class CreateCommand {
}
}
private async createAttachment(options: program.OptionValues) {
if (options.itemid == null || options.itemid === "") {
return Response.badRequest("--itemid <itemid> required.");
private async createAttachment(options: Options, additionalData: any) {
if (options.itemId == null || options.itemId === "") {
return Response.badRequest("`itemid` option is required.");
}
if (options.file == null || options.file === "") {
return Response.badRequest("--file <file> required.");
}
const filePath = path.resolve(options.file);
if (!fs.existsSync(options.file)) {
return Response.badRequest("Cannot find file at " + filePath);
let fileBuf: Buffer = null;
let fileName: string = null;
if (process.env.BW_SERVE === "true") {
fileBuf = additionalData.fileBuffer;
fileName = additionalData.fileName;
} else {
if (options.file == null || options.file === "") {
return Response.badRequest("`file` option is required.");
}
const filePath = path.resolve(options.file);
if (!fs.existsSync(options.file)) {
return Response.badRequest("Cannot find file at " + filePath);
}
fileBuf = fs.readFileSync(filePath);
fileName = path.basename(filePath);
}
const itemId = options.itemid.toLowerCase();
if (fileBuf == null) {
return Response.badRequest("File not provided.");
}
if (fileName == null || fileName.trim() === "") {
return Response.badRequest("File name not provided.");
}
const itemId = options.itemId.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
@ -113,16 +138,14 @@ export class CreateCommand {
}
try {
const fileBuf = fs.readFileSync(filePath);
await this.cipherService.saveAttachmentRawWithServer(
cipher,
path.basename(filePath),
fileName,
new Uint8Array(fileBuf).buffer
);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
@ -141,18 +164,15 @@ export class CreateCommand {
}
}
private async createOrganizationCollection(
req: OrganizationCollectionRequest,
options: program.OptionValues
) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
private async createOrganizationCollection(req: OrganizationCollectionRequest, options: Options) {
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("`organizationid` option is required.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
if (options.organizationid !== req.organizationId) {
return Response.error("--organizationid <organizationid> does not match request object.");
if (options.organizationId !== req.organizationId) {
return Response.badRequest("`organizationid` option does not match request object.");
}
try {
const orgKey = await this.cryptoService.getOrgKey(req.organizationId);
@ -178,3 +198,15 @@ export class CreateCommand {
}
}
}
class Options {
itemId: string;
organizationId: string;
file: string;
constructor(passedOptions: Record<string, any>) {
this.organizationId = passedOptions.organizationid || passedOptions.organizationId;
this.itemId = passedOptions.itemid || passedOptions.itemId;
this.file = passedOptions.file;
}
}

View File

@ -8,6 +8,7 @@ import { StateService } from "jslib-common/abstractions/state.service";
import { Response } from "jslib-node/cli/models/response";
import { Utils } from "jslib-common/misc/utils";
import { CliUtils } from "src/utils";
export class DeleteCommand {
constructor(
@ -17,26 +18,27 @@ export class DeleteCommand {
private apiService: ApiService
) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
const normalizedOptions = new Options(cmdOptions);
switch (object.toLowerCase()) {
case "item":
return await this.deleteCipher(id, cmd);
return await this.deleteCipher(id, normalizedOptions);
case "attachment":
return await this.deleteAttachment(id, cmd);
return await this.deleteAttachment(id, normalizedOptions);
case "folder":
return await this.deleteFolder(id);
case "org-collection":
return await this.deleteOrganizationCollection(id, cmd);
return await this.deleteOrganizationCollection(id, normalizedOptions);
default:
return Response.badRequest("Unknown object.");
}
}
private async deleteCipher(id: string, options: program.OptionValues) {
private async deleteCipher(id: string, options: Options) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();
@ -54,12 +56,12 @@ export class DeleteCommand {
}
}
private async deleteAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === "") {
return Response.badRequest("--itemid <itemid> required.");
private async deleteAttachment(id: string, options: Options) {
if (options.itemId == null || options.itemId === "") {
return Response.badRequest("`itemid` option is required.");
}
const itemId = options.itemid.toLowerCase();
const itemId = options.itemId.toLowerCase();
const cipher = await this.cipherService.get(itemId);
if (cipher == null) {
return Response.notFound();
@ -100,21 +102,33 @@ export class DeleteCommand {
}
}
private async deleteOrganizationCollection(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
private async deleteOrganizationCollection(id: string, options: Options) {
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("`organizationid` options is required.");
}
if (!Utils.isGuid(id)) {
return Response.error("`" + id + "` is not a GUID.");
return Response.badRequest("`" + id + "` is not a GUID.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
try {
await this.apiService.deleteCollection(options.organizationid, id);
await this.apiService.deleteCollection(options.organizationId, id);
return Response.success();
} catch (e) {
return Response.error(e);
}
}
}
class Options {
itemId: string;
organizationId: string;
permanent: boolean;
constructor(passedOptions: Record<string, any>) {
this.organizationId = passedOptions.organizationid || passedOptions.organizationId;
this.itemId = passedOptions.itemid || passedOptions.itemId;
this.permanent = CliUtils.convertBooleanOption(passedOptions.permanent);
}
}

View File

@ -5,6 +5,7 @@ import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { Response } from "jslib-node/cli/models/response";
import { FileResponse } from "jslib-node/cli/models/response/fileResponse";
import { CliUtils } from "../utils";
@ -27,7 +28,12 @@ export abstract class DownloadCommand {
try {
const buf = await response.arrayBuffer();
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName);
if (process.env.BW_SERVE === "true") {
const res = new FileResponse(Buffer.from(decBuf), fileName);
return Response.success(res);
} else {
return await CliUtils.saveResultToFile(Buffer.from(decBuf), output, fileName);
}
} catch (e) {
if (typeof e === "string") {
return Response.error(e);

View File

@ -35,10 +35,10 @@ export class EditCommand {
async run(
object: string,
id: string,
requestJson: string,
cmd: program.Command
requestJson: any,
cmdOptions: Record<string, any>
): Promise<Response> {
if (requestJson == null || requestJson === "") {
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
requestJson = await CliUtils.readStdin();
}
@ -47,17 +47,22 @@ export class EditCommand {
}
let req: any = null;
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
if (typeof requestJson !== "string") {
req = requestJson;
} else {
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
}
if (id != null) {
id = id.toLowerCase();
}
const normalizedOptions = new Options(cmdOptions);
switch (object.toLowerCase()) {
case "item":
return await this.editCipher(id, req);
@ -66,7 +71,7 @@ export class EditCommand {
case "folder":
return await this.editFolder(id, req);
case "org-collection":
return await this.editOrganizationCollection(id, req, cmd);
return await this.editOrganizationCollection(id, req, normalizedOptions);
default:
return Response.badRequest("Unknown object.");
}
@ -80,9 +85,7 @@ export class EditCommand {
let cipherView = await cipher.decrypt();
if (cipherView.isDeleted) {
return Response.badRequest(
"You may not edit a deleted cipher. Use restore item <id> command first."
);
return Response.badRequest("You may not edit a deleted item. Use the restore command first.");
}
cipherView = Cipher.toView(req, cipherView);
const encCipher = await this.cipherService.encrypt(cipherView);
@ -104,7 +107,7 @@ export class EditCommand {
}
if (cipher.organizationId == null) {
return Response.badRequest(
"Item does not belong to an organization. Consider sharing it first."
"Item does not belong to an organization. Consider moving it first."
);
}
@ -143,19 +146,19 @@ export class EditCommand {
private async editOrganizationCollection(
id: string,
req: OrganizationCollectionRequest,
options: program.OptionValues
options: Options
) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("`organizationid` option is required.");
}
if (!Utils.isGuid(id)) {
return Response.error("`" + id + "` is not a GUID.");
return Response.badRequest("`" + id + "` is not a GUID.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
if (options.organizationid !== req.organizationId) {
return Response.error("--organizationid <organizationid> does not match request object.");
if (options.organizationId !== req.organizationId) {
return Response.badRequest("`organizationid` option does not match request object.");
}
try {
const orgKey = await this.cryptoService.getOrgKey(req.organizationId);
@ -181,3 +184,11 @@ export class EditCommand {
}
}
}
class Options {
organizationId: string;
constructor(passedOptions: Record<string, any>) {
this.organizationId = passedOptions.organizationid || passedOptions.organizationId;
}
}

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";

View File

@ -1,42 +1,27 @@
import * as program from "commander";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { CliUtils } from "../utils";
export class GenerateCommand {
constructor(private passwordGenerationService: PasswordGenerationService) {}
async run(cmdOptions: program.OptionValues): Promise<Response> {
async run(cmdOptions: Record<string, any>): Promise<Response> {
const normalizedOptions = new Options(cmdOptions);
const options = {
uppercase: cmdOptions.uppercase || false,
lowercase: cmdOptions.lowercase || false,
number: cmdOptions.number || false,
special: cmdOptions.special || false,
length: cmdOptions.length || 14,
type: cmdOptions.passphrase ? "passphrase" : "password",
wordSeparator: cmdOptions.separator == null ? "-" : cmdOptions.separator,
numWords: cmdOptions.words || 3,
capitalize: cmdOptions.capitalize || false,
includeNumber: cmdOptions.includeNumber || false,
uppercase: normalizedOptions.uppercase,
lowercase: normalizedOptions.lowercase,
number: normalizedOptions.number,
special: normalizedOptions.special,
length: normalizedOptions.length,
type: normalizedOptions.type,
wordSeparator: normalizedOptions.separator,
numWords: normalizedOptions.words,
capitalize: normalizedOptions.capitalize,
includeNumber: normalizedOptions.includeNumber,
};
if (!options.uppercase && !options.lowercase && !options.special && !options.number) {
options.lowercase = true;
options.uppercase = true;
options.number = true;
}
if (options.length < 5) {
options.length = 5;
}
if (options.numWords < 3) {
options.numWords = 3;
}
if (options.wordSeparator === "space") {
options.wordSeparator = " ";
} else if (options.wordSeparator != null && options.wordSeparator.length > 1) {
options.wordSeparator = options.wordSeparator[0];
}
const enforcedOptions =
await this.passwordGenerationService.enforcePasswordGeneratorPoliciesOnOptions(options);
const password = await this.passwordGenerationService.generatePassword(enforcedOptions[0]);
@ -44,3 +29,46 @@ export class GenerateCommand {
return Response.success(res);
}
}
class Options {
uppercase: boolean;
lowercase: boolean;
number: boolean;
special: boolean;
length: number;
type: "passphrase" | "password";
separator: string;
words: number;
capitalize: boolean;
includeNumber: boolean;
constructor(passedOptions: Record<string, any>) {
this.uppercase = CliUtils.convertBooleanOption(passedOptions.uppercase);
this.lowercase = CliUtils.convertBooleanOption(passedOptions.lowercase);
this.number = CliUtils.convertBooleanOption(passedOptions.number);
this.special = CliUtils.convertBooleanOption(passedOptions.special);
this.capitalize = CliUtils.convertBooleanOption(passedOptions.capitalize);
this.includeNumber = CliUtils.convertBooleanOption(passedOptions.includeNumber);
this.length = passedOptions.length != null ? parseInt(passedOptions.length, null) : 14;
this.type = passedOptions.passphrase ? "passphrase" : "password";
this.separator = passedOptions.separator == null ? "-" : passedOptions.separator + "";
this.words = passedOptions.words != null ? parseInt(passedOptions.words, null) : 3;
if (!this.uppercase && !this.lowercase && !this.special && !this.number) {
this.lowercase = true;
this.uppercase = true;
this.number = true;
}
if (this.length < 5) {
this.length = 5;
}
if (this.words < 3) {
this.words = 3;
}
if (this.separator === "space") {
this.separator = " ";
} else if (this.separator != null && this.separator.length > 1) {
this.separator = this.separator[0];
}
}
}

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { CipherType } from "jslib-common/enums/cipherType";
import { ApiService } from "jslib-common/abstractions/api.service";
@ -71,11 +69,12 @@ export class GetCommand extends DownloadCommand {
super(cryptoService);
}
async run(object: string, id: string, options: program.OptionValues): Promise<Response> {
async run(object: string, id: string, cmdOptions: Record<string, any>): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
const normalizedOptions = new Options(cmdOptions);
switch (object.toLowerCase()) {
case "item":
return await this.getCipher(id);
@ -92,13 +91,13 @@ export class GetCommand extends DownloadCommand {
case "exposed":
return await this.getExposed(id);
case "attachment":
return await this.getAttachment(id, options);
return await this.getAttachment(id, normalizedOptions);
case "folder":
return await this.getFolder(id);
case "collection":
return await this.getCollection(id);
case "org-collection":
return await this.getOrganizationCollection(id, options);
return await this.getOrganizationCollection(id, normalizedOptions);
case "organization":
return await this.getOrganization(id);
case "template":
@ -292,12 +291,12 @@ export class GetCommand extends DownloadCommand {
return Response.success(res);
}
private async getAttachment(id: string, options: program.OptionValues) {
if (options.itemid == null || options.itemid === "") {
private async getAttachment(id: string, options: Options) {
if (options.itemId == null || options.itemId === "") {
return Response.badRequest("--itemid <itemid> required.");
}
const itemId = options.itemid.toLowerCase();
const itemId = options.itemId.toLowerCase();
const cipherResponse = await this.getCipher(itemId);
if (!cipherResponse.success) {
return cipherResponse;
@ -412,23 +411,23 @@ export class GetCommand extends DownloadCommand {
return Response.success(res);
}
private async getOrganizationCollection(id: string, options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
private async getOrganizationCollection(id: string, options: Options) {
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("`organizationid` option is required.");
}
if (!Utils.isGuid(id)) {
return Response.error("`" + id + "` is not a GUID.");
return Response.badRequest("`" + id + "` is not a GUID.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
try {
const orgKey = await this.cryptoService.getOrgKey(options.organizationid);
const orgKey = await this.cryptoService.getOrgKey(options.organizationId);
if (orgKey == null) {
throw new Error("No encryption key for this organization.");
}
const response = await this.apiService.getCollectionDetails(options.organizationid, id);
const response = await this.apiService.getCollectionDetails(options.organizationId, id);
const decCollection = new CollectionView(response);
decCollection.name = await this.cryptoService.decryptToUtf8(
new EncString(response.name),
@ -536,3 +535,15 @@ export class GetCommand extends DownloadCommand {
return Response.success(res);
}
}
class Options {
itemId: string;
organizationId: string;
output: string;
constructor(passedOptions: Record<string, any>) {
this.organizationId = passedOptions.organizationid || passedOptions.organizationId;
this.itemId = passedOptions.itemid || passedOptions.itemId;
this.output = passedOptions.output;
}
}

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { CipherView } from "jslib-common/models/view/cipherView";
import { ApiService } from "jslib-common/abstractions/api.service";
@ -42,26 +40,27 @@ export class ListCommand {
private apiService: ApiService
) {}
async run(object: string, cmd: program.Command): Promise<Response> {
async run(object: string, cmdOptions: Record<string, any>): Promise<Response> {
const normalizedOptions = new Options(cmdOptions);
switch (object.toLowerCase()) {
case "items":
return await this.listCiphers(cmd);
return await this.listCiphers(normalizedOptions);
case "folders":
return await this.listFolders(cmd);
return await this.listFolders(normalizedOptions);
case "collections":
return await this.listCollections(cmd);
return await this.listCollections(normalizedOptions);
case "org-collections":
return await this.listOrganizationCollections(cmd);
return await this.listOrganizationCollections(normalizedOptions);
case "org-members":
return await this.listOrganizationMembers(cmd);
return await this.listOrganizationMembers(normalizedOptions);
case "organizations":
return await this.listOrganizations(cmd);
return await this.listOrganizations(normalizedOptions);
default:
return Response.badRequest("Unknown object.");
}
}
private async listCiphers(options: program.OptionValues) {
private async listCiphers(options: Options) {
let ciphers: CipherView[];
options.trash = options.trash || false;
if (options.url != null && options.url.trim() !== "") {
@ -71,43 +70,43 @@ export class ListCommand {
}
if (
options.folderid != null ||
options.collectionid != null ||
options.organizationid != null
options.folderId != null ||
options.collectionId != null ||
options.organizationId != null
) {
ciphers = ciphers.filter((c) => {
if (options.trash !== c.isDeleted) {
return false;
}
if (options.folderid != null) {
if (options.folderid === "notnull" && c.folderId != null) {
if (options.folderId != null) {
if (options.folderId === "notnull" && c.folderId != null) {
return true;
}
const folderId = options.folderid === "null" ? null : options.folderid;
const folderId = options.folderId === "null" ? null : options.folderId;
if (folderId === c.folderId) {
return true;
}
}
if (options.organizationid != null) {
if (options.organizationid === "notnull" && c.organizationId != null) {
if (options.organizationId != null) {
if (options.organizationId === "notnull" && c.organizationId != null) {
return true;
}
const organizationId = options.organizationid === "null" ? null : options.organizationid;
const organizationId = options.organizationId === "null" ? null : options.organizationId;
if (organizationId === c.organizationId) {
return true;
}
}
if (options.collectionid != null) {
if (options.collectionId != null) {
if (
options.collectionid === "notnull" &&
options.collectionId === "notnull" &&
c.collectionIds != null &&
c.collectionIds.length > 0
) {
return true;
}
const collectionId = options.collectionid === "null" ? null : options.collectionid;
const collectionId = options.collectionId === "null" ? null : options.collectionId;
if (collectionId == null && (c.collectionIds == null || c.collectionIds.length === 0)) {
return true;
}
@ -133,7 +132,7 @@ export class ListCommand {
return Response.success(res);
}
private async listFolders(options: program.OptionValues) {
private async listFolders(options: Options) {
let folders = await this.folderService.getAllDecrypted();
if (options.search != null && options.search.trim() !== "") {
@ -144,12 +143,12 @@ export class ListCommand {
return Response.success(res);
}
private async listCollections(options: program.OptionValues) {
private async listCollections(options: Options) {
let collections = await this.collectionService.getAllDecrypted();
if (options.organizationid != null) {
if (options.organizationId != null) {
collections = collections.filter((c) => {
if (options.organizationid === c.organizationId) {
if (options.organizationId === c.organizationId) {
return true;
}
return false;
@ -164,14 +163,14 @@ export class ListCommand {
return Response.success(res);
}
private async listOrganizationCollections(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
private async listOrganizationCollections(options: Options) {
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("`organizationid` option is required.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
const organization = await this.organizationService.get(options.organizationid);
const organization = await this.organizationService.get(options.organizationId);
if (organization == null) {
return Response.error("Organization not found.");
}
@ -179,12 +178,12 @@ export class ListCommand {
try {
let response: ApiListResponse<ApiCollectionResponse>;
if (organization.canViewAllCollections) {
response = await this.apiService.getCollections(options.organizationid);
response = await this.apiService.getCollections(options.organizationId);
} else {
response = await this.apiService.getUserCollections();
}
const collections = response.data
.filter((c) => c.organizationId === options.organizationid)
.filter((c) => c.organizationId === options.organizationId)
.map((r) => new Collection(new CollectionData(r as ApiCollectionDetailsResponse)));
let decCollections = await this.collectionService.decryptMany(collections);
if (options.search != null && options.search.trim() !== "") {
@ -197,20 +196,20 @@ export class ListCommand {
}
}
private async listOrganizationMembers(options: program.OptionValues) {
if (options.organizationid == null || options.organizationid === "") {
return Response.badRequest("--organizationid <organizationid> required.");
private async listOrganizationMembers(options: Options) {
if (options.organizationId == null || options.organizationId === "") {
return Response.badRequest("`organizationid` option is required.");
}
if (!Utils.isGuid(options.organizationid)) {
return Response.error("`" + options.organizationid + "` is not a GUID.");
if (!Utils.isGuid(options.organizationId)) {
return Response.badRequest("`" + options.organizationId + "` is not a GUID.");
}
const organization = await this.organizationService.get(options.organizationid);
const organization = await this.organizationService.get(options.organizationId);
if (organization == null) {
return Response.error("Organization not found.");
}
try {
const response = await this.apiService.getOrganizationUsers(options.organizationid);
const response = await this.apiService.getOrganizationUsers(options.organizationId);
const res = new ListResponse(
response.data.map((r) => {
const u = new OrganizationUserResponse();
@ -229,7 +228,7 @@ export class ListCommand {
}
}
private async listOrganizations(options: program.OptionValues) {
private async listOrganizations(options: Options) {
let organizations = await this.organizationService.getAll();
if (options.search != null && options.search.trim() !== "") {
@ -240,3 +239,21 @@ export class ListCommand {
return Response.success(res);
}
}
class Options {
organizationId: string;
collectionId: string;
folderId: string;
search: string;
url: string;
trash: boolean;
constructor(passedOptions: Record<string, any>) {
this.organizationId = passedOptions.organizationid || passedOptions.organizationId;
this.collectionId = passedOptions.collectionid || passedOptions.collectionId;
this.folderId = passedOptions.folderid || passedOptions.folderId;
this.search = passedOptions.search;
this.url = passedOptions.url;
this.trash = CliUtils.convertBooleanOption(passedOptions.trash);
}
}

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { Response } from "jslib-node/cli/models/response";
@ -8,7 +6,7 @@ import { MessageResponse } from "jslib-node/cli/models/response/messageResponse"
export class LockCommand {
constructor(private vaultTimeoutService: VaultTimeoutService) {}
async run(cmd: program.Command) {
async run() {
await this.vaultTimeoutService.lock();
process.env.BW_SESSION = null;
const res = new MessageResponse("Your vault is locked.", null);

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { Response } from "jslib-node/cli/models/response";
@ -7,20 +5,20 @@ import { Response } from "jslib-node/cli/models/response";
export class RestoreCommand {
constructor(private cipherService: CipherService) {}
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
async run(object: string, id: string): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}
switch (object.toLowerCase()) {
case "item":
return await this.restoreCipher(id, cmd);
return await this.restoreCipher(id);
default:
return Response.badRequest("Unknown object.");
}
}
private async restoreCipher(id: string, cmd: program.Command) {
private async restoreCipher(id: string) {
const cipher = await this.cipherService.get(id);
if (cipher == null) {
return Response.notFound();

View File

@ -1,4 +1,3 @@
import * as program from "commander";
import * as fs from "fs";
import * as path from "path";
@ -11,7 +10,6 @@ import { SendType } from "jslib-common/enums/sendType";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { Response } from "jslib-node/cli/models/response";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { SendResponse } from "../../models/response/sendResponse";
import { SendTextResponse } from "../../models/response/sendTextResponse";
@ -25,9 +23,9 @@ export class SendCreateCommand {
private environmentService: EnvironmentService
) {}
async run(requestJson: string, options: program.OptionValues) {
async run(requestJson: any, cmdOptions: Record<string, any>) {
let req: any = null;
if (requestJson == null || requestJson === "") {
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
requestJson = await CliUtils.readStdin();
}
@ -35,15 +33,19 @@ export class SendCreateCommand {
return Response.badRequest("`requestJson` was not provided.");
}
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
if (typeof requestJson !== "string") {
req = requestJson;
} else {
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
if (req == null) {
throw new Error("Null request");
if (req == null) {
throw new Error("Null request");
}
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
if (
@ -58,10 +60,11 @@ export class SendCreateCommand {
return Response.badRequest("Unable to parse expirationDate: " + req.expirationDate);
}
return this.createSend(req, options);
const normalizedOptions = new Options(cmdOptions);
return this.createSend(req, normalizedOptions);
}
private async createSend(req: SendResponse, options: program.OptionValues) {
private async createSend(req: SendResponse, options: Options) {
const filePath = req.file?.fileName ?? options.file;
const text = req.text?.text ?? options.text;
const hidden = req.text?.hidden ?? options.hidden;
@ -73,13 +76,19 @@ export class SendCreateCommand {
switch (req.type) {
case SendType.File:
if (process.env.BW_SERVE === "true") {
return Response.error(
"Creating a file-based Send is unsupported through the `serve` command at this time."
);
}
if (!(await this.stateService.getCanAccessPremium())) {
return Response.error("Premium status is required to use this feature.");
}
if (filePath == null) {
return Response.badRequest(
"Must specify a file to Send either with the --file option or in the encoded json"
"Must specify a file to Send either with the --file option or in the request JSON."
);
}
@ -88,7 +97,7 @@ export class SendCreateCommand {
case SendType.Text:
if (text == null) {
return Response.badRequest(
"Must specify text content to Send either with the --text option or in the encoded json"
"Must specify text content to Send either with the --text option or in the request JSON."
);
}
req.text = new SendTextResponse();
@ -97,7 +106,7 @@ export class SendCreateCommand {
break;
default:
return Response.badRequest(
"Unknown Send type " + SendType[req.type] + "valid types are: file, text"
"Unknown Send type " + SendType[req.type] + ". Valid types are: file, text"
);
}
@ -117,13 +126,26 @@ export class SendCreateCommand {
const newSend = await this.sendService.get(encSend.id);
const decSend = await newSend.decrypt();
const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl());
return Response.success(
options.fullObject
? res
: new StringResponse("Send created! It can be accessed at:\n" + res.accessUrl)
);
return Response.success(res);
} catch (e) {
return Response.error(e);
}
}
}
class Options {
file: string;
text: string;
maxAccessCount: number;
password: string;
hidden: boolean;
constructor(passedOptions: Record<string, any>) {
this.file = passedOptions.file;
this.text = passedOptions.text;
this.password = passedOptions.password;
this.hidden = CliUtils.convertBooleanOption(passedOptions.hidden);
this.maxAccessCount =
passedOptions.maxAccessCount != null ? parseInt(passedOptions.maxAccessCount, null) : null;
}
}

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { SendService } from "jslib-common/abstractions/send.service";
import { StateService } from "jslib-common/abstractions/state.service";
@ -18,24 +16,29 @@ export class SendEditCommand {
private getCommand: SendGetCommand
) {}
async run(encodedJson: string, options: program.OptionValues): Promise<Response> {
if (encodedJson == null || encodedJson === "") {
encodedJson = await CliUtils.readStdin();
async run(requestJson: string, cmdOptions: Record<string, any>): Promise<Response> {
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
requestJson = await CliUtils.readStdin();
}
if (encodedJson == null || encodedJson === "") {
return Response.badRequest("`encodedJson` was not provided.");
if (requestJson == null || requestJson === "") {
return Response.badRequest("`requestJson` was not provided.");
}
let req: SendResponse = null;
try {
const reqJson = Buffer.from(encodedJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
if (typeof requestJson !== "string") {
req = requestJson;
} else {
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = SendResponse.fromJson(reqJson);
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
}
req.id = options.itemid || req.id;
const normalizedOptions = new Options(cmdOptions);
req.id = normalizedOptions.itemId || req.id;
if (req.id != null) {
req.id = req.id.toLowerCase();
@ -76,3 +79,11 @@ export class SendEditCommand {
return await this.getCommand.run(send.id, {});
}
}
class Options {
itemId: string;
constructor(passedOptions: Record<string, any>) {
this.itemId = passedOptions.itemId || passedOptions.itemid;
}
}

View File

@ -1,6 +1,5 @@
import * as program from "commander";
import { ApiService } from "jslib-common/abstractions/api.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SearchService } from "jslib-common/abstractions/search.service";
@ -27,6 +26,11 @@ export class SendGetCommand extends DownloadCommand {
}
async run(id: string, options: program.OptionValues) {
const serveCommand = process.env.BW_SERVE === "true";
if (serveCommand && !Utils.isGuid(id)) {
return Response.badRequest("`" + id + "` is not a GUID.");
}
let sends = await this.getSendView(id);
if (sends == null) {
return Response.notFound();
@ -36,7 +40,7 @@ export class SendGetCommand extends DownloadCommand {
let filter = (s: SendView) => true;
let selector = async (s: SendView): Promise<Response> =>
Response.success(new SendResponse(s, webVaultUrl));
if (options.text != null) {
if (!serveCommand && options?.text != null) {
filter = (s) => {
return filter(s) && s.text != null;
};

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { SearchService } from "jslib-common/abstractions/search.service";
import { SendService } from "jslib-common/abstractions/send.service";
@ -16,11 +14,12 @@ export class SendListCommand {
private searchService: SearchService
) {}
async run(options: program.OptionValues): Promise<Response> {
async run(cmdOptions: Record<string, any>): Promise<Response> {
let sends = await this.sendService.getAllDecrypted();
if (options.search != null && options.search.trim() !== "") {
sends = this.searchService.searchSends(sends, options.search);
const normalizedOptions = new Options(cmdOptions);
if (normalizedOptions.search != null && normalizedOptions.search.trim() !== "") {
sends = this.searchService.searchSends(sends, normalizedOptions.search);
}
const webVaultUrl = this.environmentService.getWebVaultUrl();
@ -28,3 +27,11 @@ export class SendListCommand {
return Response.success(res);
}
}
class Options {
search: string;
constructor(passedOptions: Record<string, any>) {
this.search = passedOptions.search;
}
}

View File

@ -0,0 +1,294 @@
import * as program from "commander";
import * as express from "express";
import * as multer from "multer";
import { Main } from "../bw";
import { ConfirmCommand } from "./confirm.command";
import { CreateCommand } from "./create.command";
import { DeleteCommand } from "./delete.command";
import { EditCommand } from "./edit.command";
import { GenerateCommand } from "./generate.command";
import { GetCommand } from "./get.command";
import { ListCommand } from "./list.command";
import { LockCommand } from "./lock.command";
import { RestoreCommand } from "./restore.command";
import { ShareCommand } from "./share.command";
import { StatusCommand } from "./status.command";
import { SyncCommand } from "./sync.command";
import { UnlockCommand } from "./unlock.command";
import { SendCreateCommand } from "./send/create.command";
import { SendDeleteCommand } from "./send/delete.command";
import { SendEditCommand } from "./send/edit.command";
import { SendGetCommand } from "./send/get.command";
import { SendListCommand } from "./send/list.command";
import { SendRemovePasswordCommand } from "./send/removePassword.command";
import { Response } from "jslib-node/cli/models/response";
import { FileResponse } from "jslib-node/cli/models/response/fileResponse";
export class ServeCommand {
private listCommand: ListCommand;
private getCommand: GetCommand;
private createCommand: CreateCommand;
private editCommand: EditCommand;
private generateCommand: GenerateCommand;
private shareCommand: ShareCommand;
private statusCommand: StatusCommand;
private syncCommand: SyncCommand;
private deleteCommand: DeleteCommand;
private confirmCommand: ConfirmCommand;
private restoreCommand: RestoreCommand;
private lockCommand: LockCommand;
private unlockCommand: UnlockCommand;
private sendCreateCommand: SendCreateCommand;
private sendDeleteCommand: SendDeleteCommand;
private sendEditCommand: SendEditCommand;
private sendGetCommand: SendGetCommand;
private sendListCommand: SendListCommand;
private sendRemovePasswordCommand: SendRemovePasswordCommand;
constructor(protected main: Main) {
this.getCommand = new GetCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.totpService,
this.main.auditService,
this.main.cryptoService,
this.main.stateService,
this.main.searchService,
this.main.apiService,
this.main.organizationService
);
this.listCommand = new ListCommand(
this.main.cipherService,
this.main.folderService,
this.main.collectionService,
this.main.organizationService,
this.main.searchService,
this.main.apiService
);
this.createCommand = new CreateCommand(
this.main.cipherService,
this.main.folderService,
this.main.stateService,
this.main.cryptoService,
this.main.apiService
);
this.editCommand = new EditCommand(
this.main.cipherService,
this.main.folderService,
this.main.cryptoService,
this.main.apiService
);
this.generateCommand = new GenerateCommand(this.main.passwordGenerationService);
this.syncCommand = new SyncCommand(this.main.syncService);
this.statusCommand = new StatusCommand(
this.main.environmentService,
this.main.syncService,
this.main.stateService,
this.main.vaultTimeoutService
);
this.deleteCommand = new DeleteCommand(
this.main.cipherService,
this.main.folderService,
this.main.stateService,
this.main.apiService
);
this.confirmCommand = new ConfirmCommand(this.main.apiService, this.main.cryptoService);
this.restoreCommand = new RestoreCommand(this.main.cipherService);
this.shareCommand = new ShareCommand(this.main.cipherService);
this.lockCommand = new LockCommand(this.main.vaultTimeoutService);
this.unlockCommand = new UnlockCommand(
this.main.cryptoService,
this.main.stateService,
this.main.cryptoFunctionService,
this.main.apiService,
this.main.logService
);
this.sendCreateCommand = new SendCreateCommand(
this.main.sendService,
this.main.stateService,
this.main.environmentService
);
this.sendDeleteCommand = new SendDeleteCommand(this.main.sendService);
this.sendGetCommand = new SendGetCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService,
this.main.cryptoService
);
this.sendEditCommand = new SendEditCommand(
this.main.sendService,
this.main.stateService,
this.sendGetCommand
);
this.sendListCommand = new SendListCommand(
this.main.sendService,
this.main.environmentService,
this.main.searchService
);
this.sendRemovePasswordCommand = new SendRemovePasswordCommand(this.main.sendService);
}
async run(options: program.OptionValues) {
const port = options.port || 8087;
const server = express();
process.env.BW_SERVE = "true";
process.env.BW_NOINTERACTION = "true";
server.use(express.json());
server.use((req, res, next) => {
const sessionHeader = req.get("Session");
if (sessionHeader != null && sessionHeader !== "") {
process.env.BW_SESSION = sessionHeader;
}
next();
});
server.get("/generate", async (req, res) => {
const response = await this.generateCommand.run(req.query);
this.processResponse(res, response);
});
server.get("/status", async (req, res) => {
const response = await this.statusCommand.run();
this.processResponse(res, response);
});
server.get("/list/:object", async (req, res) => {
let response: Response = null;
if (req.params.object === "send") {
response = await this.sendListCommand.run(req.query);
} else {
response = await this.listCommand.run(req.params.object, req.query);
}
this.processResponse(res, response);
});
server.get("/send/list", async (req, res) => {
const response = await this.sendListCommand.run(req.query);
this.processResponse(res, response);
});
server.post("/sync", async (req, res) => {
const response = await this.syncCommand.run(req.query);
this.processResponse(res, response);
});
server.post("/lock", async (req, res) => {
const response = await this.lockCommand.run();
this.processResponse(res, response);
});
server.post("/unlock", async (req, res) => {
const response = await this.unlockCommand.run(
req.body == null ? null : (req.body.password as string),
req.query
);
this.processResponse(res, response);
});
server.post("/confirm/:object/:id", async (req, res) => {
const response = await this.confirmCommand.run(req.params.object, req.params.id, req.query);
this.processResponse(res, response);
});
server.post("/restore/:object/:id", async (req, res) => {
const response = await this.restoreCommand.run(req.params.object, req.params.id);
this.processResponse(res, response);
});
server.post("/move/:id/:organizationId", async (req, res) => {
const response = await this.shareCommand.run(
req.params.id,
req.params.organizationId,
req.body
);
this.processResponse(res, response);
});
server.post("/attachment", multer().single("file"), async (req, res) => {
const response = await this.createCommand.run("attachment", req.body, req.query, {
fileBuffer: req.file.buffer,
fileName: req.file.originalname,
});
this.processResponse(res, response);
});
server.post("/send/:id/remove-password", async (req, res) => {
const response = await this.sendRemovePasswordCommand.run(req.params.id);
this.processResponse(res, response);
});
server.post("/:object", async (req, res) => {
let response: Response = null;
if (req.params.object === "send") {
response = await this.sendCreateCommand.run(req.body, req.query);
} else {
response = await this.createCommand.run(req.params.object, req.body, req.query);
}
this.processResponse(res, response);
});
server.put("/:object/:id", async (req, res) => {
let response: Response = null;
if (req.params.object === "send") {
req.body.id = req.params.id;
response = await this.sendEditCommand.run(req.body, req.query);
} else {
response = await this.editCommand.run(
req.params.object,
req.params.id,
req.body,
req.query
);
}
this.processResponse(res, response);
});
server.get("/:object/:id", async (req, res) => {
let response: Response = null;
if (req.params.object === "send") {
response = await this.sendGetCommand.run(req.params.id, null);
} else {
response = await this.getCommand.run(req.params.object, req.params.id, req.query);
}
this.processResponse(res, response);
});
server.delete("/:object/:id", async (req, res) => {
let response: Response = null;
if (req.params.object === "send") {
response = await this.sendDeleteCommand.run(req.params.id);
} else {
response = await this.deleteCommand.run(req.params.object, req.params.id, req.query);
}
this.processResponse(res, response);
});
server.listen(port, () => {
this.main.logService.info("Listening on port " + port);
});
}
private processResponse(res: any, commandResponse: Response) {
if (!commandResponse.success) {
res.statusCode = 400;
}
if (commandResponse.data instanceof FileResponse) {
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Content-Disposition": "attachment;filename=" + commandResponse.data.fileName,
"Content-Length": commandResponse.data.data.length,
});
res.end(commandResponse.data.data);
} else {
res.json(commandResponse);
}
}
}

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { CipherService } from "jslib-common/abstractions/cipher.service";
import { Response } from "jslib-node/cli/models/response";
@ -11,13 +9,8 @@ import { CliUtils } from "../utils";
export class ShareCommand {
constructor(private cipherService: CipherService) {}
async run(
id: string,
organizationId: string,
requestJson: string,
cmd: program.Command
): Promise<Response> {
if (requestJson == null || requestJson === "") {
async run(id: string, organizationId: string, requestJson: string): Promise<Response> {
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
requestJson = await CliUtils.readStdin();
}
@ -26,14 +19,18 @@ export class ShareCommand {
}
let req: string[] = [];
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
if (req == null || req.length === 0) {
return Response.badRequest("You must provide at least one collection id for this item.");
if (typeof requestJson !== "string") {
req = requestJson;
} else {
try {
const reqJson = Buffer.from(requestJson, "base64").toString();
req = JSON.parse(reqJson);
if (req == null || req.length === 0) {
return Response.badRequest("You must provide at least one collection id for this item.");
}
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
} catch (e) {
return Response.badRequest("Error parsing the encoded request data.");
}
if (id != null) {

View File

@ -1,5 +1,3 @@
import * as program from "commander";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { SyncService } from "jslib-common/abstractions/sync.service";

View File

@ -1,21 +1,22 @@
import * as program from "commander";
import { SyncService } from "jslib-common/abstractions/sync.service";
import { Response } from "jslib-node/cli/models/response";
import { MessageResponse } from "jslib-node/cli/models/response/messageResponse";
import { StringResponse } from "jslib-node/cli/models/response/stringResponse";
import { CliUtils } from "src/utils";
export class SyncCommand {
constructor(private syncService: SyncService) {}
async run(options: program.OptionValues): Promise<Response> {
if (options.last || false) {
async run(cmdOptions: Record<string, any>): Promise<Response> {
const normalizedOptions = new Options(cmdOptions);
if (normalizedOptions.last) {
return await this.getLastSync();
}
try {
const result = await this.syncService.fullSync(options.force || false, true);
const result = await this.syncService.fullSync(normalizedOptions.force, true);
const res = new MessageResponse("Syncing complete.", null);
return Response.success(res);
} catch (e) {
@ -29,3 +30,13 @@ export class SyncCommand {
return Response.success(res);
}
}
class Options {
last: boolean;
force: boolean;
constructor(passedOptions: Record<string, any>) {
this.last = CliUtils.convertBooleanOption(passedOptions.last);
this.force = CliUtils.convertBooleanOption(passedOptions.force);
}
}

View File

@ -1,4 +1,3 @@
import * as program from "commander";
import * as inquirer from "inquirer";
import { ApiService } from "jslib-common/abstractions/api.service";
@ -26,17 +25,18 @@ export class UnlockCommand {
private logService: ConsoleLogService
) {}
async run(password: string, options: program.OptionValues) {
async run(password: string, cmdOptions: Record<string, any>) {
const canInteract = process.env.BW_NOINTERACTION !== "true";
const normalizedOptions = new Options(cmdOptions);
if (password == null || password === "") {
if (options?.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options?.passwordenv) {
if (process.env[options.passwordenv]) {
password = process.env[options.passwordenv];
if (normalizedOptions?.passwordFile) {
password = await NodeUtils.readFirstLine(normalizedOptions.passwordFile);
} else if (normalizedOptions?.passwordEnv) {
if (process.env[normalizedOptions.passwordEnv]) {
password = process.env[normalizedOptions.passwordEnv];
} else {
this.logService.warning(
`Warning: Provided passwordenv ${options.passwordenv} is not set`
`Warning: Provided passwordenv ${normalizedOptions.passwordEnv} is not set`
);
}
}
@ -118,3 +118,13 @@ export class UnlockCommand {
process.env.BW_SESSION = Utils.fromBufferToB64(key);
}
}
class Options {
passwordEnv: string;
passwordFile: string;
constructor(passedOptions: Record<string, any>) {
this.passwordEnv = passedOptions.passwordenv || passedOptions.passwordEnv;
this.passwordFile = passedOptions.passwordfile || passedOptions.passwordFile;
}
}

View File

@ -8,6 +8,7 @@ import { EncodeCommand } from "./commands/encode.command";
import { GenerateCommand } from "./commands/generate.command";
import { LockCommand } from "./commands/lock.command";
import { LoginCommand } from "./commands/login.command";
import { ServeCommand } from "./commands/serve.command";
import { StatusCommand } from "./commands/status.command";
import { SyncCommand } from "./commands/sync.command";
import { UnlockCommand } from "./commands/unlock.command";
@ -214,7 +215,7 @@ export class Program extends BaseProgram {
}
const command = new LockCommand(this.main.vaultTimeoutService);
const response = await command.run(cmd);
const response = await command.run();
this.processResponse(response);
});
@ -462,6 +463,22 @@ export class Program extends BaseProgram {
const response = await command.run();
this.processResponse(response);
});
program
.command("serve")
.description("Start a RESTful API webserver.")
.option("--port <port>", "The port to run your API webserver on. Default port is 8087.")
.on("--help", () => {
writeLn("\n Examples:");
writeLn("");
writeLn(" bw serve");
writeLn(" bw serve --port 8080");
writeLn("", true);
})
.action(async (cmd) => {
const command = new ServeCommand(this.main);
await command.run(cmd);
});
}
protected processResponse(response: Response, exitImmediately = false) {

View File

@ -170,4 +170,8 @@ export class CliUtils {
return false;
});
}
static convertBooleanOption(optionValue: any) {
return optionValue || optionValue === "" ? true : false;
}
}

View File

@ -346,7 +346,7 @@ export class VaultProgram extends Program {
await this.exitIfLocked();
const command = new RestoreCommand(this.main.cipherService);
const response = await command.run(object, id, cmd);
const response = await command.run(object, id);
this.processResponse(response);
});
}
@ -383,7 +383,7 @@ export class VaultProgram extends Program {
.action(async (id, organizationId, encodedJson, cmd) => {
await this.exitIfLocked();
const command = new ShareCommand(this.main.cipherService);
const response = await command.run(id, organizationId, encodedJson, cmd);
const response = await command.run(id, organizationId, encodedJson);
this.processResponse(response);
});
}