aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-06-20 14:17:34 +0200
committerChocobozzz <me@florianbigard.com>2023-06-20 14:17:34 +0200
commite915cde30ec47258a2beeec5ca748c928b59858c (patch)
treef5692ab20c534a61487f3bd471bb6105ed58d88a /server
parent923e41fa4f342019298b46e407ea1f0207f74205 (diff)
downloadPeerTube-e915cde30ec47258a2beeec5ca748c928b59858c.tar.gz
PeerTube-e915cde30ec47258a2beeec5ca748c928b59858c.tar.zst
PeerTube-e915cde30ec47258a2beeec5ca748c928b59858c.zip
Fix runner api rate limit bypass
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/abuse.ts3
-rw-r--r--server/controllers/api/accounts.ts3
-rw-r--r--server/controllers/api/blocklist.ts6
-rw-r--r--server/controllers/api/bulk.ts4
-rw-r--r--server/controllers/api/config.ts4
-rw-r--r--server/controllers/api/custom-page.ts4
-rw-r--r--server/controllers/api/index.ts11
-rw-r--r--server/controllers/api/jobs.ts3
-rw-r--r--server/controllers/api/metrics.ts6
-rw-r--r--server/controllers/api/oauth-clients.ts4
-rw-r--r--server/controllers/api/overviews.ts6
-rw-r--r--server/controllers/api/plugins.ts3
-rw-r--r--server/controllers/api/runners/index.ts2
-rw-r--r--server/controllers/api/runners/jobs-files.ts5
-rw-r--r--server/controllers/api/runners/jobs.ts6
-rw-r--r--server/controllers/api/runners/manage-runners.ts5
-rw-r--r--server/controllers/api/runners/registration-tokens.ts6
-rw-r--r--server/controllers/api/search/index.ts3
-rw-r--r--server/controllers/api/server/index.ts3
-rw-r--r--server/controllers/api/users/index.ts4
-rw-r--r--server/controllers/api/video-channel-sync.ts3
-rw-r--r--server/controllers/api/video-channel.ts3
-rw-r--r--server/controllers/api/video-playlist.ts3
-rw-r--r--server/controllers/api/videos/index.ts3
-rw-r--r--server/middlewares/rate-limiter.ts6
-rw-r--r--server/tests/api/runners/runner-common.ts44
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 {
16 abusesSortValidator, 16 abusesSortValidator,
17 abuseUpdateValidator, 17 abuseUpdateValidator,
18 addAbuseMessageValidator, 18 addAbuseMessageValidator,
19 apiRateLimiter,
19 asyncMiddleware, 20 asyncMiddleware,
20 asyncRetryTransactionMiddleware, 21 asyncRetryTransactionMiddleware,
21 authenticate, 22 authenticate,
@@ -32,6 +33,8 @@ import { AccountModel } from '../../models/account/account'
32 33
33const abuseRouter = express.Router() 34const abuseRouter = express.Router()
34 35
36abuseRouter.use(apiRateLimiter)
37
35abuseRouter.get('/', 38abuseRouter.get('/',
36 openapiOperationDoc({ operationId: 'getAbuses' }), 39 openapiOperationDoc({ operationId: 'getAbuses' }),
37 authenticate, 40 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'
9import { JobQueue } from '../../lib/job-queue' 9import { JobQueue } from '../../lib/job-queue'
10import { Hooks } from '../../lib/plugins/hooks' 10import { Hooks } from '../../lib/plugins/hooks'
11import { 11import {
12 apiRateLimiter,
12 asyncMiddleware, 13 asyncMiddleware,
13 authenticate, 14 authenticate,
14 commonVideosFiltersValidator, 15 commonVideosFiltersValidator,
@@ -41,6 +42,8 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist'
41 42
42const accountsRouter = express.Router() 43const accountsRouter = express.Router()
43 44
45accountsRouter.use(apiRateLimiter)
46
44accountsRouter.get('/', 47accountsRouter.get('/',
45 paginationValidator, 48 paginationValidator,
46 accountsSortValidator, 49 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 @@
1import express from 'express' 1import express from 'express'
2import { handleToNameAndHost } from '@server/helpers/actors' 2import { handleToNameAndHost } from '@server/helpers/actors'
3import { logger } from '@server/helpers/logger'
3import { AccountBlocklistModel } from '@server/models/account/account-blocklist' 4import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
4import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
5import { ServerBlocklistModel } from '@server/models/server/server-blocklist' 6import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
6import { MActorAccountId, MUserAccountId } from '@server/types/models' 7import { MActorAccountId, MUserAccountId } from '@server/types/models'
7import { BlockStatus } from '@shared/models' 8import { BlockStatus } from '@shared/models'
8import { asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares' 9import { apiRateLimiter, asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares'
9import { logger } from '@server/helpers/logger'
10 10
11const blocklistRouter = express.Router() 11const blocklistRouter = express.Router()
12 12
13blocklistRouter.use(apiRateLimiter)
14
13blocklistRouter.get('/status', 15blocklistRouter.get('/status',
14 optionalAuthenticate, 16 optionalAuthenticate,
15 blocklistStatusValidator, 17 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
4import { VideoCommentModel } from '@server/models/video/video-comment' 4import { VideoCommentModel } from '@server/models/video/video-comment'
5import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { BulkRemoveCommentsOfBody } from '@shared/models/bulk/bulk-remove-comments-of-body.model' 6import { BulkRemoveCommentsOfBody } from '@shared/models/bulk/bulk-remove-comments-of-body.model'
7import { asyncMiddleware, authenticate } from '../../middlewares' 7import { apiRateLimiter, asyncMiddleware, authenticate } from '../../middlewares'
8 8
9const bulkRouter = express.Router() 9const bulkRouter = express.Router()
10 10
11bulkRouter.use(apiRateLimiter)
12
11bulkRouter.post('/remove-comments-of', 13bulkRouter.post('/remove-comments-of',
12 authenticate, 14 authenticate,
13 asyncMiddleware(bulkRemoveCommentsOfValidator), 15 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 '..
8import { objectConverter } from '../../helpers/core-utils' 8import { objectConverter } from '../../helpers/core-utils'
9import { CONFIG, reloadConfig } from '../../initializers/config' 9import { CONFIG, reloadConfig } from '../../initializers/config'
10import { ClientHtml } from '../../lib/client-html' 10import { ClientHtml } from '../../lib/client-html'
11import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares' 11import { apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares'
12import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config' 12import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config'
13 13
14const configRouter = express.Router() 14const configRouter = express.Router()
15 15
16configRouter.use(apiRateLimiter)
17
16const auditLogger = auditLoggerFactory('config') 18const auditLogger = auditLoggerFactory('config')
17 19
18configRouter.get('/', 20configRouter.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'
2import { ServerConfigManager } from '@server/lib/server-config-manager' 2import { ServerConfigManager } from '@server/lib/server-config-manager'
3import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' 3import { ActorCustomPageModel } from '@server/models/account/actor-custom-page'
4import { HttpStatusCode, UserRight } from '@shared/models' 4import { HttpStatusCode, UserRight } from '@shared/models'
5import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' 5import { apiRateLimiter, asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
6 6
7const customPageRouter = express.Router() 7const customPageRouter = express.Router()
8 8
9customPageRouter.use(apiRateLimiter)
10
9customPageRouter.get('/homepage/instance', 11customPageRouter.get('/homepage/instance',
10 asyncMiddleware(getInstanceHomepage) 12 asyncMiddleware(getInstanceHomepage)
11) 13)
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 @@
1import cors from 'cors' 1import cors from 'cors'
2import express from 'express' 2import express from 'express'
3import { buildRateLimiter } from '@server/middlewares' 3
4import { HttpStatusCode } from '../../../shared/models' 4import { HttpStatusCode } from '../../../shared/models'
5import { badRequest } from '../../helpers/express-utils' 5import { badRequest } from '../../helpers/express-utils'
6import { CONFIG } from '../../initializers/config'
7import { abuseRouter } from './abuse' 6import { abuseRouter } from './abuse'
8import { accountsRouter } from './accounts' 7import { accountsRouter } from './accounts'
9import { blocklistRouter } from './blocklist' 8import { blocklistRouter } from './blocklist'
@@ -32,12 +31,6 @@ apiRouter.use(cors({
32 credentials: true 31 credentials: true
33})) 32}))
34 33
35const apiRateLimiter = buildRateLimiter({
36 windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS,
37 max: CONFIG.RATES_LIMIT.API.MAX
38})
39apiRouter.use(apiRateLimiter)
40
41apiRouter.use('/server', serverRouter) 34apiRouter.use('/server', serverRouter)
42apiRouter.use('/abuses', abuseRouter) 35apiRouter.use('/abuses', abuseRouter)
43apiRouter.use('/bulk', bulkRouter) 36apiRouter.use('/bulk', bulkRouter)
@@ -57,6 +50,8 @@ apiRouter.use('/plugins', pluginRouter)
57apiRouter.use('/custom-pages', customPageRouter) 50apiRouter.use('/custom-pages', customPageRouter)
58apiRouter.use('/blocklist', blocklistRouter) 51apiRouter.use('/blocklist', blocklistRouter)
59apiRouter.use('/runners', runnersRouter) 52apiRouter.use('/runners', runnersRouter)
53
54// apiRouter.use(apiRateLimiter)
60apiRouter.use('/ping', pong) 55apiRouter.use('/ping', pong)
61apiRouter.use('/*', badRequest) 56apiRouter.use('/*', badRequest)
62 57
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 '@
4import { isArray } from '../../helpers/custom-validators/misc' 4import { isArray } from '../../helpers/custom-validators/misc'
5import { JobQueue } from '../../lib/job-queue' 5import { JobQueue } from '../../lib/job-queue'
6import { 6import {
7 apiRateLimiter,
7 asyncMiddleware, 8 asyncMiddleware,
8 authenticate, 9 authenticate,
9 ensureUserHasRight, 10 ensureUserHasRight,
@@ -17,6 +18,8 @@ import { listJobsValidator } from '../../middlewares/validators/jobs'
17 18
18const jobsRouter = express.Router() 19const jobsRouter = express.Router()
19 20
21jobsRouter.use(apiRateLimiter)
22
20jobsRouter.post('/pause', 23jobsRouter.post('/pause',
21 authenticate, 24 authenticate,
22 ensureUserHasRight(UserRight.MANAGE_JOBS), 25 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 @@
1import express from 'express' 1import express from 'express'
2import { CONFIG } from '@server/initializers/config'
2import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics' 3import { OpenTelemetryMetrics } from '@server/lib/opentelemetry/metrics'
3import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' 4import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models'
4import { addPlaybackMetricValidator, asyncMiddleware } from '../../middlewares' 5import { addPlaybackMetricValidator, apiRateLimiter, asyncMiddleware } from '../../middlewares'
5import { CONFIG } from '@server/initializers/config'
6 6
7const metricsRouter = express.Router() 7const metricsRouter = express.Router()
8 8
9metricsRouter.use(apiRateLimiter)
10
9metricsRouter.post('/playback', 11metricsRouter.post('/playback',
10 asyncMiddleware(addPlaybackMetricValidator), 12 asyncMiddleware(addPlaybackMetricValidator),
11 addPlaybackMetric 13 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'
4import { HttpStatusCode, OAuthClientLocal } from '@shared/models' 4import { HttpStatusCode, OAuthClientLocal } from '@shared/models'
5import { logger } from '../../helpers/logger' 5import { logger } from '../../helpers/logger'
6import { CONFIG } from '../../initializers/config' 6import { CONFIG } from '../../initializers/config'
7import { asyncMiddleware, openapiOperationDoc } from '../../middlewares' 7import { apiRateLimiter, asyncMiddleware, openapiOperationDoc } from '../../middlewares'
8 8
9const oauthClientsRouter = express.Router() 9const oauthClientsRouter = express.Router()
10 10
11oauthClientsRouter.use(apiRateLimiter)
12
11oauthClientsRouter.get('/local', 13oauthClientsRouter.get('/local',
12 openapiOperationDoc({ operationId: 'getOAuthClient' }), 14 openapiOperationDoc({ operationId: 'getOAuthClient' }),
13 asyncMiddleware(getLocalClient) 15 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'
2import memoizee from 'memoizee' 2import memoizee from 'memoizee'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { getServerActor } from '@server/models/application/application'
5import { VideoModel } from '@server/models/video/video' 6import { VideoModel } from '@server/models/video/video'
6import { CategoryOverview, ChannelOverview, TagOverview, VideosOverview } from '../../../shared/models/overviews' 7import { CategoryOverview, ChannelOverview, TagOverview, VideosOverview } from '../../../shared/models/overviews'
7import { buildNSFWFilter } from '../../helpers/express-utils' 8import { buildNSFWFilter } from '../../helpers/express-utils'
8import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants' 9import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants'
9import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares' 10import { apiRateLimiter, asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares'
10import { TagModel } from '../../models/video/tag' 11import { TagModel } from '../../models/video/tag'
11import { getServerActor } from '@server/models/application/application'
12 12
13const overviewsRouter = express.Router() 13const overviewsRouter = express.Router()
14 14
15overviewsRouter.use(apiRateLimiter)
16
15overviewsRouter.get('/videos', 17overviewsRouter.get('/videos',
16 videosOverviewValidator, 18 videosOverviewValidator,
17 optionalAuthenticate, 19 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'
4import { listAvailablePluginsFromIndex } from '@server/lib/plugins/plugin-index' 4import { listAvailablePluginsFromIndex } from '@server/lib/plugins/plugin-index'
5import { PluginManager } from '@server/lib/plugins/plugin-manager' 5import { PluginManager } from '@server/lib/plugins/plugin-manager'
6import { 6import {
7 apiRateLimiter,
7 asyncMiddleware, 8 asyncMiddleware,
8 authenticate, 9 authenticate,
9 availablePluginsSortValidator, 10 availablePluginsSortValidator,
@@ -35,6 +36,8 @@ import {
35 36
36const pluginRouter = express.Router() 37const pluginRouter = express.Router()
37 38
39pluginRouter.use(apiRateLimiter)
40
38pluginRouter.get('/available', 41pluginRouter.get('/available',
39 openapiOperationDoc({ operationId: 'getAvailablePlugins' }), 42 openapiOperationDoc({ operationId: 'getAvailablePlugins' }),
40 authenticate, 43 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'
6 6
7const runnersRouter = express.Router() 7const runnersRouter = express.Router()
8 8
9// No api route limiter here, they are defined in child routers
10
9runnersRouter.use('/', manageRunnersRouter) 11runnersRouter.use('/', manageRunnersRouter)
10runnersRouter.use('/', runnerJobsRouter) 12runnersRouter.use('/', runnerJobsRouter)
11runnersRouter.use('/', runnerJobFilesRouter) 13runnersRouter.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'
3import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage' 3import { proxifyHLS, proxifyWebTorrentFile } from '@server/lib/object-storage'
4import { VideoPathManager } from '@server/lib/video-path-manager' 4import { VideoPathManager } from '@server/lib/video-path-manager'
5import { getStudioTaskFilePath } from '@server/lib/video-studio' 5import { getStudioTaskFilePath } from '@server/lib/video-studio'
6import { asyncMiddleware } from '@server/middlewares' 6import { apiRateLimiter, asyncMiddleware } from '@server/middlewares'
7import { jobOfRunnerGetValidator } from '@server/middlewares/validators/runners' 7import { jobOfRunnerGetValidator } from '@server/middlewares/validators/runners'
8import { 8import {
9 runnerJobGetVideoStudioTaskFileValidator, 9 runnerJobGetVideoStudioTaskFileValidator,
@@ -16,18 +16,21 @@ const lTags = loggerTagsFactory('api', 'runner')
16const runnerJobFilesRouter = express.Router() 16const runnerJobFilesRouter = express.Router()
17 17
18runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality', 18runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/max-quality',
19 apiRateLimiter,
19 asyncMiddleware(jobOfRunnerGetValidator), 20 asyncMiddleware(jobOfRunnerGetValidator),
20 asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), 21 asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
21 asyncMiddleware(getMaxQualityVideoFile) 22 asyncMiddleware(getMaxQualityVideoFile)
22) 23)
23 24
24runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/previews/max-quality', 25runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/previews/max-quality',
26 apiRateLimiter,
25 asyncMiddleware(jobOfRunnerGetValidator), 27 asyncMiddleware(jobOfRunnerGetValidator),
26 asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), 28 asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
27 getMaxQualityVideoPreview 29 getMaxQualityVideoPreview
28) 30)
29 31
30runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename', 32runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-files/:filename',
33 apiRateLimiter,
31 asyncMiddleware(jobOfRunnerGetValidator), 34 asyncMiddleware(jobOfRunnerGetValidator),
32 asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), 35 asyncMiddleware(runnerJobGetVideoTranscodingFileValidator),
33 runnerJobGetVideoStudioTaskFileValidator, 36 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'
7import { sequelizeTypescript } from '@server/initializers/database' 7import { sequelizeTypescript } from '@server/initializers/database'
8import { getRunnerJobHandlerClass, updateLastRunnerContact } from '@server/lib/runners' 8import { getRunnerJobHandlerClass, updateLastRunnerContact } from '@server/lib/runners'
9import { 9import {
10 apiRateLimiter,
10 asyncMiddleware, 11 asyncMiddleware,
11 authenticate, 12 authenticate,
12 ensureUserHasRight, 13 ensureUserHasRight,
@@ -69,11 +70,13 @@ const runnerJobsRouter = express.Router()
69// --------------------------------------------------------------------------- 70// ---------------------------------------------------------------------------
70 71
71runnerJobsRouter.post('/jobs/request', 72runnerJobsRouter.post('/jobs/request',
73 apiRateLimiter,
72 asyncMiddleware(getRunnerFromTokenValidator), 74 asyncMiddleware(getRunnerFromTokenValidator),
73 asyncMiddleware(requestRunnerJob) 75 asyncMiddleware(requestRunnerJob)
74) 76)
75 77
76runnerJobsRouter.post('/jobs/:jobUUID/accept', 78runnerJobsRouter.post('/jobs/:jobUUID/accept',
79 apiRateLimiter,
77 asyncMiddleware(runnerJobGetValidator), 80 asyncMiddleware(runnerJobGetValidator),
78 acceptRunnerJobValidator, 81 acceptRunnerJobValidator,
79 asyncMiddleware(getRunnerFromTokenValidator), 82 asyncMiddleware(getRunnerFromTokenValidator),
@@ -81,6 +84,7 @@ runnerJobsRouter.post('/jobs/:jobUUID/accept',
81) 84)
82 85
83runnerJobsRouter.post('/jobs/:jobUUID/abort', 86runnerJobsRouter.post('/jobs/:jobUUID/abort',
87 apiRateLimiter,
84 asyncMiddleware(jobOfRunnerGetValidator), 88 asyncMiddleware(jobOfRunnerGetValidator),
85 abortRunnerJobValidator, 89 abortRunnerJobValidator,
86 asyncMiddleware(abortRunnerJob) 90 asyncMiddleware(abortRunnerJob)
@@ -88,6 +92,7 @@ runnerJobsRouter.post('/jobs/:jobUUID/abort',
88 92
89runnerJobsRouter.post('/jobs/:jobUUID/update', 93runnerJobsRouter.post('/jobs/:jobUUID/update',
90 runnerJobUpdateVideoFiles, 94 runnerJobUpdateVideoFiles,
95 apiRateLimiter, // Has to be after multer middleware to parse runner token
91 asyncMiddleware(jobOfRunnerGetValidator), 96 asyncMiddleware(jobOfRunnerGetValidator),
92 updateRunnerJobValidator, 97 updateRunnerJobValidator,
93 asyncMiddleware(updateRunnerJobController) 98 asyncMiddleware(updateRunnerJobController)
@@ -101,6 +106,7 @@ runnerJobsRouter.post('/jobs/:jobUUID/error',
101 106
102runnerJobsRouter.post('/jobs/:jobUUID/success', 107runnerJobsRouter.post('/jobs/:jobUUID/success',
103 postRunnerJobSuccessVideoFiles, 108 postRunnerJobSuccessVideoFiles,
109 apiRateLimiter, // Has to be after multer middleware to parse runner token
104 asyncMiddleware(jobOfRunnerGetValidator), 110 asyncMiddleware(jobOfRunnerGetValidator),
105 successRunnerJobValidator, 111 successRunnerJobValidator,
106 asyncMiddleware(postRunnerJobSuccess) 112 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'
2import { logger, loggerTagsFactory } from '@server/helpers/logger' 2import { logger, loggerTagsFactory } from '@server/helpers/logger'
3import { generateRunnerToken } from '@server/helpers/token-generator' 3import { generateRunnerToken } from '@server/helpers/token-generator'
4import { 4import {
5 apiRateLimiter,
5 asyncMiddleware, 6 asyncMiddleware,
6 authenticate, 7 authenticate,
7 ensureUserHasRight, 8 ensureUserHasRight,
@@ -19,15 +20,18 @@ const lTags = loggerTagsFactory('api', 'runner')
19const manageRunnersRouter = express.Router() 20const manageRunnersRouter = express.Router()
20 21
21manageRunnersRouter.post('/register', 22manageRunnersRouter.post('/register',
23 apiRateLimiter,
22 asyncMiddleware(registerRunnerValidator), 24 asyncMiddleware(registerRunnerValidator),
23 asyncMiddleware(registerRunner) 25 asyncMiddleware(registerRunner)
24) 26)
25manageRunnersRouter.post('/unregister', 27manageRunnersRouter.post('/unregister',
28 apiRateLimiter,
26 asyncMiddleware(getRunnerFromTokenValidator), 29 asyncMiddleware(getRunnerFromTokenValidator),
27 asyncMiddleware(unregisterRunner) 30 asyncMiddleware(unregisterRunner)
28) 31)
29 32
30manageRunnersRouter.delete('/:runnerId', 33manageRunnersRouter.delete('/:runnerId',
34 apiRateLimiter,
31 authenticate, 35 authenticate,
32 ensureUserHasRight(UserRight.MANAGE_RUNNERS), 36 ensureUserHasRight(UserRight.MANAGE_RUNNERS),
33 asyncMiddleware(deleteRunnerValidator), 37 asyncMiddleware(deleteRunnerValidator),
@@ -35,6 +39,7 @@ manageRunnersRouter.delete('/:runnerId',
35) 39)
36 40
37manageRunnersRouter.get('/', 41manageRunnersRouter.get('/',
42 apiRateLimiter,
38 authenticate, 43 authenticate,
39 ensureUserHasRight(UserRight.MANAGE_RUNNERS), 44 ensureUserHasRight(UserRight.MANAGE_RUNNERS),
40 paginationValidator, 45 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 @@
1import express from 'express' 1import express from 'express'
2import { logger, loggerTagsFactory } from '@server/helpers/logger'
2import { generateRunnerRegistrationToken } from '@server/helpers/token-generator' 3import { generateRunnerRegistrationToken } from '@server/helpers/token-generator'
3import { 4import {
5 apiRateLimiter,
4 asyncMiddleware, 6 asyncMiddleware,
5 authenticate, 7 authenticate,
6 ensureUserHasRight, 8 ensureUserHasRight,
@@ -12,19 +14,20 @@ import {
12import { deleteRegistrationTokenValidator } from '@server/middlewares/validators/runners' 14import { deleteRegistrationTokenValidator } from '@server/middlewares/validators/runners'
13import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token' 15import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token'
14import { HttpStatusCode, ListRunnerRegistrationTokensQuery, UserRight } from '@shared/models' 16import { HttpStatusCode, ListRunnerRegistrationTokensQuery, UserRight } from '@shared/models'
15import { logger, loggerTagsFactory } from '@server/helpers/logger'
16 17
17const lTags = loggerTagsFactory('api', 'runner') 18const lTags = loggerTagsFactory('api', 'runner')
18 19
19const runnerRegistrationTokensRouter = express.Router() 20const runnerRegistrationTokensRouter = express.Router()
20 21
21runnerRegistrationTokensRouter.post('/registration-tokens/generate', 22runnerRegistrationTokensRouter.post('/registration-tokens/generate',
23 apiRateLimiter,
22 authenticate, 24 authenticate,
23 ensureUserHasRight(UserRight.MANAGE_RUNNERS), 25 ensureUserHasRight(UserRight.MANAGE_RUNNERS),
24 asyncMiddleware(generateRegistrationToken) 26 asyncMiddleware(generateRegistrationToken)
25) 27)
26 28
27runnerRegistrationTokensRouter.delete('/registration-tokens/:id', 29runnerRegistrationTokensRouter.delete('/registration-tokens/:id',
30 apiRateLimiter,
28 authenticate, 31 authenticate,
29 ensureUserHasRight(UserRight.MANAGE_RUNNERS), 32 ensureUserHasRight(UserRight.MANAGE_RUNNERS),
30 asyncMiddleware(deleteRegistrationTokenValidator), 33 asyncMiddleware(deleteRegistrationTokenValidator),
@@ -32,6 +35,7 @@ runnerRegistrationTokensRouter.delete('/registration-tokens/:id',
32) 35)
33 36
34runnerRegistrationTokensRouter.get('/registration-tokens', 37runnerRegistrationTokensRouter.get('/registration-tokens',
38 apiRateLimiter,
35 authenticate, 39 authenticate,
36 ensureUserHasRight(UserRight.MANAGE_RUNNERS), 40 ensureUserHasRight(UserRight.MANAGE_RUNNERS),
37 paginationValidator, 41 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 @@
1import express from 'express' 1import express from 'express'
2import { apiRateLimiter } from '@server/middlewares'
2import { searchChannelsRouter } from './search-video-channels' 3import { searchChannelsRouter } from './search-video-channels'
3import { searchPlaylistsRouter } from './search-video-playlists' 4import { searchPlaylistsRouter } from './search-video-playlists'
4import { searchVideosRouter } from './search-videos' 5import { searchVideosRouter } from './search-videos'
5 6
6const searchRouter = express.Router() 7const searchRouter = express.Router()
7 8
9searchRouter.use(apiRateLimiter)
10
8searchRouter.use('/', searchVideosRouter) 11searchRouter.use('/', searchVideosRouter)
9searchRouter.use('/', searchChannelsRouter) 12searchRouter.use('/', searchChannelsRouter)
10searchRouter.use('/', searchPlaylistsRouter) 13searchRouter.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 @@
1import express from 'express' 1import express from 'express'
2import { apiRateLimiter } from '@server/middlewares'
2import { contactRouter } from './contact' 3import { contactRouter } from './contact'
3import { debugRouter } from './debug' 4import { debugRouter } from './debug'
4import { serverFollowsRouter } from './follows' 5import { serverFollowsRouter } from './follows'
@@ -9,6 +10,8 @@ import { statsRouter } from './stats'
9 10
10const serverRouter = express.Router() 11const serverRouter = express.Router()
11 12
13serverRouter.use(apiRateLimiter)
14
12serverRouter.use('/', serverFollowsRouter) 15serverRouter.use('/', serverFollowsRouter)
13serverRouter.use('/', serverRedundancyRouter) 16serverRouter.use('/', serverRedundancyRouter)
14serverRouter.use('/', statsRouter) 17serverRouter.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'
15import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user' 15import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
16import { 16import {
17 adminUsersSortValidator, 17 adminUsersSortValidator,
18 apiRateLimiter,
18 asyncMiddleware, 19 asyncMiddleware,
19 asyncRetryTransactionMiddleware, 20 asyncRetryTransactionMiddleware,
20 authenticate, 21 authenticate,
@@ -50,6 +51,9 @@ import { twoFactorRouter } from './two-factor'
50const auditLogger = auditLoggerFactory('users') 51const auditLogger = auditLoggerFactory('users')
51 52
52const usersRouter = express.Router() 53const usersRouter = express.Router()
54
55usersRouter.use(apiRateLimiter)
56
53usersRouter.use('/', emailVerificationRouter) 57usersRouter.use('/', emailVerificationRouter)
54usersRouter.use('/', registrationsRouter) 58usersRouter.use('/', registrationsRouter)
55usersRouter.use('/', twoFactorRouter) 59usersRouter.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'
2import { auditLoggerFactory, getAuditIdFromRes, VideoChannelSyncAuditView } from '@server/helpers/audit-logger' 2import { auditLoggerFactory, getAuditIdFromRes, VideoChannelSyncAuditView } from '@server/helpers/audit-logger'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { 4import {
5 apiRateLimiter,
5 asyncMiddleware, 6 asyncMiddleware,
6 asyncRetryTransactionMiddleware, 7 asyncRetryTransactionMiddleware,
7 authenticate, 8 authenticate,
@@ -17,6 +18,8 @@ import { HttpStatusCode, VideoChannelSyncState } from '@shared/models'
17const videoChannelSyncRouter = express.Router() 18const videoChannelSyncRouter = express.Router()
18const auditLogger = auditLoggerFactory('channel-syncs') 19const auditLogger = auditLoggerFactory('channel-syncs')
19 20
21videoChannelSyncRouter.use(apiRateLimiter)
22
20videoChannelSyncRouter.post('/', 23videoChannelSyncRouter.post('/',
21 authenticate, 24 authenticate,
22 ensureSyncIsEnabled, 25 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'
19import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../lib/local-actor' 19import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../lib/local-actor'
20import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' 20import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
21import { 21import {
22 apiRateLimiter,
22 asyncMiddleware, 23 asyncMiddleware,
23 asyncRetryTransactionMiddleware, 24 asyncRetryTransactionMiddleware,
24 authenticate, 25 authenticate,
@@ -57,6 +58,8 @@ const reqBannerFile = createReqFiles([ 'bannerfile' ], MIMETYPES.IMAGE.MIMETYPE_
57 58
58const videoChannelRouter = express.Router() 59const videoChannelRouter = express.Router()
59 60
61videoChannelRouter.use(apiRateLimiter)
62
60videoChannelRouter.get('/', 63videoChannelRouter.get('/',
61 paginationValidator, 64 paginationValidator,
62 videoChannelsSortValidator, 65 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
25import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' 25import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url'
26import { updatePlaylistMiniatureFromExisting } from '../../lib/thumbnail' 26import { updatePlaylistMiniatureFromExisting } from '../../lib/thumbnail'
27import { 27import {
28 apiRateLimiter,
28 asyncMiddleware, 29 asyncMiddleware,
29 asyncRetryTransactionMiddleware, 30 asyncRetryTransactionMiddleware,
30 authenticate, 31 authenticate,
@@ -52,6 +53,8 @@ const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIM
52 53
53const videoPlaylistRouter = express.Router() 54const videoPlaylistRouter = express.Router()
54 55
56videoPlaylistRouter.use(apiRateLimiter)
57
55videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies) 58videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies)
56 59
57videoPlaylistRouter.get('/', 60videoPlaylistRouter.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'
15import { JobQueue } from '../../../lib/job-queue' 15import { JobQueue } from '../../../lib/job-queue'
16import { Hooks } from '../../../lib/plugins/hooks' 16import { Hooks } from '../../../lib/plugins/hooks'
17import { 17import {
18 apiRateLimiter,
18 asyncMiddleware, 19 asyncMiddleware,
19 asyncRetryTransactionMiddleware, 20 asyncRetryTransactionMiddleware,
20 authenticate, 21 authenticate,
@@ -50,6 +51,8 @@ import { viewRouter } from './view'
50const auditLogger = auditLoggerFactory('videos') 51const auditLogger = auditLoggerFactory('videos')
51const videosRouter = express.Router() 52const videosRouter = express.Router()
52 53
54videosRouter.use(apiRateLimiter)
55
53videosRouter.use('/', blacklistRouter) 56videosRouter.use('/', blacklistRouter)
54videosRouter.use('/', statsRouter) 57videosRouter.use('/', statsRouter)
55videosRouter.use('/', rateVideoRouter) 58videosRouter.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 @@
1import express from 'express' 1import express from 'express'
2import RateLimit, { Options as RateLimitHandlerOptions } from 'express-rate-limit' 2import RateLimit, { Options as RateLimitHandlerOptions } from 'express-rate-limit'
3import { CONFIG } from '@server/initializers/config'
3import { RunnerModel } from '@server/models/runner/runner' 4import { RunnerModel } from '@server/models/runner/runner'
4import { UserRole } from '@shared/models' 5import { UserRole } from '@shared/models'
5import { optionalAuthenticate } from './auth' 6import { optionalAuthenticate } from './auth'
@@ -39,6 +40,11 @@ export function buildRateLimiter (options: {
39 }) 40 })
40} 41}
41 42
43export const apiRateLimiter = buildRateLimiter({
44 windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS,
45 max: CONFIG.RATES_LIMIT.API.MAX
46})
47
42// --------------------------------------------------------------------------- 48// ---------------------------------------------------------------------------
43// Private 49// Private
44// --------------------------------------------------------------------------- 50// ---------------------------------------------------------------------------
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 {
14import { 14import {
15 cleanupTests, 15 cleanupTests,
16 createSingleServer, 16 createSingleServer,
17 makePostBodyRequest,
18 PeerTubeServer, 17 PeerTubeServer,
19 setAccessTokensToServers, 18 setAccessTokensToServers,
20 setDefaultVideoChannel, 19 setDefaultVideoChannel,
@@ -641,24 +640,47 @@ describe('Test runner common actions', function () {
641 }) 640 })
642 }) 641 })
643 642
644 it('Should rate limit an unknown runner', async function () { 643 it('Should rate limit an unknown runner, but not a registered one', async function () {
645 const path = '/api/v1/ping' 644 this.timeout(60000)
646 const fields = { runnerToken: 'toto' } 645
646 await server.videos.quickUpload({ name: 'video' })
647 await waitJobs([ server ])
648
649 const { job } = await server.runnerJobs.autoAccept({ runnerToken })
647 650
648 for (let i = 0; i < 20; i++) { 651 for (let i = 0; i < 20; i++) {
649 try { 652 try {
650 await makePostBodyRequest({ url: server.url, path, fields, expectedStatus: HttpStatusCode.OK_200 }) 653 await server.runnerJobs.request({ runnerToken })
654 await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid })
651 } catch {} 655 } catch {}
652 } 656 }
653 657
654 await makePostBodyRequest({ url: server.url, path, fields, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 }) 658 // Invalid
655 }) 659 {
660 await server.runnerJobs.request({ runnerToken: 'toto', expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 })
661 await server.runnerJobs.update({
662 runnerToken: 'toto',
663 jobToken: job.jobToken,
664 jobUUID: job.uuid,
665 expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429
666 })
667 }
656 668
657 it('Should not rate limit a registered runner', async function () { 669 // Not provided
658 const path = '/api/v1/ping' 670 {
671 await server.runnerJobs.request({ runnerToken: undefined, expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429 })
672 await server.runnerJobs.update({
673 runnerToken: undefined,
674 jobToken: job.jobToken,
675 jobUUID: job.uuid,
676 expectedStatus: HttpStatusCode.TOO_MANY_REQUESTS_429
677 })
678 }
659 679
660 for (let i = 0; i < 20; i++) { 680 // Registered
661 await makePostBodyRequest({ url: server.url, path, fields: { runnerToken }, expectedStatus: HttpStatusCode.OK_200 }) 681 {
682 await server.runnerJobs.request({ runnerToken })
683 await server.runnerJobs.update({ runnerToken, jobToken: job.jobToken, jobUUID: job.uuid })
662 } 684 }
663 }) 685 })
664 }) 686 })