Browse Source

added new serve command

servecommand
Kyle Spearrin 4 years ago
parent
commit
b9bff7345f
  1. 1329
      package-lock.json
  2. 4
      package.json
  3. 2
      src/commands/confirm.command.ts
  4. 51
      src/commands/create.command.ts
  5. 2
      src/commands/delete.command.ts
  6. 8
      src/commands/download.command.ts
  7. 16
      src/commands/edit.command.ts
  8. 2
      src/commands/generate.command.ts
  9. 2
      src/commands/get.command.ts
  10. 2
      src/commands/list.command.ts
  11. 2
      src/commands/lock.command.ts
  12. 2
      src/commands/restore.command.ts
  13. 173
      src/commands/serve.command.ts
  14. 21
      src/commands/share.command.ts
  15. 2
      src/commands/unlock.command.ts
  16. 16
      src/program.ts

1329
package-lock.json generated

File diff suppressed because it is too large Load Diff

4
package.json

@ -49,10 +49,12 @@ @@ -49,10 +49,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": "^14.17.1",
"@types/node-fetch": "^2.5.10",
"@types/node-forge": "^0.9.7",
@ -77,12 +79,14 @@ @@ -77,12 +79,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",

2
src/commands/confirm.command.ts

@ -12,7 +12,7 @@ import { Utils } from 'jslib-common/misc/utils'; @@ -12,7 +12,7 @@ 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, cmd: program.Command | any): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}

51
src/commands/create.command.ts

@ -32,7 +32,8 @@ export class CreateCommand { @@ -32,7 +32,8 @@ export class CreateCommand {
private userService: UserService, private cryptoService: CryptoService,
private apiService: ApiService) { }
async run(object: string, requestJson: string, cmd: program.Command): Promise<Response> {
async run(object: string, requestJson: any, cmd: program.Command | any,
additionalData: any = null): Promise<Response> {
let req: any = null;
if (object !== 'attachment') {
if (requestJson == null || requestJson === '') {
@ -43,11 +44,15 @@ export class CreateCommand { @@ -43,11 +44,15 @@ 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.');
}
}
}
@ -55,7 +60,7 @@ export class CreateCommand { @@ -55,7 +60,7 @@ export class CreateCommand {
case 'item':
return await this.createCipher(req);
case 'attachment':
return await this.createAttachment(cmd);
return await this.createAttachment(cmd, additionalData);
case 'folder':
return await this.createFolder(req);
case 'org-collection':
@ -78,16 +83,32 @@ export class CreateCommand { @@ -78,16 +83,32 @@ export class CreateCommand {
}
}
private async createAttachment(options: program.OptionValues) {
private async createAttachment(options: program.OptionValues, additionalData: any) {
if (options.itemid == null || options.itemid === '') {
return Response.badRequest('--itemid <itemid> required.');
}
if (options.file == null || options.file === '') {
return Response.badRequest('--file <file> required.');
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 <file> 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);
}
if (fileBuf == null) {
return Response.badRequest('File not provided.');
}
const filePath = path.resolve(options.file);
if (!fs.existsSync(options.file)) {
return Response.badRequest('Cannot find file at ' + filePath);
if (fileName == null || fileName.trim() === '') {
return Response.badRequest('File name not provided.');
}
const itemId = options.itemid.toLowerCase();
@ -107,9 +128,7 @@ export class CreateCommand { @@ -107,9 +128,7 @@ export class CreateCommand {
}
try {
const fileBuf = fs.readFileSync(filePath);
await this.cipherService.saveAttachmentRawWithServer(cipher, path.basename(filePath),
new Uint8Array(fileBuf).buffer);
await this.cipherService.saveAttachmentRawWithServer(cipher, fileName, new Uint8Array(fileBuf).buffer);
const updatedCipher = await this.cipherService.get(cipher.id);
const decCipher = await updatedCipher.decrypt();
const res = new CipherResponse(decCipher);

2
src/commands/delete.command.ts

@ -13,7 +13,7 @@ export class DeleteCommand { @@ -13,7 +13,7 @@ export class DeleteCommand {
constructor(private cipherService: CipherService, private folderService: FolderService,
private userService: UserService, private apiService: ApiService) { }
async run(object: string, id: string, cmd: program.Command): Promise<Response> {
async run(object: string, id: string, cmd: program.Command | any): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}

8
src/commands/download.command.ts

@ -5,6 +5,7 @@ import { CryptoService } from 'jslib-common/abstractions/crypto.service'; @@ -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';
@ -20,7 +21,12 @@ export abstract class DownloadCommand { @@ -20,7 +21,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);

16
src/commands/edit.command.ts

@ -28,7 +28,7 @@ export class EditCommand { @@ -28,7 +28,7 @@ export class EditCommand {
constructor(private cipherService: CipherService, private folderService: FolderService,
private cryptoService: CryptoService, private apiService: ApiService) { }
async run(object: string, id: string, requestJson: string, cmd: program.Command): Promise<Response> {
async run(object: string, id: string, requestJson: any, cmd: program.Command | any): Promise<Response> {
if (requestJson == null || requestJson === '') {
requestJson = await CliUtils.readStdin();
}
@ -38,11 +38,15 @@ export class EditCommand { @@ -38,11 +38,15 @@ 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) {

2
src/commands/generate.command.ts

@ -8,7 +8,7 @@ import { StringResponse } from 'jslib-node/cli/models/response/stringResponse'; @@ -8,7 +8,7 @@ import { StringResponse } from 'jslib-node/cli/models/response/stringResponse';
export class GenerateCommand {
constructor(private passwordGenerationService: PasswordGenerationService) { }
async run(cmdOptions: program.OptionValues): Promise<Response> {
async run(cmdOptions: program.OptionValues | any): Promise<Response> {
const options = {
uppercase: cmdOptions.uppercase || false,
lowercase: cmdOptions.lowercase || false,

2
src/commands/get.command.ts

@ -66,7 +66,7 @@ export class GetCommand extends DownloadCommand { @@ -66,7 +66,7 @@ export class GetCommand extends DownloadCommand {
super(cryptoService);
}
async run(object: string, id: string, options: program.OptionValues): Promise<Response> {
async run(object: string, id: string, options: program.OptionValues | any): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}

2
src/commands/list.command.ts

@ -37,7 +37,7 @@ export class ListCommand { @@ -37,7 +37,7 @@ export class ListCommand {
private collectionService: CollectionService, private userService: UserService,
private searchService: SearchService, private apiService: ApiService) { }
async run(object: string, cmd: program.Command): Promise<Response> {
async run(object: string, cmd: program.Command | any): Promise<Response> {
switch (object.toLowerCase()) {
case 'items':
return await this.listCiphers(cmd);

2
src/commands/lock.command.ts

@ -8,7 +8,7 @@ import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse' @@ -8,7 +8,7 @@ import { MessageResponse } from 'jslib-node/cli/models/response/messageResponse'
export class LockCommand {
constructor(private vaultTimeoutService: VaultTimeoutService) { }
async run(cmd: program.Command) {
async run(cmd: program.Command | any) {
await this.vaultTimeoutService.lock();
process.env.BW_SESSION = null;
const res = new MessageResponse('Your vault is locked.', null);

2
src/commands/restore.command.ts

@ -7,7 +7,7 @@ import { Response } from 'jslib-node/cli/models/response'; @@ -7,7 +7,7 @@ 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, cmd: program.Command | any): Promise<Response> {
if (id != null) {
id = id.toLowerCase();
}

173
src/commands/serve.command.ts

@ -0,0 +1,173 @@ @@ -0,0 +1,173 @@
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 { 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;
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.userService, this.main.searchService,
this.main.apiService, this.main.sendService, this.main.environmentService);
this.listCommand = new ListCommand(this.main.cipherService, this.main.folderService,
this.main.collectionService, this.main.userService, this.main.searchService, this.main.apiService);
this.createCommand = new CreateCommand(this.main.cipherService, this.main.folderService,
this.main.userService, 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.userService, this.main.vaultTimeoutService);
this.deleteCommand = new DeleteCommand(this.main.cipherService, this.main.folderService, this.main.userService,
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.userService,
this.main.cryptoFunctionService, this.main.apiService, this.main.logService);
}
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) => {
const response = await this.listCommand.run(req.params.object, 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(req.query);
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, req.query);
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, req.query);
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('/:object', async (req, res) => {
const 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);
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);
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);
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);
}
}
}

21
src/commands/share.command.ts

@ -11,7 +11,7 @@ import { CliUtils } from '../utils'; @@ -11,7 +11,7 @@ import { CliUtils } from '../utils';
export class ShareCommand {
constructor(private cipherService: CipherService) { }
async run(id: string, organizationId: string, requestJson: string, cmd: program.Command): Promise<Response> {
async run(id: string, organizationId: string, requestJson: string, cmd: program.Command | any): Promise<Response> {
if (requestJson == null || requestJson === '') {
requestJson = await CliUtils.readStdin();
}
@ -21,14 +21,19 @@ export class ShareCommand { @@ -21,14 +21,19 @@ 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) {

2
src/commands/unlock.command.ts

@ -23,7 +23,7 @@ export class UnlockCommand { @@ -23,7 +23,7 @@ export class UnlockCommand {
private logService: ConsoleLogService) {
}
async run(password: string, options: program.OptionValues) {
async run(password: string, options: program.OptionValues | any) {
const canInteract = process.env.BW_NOINTERACTION !== 'true';
if (password == null || password === '') {
if (options?.passwordfile) {

16
src/program.ts

@ -8,6 +8,7 @@ import { EncodeCommand } from './commands/encode.command'; @@ -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';
@ -395,6 +396,21 @@ export class Program extends BaseProgram { @@ -395,6 +396,21 @@ export class Program extends BaseProgram {
this.processResponse(response);
});
program
.command('serve')
.description('Start a RESTful API webserver.')
.option('--port <port>', 'Provides a custom web vault URL that differs from the base URL.')
.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) {

Loading…
Cancel
Save