abusesSortValidator,
abuseUpdateValidator,
addAbuseMessageValidator,
+ apiRateLimiter,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
const abuseRouter = express.Router()
+abuseRouter.use(apiRateLimiter)
+
abuseRouter.get('/',
openapiOperationDoc({ operationId: 'getAbuses' }),
authenticate,
import { JobQueue } from '../../lib/job-queue'
import { Hooks } from '../../lib/plugins/hooks'
import {
+ apiRateLimiter,
asyncMiddleware,
authenticate,
commonVideosFiltersValidator,
const accountsRouter = express.Router()
+accountsRouter.use(apiRateLimiter)
+
accountsRouter.get('/',
paginationValidator,
accountsSortValidator,
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,
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),
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('/',
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)
)
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'
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)
apiRouter.use('/custom-pages', customPageRouter)
apiRouter.use('/blocklist', blocklistRouter)
apiRouter.use('/runners', runnersRouter)
+
+// apiRouter.use(apiRateLimiter)
apiRouter.use('/ping', pong)
apiRouter.use('/*', badRequest)
import { isArray } from '../../helpers/custom-validators/misc'
import { JobQueue } from '../../lib/job-queue'
import {
+ apiRateLimiter,
asyncMiddleware,
authenticate,
ensureUserHasRight,
const jobsRouter = express.Router()
+jobsRouter.use(apiRateLimiter)
+
jobsRouter.post('/pause',
authenticate,
ensureUserHasRight(UserRight.MANAGE_JOBS),
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
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)
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,
import { listAvailablePluginsFromIndex } from '@server/lib/plugins/plugin-index'
import { PluginManager } from '@server/lib/plugins/plugin-manager'
import {
+ apiRateLimiter,
asyncMiddleware,
authenticate,
availablePluginsSortValidator,
const pluginRouter = express.Router()
+pluginRouter.use(apiRateLimiter)
+
pluginRouter.get('/available',
openapiOperationDoc({ operationId: 'getAvailablePlugins' }),
authenticate,
const runnersRouter = express.Router()
+// No api route limiter here, they are defined in child routers
+
runnersRouter.use('/', manageRunnersRouter)
runnersRouter.use('/', runnerJobsRouter)
runnersRouter.use('/', runnerJobFilesRouter)
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,
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,
import { sequelizeTypescript } from '@server/initializers/database'
import { getRunnerJobHandlerClass, updateLastRunnerContact } from '@server/lib/runners'
import {
+ apiRateLimiter,
asyncMiddleware,
authenticate,
ensureUserHasRight,
// ---------------------------------------------------------------------------
runnerJobsRouter.post('/jobs/request',
+ apiRateLimiter,
asyncMiddleware(getRunnerFromTokenValidator),
asyncMiddleware(requestRunnerJob)
)
runnerJobsRouter.post('/jobs/:jobUUID/accept',
+ apiRateLimiter,
asyncMiddleware(runnerJobGetValidator),
acceptRunnerJobValidator,
asyncMiddleware(getRunnerFromTokenValidator),
)
runnerJobsRouter.post('/jobs/:jobUUID/abort',
+ apiRateLimiter,
asyncMiddleware(jobOfRunnerGetValidator),
abortRunnerJobValidator,
asyncMiddleware(abortRunnerJob)
runnerJobsRouter.post('/jobs/:jobUUID/update',
runnerJobUpdateVideoFiles,
+ apiRateLimiter, // Has to be after multer middleware to parse runner token
asyncMiddleware(jobOfRunnerGetValidator),
updateRunnerJobValidator,
asyncMiddleware(updateRunnerJobController)
runnerJobsRouter.post('/jobs/:jobUUID/success',
postRunnerJobSuccessVideoFiles,
+ apiRateLimiter, // Has to be after multer middleware to parse runner token
asyncMiddleware(jobOfRunnerGetValidator),
successRunnerJobValidator,
asyncMiddleware(postRunnerJobSuccess)
import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { generateRunnerToken } from '@server/helpers/token-generator'
import {
+ apiRateLimiter,
asyncMiddleware,
authenticate,
ensureUserHasRight,
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),
)
manageRunnersRouter.get('/',
+ apiRateLimiter,
authenticate,
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
paginationValidator,
import express from 'express'
+import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { generateRunnerRegistrationToken } from '@server/helpers/token-generator'
import {
+ apiRateLimiter,
asyncMiddleware,
authenticate,
ensureUserHasRight,
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),
)
runnerRegistrationTokensRouter.get('/registration-tokens',
+ apiRateLimiter,
authenticate,
ensureUserHasRight(UserRight.MANAGE_RUNNERS),
paginationValidator,
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)
import express from 'express'
+import { apiRateLimiter } from '@server/middlewares'
import { contactRouter } from './contact'
import { debugRouter } from './debug'
import { serverFollowsRouter } from './follows'
const serverRouter = express.Router()
+serverRouter.use(apiRateLimiter)
+
serverRouter.use('/', serverFollowsRouter)
serverRouter.use('/', serverRedundancyRouter)
serverRouter.use('/', statsRouter)
import { buildUser, createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
import {
adminUsersSortValidator,
+ apiRateLimiter,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
const auditLogger = auditLoggerFactory('users')
const usersRouter = express.Router()
+
+usersRouter.use(apiRateLimiter)
+
usersRouter.use('/', emailVerificationRouter)
usersRouter.use('/', registrationsRouter)
usersRouter.use('/', twoFactorRouter)
import { auditLoggerFactory, getAuditIdFromRes, VideoChannelSyncAuditView } from '@server/helpers/audit-logger'
import { logger } from '@server/helpers/logger'
import {
+ apiRateLimiter,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
const videoChannelSyncRouter = express.Router()
const auditLogger = auditLoggerFactory('channel-syncs')
+videoChannelSyncRouter.use(apiRateLimiter)
+
videoChannelSyncRouter.post('/',
authenticate,
ensureSyncIsEnabled,
import { deleteLocalActorImageFile, updateLocalActorImageFiles } from '../../lib/local-actor'
import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
import {
+ apiRateLimiter,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
const videoChannelRouter = express.Router()
+videoChannelRouter.use(apiRateLimiter)
+
videoChannelRouter.get('/',
paginationValidator,
videoChannelsSortValidator,
import { getLocalVideoPlaylistActivityPubUrl, getLocalVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url'
import { updatePlaylistMiniatureFromExisting } from '../../lib/thumbnail'
import {
+ apiRateLimiter,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
const videoPlaylistRouter = express.Router()
+videoPlaylistRouter.use(apiRateLimiter)
+
videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies)
videoPlaylistRouter.get('/',
import { JobQueue } from '../../../lib/job-queue'
import { Hooks } from '../../../lib/plugins/hooks'
import {
+ apiRateLimiter,
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
const auditLogger = auditLoggerFactory('videos')
const videosRouter = express.Router()
+videosRouter.use(apiRateLimiter)
+
videosRouter.use('/', blacklistRouter)
videosRouter.use('/', statsRouter)
videosRouter.use('/', rateVideoRouter)
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'
})
}
+export const apiRateLimiter = buildRateLimiter({
+ windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS,
+ max: CONFIG.RATES_LIMIT.API.MAX
+})
+
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
import {
cleanupTests,
createSingleServer,
- makePostBodyRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
})
})
- 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 })
}
})
})