diff options
23 files changed, 216 insertions, 37 deletions
diff --git a/config/default.yaml b/config/default.yaml index 10d3f79e7..53d8c45de 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -36,6 +36,26 @@ rates_limit: | |||
36 | # 10 attempts in 10 min | 36 | # 10 attempts in 10 min |
37 | window: 10 minutes | 37 | window: 10 minutes |
38 | max: 10 | 38 | max: 10 |
39 | plugins: | ||
40 | # 500 attempts in 10 seconds (we also serve plugin static files) | ||
41 | window: 10 seconds | ||
42 | max: 500 | ||
43 | well_known: | ||
44 | # 200 attempts in 10 seconds | ||
45 | window: 10 seconds | ||
46 | max: 200 | ||
47 | feeds: | ||
48 | # 50 attempts in 10 seconds | ||
49 | window: 10 seconds | ||
50 | max: 50 | ||
51 | activity_pub: | ||
52 | # 500 attempts in 10 seconds (we can have many AP requests) | ||
53 | window: 10 seconds | ||
54 | max: 500 | ||
55 | client: # HTML files generated by PeerTube | ||
56 | # 500 attempts in 10 seconds (to not break crawlers) | ||
57 | window: 10 seconds | ||
58 | max: 500 | ||
39 | 59 | ||
40 | oauth2: | 60 | oauth2: |
41 | token_lifetime: | 61 | token_lifetime: |
diff --git a/config/production.yaml.example b/config/production.yaml.example index a829b46f9..87ef2b676 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -34,6 +34,26 @@ rates_limit: | |||
34 | # 10 attempts in 10 min | 34 | # 10 attempts in 10 min |
35 | window: 10 minutes | 35 | window: 10 minutes |
36 | max: 10 | 36 | max: 10 |
37 | plugins: | ||
38 | # 500 attempts in 10 seconds (we also serve plugin static files) | ||
39 | window: 10 seconds | ||
40 | max: 500 | ||
41 | well_known: | ||
42 | # 200 attempts in 10 seconds | ||
43 | window: 10 seconds | ||
44 | max: 200 | ||
45 | feeds: | ||
46 | # 50 attempts in 10 seconds | ||
47 | window: 10 seconds | ||
48 | max: 50 | ||
49 | activity_pub: | ||
50 | # 500 attempts in 10 seconds (we can have many AP requests) | ||
51 | window: 10 seconds | ||
52 | max: 500 | ||
53 | client: # HTML files generated by PeerTube | ||
54 | # 500 attempts in 10 seconds (to not break crawlers) | ||
55 | window: 10 seconds | ||
56 | max: 500 | ||
37 | 57 | ||
38 | oauth2: | 58 | oauth2: |
39 | token_lifetime: | 59 | token_lifetime: |
@@ -115,7 +115,7 @@ import { | |||
115 | pluginsRouter, | 115 | pluginsRouter, |
116 | trackerRouter, | 116 | trackerRouter, |
117 | createWebsocketTrackerServer, | 117 | createWebsocketTrackerServer, |
118 | botsRouter, | 118 | sitemapRouter, |
119 | downloadRouter | 119 | downloadRouter |
120 | } from './server/controllers' | 120 | } from './server/controllers' |
121 | import { advertiseDoNotTrack } from './server/middlewares/dnt' | 121 | import { advertiseDoNotTrack } from './server/middlewares/dnt' |
@@ -222,9 +222,7 @@ OpenTelemetryMetrics.Instance.init(app) | |||
222 | 222 | ||
223 | // ----------- Views, routes and static files ----------- | 223 | // ----------- Views, routes and static files ----------- |
224 | 224 | ||
225 | // API | 225 | app.use('/api/' + API_VERSION, apiRouter) |
226 | const apiRoute = '/api/' + API_VERSION | ||
227 | app.use(apiRoute, apiRouter) | ||
228 | 226 | ||
229 | // Services (oembed...) | 227 | // Services (oembed...) |
230 | app.use('/services', servicesRouter) | 228 | app.use('/services', servicesRouter) |
@@ -235,7 +233,7 @@ app.use('/', pluginsRouter) | |||
235 | app.use('/', activityPubRouter) | 233 | app.use('/', activityPubRouter) |
236 | app.use('/', feedsRouter) | 234 | app.use('/', feedsRouter) |
237 | app.use('/', trackerRouter) | 235 | app.use('/', trackerRouter) |
238 | app.use('/', botsRouter) | 236 | app.use('/', sitemapRouter) |
239 | 237 | ||
240 | // Static files | 238 | // Static files |
241 | app.use('/', staticRouter) | 239 | app.use('/', staticRouter) |
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index c47c61f52..be52f1662 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -19,6 +19,7 @@ import { | |||
19 | getLocalVideoSharesActivityPubUrl | 19 | getLocalVideoSharesActivityPubUrl |
20 | } from '../../lib/activitypub/url' | 20 | } from '../../lib/activitypub/url' |
21 | import { | 21 | import { |
22 | activityPubRateLimiter, | ||
22 | asyncMiddleware, | 23 | asyncMiddleware, |
23 | ensureIsLocalChannel, | 24 | ensureIsLocalChannel, |
24 | executeIfActivityPub, | 25 | executeIfActivityPub, |
@@ -47,32 +48,38 @@ activityPubClientRouter.use(cors()) | |||
47 | activityPubClientRouter.get( | 48 | activityPubClientRouter.get( |
48 | [ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ], | 49 | [ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ], |
49 | executeIfActivityPub, | 50 | executeIfActivityPub, |
51 | activityPubRateLimiter, | ||
50 | asyncMiddleware(localAccountValidator), | 52 | asyncMiddleware(localAccountValidator), |
51 | asyncMiddleware(accountController) | 53 | asyncMiddleware(accountController) |
52 | ) | 54 | ) |
53 | activityPubClientRouter.get('/accounts?/:name/followers', | 55 | activityPubClientRouter.get('/accounts?/:name/followers', |
54 | executeIfActivityPub, | 56 | executeIfActivityPub, |
57 | activityPubRateLimiter, | ||
55 | asyncMiddleware(localAccountValidator), | 58 | asyncMiddleware(localAccountValidator), |
56 | asyncMiddleware(accountFollowersController) | 59 | asyncMiddleware(accountFollowersController) |
57 | ) | 60 | ) |
58 | activityPubClientRouter.get('/accounts?/:name/following', | 61 | activityPubClientRouter.get('/accounts?/:name/following', |
59 | executeIfActivityPub, | 62 | executeIfActivityPub, |
63 | activityPubRateLimiter, | ||
60 | asyncMiddleware(localAccountValidator), | 64 | asyncMiddleware(localAccountValidator), |
61 | asyncMiddleware(accountFollowingController) | 65 | asyncMiddleware(accountFollowingController) |
62 | ) | 66 | ) |
63 | activityPubClientRouter.get('/accounts?/:name/playlists', | 67 | activityPubClientRouter.get('/accounts?/:name/playlists', |
64 | executeIfActivityPub, | 68 | executeIfActivityPub, |
69 | activityPubRateLimiter, | ||
65 | asyncMiddleware(localAccountValidator), | 70 | asyncMiddleware(localAccountValidator), |
66 | asyncMiddleware(accountPlaylistsController) | 71 | asyncMiddleware(accountPlaylistsController) |
67 | ) | 72 | ) |
68 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', | 73 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', |
69 | executeIfActivityPub, | 74 | executeIfActivityPub, |
75 | activityPubRateLimiter, | ||
70 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), | 76 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), |
71 | asyncMiddleware(getAccountVideoRateValidatorFactory('like')), | 77 | asyncMiddleware(getAccountVideoRateValidatorFactory('like')), |
72 | asyncMiddleware(getAccountVideoRateFactory('like')) | 78 | asyncMiddleware(getAccountVideoRateFactory('like')) |
73 | ) | 79 | ) |
74 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | 80 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', |
75 | executeIfActivityPub, | 81 | executeIfActivityPub, |
82 | activityPubRateLimiter, | ||
76 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), | 83 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), |
77 | asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')), | 84 | asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')), |
78 | asyncMiddleware(getAccountVideoRateFactory('dislike')) | 85 | asyncMiddleware(getAccountVideoRateFactory('dislike')) |
@@ -81,47 +88,56 @@ activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | |||
81 | activityPubClientRouter.get( | 88 | activityPubClientRouter.get( |
82 | [ '/videos/watch/:id', '/w/:id' ], | 89 | [ '/videos/watch/:id', '/w/:id' ], |
83 | executeIfActivityPub, | 90 | executeIfActivityPub, |
91 | activityPubRateLimiter, | ||
84 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), | 92 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), |
85 | asyncMiddleware(videosCustomGetValidator('all')), | 93 | asyncMiddleware(videosCustomGetValidator('all')), |
86 | asyncMiddleware(videoController) | 94 | asyncMiddleware(videoController) |
87 | ) | 95 | ) |
88 | activityPubClientRouter.get('/videos/watch/:id/activity', | 96 | activityPubClientRouter.get('/videos/watch/:id/activity', |
89 | executeIfActivityPub, | 97 | executeIfActivityPub, |
98 | activityPubRateLimiter, | ||
90 | asyncMiddleware(videosCustomGetValidator('all')), | 99 | asyncMiddleware(videosCustomGetValidator('all')), |
91 | asyncMiddleware(videoController) | 100 | asyncMiddleware(videoController) |
92 | ) | 101 | ) |
93 | activityPubClientRouter.get('/videos/watch/:id/announces', | 102 | activityPubClientRouter.get('/videos/watch/:id/announces', |
94 | executeIfActivityPub, | 103 | executeIfActivityPub, |
104 | activityPubRateLimiter, | ||
95 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | 105 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
96 | asyncMiddleware(videoAnnouncesController) | 106 | asyncMiddleware(videoAnnouncesController) |
97 | ) | 107 | ) |
98 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | 108 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', |
99 | executeIfActivityPub, | 109 | executeIfActivityPub, |
110 | activityPubRateLimiter, | ||
100 | asyncMiddleware(videosShareValidator), | 111 | asyncMiddleware(videosShareValidator), |
101 | asyncMiddleware(videoAnnounceController) | 112 | asyncMiddleware(videoAnnounceController) |
102 | ) | 113 | ) |
103 | activityPubClientRouter.get('/videos/watch/:id/likes', | 114 | activityPubClientRouter.get('/videos/watch/:id/likes', |
104 | executeIfActivityPub, | 115 | executeIfActivityPub, |
116 | activityPubRateLimiter, | ||
105 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | 117 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
106 | asyncMiddleware(videoLikesController) | 118 | asyncMiddleware(videoLikesController) |
107 | ) | 119 | ) |
108 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | 120 | activityPubClientRouter.get('/videos/watch/:id/dislikes', |
109 | executeIfActivityPub, | 121 | executeIfActivityPub, |
122 | activityPubRateLimiter, | ||
110 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | 123 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
111 | asyncMiddleware(videoDislikesController) | 124 | asyncMiddleware(videoDislikesController) |
112 | ) | 125 | ) |
113 | activityPubClientRouter.get('/videos/watch/:id/comments', | 126 | activityPubClientRouter.get('/videos/watch/:id/comments', |
114 | executeIfActivityPub, | 127 | executeIfActivityPub, |
128 | activityPubRateLimiter, | ||
115 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | 129 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
116 | asyncMiddleware(videoCommentsController) | 130 | asyncMiddleware(videoCommentsController) |
117 | ) | 131 | ) |
118 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', | 132 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', |
119 | executeIfActivityPub, | 133 | executeIfActivityPub, |
134 | activityPubRateLimiter, | ||
120 | asyncMiddleware(videoCommentGetValidator), | 135 | asyncMiddleware(videoCommentGetValidator), |
121 | asyncMiddleware(videoCommentController) | 136 | asyncMiddleware(videoCommentController) |
122 | ) | 137 | ) |
123 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', | 138 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', |
124 | executeIfActivityPub, | 139 | executeIfActivityPub, |
140 | activityPubRateLimiter, | ||
125 | asyncMiddleware(videoCommentGetValidator), | 141 | asyncMiddleware(videoCommentGetValidator), |
126 | asyncMiddleware(videoCommentController) | 142 | asyncMiddleware(videoCommentController) |
127 | ) | 143 | ) |
@@ -129,24 +145,28 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity | |||
129 | activityPubClientRouter.get( | 145 | activityPubClientRouter.get( |
130 | [ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ], | 146 | [ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ], |
131 | executeIfActivityPub, | 147 | executeIfActivityPub, |
148 | activityPubRateLimiter, | ||
132 | asyncMiddleware(videoChannelsNameWithHostValidator), | 149 | asyncMiddleware(videoChannelsNameWithHostValidator), |
133 | ensureIsLocalChannel, | 150 | ensureIsLocalChannel, |
134 | asyncMiddleware(videoChannelController) | 151 | asyncMiddleware(videoChannelController) |
135 | ) | 152 | ) |
136 | activityPubClientRouter.get('/video-channels/:nameWithHost/followers', | 153 | activityPubClientRouter.get('/video-channels/:nameWithHost/followers', |
137 | executeIfActivityPub, | 154 | executeIfActivityPub, |
155 | activityPubRateLimiter, | ||
138 | asyncMiddleware(videoChannelsNameWithHostValidator), | 156 | asyncMiddleware(videoChannelsNameWithHostValidator), |
139 | ensureIsLocalChannel, | 157 | ensureIsLocalChannel, |
140 | asyncMiddleware(videoChannelFollowersController) | 158 | asyncMiddleware(videoChannelFollowersController) |
141 | ) | 159 | ) |
142 | activityPubClientRouter.get('/video-channels/:nameWithHost/following', | 160 | activityPubClientRouter.get('/video-channels/:nameWithHost/following', |
143 | executeIfActivityPub, | 161 | executeIfActivityPub, |
162 | activityPubRateLimiter, | ||
144 | asyncMiddleware(videoChannelsNameWithHostValidator), | 163 | asyncMiddleware(videoChannelsNameWithHostValidator), |
145 | ensureIsLocalChannel, | 164 | ensureIsLocalChannel, |
146 | asyncMiddleware(videoChannelFollowingController) | 165 | asyncMiddleware(videoChannelFollowingController) |
147 | ) | 166 | ) |
148 | activityPubClientRouter.get('/video-channels/:nameWithHost/playlists', | 167 | activityPubClientRouter.get('/video-channels/:nameWithHost/playlists', |
149 | executeIfActivityPub, | 168 | executeIfActivityPub, |
169 | activityPubRateLimiter, | ||
150 | asyncMiddleware(videoChannelsNameWithHostValidator), | 170 | asyncMiddleware(videoChannelsNameWithHostValidator), |
151 | ensureIsLocalChannel, | 171 | ensureIsLocalChannel, |
152 | asyncMiddleware(videoChannelPlaylistsController) | 172 | asyncMiddleware(videoChannelPlaylistsController) |
@@ -154,11 +174,13 @@ activityPubClientRouter.get('/video-channels/:nameWithHost/playlists', | |||
154 | 174 | ||
155 | activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', | 175 | activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', |
156 | executeIfActivityPub, | 176 | executeIfActivityPub, |
177 | activityPubRateLimiter, | ||
157 | asyncMiddleware(videoFileRedundancyGetValidator), | 178 | asyncMiddleware(videoFileRedundancyGetValidator), |
158 | asyncMiddleware(videoRedundancyController) | 179 | asyncMiddleware(videoRedundancyController) |
159 | ) | 180 | ) |
160 | activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId', | 181 | activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId', |
161 | executeIfActivityPub, | 182 | executeIfActivityPub, |
183 | activityPubRateLimiter, | ||
162 | asyncMiddleware(videoPlaylistRedundancyGetValidator), | 184 | asyncMiddleware(videoPlaylistRedundancyGetValidator), |
163 | asyncMiddleware(videoRedundancyController) | 185 | asyncMiddleware(videoRedundancyController) |
164 | ) | 186 | ) |
@@ -166,17 +188,20 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT | |||
166 | activityPubClientRouter.get( | 188 | activityPubClientRouter.get( |
167 | [ '/video-playlists/:playlistId', '/videos/watch/playlist/:playlistId', '/w/p/:playlistId' ], | 189 | [ '/video-playlists/:playlistId', '/videos/watch/playlist/:playlistId', '/w/p/:playlistId' ], |
168 | executeIfActivityPub, | 190 | executeIfActivityPub, |
191 | activityPubRateLimiter, | ||
169 | asyncMiddleware(videoPlaylistsGetValidator('all')), | 192 | asyncMiddleware(videoPlaylistsGetValidator('all')), |
170 | asyncMiddleware(videoPlaylistController) | 193 | asyncMiddleware(videoPlaylistController) |
171 | ) | 194 | ) |
172 | activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId', | 195 | activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId', |
173 | executeIfActivityPub, | 196 | executeIfActivityPub, |
197 | activityPubRateLimiter, | ||
174 | asyncMiddleware(videoPlaylistElementAPGetValidator), | 198 | asyncMiddleware(videoPlaylistElementAPGetValidator), |
175 | asyncMiddleware(videoPlaylistElementController) | 199 | asyncMiddleware(videoPlaylistElementController) |
176 | ) | 200 | ) |
177 | 201 | ||
178 | activityPubClientRouter.get('/videos/local-viewer/:localViewerId', | 202 | activityPubClientRouter.get('/videos/local-viewer/:localViewerId', |
179 | executeIfActivityPub, | 203 | executeIfActivityPub, |
204 | activityPubRateLimiter, | ||
180 | asyncMiddleware(getVideoLocalViewerValidator), | 205 | asyncMiddleware(getVideoLocalViewerValidator), |
181 | asyncMiddleware(getVideoLocalViewerController) | 206 | asyncMiddleware(getVideoLocalViewerController) |
182 | ) | 207 | ) |
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index 66a38e055..862c7baf1 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts | |||
@@ -5,6 +5,7 @@ import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | |||
5 | import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity' | 5 | import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity' |
6 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
7 | import { | 7 | import { |
8 | activityPubRateLimiter, | ||
8 | asyncMiddleware, | 9 | asyncMiddleware, |
9 | checkSignature, | 10 | checkSignature, |
10 | ensureIsLocalChannel, | 11 | ensureIsLocalChannel, |
@@ -17,6 +18,7 @@ import { activityPubValidator } from '../../middlewares/validators/activitypub/a | |||
17 | const inboxRouter = express.Router() | 18 | const inboxRouter = express.Router() |
18 | 19 | ||
19 | inboxRouter.post('/inbox', | 20 | inboxRouter.post('/inbox', |
21 | activityPubRateLimiter, | ||
20 | signatureValidator, | 22 | signatureValidator, |
21 | asyncMiddleware(checkSignature), | 23 | asyncMiddleware(checkSignature), |
22 | asyncMiddleware(activityPubValidator), | 24 | asyncMiddleware(activityPubValidator), |
@@ -24,13 +26,16 @@ inboxRouter.post('/inbox', | |||
24 | ) | 26 | ) |
25 | 27 | ||
26 | inboxRouter.post('/accounts/:name/inbox', | 28 | inboxRouter.post('/accounts/:name/inbox', |
29 | activityPubRateLimiter, | ||
27 | signatureValidator, | 30 | signatureValidator, |
28 | asyncMiddleware(checkSignature), | 31 | asyncMiddleware(checkSignature), |
29 | asyncMiddleware(localAccountValidator), | 32 | asyncMiddleware(localAccountValidator), |
30 | asyncMiddleware(activityPubValidator), | 33 | asyncMiddleware(activityPubValidator), |
31 | inboxController | 34 | inboxController |
32 | ) | 35 | ) |
36 | |||
33 | inboxRouter.post('/video-channels/:nameWithHost/inbox', | 37 | inboxRouter.post('/video-channels/:nameWithHost/inbox', |
38 | activityPubRateLimiter, | ||
34 | signatureValidator, | 39 | signatureValidator, |
35 | asyncMiddleware(checkSignature), | 40 | asyncMiddleware(checkSignature), |
36 | asyncMiddleware(videoChannelsNameWithHostValidator), | 41 | asyncMiddleware(videoChannelsNameWithHostValidator), |
diff --git a/server/controllers/activitypub/index.ts b/server/controllers/activitypub/index.ts index 8c681820a..c14d95108 100644 --- a/server/controllers/activitypub/index.ts +++ b/server/controllers/activitypub/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | |||
2 | import { activityPubClientRouter } from './client' | 3 | import { activityPubClientRouter } from './client' |
3 | import { inboxRouter } from './inbox' | 4 | import { inboxRouter } from './inbox' |
4 | import { outboxRouter } from './outbox' | 5 | import { outboxRouter } from './outbox' |
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 4175cf276..8c88b6971 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -7,7 +7,13 @@ import { VideoPrivacy } from '../../../shared/models/videos' | |||
7 | import { logger } from '../../helpers/logger' | 7 | import { logger } from '../../helpers/logger' |
8 | import { buildAudience } from '../../lib/activitypub/audience' | 8 | import { buildAudience } from '../../lib/activitypub/audience' |
9 | import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' | 9 | import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' |
10 | import { asyncMiddleware, ensureIsLocalChannel, localAccountValidator, videoChannelsNameWithHostValidator } from '../../middlewares' | 10 | import { |
11 | activityPubRateLimiter, | ||
12 | asyncMiddleware, | ||
13 | ensureIsLocalChannel, | ||
14 | localAccountValidator, | ||
15 | videoChannelsNameWithHostValidator | ||
16 | } from '../../middlewares' | ||
11 | import { apPaginationValidator } from '../../middlewares/validators/activitypub' | 17 | import { apPaginationValidator } from '../../middlewares/validators/activitypub' |
12 | import { VideoModel } from '../../models/video/video' | 18 | import { VideoModel } from '../../models/video/video' |
13 | import { activityPubResponse } from './utils' | 19 | import { activityPubResponse } from './utils' |
@@ -15,12 +21,14 @@ import { activityPubResponse } from './utils' | |||
15 | const outboxRouter = express.Router() | 21 | const outboxRouter = express.Router() |
16 | 22 | ||
17 | outboxRouter.get('/accounts/:name/outbox', | 23 | outboxRouter.get('/accounts/:name/outbox', |
24 | activityPubRateLimiter, | ||
18 | apPaginationValidator, | 25 | apPaginationValidator, |
19 | localAccountValidator, | 26 | localAccountValidator, |
20 | asyncMiddleware(outboxController) | 27 | asyncMiddleware(outboxController) |
21 | ) | 28 | ) |
22 | 29 | ||
23 | outboxRouter.get('/video-channels/:nameWithHost/outbox', | 30 | outboxRouter.get('/video-channels/:nameWithHost/outbox', |
31 | activityPubRateLimiter, | ||
24 | apPaginationValidator, | 32 | apPaginationValidator, |
25 | asyncMiddleware(videoChannelsNameWithHostValidator), | 33 | asyncMiddleware(videoChannelsNameWithHostValidator), |
26 | ensureIsLocalChannel, | 34 | ensureIsLocalChannel, |
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index a85c10720..2d0c49904 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -5,27 +5,53 @@ import { join } from 'path' | |||
5 | import { logger } from '@server/helpers/logger' | 5 | import { logger } from '@server/helpers/logger' |
6 | import { CONFIG } from '@server/initializers/config' | 6 | import { CONFIG } from '@server/initializers/config' |
7 | import { Hooks } from '@server/lib/plugins/hooks' | 7 | import { Hooks } from '@server/lib/plugins/hooks' |
8 | import { root } from '@shared/core-utils' | ||
8 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' | 9 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' |
9 | import { HttpStatusCode } from '@shared/models' | 10 | import { HttpStatusCode } from '@shared/models' |
10 | import { root } from '@shared/core-utils' | ||
11 | import { STATIC_MAX_AGE } from '../initializers/constants' | 11 | import { STATIC_MAX_AGE } from '../initializers/constants' |
12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' | 12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' |
13 | import { asyncMiddleware, embedCSP } from '../middlewares' | 13 | import { asyncMiddleware, buildRateLimiter, embedCSP } from '../middlewares' |
14 | 14 | ||
15 | const clientsRouter = express.Router() | 15 | const clientsRouter = express.Router() |
16 | 16 | ||
17 | const clientsRateLimiter = buildRateLimiter({ | ||
18 | windowMs: CONFIG.RATES_LIMIT.CLIENT.WINDOW_MS, | ||
19 | max: CONFIG.RATES_LIMIT.CLIENT.MAX | ||
20 | }) | ||
21 | |||
17 | const distPath = join(root(), 'client', 'dist') | 22 | const distPath = join(root(), 'client', 'dist') |
18 | const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html') | 23 | const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html') |
19 | 24 | ||
20 | // Special route that add OpenGraph and oEmbed tags | 25 | // Special route that add OpenGraph and oEmbed tags |
21 | // Do not use a template engine for a so little thing | 26 | // Do not use a template engine for a so little thing |
22 | clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], asyncMiddleware(generateWatchPlaylistHtmlPage)) | 27 | clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], |
23 | clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], asyncMiddleware(generateWatchHtmlPage)) | 28 | clientsRateLimiter, |
24 | clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], asyncMiddleware(generateAccountHtmlPage)) | 29 | asyncMiddleware(generateWatchPlaylistHtmlPage) |
25 | clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], asyncMiddleware(generateVideoChannelHtmlPage)) | 30 | ) |
26 | clientsRouter.use('/@:nameWithHost', asyncMiddleware(generateActorHtmlPage)) | 31 | |
32 | clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], | ||
33 | clientsRateLimiter, | ||
34 | asyncMiddleware(generateWatchHtmlPage) | ||
35 | ) | ||
36 | |||
37 | clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], | ||
38 | clientsRateLimiter, | ||
39 | asyncMiddleware(generateAccountHtmlPage) | ||
40 | ) | ||
41 | |||
42 | clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], | ||
43 | clientsRateLimiter, | ||
44 | asyncMiddleware(generateVideoChannelHtmlPage) | ||
45 | ) | ||
46 | |||
47 | clientsRouter.use('/@:nameWithHost', | ||
48 | clientsRateLimiter, | ||
49 | asyncMiddleware(generateActorHtmlPage) | ||
50 | ) | ||
27 | 51 | ||
28 | const embedMiddlewares = [ | 52 | const embedMiddlewares = [ |
53 | clientsRateLimiter, | ||
54 | |||
29 | CONFIG.CSP.ENABLED | 55 | CONFIG.CSP.ENABLED |
30 | ? embedCSP | 56 | ? embedCSP |
31 | : (req: express.Request, res: express.Response, next: express.NextFunction) => next(), | 57 | : (req: express.Request, res: express.Response, next: express.NextFunction) => next(), |
@@ -48,11 +74,11 @@ clientsRouter.use('/video-playlists/embed', ...embedMiddlewares) | |||
48 | 74 | ||
49 | const testEmbedController = (req: express.Request, res: express.Response) => res.sendFile(testEmbedPath) | 75 | const testEmbedController = (req: express.Request, res: express.Response) => res.sendFile(testEmbedPath) |
50 | 76 | ||
51 | clientsRouter.use('/videos/test-embed', testEmbedController) | 77 | clientsRouter.use('/videos/test-embed', clientsRateLimiter, testEmbedController) |
52 | clientsRouter.use('/video-playlists/test-embed', testEmbedController) | 78 | clientsRouter.use('/video-playlists/test-embed', clientsRateLimiter, testEmbedController) |
53 | 79 | ||
54 | // Dynamic PWA manifest | 80 | // Dynamic PWA manifest |
55 | clientsRouter.get('/manifest.webmanifest', asyncMiddleware(generateManifest)) | 81 | clientsRouter.get('/manifest.webmanifest', clientsRateLimiter, asyncMiddleware(generateManifest)) |
56 | 82 | ||
57 | // Static client overrides | 83 | // Static client overrides |
58 | // Must be consistent with static client overrides redirections in /support/nginx/peertube | 84 | // 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) => | |||
88 | 114 | ||
89 | // Always serve index client page (the client is a single page application, let it handle routing) | 115 | // Always serve index client page (the client is a single page application, let it handle routing) |
90 | // Try to provide the right language index.html | 116 | // Try to provide the right language index.html |
91 | clientsRouter.use('/(:language)?', asyncMiddleware(serveIndexHTML)) | 117 | clientsRouter.use('/(:language)?', |
118 | clientsRateLimiter, | ||
119 | asyncMiddleware(serveIndexHTML) | ||
120 | ) | ||
92 | 121 | ||
93 | // --------------------------------------------------------------------------- | 122 | // --------------------------------------------------------------------------- |
94 | 123 | ||
diff --git a/server/controllers/feeds/comment-feeds.ts b/server/controllers/feeds/comment-feeds.ts index bdc53b51f..68dc9ef90 100644 --- a/server/controllers/feeds/comment-feeds.ts +++ b/server/controllers/feeds/comment-feeds.ts | |||
@@ -23,7 +23,7 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({ | |||
23 | 23 | ||
24 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
25 | 25 | ||
26 | commentFeedsRouter.get('/feeds/video-comments.:format', | 26 | commentFeedsRouter.get('/video-comments.:format', |
27 | feedsFormatValidator, | 27 | feedsFormatValidator, |
28 | setFeedFormatContentType, | 28 | setFeedFormatContentType, |
29 | cacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), | 29 | cacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), |
diff --git a/server/controllers/feeds/index.ts b/server/controllers/feeds/index.ts index e344a1448..19352318d 100644 --- a/server/controllers/feeds/index.ts +++ b/server/controllers/feeds/index.ts | |||
@@ -1,13 +1,22 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { CONFIG } from '@server/initializers/config' | ||
3 | import { buildRateLimiter } from '@server/middlewares' | ||
2 | import { commentFeedsRouter } from './comment-feeds' | 4 | import { commentFeedsRouter } from './comment-feeds' |
3 | import { videoFeedsRouter } from './video-feeds' | 5 | import { videoFeedsRouter } from './video-feeds' |
4 | import { videoPodcastFeedsRouter } from './video-podcast-feeds' | 6 | import { videoPodcastFeedsRouter } from './video-podcast-feeds' |
5 | 7 | ||
6 | const feedsRouter = express.Router() | 8 | const feedsRouter = express.Router() |
7 | 9 | ||
8 | feedsRouter.use('/', commentFeedsRouter) | 10 | const feedsRateLimiter = buildRateLimiter({ |
9 | feedsRouter.use('/', videoFeedsRouter) | 11 | windowMs: CONFIG.RATES_LIMIT.FEEDS.WINDOW_MS, |
10 | feedsRouter.use('/', videoPodcastFeedsRouter) | 12 | max: CONFIG.RATES_LIMIT.FEEDS.MAX |
13 | }) | ||
14 | |||
15 | feedsRouter.use('/feeds', feedsRateLimiter) | ||
16 | |||
17 | feedsRouter.use('/feeds', commentFeedsRouter) | ||
18 | feedsRouter.use('/feeds', videoFeedsRouter) | ||
19 | feedsRouter.use('/feeds', videoPodcastFeedsRouter) | ||
11 | 20 | ||
12 | // --------------------------------------------------------------------------- | 21 | // --------------------------------------------------------------------------- |
13 | 22 | ||
diff --git a/server/controllers/feeds/video-feeds.ts b/server/controllers/feeds/video-feeds.ts index b6e0663eb..97ac594ec 100644 --- a/server/controllers/feeds/video-feeds.ts +++ b/server/controllers/feeds/video-feeds.ts | |||
@@ -26,7 +26,7 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({ | |||
26 | 26 | ||
27 | // --------------------------------------------------------------------------- | 27 | // --------------------------------------------------------------------------- |
28 | 28 | ||
29 | videoFeedsRouter.get('/feeds/videos.:format', | 29 | videoFeedsRouter.get('/videos.:format', |
30 | videosSortValidator, | 30 | videosSortValidator, |
31 | setDefaultVideosSort, | 31 | setDefaultVideosSort, |
32 | feedsFormatValidator, | 32 | feedsFormatValidator, |
@@ -37,7 +37,7 @@ videoFeedsRouter.get('/feeds/videos.:format', | |||
37 | asyncMiddleware(generateVideoFeed) | 37 | asyncMiddleware(generateVideoFeed) |
38 | ) | 38 | ) |
39 | 39 | ||
40 | videoFeedsRouter.get('/feeds/subscriptions.:format', | 40 | videoFeedsRouter.get('/subscriptions.:format', |
41 | videosSortValidator, | 41 | videosSortValidator, |
42 | setDefaultVideosSort, | 42 | setDefaultVideosSort, |
43 | feedsFormatValidator, | 43 | feedsFormatValidator, |
diff --git a/server/controllers/feeds/video-podcast-feeds.ts b/server/controllers/feeds/video-podcast-feeds.ts index bd399580e..fca82ba68 100644 --- a/server/controllers/feeds/video-podcast-feeds.ts +++ b/server/controllers/feeds/video-podcast-feeds.ts | |||
@@ -40,7 +40,7 @@ for (const event of ([ 'channel-updated', 'channel-deleted' ] as const)) { | |||
40 | 40 | ||
41 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
42 | 42 | ||
43 | videoPodcastFeedsRouter.get('/feeds/podcast/videos.xml', | 43 | videoPodcastFeedsRouter.get('/podcast/videos.xml', |
44 | setFeedPodcastContentType, | 44 | setFeedPodcastContentType, |
45 | videoFeedsPodcastSetCacheKey, | 45 | videoFeedsPodcastSetCacheKey, |
46 | podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), | 46 | podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), |
diff --git a/server/controllers/index.ts b/server/controllers/index.ts index 09d657aca..8a647aff1 100644 --- a/server/controllers/index.ts +++ b/server/controllers/index.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | export * from './activitypub' | 1 | export * from './activitypub' |
2 | export * from './api' | 2 | export * from './api' |
3 | export * from './bots' | 3 | export * from './sitemap' |
4 | export * from './client' | 4 | export * from './client' |
5 | export * from './download' | 5 | export * from './download' |
6 | export * from './feeds' | 6 | export * from './feeds' |
diff --git a/server/controllers/misc.ts b/server/controllers/misc.ts index 163352ac5..a7dfc7867 100644 --- a/server/controllers/misc.ts +++ b/server/controllers/misc.ts | |||
@@ -7,7 +7,7 @@ import { HttpStatusCode } from '@shared/models' | |||
7 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model' | 7 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model' |
8 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION, ROUTE_CACHE_LIFETIME } from '../initializers/constants' | 8 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION, ROUTE_CACHE_LIFETIME } from '../initializers/constants' |
9 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' | 9 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' |
10 | import { asyncMiddleware } from '../middlewares' | 10 | import { apiRateLimiter, asyncMiddleware } from '../middlewares' |
11 | import { cacheRoute } from '../middlewares/cache/cache' | 11 | import { cacheRoute } from '../middlewares/cache/cache' |
12 | import { UserModel } from '../models/user/user' | 12 | import { UserModel } from '../models/user/user' |
13 | import { VideoModel } from '../models/video/video' | 13 | import { VideoModel } from '../models/video/video' |
@@ -18,12 +18,14 @@ const miscRouter = express.Router() | |||
18 | miscRouter.use(cors()) | 18 | miscRouter.use(cors()) |
19 | 19 | ||
20 | miscRouter.use('/nodeinfo/:version.json', | 20 | miscRouter.use('/nodeinfo/:version.json', |
21 | apiRateLimiter, | ||
21 | cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO), | 22 | cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO), |
22 | asyncMiddleware(generateNodeinfo) | 23 | asyncMiddleware(generateNodeinfo) |
23 | ) | 24 | ) |
24 | 25 | ||
25 | // robots.txt service | 26 | // robots.txt service |
26 | miscRouter.get('/robots.txt', | 27 | miscRouter.get('/robots.txt', |
28 | apiRateLimiter, | ||
27 | cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS), | 29 | cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS), |
28 | (_, res: express.Response) => { | 30 | (_, res: express.Response) => { |
29 | res.type('text/plain') | 31 | res.type('text/plain') |
@@ -33,12 +35,14 @@ miscRouter.get('/robots.txt', | |||
33 | ) | 35 | ) |
34 | 36 | ||
35 | miscRouter.all('/teapot', | 37 | miscRouter.all('/teapot', |
38 | apiRateLimiter, | ||
36 | getCup, | 39 | getCup, |
37 | asyncMiddleware(serveIndexHTML) | 40 | asyncMiddleware(serveIndexHTML) |
38 | ) | 41 | ) |
39 | 42 | ||
40 | // security.txt service | 43 | // security.txt service |
41 | miscRouter.get('/security.txt', | 44 | miscRouter.get('/security.txt', |
45 | apiRateLimiter, | ||
42 | (_, res: express.Response) => { | 46 | (_, res: express.Response) => { |
43 | return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt') | 47 | return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt') |
44 | } | 48 | } |
diff --git a/server/controllers/plugins.ts b/server/controllers/plugins.ts index 51db1ad89..f0491b16a 100644 --- a/server/controllers/plugins.ts +++ b/server/controllers/plugins.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { logger } from '@server/helpers/logger' | 3 | import { logger } from '@server/helpers/logger' |
4 | import { CONFIG } from '@server/initializers/config' | ||
5 | import { buildRateLimiter } from '@server/middlewares' | ||
4 | import { optionalAuthenticate } from '@server/middlewares/auth' | 6 | import { optionalAuthenticate } from '@server/middlewares/auth' |
5 | import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n' | 7 | import { getCompleteLocale, is18nLocale } from '../../shared/core-utils/i18n' |
6 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' | 8 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' |
@@ -18,57 +20,72 @@ const sendFileOptions = { | |||
18 | 20 | ||
19 | const pluginsRouter = express.Router() | 21 | const pluginsRouter = express.Router() |
20 | 22 | ||
23 | const pluginsRateLimiter = buildRateLimiter({ | ||
24 | windowMs: CONFIG.RATES_LIMIT.PLUGINS.WINDOW_MS, | ||
25 | max: CONFIG.RATES_LIMIT.PLUGINS.MAX | ||
26 | }) | ||
27 | |||
21 | pluginsRouter.get('/plugins/global.css', | 28 | pluginsRouter.get('/plugins/global.css', |
29 | pluginsRateLimiter, | ||
22 | servePluginGlobalCSS | 30 | servePluginGlobalCSS |
23 | ) | 31 | ) |
24 | 32 | ||
25 | pluginsRouter.get('/plugins/translations/:locale.json', | 33 | pluginsRouter.get('/plugins/translations/:locale.json', |
34 | pluginsRateLimiter, | ||
26 | getPluginTranslations | 35 | getPluginTranslations |
27 | ) | 36 | ) |
28 | 37 | ||
29 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName', | 38 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName', |
39 | pluginsRateLimiter, | ||
30 | getPluginValidator(PluginType.PLUGIN), | 40 | getPluginValidator(PluginType.PLUGIN), |
31 | getExternalAuthValidator, | 41 | getExternalAuthValidator, |
32 | handleAuthInPlugin | 42 | handleAuthInPlugin |
33 | ) | 43 | ) |
34 | 44 | ||
35 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', | 45 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', |
46 | pluginsRateLimiter, | ||
36 | getPluginValidator(PluginType.PLUGIN), | 47 | getPluginValidator(PluginType.PLUGIN), |
37 | pluginStaticDirectoryValidator, | 48 | pluginStaticDirectoryValidator, |
38 | servePluginStaticDirectory | 49 | servePluginStaticDirectory |
39 | ) | 50 | ) |
40 | 51 | ||
41 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', | 52 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', |
53 | pluginsRateLimiter, | ||
42 | getPluginValidator(PluginType.PLUGIN), | 54 | getPluginValidator(PluginType.PLUGIN), |
43 | pluginStaticDirectoryValidator, | 55 | pluginStaticDirectoryValidator, |
44 | servePluginClientScripts | 56 | servePluginClientScripts |
45 | ) | 57 | ) |
46 | 58 | ||
47 | pluginsRouter.use('/plugins/:pluginName/router', | 59 | pluginsRouter.use('/plugins/:pluginName/router', |
60 | pluginsRateLimiter, | ||
48 | getPluginValidator(PluginType.PLUGIN, false), | 61 | getPluginValidator(PluginType.PLUGIN, false), |
49 | optionalAuthenticate, | 62 | optionalAuthenticate, |
50 | servePluginCustomRoutes | 63 | servePluginCustomRoutes |
51 | ) | 64 | ) |
52 | 65 | ||
53 | pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router', | 66 | pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router', |
67 | pluginsRateLimiter, | ||
54 | getPluginValidator(PluginType.PLUGIN), | 68 | getPluginValidator(PluginType.PLUGIN), |
55 | optionalAuthenticate, | 69 | optionalAuthenticate, |
56 | servePluginCustomRoutes | 70 | servePluginCustomRoutes |
57 | ) | 71 | ) |
58 | 72 | ||
59 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)', | 73 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)', |
74 | pluginsRateLimiter, | ||
60 | getPluginValidator(PluginType.THEME), | 75 | getPluginValidator(PluginType.THEME), |
61 | pluginStaticDirectoryValidator, | 76 | pluginStaticDirectoryValidator, |
62 | servePluginStaticDirectory | 77 | servePluginStaticDirectory |
63 | ) | 78 | ) |
64 | 79 | ||
65 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', | 80 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', |
81 | pluginsRateLimiter, | ||
66 | getPluginValidator(PluginType.THEME), | 82 | getPluginValidator(PluginType.THEME), |
67 | pluginStaticDirectoryValidator, | 83 | pluginStaticDirectoryValidator, |
68 | servePluginClientScripts | 84 | servePluginClientScripts |
69 | ) | 85 | ) |
70 | 86 | ||
71 | pluginsRouter.get('/themes/:themeName/:themeVersion/css/:staticEndpoint(*)', | 87 | pluginsRouter.get('/themes/:themeName/:themeVersion/css/:staticEndpoint(*)', |
88 | pluginsRateLimiter, | ||
72 | serveThemeCSSValidator, | 89 | serveThemeCSSValidator, |
73 | serveThemeCSSDirectory | 90 | serveThemeCSSDirectory |
74 | ) | 91 | ) |
diff --git a/server/controllers/services.ts b/server/controllers/services.ts index 7c7ca1ff3..0fd63a30f 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts | |||
@@ -2,17 +2,19 @@ import express from 'express' | |||
2 | import { MChannelSummary } from '@server/types/models' | 2 | import { MChannelSummary } from '@server/types/models' |
3 | import { escapeHTML } from '@shared/core-utils/renderer' | 3 | import { escapeHTML } from '@shared/core-utils/renderer' |
4 | import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' | 4 | import { EMBED_SIZE, PREVIEWS_SIZE, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' |
5 | import { asyncMiddleware, oembedValidator } from '../middlewares' | 5 | import { apiRateLimiter, asyncMiddleware, oembedValidator } from '../middlewares' |
6 | import { accountNameWithHostGetValidator } from '../middlewares/validators' | 6 | import { accountNameWithHostGetValidator } from '../middlewares/validators' |
7 | import { forceNumber } from '@shared/core-utils' | 7 | import { forceNumber } from '@shared/core-utils' |
8 | 8 | ||
9 | const servicesRouter = express.Router() | 9 | const servicesRouter = express.Router() |
10 | 10 | ||
11 | servicesRouter.use('/oembed', | 11 | servicesRouter.use('/oembed', |
12 | apiRateLimiter, | ||
12 | asyncMiddleware(oembedValidator), | 13 | asyncMiddleware(oembedValidator), |
13 | generateOEmbed | 14 | generateOEmbed |
14 | ) | 15 | ) |
15 | servicesRouter.use('/redirect/accounts/:accountName', | 16 | servicesRouter.use('/redirect/accounts/:accountName', |
17 | apiRateLimiter, | ||
16 | asyncMiddleware(accountNameWithHostGetValidator), | 18 | asyncMiddleware(accountNameWithHostGetValidator), |
17 | redirectToAccountUrl | 19 | redirectToAccountUrl |
18 | ) | 20 | ) |
diff --git a/server/controllers/bots.ts b/server/controllers/sitemap.ts index 2b825a730..07f4c554e 100644 --- a/server/controllers/bots.ts +++ b/server/controllers/sitemap.ts | |||
@@ -5,17 +5,16 @@ import { logger } from '@server/helpers/logger' | |||
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { buildNSFWFilter } from '../helpers/express-utils' | 6 | import { buildNSFWFilter } from '../helpers/express-utils' |
7 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' | 7 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' |
8 | import { asyncMiddleware } from '../middlewares' | 8 | import { apiRateLimiter, asyncMiddleware } from '../middlewares' |
9 | import { cacheRoute } from '../middlewares/cache/cache' | 9 | import { cacheRoute } from '../middlewares/cache/cache' |
10 | import { AccountModel } from '../models/account/account' | 10 | import { AccountModel } from '../models/account/account' |
11 | import { VideoModel } from '../models/video/video' | 11 | import { VideoModel } from '../models/video/video' |
12 | import { VideoChannelModel } from '../models/video/video-channel' | 12 | import { VideoChannelModel } from '../models/video/video-channel' |
13 | 13 | ||
14 | const botsRouter = express.Router() | 14 | const sitemapRouter = express.Router() |
15 | 15 | ||
16 | // Special route that add OpenGraph and oEmbed tags | 16 | sitemapRouter.use('/sitemap.xml', |
17 | // Do not use a template engine for a so little thing | 17 | apiRateLimiter, |
18 | botsRouter.use('/sitemap.xml', | ||
19 | cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP), | 18 | cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP), |
20 | asyncMiddleware(getSitemap) | 19 | asyncMiddleware(getSitemap) |
21 | ) | 20 | ) |
@@ -23,7 +22,7 @@ botsRouter.use('/sitemap.xml', | |||
23 | // --------------------------------------------------------------------------- | 22 | // --------------------------------------------------------------------------- |
24 | 23 | ||
25 | export { | 24 | export { |
26 | botsRouter | 25 | sitemapRouter |
27 | } | 26 | } |
28 | 27 | ||
29 | // --------------------------------------------------------------------------- | 28 | // --------------------------------------------------------------------------- |
diff --git a/server/controllers/well-known.ts b/server/controllers/well-known.ts index bb9acfb37..322cf6ea2 100644 --- a/server/controllers/well-known.ts +++ b/server/controllers/well-known.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import cors from 'cors' | 1 | import cors from 'cors' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { asyncMiddleware, handleStaticError, webfingerValidator } from '@server/middlewares' | 4 | import { asyncMiddleware, buildRateLimiter, handleStaticError, webfingerValidator } from '@server/middlewares' |
5 | import { root } from '@shared/core-utils' | 5 | import { root } from '@shared/core-utils' |
6 | import { CONFIG } from '../initializers/config' | 6 | import { CONFIG } from '../initializers/config' |
7 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' | 7 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' |
@@ -9,14 +9,21 @@ import { cacheRoute } from '../middlewares/cache/cache' | |||
9 | 9 | ||
10 | const wellKnownRouter = express.Router() | 10 | const wellKnownRouter = express.Router() |
11 | 11 | ||
12 | const wellKnownRateLimiter = buildRateLimiter({ | ||
13 | windowMs: CONFIG.RATES_LIMIT.WELL_KNOWN.WINDOW_MS, | ||
14 | max: CONFIG.RATES_LIMIT.WELL_KNOWN.MAX | ||
15 | }) | ||
16 | |||
12 | wellKnownRouter.use(cors()) | 17 | wellKnownRouter.use(cors()) |
13 | 18 | ||
14 | wellKnownRouter.get('/.well-known/webfinger', | 19 | wellKnownRouter.get('/.well-known/webfinger', |
20 | wellKnownRateLimiter, | ||
15 | asyncMiddleware(webfingerValidator), | 21 | asyncMiddleware(webfingerValidator), |
16 | webfingerController | 22 | webfingerController |
17 | ) | 23 | ) |
18 | 24 | ||
19 | wellKnownRouter.get('/.well-known/security.txt', | 25 | wellKnownRouter.get('/.well-known/security.txt', |
26 | wellKnownRateLimiter, | ||
20 | cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT), | 27 | cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT), |
21 | (_, res: express.Response) => { | 28 | (_, res: express.Response) => { |
22 | res.type('text/plain') | 29 | res.type('text/plain') |
@@ -26,6 +33,7 @@ wellKnownRouter.get('/.well-known/security.txt', | |||
26 | 33 | ||
27 | // nodeinfo service | 34 | // nodeinfo service |
28 | wellKnownRouter.use('/.well-known/nodeinfo', | 35 | wellKnownRouter.use('/.well-known/nodeinfo', |
36 | wellKnownRateLimiter, | ||
29 | cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO), | 37 | cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO), |
30 | (_, res: express.Response) => { | 38 | (_, res: express.Response) => { |
31 | return res.json({ | 39 | return res.json({ |
@@ -41,6 +49,7 @@ wellKnownRouter.use('/.well-known/nodeinfo', | |||
41 | 49 | ||
42 | // dnt-policy.txt service (see https://www.eff.org/dnt-policy) | 50 | // dnt-policy.txt service (see https://www.eff.org/dnt-policy) |
43 | wellKnownRouter.use('/.well-known/dnt-policy.txt', | 51 | wellKnownRouter.use('/.well-known/dnt-policy.txt', |
52 | wellKnownRateLimiter, | ||
44 | cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY), | 53 | cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY), |
45 | (_, res: express.Response) => { | 54 | (_, res: express.Response) => { |
46 | res.type('text/plain') | 55 | res.type('text/plain') |
@@ -51,18 +60,21 @@ wellKnownRouter.use('/.well-known/dnt-policy.txt', | |||
51 | 60 | ||
52 | // dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource) | 61 | // dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource) |
53 | wellKnownRouter.use('/.well-known/dnt/', | 62 | wellKnownRouter.use('/.well-known/dnt/', |
63 | wellKnownRateLimiter, | ||
54 | (_, res: express.Response) => { | 64 | (_, res: express.Response) => { |
55 | res.json({ tracking: 'N' }) | 65 | res.json({ tracking: 'N' }) |
56 | } | 66 | } |
57 | ) | 67 | ) |
58 | 68 | ||
59 | wellKnownRouter.use('/.well-known/change-password', | 69 | wellKnownRouter.use('/.well-known/change-password', |
70 | wellKnownRateLimiter, | ||
60 | (_, res: express.Response) => { | 71 | (_, res: express.Response) => { |
61 | res.redirect('/my-account/settings') | 72 | res.redirect('/my-account/settings') |
62 | } | 73 | } |
63 | ) | 74 | ) |
64 | 75 | ||
65 | wellKnownRouter.use('/.well-known/host-meta', | 76 | wellKnownRouter.use('/.well-known/host-meta', |
77 | wellKnownRateLimiter, | ||
66 | (_, res: express.Response) => { | 78 | (_, res: express.Response) => { |
67 | res.type('application/xml') | 79 | res.type('application/xml') |
68 | 80 | ||
@@ -76,6 +88,7 @@ wellKnownRouter.use('/.well-known/host-meta', | |||
76 | ) | 88 | ) |
77 | 89 | ||
78 | wellKnownRouter.use('/.well-known/', | 90 | wellKnownRouter.use('/.well-known/', |
91 | wellKnownRateLimiter, | ||
79 | cacheRoute(ROUTE_CACHE_LIFETIME.WELL_KNOWN), | 92 | cacheRoute(ROUTE_CACHE_LIFETIME.WELL_KNOWN), |
80 | express.static(CONFIG.STORAGE.WELL_KNOWN_DIR, { fallthrough: false }), | 93 | express.static(CONFIG.STORAGE.WELL_KNOWN_DIR, { fallthrough: false }), |
81 | handleStaticError | 94 | handleStaticError |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index f77b0defb..0139ded4f 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -56,7 +56,11 @@ function checkMissedConfig () { | |||
56 | 'followers.instance.enabled', 'followers.instance.manual_approval', | 56 | 'followers.instance.enabled', 'followers.instance.manual_approval', |
57 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', | 57 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', |
58 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', | 58 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', |
59 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', | 59 | 'rates_limit.api.window', 'rates_limit.api.max', 'rates_limit.login.window', 'rates_limit.login.max', |
60 | 'rates_limit.signup.window', 'rates_limit.signup.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', | ||
61 | 'rates_limit.receive_client_log.window', 'rates_limit.receive_client_log.max', 'rates_limit.plugins.window', 'rates_limit.plugins.max', | ||
62 | 'rates_limit.well_known.window', 'rates_limit.well_known.max', 'rates_limit.feeds.window', 'rates_limit.feeds.max', | ||
63 | 'rates_limit.activity_pub.window', 'rates_limit.activity_pub.max', 'rates_limit.client.window', 'rates_limit.client.max', | ||
60 | 'static_files.private_files_require_auth', | 64 | 'static_files.private_files_require_auth', |
61 | 'object_storage.enabled', 'object_storage.endpoint', 'object_storage.region', 'object_storage.upload_acl.public', | 65 | 'object_storage.enabled', 'object_storage.endpoint', 'object_storage.region', 'object_storage.upload_acl.public', |
62 | 'object_storage.upload_acl.private', 'object_storage.proxy.proxify_private_files', 'object_storage.credentials.access_key_id', | 66 | 'object_storage.upload_acl.private', 'object_storage.proxy.proxify_private_files', 'object_storage.credentials.access_key_id', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index f12d9b85a..2724990c1 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -183,6 +183,26 @@ const CONFIG = { | |||
183 | ASK_SEND_EMAIL: { | 183 | ASK_SEND_EMAIL: { |
184 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')), | 184 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')), |
185 | MAX: config.get<number>('rates_limit.ask_send_email.max') | 185 | MAX: config.get<number>('rates_limit.ask_send_email.max') |
186 | }, | ||
187 | PLUGINS: { | ||
188 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.plugins.window')), | ||
189 | MAX: config.get<number>('rates_limit.plugins.max') | ||
190 | }, | ||
191 | WELL_KNOWN: { | ||
192 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.well_known.window')), | ||
193 | MAX: config.get<number>('rates_limit.well_known.max') | ||
194 | }, | ||
195 | FEEDS: { | ||
196 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.feeds.window')), | ||
197 | MAX: config.get<number>('rates_limit.feeds.max') | ||
198 | }, | ||
199 | ACTIVITY_PUB: { | ||
200 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.activity_pub.window')), | ||
201 | MAX: config.get<number>('rates_limit.activity_pub.max') | ||
202 | }, | ||
203 | CLIENT: { | ||
204 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.client.window')), | ||
205 | MAX: config.get<number>('rates_limit.client.max') | ||
186 | } | 206 | } |
187 | }, | 207 | }, |
188 | TRUST_PROXY: config.get<string[]>('trust_proxy'), | 208 | TRUST_PROXY: config.get<string[]>('trust_proxy'), |
diff --git a/server/middlewares/rate-limiter.ts b/server/middlewares/rate-limiter.ts index 8257965dd..143d43632 100644 --- a/server/middlewares/rate-limiter.ts +++ b/server/middlewares/rate-limiter.ts | |||
@@ -45,6 +45,11 @@ export const apiRateLimiter = buildRateLimiter({ | |||
45 | max: CONFIG.RATES_LIMIT.API.MAX | 45 | max: CONFIG.RATES_LIMIT.API.MAX |
46 | }) | 46 | }) |
47 | 47 | ||
48 | export const activityPubRateLimiter = buildRateLimiter({ | ||
49 | windowMs: CONFIG.RATES_LIMIT.ACTIVITY_PUB.WINDOW_MS, | ||
50 | max: CONFIG.RATES_LIMIT.ACTIVITY_PUB.MAX | ||
51 | }) | ||
52 | |||
48 | // --------------------------------------------------------------------------- | 53 | // --------------------------------------------------------------------------- |
49 | // Private | 54 | // Private |
50 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
diff --git a/server/tests/api/check-params/video-source.ts b/server/tests/api/check-params/video-source.ts index 3c641ccd3..767590d5e 100644 --- a/server/tests/api/check-params/video-source.ts +++ b/server/tests/api/check-params/video-source.ts | |||
@@ -114,7 +114,7 @@ describe('Test video sources API validator', function () { | |||
114 | await server.videos.replaceSourceFile({ | 114 | await server.videos.replaceSourceFile({ |
115 | fixture: 'video_short_fake.webm', | 115 | fixture: 'video_short_fake.webm', |
116 | videoId, | 116 | videoId, |
117 | expectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422 | 117 | completedExpectedStatus: HttpStatusCode.UNPROCESSABLE_ENTITY_422 |
118 | }) | 118 | }) |
119 | 119 | ||
120 | await server.videos.replaceSourceFile({ | 120 | await server.videos.replaceSourceFile({ |
diff --git a/server/tests/api/search/search-activitypub-video-playlists.ts b/server/tests/api/search/search-activitypub-video-playlists.ts index 25b162074..2bb5d869a 100644 --- a/server/tests/api/search/search-activitypub-video-playlists.ts +++ b/server/tests/api/search/search-activitypub-video-playlists.ts | |||
@@ -23,7 +23,7 @@ describe('Test ActivityPub playlists search', function () { | |||
23 | let command: SearchCommand | 23 | let command: SearchCommand |
24 | 24 | ||
25 | before(async function () { | 25 | before(async function () { |
26 | this.timeout(120000) | 26 | this.timeout(240000) |
27 | 27 | ||
28 | servers = await createMultipleServers(2) | 28 | servers = await createMultipleServers(2) |
29 | 29 | ||