Source: middleware/sanitiser.mjs

// eslint-disable-next-line no-unused-vars -- Referenced by the JSDoc return type.
import express from "express";
import { logDanger, logWarning, logInfo } from "../utilities/logger.mjs";

/**
 * Creates middleware that cleans, warns about, or rejects unsafe request body values.
 * @param {"clean"|"warn"|"reject"|"disable"} action Sanitisation behavior.
 * @returns {express.RequestHandler} Configured sanitiser middleware.
 */
export const sanitiser = function (action = "fail") {
  // options can be
  //      clean = to replace bad characters
  //      warn = log, but dont change anything
  //      reject = return a status 422 and dont process
  console.log(logInfo, `Sanitiser Middleware: Activated - Mode: ${action}`);

  return (req, res, next) => {
    if (req.body !== undefined) {
      if (action.toLowerCase() === "disable") {
        console.log(logInfo, `Sanitiser ** Disabled!`);
      } else {
        // Remove potentially dangerous characters
        for (const key of Object.keys(req.body)) {
          const testBad = /[$&+;=#|<>{}^*%]/.test(req.body[key]);
          // if bad characters found, print to the log and santisise bad data
          if (testBad) {
            const display = () => {
              if (process.env.NODE_ENV === "development") {
                console.log(logInfo, `Sanitiser (Mode: ${action})`);
                console.log(
                  logInfo,
                  `              -  From IP        ${req.connection.remoteAddress}`,
                );
                console.log(
                  logInfo,
                  `              -  Logged at:     ${new Date().toLocaleString()}`,
                );
                console.log(
                  logInfo,
                  `              -  Issue with:    body.${key}`,
                );
                console.log(
                  logInfo,
                  `              -  Original text: ${req.body[key]} `,
                );
              }
            };

            // ignore if password field
            if (key.toLowerCase().includes("password")) {
              continue;
            }

            switch (action.toLowerCase()) {
              case "clean":
                display();
                req.body[key] = req.body[key].replace(
                  /[$&+,:;=?#|'<>.^*()%!-]/g,
                  "",
                );
                if (process.env.NODE_ENV === "development") {
                  console.log(
                    logInfo,
                    `                Sanitised to: ${req.body[key]} `,
                  );
                }
                break;
              case "warn":
                display();
                if (process.env.NODE_ENV === "development") {
                  console.log(logWarning, `             Warning only`);
                }
                break;
              case "reject": {
                const errorMessage = `Field: ${key} - '${req.body[key]}'`;
                //if (process.env.NODE_ENV === "development") {
                console.log(
                  logWarning,
                  `Sanitiser (Mode: ${action}) ${errorMessage}`,
                );
                //}
                return res.status(422).render("status.ejs", {
                  status: "Request Rejected",
                  message: errorMessage,
                  authenticatedUser: req.authenticatedUser,
                });
              }
              default:
                res.status(500);
                if (process.env.NODE_ENV === "development") {
                  console.log(
                    logDanger,
                    `Sanitiser (Mode: ${action}) You have NOT SELECTED A VALID OPTION for sanitiser - no action taken !!!`,
                  );
                }
                res
                  .setHeader("Content-Type", "application/json")
                  .status(500)
                  .json(
                    `{message: sanitiser middleware triggered, but using invalid option '${action}' - No action taken }`,
                  );
                return;
            }
          }
        }
      }
    }
    next();
  };
};