mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-03 20:15:45 +08:00
Add new rate limiting flow (#3230) Bruception
* Add new rate limiting flow * Oops * Fix nit * Fix some bugs * Split key generation functions * Remove 429 * Change message for root limiter * Flag 429 and add config * Add status code config * Check enabled flag * Add custom status for ape keys * Bump coverage * swapped conditions around whats the point of checking if the status code is in the array if the whole thing is turned off anyway Co-authored-by: Miodec <bartnikjack@gmail.com>
This commit is contained in:
parent
bda7788bb2
commit
9da5e441be
21 changed files with 368 additions and 179 deletions
|
@ -9,8 +9,8 @@ export default {
|
|||
// These percentages should never decrease
|
||||
statements: 38,
|
||||
branches: 38,
|
||||
functions: 21,
|
||||
lines: 41,
|
||||
functions: 22,
|
||||
lines: 42,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
11
backend/package-lock.json
generated
11
backend/package-lock.json
generated
|
@ -29,6 +29,7 @@
|
|||
"object-hash": "3.0.0",
|
||||
"path": "0.12.7",
|
||||
"prom-client": "14.0.1",
|
||||
"rate-limiter-flexible": "2.3.7",
|
||||
"simple-git": "2.45.1",
|
||||
"string-similarity": "4.0.4",
|
||||
"swagger-stats": "0.99.2",
|
||||
|
@ -7167,6 +7168,11 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/rate-limiter-flexible": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.3.7.tgz",
|
||||
"integrity": "sha512-dmc+J/IffVBvHlqq5/XClsdLdkOdQV/tjrz00cwneHUbEDYVrf4aUDAyR4Jybcf2+Vpn4NwoVrnnAyt/D0ciWw=="
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.4.0",
|
||||
"license": "MIT",
|
||||
|
@ -13810,6 +13816,11 @@
|
|||
"range-parser": {
|
||||
"version": "1.2.1"
|
||||
},
|
||||
"rate-limiter-flexible": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.3.7.tgz",
|
||||
"integrity": "sha512-dmc+J/IffVBvHlqq5/XClsdLdkOdQV/tjrz00cwneHUbEDYVrf4aUDAyR4Jybcf2+Vpn4NwoVrnnAyt/D0ciWw=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"requires": {
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
"object-hash": "3.0.0",
|
||||
"path": "0.12.7",
|
||||
"prom-client": "14.0.1",
|
||||
"rate-limiter-flexible": "2.3.7",
|
||||
"simple-git": "2.45.1",
|
||||
"string-similarity": "4.0.4",
|
||||
"swagger-stats": "0.99.2",
|
||||
|
|
|
@ -14,11 +14,13 @@ const buildNumberInput = (schema, parentState, key) => {
|
|||
input.classList.add("base-input");
|
||||
input.type = "number";
|
||||
input.value = parentState[key];
|
||||
input.min = schema.min || 0;
|
||||
|
||||
const min = schema.min || 0;
|
||||
input.min = min;
|
||||
|
||||
input.addEventListener("change", () => {
|
||||
const normalizedValue = parseFloat(input.value, 10);
|
||||
parentState[key] = normalizedValue;
|
||||
parentState[key] = Math.max(normalizedValue, min);
|
||||
});
|
||||
|
||||
return input;
|
||||
|
@ -164,7 +166,7 @@ const render = (state, schema) => {
|
|||
items,
|
||||
element,
|
||||
state,
|
||||
`${currentKey}[${index}]`,
|
||||
index,
|
||||
`${path}[${index}]`
|
||||
);
|
||||
|
||||
|
|
|
@ -40,16 +40,16 @@ router.use(
|
|||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.apeKeysGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.apeKeysGet,
|
||||
checkIfUserCanManageApeKeys,
|
||||
asyncHandler(ApeKeyController.getApeKeys)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
RateLimit.apeKeysGenerate,
|
||||
authenticateRequest(),
|
||||
RateLimit.apeKeysGenerate,
|
||||
checkIfUserCanManageApeKeys,
|
||||
validateRequest({
|
||||
body: {
|
||||
|
@ -62,8 +62,8 @@ router.post(
|
|||
|
||||
router.patch(
|
||||
"/:apeKeyId",
|
||||
RateLimit.apeKeysUpdate,
|
||||
authenticateRequest(),
|
||||
RateLimit.apeKeysUpdate,
|
||||
checkIfUserCanManageApeKeys,
|
||||
validateRequest({
|
||||
params: {
|
||||
|
@ -79,8 +79,8 @@ router.patch(
|
|||
|
||||
router.delete(
|
||||
"/:apeKeyId",
|
||||
RateLimit.apeKeysDelete,
|
||||
authenticateRequest(),
|
||||
RateLimit.apeKeysDelete,
|
||||
checkIfUserCanManageApeKeys,
|
||||
validateRequest({
|
||||
params: {
|
||||
|
|
|
@ -9,15 +9,15 @@ const router = Router();
|
|||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.configGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.configGet,
|
||||
asyncHandler(ConfigController.getConfig)
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/",
|
||||
RateLimit.configUpdate,
|
||||
authenticateRequest(),
|
||||
RateLimit.configUpdate,
|
||||
validateRequest({
|
||||
body: {
|
||||
config: configSchema.required(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import joi from "joi";
|
||||
import { Router } from "express";
|
||||
import * as RateLimit from "../../middlewares/rate-limit";
|
||||
import apeRateLimit from "../../middlewares/ape-rate-limit";
|
||||
import { withApeRateLimiter } from "../../middlewares/ape-rate-limit";
|
||||
import { authenticateRequest } from "../../middlewares/auth";
|
||||
import * as LeaderboardController from "../controllers/leaderboard";
|
||||
import {
|
||||
|
@ -38,8 +38,8 @@ const requireDailyLeaderboardsEnabled = validateConfiguration({
|
|||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.leaderboardsGet,
|
||||
authenticateRequest({ isPublic: true, acceptApeKeys: true }),
|
||||
withApeRateLimiter(RateLimit.leaderboardsGet),
|
||||
validateRequest({
|
||||
query: LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT,
|
||||
}),
|
||||
|
@ -48,9 +48,8 @@ router.get(
|
|||
|
||||
router.get(
|
||||
"/rank",
|
||||
RateLimit.leaderboardsGet,
|
||||
authenticateRequest({ acceptApeKeys: true }),
|
||||
apeRateLimit,
|
||||
withApeRateLimiter(RateLimit.leaderboardsGet),
|
||||
validateRequest({
|
||||
query: BASE_LEADERBOARD_VALIDATION_SCHEMA,
|
||||
}),
|
||||
|
@ -60,8 +59,8 @@ router.get(
|
|||
router.get(
|
||||
"/daily",
|
||||
requireDailyLeaderboardsEnabled,
|
||||
RateLimit.leaderboardsGet,
|
||||
authenticateRequest({ isPublic: true }),
|
||||
RateLimit.leaderboardsGet,
|
||||
validateRequest({
|
||||
query: DAILY_LEADERBOARD_VALIDATION_SCHEMA,
|
||||
}),
|
||||
|
@ -71,8 +70,8 @@ router.get(
|
|||
router.get(
|
||||
"/daily/rank",
|
||||
requireDailyLeaderboardsEnabled,
|
||||
RateLimit.leaderboardsGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.leaderboardsGet,
|
||||
validateRequest({
|
||||
query: DAILY_LEADERBOARD_VALIDATION_SCHEMA,
|
||||
}),
|
||||
|
|
|
@ -20,15 +20,15 @@ const presetNameSchema = joi
|
|||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.presetsGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.presetsGet,
|
||||
asyncHandler(PresetController.getPresets)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
RateLimit.presetsAdd,
|
||||
authenticateRequest(),
|
||||
RateLimit.presetsAdd,
|
||||
validateRequest({
|
||||
body: {
|
||||
name: presetNameSchema,
|
||||
|
@ -42,8 +42,8 @@ router.post(
|
|||
|
||||
router.patch(
|
||||
"/",
|
||||
RateLimit.presetsEdit,
|
||||
authenticateRequest(),
|
||||
RateLimit.presetsEdit,
|
||||
validateRequest({
|
||||
body: {
|
||||
_id: joi.string().required(),
|
||||
|
@ -60,8 +60,8 @@ router.patch(
|
|||
|
||||
router.delete(
|
||||
"/:presetId",
|
||||
RateLimit.presetsRemove,
|
||||
authenticateRequest(),
|
||||
RateLimit.presetsRemove,
|
||||
validateRequest({
|
||||
params: {
|
||||
presetId: joi.string().required(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Router } from "express";
|
||||
import * as PsaController from "../controllers/psa";
|
||||
import * as RateLimit from "../../middlewares/rate-limit";
|
||||
import { asyncHandler } from "../../middlewares/api-utils";
|
||||
import { Router } from "express";
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@ const checkIfUserIsQuoteMod = checkUserPermissions({
|
|||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.newQuotesGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.newQuotesGet,
|
||||
checkIfUserIsQuoteMod,
|
||||
asyncHandler(QuoteController.getQuotes)
|
||||
);
|
||||
|
@ -35,8 +35,8 @@ router.post(
|
|||
invalidMessage:
|
||||
"Quote submission is disabled temporarily. The queue is quite long and we need some time to catch up.",
|
||||
}),
|
||||
RateLimit.newQuotesAdd,
|
||||
authenticateRequest(),
|
||||
RateLimit.newQuotesAdd,
|
||||
validateRequest({
|
||||
body: {
|
||||
text: joi.string().min(60).required(),
|
||||
|
@ -51,8 +51,8 @@ router.post(
|
|||
|
||||
router.post(
|
||||
"/approve",
|
||||
RateLimit.newQuotesAction,
|
||||
authenticateRequest(),
|
||||
RateLimit.newQuotesAction,
|
||||
validateRequest({
|
||||
body: {
|
||||
quoteId: joi.string().required(),
|
||||
|
@ -67,8 +67,8 @@ router.post(
|
|||
|
||||
router.post(
|
||||
"/reject",
|
||||
RateLimit.newQuotesAction,
|
||||
authenticateRequest(),
|
||||
RateLimit.newQuotesAction,
|
||||
validateRequest({
|
||||
body: {
|
||||
quoteId: joi.string().required(),
|
||||
|
@ -80,8 +80,8 @@ router.post(
|
|||
|
||||
router.get(
|
||||
"/rating",
|
||||
RateLimit.quoteRatingsGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.quoteRatingsGet,
|
||||
validateRequest({
|
||||
query: {
|
||||
quoteId: joi.string().regex(/^\d+$/).required(),
|
||||
|
@ -93,8 +93,8 @@ router.get(
|
|||
|
||||
router.post(
|
||||
"/rating",
|
||||
RateLimit.quoteRatingsSubmit,
|
||||
authenticateRequest(),
|
||||
RateLimit.quoteRatingsSubmit,
|
||||
validateRequest({
|
||||
body: {
|
||||
quoteId: joi.number().required(),
|
||||
|
@ -113,8 +113,8 @@ router.post(
|
|||
},
|
||||
invalidMessage: "Quote reporting is unavailable.",
|
||||
}),
|
||||
RateLimit.quoteReportSubmit,
|
||||
authenticateRequest(),
|
||||
RateLimit.quoteReportSubmit,
|
||||
validateRequest({
|
||||
body: {
|
||||
quoteId: joi.string().required(),
|
||||
|
|
|
@ -9,14 +9,14 @@ import * as RateLimit from "../../middlewares/rate-limit";
|
|||
import { Router } from "express";
|
||||
import { authenticateRequest } from "../../middlewares/auth";
|
||||
import joi from "joi";
|
||||
import apeRateLimit from "../../middlewares/ape-rate-limit";
|
||||
import { withApeRateLimiter } from "../../middlewares/ape-rate-limit";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.resultsGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.resultsGet,
|
||||
asyncHandler(ResultController.getResults)
|
||||
);
|
||||
|
||||
|
@ -28,8 +28,8 @@ router.post(
|
|||
},
|
||||
invalidMessage: "Results are not being saved at this time.",
|
||||
}),
|
||||
RateLimit.resultsAdd,
|
||||
authenticateRequest(),
|
||||
RateLimit.resultsAdd,
|
||||
validateRequest({
|
||||
body: {
|
||||
result: resultSchema,
|
||||
|
@ -40,8 +40,8 @@ router.post(
|
|||
|
||||
router.patch(
|
||||
"/tags",
|
||||
RateLimit.resultsTagsUpdate,
|
||||
authenticateRequest(),
|
||||
RateLimit.resultsTagsUpdate,
|
||||
validateRequest({
|
||||
body: {
|
||||
tagIds: joi.array().items(joi.string()).required(),
|
||||
|
@ -53,18 +53,17 @@ router.patch(
|
|||
|
||||
router.delete(
|
||||
"/",
|
||||
RateLimit.resultsDeleteAll,
|
||||
authenticateRequest(),
|
||||
RateLimit.resultsDeleteAll,
|
||||
asyncHandler(ResultController.deleteAll)
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/last",
|
||||
RateLimit.resultsGet,
|
||||
authenticateRequest({
|
||||
acceptApeKeys: true,
|
||||
}),
|
||||
apeRateLimit,
|
||||
withApeRateLimiter(RateLimit.resultsGet),
|
||||
asyncHandler(ResultController.getLastResult)
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
validateConfiguration,
|
||||
} from "../../middlewares/api-utils";
|
||||
import * as RateLimit from "../../middlewares/rate-limit";
|
||||
import apeRateLimit from "../../middlewares/ape-rate-limit";
|
||||
import { withApeRateLimiter } from "../../middlewares/ape-rate-limit";
|
||||
import { containsProfanity, isUsernameValid } from "../../utils/validation";
|
||||
import filterSchema from "../schemas/filter-schema";
|
||||
|
||||
|
@ -82,15 +82,15 @@ const quoteIdSchema = joi.string().min(1).max(5).regex(/\d+/).required();
|
|||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.userGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.userGet,
|
||||
asyncHandler(UserController.getUser)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/signup",
|
||||
RateLimit.userSignup,
|
||||
authenticateRequest(),
|
||||
RateLimit.userSignup,
|
||||
validateRequest({
|
||||
body: {
|
||||
email: joi.string().email(),
|
||||
|
@ -114,15 +114,15 @@ router.get(
|
|||
|
||||
router.delete(
|
||||
"/",
|
||||
RateLimit.userDelete,
|
||||
authenticateRequest(),
|
||||
RateLimit.userDelete,
|
||||
asyncHandler(UserController.deleteUser)
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/name",
|
||||
RateLimit.userUpdateName,
|
||||
authenticateRequest(),
|
||||
RateLimit.userUpdateName,
|
||||
validateRequest({
|
||||
body: {
|
||||
name: usernameValidation,
|
||||
|
@ -133,8 +133,8 @@ router.patch(
|
|||
|
||||
router.patch(
|
||||
"/leaderboardMemory",
|
||||
RateLimit.userUpdateLBMemory,
|
||||
authenticateRequest(),
|
||||
RateLimit.userUpdateLBMemory,
|
||||
validateRequest({
|
||||
body: {
|
||||
mode: joi
|
||||
|
@ -151,8 +151,8 @@ router.patch(
|
|||
|
||||
router.patch(
|
||||
"/email",
|
||||
RateLimit.userUpdateEmail,
|
||||
authenticateRequest(),
|
||||
RateLimit.userUpdateEmail,
|
||||
validateRequest({
|
||||
body: {
|
||||
newEmail: joi.string().email().required(),
|
||||
|
@ -164,8 +164,8 @@ router.patch(
|
|||
|
||||
router.delete(
|
||||
"/personalBests",
|
||||
RateLimit.userClearPB,
|
||||
authenticateRequest(),
|
||||
RateLimit.userClearPB,
|
||||
asyncHandler(UserController.clearPb)
|
||||
);
|
||||
|
||||
|
@ -178,9 +178,9 @@ const requireFilterPresetsEnabled = validateConfiguration({
|
|||
|
||||
router.post(
|
||||
"/resultFilterPresets",
|
||||
RateLimit.userCustomFilterAdd,
|
||||
requireFilterPresetsEnabled,
|
||||
authenticateRequest(),
|
||||
RateLimit.userCustomFilterAdd,
|
||||
validateRequest({
|
||||
body: filterSchema,
|
||||
}),
|
||||
|
@ -189,9 +189,9 @@ router.post(
|
|||
|
||||
router.delete(
|
||||
"/resultFilterPresets/:presetId",
|
||||
RateLimit.userCustomFilterRemove,
|
||||
requireFilterPresetsEnabled,
|
||||
authenticateRequest(),
|
||||
RateLimit.userCustomFilterRemove,
|
||||
validateRequest({
|
||||
params: {
|
||||
presetId: joi.string().required(),
|
||||
|
@ -202,15 +202,15 @@ router.delete(
|
|||
|
||||
router.get(
|
||||
"/tags",
|
||||
RateLimit.userTagsGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.userTagsGet,
|
||||
asyncHandler(UserController.getTags)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/tags",
|
||||
RateLimit.userTagsAdd,
|
||||
authenticateRequest(),
|
||||
RateLimit.userTagsAdd,
|
||||
validateRequest({
|
||||
body: {
|
||||
tagName: tagNameValidation,
|
||||
|
@ -221,8 +221,8 @@ router.post(
|
|||
|
||||
router.patch(
|
||||
"/tags",
|
||||
RateLimit.userTagsEdit,
|
||||
authenticateRequest(),
|
||||
RateLimit.userTagsEdit,
|
||||
validateRequest({
|
||||
body: {
|
||||
tagId: joi.string().required(),
|
||||
|
@ -234,8 +234,8 @@ router.patch(
|
|||
|
||||
router.delete(
|
||||
"/tags/:tagId",
|
||||
RateLimit.userTagsRemove,
|
||||
authenticateRequest(),
|
||||
RateLimit.userTagsRemove,
|
||||
validateRequest({
|
||||
params: {
|
||||
tagId: joi.string().required(),
|
||||
|
@ -246,8 +246,8 @@ router.delete(
|
|||
|
||||
router.delete(
|
||||
"/tags/:tagId/personalBest",
|
||||
RateLimit.userTagsClearPB,
|
||||
authenticateRequest(),
|
||||
RateLimit.userTagsClearPB,
|
||||
validateRequest({
|
||||
params: {
|
||||
tagId: joi.string().required(),
|
||||
|
@ -258,15 +258,15 @@ router.delete(
|
|||
|
||||
router.get(
|
||||
"/customThemes",
|
||||
RateLimit.userCustomThemeGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.userCustomThemeGet,
|
||||
asyncHandler(UserController.getCustomThemes)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/customThemes",
|
||||
RateLimit.userCustomThemeAdd,
|
||||
authenticateRequest(),
|
||||
RateLimit.userCustomThemeAdd,
|
||||
validateRequest({
|
||||
body: {
|
||||
name: customThemeNameValidation,
|
||||
|
@ -278,8 +278,8 @@ router.post(
|
|||
|
||||
router.delete(
|
||||
"/customThemes",
|
||||
RateLimit.userCustomThemeRemove,
|
||||
authenticateRequest(),
|
||||
RateLimit.userCustomThemeRemove,
|
||||
validateRequest({
|
||||
body: {
|
||||
themeId: customThemeIdValidation,
|
||||
|
@ -290,8 +290,8 @@ router.delete(
|
|||
|
||||
router.patch(
|
||||
"/customThemes",
|
||||
RateLimit.userCustomThemeEdit,
|
||||
authenticateRequest(),
|
||||
RateLimit.userCustomThemeEdit,
|
||||
validateRequest({
|
||||
body: {
|
||||
themeId: customThemeIdValidation,
|
||||
|
@ -313,9 +313,9 @@ const requireDiscordIntegrationEnabled = validateConfiguration({
|
|||
|
||||
router.post(
|
||||
"/discord/link",
|
||||
RateLimit.userDiscordLink,
|
||||
requireDiscordIntegrationEnabled,
|
||||
authenticateRequest(),
|
||||
RateLimit.userDiscordLink,
|
||||
validateRequest({
|
||||
body: {
|
||||
tokenType: joi.string().required(),
|
||||
|
@ -327,18 +327,17 @@ router.post(
|
|||
|
||||
router.post(
|
||||
"/discord/unlink",
|
||||
RateLimit.userDiscordUnlink,
|
||||
authenticateRequest(),
|
||||
RateLimit.userDiscordUnlink,
|
||||
asyncHandler(UserController.unlinkDiscord)
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/personalBests",
|
||||
RateLimit.userGet,
|
||||
authenticateRequest({
|
||||
acceptApeKeys: true,
|
||||
}),
|
||||
apeRateLimit,
|
||||
withApeRateLimiter(RateLimit.userGet),
|
||||
validateRequest({
|
||||
query: {
|
||||
mode: joi.string().required(),
|
||||
|
@ -350,25 +349,24 @@ router.get(
|
|||
|
||||
router.get(
|
||||
"/stats",
|
||||
RateLimit.userGet,
|
||||
authenticateRequest({
|
||||
acceptApeKeys: true,
|
||||
}),
|
||||
apeRateLimit,
|
||||
withApeRateLimiter(RateLimit.userGet),
|
||||
asyncHandler(UserController.getStats)
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/favoriteQuotes",
|
||||
RateLimit.quoteFavoriteGet,
|
||||
authenticateRequest(),
|
||||
RateLimit.quoteFavoriteGet,
|
||||
asyncHandler(UserController.getFavoriteQuotes)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/favoriteQuotes",
|
||||
RateLimit.quoteFavoritePost,
|
||||
authenticateRequest(),
|
||||
RateLimit.quoteFavoritePost,
|
||||
validateRequest({
|
||||
body: {
|
||||
language: languageSchema,
|
||||
|
@ -380,8 +378,8 @@ router.post(
|
|||
|
||||
router.delete(
|
||||
"/favoriteQuotes",
|
||||
RateLimit.quoteFavoriteDelete,
|
||||
authenticateRequest(),
|
||||
RateLimit.quoteFavoriteDelete,
|
||||
validateRequest({
|
||||
body: {
|
||||
language: languageSchema,
|
||||
|
@ -400,11 +398,11 @@ const requireProfilesEnabled = validateConfiguration({
|
|||
|
||||
router.get(
|
||||
"/:uid/profile",
|
||||
RateLimit.userProfileGet,
|
||||
requireProfilesEnabled,
|
||||
authenticateRequest({
|
||||
isPublic: true,
|
||||
}),
|
||||
RateLimit.userProfileGet,
|
||||
validateRequest({
|
||||
params: {
|
||||
uid: joi.string().required(),
|
||||
|
@ -427,9 +425,9 @@ const profileDetailsBase = joi
|
|||
|
||||
router.patch(
|
||||
"/profile",
|
||||
RateLimit.userProfileUpdate,
|
||||
requireProfilesEnabled,
|
||||
authenticateRequest(),
|
||||
RateLimit.userProfileUpdate,
|
||||
validateRequest({
|
||||
body: {
|
||||
bio: profileDetailsBase.max(150),
|
||||
|
|
|
@ -4,6 +4,10 @@ import addApiRoutes from "./api/routes";
|
|||
import express, { urlencoded, json } from "express";
|
||||
import contextMiddleware from "./middlewares/context";
|
||||
import errorHandlingMiddleware from "./middlewares/error";
|
||||
import {
|
||||
badAuthRateLimiterHandler,
|
||||
rootRateLimiter,
|
||||
} from "./middlewares/rate-limit";
|
||||
|
||||
function buildApp(): express.Application {
|
||||
const app = express();
|
||||
|
@ -17,6 +21,9 @@ function buildApp(): express.Application {
|
|||
|
||||
app.use(contextMiddleware);
|
||||
|
||||
app.use(badAuthRateLimiterHandler);
|
||||
app.use(rootRateLimiter);
|
||||
|
||||
addApiRoutes(app);
|
||||
|
||||
app.use(errorHandlingMiddleware);
|
||||
|
|
|
@ -42,7 +42,13 @@ export const BASE_CONFIGURATION: MonkeyTypes.Configuration = {
|
|||
enabled: false,
|
||||
},
|
||||
},
|
||||
|
||||
rateLimiting: {
|
||||
badAuthentication: {
|
||||
enabled: false,
|
||||
penalty: 0,
|
||||
flaggedStatusCodes: [],
|
||||
},
|
||||
},
|
||||
dailyLeaderboards: {
|
||||
enabled: false,
|
||||
maxResults: 0,
|
||||
|
@ -54,7 +60,42 @@ export const BASE_CONFIGURATION: MonkeyTypes.Configuration = {
|
|||
},
|
||||
};
|
||||
|
||||
export const CONFIGURATION_FORM_SCHEMA = {
|
||||
interface BaseSchema {
|
||||
type: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
interface NumberSchema extends BaseSchema {
|
||||
type: "number";
|
||||
min?: number;
|
||||
}
|
||||
|
||||
interface BooleanSchema extends BaseSchema {
|
||||
type: "boolean";
|
||||
}
|
||||
|
||||
interface StringSchema extends BaseSchema {
|
||||
type: "string";
|
||||
}
|
||||
|
||||
interface ArraySchema extends BaseSchema {
|
||||
type: "array";
|
||||
items: Schema;
|
||||
}
|
||||
|
||||
interface ObjectSchema extends BaseSchema {
|
||||
type: "object";
|
||||
fields: Record<string, Schema>;
|
||||
}
|
||||
|
||||
type Schema =
|
||||
| ObjectSchema
|
||||
| ArraySchema
|
||||
| StringSchema
|
||||
| NumberSchema
|
||||
| BooleanSchema;
|
||||
|
||||
export const CONFIGURATION_FORM_SCHEMA: ObjectSchema = {
|
||||
type: "object",
|
||||
label: "Server Configuration",
|
||||
fields: {
|
||||
|
@ -198,7 +239,36 @@ export const CONFIGURATION_FORM_SCHEMA = {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
rateLimiting: {
|
||||
type: "object",
|
||||
label: "Rate Limiting",
|
||||
fields: {
|
||||
badAuthentication: {
|
||||
type: "object",
|
||||
label: "Bad Authentication Rate Limiter",
|
||||
fields: {
|
||||
enabled: {
|
||||
type: "boolean",
|
||||
label: "Enabled",
|
||||
},
|
||||
penalty: {
|
||||
type: "number",
|
||||
label: "Penalty",
|
||||
min: 0,
|
||||
},
|
||||
flaggedStatusCodes: {
|
||||
type: "array",
|
||||
label: "Flagged Status Codes",
|
||||
items: {
|
||||
label: "Status Code",
|
||||
type: "number",
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dailyLeaderboards: {
|
||||
type: "object",
|
||||
label: "Daily Leaderboards",
|
||||
|
|
|
@ -16,6 +16,7 @@ interface Statuses {
|
|||
APE_KEY_INVALID: Status;
|
||||
APE_KEY_INACTIVE: Status;
|
||||
APE_KEY_MALFORMED: Status;
|
||||
APE_KEY_RATE_LIMIT_EXCEEDED: Status;
|
||||
}
|
||||
|
||||
const statuses: Statuses = {
|
||||
|
@ -59,6 +60,10 @@ const statuses: Statuses = {
|
|||
code: 472,
|
||||
message: "ApeKey is malformed",
|
||||
},
|
||||
APE_KEY_RATE_LIMIT_EXCEEDED: {
|
||||
code: 479,
|
||||
message: "ApeKey rate limit exceeded",
|
||||
},
|
||||
};
|
||||
|
||||
const CUSTOM_STATUS_CODES = new Set(
|
||||
|
|
|
@ -1,31 +1,47 @@
|
|||
import { Response, NextFunction } from "express";
|
||||
import rateLimit, { Options } from "express-rate-limit";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Response, NextFunction, RequestHandler } from "express";
|
||||
import statuses from "../constants/monkey-status-codes";
|
||||
import rateLimit, {
|
||||
RateLimitRequestHandler,
|
||||
Options,
|
||||
} from "express-rate-limit";
|
||||
|
||||
const REQUEST_MULTIPLIER = process.env.MODE === "dev" ? 100 : 1;
|
||||
const REQUEST_MULTIPLIER = process.env.MODE === "dev" ? 1 : 1;
|
||||
|
||||
const getKey = (req: MonkeyTypes.Request, _res: Response): string => {
|
||||
return req?.ctx?.decodedToken?.uid;
|
||||
};
|
||||
|
||||
const customHandler = (
|
||||
const ONE_MINUTE = 1000 * 60;
|
||||
|
||||
const {
|
||||
APE_KEY_RATE_LIMIT_EXCEEDED: { message, code },
|
||||
} = statuses;
|
||||
|
||||
export const customHandler = (
|
||||
_req: MonkeyTypes.Request,
|
||||
_res: Response,
|
||||
_next: NextFunction,
|
||||
_options: Options
|
||||
): void => {
|
||||
throw new MonkeyError(429, "Too many attempts, please try again later.");
|
||||
throw new MonkeyError(code, message);
|
||||
};
|
||||
|
||||
const ONE_MINUTE = 1000 * 60;
|
||||
|
||||
export default rateLimit({
|
||||
const apeRateLimiter = rateLimit({
|
||||
windowMs: ONE_MINUTE,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getKey,
|
||||
handler: customHandler,
|
||||
skip: (req: MonkeyTypes.Request, _res) => {
|
||||
const decodedToken = req?.ctx?.decodedToken;
|
||||
return decodedToken?.type !== "ApeKey";
|
||||
},
|
||||
});
|
||||
|
||||
export function withApeRateLimiter(
|
||||
defaultRateLimiter: RateLimitRequestHandler
|
||||
): RequestHandler {
|
||||
return (req: MonkeyTypes.Request, res: Response, next: NextFunction) => {
|
||||
if (req.ctx.decodedToken.type === "ApeKey") {
|
||||
return apeRateLimiter(req, res, next);
|
||||
}
|
||||
|
||||
return defaultRateLimiter(req, res, next);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ async function authenticateWithApeKey(
|
|||
options: RequestAuthenticationOptions
|
||||
): Promise<MonkeyTypes.DecodedToken> {
|
||||
if (!configuration.apeKeys.acceptKeys) {
|
||||
throw new MonkeyError(403, "ApeKeys are not being accepted at this time");
|
||||
throw new MonkeyError(503, "ApeKeys are not being accepted at this time");
|
||||
}
|
||||
|
||||
if (!options.acceptApeKeys) {
|
||||
|
|
|
@ -2,8 +2,9 @@ import * as db from "../init/db";
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
import Logger from "../utils/logger";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { MonkeyResponse, handleMonkeyResponse } from "../utils/monkey-response";
|
||||
import { incrementBadAuth } from "./rate-limit";
|
||||
import { NextFunction, Response } from "express";
|
||||
import { MonkeyResponse, handleMonkeyResponse } from "../utils/monkey-response";
|
||||
|
||||
async function errorHandlingMiddleware(
|
||||
error: Error,
|
||||
|
@ -33,6 +34,8 @@ async function errorHandlingMiddleware(
|
|||
monkeyResponse.message = `Oops! Our monkeys dropped their bananas. Please try again later. - ${monkeyResponse.data.errorId}`;
|
||||
}
|
||||
|
||||
await incrementBadAuth(req, res, monkeyResponse.status);
|
||||
|
||||
if (process.env.MODE !== "dev" && monkeyResponse.status >= 500) {
|
||||
const { uid, errorId } = monkeyResponse.data;
|
||||
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import { Response, NextFunction } from "express";
|
||||
import rateLimit, { Options } from "express-rate-limit";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Response, NextFunction } from "express";
|
||||
import { RateLimiterMemory } from "rate-limiter-flexible";
|
||||
import rateLimit, { Options } from "express-rate-limit";
|
||||
|
||||
const REQUEST_MULTIPLIER = process.env.MODE === "dev" ? 100 : 1;
|
||||
|
||||
const getAddress = (req: MonkeyTypes.Request, _res: Response): string => {
|
||||
const getKey = (req: MonkeyTypes.Request, _res: Response): string => {
|
||||
return (req.headers["cf-connecting-ip"] ||
|
||||
req.headers["x-forwarded-for"] ||
|
||||
req.ip ||
|
||||
"255.255.255.255") as string;
|
||||
};
|
||||
|
||||
const customHandler = (
|
||||
const getKeyWithUid = (req: MonkeyTypes.Request, _res: Response): string => {
|
||||
const uid = req?.ctx?.decodedToken?.uid;
|
||||
const useUid = uid.length > 0 && uid;
|
||||
|
||||
return (useUid || getKey(req, _res)) as string;
|
||||
};
|
||||
|
||||
export const customHandler = (
|
||||
_req: MonkeyTypes.Request,
|
||||
_res: Response,
|
||||
_next: NextFunction,
|
||||
|
@ -20,65 +28,129 @@ const customHandler = (
|
|||
throw new MonkeyError(429, "Too many attempts, please try again later.");
|
||||
};
|
||||
|
||||
const ONE_HOUR = 1000 * 60 * 60;
|
||||
const ONE_HOUR_SECONDS = 60 * 60;
|
||||
const ONE_HOUR_MS = 1000 * ONE_HOUR_SECONDS;
|
||||
|
||||
// Root Rate Limit
|
||||
export const rootRateLimiter = rateLimit({
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 2000 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getKey,
|
||||
handler: (_req, _res, _next, _options): void => {
|
||||
throw new MonkeyError(
|
||||
429,
|
||||
"Maximum API request limit reached. Please try again later."
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Bad Authentication Rate Limiter
|
||||
const badAuthRateLimiter = new RateLimiterMemory({
|
||||
points: 30 * REQUEST_MULTIPLIER,
|
||||
duration: ONE_HOUR_SECONDS,
|
||||
});
|
||||
|
||||
export async function badAuthRateLimiterHandler(
|
||||
req: MonkeyTypes.Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
if (!req.ctx.configuration.rateLimiting.badAuthentication.enabled) {
|
||||
return next();
|
||||
}
|
||||
|
||||
try {
|
||||
const key = getKey(req, res);
|
||||
const rateLimitStatus = await badAuthRateLimiter.get(key);
|
||||
|
||||
if (rateLimitStatus !== null && rateLimitStatus?.remainingPoints <= 0) {
|
||||
throw new MonkeyError(
|
||||
429,
|
||||
"Too many bad authentication attempts, please try again later."
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return next(error);
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export async function incrementBadAuth(
|
||||
req: MonkeyTypes.Request,
|
||||
res: Response,
|
||||
status: number
|
||||
): Promise<void> {
|
||||
const { enabled, penalty, flaggedStatusCodes } =
|
||||
req.ctx.configuration.rateLimiting.badAuthentication;
|
||||
|
||||
if (!enabled || !flaggedStatusCodes.includes(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const key = getKey(req, res);
|
||||
await badAuthRateLimiter.penalty(key, penalty);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
// Config Routing
|
||||
export const configUpdate = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const configGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 120 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// Leaderboards Routing
|
||||
export const leaderboardsGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// New Quotes Routing
|
||||
export const newQuotesGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const newQuotesAdd = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const newQuotesAction = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// Quote Ratings Routing
|
||||
export const quoteRatingsGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const quoteRatingsSubmit = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
|
@ -86,7 +158,7 @@ export const quoteRatingsSubmit = rateLimit({
|
|||
export const quoteReportSubmit = rateLimit({
|
||||
windowMs: 30 * 60 * 1000, // 30 min
|
||||
max: 50 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
|
@ -94,50 +166,50 @@ export const quoteReportSubmit = rateLimit({
|
|||
export const quoteFavoriteGet = rateLimit({
|
||||
windowMs: 30 * 60 * 1000, // 30 min
|
||||
max: 50 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const quoteFavoritePost = rateLimit({
|
||||
windowMs: 30 * 60 * 1000, // 30 min
|
||||
max: 50 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const quoteFavoriteDelete = rateLimit({
|
||||
windowMs: 30 * 60 * 1000, // 30 min
|
||||
max: 50 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// Presets Routing
|
||||
export const presetsGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const presetsAdd = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const presetsRemove = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const presetsEdit = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
|
@ -145,229 +217,229 @@ export const presetsEdit = rateLimit({
|
|||
export const psaGet = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// Results Routing
|
||||
export const resultsGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const resultsAdd = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 500 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const resultsTagsUpdate = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 100 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const resultsDeleteAll = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 10 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const resultsLeaderboardGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const resultsLeaderboardQualificationGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// Users Routing
|
||||
export const userGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userSignup = rateLimit({
|
||||
windowMs: 24 * ONE_HOUR, // 1 day
|
||||
windowMs: 24 * ONE_HOUR_MS, // 1 day
|
||||
max: 3 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userDelete = rateLimit({
|
||||
windowMs: 24 * ONE_HOUR, // 1 day
|
||||
windowMs: 24 * ONE_HOUR_MS, // 1 day
|
||||
max: 3 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCheckName = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userUpdateName = rateLimit({
|
||||
windowMs: 24 * ONE_HOUR, // 1 day
|
||||
windowMs: 24 * ONE_HOUR_MS, // 1 day
|
||||
max: 3 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userUpdateLBMemory = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userUpdateEmail = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userClearPB = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCustomFilterAdd = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCustomFilterRemove = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userTagsGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userTagsRemove = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userTagsClearPB = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userTagsEdit = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userTagsAdd = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCustomThemeGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCustomThemeAdd = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCustomThemeRemove = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userCustomThemeEdit = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 30 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userDiscordLink = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 15 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const usersTagsEdit = userDiscordLink;
|
||||
|
||||
export const userDiscordUnlink = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 15 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userProfileGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 100 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const userProfileUpdate = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 60 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
// ApeKeys Routing
|
||||
export const apeKeysGet = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 120 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
export const apeKeysGenerate = rateLimit({
|
||||
windowMs: ONE_HOUR,
|
||||
windowMs: ONE_HOUR_MS,
|
||||
max: 15 * REQUEST_MULTIPLIER,
|
||||
keyGenerator: getAddress,
|
||||
keyGenerator: getKeyWithUid,
|
||||
handler: customHandler,
|
||||
});
|
||||
|
||||
|
|
8
backend/src/types/types.d.ts
vendored
8
backend/src/types/types.d.ts
vendored
|
@ -48,7 +48,13 @@ declare namespace MonkeyTypes {
|
|||
apeKeyBytes: number;
|
||||
apeKeySaltRounds: number;
|
||||
};
|
||||
|
||||
rateLimiting: {
|
||||
badAuthentication: {
|
||||
enabled: boolean;
|
||||
penalty: number;
|
||||
flaggedStatusCodes: number[];
|
||||
};
|
||||
};
|
||||
dailyLeaderboards: {
|
||||
enabled: boolean;
|
||||
leaderboardExpirationTimeInDays: number;
|
||||
|
|
|
@ -5,7 +5,7 @@ class MonkeyError extends Error {
|
|||
errorId: string;
|
||||
uid?: string;
|
||||
|
||||
constructor(status: number, message: string, stack?: string, uid?: string) {
|
||||
constructor(status: number, message?: string, stack?: string, uid?: string) {
|
||||
super();
|
||||
this.status = status ?? 500;
|
||||
this.errorId = uuidv4();
|
||||
|
|
Loading…
Add table
Reference in a new issue