diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-11-09 16:25:27 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-11-25 11:07:56 +0100 |
commit | 5beb89f223539f1e415a976ff104f772526b4d20 (patch) | |
tree | 2164677d16a2965d63499e249aa75ab0e06e3a6c /server | |
parent | afff310e50f2fa8419bb4242470cbde46ab54463 (diff) | |
download | PeerTube-5beb89f223539f1e415a976ff104f772526b4d20.tar.gz PeerTube-5beb89f223539f1e415a976ff104f772526b4d20.tar.zst PeerTube-5beb89f223539f1e415a976ff104f772526b4d20.zip |
refactor scoped token service
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/feeds.ts | 155 | ||||
-rw-r--r-- | server/helpers/middlewares/accounts.ts | 8 | ||||
-rw-r--r-- | server/middlewares/validators/feeds.ts | 5 | ||||
-rw-r--r-- | server/tests/feeds/feeds.ts | 8 |
4 files changed, 107 insertions, 69 deletions
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 6e9f7e60c..5c95069fc 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -18,7 +18,6 @@ import { cacheRoute } from '../middlewares/cache' | |||
18 | import { VideoModel } from '../models/video/video' | 18 | import { VideoModel } from '../models/video/video' |
19 | import { VideoCommentModel } from '../models/video/video-comment' | 19 | import { VideoCommentModel } from '../models/video/video-comment' |
20 | import { VideoFilter } from '../../shared/models/videos/video-query.type' | 20 | import { VideoFilter } from '../../shared/models/videos/video-query.type' |
21 | import { logger } from '../helpers/logger' | ||
22 | 21 | ||
23 | const feedsRouter = express.Router() | 22 | const feedsRouter = express.Router() |
24 | 23 | ||
@@ -47,10 +46,24 @@ feedsRouter.get('/feeds/videos.:format', | |||
47 | })(ROUTE_CACHE_LIFETIME.FEEDS)), | 46 | })(ROUTE_CACHE_LIFETIME.FEEDS)), |
48 | commonVideosFiltersValidator, | 47 | commonVideosFiltersValidator, |
49 | asyncMiddleware(videoFeedsValidator), | 48 | asyncMiddleware(videoFeedsValidator), |
50 | asyncMiddleware(videoSubscriptonFeedsValidator), | ||
51 | asyncMiddleware(generateVideoFeed) | 49 | asyncMiddleware(generateVideoFeed) |
52 | ) | 50 | ) |
53 | 51 | ||
52 | feedsRouter.get('/feeds/subscriptions.:format', | ||
53 | videosSortValidator, | ||
54 | setDefaultVideosSort, | ||
55 | feedsFormatValidator, | ||
56 | setFeedFormatContentType, | ||
57 | asyncMiddleware(cacheRoute({ | ||
58 | headerBlacklist: [ | ||
59 | 'Content-Type' | ||
60 | ] | ||
61 | })(ROUTE_CACHE_LIFETIME.FEEDS)), | ||
62 | commonVideosFiltersValidator, | ||
63 | asyncMiddleware(videoSubscriptonFeedsValidator), | ||
64 | asyncMiddleware(generateVideoFeedForSubscriptions) | ||
65 | ) | ||
66 | |||
54 | // --------------------------------------------------------------------------- | 67 | // --------------------------------------------------------------------------- |
55 | 68 | ||
56 | export { | 69 | export { |
@@ -61,7 +74,6 @@ export { | |||
61 | 74 | ||
62 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { | 75 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { |
63 | const start = 0 | 76 | const start = 0 |
64 | |||
65 | const video = res.locals.videoAll | 77 | const video = res.locals.videoAll |
66 | const account = res.locals.account | 78 | const account = res.locals.account |
67 | const videoChannel = res.locals.videoChannel | 79 | const videoChannel = res.locals.videoChannel |
@@ -125,10 +137,8 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
125 | 137 | ||
126 | async function generateVideoFeed (req: express.Request, res: express.Response) { | 138 | async function generateVideoFeed (req: express.Request, res: express.Response) { |
127 | const start = 0 | 139 | const start = 0 |
128 | |||
129 | const account = res.locals.account | 140 | const account = res.locals.account |
130 | const videoChannel = res.locals.videoChannel | 141 | const videoChannel = res.locals.videoChannel |
131 | const token = req.query.token | ||
132 | const nsfw = buildNSFWFilter(res, req.query.nsfw) | 142 | const nsfw = buildNSFWFilter(res, req.query.nsfw) |
133 | 143 | ||
134 | let name: string | 144 | let name: string |
@@ -152,21 +162,10 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
152 | queryString: new URL(WEBSERVER.URL + req.url).search | 162 | queryString: new URL(WEBSERVER.URL + req.url).search |
153 | }) | 163 | }) |
154 | 164 | ||
155 | /** | 165 | const options = { |
156 | * We have two ways to query video results: | 166 | accountId: account ? account.id : null, |
157 | * - one with account and token -> get subscription videos | 167 | videoChannelId: videoChannel ? videoChannel.id : null |
158 | * - one with either account, channel, or nothing: just videos with these filters | 168 | } |
159 | */ | ||
160 | const options = token && token !== '' && res.locals.user | ||
161 | ? { | ||
162 | followerActorId: res.locals.user.Account.Actor.id, | ||
163 | user: res.locals.user, | ||
164 | includeLocalVideos: false | ||
165 | } | ||
166 | : { | ||
167 | accountId: account ? account.id : null, | ||
168 | videoChannelId: videoChannel ? videoChannel.id : null | ||
169 | } | ||
170 | 169 | ||
171 | const resultList = await VideoModel.listForApi({ | 170 | const resultList = await VideoModel.listForApi({ |
172 | start, | 171 | start, |
@@ -179,10 +178,86 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
179 | ...options | 178 | ...options |
180 | }) | 179 | }) |
181 | 180 | ||
181 | addVideosToFeed(feed, resultList.data) | ||
182 | |||
183 | // Now the feed generation is done, let's send it! | ||
184 | return sendFeed(feed, req, res) | ||
185 | } | ||
186 | |||
187 | async function generateVideoFeedForSubscriptions (req: express.Request, res: express.Response) { | ||
188 | const start = 0 | ||
189 | const account = res.locals.account | ||
190 | const nsfw = buildNSFWFilter(res, req.query.nsfw) | ||
191 | const name = account.getDisplayName() | ||
192 | const description = account.description | ||
193 | |||
194 | const feed = initFeed({ | ||
195 | name, | ||
196 | description, | ||
197 | resourceType: 'videos', | ||
198 | queryString: new URL(WEBSERVER.URL + req.url).search | ||
199 | }) | ||
200 | |||
201 | const options = { | ||
202 | followerActorId: res.locals.user.Account.Actor.id, | ||
203 | user: res.locals.user | ||
204 | } | ||
205 | |||
206 | const resultList = await VideoModel.listForApi({ | ||
207 | start, | ||
208 | count: FEEDS.COUNT, | ||
209 | sort: req.query.sort, | ||
210 | includeLocalVideos: true, | ||
211 | nsfw, | ||
212 | filter: req.query.filter as VideoFilter, | ||
213 | withFiles: true, | ||
214 | ...options | ||
215 | }) | ||
216 | |||
217 | addVideosToFeed(feed, resultList.data) | ||
218 | |||
219 | // Now the feed generation is done, let's send it! | ||
220 | return sendFeed(feed, req, res) | ||
221 | } | ||
222 | |||
223 | function initFeed (parameters: { | ||
224 | name: string | ||
225 | description: string | ||
226 | resourceType?: 'videos' | 'video-comments' | ||
227 | queryString?: string | ||
228 | }) { | ||
229 | const webserverUrl = WEBSERVER.URL | ||
230 | const { name, description, resourceType, queryString } = parameters | ||
231 | |||
232 | return new Feed({ | ||
233 | title: name, | ||
234 | description, | ||
235 | // updated: TODO: somehowGetLatestUpdate, // optional, default = today | ||
236 | id: webserverUrl, | ||
237 | link: webserverUrl, | ||
238 | image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', | ||
239 | favicon: webserverUrl + '/client/assets/images/favicon.png', | ||
240 | copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` + | ||
241 | ` and potential licenses granted by each content's rightholder.`, | ||
242 | generator: `Toraifōsu`, // ^.~ | ||
243 | feedLinks: { | ||
244 | json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`, | ||
245 | atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`, | ||
246 | rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}` | ||
247 | }, | ||
248 | author: { | ||
249 | name: 'Instance admin of ' + CONFIG.INSTANCE.NAME, | ||
250 | email: CONFIG.ADMIN.EMAIL, | ||
251 | link: `${webserverUrl}/about` | ||
252 | } | ||
253 | }) | ||
254 | } | ||
255 | |||
256 | function addVideosToFeed (feed, videos: VideoModel[]) { | ||
182 | /** | 257 | /** |
183 | * Adding video items to the feed object, one at a time | 258 | * Adding video items to the feed object, one at a time |
184 | */ | 259 | */ |
185 | resultList.data.forEach(video => { | 260 | for (const video of videos) { |
186 | const formattedVideoFiles = video.getFormattedVideoFilesJSON() | 261 | const formattedVideoFiles = video.getFormattedVideoFilesJSON() |
187 | 262 | ||
188 | const torrents = formattedVideoFiles.map(videoFile => ({ | 263 | const torrents = formattedVideoFiles.map(videoFile => ({ |
@@ -252,43 +327,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
252 | } | 327 | } |
253 | ] | 328 | ] |
254 | }) | 329 | }) |
255 | }) | 330 | } |
256 | |||
257 | // Now the feed generation is done, let's send it! | ||
258 | return sendFeed(feed, req, res) | ||
259 | } | ||
260 | |||
261 | function initFeed (parameters: { | ||
262 | name: string | ||
263 | description: string | ||
264 | resourceType?: 'videos' | 'video-comments' | ||
265 | queryString?: string | ||
266 | }) { | ||
267 | const webserverUrl = WEBSERVER.URL | ||
268 | const { name, description, resourceType, queryString } = parameters | ||
269 | |||
270 | return new Feed({ | ||
271 | title: name, | ||
272 | description, | ||
273 | // updated: TODO: somehowGetLatestUpdate, // optional, default = today | ||
274 | id: webserverUrl, | ||
275 | link: webserverUrl, | ||
276 | image: webserverUrl + '/client/assets/images/icons/icon-96x96.png', | ||
277 | favicon: webserverUrl + '/client/assets/images/favicon.png', | ||
278 | copyright: `All rights reserved, unless otherwise specified in the terms specified at ${webserverUrl}/about` + | ||
279 | ` and potential licenses granted by each content's rightholder.`, | ||
280 | generator: `Toraifōsu`, // ^.~ | ||
281 | feedLinks: { | ||
282 | json: `${webserverUrl}/feeds/${resourceType}.json${queryString}`, | ||
283 | atom: `${webserverUrl}/feeds/${resourceType}.atom${queryString}`, | ||
284 | rss: `${webserverUrl}/feeds/${resourceType}.xml${queryString}` | ||
285 | }, | ||
286 | author: { | ||
287 | name: 'Instance admin of ' + CONFIG.INSTANCE.NAME, | ||
288 | email: CONFIG.ADMIN.EMAIL, | ||
289 | link: `${webserverUrl}/about` | ||
290 | } | ||
291 | }) | ||
292 | } | 331 | } |
293 | 332 | ||
294 | function sendFeed (feed, req: express.Request, res: express.Response) { | 333 | function sendFeed (feed, req: express.Request, res: express.Response) { |
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 9be80167c..fa4a51e6c 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts | |||
@@ -28,8 +28,7 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se | |||
28 | if (!account) { | 28 | if (!account) { |
29 | if (sendNotFound === true) { | 29 | if (sendNotFound === true) { |
30 | res.status(404) | 30 | res.status(404) |
31 | .send({ error: 'Account not found' }) | 31 | .json({ error: 'Account not found' }) |
32 | .end() | ||
33 | } | 32 | } |
34 | 33 | ||
35 | return false | 34 | return false |
@@ -41,12 +40,11 @@ async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, se | |||
41 | } | 40 | } |
42 | 41 | ||
43 | async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) { | 42 | async function doesUserFeedTokenCorrespond (id: number | string, token: string, res: Response) { |
44 | const user = await UserModel.loadById(parseInt(id + '', 10)) | 43 | const user = await UserModel.loadByIdWithChannels(parseInt(id + '', 10)) |
45 | 44 | ||
46 | if (token !== user.feedToken) { | 45 | if (token !== user.feedToken) { |
47 | res.status(401) | 46 | res.status(401) |
48 | .send({ error: 'User and token mismatch' }) | 47 | .json({ error: 'User and token mismatch' }) |
49 | .end() | ||
50 | 48 | ||
51 | return false | 49 | return false |
52 | } | 50 | } |
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index 5c76a679f..35080ffca 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts | |||
@@ -64,8 +64,8 @@ const videoFeedsValidator = [ | |||
64 | ] | 64 | ] |
65 | 65 | ||
66 | const videoSubscriptonFeedsValidator = [ | 66 | const videoSubscriptonFeedsValidator = [ |
67 | query('accountId').optional().custom(isIdValid), | 67 | query('accountId').custom(isIdValid), |
68 | query('token').optional(), | 68 | query('token'), |
69 | 69 | ||
70 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 70 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
71 | logger.debug('Checking feeds parameters', { parameters: req.query }) | 71 | logger.debug('Checking feeds parameters', { parameters: req.query }) |
@@ -74,6 +74,7 @@ const videoSubscriptonFeedsValidator = [ | |||
74 | 74 | ||
75 | // a token alone is erroneous | 75 | // a token alone is erroneous |
76 | if (req.query.token && !req.query.accountId) return | 76 | if (req.query.token && !req.query.accountId) return |
77 | if (req.query.accountId && !await doesAccountIdExist(req.query.accountId, res)) return | ||
77 | if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return | 78 | if (req.query.token && !await doesUserFeedTokenCorrespond(res.locals.account.userId, req.query.token, res)) return |
78 | 79 | ||
79 | return next() | 80 | return next() |
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 2cd9b2d0a..175ea9102 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -326,7 +326,7 @@ describe('Test syndication feeds', () => { | |||
326 | const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken) | 326 | const res = await listUserSubscriptionVideos(servers[0].url, feeduserAccessToken) |
327 | expect(res.body.total).to.equal(0) | 327 | expect(res.body.total).to.equal(0) |
328 | 328 | ||
329 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: feeduserAccountId, token: feeduserFeedToken }) | 329 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: feeduserAccountId, token: feeduserFeedToken }) |
330 | const jsonObj = JSON.parse(json.text) | 330 | const jsonObj = JSON.parse(json.text) |
331 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos | 331 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos |
332 | } | 332 | } |
@@ -337,7 +337,7 @@ describe('Test syndication feeds', () => { | |||
337 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) | 337 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) |
338 | expect(res.body.total).to.equal(0) | 338 | expect(res.body.total).to.equal(0) |
339 | 339 | ||
340 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken }) | 340 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken }) |
341 | const jsonObj = JSON.parse(json.text) | 341 | const jsonObj = JSON.parse(json.text) |
342 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos | 342 | expect(jsonObj.items.length).to.be.equal(0) // no subscription, it should not list the instance's videos but list 0 videos |
343 | } | 343 | } |
@@ -354,7 +354,7 @@ describe('Test syndication feeds', () => { | |||
354 | expect(res.body.total).to.equal(1) | 354 | expect(res.body.total).to.equal(1) |
355 | expect(res.body.data[0].name).to.equal('user video') | 355 | expect(res.body.data[0].name).to.equal('user video') |
356 | 356 | ||
357 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 1 }) | 357 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 1 }) |
358 | const jsonObj = JSON.parse(json.text) | 358 | const jsonObj = JSON.parse(json.text) |
359 | expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's | 359 | expect(jsonObj.items.length).to.be.equal(1) // subscribed to self, it should not list the instance's videos but list john's |
360 | } | 360 | } |
@@ -370,7 +370,7 @@ describe('Test syndication feeds', () => { | |||
370 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) | 370 | const res = await listUserSubscriptionVideos(servers[0].url, userAccessToken) |
371 | expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription") | 371 | expect(res.body.total).to.equal(2, "there should be 2 videos part of the subscription") |
372 | 372 | ||
373 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId, token: userFeedToken, version: 2 }) | 373 | const json = await getJSONfeed(servers[0].url, 'subscriptions', { accountId: userAccountId, token: userFeedToken, version: 2 }) |
374 | const jsonObj = JSON.parse(json.text) | 374 | const jsonObj = JSON.parse(json.text) |
375 | expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's | 375 | expect(jsonObj.items.length).to.be.equal(2) // subscribed to root, it should not list the instance's videos but list root/john's |
376 | } | 376 | } |