Serve command fixes (#492)

* change to koa to support async/await

* get rid of session header

* error is unlocked for certain commands

* fix lint error

* use "object" routes

* revert change to vs code  launch
This commit is contained in:
Kyle Spearrin 2022-02-25 17:35:27 -05:00 committed by GitHub
parent 3a14f04960
commit ed33d77b7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 994 additions and 626 deletions

1291
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -54,13 +54,16 @@
}, },
"devDependencies": { "devDependencies": {
"@fluffy-spoon/substitute": "^1.208.0", "@fluffy-spoon/substitute": "^1.208.0",
"@types/express": "^4.17.13",
"@types/inquirer": "^7.3.1", "@types/inquirer": "^7.3.1",
"@types/jasmine": "^3.7.0", "@types/jasmine": "^3.7.0",
"@types/jsdom": "^16.2.10", "@types/jsdom": "^16.2.10",
"@types/koa": "^2.13.4",
"@types/koa__multer": "^2.0.4",
"@types/koa__router": "^8.0.11",
"@types/koa-bodyparser": "^4.3.5",
"@types/koa-json": "^2.0.20",
"@types/lowdb": "^1.0.10", "@types/lowdb": "^1.0.10",
"@types/lunr": "^2.3.3", "@types/lunr": "^2.3.3",
"@types/multer": "^1.4.7",
"@types/node": "^16.11.12", "@types/node": "^16.11.12",
"@types/node-fetch": "^2.5.10", "@types/node-fetch": "^2.5.10",
"@types/node-forge": "^0.9.7", "@types/node-forge": "^0.9.7",
@ -93,18 +96,22 @@
"webpack-node-externals": "^3.0.0" "webpack-node-externals": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"@koa/multer": "^3.0.0",
"@koa/router": "^10.1.1",
"big-integer": "1.6.48", "big-integer": "1.6.48",
"browser-hrtime": "^1.1.8", "browser-hrtime": "^1.1.8",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"commander": "7.2.0", "commander": "7.2.0",
"express": "^4.17.1",
"form-data": "4.0.0", "form-data": "4.0.0",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"inquirer": "8.0.0", "inquirer": "8.0.0",
"jsdom": "^16.5.3", "jsdom": "^16.5.3",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"koa-json": "^2.0.2",
"lowdb": "1.0.0", "lowdb": "1.0.0",
"lunr": "^2.3.9", "lunr": "^2.3.9",
"multer": "^1.4.3", "multer": "^1.4.4",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-forge": "0.10.0", "node-forge": "0.10.0",
"open": "^8.0.8", "open": "^8.0.8",

View File

@ -1,6 +1,9 @@
import * as koaMulter from "@koa/multer";
import * as koaRouter from "@koa/router";
import * as program from "commander"; import * as program from "commander";
import * as express from "express"; import * as koa from "koa";
import * as multer from "multer"; import * as koaBodyParser from "koa-bodyparser";
import * as koaJson from "koa-json";
import { Main } from "../bw"; import { Main } from "../bw";
@ -28,6 +31,8 @@ import { SendRemovePasswordCommand } from "./send/removePassword.command";
import { Response } from "jslib-node/cli/models/response"; import { Response } from "jslib-node/cli/models/response";
import { FileResponse } from "jslib-node/cli/models/response/fileResponse"; import { FileResponse } from "jslib-node/cli/models/response/fileResponse";
import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions";
export class ServeCommand { export class ServeCommand {
private listCommand: ListCommand; private listCommand: ListCommand;
private getCommand: GetCommand; private getCommand: GetCommand;
@ -144,158 +149,247 @@ export class ServeCommand {
async run(options: program.OptionValues) { async run(options: program.OptionValues) {
const port = options.port || 8087; const port = options.port || 8087;
const server = express(); const server = new koa();
const router = new koaRouter();
process.env.BW_SERVE = "true"; process.env.BW_SERVE = "true";
process.env.BW_NOINTERACTION = "true"; process.env.BW_NOINTERACTION = "true";
server.use(express.json()); server.use(koaBodyParser()).use(koaJson({ pretty: false, param: "pretty" }));
server.use((req, res, next) => {
const sessionHeader = req.get("Session"); router.get("/generate", async (ctx, next) => {
if (sessionHeader != null && sessionHeader !== "") { const response = await this.generateCommand.run(ctx.request.query);
process.env.BW_SESSION = sessionHeader; this.processResponse(ctx.response, response);
} await next();
next();
}); });
server.get("/generate", async (req, res) => { router.get("/status", async (ctx, next) => {
const response = await this.generateCommand.run(req.query);
this.processResponse(res, response);
});
server.get("/status", async (req, res) => {
const response = await this.statusCommand.run(); const response = await this.statusCommand.run();
this.processResponse(res, response); this.processResponse(ctx.response, response);
await next();
}); });
server.get("/list/:object", async (req, res) => { router.get("/list/object/:object", async (ctx, next) => {
let response: Response = null; if (await this.errorIfLocked(ctx.response)) {
if (req.params.object === "send") { await next();
response = await this.sendListCommand.run(req.query); return;
} else {
response = await this.listCommand.run(req.params.object, req.query);
} }
this.processResponse(res, response); let response: Response = null;
if (ctx.params.object === "send") {
response = await this.sendListCommand.run(ctx.request.query);
} else {
response = await this.listCommand.run(ctx.params.object, ctx.request.query);
}
this.processResponse(ctx.response, response);
await next();
}); });
server.get("/send/list", async (req, res) => { router.get("/send/list", async (ctx, next) => {
const response = await this.sendListCommand.run(req.query); if (await this.errorIfLocked(ctx.response)) {
this.processResponse(res, response); await next();
return;
}
const response = await this.sendListCommand.run(ctx.request.query);
this.processResponse(ctx.response, response);
await next();
}); });
server.post("/sync", async (req, res) => { router.post("/sync", async (ctx, next) => {
const response = await this.syncCommand.run(req.query); const response = await this.syncCommand.run(ctx.request.query);
this.processResponse(res, response); this.processResponse(ctx.response, response);
await next();
}); });
server.post("/lock", async (req, res) => { router.post("/lock", async (ctx, next) => {
const response = await this.lockCommand.run(); const response = await this.lockCommand.run();
this.processResponse(res, response); this.processResponse(ctx.response, response);
await next();
}); });
server.post("/unlock", async (req, res) => { router.post("/unlock", async (ctx, next) => {
const response = await this.unlockCommand.run( const response = await this.unlockCommand.run(
req.body == null ? null : (req.body.password as string), ctx.request.body.password == null ? null : (ctx.request.body.password as string),
req.query ctx.request.query
); );
this.processResponse(res, response); this.processResponse(ctx.response, response);
await next();
}); });
server.post("/confirm/:object/:id", async (req, res) => { router.post("/confirm/:object/:id", async (ctx, next) => {
const response = await this.confirmCommand.run(req.params.object, req.params.id, req.query); if (await this.errorIfLocked(ctx.response)) {
this.processResponse(res, response); await next();
}); return;
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); const response = await this.confirmCommand.run(
ctx.params.object,
ctx.params.id,
ctx.request.query
);
this.processResponse(ctx.response, response);
await next();
}); });
server.put("/:object/:id", async (req, res) => { router.post("/restore/:object/:id", async (ctx, next) => {
if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
const response = await this.restoreCommand.run(ctx.params.object, ctx.params.id);
this.processResponse(ctx.response, response);
await next();
});
router.post("/move/:id/:organizationId", async (ctx, next) => {
if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
const response = await this.shareCommand.run(
ctx.params.id,
ctx.params.organizationId,
ctx.request.body // TODO: Check the format of this body for an array of collection ids
);
this.processResponse(ctx.response, response);
await next();
});
router.post("/attachment", koaMulter().single("file"), async (ctx, next) => {
if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
const response = await this.createCommand.run(
"attachment",
ctx.request.body,
ctx.request.query,
{
fileBuffer: ctx.request.file.buffer,
fileName: ctx.request.file.originalname,
}
);
this.processResponse(ctx.response, response);
await next();
});
router.post("/send/:id/remove-password", async (ctx, next) => {
if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
const response = await this.sendRemovePasswordCommand.run(ctx.params.id);
this.processResponse(ctx.response, response);
await next();
});
router.post("/object/:object", async (ctx, next) => {
if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
let response: Response = null; let response: Response = null;
if (req.params.object === "send") { if (ctx.params.object === "send") {
req.body.id = req.params.id; response = await this.sendCreateCommand.run(ctx.request.body, ctx.request.query);
response = await this.sendEditCommand.run(req.body, req.query);
} else { } else {
response = await this.editCommand.run( response = await this.createCommand.run(
req.params.object, ctx.params.object,
req.params.id, ctx.request.body,
req.body, ctx.request.query
req.query
); );
} }
this.processResponse(res, response); this.processResponse(ctx.response, response);
await next();
}); });
server.get("/:object/:id", async (req, res) => { router.put("/object/:object/:id", async (ctx, next) => {
let response: Response = null; if (await this.errorIfLocked(ctx.response)) {
if (req.params.object === "send") { await next();
response = await this.sendGetCommand.run(req.params.id, null); return;
} 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; let response: Response = null;
if (req.params.object === "send") { if (ctx.params.object === "send") {
response = await this.sendDeleteCommand.run(req.params.id); ctx.request.body.id = ctx.params.id;
response = await this.sendEditCommand.run(ctx.request.body, ctx.request.query);
} else { } else {
response = await this.deleteCommand.run(req.params.object, req.params.id, req.query); response = await this.editCommand.run(
ctx.params.object,
ctx.params.id,
ctx.request.body,
ctx.request.query
);
} }
this.processResponse(res, response); this.processResponse(ctx.response, response);
await next();
}); });
server.listen(port, () => { router.get("/object/:object/:id", async (ctx, next) => {
this.main.logService.info("Listening on port " + port); if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
let response: Response = null;
if (ctx.params.object === "send") {
response = await this.sendGetCommand.run(ctx.params.id, null);
} else {
response = await this.getCommand.run(ctx.params.object, ctx.params.id, ctx.request.query);
}
this.processResponse(ctx.response, response);
await next();
}); });
router.delete("/object/:object/:id", async (ctx, next) => {
if (await this.errorIfLocked(ctx.response)) {
await next();
return;
}
let response: Response = null;
if (ctx.params.object === "send") {
response = await this.sendDeleteCommand.run(ctx.params.id);
} else {
response = await this.deleteCommand.run(
ctx.params.object,
ctx.params.id,
ctx.request.query
);
}
this.processResponse(ctx.response, response);
await next();
});
server
.use(router.routes())
.use(router.allowedMethods())
.listen(port, () => {
this.main.logService.info("Listening on port " + port);
});
} }
private processResponse(res: any, commandResponse: Response) { private processResponse(res: koa.Response, commandResponse: Response) {
if (!commandResponse.success) { if (!commandResponse.success) {
res.statusCode = 400; res.status = 400;
} }
if (commandResponse.data instanceof FileResponse) { if (commandResponse.data instanceof FileResponse) {
res.writeHead(200, { res.body = commandResponse.data.data;
"Content-Type": "application/octet-stream", res.attachment(commandResponse.data.fileName);
"Content-Disposition": "attachment;filename=" + commandResponse.data.fileName, res.set("Content-Type", "application/octet-stream");
"Content-Length": commandResponse.data.data.length, res.set("Content-Length", commandResponse.data.data.length.toString());
});
res.end(commandResponse.data.data);
} else { } else {
res.json(commandResponse); res.body = commandResponse;
} }
} }
private async errorIfLocked(res: koa.Response) {
const authed = await this.main.stateService.getIsAuthenticated();
if (!authed) {
this.processResponse(res, Response.error("You are not logged in."));
return true;
}
if (await this.main.cryptoService.hasKeyInMemory()) {
return false;
} else if (await this.main.cryptoService.hasKeyStored(KeySuffixOptions.Auto)) {
// load key into memory
await this.main.cryptoService.getKey();
return false;
}
this.processResponse(res, Response.error("Vault is locked."));
return true;
}
} }