From 7ff9cc3e2901ae0181e8b7ac73b9af158a6d62b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Huchet?= <raph@selego.co>
Date: Thu, 10 Mar 2022 17:50:28 +0100
Subject: [PATCH 1/2] chore(security): place controller

---
 api/src/controllers/place.js | 128 ++++++++++++++++++++---------------
 1 file changed, 73 insertions(+), 55 deletions(-)

diff --git a/api/src/controllers/place.js b/api/src/controllers/place.js
index 476366f6d..8595b413c 100644
--- a/api/src/controllers/place.js
+++ b/api/src/controllers/place.js
@@ -2,31 +2,45 @@ const express = require("express");
 const router = express.Router();
 const passport = require("passport");
 const { Op } = require("sequelize");
+const { z } = require("zod");
 const { catchErrors } = require("../errors");
 const validateOrganisationEncryption = require("../middleware/validateOrganisationEncryption");
 const validateUser = require("../middleware/validateUser");
 const Place = require("../models/place");
+const { looseUuidRegex, positiveIntegerRegex } = require("../utils");
 
 router.post(
   "/",
   passport.authenticate("user", { session: false }),
   validateUser(["admin", "normal"]),
   validateOrganisationEncryption,
-  catchErrors(async (req, res, next) => {
-    if (!req.body.name) return res.status(400).send({ ok: false, error: "Name is needed" });
-
-    const newPlace = {};
-
-    newPlace.organisation = req.user.organisation;
-
-    if (!req.body.hasOwnProperty("encrypted") || !req.body.hasOwnProperty("encryptedEntityKey")) {
-      return next("No encrypted field in place create");
+  catchErrors(async (req, res) => {
+    try {
+      z.string().parse(req.body.encrypted);
+      z.string().parse(req.body.encryptedEntityKey);
+    } catch (e) {
+      return res.status(400).send({ ok: false, error: "Invalid request" });
     }
-    if (req.body.hasOwnProperty("encrypted")) newPlace.encrypted = req.body.encrypted || null;
-    if (req.body.hasOwnProperty("encryptedEntityKey")) newPlace.encryptedEntityKey = req.body.encryptedEntityKey || null;
 
-    const data = await Place.create(newPlace, { returning: true });
-    return res.status(200).send({ ok: true, data });
+    const data = await Place.create(
+      {
+        organisation: req.user.organisation,
+        encrypted: req.body.encrypted,
+        encryptedEntityKey: req.body.encryptedEntityKey,
+      },
+      { returning: true }
+    );
+    return res.status(200).send({
+      ok: true,
+      data: {
+        _id: data._id,
+        encrypted: data.encrypted,
+        encryptedEntityKey: data.encryptedEntityKey,
+        organisation: data.organisation,
+        createdAt: data.createdAt,
+        updatedAt: data.updatedAt,
+      },
+    });
   })
 );
 
@@ -35,34 +49,30 @@ router.get(
   passport.authenticate("user", { session: false }),
   validateUser(["admin", "normal"]),
   catchErrors(async (req, res) => {
+    try {
+      z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.limit);
+      z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.page);
+      z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.lastRefresh);
+    } catch (e) {
+      return res.status(400).send({ ok: false, error: "Invalid request" });
+    }
+    const { limit, page, lastRefresh } = req.query;
+
     const query = {
-      where: {
-        organisation: req.user.organisation,
-      },
+      where: { organisation: req.user.organisation },
       order: [["createdAt", "DESC"]],
     };
-    if (req.query.lastRefresh) {
-      query.where.updatedAt = { [Op.gte]: new Date(Number(req.query.lastRefresh)) };
-    }
 
     const total = await Place.count(query);
-    const limit = parseInt(req.query.limit, 10);
-    if (!!req.query.limit) query.limit = limit;
-    if (req.query.page) query.offset = parseInt(req.query.page, 10) * limit;
+    if (limit) query.limit = Number(limit);
+    if (page) query.offset = Number(page) * limit;
+    if (lastRefresh) query.where.updatedAt = { [Op.gte]: new Date(Number(lastRefresh)) };
 
     const data = await Place.findAll({
       ...query,
-      attributes: [
-        // Generic fields
-        "_id",
-        "encrypted",
-        "encryptedEntityKey",
-        "organisation",
-        "createdAt",
-        "updatedAt",
-      ],
+      attributes: ["_id", "encrypted", "encryptedEntityKey", "organisation", "createdAt", "updatedAt"],
     });
-    return res.status(200).send({ ok: true, data, hasMore: data.length === limit, total });
+    return res.status(200).send({ ok: true, data, hasMore: data.length === Number(limit), total });
   })
 );
 
@@ -71,28 +81,36 @@ router.put(
   passport.authenticate("user", { session: false }),
   validateUser(["admin", "normal"]),
   validateOrganisationEncryption,
-  catchErrors(async (req, res, next) => {
-    const query = {
-      where: {
-        _id: req.params._id,
-        organisation: req.user.organisation,
-      },
-    };
-
+  catchErrors(async (req, res) => {
+    try {
+      z.string().regex(looseUuidRegex).parse(req.params._id);
+      z.string().parse(req.body.encrypted);
+      z.string().parse(req.body.encryptedEntityKey);
+    } catch (e) {
+      return res.status(400).send({ ok: false, error: "Invalid request" });
+    }
+    const query = { where: { _id: req.params._id, organisation: req.user.organisation } };
     const place = await Place.findOne(query);
     if (!place) return res.status(404).send({ ok: false, error: "Not found" });
 
-    const updatePlace = {};
-    if (!req.body.hasOwnProperty("encrypted") || !req.body.hasOwnProperty("encryptedEntityKey")) {
-      return next("No encrypted field in place update");
-    }
-    if (req.body.hasOwnProperty("encrypted")) updatePlace.encrypted = req.body.encrypted || null;
-    if (req.body.hasOwnProperty("encryptedEntityKey")) updatePlace.encryptedEntityKey = req.body.encryptedEntityKey || null;
-
-    place.set(updatePlace);
+    const { encrypted, encryptedEntityKey } = req.body;
+    place.set({
+      encrypted: encrypted,
+      encryptedEntityKey: encryptedEntityKey,
+    });
     await place.save();
 
-    return res.status(200).send({ ok: true, data: place });
+    return res.status(200).send({
+      ok: true,
+      data: {
+        _id: place._id,
+        encrypted: place.encrypted,
+        encryptedEntityKey: place.encryptedEntityKey,
+        organisation: place.organisation,
+        createdAt: place.createdAt,
+        updatedAt: place.updatedAt,
+      },
+    });
   })
 );
 
@@ -101,12 +119,12 @@ router.delete(
   passport.authenticate("user", { session: false }),
   validateUser(["admin", "normal"]),
   catchErrors(async (req, res) => {
-    const query = {
-      where: {
-        _id: req.params._id,
-        organisation: req.user.organisation,
-      },
-    };
+    try {
+      z.string().regex(looseUuidRegex).parse(req.params._id);
+    } catch (e) {
+      return res.status(400).send({ ok: false, error: "Invalid request" });
+    }
+    const query = { where: { _id: req.params._id, organisation: req.user.organisation } };
 
     const place = await Place.findOne(query);
     if (!place) return res.status(404).send({ ok: false, error: "Not Found" });

From 9e15792559cfb828badb03e314ce6509f0649ce5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Huchet?= <raph@selego.co>
Date: Thu, 10 Mar 2022 17:52:01 +0100
Subject: [PATCH 2/2] chore(security): public controller

---
 api/src/controllers/public.js | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/api/src/controllers/public.js b/api/src/controllers/public.js
index 98b3bb5b6..380357c88 100644
--- a/api/src/controllers/public.js
+++ b/api/src/controllers/public.js
@@ -1,8 +1,7 @@
 const express = require("express");
 const router = express.Router();
-
+const { z } = require("zod");
 const { catchErrors } = require("../errors");
-
 const Action = require("../models/action");
 const Comment = require("../models/comment");
 const Person = require("../models/person");
@@ -12,16 +11,25 @@ const TerritoryObservation = require("../models/territoryObservation");
 const Report = require("../models/report");
 const RelPersonPlace = require("../models/relPersonPlace");
 const { Op } = require("sequelize");
+const { positiveIntegerRegex, looseUuidRegex } = require("../utils");
 
 router.get(
   "/stats",
   catchErrors(async (req, res) => {
+    try {
+      if (req.query.organisation) z.string().regex(looseUuidRegex).parse(req.query.organisation);
+      z.optional(z.string().regex(positiveIntegerRegex)).parse(req.query.lastRefresh);
+    } catch (e) {
+      return res.status(400).send({ ok: false, error: "Invalid request" });
+    }
+
     const query = {};
     if (req.query.organisation) query.where = { organisation: req.query.organisation };
     if (Number(req.query.lastRefresh)) {
       if (!query.where) query.where = {};
       query.where.updatedAt = { [Op.gte]: new Date(Number(req.query.lastRefresh)) };
     }
+
     const places = await Place.count(query);
     const relsPersonPlace = await RelPersonPlace.count(query);
     const actions = await Action.count(query);