diff --git a/backend/api/routes/quotes.js b/backend/api/routes/quotes.js index 93eb1d828..d5e18b984 100644 --- a/backend/api/routes/quotes.js +++ b/backend/api/routes/quotes.js @@ -1,8 +1,11 @@ +const joi = require("joi"); const { authenticateRequest } = require("../../middlewares/auth"); const { Router } = require("express"); const NewQuotesController = require("../controllers/new-quotes"); const QuoteRatingsController = require("../controllers/quote-ratings"); const RateLimit = require("../../middlewares/rate-limit"); +const { requestValidation } = require("../../middlewares/apiUtils"); +const SUPPORTED_QUOTE_LANGUAGES = require("../../constants/quoteLanguages"); const quotesRouter = Router(); @@ -48,4 +51,31 @@ quotesRouter.post( QuoteRatingsController.submitRating ); +quotesRouter.post( + "/report", + RateLimit.quoteReportSubmit, + authenticateRequest, + requestValidation({ + body: { + quoteId: joi.string().required(), + quoteLanguage: joi + .string() + .valid(...SUPPORTED_QUOTE_LANGUAGES) + .required(), + reason: joi + .string() + .valid( + "Grammatical error", + "Inappropriate content", + "Low quality content" + ) + .required(), + comment: joi.string().allow("").max(250).required(), + }, + }), + (req, res) => { + res.sendStatus(200); + } +); + module.exports = quotesRouter; diff --git a/backend/constants/quoteLanguages.js b/backend/constants/quoteLanguages.js new file mode 100644 index 000000000..afb7cedec --- /dev/null +++ b/backend/constants/quoteLanguages.js @@ -0,0 +1,37 @@ +const SUPPORTED_QUOTE_LANGUAGES = [ + "albanian", + "arabic", + "code_c++", + "code_c", + "code_java", + "code_javascript", + "code_python", + "code_rust", + "czech", + "danish", + "dutch", + "english", + "filipino", + "french", + "german", + "hindi", + "icelandic", + "indonesian", + "irish", + "italian", + "lithuanian", + "malagasy", + "polish", + "portuguese", + "russian", + "serbian", + "slovak", + "spanish", + "swedish", + "thai", + "toki_pona", + "turkish", + "vietnamese", +]; + +module.exports = SUPPORTED_QUOTE_LANGUAGES; diff --git a/backend/middlewares/apiUtils.js b/backend/middlewares/apiUtils.js new file mode 100644 index 000000000..f8aaaa91d --- /dev/null +++ b/backend/middlewares/apiUtils.js @@ -0,0 +1,32 @@ +const joi = require("joi"); +const MonkeyError = require("../handlers/error"); + +function requestValidation(validationSchema) { + return (req, res, next) => { + // In dev environments, as an alternative to token authentication, + // you can pass the authentication middleware by having a user id in the body. + // Inject the user id into the schema so that validation will not fail. + if (process.env.MODE === "dev") { + validationSchema.body = { + uid: joi.any(), + ...(validationSchema.body ?? {}), + }; + } + + Object.keys(validationSchema).forEach((key) => { + const schema = validationSchema[key]; + const joiSchema = joi.object().keys(schema); + const { error } = joiSchema.validate(req[key] ?? {}); + if (error) { + const errorMessage = error.details[0].message; + throw new MonkeyError(400, `Invalid request: ${errorMessage}`); + } + }); + + next(); + }; +} + +module.exports = { + requestValidation, +}; diff --git a/backend/middlewares/rate-limit.js b/backend/middlewares/rate-limit.js index 9559695a1..34d210532 100644 --- a/backend/middlewares/rate-limit.js +++ b/backend/middlewares/rate-limit.js @@ -68,6 +68,14 @@ exports.quoteRatingsSubmit = rateLimit({ keyGenerator: getAddress, }); +// Quote reporting +exports.quoteReportSubmit = rateLimit({ + windowMs: 30 * 60 * 1000, // 30 min + max: 50 * multiplier, + message, + keyGenerator: getAddress, +}); + // Presets Routing exports.presetsGet = rateLimit({ windowMs: 60 * 60 * 1000, // 60 min diff --git a/package-lock.json b/package-lock.json index 78c24d459..9347d6c85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1735,6 +1735,19 @@ } } }, + "@hapi/hoek": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", + "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -2005,6 +2018,24 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "optional": true }, + "@sideway/address": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz", + "integrity": "sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -7813,6 +7844,18 @@ "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", "dev": true }, + "joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "jose": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", diff --git a/package.json b/package.json index a71394175..3021ee395 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "gulp-replace": "^1.1.3", "helmet": "^4.6.0", "howler": "^2.2.1", + "joi": "^17.6.0", "moment-timezone": "^0.5.33", "mongodb": "^3.6.9", "node-fetch": "^2.6.7",