1
0
Fork 0

Bypass rate limits for admins and moderators

This commit is contained in:
Chocobozzz 2022-05-30 11:33:38 +02:00
parent f823637d18
commit e5a781ec25
No known key found for this signature in database
GPG key ID: 583A612D890159BE
6 changed files with 50 additions and 10 deletions

View file

@ -1,6 +1,6 @@
import cors from 'cors' import cors from 'cors'
import express from 'express' import express from 'express'
import RateLimit from 'express-rate-limit' import { buildRateLimiter } from '@server/middlewares'
import { HttpStatusCode } from '../../../shared/models' import { HttpStatusCode } from '../../../shared/models'
import { badRequest } from '../../helpers/express-utils' import { badRequest } from '../../helpers/express-utils'
import { CONFIG } from '../../initializers/config' import { CONFIG } from '../../initializers/config'
@ -29,7 +29,7 @@ apiRouter.use(cors({
credentials: true credentials: true
})) }))
const apiRateLimiter = RateLimit({ const apiRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS,
max: CONFIG.RATES_LIMIT.API.MAX max: CONFIG.RATES_LIMIT.API.MAX
}) })

View file

@ -1,5 +1,4 @@
import express from 'express' import express from 'express'
import RateLimit from 'express-rate-limit'
import { tokensRouter } from '@server/controllers/api/users/token' import { tokensRouter } from '@server/controllers/api/users/token'
import { Hooks } from '@server/lib/plugins/hooks' import { Hooks } from '@server/lib/plugins/hooks'
import { OAuthTokenModel } from '@server/models/oauth/oauth-token' import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
@ -17,9 +16,11 @@ import { Notifier } from '../../../lib/notifier'
import { Redis } from '../../../lib/redis' import { Redis } from '../../../lib/redis'
import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' import { buildUser, createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
import { import {
adminUsersSortValidator,
asyncMiddleware, asyncMiddleware,
asyncRetryTransactionMiddleware, asyncRetryTransactionMiddleware,
authenticate, authenticate,
buildRateLimiter,
ensureUserHasRight, ensureUserHasRight,
ensureUserRegistrationAllowed, ensureUserRegistrationAllowed,
ensureUserRegistrationAllowedForIP, ensureUserRegistrationAllowedForIP,
@ -32,7 +33,6 @@ import {
usersListValidator, usersListValidator,
usersRegisterValidator, usersRegisterValidator,
usersRemoveValidator, usersRemoveValidator,
adminUsersSortValidator,
usersUpdateValidator usersUpdateValidator
} from '../../../middlewares' } from '../../../middlewares'
import { import {
@ -54,13 +54,13 @@ import { myVideoPlaylistsRouter } from './my-video-playlists'
const auditLogger = auditLoggerFactory('users') const auditLogger = auditLoggerFactory('users')
const signupRateLimiter = RateLimit({ const signupRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS,
max: CONFIG.RATES_LIMIT.SIGNUP.MAX, max: CONFIG.RATES_LIMIT.SIGNUP.MAX,
skipFailedRequests: true skipFailedRequests: true
}) })
const askSendEmailLimiter = RateLimit({ const askSendEmailLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, windowMs: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX max: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX
}) })

View file

@ -1,18 +1,17 @@
import express from 'express' import express from 'express'
import RateLimit from 'express-rate-limit'
import { logger } from '@server/helpers/logger' import { logger } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config' import { CONFIG } from '@server/initializers/config'
import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth' import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
import { handleOAuthToken } from '@server/lib/auth/oauth' import { handleOAuthToken } from '@server/lib/auth/oauth'
import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model' import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
import { Hooks } from '@server/lib/plugins/hooks' 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 { buildUUID } from '@shared/extra-utils'
import { ScopedToken } from '@shared/models/users/user-scoped-token' import { ScopedToken } from '@shared/models/users/user-scoped-token'
const tokensRouter = express.Router() const tokensRouter = express.Router()
const loginRateLimiter = RateLimit({ const loginRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
max: CONFIG.RATES_LIMIT.LOGIN.MAX max: CONFIG.RATES_LIMIT.LOGIN.MAX
}) })

View file

@ -4,6 +4,7 @@ export * from './activitypub'
export * from './async' export * from './async'
export * from './auth' export * from './auth'
export * from './pagination' export * from './pagination'
export * from './rate-limiter'
export * from './robots' export * from './robots'
export * from './servers' export * from './servers'
export * from './sort' export * from './sort'

View file

@ -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
}

View file

@ -7,6 +7,7 @@ import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServ
describe('Test application behind a reverse proxy', function () { describe('Test application behind a reverse proxy', function () {
let server: PeerTubeServer let server: PeerTubeServer
let userAccessToken: string
let videoId: string let videoId: string
before(async function () { before(async function () {
@ -34,6 +35,8 @@ describe('Test application behind a reverse proxy', function () {
server = await createSingleServer(1, config) server = await createSingleServer(1, config)
await setAccessTokensToServers([ server ]) await setAccessTokensToServers([ server ])
userAccessToken = await server.users.generateUserAndToken('user')
const { uuid } = await server.videos.upload() const { uuid } = await server.videos.upload()
videoId = uuid videoId = uuid
}) })
@ -93,7 +96,7 @@ describe('Test application behind a reverse proxy', function () {
it('Should rate limit logins', async function () { it('Should rate limit logins', async function () {
const user = { username: 'root', password: 'fail' } 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 }) 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 }) 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 () { after(async function () {
await cleanupTests([ server ]) await cleanupTests([ server ])
}) })