Skip to content

Commit

Permalink
Merge pull request #14546 from strapi/security/adminRateLimit
Browse files Browse the repository at this point in the history
  • Loading branch information
Convly committed Dec 27, 2022
2 parents 44c3c09 + ae7c609 commit 3446db4
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 9 deletions.
1 change: 1 addition & 0 deletions packages/core/admin/package.json
Expand Up @@ -82,6 +82,7 @@
"jsonwebtoken": "8.5.1",
"koa-compose": "4.1.0",
"koa-passport": "5.0.0",
"koa2-ratelimit": "^1.1.2",
"koa-static": "5.0.0",
"lodash": "4.17.21",
"markdown-it": "^12.3.2",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/admin/server/index.js
Expand Up @@ -10,6 +10,7 @@ const routes = require('./routes');
const services = require('./services');
const controllers = require('./controllers');
const contentTypes = require('./content-types');
const middlewares = require('./middlewares');

module.exports = {
register,
Expand All @@ -21,4 +22,5 @@ module.exports = {
services,
controllers,
contentTypes,
middlewares,
};
7 changes: 7 additions & 0 deletions packages/core/admin/server/middlewares/index.js
@@ -0,0 +1,7 @@
'use strict';

const rateLimit = require('./rateLimit');

module.exports = {
rateLimit,
};
43 changes: 43 additions & 0 deletions packages/core/admin/server/middlewares/rateLimit.js
@@ -0,0 +1,43 @@
'use strict';

const utils = require('@strapi/utils');
const { has, toLower } = require('lodash/fp');

const { RateLimitError } = utils.errors;

module.exports =
(config, { strapi }) =>
async (ctx, next) => {
let rateLimitConfig = strapi.config.get('admin.rateLimit');

if (!rateLimitConfig) {
rateLimitConfig = {
enabled: true,
};
}

if (!has('enabled', rateLimitConfig)) {
rateLimitConfig.enabled = true;
}

if (rateLimitConfig.enabled === true) {
const rateLimit = require('koa2-ratelimit').RateLimit;

const userEmail = toLower(ctx.request.body.email) || 'unknownEmail';

const loadConfig = {
interval: { min: 5 },
max: 5,
prefixKey: `${userEmail}:${ctx.request.path}:${ctx.request.ip}`,
handler() {
throw new RateLimitError();
},
...rateLimitConfig,
...config,
};

return rateLimit.middleware(loadConfig)(ctx, next);
}

return next();
};
5 changes: 4 additions & 1 deletion packages/core/admin/server/routes/authentication.js
Expand Up @@ -5,7 +5,10 @@ module.exports = [
method: 'POST',
path: '/login',
handler: 'authentication.login',
config: { auth: false },
config: {
auth: false,
middlewares: ['admin::rateLimit'],
},
},
{
method: 'POST',
Expand Down
6 changes: 5 additions & 1 deletion packages/core/strapi/lib/services/errors.js
@@ -1,7 +1,7 @@
'use strict';

const createError = require('http-errors');
const { NotFoundError, UnauthorizedError, ForbiddenError, PayloadTooLargeError } =
const { NotFoundError, UnauthorizedError, ForbiddenError, PayloadTooLargeError, RateLimitError } =
require('@strapi/utils').errors;

const mapErrorsAndStatus = [
Expand All @@ -21,6 +21,10 @@ const mapErrorsAndStatus = [
classError: PayloadTooLargeError,
status: 413,
},
{
classError: RateLimitError,
status: 429,
},
];

const formatApplicationError = (error) => {
Expand Down
24 changes: 17 additions & 7 deletions packages/core/utils/lib/errors.js
Expand Up @@ -55,19 +55,28 @@ class ForbiddenError extends ApplicationError {
}
}

class PayloadTooLargeError extends ApplicationError {
class UnauthorizedError extends ApplicationError {
constructor(message, details) {
super(message, details);
this.name = 'PayloadTooLargeError';
this.message = message || 'Entity too large';
this.name = 'UnauthorizedError';
this.message = message || 'Unauthorized';
}
}

class UnauthorizedError extends ApplicationError {
class RateLimitError extends ApplicationError {
constructor(message, details) {
super(message, details);
this.name = 'UnauthorizedError';
this.message = message || 'Unauthorized';
this.name = 'RateLimitError';
this.message = message || 'Too many requests, please try again later.';
this.details = details || {};
}
}

class PayloadTooLargeError extends ApplicationError {
constructor(message, details) {
super(message, details);
this.name = 'PayloadTooLargeError';
this.message = message || 'Entity too large';
}
}

Expand All @@ -88,7 +97,8 @@ module.exports = {
PaginationError,
NotFoundError,
ForbiddenError,
PayloadTooLargeError,
UnauthorizedError,
RateLimitError,
PayloadTooLargeError,
PolicyError,
};

0 comments on commit 3446db4

Please sign in to comment.