From e915cde30ec47258a2beeec5ca748c928b59858c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 20 Jun 2023 14:17:34 +0200 Subject: [PATCH] Fix runner api rate limit bypass --- server/controllers/api/abuse.ts | 3 ++ server/controllers/api/accounts.ts | 3 ++ server/controllers/api/blocklist.ts | 6 ++- server/controllers/api/bulk.ts | 4 +- server/controllers/api/config.ts | 4 +- server/controllers/api/custom-page.ts | 4 +- server/controllers/api/index.ts | 11 ++--- server/controllers/api/jobs.ts | 3 ++ server/controllers/api/metrics.ts | 6 ++- server/controllers/api/oauth-clients.ts | 4 +- server/controllers/api/overviews.ts | 6 ++- server/controllers/api/plugins.ts | 3 ++ server/controllers/api/runners/index.ts | 2 + server/controllers/api/runners/jobs-files.ts | 5 ++- server/controllers/api/runners/jobs.ts | 6 +++ .../controllers/api/runners/manage-runners.ts | 5 +++ .../api/runners/registration-tokens.ts | 6 ++- server/controllers/api/search/index.ts | 3 ++ server/controllers/api/server/index.ts | 3 ++ server/controllers/api/users/index.ts | 4 ++ server/controllers/api/video-channel-sync.ts | 3 ++ server/controllers/api/video-channel.ts | 3 ++ server/controllers/api/video-playlist.ts | 3 ++ server/controllers/api/videos/index.ts | 3 ++ server/middlewares/rate-limiter.ts | 6 +++ server/tests/api/runners/runner-common.ts | 44 ++++++++++++++----- 26 files changed, 122 insertions(+), 31 deletions(-) diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts index d6211cc83..d582f198d 100644 --- a/server/controllers/api/abuse.ts +++ b/server/controllers/api/abuse.ts @@ -16,6 +16,7 @@ import { abusesSortValidator, abuseUpdateValidator, addAbuseMessageValidator, + apiRateLimiter, asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, @@ -32,6 +33,8 @@ import { AccountModel } from '../../models/account/account' const abuseRouter = express.Router() +abuseRouter.use(apiRateLimiter) + abuseRouter.get('/', openapiOperationDoc({ operationId: 'getAbuses' }), authenticate, diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 2d86d393c..96f36bf6f 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts @@ -9,6 +9,7 @@ import { getFormattedObjects } from '../../helpers/utils' import { JobQueue } from '../../lib/job-queue' import { Hooks } from '../../lib/plugins/hooks' import { + apiRateLimiter, asyncMiddleware, authenticate, commonVideosFiltersValidator, @@ -41,6 +42,8 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist' const accountsRouter = express.Router() +accountsRouter.use(apiRateLimiter) + accountsRouter.get('/', paginationValidator, accountsSortValidator, diff --git a/server/controllers/api/blocklist.ts b/server/controllers/api/blocklist.ts index 1e936ad10..dee12b108 100644 --- a/server/controllers/api/blocklist.ts +++ b/server/controllers/api/blocklist.ts @@ -1,15 +1,17 @@ import express from 'express' import { handleToNameAndHost } from '@server/helpers/actors' +import { logger } from '@server/helpers/logger' import { AccountBlocklistModel } from '@server/models/account/account-blocklist' import { getServerActor } from '@server/models/application/application' import { ServerBlocklistModel } from '@server/models/server/server-blocklist' import { MActorAccountId, MUserAccountId } from '@server/types/models' import { BlockStatus } from '@shared/models' -import { asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares' -import { logger } from '@server/helpers/logger' +import { apiRateLimiter, asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares' const blocklistRouter = express.Router() +blocklistRouter.use(apiRateLimiter) + blocklistRouter.get('/status', optionalAuthenticate, blocklistStatusValidator, diff --git a/server/controllers/api/bulk.ts b/server/controllers/api/bulk.ts index 51292175b..c41c7d378 100644 --- a/server/controllers/api/bulk.ts +++ b/server/controllers/api/bulk.ts @@ -4,10 +4,12 @@ import { bulkRemoveCommentsOfValidator } from '@server/middlewares/validators/bu import { VideoCommentModel } from '@server/models/video/video-comment' import { HttpStatusCode } from '@shared/models' import { BulkRemoveCommentsOfBody } from '@shared/models/bulk/bulk-remove-comments-of-body.model' -import { asyncMiddleware, authenticate } from '../../middlewares' +import { apiRateLimiter, asyncMiddleware, authenticate } from '../../middlewares' const bulkRouter = express.Router() +bulkRouter.use(apiRateLimiter) + bulkRouter.post('/remove-comments-of', authenticate, asyncMiddleware(bulkRemoveCommentsOfValidator), diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 3b6230f4a..228eae109 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts @@ -8,11 +8,13 @@ import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '.. import { objectConverter } from '../../helpers/core-utils' import { CONFIG, reloadConfig } from '../../initializers/config' import { ClientHtml } from '../../lib/client-html' -import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares' +import { apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares' import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config' const configRouter = express.Router() +configRouter.use(apiRateLimiter) + const auditLogger = auditLoggerFactory('config') configRouter.get('/', diff --git a/server/controllers/api/custom-page.ts b/server/controllers/api/custom-page.ts index d1c672f3f..f4e1a0e79 100644 --- a/server/controllers/api/custom-page.ts +++ b/server/controllers/api/custom-page.ts @@ -2,10 +2,12 @@ import express from 'express' import { ServerConfigManager } from '@server/lib/server-config-manager' import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' import { HttpStatusCode, UserRight } from '@shared/models' -import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' +import { apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' const customPageRouter = express.Router() +customPageRouter.use(apiRateLimiter) + customPageRouter.get('/homepage/instance', asyncMiddleware(getInstanceHomepage) ) diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 646f9597e..31f1a56f9 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts @@ -1,9 +1,8 @@ import cors from 'cors' import express from 'express' -import { buildRateLimiter } from '@server/middlewares' + import { HttpStatusCode } from '../../../shared/models' import { badRequest } from '../../helpers/express-utils' -import { CONFIG } from '../../initializers/config' import { abuseRouter } from './abuse' import { accountsRouter } from './accounts' import { blocklistRouter } from './blocklist' @@ -32,12 +31,6 @@ apiRouter.use(cors({ credentials: true })) -const apiRateLimiter = buildRateLimiter({ - windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, - max: CONFIG.RATES_LIMIT.API.MAX -}) -apiRouter.use(apiRateLimiter) - apiRouter.use('/server', serverRouter) apiRouter.use('/abuses', abuseRouter) apiRouter.use('/bulk', bulkRouter) @@ -57,6 +50,8 @@ apiRouter.use('/plugins', pluginRouter) apiRouter.use('/custom-pages', customPageRouter) apiRouter.use('/blocklist', blocklistRouter) apiRouter.use('/runners', runnersRouter) + +// apiRouter.use(apiRateLimiter) apiRouter.use('/ping', pong) apiRouter.use('/*', badRequest) diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts index b63e2f962..c701bc970 100644 --- a/server/controllers/api/jobs.ts +++ b/server/controllers/api/jobs.ts @@ -4,6 +4,7 @@ import { HttpStatusCode, Job, JobState, JobType, ResultList, UserRight } from '@ import { isArray } from '../../helpers/custom-validators/misc' import { JobQueue } from '../../lib/job-queue' import { + apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, @@ -17,6 +18,8 @@ import { listJobsValidator } from '../../middlewares/validators/jobs' const jobsRouter = express.Router() +jobsRouter.use(apiRateLimiter) + jobsRouter.post('/pause', authenticate, ensureUserHasRight(UserRight.MANAGE_JOBS), diff --git a/server/controllers/api/metrics.ts b/server/controllers/api/metrics.ts index f66173875..909963fa7 100644 --- a/server/controllers/api/metrics.ts +++ b/server/controllers/api/metrics.ts @@ -1,11 +1,13 @@ import express from 'express' +import { CONFIG } from '@server/initializers/config' import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics' import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' -import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares' -import { CONFIG } from '@server/initializers/config' +import { addPlaybackMetricValidator, apiRateLimiter, asyncMiddleware } from '../../middlewares' const metricsRouter = express.Router() +metricsRouter.use(apiRateLimiter) + metricsRouter.post('/playback', asyncMiddleware(addPlaybackMetricValidator), addPlaybackMetric diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts index eb7942fd6..b619b78a3 100644 --- a/server/controllers/api/oauth-clients.ts +++ b/server/controllers/api/oauth-clients.ts @@ -4,10 +4,12 @@ import { OAuthClientModel } from '@server/models/oauth/oauth-client' import { HttpStatusCode, OAuthClientLocal } from '@shared/models' import { logger } from '../../helpers/logger' import { CONFIG } from '../../initializers/config' -import { asyncMiddleware, openapiOperationDoc } from '../../middlewares' +import { apiRateLimiter, asyncMiddleware, openapiOperationDoc } from '../../middlewares' const oauthClientsRouter = express.Router() +oauthClientsRouter.use(apiRateLimiter) + oauthClientsRouter.get('/local', openapiOperationDoc({ operationId: 'getOAuthClient' }), asyncMiddleware(getLocalClient) diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts index 34585e557..fc616281e 100644 --- a/server/controllers/api/overviews.ts +++ b/server/controllers/api/overviews.ts @@ -2,16 +2,18 @@ import express from 'express' import memoizee from 'memoizee' import { logger } from '@server/helpers/logger' import { Hooks } from '@server/lib/plugins/hooks' +import { getServerActor } from '@server/models/application/application' import { VideoModel } from '@server/models/video/video' import { CategoryOverview, ChannelOverview, TagOverview, VideosOverview } from '../../../shared/models/overviews' import { buildNSFWFilter } from '../../helpers/express-utils' import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants' -import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares' +import { apiRateLimiter, asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares' import { TagModel } from '../../models/video/tag' -import { getServerActor } from '@server/models/application/application' const overviewsRouter = express.Router() +overviewsRouter.use(apiRateLimiter) + overviewsRouter.get('/videos', videosOverviewValidator, optionalAuthenticate, diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index e85fd6e11..337b72b2f 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts @@ -4,6 +4,7 @@ import { getFormattedObjects } from '@server/helpers/utils' import { listAvailablePluginsFromIndex } from '@server/lib/plugins/plugin-index' import { PluginManager } from '@server/lib/plugins/plugin-manager' import { + apiRateLimiter, asyncMiddleware, authenticate, availablePluginsSortValidator, @@ -35,6 +36,8 @@ import { const pluginRouter = express.Router() +pluginRouter.use(apiRateLimiter) + pluginRouter.get('/available', openapiOperationDoc({ operationId: 'getAvailablePlugins' }), authenticate, diff --git a/server/controllers/api/runners/index.ts b/server/controllers/api/runners/index.ts index c98ded354..9998fe4cc 100644 --- a/server/controllers/api/runners/index.ts +++ b/server/controllers/api/runners/index.ts @@ -6,6 +6,8 @@ import { runnerRegistrationTokensRouter } from './registration-tokens' const runnersRouter = express.Router() +// No api route limiter here, they are defined in child routers + runnersRouter.use('/', manageRunnersRouter) runnersRouter.use('/', runnerJobsRouter) runnersRouter.use('/', runnerJobFilesRouter) diff --git a/server/controllers/api/runners/jobs-files.ts b/server/controllers/api/runners/jobs-files.ts index 260d824a8..4e69fb902 100644 --- a/server/controllers/api/runners/jobs-files.ts +++ b/server/controllers/api/runners/jobs-files.ts @@ -3,7 +3,7 @@ import { logger, loggerTagsFactory } from '@server/helpers/logger' import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage' import { VideoPathManager } from '@server/lib/video-path-manager' import { getStudioTaskFilePath } from '@server/lib/video-studio' -import { asyncMiddleware } from '@server/middlewares' +import { apiRateLimiter, asyncMiddleware } from '@server/middlewares' import { jobOfRunnerGetValidator } from '@server/middlewares/validators/runners' import { runnerJobGetVideoStudioTaskFileValidator, @@ -16,18 +16,21 @@ const lTags = loggerTagsFactory('api', 'runner') const runnerJobFilesRouter = express.Router() runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality', + apiRateLimiter, asyncMiddleware(jobOfRunnerGetValidator), asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), asyncMiddleware(getMaxQualityVideoFile) ) runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/previews/max-quality', + apiRateLimiter, asyncMiddleware(jobOfRunnerGetValidator), asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), getMaxQualityVideoPreview ) runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename', + apiRateLimiter, asyncMiddleware(jobOfRunnerGetValidator), asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), runnerJobGetVideoStudioTaskFileValidator, diff --git a/server/controllers/api/runners/jobs.ts b/server/controllers/api/runners/jobs.ts index 140f062be..5d687e689 100644 --- a/server/controllers/api/runners/jobs.ts +++ b/server/controllers/api/runners/jobs.ts @@ -7,6 +7,7 @@ import { MIMETYPES } from '@server/initializers/constants' import { sequelizeTypescript } from '@server/initializers/database' import { getRunnerJobHandlerClass, updateLastRunnerContact } from '@server/lib/runners' import { + apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, @@ -69,11 +70,13 @@ const runnerJobsRouter = express.Router() // --------------------------------------------------------------------------- runnerJobsRouter.post('/jobs/request', + apiRateLimiter, asyncMiddleware(getRunnerFromTokenValidator), asyncMiddleware(requestRunnerJob) ) runnerJobsRouter.post('/jobs/:jobUUID/accept', + apiRateLimiter, asyncMiddleware(runnerJobGetValidator), acceptRunnerJobValidator, asyncMiddleware(getRunnerFromTokenValidator), @@ -81,6 +84,7 @@ runnerJobsRouter.post('/jobs/:jobUUID/accept', ) runnerJobsRouter.post('/jobs/:jobUUID/abort', + apiRateLimiter, asyncMiddleware(jobOfRunnerGetValidator), abortRunnerJobValidator, asyncMiddleware(abortRunnerJob) @@ -88,6 +92,7 @@ runnerJobsRouter.post('/jobs/:jobUUID/abort', runnerJobsRouter.post('/jobs/:jobUUID/update', runnerJobUpdateVideoFiles, + apiRateLimiter, // Has to be after multer middleware to parse runner token asyncMiddleware(jobOfRunnerGetValidator), updateRunnerJobValidator, asyncMiddleware(updateRunnerJobController) @@ -101,6 +106,7 @@ runnerJobsRouter.post('/jobs/:jobUUID/error', runnerJobsRouter.post('/jobs/:jobUUID/success', postRunnerJobSuccessVideoFiles, + apiRateLimiter, // Has to be after multer middleware to parse runner token asyncMiddleware(jobOfRunnerGetValidator), successRunnerJobValidator, asyncMiddleware(postRunnerJobSuccess) diff --git a/server/controllers/api/runners/manage-runners.ts b/server/controllers/api/runners/manage-runners.ts index eb08c4b1d..be7ebc0b3 100644 --- a/server/controllers/api/runners/manage-runners.ts +++ b/server/controllers/api/runners/manage-runners.ts @@ -2,6 +2,7 @@ import express from 'express' import { logger, loggerTagsFactory } from '@server/helpers/logger' import { generateRunnerToken } from '@server/helpers/token-generator' import { + apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, @@ -19,15 +20,18 @@ const lTags = loggerTagsFactory('api', 'runner') const manageRunnersRouter = express.Router() manageRunnersRouter.post('/register', + apiRateLimiter, asyncMiddleware(registerRunnerValidator), asyncMiddleware(registerRunner) ) manageRunnersRouter.post('/unregister', + apiRateLimiter, asyncMiddleware(getRunnerFromTokenValidator), asyncMiddleware(unregisterRunner) ) manageRunnersRouter.delete('/:runnerId', + apiRateLimiter, authenticate, ensureUserHasRight(UserRight.MANAGE_RUNNERS), asyncMiddleware(deleteRunnerValidator), @@ -35,6 +39,7 @@ manageRunnersRouter.delete('/:runnerId', ) manageRunnersRouter.get('/', + apiRateLimiter, authenticate, ensureUserHasRight(UserRight.MANAGE_RUNNERS), paginationValidator, diff --git a/server/controllers/api/runners/registration-tokens.ts b/server/controllers/api/runners/registration-tokens.ts index 5ac3773fe..117ff271b 100644 --- a/server/controllers/api/runners/registration-tokens.ts +++ b/server/controllers/api/runners/registration-tokens.ts @@ -1,6 +1,8 @@ import express from 'express' +import { logger, loggerTagsFactory } from '@server/helpers/logger' import { generateRunnerRegistrationToken } from '@server/helpers/token-generator' import { + apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, @@ -12,19 +14,20 @@ import { import { deleteRegistrationTokenValidator } from '@server/middlewares/validators/runners' import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token' import { HttpStatusCode, ListRunnerRegistrationTokensQuery, UserRight } from '@shared/models' -import { logger, loggerTagsFactory } from '@server/helpers/logger' const lTags = loggerTagsFactory('api', 'runner') const runnerRegistrationTokensRouter = express.Router() runnerRegistrationTokensRouter.post('/registration-tokens/generate', + apiRateLimiter, authenticate, ensureUserHasRight(UserRight.MANAGE_RUNNERS), asyncMiddleware(generateRegistrationToken) ) runnerRegistrationTokensRouter.delete('/registration-tokens/:id', + apiRateLimiter, authenticate, ensureUserHasRight(UserRight.MANAGE_RUNNERS), asyncMiddleware(deleteRegistrationTokenValidator), @@ -32,6 +35,7 @@ runnerRegistrationTokensRouter.delete('/registration-tokens/:id', ) runnerRegistrationTokensRouter.get('/registration-tokens', + apiRateLimiter, authenticate, ensureUserHasRight(UserRight.MANAGE_RUNNERS), paginationValidator, diff --git a/server/controllers/api/search/index.ts b/server/controllers/api/search/index.ts index 39efc0b10..4d395161c 100644 --- a/server/controllers/api/search/index.ts +++ b/server/controllers/api/search/index.ts @@ -1,10 +1,13 @@ import express from 'express' +import { apiRateLimiter } from '@server/middlewares' import { searchChannelsRouter } from './search-video-channels' import { searchPlaylistsRouter } from './search-video-playlists' import { searchVideosRouter } from './search-videos' const searchRouter = express.Router() +searchRouter.use(apiRateLimiter) + searchRouter.use('/', searchVideosRouter) searchRouter.use('/', searchChannelsRouter) searchRouter.use('/', searchPlaylistsRouter) diff --git a/server/controllers/api/server/index.ts b/server/controllers/api/server/index.ts index b20718d09..57f7d601c 100644 --- a/server/controllers/api/server/index.ts +++ b/server/controllers/api/server/index.ts @@ -1,4 +1,5 @@ import express from 'express' +import { apiRateLimiter } from '@server/middlewares' import { contactRouter } from './contact' import { debugRouter } from './debug' import { serverFollowsRouter } from './follows' @@ -9,6 +10,8 @@ import { statsRouter } from './stats' const serverRouter = express.Router() +serverRouter.use(apiRateLimiter) + serverRouter.use('/', serverFollowsRouter) serverRouter.use('/', serverRedundancyRouter) serverRouter.use('/', statsRouter) diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 96366d68c..5eac6fd0f 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -15,6 +15,7 @@ import { Redis } from '../../../lib/redis' import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user' import { adminUsersSortValidator, + apiRateLimiter, asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, @@ -50,6 +51,9 @@ import { twoFactorRouter } from './two-factor' const auditLogger = auditLoggerFactory('users') const usersRouter = express.Router() + +usersRouter.use(apiRateLimiter) + usersRouter.use('/', emailVerificationRouter) usersRouter.use('/', registrationsRouter) usersRouter.use('/', twoFactorRouter) diff --git a/server/controllers/api/video-channel-sync.ts b/server/controllers/api/video-channel-sync.ts index 03c54b59c..6b52ac7dd 100644 --- a/server/controllers/api/video-channel-sync.ts +++ b/server/controllers/api/video-channel-sync.ts @@ -2,6 +2,7 @@ import express from 'express' import { auditLoggerFactory, getAuditIdFromRes, VideoChannelSyncAuditView } from '@server/helpers/audit-logger' import { logger } from '@server/helpers/logger' import { + apiRateLimiter, asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, @@ -17,6 +18,8 @@ import { HttpStatusCode, VideoChannelSyncState } from '@shared/models' const videoChannelSyncRouter = express.Router() const auditLogger = auditLoggerFactory('channel-syncs') +videoChannelSyncRouter.use(apiRateLimiter) + videoChannelSyncRouter.post('/', authenticate, ensureSyncIsEnabled, diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index c6d144f79..cdafa31dc 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts @@ -19,6 +19,7 @@ import { JobQueue } from '../../lib/job-queue' import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../lib/local-actor' import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' import { + apiRateLimiter, asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, @@ -57,6 +58,8 @@ const reqBannerFile = createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_ const videoChannelRouter = express.Router() +videoChannelRouter.use(apiRateLimiter) + videoChannelRouter.get('/', paginationValidator, videoChannelsSortValidator, diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index de32dec88..fe00034ed 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts @@ -25,6 +25,7 @@ import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlayli import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' import { updatePlaylistMiniatureFromExisting } from '../../lib/thumbnail' import { + apiRateLimiter, asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, @@ -52,6 +53,8 @@ const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIM const videoPlaylistRouter = express.Router() +videoPlaylistRouter.use(apiRateLimiter) + videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies) videoPlaylistRouter.get('/', diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 41992155d..a34325e79 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -15,6 +15,7 @@ import { sequelizeTypescript } from '../../../initializers/database' import { JobQueue } from '../../../lib/job-queue' import { Hooks } from '../../../lib/plugins/hooks' import { + apiRateLimiter, asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, @@ -50,6 +51,8 @@ import { viewRouter } from './view' const auditLogger = auditLoggerFactory('videos') const videosRouter = express.Router() +videosRouter.use(apiRateLimiter) + videosRouter.use('/', blacklistRouter) videosRouter.use('/', statsRouter) videosRouter.use('/', rateVideoRouter) diff --git a/server/middlewares/rate-limiter.ts b/server/middlewares/rate-limiter.ts index 0e936028c..8257965dd 100644 --- a/server/middlewares/rate-limiter.ts +++ b/server/middlewares/rate-limiter.ts @@ -1,5 +1,6 @@ import express from 'express' import RateLimit, { Options as RateLimitHandlerOptions } from 'express-rate-limit' +import { CONFIG } from '@server/initializers/config' import { RunnerModel } from '@server/models/runner/runner' import { UserRole } from '@shared/models' import { optionalAuthenticate } from './auth' @@ -39,6 +40,11 @@ export function buildRateLimiter (options: { }) } +export const apiRateLimiter = buildRateLimiter({ + windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, + max: CONFIG.RATES_LIMIT.API.MAX +}) + // --------------------------------------------------------------------------- // Private // --------------------------------------------------------------------------- diff --git a/server/tests/api/runners/runner-common.ts b/server/tests/api/runners/runner-common.ts index 554024190..34a51abe7 100644 --- a/server/tests/api/runners/runner-common.ts +++ b/server/tests/api/runners/runner-common.ts @@ -14,7 +14,6 @@ import { import { cleanupTests, createSingleServer, - makePostBodyRequest, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel, @@ -641,24 +640,47 @@ describe('Test runner common actions', function () { }) }) - it('Should rate limit an unknown runner', async function () { - const path = '/api/v1/ping' - const fields = { runnerToken: 'toto' } + it('Should rate limit an unknown runner, but not a registered one', async function () { + this.timeout(60000) + + await server.videos.quickUpload({ name: 'video' }) + await waitJobs([ server ]) + + const { job } = await server.runnerJobs.autoAccept({ runnerToken }) for (let i = 0; i < 20; i++) { try { - await makePostBodyRequest({ url: server.url, path, fields, expectedStatus: HttpStatusCode.OK_200 }) + await server.runnerJobs.request({ runnerToken }) + await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid }) } catch {} } - await makePostBodyRequest({ url: server.url, path, fields, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) - }) + // Invalid + { + await server.runnerJobs.request({ runnerToken: 'toto', expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) + await server.runnerJobs.update({ + runnerToken: 'toto', + jobToken: job.jobToken, + jobUUID: job.uuid, + expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 + }) + } - it('Should not rate limit a registered runner', async function () { - const path = '/api/v1/ping' + // Not provided + { + await server.runnerJobs.request({ runnerToken: undefined, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) + await server.runnerJobs.update({ + runnerToken: undefined, + jobToken: job.jobToken, + jobUUID: job.uuid, + expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 + }) + } - for (let i = 0; i < 20; i++) { - await makePostBodyRequest({ url: server.url, path, fields: { runnerToken }, expectedStatus: HttpStatusCode.OK_200 }) + // Registered + { + await server.runnerJobs.request({ runnerToken }) + await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid }) } }) }) -- 2.41.0