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": {
"@prisma/client": "^4.0.0",
"body-parser": "^1.20.0",
"class-validator": "^0.13.2",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-rate-limit": "^6.4.0",
@ -930,6 +931,15 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"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": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@ -2935,6 +2945,11 @@
"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": {
"version": "4.0.0",
"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"
}
},
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -6265,6 +6288,15 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"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": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@ -7685,6 +7717,11 @@
"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": {
"version": "4.0.0",
"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"
}
},
"validator": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
"integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

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

View File

@ -3,9 +3,10 @@ import request from "supertest";
import { describe, it, expect } from "vitest";
import prisma from "./client";
// const testNote with base64 ciphertext and hmac
const testNote = {
ciphertext: "sample_ciphertext",
hmac: "sample_hmac",
ciphertext: Buffer.from("sample_ciphertext").toString("base64"),
hmac: Buffer.from("sample_hmac").toString("base64"),
};
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 () => {
// Make post request
let res = await request(app).post("/api/note").send(testNote);

View File

@ -1,5 +1,5 @@
import "dotenv/config";
import express, { Express, Request } from "express";
import express, { Express, Request, Response } from "express";
import { EncryptedNote } from "@prisma/client";
import { addDays } from "./util";
import helmet from "helmet";
@ -8,6 +8,8 @@ import pinoHttp from "pino-http";
import logger from "./logger";
import prisma from "./client";
import bodyParser from "body-parser";
import { NotePostRequest } from "./model/NotePostRequest";
import { validateOrReject } from "class-validator";
// Initialize middleware clients
const app: Express = express();
@ -48,7 +50,7 @@ const getLimiter = rateLimit({
app.use(bodyParser.json({ limit: "400k" }));
// 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
.findUnique({
where: { id: req.params.id },
@ -63,24 +65,28 @@ app.get("/api/note/:id", getLimiter, (req, res, next) => {
});
// Post new encrypted note
app.post(
"/api/note/",
postLimiter,
(req: Request<{}, {}, EncryptedNote>, res, next) => {
const note = req.body;
prisma.encryptedNote
.create({
data: { ...note, expire_time: addDays(new Date(), 30) },
})
.then((savedNote) => {
res.json({
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
expire_time: savedNote.expire_time,
});
})
.catch(next);
}
);
app.post("/api/note/", postLimiter, (req: Request, res: Response, next) => {
const notePostRequest = new NotePostRequest();
Object.assign(notePostRequest, req.body);
validateOrReject(notePostRequest).catch((err) => {
res.status(400).send(err.message);
});
const note = notePostRequest as EncryptedNote;
prisma.encryptedNote
.create({
data: {
...note,
expire_time: addDays(new Date(), 30),
},
})
.then((savedNote) => {
res.json({
view_url: `${process.env.FRONTEND_URL}/note/${savedNote.id}`,
expire_time: savedNote.expire_time,
});
})
.catch(next);
});
// For testing purposes
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": {
"experimentalDecorators": true,
"sourceMap": true,
"outDir": "./build",
"lib": ["esnext"],