Add more rate limits
This commit is contained in:
parent
9901c8d690
commit
97583d0023
|
@ -36,6 +36,26 @@ rates_limit:
|
|||
# 10 attempts in 10 min
|
||||
window: 10 minutes
|
||||
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:
|
||||
token_lifetime:
|
||||
|
|
|
@ -34,6 +34,26 @@ rates_limit:
|
|||
# 10 attempts in 10 min
|
||||
window: 10 minutes
|
||||
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:
|
||||
token_lifetime:
|
||||
|
|
|
@ -115,7 +115,7 @@ import {
|
|||
pluginsRouter,
|
||||
trackerRouter,
|
||||
createWebsocketTrackerServer,
|
||||
botsRouter,
|
||||
sitemapRouter,
|
||||
downloadRouter
|
||||
} from './server/controllers'
|
||||
import { advertiseDoNotTrack } from './server/middlewares/dnt'
|
||||
|
@ -222,9 +222,7 @@ OpenTelemetryMetrics.Instance.init(app)
|
|||
|
||||
// ----------- Views, routes and static files -----------
|
||||
|
||||
// API
|
||||
const apiRoute = '/api/' + API_VERSION
|
||||
app.use(apiRoute, apiRouter)
|
||||
app.use('/api/' + API_VERSION, apiRouter)
|
||||
|
||||
// Services (oembed...)
|
||||
app.use('/services', servicesRouter)
|
||||
|
@ -235,7 +233,7 @@ app.use('/', pluginsRouter)
|
|||
app.use('/', activityPubRouter)
|
||||
app.use('/', feedsRouter)
|
||||
app.use('/', trackerRouter)
|
||||
app.use('/', botsRouter)
|
||||
app.use('/', sitemapRouter)
|
||||
|
||||
// Static files
|
||||
app.use('/', staticRouter)
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
getLocalVideoSharesActivityPubUrl
|
||||
} from '../../lib/activitypub/url'
|
||||
import {
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware,
|
||||
ensureIsLocalChannel,
|
||||
executeIfActivityPub,
|
||||
|
@ -47,32 +48,38 @@ activityPubClientRouter.use(cors())
|
|||
activityPubClientRouter.get(
|
||||
[ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ],
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(localAccountValidator),
|
||||
asyncMiddleware(accountController)
|
||||
)
|
||||
activityPubClientRouter.get('/accounts?/:name/followers',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(localAccountValidator),
|
||||
asyncMiddleware(accountFollowersController)
|
||||
)
|
||||
activityPubClientRouter.get('/accounts?/:name/following',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(localAccountValidator),
|
||||
asyncMiddleware(accountFollowingController)
|
||||
)
|
||||
activityPubClientRouter.get('/accounts?/:name/playlists',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(localAccountValidator),
|
||||
asyncMiddleware(accountPlaylistsController)
|
||||
)
|
||||
activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
||||
asyncMiddleware(getAccountVideoRateValidatorFactory('like')),
|
||||
asyncMiddleware(getAccountVideoRateFactory('like'))
|
||||
)
|
||||
activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
||||
asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')),
|
||||
asyncMiddleware(getAccountVideoRateFactory('dislike'))
|
||||
|
@ -81,47 +88,56 @@ activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
|
|||
activityPubClientRouter.get(
|
||||
[ '/videos/watch/:id', '/w/:id' ],
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
||||
asyncMiddleware(videosCustomGetValidator('all')),
|
||||
asyncMiddleware(videoController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/activity',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videosCustomGetValidator('all')),
|
||||
asyncMiddleware(videoController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/announces',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoAnnouncesController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videosShareValidator),
|
||||
asyncMiddleware(videoAnnounceController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/likes',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoLikesController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/dislikes',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoDislikesController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:id/comments',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
||||
asyncMiddleware(videoCommentsController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoCommentGetValidator),
|
||||
asyncMiddleware(videoCommentController)
|
||||
)
|
||||
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoCommentGetValidator),
|
||||
asyncMiddleware(videoCommentController)
|
||||
)
|
||||
|
@ -129,24 +145,28 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
|
|||
activityPubClientRouter.get(
|
||||
[ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ],
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoChannelsNameWithHostValidator),
|
||||
ensureIsLocalChannel,
|
||||
asyncMiddleware(videoChannelController)
|
||||
)
|
||||
activityPubClientRouter.get('/video-channels/:nameWithHost/followers',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoChannelsNameWithHostValidator),
|
||||
ensureIsLocalChannel,
|
||||
asyncMiddleware(videoChannelFollowersController)
|
||||
)
|
||||
activityPubClientRouter.get('/video-channels/:nameWithHost/following',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoChannelsNameWithHostValidator),
|
||||
ensureIsLocalChannel,
|
||||
asyncMiddleware(videoChannelFollowingController)
|
||||
)
|
||||
activityPubClientRouter.get('/video-channels/:nameWithHost/playlists',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoChannelsNameWithHostValidator),
|
||||
ensureIsLocalChannel,
|
||||
asyncMiddleware(videoChannelPlaylistsController)
|
||||
|
@ -154,11 +174,13 @@ activityPubClientRouter.get('/video-channels/:nameWithHost/playlists',
|
|||
|
||||
activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoFileRedundancyGetValidator),
|
||||
asyncMiddleware(videoRedundancyController)
|
||||
)
|
||||
activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoPlaylistRedundancyGetValidator),
|
||||
asyncMiddleware(videoRedundancyController)
|
||||
)
|
||||
|
@ -166,17 +188,20 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT
|
|||
activityPubClientRouter.get(
|
||||
[ '/video-playlists/:playlistId', '/videos/watch/playlist/:playlistId', '/w/p/:playlistId' ],
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoPlaylistsGetValidator('all')),
|
||||
asyncMiddleware(videoPlaylistController)
|
||||
)
|
||||
activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(videoPlaylistElementAPGetValidator),
|
||||
asyncMiddleware(videoPlaylistElementController)
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/videos/local-viewer/:localViewerId',
|
||||
executeIfActivityPub,
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware(getVideoLocalViewerValidator),
|
||||
asyncMiddleware(getVideoLocalViewerController)
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
|
|||
import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import {
|
||||
activityPubRateLimiter,
|
||||
asyncMiddleware,
|
||||
checkSignature,
|
||||
ensureIsLocalChannel,
|
||||
|
@ -17,6 +18,7 @@ import { activityPubValidator } from '../../middlewares/validators/activitypub/a
|
|||
const inboxRouter = express.Router()
|
||||
|
||||
inboxRouter.post('/inbox',
|
||||
activityPubRateLimiter,
|
||||
signatureValidator,
|
||||
asyncMiddleware(checkSignature),
|
||||
asyncMiddleware(activityPubValidator),
|
||||
|
@ -24,13 +26,16 @@ inboxRouter.post('/inbox',
|
|||
)
|
||||
|
||||
inboxRouter.post('/accounts/:name/inbox',
|
||||
activityPubRateLimiter,
|
||||
signatureValidator,
|
||||
asyncMiddleware(checkSignature),
|
||||
asyncMiddleware(localAccountValidator),
|
||||
asyncMiddleware(activityPubValidator),
|
||||
inboxController
|
||||
)
|
||||
|
||||
inboxRouter.post('/video-channels/:nameWithHost/inbox',
|
||||
activityPubRateLimiter,
|
||||
signatureValidator,
|
||||
asyncMiddleware(checkSignature),
|
||||
asyncMiddleware(videoChannelsNameWithHostValidator),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import express from 'express'
|
||||
|
||||
import { activityPubClientRouter } from './client'
|
||||
import { inboxRouter } from './inbox'
|
||||
import { outboxRouter } from './outbox'
|
||||
|
|
|
@ -7,7 +7,13 @@ import { VideoPrivacy } from '../../../shared/models/videos'
|
|||
import { logger } from '../../helpers/logger'
|
||||
import { buildAudience } from '../../lib/activitypub/audience'
|
||||
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 { VideoModel } from '../../models/video/video'
|
||||
import { activityPubResponse } from './utils'
|
||||
|
@ -15,12 +21,14 @@ import { activityPubResponse } from './utils'
|
|||
const outboxRouter = express.Router()
|
||||
|
||||
outboxRouter.get('/accounts/:name/outbox',
|
||||
activityPubRateLimiter,
|
||||
apPaginationValidator,
|
||||
localAccountValidator,
|
||||
asyncMiddleware(outboxController)
|
||||
)
|
||||
|
||||
outboxRouter.get('/video-channels/:nameWithHost/outbox',
|
||||
activityPubRateLimiter,
|
||||
apPaginationValidator,
|
||||
asyncMiddleware(videoChannelsNameWithHostValidator),
|
||||
ensureIsLocalChannel,
|
||||
|
|
|
@ -5,27 +5,53 @@ import { join } from 'path'
|
|||
import { logger } from '@server/helpers/logger'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
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 { HttpStatusCode } from '@shared/models'
|
||||
import { root } from '@shared/core-utils'
|
||||
import { STATIC_MAX_AGE } from '../initializers/constants'
|
||||
import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html'
|
||||
import { asyncMiddleware, embedCSP } from '../middlewares'
|
||||
import { asyncMiddleware, buildRateLimiter, embedCSP } from '../middlewares'
|
||||
|
||||
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 testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
|
||||
|
||||
// Special route that add OpenGraph and oEmbed tags
|
||||
// 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/:id', '/videos/watch/:id' ], asyncMiddleware(generateWatchHtmlPage))
|
||||
clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage))
|
||||
clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage))
|
||||
clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage))
|
||||
clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ],
|
||||
clientsRateLimiter,
|
||||
asyncMiddleware(generateWatchPlaylistHtmlPage)
|
||||
)
|
||||
|
||||
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 = [
|
||||
clientsRateLimiter,
|
||||
|
||||
CONFIG.CSP.ENABLED
|
||||
? embedCSP
|
||||
: (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)
|
||||
|
||||
clientsRouter.use('/videos/test-embed', testEmbedController)
|
||||
clientsRouter.use('/video-playlists/test-embed', testEmbedController)
|
||||
clientsRouter.use('/videos/test-embed', clientsRateLimiter, testEmbedController)
|
||||
clientsRouter.use('/video-playlists/test-embed', clientsRateLimiter, testEmbedController)
|
||||
|
||||
// Dynamic PWA manifest
|
||||
clientsRouter.get('/manifest.webmanifest', asyncMiddleware(generateManifest))
|
||||
clientsRouter.get('/manifest.webmanifest', clientsRateLimiter, asyncMiddleware(generateManifest))
|
||||
|
||||
// Static client overrides
|
||||
// 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)
|
||||
// Try to provide the right language index.html
|
||||
clientsRouter.use('/(:language)?', asyncMiddleware(serveIndexHTML))
|
||||
clientsRouter.use('/(:language)?',
|
||||
clientsRateLimiter,
|
||||
asyncMiddleware(serveIndexHTML)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
commentFeedsRouter.get('/feeds/video-comments.:format',
|
||||
commentFeedsRouter.get('/video-comments.:format',
|
||||
feedsFormatValidator,
|
||||
setFeedFormatContentType,
|
||||
cacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS),
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
import express from 'express'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { buildRateLimiter } from '@server/middlewares'
|
||||
import { commentFeedsRouter } from './comment-feeds'
|
||||
import { videoFeedsRouter } from './video-feeds'
|
||||
import { videoPodcastFeedsRouter } from './video-podcast-feeds'
|
||||
|
||||
const feedsRouter = express.Router()
|
||||
|
||||
feedsRouter.use('/', commentFeedsRouter)
|
||||
feedsRouter.use('/', videoFeedsRouter)
|
||||
feedsRouter.use('/', videoPodcastFeedsRouter)
|
||||
const feedsRateLimiter = buildRateLimiter({
|
||||
windowMs: CONFIG.RATES_LIMIT.FEEDS.WINDOW_MS,
|
||||
max: CONFIG.RATES_LIMIT.FEEDS.MAX
|
||||
})
|
||||
|
||||
feedsRouter.use('/feeds', feedsRateLimiter)
|
||||
|
||||
feedsRouter.use('/feeds', commentFeedsRouter)
|
||||
feedsRouter.use('/feeds', videoFeedsRouter)
|
||||
feedsRouter.use('/feeds', videoPodcastFeedsRouter)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
videoFeedsRouter.get('/feeds/videos.:format',
|
||||
videoFeedsRouter.get('/videos.:format',
|
||||
videosSortValidator,
|
||||
setDefaultVideosSort,
|
||||
feedsFormatValidator,
|
||||
|
@ -37,7 +37,7 @@ videoFeedsRouter.get('/feeds/videos.:format',
|
|||
asyncMiddleware(generateVideoFeed)
|
||||
)
|
||||
|
||||
videoFeedsRouter.get('/feeds/subscriptions.:format',
|
||||
videoFeedsRouter.get('/subscriptions.:format',
|
||||
videosSortValidator,
|
||||
setDefaultVideosSort,
|
||||
feedsFormatValidator,
|
||||
|
|
|
@ -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,
|
||||
videoFeedsPodcastSetCacheKey,
|
||||
podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export * from './activitypub'
|
||||
export * from './api'
|
||||
export * from './bots'
|
||||
export * from './sitemap'
|
||||
export * from './client'
|
||||
export * from './download'
|
||||
export * from './feeds'
|
||||
|
|
|
@ -7,7 +7,7 @@ import { HttpStatusCode } from '@shared/models'
|
|||
import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
|
||||
import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION, ROUTE_CACHE_LIFETIME } from '../initializers/constants'
|
||||
import { getThemeOrDefault } from '../lib/plugins/theme-utils'
|
||||
import { asyncMiddleware } from '../middlewares'
|
||||
import { apiRateLimiter, asyncMiddleware } from '../middlewares'
|
||||
import { cacheRoute } from '../middlewares/cache/cache'
|
||||
import { UserModel } from '../models/user/user'
|
||||
import { VideoModel } from '../models/video/video'
|
||||
|
@ -18,12 +18,14 @@ const miscRouter = express.Router()
|
|||
miscRouter.use(cors())
|
||||
|
||||
miscRouter.use('/nodeinfo/:version.json',
|
||||
apiRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
|
||||
asyncMiddleware(generateNodeinfo)
|
||||
)
|
||||
|
||||
// robots.txt service
|
||||
miscRouter.get('/robots.txt',
|
||||
apiRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS),
|
||||
(_, res: express.Response) => {
|
||||
res.type('text/plain')
|
||||
|
@ -33,12 +35,14 @@ miscRouter.get('/robots.txt',
|
|||
)
|
||||
|
||||
miscRouter.all('/teapot',
|
||||
apiRateLimiter,
|
||||
getCup,
|
||||
asyncMiddleware(serveIndexHTML)
|
||||
)
|
||||
|
||||
// security.txt service
|
||||
miscRouter.get('/security.txt',
|
||||
apiRateLimiter,
|
||||
(_, res: express.Response) => {
|
||||
return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt')
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import express from 'express'
|
||||
import { join } from 'path'
|
||||
import { logger } from '@server/helpers/logger'
|
||||
import { CONFIG } from '@server/initializers/config'
|
||||
import { buildRateLimiter } from '@server/middlewares'
|
||||
import { optionalAuthenticate } from '@server/middlewares/auth'
|
||||
import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n'
|
||||
import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
|
||||
|
@ -18,57 +20,72 @@ const sendFileOptions = {
|
|||
|
||||
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',
|
||||
pluginsRateLimiter,
|
||||
servePluginGlobalCSS
|
||||
)
|
||||
|
||||
pluginsRouter.get('/plugins/translations/:locale.json',
|
||||
pluginsRateLimiter,
|
||||
getPluginTranslations
|
||||
)
|
||||
|
||||
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.PLUGIN),
|
||||
getExternalAuthValidator,
|
||||
handleAuthInPlugin
|
||||
)
|
||||
|
||||
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.PLUGIN),
|
||||
pluginStaticDirectoryValidator,
|
||||
servePluginStaticDirectory
|
||||
)
|
||||
|
||||
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.PLUGIN),
|
||||
pluginStaticDirectoryValidator,
|
||||
servePluginClientScripts
|
||||
)
|
||||
|
||||
pluginsRouter.use('/plugins/:pluginName/router',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.PLUGIN, false),
|
||||
optionalAuthenticate,
|
||||
servePluginCustomRoutes
|
||||
)
|
||||
|
||||
pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.PLUGIN),
|
||||
optionalAuthenticate,
|
||||
servePluginCustomRoutes
|
||||
)
|
||||
|
||||
pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.THEME),
|
||||
pluginStaticDirectoryValidator,
|
||||
servePluginStaticDirectory
|
||||
)
|
||||
|
||||
pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)',
|
||||
pluginsRateLimiter,
|
||||
getPluginValidator(PluginType.THEME),
|
||||
pluginStaticDirectoryValidator,
|
||||
servePluginClientScripts
|
||||
)
|
||||
|
||||
pluginsRouter.get('/themes/:themeName/:themeVersion/css/:staticEndpoint(*)',
|
||||
pluginsRateLimiter,
|
||||
serveThemeCSSValidator,
|
||||
serveThemeCSSDirectory
|
||||
)
|
||||
|
|
|
@ -2,17 +2,19 @@ import express from 'express'
|
|||
import { MChannelSummary } from '@server/types/models'
|
||||
import { escapeHTML } from '@shared/core-utils/renderer'
|
||||
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 { forceNumber } from '@shared/core-utils'
|
||||
|
||||
const servicesRouter = express.Router()
|
||||
|
||||
servicesRouter.use('/oembed',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(oembedValidator),
|
||||
generateOEmbed
|
||||
)
|
||||
servicesRouter.use('/redirect/accounts/:accountName',
|
||||
apiRateLimiter,
|
||||
asyncMiddleware(accountNameWithHostGetValidator),
|
||||
redirectToAccountUrl
|
||||
)
|
||||
|
|
|
@ -5,17 +5,16 @@ import { logger } from '@server/helpers/logger'
|
|||
import { getServerActor } from '@server/models/application/application'
|
||||
import { buildNSFWFilter } from '../helpers/express-utils'
|
||||
import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
|
||||
import { asyncMiddleware } from '../middlewares'
|
||||
import { apiRateLimiter, asyncMiddleware } from '../middlewares'
|
||||
import { cacheRoute } from '../middlewares/cache/cache'
|
||||
import { AccountModel } from '../models/account/account'
|
||||
import { VideoModel } from '../models/video/video'
|
||||
import { VideoChannelModel } from '../models/video/video-channel'
|
||||
|
||||
const botsRouter = express.Router()
|
||||
const sitemapRouter = express.Router()
|
||||
|
||||
// Special route that add OpenGraph and oEmbed tags
|
||||
// Do not use a template engine for a so little thing
|
||||
botsRouter.use('/sitemap.xml',
|
||||
sitemapRouter.use('/sitemap.xml',
|
||||
apiRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP),
|
||||
asyncMiddleware(getSitemap)
|
||||
)
|
||||
|
@ -23,7 +22,7 @@ botsRouter.use('/sitemap.xml',
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
botsRouter
|
||||
sitemapRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
|
@ -1,7 +1,7 @@
|
|||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
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 { CONFIG } from '../initializers/config'
|
||||
import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
|
||||
|
@ -9,14 +9,21 @@ import { cacheRoute } from '../middlewares/cache/cache'
|
|||
|
||||
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.get('/.well-known/webfinger',
|
||||
wellKnownRateLimiter,
|
||||
asyncMiddleware(webfingerValidator),
|
||||
webfingerController
|
||||
)
|
||||
|
||||
wellKnownRouter.get('/.well-known/security.txt',
|
||||
wellKnownRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT),
|
||||
(_, res: express.Response) => {
|
||||
res.type('text/plain')
|
||||
|
@ -26,6 +33,7 @@ wellKnownRouter.get('/.well-known/security.txt',
|
|||
|
||||
// nodeinfo service
|
||||
wellKnownRouter.use('/.well-known/nodeinfo',
|
||||
wellKnownRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
|
||||
(_, res: express.Response) => {
|
||||
return res.json({
|
||||
|
@ -41,6 +49,7 @@ wellKnownRouter.use('/.well-known/nodeinfo',
|
|||
|
||||
// dnt-policy.txt service (see https://www.eff.org/dnt-policy)
|
||||
wellKnownRouter.use('/.well-known/dnt-policy.txt',
|
||||
wellKnownRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY),
|
||||
(_, res: express.Response) => {
|
||||
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)
|
||||
wellKnownRouter.use('/.well-known/dnt/',
|
||||
wellKnownRateLimiter,
|
||||
(_, res: express.Response) => {
|
||||
res.json({ tracking: 'N' })
|
||||
}
|
||||
)
|
||||
|
||||
wellKnownRouter.use('/.well-known/change-password',
|
||||
wellKnownRateLimiter,
|
||||
(_, res: express.Response) => {
|
||||
res.redirect('/my-account/settings')
|
||||
}
|
||||
)
|
||||
|
||||
wellKnownRouter.use('/.well-known/host-meta',
|
||||
wellKnownRateLimiter,
|
||||
(_, res: express.Response) => {
|
||||
res.type('application/xml')
|
||||
|
||||
|
@ -76,6 +88,7 @@ wellKnownRouter.use('/.well-known/host-meta',
|
|||
)
|
||||
|
||||
wellKnownRouter.use('/.well-known/',
|
||||
wellKnownRateLimiter,
|
||||
cacheRoute(ROUTE_CACHE_LIFETIME.WELL_KNOWN),
|
||||
express.static(CONFIG.STORAGE.WELL_KNOWN_DIR, { fallthrough: false }),
|
||||
handleStaticError
|
||||
|
|
|
@ -56,7 +56,11 @@ function checkMissedConfig () {
|
|||
'followers.instance.enabled', 'followers.instance.manual_approval',
|
||||
'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',
|
||||
'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',
|
||||
'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',
|
||||
|
|
|
@ -183,6 +183,26 @@ const CONFIG = {
|
|||
ASK_SEND_EMAIL: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
|
||||
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'),
|
||||
|
|
|
@ -45,6 +45,11 @@ export const apiRateLimiter = buildRateLimiter({
|
|||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -114,7 +114,7 @@ describe('Test video sources API validator', function () {
|
|||
await server.videos.replaceSourceFile({
|
||||
fixture: 'video_short_fake.webm',
|
||||
videoId,
|
||||
expectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
|
||||
completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422
|
||||
})
|
||||
|
||||
await server.videos.replaceSourceFile({
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('Test ActivityPub playlists search', function () {
|
|||
let command: SearchCommand
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
this.timeout(240000)
|
||||
|
||||
servers = await createMultipleServers(2)
|
||||
|
||||
|
|
Loading…
Reference in New Issue