Add more rate limits

This commit is contained in:
Chocobozzz 2023-07-25 15:18:10 +02:00
parent 9901c8d690
commit 97583d0023
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
23 changed files with 216 additions and 37 deletions

View File

@ -36,6 +36,26 @@ rates_limit:
# 10 attempts in 10 min # 10 attempts in 10 min
window: 10 minutes window: 10 minutes
max: 10 max: 10
plugins:
# 500 attempts in 10 seconds (we also serve plugin static files)
window: 10 seconds
max: 500
well_known:
# 200 attempts in 10 seconds
window: 10 seconds
max: 200
feeds:
# 50 attempts in 10 seconds
window: 10 seconds
max: 50
activity_pub:
# 500 attempts in 10 seconds (we can have many AP requests)
window: 10 seconds
max: 500
client: # HTML files generated by PeerTube
# 500 attempts in 10 seconds (to not break crawlers)
window: 10 seconds
max: 500
oauth2: oauth2:
token_lifetime: token_lifetime:

View File

@ -34,6 +34,26 @@ rates_limit:
# 10 attempts in 10 min # 10 attempts in 10 min
window: 10 minutes window: 10 minutes
max: 10 max: 10
plugins:
# 500 attempts in 10 seconds (we also serve plugin static files)
window: 10 seconds
max: 500
well_known:
# 200 attempts in 10 seconds
window: 10 seconds
max: 200
feeds:
# 50 attempts in 10 seconds
window: 10 seconds
max: 50
activity_pub:
# 500 attempts in 10 seconds (we can have many AP requests)
window: 10 seconds
max: 500
client: # HTML files generated by PeerTube
# 500 attempts in 10 seconds (to not break crawlers)
window: 10 seconds
max: 500
oauth2: oauth2:
token_lifetime: token_lifetime:

View File

@ -115,7 +115,7 @@ import {
pluginsRouter, pluginsRouter,
trackerRouter, trackerRouter,
createWebsocketTrackerServer, createWebsocketTrackerServer,
botsRouter, sitemapRouter,
downloadRouter downloadRouter
} from './server/controllers' } from './server/controllers'
import { advertiseDoNotTrack } from './server/middlewares/dnt' import { advertiseDoNotTrack } from './server/middlewares/dnt'
@ -222,9 +222,7 @@ OpenTelemetryMetrics.Instance.init(app)
// ----------- Views, routes and static files ----------- // ----------- Views, routes and static files -----------
// API app.use('/api/' + API_VERSION, apiRouter)
const apiRoute = '/api/' + API_VERSION
app.use(apiRoute, apiRouter)
// Services (oembed...) // Services (oembed...)
app.use('/services', servicesRouter) app.use('/services', servicesRouter)
@ -235,7 +233,7 @@ app.use('/', pluginsRouter)
app.use('/', activityPubRouter) app.use('/', activityPubRouter)
app.use('/', feedsRouter) app.use('/', feedsRouter)
app.use('/', trackerRouter) app.use('/', trackerRouter)
app.use('/', botsRouter) app.use('/', sitemapRouter)
// Static files // Static files
app.use('/', staticRouter) app.use('/', staticRouter)

View File

@ -19,6 +19,7 @@ import {
getLocalVideoSharesActivityPubUrl getLocalVideoSharesActivityPubUrl
} from '../../lib/activitypub/url' } from '../../lib/activitypub/url'
import { import {
activityPubRateLimiter,
asyncMiddleware, asyncMiddleware,
ensureIsLocalChannel, ensureIsLocalChannel,
executeIfActivityPub, executeIfActivityPub,
@ -47,32 +48,38 @@ activityPubClientRouter.use(cors())
activityPubClientRouter.get( activityPubClientRouter.get(
[ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ], [ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ],
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(localAccountValidator), asyncMiddleware(localAccountValidator),
asyncMiddleware(accountController) asyncMiddleware(accountController)
) )
activityPubClientRouter.get('/accounts?/:name/followers', activityPubClientRouter.get('/accounts?/:name/followers',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(localAccountValidator), asyncMiddleware(localAccountValidator),
asyncMiddleware(accountFollowersController) asyncMiddleware(accountFollowersController)
) )
activityPubClientRouter.get('/accounts?/:name/following', activityPubClientRouter.get('/accounts?/:name/following',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(localAccountValidator), asyncMiddleware(localAccountValidator),
asyncMiddleware(accountFollowingController) asyncMiddleware(accountFollowingController)
) )
activityPubClientRouter.get('/accounts?/:name/playlists', activityPubClientRouter.get('/accounts?/:name/playlists',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(localAccountValidator), asyncMiddleware(localAccountValidator),
asyncMiddleware(accountPlaylistsController) asyncMiddleware(accountPlaylistsController)
) )
activityPubClientRouter.get('/accounts?/:name/likes/:videoId', activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
asyncMiddleware(getAccountVideoRateValidatorFactory('like')), asyncMiddleware(getAccountVideoRateValidatorFactory('like')),
asyncMiddleware(getAccountVideoRateFactory('like')) asyncMiddleware(getAccountVideoRateFactory('like'))
) )
activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')), asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')),
asyncMiddleware(getAccountVideoRateFactory('dislike')) asyncMiddleware(getAccountVideoRateFactory('dislike'))
@ -81,47 +88,56 @@ activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
activityPubClientRouter.get( activityPubClientRouter.get(
[ '/videos/watch/:id', '/w/:id' ], [ '/videos/watch/:id', '/w/:id' ],
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
asyncMiddleware(videosCustomGetValidator('all')), asyncMiddleware(videosCustomGetValidator('all')),
asyncMiddleware(videoController) asyncMiddleware(videoController)
) )
activityPubClientRouter.get('/videos/watch/:id/activity', activityPubClientRouter.get('/videos/watch/:id/activity',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('all')), asyncMiddleware(videosCustomGetValidator('all')),
asyncMiddleware(videoController) asyncMiddleware(videoController)
) )
activityPubClientRouter.get('/videos/watch/:id/announces', activityPubClientRouter.get('/videos/watch/:id/announces',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videoAnnouncesController) asyncMiddleware(videoAnnouncesController)
) )
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosShareValidator), asyncMiddleware(videosShareValidator),
asyncMiddleware(videoAnnounceController) asyncMiddleware(videoAnnounceController)
) )
activityPubClientRouter.get('/videos/watch/:id/likes', activityPubClientRouter.get('/videos/watch/:id/likes',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videoLikesController) asyncMiddleware(videoLikesController)
) )
activityPubClientRouter.get('/videos/watch/:id/dislikes', activityPubClientRouter.get('/videos/watch/:id/dislikes',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videoDislikesController) asyncMiddleware(videoDislikesController)
) )
activityPubClientRouter.get('/videos/watch/:id/comments', activityPubClientRouter.get('/videos/watch/:id/comments',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videoCommentsController) asyncMiddleware(videoCommentsController)
) )
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoCommentGetValidator), asyncMiddleware(videoCommentGetValidator),
asyncMiddleware(videoCommentController) asyncMiddleware(videoCommentController)
) )
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoCommentGetValidator), asyncMiddleware(videoCommentGetValidator),
asyncMiddleware(videoCommentController) asyncMiddleware(videoCommentController)
) )
@ -129,24 +145,28 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
activityPubClientRouter.get( activityPubClientRouter.get(
[ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ], [ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ],
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoChannelsNameWithHostValidator), asyncMiddleware(videoChannelsNameWithHostValidator),
ensureIsLocalChannel, ensureIsLocalChannel,
asyncMiddleware(videoChannelController) asyncMiddleware(videoChannelController)
) )
activityPubClientRouter.get('/video-channels/:nameWithHost/followers', activityPubClientRouter.get('/video-channels/:nameWithHost/followers',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoChannelsNameWithHostValidator), asyncMiddleware(videoChannelsNameWithHostValidator),
ensureIsLocalChannel, ensureIsLocalChannel,
asyncMiddleware(videoChannelFollowersController) asyncMiddleware(videoChannelFollowersController)
) )
activityPubClientRouter.get('/video-channels/:nameWithHost/following', activityPubClientRouter.get('/video-channels/:nameWithHost/following',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoChannelsNameWithHostValidator), asyncMiddleware(videoChannelsNameWithHostValidator),
ensureIsLocalChannel, ensureIsLocalChannel,
asyncMiddleware(videoChannelFollowingController) asyncMiddleware(videoChannelFollowingController)
) )
activityPubClientRouter.get('/video-channels/:nameWithHost/playlists', activityPubClientRouter.get('/video-channels/:nameWithHost/playlists',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoChannelsNameWithHostValidator), asyncMiddleware(videoChannelsNameWithHostValidator),
ensureIsLocalChannel, ensureIsLocalChannel,
asyncMiddleware(videoChannelPlaylistsController) asyncMiddleware(videoChannelPlaylistsController)
@ -154,11 +174,13 @@ activityPubClientRouter.get('/video-channels/:nameWithHost/playlists',
activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoFileRedundancyGetValidator), asyncMiddleware(videoFileRedundancyGetValidator),
asyncMiddleware(videoRedundancyController) asyncMiddleware(videoRedundancyController)
) )
activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId', activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoPlaylistRedundancyGetValidator), asyncMiddleware(videoPlaylistRedundancyGetValidator),
asyncMiddleware(videoRedundancyController) asyncMiddleware(videoRedundancyController)
) )
@ -166,17 +188,20 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT
activityPubClientRouter.get( activityPubClientRouter.get(
[ '/video-playlists/:playlistId', '/videos/watch/playlist/:playlistId', '/w/p/:playlistId' ], [ '/video-playlists/:playlistId', '/videos/watch/playlist/:playlistId', '/w/p/:playlistId' ],
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoPlaylistsGetValidator('all')), asyncMiddleware(videoPlaylistsGetValidator('all')),
asyncMiddleware(videoPlaylistController) asyncMiddleware(videoPlaylistController)
) )
activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId', activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoPlaylistElementAPGetValidator), asyncMiddleware(videoPlaylistElementAPGetValidator),
asyncMiddleware(videoPlaylistElementController) asyncMiddleware(videoPlaylistElementController)
) )
activityPubClientRouter.get('/videos/local-viewer/:localViewerId', activityPubClientRouter.get('/videos/local-viewer/:localViewerId',
executeIfActivityPub, executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(getVideoLocalViewerValidator), asyncMiddleware(getVideoLocalViewerValidator),
asyncMiddleware(getVideoLocalViewerController) asyncMiddleware(getVideoLocalViewerController)
) )

View File

@ -5,6 +5,7 @@ import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity' import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { import {
activityPubRateLimiter,
asyncMiddleware, asyncMiddleware,
checkSignature, checkSignature,
ensureIsLocalChannel, ensureIsLocalChannel,
@ -17,6 +18,7 @@ import { activityPubValidator } from '../../middlewares/validators/activitypub/a
const inboxRouter = express.Router() const inboxRouter = express.Router()
inboxRouter.post('/inbox', inboxRouter.post('/inbox',
activityPubRateLimiter,
signatureValidator, signatureValidator,
asyncMiddleware(checkSignature), asyncMiddleware(checkSignature),
asyncMiddleware(activityPubValidator), asyncMiddleware(activityPubValidator),
@ -24,13 +26,16 @@ inboxRouter.post('/inbox',
) )
inboxRouter.post('/accounts/:name/inbox', inboxRouter.post('/accounts/:name/inbox',
activityPubRateLimiter,
signatureValidator, signatureValidator,
asyncMiddleware(checkSignature), asyncMiddleware(checkSignature),
asyncMiddleware(localAccountValidator), asyncMiddleware(localAccountValidator),
asyncMiddleware(activityPubValidator), asyncMiddleware(activityPubValidator),
inboxController inboxController
) )
inboxRouter.post('/video-channels/:nameWithHost/inbox', inboxRouter.post('/video-channels/:nameWithHost/inbox',
activityPubRateLimiter,
signatureValidator, signatureValidator,
asyncMiddleware(checkSignature), asyncMiddleware(checkSignature),
asyncMiddleware(videoChannelsNameWithHostValidator), asyncMiddleware(videoChannelsNameWithHostValidator),

View File

@ -1,4 +1,5 @@
import express from 'express' import express from 'express'
import { activityPubClientRouter } from './client' import { activityPubClientRouter } from './client'
import { inboxRouter } from './inbox' import { inboxRouter } from './inbox'
import { outboxRouter } from './outbox' import { outboxRouter } from './outbox'

View File

@ -7,7 +7,13 @@ import { VideoPrivacy } from '../../../shared/models/videos'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { buildAudience } from '../../lib/activitypub/audience' import { buildAudience } from '../../lib/activitypub/audience'
import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send'
import { asyncMiddleware, ensureIsLocalChannel, localAccountValidator, videoChannelsNameWithHostValidator } from '../../middlewares' import {
activityPubRateLimiter,
asyncMiddleware,
ensureIsLocalChannel,
localAccountValidator,
videoChannelsNameWithHostValidator
} from '../../middlewares'
import { apPaginationValidator } from '../../middlewares/validators/activitypub' import { apPaginationValidator } from '../../middlewares/validators/activitypub'
import { VideoModel } from '../../models/video/video' import { VideoModel } from '../../models/video/video'
import { activityPubResponse } from './utils' import { activityPubResponse } from './utils'
@ -15,12 +21,14 @@ import { activityPubResponse } from './utils'
const outboxRouter = express.Router() const outboxRouter = express.Router()
outboxRouter.get('/accounts/:name/outbox', outboxRouter.get('/accounts/:name/outbox',
activityPubRateLimiter,
apPaginationValidator, apPaginationValidator,
localAccountValidator, localAccountValidator,
asyncMiddleware(outboxController) asyncMiddleware(outboxController)
) )
outboxRouter.get('/video-channels/:nameWithHost/outbox', outboxRouter.get('/video-channels/:nameWithHost/outbox',
activityPubRateLimiter,
apPaginationValidator, apPaginationValidator,
asyncMiddleware(videoChannelsNameWithHostValidator), asyncMiddleware(videoChannelsNameWithHostValidator),
ensureIsLocalChannel, ensureIsLocalChannel,

View File

@ -5,27 +5,53 @@ import { join } from 'path'
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 { Hooks } from '@server/lib/plugins/hooks' import { Hooks } from '@server/lib/plugins/hooks'
import { root } from '@shared/core-utils'
import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n'
import { HttpStatusCode } from '@shared/models' import { HttpStatusCode } from '@shared/models'
import { root } from '@shared/core-utils'
import { STATIC_MAX_AGE } from '../initializers/constants' import { STATIC_MAX_AGE } from '../initializers/constants'
import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html'
import { asyncMiddleware, embedCSP } from '../middlewares' import { asyncMiddleware, buildRateLimiter, embedCSP } from '../middlewares'
const clientsRouter = express.Router() const clientsRouter = express.Router()
const clientsRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.CLIENT.WINDOW_MS,
max: CONFIG.RATES_LIMIT.CLIENT.MAX
})
const distPath = join(root(), 'client', 'dist') const distPath = join(root(), 'client', 'dist')
const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html') const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
// Special route that add OpenGraph and oEmbed tags // Special route that add OpenGraph and oEmbed tags
// Do not use a template engine for a so little thing // Do not use a template engine for a so little thing
clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], asyncMiddleware(generateWatchPlaylistHtmlPage)) clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ],
clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], asyncMiddleware(generateWatchHtmlPage)) clientsRateLimiter,
clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage)) asyncMiddleware(generateWatchPlaylistHtmlPage)
clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage)) )
clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage))
clientsRouter.use([ '/w/:id', '/videos/watch/:id' ],
clientsRateLimiter,
asyncMiddleware(generateWatchHtmlPage)
)
clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ],
clientsRateLimiter,
asyncMiddleware(generateAccountHtmlPage)
)
clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ],
clientsRateLimiter,
asyncMiddleware(generateVideoChannelHtmlPage)
)
clientsRouter.use('/@:nameWithHost',
clientsRateLimiter,
asyncMiddleware(generateActorHtmlPage)
)
const embedMiddlewares = [ const embedMiddlewares = [
clientsRateLimiter,
CONFIG.CSP.ENABLED CONFIG.CSP.ENABLED
? embedCSP ? embedCSP
: (req: express.Request, res: express.Response, next: express.NextFunction) => next(), : (req: express.Request, res: express.Response, next: express.NextFunction) => next(),
@ -48,11 +74,11 @@ clientsRouter.use('/video-playlists/embed', ...embedMiddlewares)
const testEmbedController = (req: express.Request, res: express.Response) => res.sendFile(testEmbedPath) const testEmbedController = (req: express.Request, res: express.Response) => res.sendFile(testEmbedPath)
clientsRouter.use('/videos/test-embed', testEmbedController) clientsRouter.use('/videos/test-embed', clientsRateLimiter, testEmbedController)
clientsRouter.use('/video-playlists/test-embed', testEmbedController) clientsRouter.use('/video-playlists/test-embed', clientsRateLimiter, testEmbedController)
// Dynamic PWA manifest // Dynamic PWA manifest
clientsRouter.get('/manifest.webmanifest', asyncMiddleware(generateManifest)) clientsRouter.get('/manifest.webmanifest', clientsRateLimiter, asyncMiddleware(generateManifest))
// Static client overrides // Static client overrides
// Must be consistent with static client overrides redirections in /support/nginx/peertube // Must be consistent with static client overrides redirections in /support/nginx/peertube
@ -88,7 +114,10 @@ clientsRouter.use('/client/*', (req: express.Request, res: express.Response) =>
// Always serve index client page (the client is a single page application, let it handle routing) // Always serve index client page (the client is a single page application, let it handle routing)
// Try to provide the right language index.html // Try to provide the right language index.html
clientsRouter.use('/(:language)?', asyncMiddleware(serveIndexHTML)) clientsRouter.use('/(:language)?',
clientsRateLimiter,
asyncMiddleware(serveIndexHTML)
)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -23,7 +23,7 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
commentFeedsRouter.get('/feeds/video-comments.:format', commentFeedsRouter.get('/video-comments.:format',
feedsFormatValidator, feedsFormatValidator,
setFeedFormatContentType, setFeedFormatContentType,
cacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), cacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS),

View File

@ -1,13 +1,22 @@
import express from 'express' import express from 'express'
import { CONFIG } from '@server/initializers/config'
import { buildRateLimiter } from '@server/middlewares'
import { commentFeedsRouter } from './comment-feeds' import { commentFeedsRouter } from './comment-feeds'
import { videoFeedsRouter } from './video-feeds' import { videoFeedsRouter } from './video-feeds'
import { videoPodcastFeedsRouter } from './video-podcast-feeds' import { videoPodcastFeedsRouter } from './video-podcast-feeds'
const feedsRouter = express.Router() const feedsRouter = express.Router()
feedsRouter.use('/', commentFeedsRouter) const feedsRateLimiter = buildRateLimiter({
feedsRouter.use('/', videoFeedsRouter) windowMs: CONFIG.RATES_LIMIT.FEEDS.WINDOW_MS,
feedsRouter.use('/', videoPodcastFeedsRouter) max: CONFIG.RATES_LIMIT.FEEDS.MAX
})
feedsRouter.use('/feeds', feedsRateLimiter)
feedsRouter.use('/feeds', commentFeedsRouter)
feedsRouter.use('/feeds', videoFeedsRouter)
feedsRouter.use('/feeds', videoPodcastFeedsRouter)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -26,7 +26,7 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
videoFeedsRouter.get('/feeds/videos.:format', videoFeedsRouter.get('/videos.:format',
videosSortValidator, videosSortValidator,
setDefaultVideosSort, setDefaultVideosSort,
feedsFormatValidator, feedsFormatValidator,
@ -37,7 +37,7 @@ videoFeedsRouter.get('/feeds/videos.:format',
asyncMiddleware(generateVideoFeed) asyncMiddleware(generateVideoFeed)
) )
videoFeedsRouter.get('/feeds/subscriptions.:format', videoFeedsRouter.get('/subscriptions.:format',
videosSortValidator, videosSortValidator,
setDefaultVideosSort, setDefaultVideosSort,
feedsFormatValidator, feedsFormatValidator,

View File

@ -40,7 +40,7 @@ for (const event of ([ 'channel-updated', 'channel-deleted' ] as const)) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
videoPodcastFeedsRouter.get('/feeds/podcast/videos.xml', videoPodcastFeedsRouter.get('/podcast/videos.xml',
setFeedPodcastContentType, setFeedPodcastContentType,
videoFeedsPodcastSetCacheKey, videoFeedsPodcastSetCacheKey,
podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS),

View File

@ -1,6 +1,6 @@
export * from './activitypub' export * from './activitypub'
export * from './api' export * from './api'
export * from './bots' export * from './sitemap'
export * from './client' export * from './client'
export * from './download' export * from './download'
export * from './feeds' export * from './feeds'

View File

@ -7,7 +7,7 @@ import { HttpStatusCode } from '@shared/models'
import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model' import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION, ROUTE_CACHE_LIFETIME } from '../initializers/constants' import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION, ROUTE_CACHE_LIFETIME } from '../initializers/constants'
import { getThemeOrDefault } from '../lib/plugins/theme-utils' import { getThemeOrDefault } from '../lib/plugins/theme-utils'
import { asyncMiddleware } from '../middlewares' import { apiRateLimiter, asyncMiddleware } from '../middlewares'
import { cacheRoute } from '../middlewares/cache/cache' import { cacheRoute } from '../middlewares/cache/cache'
import { UserModel } from '../models/user/user' import { UserModel } from '../models/user/user'
import { VideoModel } from '../models/video/video' import { VideoModel } from '../models/video/video'
@ -18,12 +18,14 @@ const miscRouter = express.Router()
miscRouter.use(cors()) miscRouter.use(cors())
miscRouter.use('/nodeinfo/:version.json', miscRouter.use('/nodeinfo/:version.json',
apiRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO), cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
asyncMiddleware(generateNodeinfo) asyncMiddleware(generateNodeinfo)
) )
// robots.txt service // robots.txt service
miscRouter.get('/robots.txt', miscRouter.get('/robots.txt',
apiRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS), cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS),
(_, res: express.Response) => { (_, res: express.Response) => {
res.type('text/plain') res.type('text/plain')
@ -33,12 +35,14 @@ miscRouter.get('/robots.txt',
) )
miscRouter.all('/teapot', miscRouter.all('/teapot',
apiRateLimiter,
getCup, getCup,
asyncMiddleware(serveIndexHTML) asyncMiddleware(serveIndexHTML)
) )
// security.txt service // security.txt service
miscRouter.get('/security.txt', miscRouter.get('/security.txt',
apiRateLimiter,
(_, res: express.Response) => { (_, res: express.Response) => {
return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt') return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt')
} }

View File

@ -1,6 +1,8 @@
import express from 'express' import express from 'express'
import { join } from 'path' import { join } from 'path'
import { logger } from '@server/helpers/logger' import { logger } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config'
import { buildRateLimiter } from '@server/middlewares'
import { optionalAuthenticate } from '@server/middlewares/auth' import { optionalAuthenticate } from '@server/middlewares/auth'
import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n' import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n'
import { HttpStatusCode } from '../../shared/models/http/http-error-codes' import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
@ -18,57 +20,72 @@ const sendFileOptions = {
const pluginsRouter = express.Router() const pluginsRouter = express.Router()
const pluginsRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.PLUGINS.WINDOW_MS,
max: CONFIG.RATES_LIMIT.PLUGINS.MAX
})
pluginsRouter.get('/plugins/global.css', pluginsRouter.get('/plugins/global.css',
pluginsRateLimiter,
servePluginGlobalCSS servePluginGlobalCSS
) )
pluginsRouter.get('/plugins/translations/:locale.json', pluginsRouter.get('/plugins/translations/:locale.json',
pluginsRateLimiter,
getPluginTranslations getPluginTranslations
) )
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName', pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName',
pluginsRateLimiter,
getPluginValidator(PluginType.PLUGIN), getPluginValidator(PluginType.PLUGIN),
getExternalAuthValidator, getExternalAuthValidator,
handleAuthInPlugin handleAuthInPlugin
) )
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
pluginsRateLimiter,
getPluginValidator(PluginType.PLUGIN), getPluginValidator(PluginType.PLUGIN),
pluginStaticDirectoryValidator, pluginStaticDirectoryValidator,
servePluginStaticDirectory servePluginStaticDirectory
) )
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)',
pluginsRateLimiter,
getPluginValidator(PluginType.PLUGIN), getPluginValidator(PluginType.PLUGIN),
pluginStaticDirectoryValidator, pluginStaticDirectoryValidator,
servePluginClientScripts servePluginClientScripts
) )
pluginsRouter.use('/plugins/:pluginName/router', pluginsRouter.use('/plugins/:pluginName/router',
pluginsRateLimiter,
getPluginValidator(PluginType.PLUGIN, false), getPluginValidator(PluginType.PLUGIN, false),
optionalAuthenticate, optionalAuthenticate,
servePluginCustomRoutes servePluginCustomRoutes
) )
pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router', pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router',
pluginsRateLimiter,
getPluginValidator(PluginType.PLUGIN), getPluginValidator(PluginType.PLUGIN),
optionalAuthenticate, optionalAuthenticate,
servePluginCustomRoutes servePluginCustomRoutes
) )
pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)', pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
pluginsRateLimiter,
getPluginValidator(PluginType.THEME), getPluginValidator(PluginType.THEME),
pluginStaticDirectoryValidator, pluginStaticDirectoryValidator,
servePluginStaticDirectory servePluginStaticDirectory
) )
pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)',
pluginsRateLimiter,
getPluginValidator(PluginType.THEME), getPluginValidator(PluginType.THEME),
pluginStaticDirectoryValidator, pluginStaticDirectoryValidator,
servePluginClientScripts servePluginClientScripts
) )
pluginsRouter.get('/themes/:themeName/:themeVersion/css/:staticEndpoint(*)', pluginsRouter.get('/themes/:themeName/:themeVersion/css/:staticEndpoint(*)',
pluginsRateLimiter,
serveThemeCSSValidator, serveThemeCSSValidator,
serveThemeCSSDirectory serveThemeCSSDirectory
) )

View File

@ -2,17 +2,19 @@ import express from 'express'
import { MChannelSummary } from '@server/types/models' import { MChannelSummary } from '@server/types/models'
import { escapeHTML } from '@shared/core-utils/renderer' import { escapeHTML } from '@shared/core-utils/renderer'
import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
import { asyncMiddleware, oembedValidator } from '../middlewares' import { apiRateLimiter, asyncMiddleware, oembedValidator } from '../middlewares'
import { accountNameWithHostGetValidator } from '../middlewares/validators' import { accountNameWithHostGetValidator } from '../middlewares/validators'
import { forceNumber } from '@shared/core-utils' import { forceNumber } from '@shared/core-utils'
const servicesRouter = express.Router() const servicesRouter = express.Router()
servicesRouter.use('/oembed', servicesRouter.use('/oembed',
apiRateLimiter,
asyncMiddleware(oembedValidator), asyncMiddleware(oembedValidator),
generateOEmbed generateOEmbed
) )
servicesRouter.use('/redirect/accounts/:accountName', servicesRouter.use('/redirect/accounts/:accountName',
apiRateLimiter,
asyncMiddleware(accountNameWithHostGetValidator), asyncMiddleware(accountNameWithHostGetValidator),
redirectToAccountUrl redirectToAccountUrl
) )

View File

@ -5,17 +5,16 @@ import { logger } from '@server/helpers/logger'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { buildNSFWFilter } from '../helpers/express-utils' import { buildNSFWFilter } from '../helpers/express-utils'
import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
import { asyncMiddleware } from '../middlewares' import { apiRateLimiter, asyncMiddleware } from '../middlewares'
import { cacheRoute } from '../middlewares/cache/cache' import { cacheRoute } from '../middlewares/cache/cache'
import { AccountModel } from '../models/account/account' import { AccountModel } from '../models/account/account'
import { VideoModel } from '../models/video/video' import { VideoModel } from '../models/video/video'
import { VideoChannelModel } from '../models/video/video-channel' import { VideoChannelModel } from '../models/video/video-channel'
const botsRouter = express.Router() const sitemapRouter = express.Router()
// Special route that add OpenGraph and oEmbed tags sitemapRouter.use('/sitemap.xml',
// Do not use a template engine for a so little thing apiRateLimiter,
botsRouter.use('/sitemap.xml',
cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP), cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP),
asyncMiddleware(getSitemap) asyncMiddleware(getSitemap)
) )
@ -23,7 +22,7 @@ botsRouter.use('/sitemap.xml',
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
botsRouter sitemapRouter
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
import cors from 'cors' import cors from 'cors'
import express from 'express' import express from 'express'
import { join } from 'path' import { join } from 'path'
import { asyncMiddleware, handleStaticError, webfingerValidator } from '@server/middlewares' import { asyncMiddleware, buildRateLimiter, handleStaticError, webfingerValidator } from '@server/middlewares'
import { root } from '@shared/core-utils' import { root } from '@shared/core-utils'
import { CONFIG } from '../initializers/config' import { CONFIG } from '../initializers/config'
import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
@ -9,14 +9,21 @@ import { cacheRoute } from '../middlewares/cache/cache'
const wellKnownRouter = express.Router() const wellKnownRouter = express.Router()
const wellKnownRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.WELL_KNOWN.WINDOW_MS,
max: CONFIG.RATES_LIMIT.WELL_KNOWN.MAX
})
wellKnownRouter.use(cors()) wellKnownRouter.use(cors())
wellKnownRouter.get('/.well-known/webfinger', wellKnownRouter.get('/.well-known/webfinger',
wellKnownRateLimiter,
asyncMiddleware(webfingerValidator), asyncMiddleware(webfingerValidator),
webfingerController webfingerController
) )
wellKnownRouter.get('/.well-known/security.txt', wellKnownRouter.get('/.well-known/security.txt',
wellKnownRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT), cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT),
(_, res: express.Response) => { (_, res: express.Response) => {
res.type('text/plain') res.type('text/plain')
@ -26,6 +33,7 @@ wellKnownRouter.get('/.well-known/security.txt',
// nodeinfo service // nodeinfo service
wellKnownRouter.use('/.well-known/nodeinfo', wellKnownRouter.use('/.well-known/nodeinfo',
wellKnownRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO), cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
(_, res: express.Response) => { (_, res: express.Response) => {
return res.json({ return res.json({
@ -41,6 +49,7 @@ wellKnownRouter.use('/.well-known/nodeinfo',
// dnt-policy.txt service (see https://www.eff.org/dnt-policy) // dnt-policy.txt service (see https://www.eff.org/dnt-policy)
wellKnownRouter.use('/.well-known/dnt-policy.txt', wellKnownRouter.use('/.well-known/dnt-policy.txt',
wellKnownRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY), cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY),
(_, res: express.Response) => { (_, res: express.Response) => {
res.type('text/plain') res.type('text/plain')
@ -51,18 +60,21 @@ wellKnownRouter.use('/.well-known/dnt-policy.txt',
// dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource) // dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource)
wellKnownRouter.use('/.well-known/dnt/', wellKnownRouter.use('/.well-known/dnt/',
wellKnownRateLimiter,
(_, res: express.Response) => { (_, res: express.Response) => {
res.json({ tracking: 'N' }) res.json({ tracking: 'N' })
} }
) )
wellKnownRouter.use('/.well-known/change-password', wellKnownRouter.use('/.well-known/change-password',
wellKnownRateLimiter,
(_, res: express.Response) => { (_, res: express.Response) => {
res.redirect('/my-account/settings') res.redirect('/my-account/settings')
} }
) )
wellKnownRouter.use('/.well-known/host-meta', wellKnownRouter.use('/.well-known/host-meta',
wellKnownRateLimiter,
(_, res: express.Response) => { (_, res: express.Response) => {
res.type('application/xml') res.type('application/xml')
@ -76,6 +88,7 @@ wellKnownRouter.use('/.well-known/host-meta',
) )
wellKnownRouter.use('/.well-known/', wellKnownRouter.use('/.well-known/',
wellKnownRateLimiter,
cacheRoute(ROUTE_CACHE_LIFETIME.WELL_KNOWN), cacheRoute(ROUTE_CACHE_LIFETIME.WELL_KNOWN),
express.static(CONFIG.STORAGE.WELL_KNOWN_DIR, { fallthrough: false }), express.static(CONFIG.STORAGE.WELL_KNOWN_DIR, { fallthrough: false }),
handleStaticError handleStaticError

View File

@ -56,7 +56,11 @@ function checkMissedConfig () {
'followers.instance.enabled', 'followers.instance.manual_approval', 'followers.instance.enabled', 'followers.instance.manual_approval',
'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces',
'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration',
'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', 'rates_limit.api.window', 'rates_limit.api.max', 'rates_limit.login.window', 'rates_limit.login.max',
'rates_limit.signup.window', 'rates_limit.signup.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max',
'rates_limit.receive_client_log.window', 'rates_limit.receive_client_log.max', 'rates_limit.plugins.window', 'rates_limit.plugins.max',
'rates_limit.well_known.window', 'rates_limit.well_known.max', 'rates_limit.feeds.window', 'rates_limit.feeds.max',
'rates_limit.activity_pub.window', 'rates_limit.activity_pub.max', 'rates_limit.client.window', 'rates_limit.client.max',
'static_files.private_files_require_auth', 'static_files.private_files_require_auth',
'object_storage.enabled', 'object_storage.endpoint', 'object_storage.region', 'object_storage.upload_acl.public', 'object_storage.enabled', 'object_storage.endpoint', 'object_storage.region', 'object_storage.upload_acl.public',
'object_storage.upload_acl.private', 'object_storage.proxy.proxify_private_files', 'object_storage.credentials.access_key_id', 'object_storage.upload_acl.private', 'object_storage.proxy.proxify_private_files', 'object_storage.credentials.access_key_id',

View File

@ -183,6 +183,26 @@ const CONFIG = {
ASK_SEND_EMAIL: { ASK_SEND_EMAIL: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')), WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
MAX: config.get<number>('rates_limit.ask_send_email.max') MAX: config.get<number>('rates_limit.ask_send_email.max')
},
PLUGINS: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.plugins.window')),
MAX: config.get<number>('rates_limit.plugins.max')
},
WELL_KNOWN: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.well_known.window')),
MAX: config.get<number>('rates_limit.well_known.max')
},
FEEDS: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.feeds.window')),
MAX: config.get<number>('rates_limit.feeds.max')
},
ACTIVITY_PUB: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.activity_pub.window')),
MAX: config.get<number>('rates_limit.activity_pub.max')
},
CLIENT: {
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.client.window')),
MAX: config.get<number>('rates_limit.client.max')
} }
}, },
TRUST_PROXY: config.get<string[]>('trust_proxy'), TRUST_PROXY: config.get<string[]>('trust_proxy'),

View File

@ -45,6 +45,11 @@ export const apiRateLimiter = buildRateLimiter({
max: CONFIG.RATES_LIMIT.API.MAX max: CONFIG.RATES_LIMIT.API.MAX
}) })
export const activityPubRateLimiter = buildRateLimiter({
windowMs: CONFIG.RATES_LIMIT.ACTIVITY_PUB.WINDOW_MS,
max: CONFIG.RATES_LIMIT.ACTIVITY_PUB.MAX
})
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Private // Private
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -114,7 +114,7 @@ describe('Test video sources API validator', function () {
await server.videos.replaceSourceFile({ await server.videos.replaceSourceFile({
fixture: 'video_short_fake.webm', fixture: 'video_short_fake.webm',
videoId, videoId,
expectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422 completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
}) })
await server.videos.replaceSourceFile({ await server.videos.replaceSourceFile({

View File

@ -23,7 +23,7 @@ describe('Test ActivityPub playlists search', function () {
let command: SearchCommand let command: SearchCommand
before(async function () { before(async function () {
this.timeout(120000) this.timeout(240000)
servers = await createMultipleServers(2) servers = await createMultipleServers(2)