]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Bypass rate limits for admins and moderators
authorChocobozzz <me@florianbigard.com>
Mon, 30 May 2022 09:33:38 +0000 (11:33 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 30 May 2022 09:39:44 +0000 (11:39 +0200)
server/controllers/api/index.ts
server/controllers/api/users/index.ts
server/controllers/api/users/token.ts
server/middlewares/index.ts
server/middlewares/rate-limiter.ts [new file with mode: 0644]
server/tests/api/server/reverse-proxy.ts

index 5f49336b13c886ad39820c0adc98ee2cef461b94..d1d4ef7657bcca6514399bb17f46888d05579339 100644 (file)
@@ -1,6 +1,6 @@
 import cors from 'cors'
 import express from 'express'
-import RateLimit from 'express-rate-limit'
+import { buildRateLimiter } from '@server/middlewares'
 import { HttpStatusCode } from '../../../shared/models'
 import { badRequest } from '../../helpers/express-utils'
 import { CONFIG } from '../../initializers/config'
@@ -29,7 +29,7 @@ apiRouter.use(cors({
   credentials: true
 }))
 
-const apiRateLimiter = RateLimit({
+const apiRateLimiter = buildRateLimiter({
   windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS,
   max: CONFIG.RATES_LIMIT.API.MAX
 })
index e13e31aafc32f3acce47bb2e3abda61340f07582..46e80d56d17e4a148a6fc0c142a11cf89614d75b 100644 (file)
@@ -1,5 +1,4 @@
 import express from 'express'
-import RateLimit from 'express-rate-limit'
 import { tokensRouter } from '@server/controllers/api/users/token'
 import { Hooks } from '@server/lib/plugins/hooks'
 import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
@@ -17,9 +16,11 @@ import { Notifier } from '../../../lib/notifier'
 import { Redis } from '../../../lib/redis'
 import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
 import {
+  adminUsersSortValidator,
   asyncMiddleware,
   asyncRetryTransactionMiddleware,
   authenticate,
+  buildRateLimiter,
   ensureUserHasRight,
   ensureUserRegistrationAllowed,
   ensureUserRegistrationAllowedForIP,
@@ -32,7 +33,6 @@ import {
   usersListValidator,
   usersRegisterValidator,
   usersRemoveValidator,
-  adminUsersSortValidator,
   usersUpdateValidator
 } from '../../../middlewares'
 import {
@@ -54,13 +54,13 @@ import { myVideoPlaylistsRouter } from './my-video-playlists'
 
 const auditLogger = auditLoggerFactory('users')
 
-const signupRateLimiter = RateLimit({
+const signupRateLimiter = buildRateLimiter({
   windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
   max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
   skipFailedRequests: true
 })
 
-const askSendEmailLimiter = RateLimit({
+const askSendEmailLimiter = buildRateLimiter({
   windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
   max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
 })
index 258b50fe9267f8ec62b691f75e7dc5cb43c3cf6d..012a497910a5667d4728c6ca2a8f146b075613fe 100644 (file)
@@ -1,18 +1,17 @@
 import express from 'express'
-import RateLimit from 'express-rate-limit'
 import { logger } from '@server/helpers/logger'
 import { CONFIG } from '@server/initializers/config'
 import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
 import { handleOAuthToken } from '@server/lib/auth/oauth'
 import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
 import { Hooks } from '@server/lib/plugins/hooks'
-import { asyncMiddleware, authenticate, openapiOperationDoc } from '@server/middlewares'
+import { asyncMiddleware, authenticate, buildRateLimiter, openapiOperationDoc } from '@server/middlewares'
 import { buildUUID } from '@shared/extra-utils'
 import { ScopedToken } from '@shared/models/users/user-scoped-token'
 
 const tokensRouter = express.Router()
 
-const loginRateLimiter = RateLimit({
+const loginRateLimiter = buildRateLimiter({
   windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
   max: CONFIG.RATES_LIMIT.LOGIN.MAX
 })
index d2ed079b6640b3741acb18e9e56d5e0bb294990f..b40f864cef948dd5f27b6223f586e08adab5ce13 100644 (file)
@@ -4,6 +4,7 @@ export * from './activitypub'
 export * from './async'
 export * from './auth'
 export * from './pagination'
+export * from './rate-limiter'
 export * from './robots'
 export * from './servers'
 export * from './sort'
diff --git a/server/middlewares/rate-limiter.ts b/server/middlewares/rate-limiter.ts
new file mode 100644 (file)
index 0000000..bc95139
--- /dev/null
@@ -0,0 +1,31 @@
+import { UserRole } from '@shared/models'
+import RateLimit from 'express-rate-limit'
+import { optionalAuthenticate } from './auth'
+
+const whitelistRoles = new Set([ UserRole.ADMINISTRATOR, UserRole.MODERATOR ])
+
+function buildRateLimiter (options: {
+  windowMs: number
+  max: number
+  skipFailedRequests?: boolean
+}) {
+  return RateLimit({
+    windowMs: options.windowMs,
+    max: options.max,
+    skipFailedRequests: options.skipFailedRequests,
+
+    handler: (req, res, next, options) => {
+      return optionalAuthenticate(req, res, () => {
+        if (res.locals.authenticated === true && whitelistRoles.has(res.locals.oauth.token.User.role)) {
+          return next()
+        }
+
+        return res.status(options.statusCode).send(options.message)
+      })
+    }
+  })
+}
+
+export {
+  buildRateLimiter
+}
index fa2063536f4ce89ccd52b709016c12d23ba94686..0a1565faff36b0e1cc75d0ac7217cee98e60fdc6 100644 (file)
@@ -7,6 +7,7 @@ import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServ
 
 describe('Test application behind a reverse proxy', function () {
   let server: PeerTubeServer
+  let userAccessToken: string
   let videoId: string
 
   before(async function () {
@@ -34,6 +35,8 @@ describe('Test application behind a reverse proxy', function () {
     server = await createSingleServer(1, config)
     await setAccessTokensToServers([ server ])
 
+    userAccessToken = await server.users.generateUserAndToken('user')
+
     const { uuid } = await server.videos.upload()
     videoId = uuid
   })
@@ -93,7 +96,7 @@ describe('Test application behind a reverse proxy', function () {
   it('Should rate limit logins', async function () {
     const user = { username: 'root', password: 'fail' }
 
-    for (let i = 0; i < 19; i++) {
+    for (let i = 0; i < 18; i++) {
       await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
     }
 
@@ -141,6 +144,12 @@ describe('Test application behind a reverse proxy', function () {
     await server.videos.get({ id: videoId, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 })
   })
 
+  it('Should rate limit API calls with a user but not with an admin', async function () {
+    await server.videos.get({ id: videoId, token: userAccessToken, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 })
+
+    await server.videos.get({ id: videoId, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+  })
+
   after(async function () {
     await cleanupTests([ server ])
   })