diff --git a/package.json b/package.json index 1cc3ad5..2aec4b1 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dayjs": "1.10.4", "discord.js": "12.5.1", "emoji-mart": "3.0.1", + "express-rate-limit": "^5.2.6", "formik": "2.2.6", "generate-license-file": "1.1.0", "josa": "3.0.1", @@ -59,6 +60,7 @@ "@types/cookie": "0.4.0", "@types/core-js": "2.5.4", "@types/emoji-mart": "3.0.4", + "@types/express-rate-limit": "^5.1.1", "@types/jest": "26.0.20", "@types/josa": "3.0.2", "@types/jsonwebtoken": "8.5.0", diff --git a/pages/api/index.ts b/pages/api/index.ts new file mode 100644 index 0000000..ab4f75c --- /dev/null +++ b/pages/api/index.ts @@ -0,0 +1 @@ +export { default } from './[...404]' \ No newline at end of file diff --git a/utils/RequestHandler.ts b/utils/RequestHandler.ts index 0c283b0..9522ef1 100644 --- a/utils/RequestHandler.ts +++ b/utils/RequestHandler.ts @@ -1,7 +1,16 @@ import { NextApiRequest, NextApiResponse } from 'next' import nc from 'next-connect' +import rateLimit from 'express-rate-limit' import ResponseWrapper from '@utils/ResponseWrapper' +const limiter = rateLimit({ + windowMs: 60 * 1000, + max: 120, + statusCode: 429, + handler: (_req, res) => ResponseWrapper(res, { code: 429 }), + keyGenerator: (req) => req.headers['x-forwarded-for'] as string, + +}) const RequestHandler = () => nc({ onNoMatch(_req, res) { @@ -12,5 +21,6 @@ const RequestHandler = () => return ResponseWrapper(res, { code: 500 }) }, }) + .use(limiter) export default RequestHandler diff --git a/utils/ResponseWrapper.ts b/utils/ResponseWrapper.ts index 5fb3177..2bf02b0 100644 --- a/utils/ResponseWrapper.ts +++ b/utils/ResponseWrapper.ts @@ -1,9 +1,8 @@ import http from 'http' -import { NextApiResponse } from 'next' import { ResponseProps } from '@types' import { ErrorText } from './Constants' export default function ResponseWrapper( - res: NextApiResponse, + res: ApiResponse, { code = 200, message, version = 2, data, errors }: ResponseProps ) { if (!code) throw new Error('`code` is required.') @@ -18,3 +17,9 @@ export default function ResponseWrapper( ...(message || !data ? { message: message || ErrorText[code] || http.STATUS_CODES[code] } : {}), }) } + +interface ApiResponse { + statusCode: number + setHeader(key: string, value: string): void + json(json: unknown): void +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a777a45..82c669f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -911,6 +911,21 @@ dependencies: "@babel/types" "^7.3.0" +"@types/body-parser@*": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.34" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" + integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + dependencies: + "@types/node" "*" + "@types/cookie@0.4.0": version "0.4.0" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz" @@ -928,6 +943,32 @@ dependencies: "@types/react" "*" +"@types/express-rate-limit@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-rate-limit/-/express-rate-limit-5.1.1.tgz#e5b0239d18c1580e52ae56dce4248333302a1dc8" + integrity sha512-6oMYZBLlhxC5sdcRXXz528QyfGz3zTy9YdHwqlxLfgx5Cd3zwYaUjjPpJcaTtHmRefLi9P8kLBPz2wB7yz4JtQ== + dependencies: + "@types/express" "*" + +"@types/express-serve-static-core@^4.17.18": + version "4.17.18" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" + integrity sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*": + version "4.17.11" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" + integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/graceful-fs@^4.1.2": version "4.1.4" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz" @@ -984,6 +1025,11 @@ resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz" integrity sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/node-emoji@1.8.1": version "1.8.1" resolved "https://registry.npmjs.org/@types/node-emoji/-/node-emoji-1.8.1.tgz" @@ -1022,6 +1068,16 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== +"@types/qs@*": + version "6.9.5" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.5.tgz#434711bdd49eb5ee69d90c1d67c354a9a8ecb18b" + integrity sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + "@types/react-dom@*": version "17.0.0" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz" @@ -1069,6 +1125,14 @@ dependencies: htmlparser2 "^4.1.0" +"@types/serve-static@*": + version "1.13.9" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" + integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + "@types/stack-utils@^2.0.0": version "2.0.0" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz" @@ -3164,6 +3228,11 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" +express-rate-limit@^5.2.6: + version "5.2.6" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.2.6.tgz#b454e1be8a252081bda58460e0a25bf43ee0f7b0" + integrity sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA== + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"