validate POST body form

This commit is contained in:
Maxime Cannoodt 2022-07-10 23:26:52 +02:00
parent 1d824d5f07
commit 276ac5cfe0
6 changed files with 90 additions and 22 deletions

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@prisma/client": "^4.0.0", "@prisma/client": "^4.0.0",
"body-parser": "^1.20.0", "body-parser": "^1.20.0",
"class-validator": "^0.13.2",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",
"express-rate-limit": "^6.4.0", "express-rate-limit": "^6.4.0",
@ -930,6 +931,15 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true "dev": true
}, },
"node_modules/class-validator": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz",
"integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==",
"dependencies": {
"libphonenumber-js": "^1.9.43",
"validator": "^13.7.0"
}
},
"node_modules/clean-stack": { "node_modules/clean-stack": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@ -2935,6 +2945,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/libphonenumber-js": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.7.tgz",
"integrity": "sha512-jZXLCCWMe1b/HXkjiLeYt2JsytZMcqH26jLFIdzFDFF0xvSUWrYKyvPlyPG+XJzEyKUFbcZxLdWGMwQsWaHDxQ=="
},
"node_modules/load-json-file": { "node_modules/load-json-file": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@ -5260,6 +5275,14 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"node_modules/validator": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -6265,6 +6288,15 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true "dev": true
}, },
"class-validator": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz",
"integrity": "sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw==",
"requires": {
"libphonenumber-js": "^1.9.43",
"validator": "^13.7.0"
}
},
"clean-stack": { "clean-stack": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@ -7685,6 +7717,11 @@
"package-json": "^6.3.0" "package-json": "^6.3.0"
} }
}, },
"libphonenumber-js": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.7.tgz",
"integrity": "sha512-jZXLCCWMe1b/HXkjiLeYt2JsytZMcqH26jLFIdzFDFF0xvSUWrYKyvPlyPG+XJzEyKUFbcZxLdWGMwQsWaHDxQ=="
},
"load-json-file": { "load-json-file": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@ -9417,6 +9454,11 @@
"spdx-expression-parse": "^3.0.0" "spdx-expression-parse": "^3.0.0"
} }
}, },
"validator": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@ -18,6 +18,7 @@
"dependencies": { "dependencies": {
"@prisma/client": "^4.0.0", "@prisma/client": "^4.0.0",
"body-parser": "^1.20.0", "body-parser": "^1.20.0",
"class-validator": "^0.13.2",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",
"express-rate-limit": "^6.4.0", "express-rate-limit": "^6.4.0",

View File

@ -3,9 +3,10 @@ import request from "supertest";
import { describe, it, expect } from "vitest"; import { describe, it, expect } from "vitest";
import prisma from "./client"; import prisma from "./client";
// const testNote with base64 ciphertext and hmac
const testNote = { const testNote = {
ciphertext: "sample_ciphertext", ciphertext: Buffer.from("sample_ciphertext").toString("base64"),
hmac: "sample_hmac", hmac: Buffer.from("sample_hmac").toString("base64"),
}; };
describe("GET /api/note", () => { describe("GET /api/note", () => {
@ -78,6 +79,11 @@ describe("POST /api/note", () => {
); );
}); });
it("Returns a bad request on invalid POST body", async () => {
const res = await request(app).post("/api/note").send({});
expect(res.statusCode).toBe(400);
});
it("returns a valid view_url on correct POST body", async () => { it("returns a valid view_url on correct POST body", async () => {
// Make post request // Make post request
let res = await request(app).post("/api/note").send(testNote); let res = await request(app).post("/api/note").send(testNote);

View File

@ -1,5 +1,5 @@
import "dotenv/config"; import "dotenv/config";
import express, { Express, Request } from "express"; import express, { Express, Request, Response } from "express";
import { EncryptedNote } from "@prisma/client"; import { EncryptedNote } from "@prisma/client";
import { addDays } from "./util"; import { addDays } from "./util";
import helmet from "helmet"; import helmet from "helmet";
@ -8,6 +8,8 @@ import pinoHttp from "pino-http";
import logger from "./logger"; import logger from "./logger";
import prisma from "./client"; import prisma from "./client";
import bodyParser from "body-parser"; import bodyParser from "body-parser";
import { NotePostRequest } from "./model/NotePostRequest";
import { validateOrReject } from "class-validator";
// Initialize middleware clients // Initialize middleware clients
const app: Express = express(); const app: Express = express();
@ -48,7 +50,7 @@ const getLimiter = rateLimit({
app.use(bodyParser.json({ limit: "400k" })); app.use(bodyParser.json({ limit: "400k" }));
// Get encrypted note // Get encrypted note
app.get("/api/note/:id", getLimiter, (req, res, next) => { app.get("/api/note/:id", getLimiter, (req: Request, res: Response, next) => {
prisma.encryptedNote prisma.encryptedNote
.findUnique({ .findUnique({
where: { id: req.params.id }, where: { id: req.params.id },
@ -63,14 +65,19 @@ app.get("/api/note/:id", getLimiter, (req, res, next) => {
}); });
// Post new encrypted note // Post new encrypted note
app.post( app.post("/api/note/", postLimiter, (req: Request, res: Response, next) => {
"/api/note/", const notePostRequest = new NotePostRequest();
postLimiter, Object.assign(notePostRequest, req.body);
(req: Request<{}, {}, EncryptedNote>, res, next) => { validateOrReject(notePostRequest).catch((err) => {
const note = req.body; res.status(400).send(err.message);
});
const note = notePostRequest as EncryptedNote;
prisma.encryptedNote prisma.encryptedNote
.create({ .create({
data: { ...note, expire_time: addDays(new Date(), 30) }, data: {
...note,
expire_time: addDays(new Date(), 30),
},
}) })
.then((savedNote) => { .then((savedNote) => {
res.json({ res.json({
@ -79,8 +86,7 @@ app.post(
}); });
}) })
.catch(next); .catch(next);
} });
);
// For testing purposes // For testing purposes
app.get("/api/test", (req, res, next) => { app.get("/api/test", (req, res, next) => {

View File

@ -0,0 +1,12 @@
import { IsBase64 } from "class-validator";
/**
* Request body for creating a note
*/
export class NotePostRequest {
@IsBase64()
ciphertext: string | undefined;
@IsBase64()
hmac: string | undefined;
}

View File

@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true,
"sourceMap": true, "sourceMap": true,
"outDir": "./build", "outDir": "./build",
"lib": ["esnext"], "lib": ["esnext"],