From d5669acc5fad5e9df9715af3178bd3e7f6199c87 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 8 Oct 2021 14:37:45 -0400 Subject: [PATCH] support for send commands --- src/commands/create.command.ts | 2 +- src/commands/edit.command.ts | 2 +- src/commands/send/create.command.ts | 59 ++++++++++++++++-------- src/commands/send/edit.command.ts | 38 ++++++++++------ src/commands/send/get.command.ts | 8 +++- src/commands/send/list.command.ts | 17 ++++--- src/commands/serve.command.ts | 70 ++++++++++++++++++++++++++--- src/commands/share.command.ts | 2 +- src/send.program.ts | 3 +- 9 files changed, 152 insertions(+), 49 deletions(-) diff --git a/src/commands/create.command.ts b/src/commands/create.command.ts index 3d2cc92..3cf5beb 100644 --- a/src/commands/create.command.ts +++ b/src/commands/create.command.ts @@ -36,7 +36,7 @@ export class CreateCommand { let req: any = null; if (object !== 'attachment') { - if (requestJson == null || requestJson === '') { + if (process.env.BW_SERVE !== 'true' && (requestJson == null || requestJson === '')) { requestJson = await CliUtils.readStdin(); } diff --git a/src/commands/edit.command.ts b/src/commands/edit.command.ts index 3a22061..4390ad7 100644 --- a/src/commands/edit.command.ts +++ b/src/commands/edit.command.ts @@ -27,7 +27,7 @@ export class EditCommand { private cryptoService: CryptoService, private apiService: ApiService) { } async run(object: string, id: string, requestJson: any, cmdOptions: Record): Promise { - if (requestJson == null || requestJson === '') { + if (process.env.BW_SERVE !== 'true' && (requestJson == null || requestJson === '')) { requestJson = await CliUtils.readStdin(); } diff --git a/src/commands/send/create.command.ts b/src/commands/send/create.command.ts index 8945282..1d96a58 100644 --- a/src/commands/send/create.command.ts +++ b/src/commands/send/create.command.ts @@ -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'; @@ -22,9 +20,9 @@ export class SendCreateCommand { constructor(private sendService: SendService, private userService: UserService, private environmentService: EnvironmentService) { } - async run(requestJson: string, options: program.OptionValues) { + async run(requestJson: any, cmdOptions: Record) { let req: any = null; - if (requestJson == null || requestJson === '') { + if (process.env.BW_SERVE !== 'true' && (requestJson == null || requestJson === '')) { requestJson = await CliUtils.readStdin(); } @@ -32,15 +30,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 (req.deletionDate == null || isNaN(new Date(req.deletionDate).getTime()) || @@ -52,10 +54,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; @@ -67,26 +70,30 @@ 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.userService.canAccessPremium())) { 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'); + return Response.badRequest('Must specify a file to Send either with the --file option or in the request JSON.'); } req.file.fileName = path.basename(filePath); break; 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'); + return Response.badRequest('Must specify text content to Send either with the `text` option or in the request JSON.'); } req.text = new SendTextResponse(); req.text.text = text; req.text.hidden = hidden; break; default: - return Response.badRequest('Unknown Send type ' + SendType[req.type] + 'valid types are: file, text'); + return Response.badRequest('Unknown Send type ' + SendType[req.type] + '. Valid types are: file, text'); } try { @@ -105,10 +112,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) { + 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; + } +} diff --git a/src/commands/send/edit.command.ts b/src/commands/send/edit.command.ts index efe0f1f..d63e459 100644 --- a/src/commands/send/edit.command.ts +++ b/src/commands/send/edit.command.ts @@ -1,6 +1,3 @@ -import * as program from 'commander'; - -import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; import { SendService } from 'jslib-common/abstractions/send.service'; import { UserService } from 'jslib-common/abstractions/user.service'; @@ -16,24 +13,29 @@ export class SendEditCommand { constructor(private sendService: SendService, private userService: UserService, private getCommand: SendGetCommand) { } - async run(encodedJson: string, options: program.OptionValues): Promise { - if (encodedJson == null || encodedJson === '') { - encodedJson = await CliUtils.readStdin(); + async run(requestJson: string, cmdOptions: Record): Promise { + 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(); @@ -74,3 +76,11 @@ export class SendEditCommand { return await this.getCommand.run(send.id, {}); } } + +class Options { + itemId: string; + + constructor(passedOptions: Record) { + this.itemId = passedOptions.itemId || passedOptions.itemid; + } +} diff --git a/src/commands/send/get.command.ts b/src/commands/send/get.command.ts index 0e752b6..016b1bc 100644 --- a/src/commands/send/get.command.ts +++ b/src/commands/send/get.command.ts @@ -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'; @@ -23,6 +22,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(); @@ -31,7 +35,7 @@ export class SendGetCommand extends DownloadCommand { const webVaultUrl = this.environmentService.getWebVaultUrl(); let filter = (s: SendView) => true; let selector = async (s: SendView): Promise => Response.success(new SendResponse(s, webVaultUrl)); - if (options.text != null) { + if (!serveCommand && options?.text != null) { filter = s => { return filter(s) && s.text != null; }; diff --git a/src/commands/send/list.command.ts b/src/commands/send/list.command.ts index b0b98c4..f12bd04 100644 --- a/src/commands/send/list.command.ts +++ b/src/commands/send/list.command.ts @@ -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'; @@ -14,11 +12,12 @@ export class SendListCommand { constructor(private sendService: SendService, private environmentService: EnvironmentService, private searchService: SearchService) { } - async run(options: program.OptionValues): Promise { + async run(cmdOptions: Record): Promise { 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(); @@ -26,3 +25,11 @@ export class SendListCommand { return Response.success(res); } } + +class Options { + search: string; + + constructor(passedOptions: Record) { + this.search = passedOptions.search; + } +} diff --git a/src/commands/serve.command.ts b/src/commands/serve.command.ts index 59bd31d..593fc0c 100644 --- a/src/commands/serve.command.ts +++ b/src/commands/serve.command.ts @@ -18,6 +18,13 @@ 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'; @@ -36,6 +43,13 @@ export class ServeCommand { 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, @@ -59,6 +73,16 @@ export class ServeCommand { this.lockCommand = new LockCommand(this.main.vaultTimeoutService); this.unlockCommand = new UnlockCommand(this.main.cryptoService, this.main.userService, this.main.cryptoFunctionService, this.main.apiService, this.main.logService); + + this.sendCreateCommand = new SendCreateCommand(this.main.sendService, this.main.userService, + 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.userService, 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) { @@ -87,7 +111,17 @@ export class ServeCommand { }); server.get('/list/:object', async (req, res) => { - const response = await this.listCommand.run(req.params.object, req.query); + 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); }); @@ -130,23 +164,49 @@ export class ServeCommand { 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) => { - const response = await this.createCommand.run(req.params.object, req.body, req.query); + let response: Response = null; + if (req.params.object === 'send') { + response = await this.sendCreateCommand.run(req.params.object, 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) => { - const response = await this.editCommand.run(req.params.object, req.params.id, req.body, req.query); + 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) => { - const response = await this.getCommand.run(req.params.object, req.params.id, req.query); + 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) => { - const response = await this.deleteCommand.run(req.params.object, req.params.id, req.query); + 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); }); diff --git a/src/commands/share.command.ts b/src/commands/share.command.ts index ecc92a5..39edfde 100644 --- a/src/commands/share.command.ts +++ b/src/commands/share.command.ts @@ -10,7 +10,7 @@ export class ShareCommand { constructor(private cipherService: CipherService) { } async run(id: string, organizationId: string, requestJson: string): Promise { - if (requestJson == null || requestJson === '') { + if (process.env.BW_SERVE !== 'true' && (requestJson == null || requestJson === '')) { requestJson = await CliUtils.readStdin(); } diff --git a/src/send.program.ts b/src/send.program.ts index 8296b58..87340f3 100644 --- a/src/send.program.ts +++ b/src/send.program.ts @@ -119,8 +119,7 @@ export class SendProgram extends Program { .action(async object => { const cmd = new GetCommand(this.main.cipherService, this.main.folderService, this.main.collectionService, this.main.totpService, this.main.auditService, this.main.cryptoService, - this.main.userService, this.main.searchService, this.main.apiService, this.main.sendService, - this.main.environmentService); + this.main.userService, this.main.searchService, this.main.apiService); const response = await cmd.run('template', object, null); this.processResponse(response); });