]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/middlewares/validators/videos/videos.ts
Allow users/visitors to search through an account's videos (#3589)
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / videos.ts
CommitLineData
69818c93 1import * as express from 'express'
c8861d5d 2import { body, param, query, ValidationChain } from 'express-validator'
fb719404 3import { isAbleToUploadVideo } from '@server/lib/user'
e6abf95e
C
4import { getServerActor } from '@server/models/application/application'
5import { MVideoFullLight } from '@server/types/models'
6import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
7import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
b60e5f38 8import {
37024082 9 exists,
2baea0c7
C
10 isBooleanValid,
11 isDateValid,
f2eb23cd 12 isFileFieldValid,
2baea0c7
C
13 isIdOrUUIDValid,
14 isIdValid,
15 isUUIDValid,
1cd3facc 16 toArray,
c8861d5d 17 toBooleanOrNull,
2baea0c7
C
18 toIntOrNull,
19 toValueOrNull
6e46de09 20} from '../../../helpers/custom-validators/misc'
e6abf95e
C
21import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
22import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
2baea0c7
C
23import {
24 isScheduleVideoUpdatePrivacyValid,
ac81d1a0
C
25 isVideoCategoryValid,
26 isVideoDescriptionValid,
f2eb23cd
RK
27 isVideoFileMimeTypeValid,
28 isVideoFileSizeValid,
1cd3facc 29 isVideoFilterValid,
ac81d1a0
C
30 isVideoImage,
31 isVideoLanguageValid,
32 isVideoLicenceValid,
33 isVideoNameValid,
fd8710b8 34 isVideoOriginallyPublishedAtValid,
ac81d1a0 35 isVideoPrivacyValid,
360329cc 36 isVideoSupportValid,
4157cdb1 37 isVideoTagsValid
6e46de09 38} from '../../../helpers/custom-validators/videos'
e6abf95e 39import { cleanUpReqFiles } from '../../../helpers/express-utils'
daf6e480 40import { getDurationFromVideoFile } from '../../../helpers/ffprobe-utils'
6e46de09 41import { logger } from '../../../helpers/logger'
8319d6ae
RK
42import {
43 checkUserCanManageVideo,
44 doesVideoChannelOfAccountExist,
45 doesVideoExist,
46 doesVideoFileOfVideoExist
47} from '../../../helpers/middlewares'
0283eaac 48import { getVideoWithAttributes } from '../../../helpers/video'
e6abf95e
C
49import { CONFIG } from '../../../initializers/config'
50import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
51import { isLocalVideoAccepted } from '../../../lib/moderation'
52import { Hooks } from '../../../lib/plugins/hooks'
53import { AccountModel } from '../../../models/account/account'
54import { VideoModel } from '../../../models/video/video'
55import { authenticatePromiseIfNeeded } from '../../oauth'
56import { areValidationErrors } from '../utils'
2d53be02 57import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
34ca3b52 58
418d092a 59const videosAddValidator = getCommonVideoEditAttributes().concat([
0c237b19 60 body('videofile')
f2eb23cd
RK
61 .custom((value, { req }) => isFileFieldValid(req.files, 'videofile'))
62 .withMessage('Should have a file'),
63 body('name')
64 .custom(isVideoNameValid)
65 .withMessage('Should have a valid name'),
0f320037 66 body('channelId')
c8861d5d 67 .customSanitizer(toIntOrNull)
2baea0c7 68 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 69
a2431b7d 70 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
71 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
72
cf7a61b5
C
73 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
74 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
a2431b7d 75
b4055e1c 76 const videoFile: Express.Multer.File & { duration?: number } = req.files['videofile'][0]
a2431b7d 77 const user = res.locals.oauth.token.User
b60e5f38 78
0f6acda1 79 if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
a2431b7d 80
f2eb23cd
RK
81 if (!isVideoFileMimeTypeValid(req.files)) {
82 res.status(HttpStatusCode.UNSUPPORTED_MEDIA_TYPE_415)
83 .json({
84 error: 'This file is not supported. Please, make sure it is of the following type: ' +
85 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
86 })
87
88 return cleanUpReqFiles(req)
89 }
90
91 if (!isVideoFileSizeValid(videoFile.size.toString())) {
92 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
93 .json({
94 error: 'This file is too large.'
95 })
96
97 return cleanUpReqFiles(req)
98 }
99
fb719404 100 if (await isAbleToUploadVideo(user.id, videoFile.size) === false) {
f2eb23cd 101 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
a2431b7d 102 .json({ error: 'The user video quota is exceeded with this video.' })
a2431b7d 103
cf7a61b5 104 return cleanUpReqFiles(req)
a2431b7d
C
105 }
106
107 let duration: number
108
109 try {
110 duration = await getDurationFromVideoFile(videoFile.path)
111 } catch (err) {
d5b7d911 112 logger.error('Invalid input file in videosAddValidator.', { err })
f2eb23cd
RK
113 res.status(HttpStatusCode.UNPROCESSABLE_ENTITY_422)
114 .json({ error: 'Video file unreadable.' })
a2431b7d 115
cf7a61b5 116 return cleanUpReqFiles(req)
a2431b7d
C
117 }
118
b4055e1c
C
119 videoFile.duration = duration
120
121 if (!await isVideoAccepted(req, res, videoFile)) return cleanUpReqFiles(req)
a2431b7d
C
122
123 return next()
b60e5f38 124 }
a920fef1 125])
b60e5f38 126
418d092a 127const videosUpdateValidator = getCommonVideoEditAttributes().concat([
72c7248b 128 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
360329cc
C
129 body('name')
130 .optional()
131 .custom(isVideoNameValid).withMessage('Should have a valid name'),
0f320037
C
132 body('channelId')
133 .optional()
c8861d5d 134 .customSanitizer(toIntOrNull)
0f320037 135 .custom(isIdValid).withMessage('Should have correct video channel id'),
b60e5f38 136
a2431b7d 137 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38
C
138 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
139
cf7a61b5
C
140 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
141 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
0f6acda1 142 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
a2431b7d 143
6221f311 144 // Check if the user who did the request is able to update the video
0f320037 145 const user = res.locals.oauth.token.User
453e83ea 146 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
a2431b7d 147
0f6acda1 148 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
0f320037 149
a2431b7d 150 return next()
b60e5f38 151 }
a920fef1 152])
c173e565 153
8d427346 154async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
0283eaac 155 const video = getVideoWithAttributes(res)
8d427346
C
156
157 // Anybody can watch local videos
158 if (video.isOwned() === true) return next()
159
160 // Logged user
161 if (res.locals.oauth) {
162 // Users can search or watch remote videos
163 if (CONFIG.SEARCH.REMOTE_URI.USERS === true) return next()
164 }
165
166 // Anybody can search or watch remote videos
167 if (CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true) return next()
168
169 // Check our instance follows an actor that shared this video
170 const serverActor = await getServerActor()
171 if (await VideoModel.checkVideoHasInstanceFollow(video.id, serverActor.id) === true) return next()
172
2d53be02 173 return res.status(HttpStatusCode.FORBIDDEN_403)
8d427346 174 .json({
e6abf95e
C
175 errorCode: ServerErrorCode.DOES_NOT_RESPECT_FOLLOW_CONSTRAINTS,
176 error: 'Cannot get this video regarding follow constraints.',
177 originUrl: video.url
8d427346
C
178 })
179}
180
7eba5e1f
C
181const videosCustomGetValidator = (
182 fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes',
183 authenticateInQuery = false
184) => {
96f29c0f
C
185 return [
186 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
7b1f49de 187
96f29c0f
C
188 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
189 logger.debug('Checking videosGet parameters', { parameters: req.params })
11474c3c 190
96f29c0f 191 if (areValidationErrors(req, res)) return
0f6acda1 192 if (!await doesVideoExist(req.params.id, res, fetchType)) return
191764f3 193
943e5193
C
194 // Controllers does not need to check video rights
195 if (fetchType === 'only-immutable-attributes') return next()
196
0283eaac 197 const video = getVideoWithAttributes(res)
453e83ea 198 const videoAll = video as MVideoFullLight
191764f3 199
96f29c0f 200 // Video private or blacklisted
22a73cb8 201 if (videoAll.requiresAuth()) {
eccf70f0 202 await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
8d427346 203
dae86118 204 const user = res.locals.oauth ? res.locals.oauth.token.User : null
191764f3 205
8d427346 206 // Only the owner or a user that have blacklist rights can see the video
22a73cb8 207 if (!user || !user.canGetVideo(videoAll)) {
2d53be02 208 return res.status(HttpStatusCode.FORBIDDEN_403)
22a73cb8 209 .json({ error: 'Cannot get this private/internal or blacklisted video.' })
8d427346 210 }
191764f3 211
8d427346 212 return next()
96f29c0f 213 }
11474c3c 214
96f29c0f
C
215 // Video is public, anyone can access it
216 if (video.privacy === VideoPrivacy.PUBLIC) return next()
11474c3c 217
96f29c0f
C
218 // Video is unlisted, check we used the uuid to fetch it
219 if (video.privacy === VideoPrivacy.UNLISTED) {
220 if (isUUIDValid(req.params.id)) return next()
81ebea48 221
96f29c0f 222 // Don't leak this unlisted video
2d53be02 223 return res.status(HttpStatusCode.NOT_FOUND_404).end()
96f29c0f 224 }
81ebea48 225 }
96f29c0f
C
226 ]
227}
228
229const videosGetValidator = videosCustomGetValidator('all')
eccf70f0 230const videosDownloadValidator = videosCustomGetValidator('all', true)
34ca3b52 231
8319d6ae
RK
232const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
233 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
234 param('videoFileId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'),
235
236 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
237 logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params })
238
239 if (areValidationErrors(req, res)) return
240 if (!await doesVideoFileOfVideoExist(+req.params.videoFileId, req.params.id, res)) return
241
242 return next()
243 }
244])
245
b60e5f38 246const videosRemoveValidator = [
72c7248b 247 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
34ca3b52 248
a2431b7d 249 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
b60e5f38 250 logger.debug('Checking videosRemove parameters', { parameters: req.params })
34ca3b52 251
a2431b7d 252 if (areValidationErrors(req, res)) return
0f6acda1 253 if (!await doesVideoExist(req.params.id, res)) return
a2431b7d
C
254
255 // Check if the user who did the request is able to delete the video
453e83ea 256 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return
a2431b7d
C
257
258 return next()
b60e5f38
C
259 }
260]
34ca3b52 261
74d63469
GR
262const videosChangeOwnershipValidator = [
263 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
264
265 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
266 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
267
268 if (areValidationErrors(req, res)) return
0f6acda1 269 if (!await doesVideoExist(req.params.videoId, res)) return
74d63469
GR
270
271 // Check if the user who did the request is able to change the ownership of the video
453e83ea 272 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
74d63469
GR
273
274 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
275 if (!nextOwner) {
2d53be02 276 res.status(HttpStatusCode.BAD_REQUEST_400)
9ccff238
LD
277 .json({ error: 'Changing video ownership to a remote account is not supported yet' })
278
74d63469
GR
279 return
280 }
281 res.locals.nextOwner = nextOwner
282
283 return next()
284 }
285]
286
287const videosTerminateChangeOwnershipValidator = [
288 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
289
290 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
291 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
292
293 if (areValidationErrors(req, res)) return
294 if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
295
296 // Check if the user who did the request is able to change the ownership of the video
297 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
298
dae86118 299 const videoChangeOwnership = res.locals.videoChangeOwnership
74d63469 300
a1587156 301 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
2d53be02 302 res.status(HttpStatusCode.FORBIDDEN_403)
a1587156 303 .json({ error: 'Ownership already accepted or refused' })
74d63469
GR
304 return
305 }
a1587156
C
306
307 return next()
74d63469
GR
308 }
309]
310
311const videosAcceptChangeOwnershipValidator = [
312 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
313 const body = req.body as VideoChangeOwnershipAccept
0f6acda1 314 if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
74d63469
GR
315
316 const user = res.locals.oauth.token.User
dae86118 317 const videoChangeOwnership = res.locals.videoChangeOwnership
fb719404 318 const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size)
74d63469 319 if (isAble === false) {
f2eb23cd 320 res.status(HttpStatusCode.PAYLOAD_TOO_LARGE_413)
74d63469 321 .json({ error: 'The user video quota is exceeded with this video.' })
9ccff238 322
74d63469
GR
323 return
324 }
325
326 return next()
327 }
328]
329
764a9657
C
330const videosOverviewValidator = [
331 query('page')
332 .optional()
333 .isInt({ min: 1, max: OVERVIEWS.VIDEOS.SAMPLES_COUNT })
334 .withMessage('Should have a valid pagination'),
335
336 (req: express.Request, res: express.Response, next: express.NextFunction) => {
337 if (areValidationErrors(req, res)) return
338
339 return next()
340 }
341]
342
418d092a 343function getCommonVideoEditAttributes () {
a920fef1
C
344 return [
345 body('thumbnailfile')
346 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
a1587156
C
347 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
348 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
349 ),
a920fef1
C
350 body('previewfile')
351 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
a1587156
C
352 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
353 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
354 ),
a920fef1
C
355
356 body('category')
357 .optional()
358 .customSanitizer(toIntOrNull)
359 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
360 body('licence')
361 .optional()
362 .customSanitizer(toIntOrNull)
363 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
364 body('language')
365 .optional()
366 .customSanitizer(toValueOrNull)
367 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
368 body('nsfw')
369 .optional()
c8861d5d 370 .customSanitizer(toBooleanOrNull)
a920fef1
C
371 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
372 body('waitTranscoding')
373 .optional()
c8861d5d 374 .customSanitizer(toBooleanOrNull)
a920fef1
C
375 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
376 body('privacy')
377 .optional()
c8861d5d 378 .customSanitizer(toValueOrNull)
a920fef1
C
379 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
380 body('description')
381 .optional()
382 .customSanitizer(toValueOrNull)
383 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
384 body('support')
385 .optional()
386 .customSanitizer(toValueOrNull)
387 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
388 body('tags')
389 .optional()
390 .customSanitizer(toValueOrNull)
391 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
392 body('commentsEnabled')
393 .optional()
c8861d5d 394 .customSanitizer(toBooleanOrNull)
a920fef1 395 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
7f2cfe3a 396 body('downloadEnabled')
1e74f19a 397 .optional()
c8861d5d 398 .customSanitizer(toBooleanOrNull)
156c50af 399 .custom(isBooleanValid).withMessage('Should have downloading enabled boolean'),
b718fd22 400 body('originallyPublishedAt')
c8861d5d
C
401 .optional()
402 .customSanitizer(toValueOrNull)
403 .custom(isVideoOriginallyPublishedAtValid).withMessage('Should have a valid original publication date'),
a920fef1
C
404 body('scheduleUpdate')
405 .optional()
406 .customSanitizer(toValueOrNull),
407 body('scheduleUpdate.updateAt')
408 .optional()
409 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
410 body('scheduleUpdate.privacy')
411 .optional()
2b65c4e5 412 .customSanitizer(toIntOrNull)
a920fef1
C
413 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
414 ] as (ValidationChain | express.Handler)[]
415}
fbad87b0 416
1cd3facc
C
417const commonVideosFiltersValidator = [
418 query('categoryOneOf')
419 .optional()
420 .customSanitizer(toArray)
421 .custom(isNumberArray).withMessage('Should have a valid one of category array'),
422 query('licenceOneOf')
423 .optional()
424 .customSanitizer(toArray)
425 .custom(isNumberArray).withMessage('Should have a valid one of licence array'),
426 query('languageOneOf')
427 .optional()
428 .customSanitizer(toArray)
429 .custom(isStringArray).withMessage('Should have a valid one of language array'),
430 query('tagsOneOf')
431 .optional()
432 .customSanitizer(toArray)
433 .custom(isStringArray).withMessage('Should have a valid one of tags array'),
434 query('tagsAllOf')
435 .optional()
436 .customSanitizer(toArray)
437 .custom(isStringArray).withMessage('Should have a valid all of tags array'),
438 query('nsfw')
439 .optional()
440 .custom(isNSFWQueryValid).withMessage('Should have a valid NSFW attribute'),
441 query('filter')
442 .optional()
443 .custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
fe987656
C
444 query('skipCount')
445 .optional()
446 .customSanitizer(toBooleanOrNull)
447 .custom(isBooleanValid).withMessage('Should have a valid skip count boolean'),
37024082
RK
448 query('search')
449 .optional()
450 .custom(exists).withMessage('Should have a valid search'),
1cd3facc
C
451
452 (req: express.Request, res: express.Response, next: express.NextFunction) => {
453 logger.debug('Checking commons video filters query', { parameters: req.query })
454
455 if (areValidationErrors(req, res)) return
456
dae86118 457 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
0aa52e17
C
458 if (
459 (req.query.filter === 'all-local' || req.query.filter === 'all') &&
460 (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
461 ) {
2d53be02 462 res.status(HttpStatusCode.UNAUTHORIZED_401)
1cd3facc
C
463 .json({ error: 'You are not allowed to see all local videos.' })
464
465 return
466 }
467
468 return next()
469 }
470]
471
fbad87b0
C
472// ---------------------------------------------------------------------------
473
474export {
475 videosAddValidator,
476 videosUpdateValidator,
477 videosGetValidator,
8319d6ae 478 videoFileMetadataGetValidator,
eccf70f0 479 videosDownloadValidator,
8d427346 480 checkVideoFollowConstraints,
96f29c0f 481 videosCustomGetValidator,
fbad87b0 482 videosRemoveValidator,
fbad87b0 483
74d63469
GR
484 videosChangeOwnershipValidator,
485 videosTerminateChangeOwnershipValidator,
486 videosAcceptChangeOwnershipValidator,
487
418d092a 488 getCommonVideoEditAttributes,
1cd3facc 489
764a9657
C
490 commonVideosFiltersValidator,
491
492 videosOverviewValidator
fbad87b0
C
493}
494
495// ---------------------------------------------------------------------------
496
497function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
498 if (req.body.scheduleUpdate) {
499 if (!req.body.scheduleUpdate.updateAt) {
7373507f
C
500 logger.warn('Invalid parameters: scheduleUpdate.updateAt is mandatory.')
501
2d53be02 502 res.status(HttpStatusCode.BAD_REQUEST_400)
fbad87b0 503 .json({ error: 'Schedule update at is mandatory.' })
fbad87b0
C
504
505 return true
506 }
507 }
508
509 return false
510}
b4055e1c
C
511
512async function isVideoAccepted (req: express.Request, res: express.Response, videoFile: Express.Multer.File & { duration?: number }) {
513 // Check we accept this video
514 const acceptParameters = {
515 videoBody: req.body,
516 videoFile,
517 user: res.locals.oauth.token.User
518 }
89cd1275
C
519 const acceptedResult = await Hooks.wrapFun(
520 isLocalVideoAccepted,
521 acceptParameters,
b4055e1c
C
522 'filter:api.video.upload.accept.result'
523 )
524
525 if (!acceptedResult || acceptedResult.accepted !== true) {
526 logger.info('Refused local video.', { acceptedResult, acceptParameters })
2d53be02 527 res.status(HttpStatusCode.FORBIDDEN_403)
b4055e1c
C
528 .json({ error: acceptedResult.errorMessage || 'Refused local video' })
529
530 return false
531 }
532
533 return true
534}