aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares/validators/videos
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares/validators/videos')
-rw-r--r--server/middlewares/validators/videos/index.ts8
-rw-r--r--server/middlewares/validators/videos/video-abuses.ts71
-rw-r--r--server/middlewares/validators/videos/video-blacklist.ts62
-rw-r--r--server/middlewares/validators/videos/video-captions.ts71
-rw-r--r--server/middlewares/validators/videos/video-channels.ts175
-rw-r--r--server/middlewares/validators/videos/video-comments.ts195
-rw-r--r--server/middlewares/validators/videos/video-imports.ts75
-rw-r--r--server/middlewares/validators/videos/video-watch.ts28
-rw-r--r--server/middlewares/validators/videos/videos.ts399
9 files changed, 1084 insertions, 0 deletions
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts
new file mode 100644
index 000000000..294783d85
--- /dev/null
+++ b/server/middlewares/validators/videos/index.ts
@@ -0,0 +1,8 @@
1export * from './video-abuses'
2export * from './video-blacklist'
3export * from './video-captions'
4export * from './video-channels'
5export * from './video-comments'
6export * from './video-imports'
7export * from './video-watch'
8export * from './videos'
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts
new file mode 100644
index 000000000..be26ca16a
--- /dev/null
+++ b/server/middlewares/validators/videos/video-abuses.ts
@@ -0,0 +1,71 @@
1import * as express from 'express'
2import 'express-validator'
3import { body, param } from 'express-validator/check'
4import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
5import { isVideoExist } from '../../../helpers/custom-validators/videos'
6import { logger } from '../../../helpers/logger'
7import { areValidationErrors } from '../utils'
8import {
9 isVideoAbuseExist,
10 isVideoAbuseModerationCommentValid,
11 isVideoAbuseReasonValid,
12 isVideoAbuseStateValid
13} from '../../../helpers/custom-validators/video-abuses'
14
15const videoAbuseReportValidator = [
16 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
17 body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
18
19 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
20 logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
21
22 if (areValidationErrors(req, res)) return
23 if (!await isVideoExist(req.params.videoId, res)) return
24
25 return next()
26 }
27]
28
29const videoAbuseGetValidator = [
30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
31 param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
32
33 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
34 logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body })
35
36 if (areValidationErrors(req, res)) return
37 if (!await isVideoExist(req.params.videoId, res)) return
38 if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
39
40 return next()
41 }
42]
43
44const videoAbuseUpdateValidator = [
45 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
46 param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
47 body('state')
48 .optional()
49 .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'),
50 body('moderationComment')
51 .optional()
52 .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'),
53
54 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
55 logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body })
56
57 if (areValidationErrors(req, res)) return
58 if (!await isVideoExist(req.params.videoId, res)) return
59 if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
60
61 return next()
62 }
63]
64
65// ---------------------------------------------------------------------------
66
67export {
68 videoAbuseReportValidator,
69 videoAbuseGetValidator,
70 videoAbuseUpdateValidator
71}
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts
new file mode 100644
index 000000000..13da7acff
--- /dev/null
+++ b/server/middlewares/validators/videos/video-blacklist.ts
@@ -0,0 +1,62 @@
1import * as express from 'express'
2import { body, param } from 'express-validator/check'
3import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
4import { isVideoExist } from '../../../helpers/custom-validators/videos'
5import { logger } from '../../../helpers/logger'
6import { areValidationErrors } from '../utils'
7import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../../helpers/custom-validators/video-blacklist'
8
9const videosBlacklistRemoveValidator = [
10 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
11
12 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
14
15 if (areValidationErrors(req, res)) return
16 if (!await isVideoExist(req.params.videoId, res)) return
17 if (!await isVideoBlacklistExist(res.locals.video.id, res)) return
18
19 return next()
20 }
21]
22
23const videosBlacklistAddValidator = [
24 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
25 body('reason')
26 .optional()
27 .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
28
29 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
30 logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params })
31
32 if (areValidationErrors(req, res)) return
33 if (!await isVideoExist(req.params.videoId, res)) return
34
35 return next()
36 }
37]
38
39const videosBlacklistUpdateValidator = [
40 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
41 body('reason')
42 .optional()
43 .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
44
45 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
46 logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params })
47
48 if (areValidationErrors(req, res)) return
49 if (!await isVideoExist(req.params.videoId, res)) return
50 if (!await isVideoBlacklistExist(res.locals.video.id, res)) return
51
52 return next()
53 }
54]
55
56// ---------------------------------------------------------------------------
57
58export {
59 videosBlacklistAddValidator,
60 videosBlacklistRemoveValidator,
61 videosBlacklistUpdateValidator
62}
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
new file mode 100644
index 000000000..63d84fbec
--- /dev/null
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -0,0 +1,71 @@
1import * as express from 'express'
2import { areValidationErrors } from '../utils'
3import { checkUserCanManageVideo, isVideoExist } from '../../../helpers/custom-validators/videos'
4import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
5import { body, param } from 'express-validator/check'
6import { CONSTRAINTS_FIELDS } from '../../../initializers'
7import { UserRight } from '../../../../shared'
8import { logger } from '../../../helpers/logger'
9import { isVideoCaptionExist, isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions'
10import { cleanUpReqFiles } from '../../../helpers/express-utils'
11
12const addVideoCaptionValidator = [
13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
15 body('captionfile')
16 .custom((value, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage(
17 'This caption file is not supported or too large. Please, make sure it is of the following type : '
18 + CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME.join(', ')
19 ),
20
21 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
22 logger.debug('Checking addVideoCaption parameters', { parameters: req.body })
23
24 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
25 if (!await isVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req)
26
27 // Check if the user who did the request is able to update the video
28 const user = res.locals.oauth.token.User
29 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
30
31 return next()
32 }
33]
34
35const deleteVideoCaptionValidator = [
36 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
37 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
38
39 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
40 logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params })
41
42 if (areValidationErrors(req, res)) return
43 if (!await isVideoExist(req.params.videoId, res)) return
44 if (!await isVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return
45
46 // Check if the user who did the request is able to update the video
47 const user = res.locals.oauth.token.User
48 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
49
50 return next()
51 }
52]
53
54const listVideoCaptionsValidator = [
55 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
56
57 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
58 logger.debug('Checking listVideoCaptions parameters', { parameters: req.params })
59
60 if (areValidationErrors(req, res)) return
61 if (!await isVideoExist(req.params.videoId, res, 'id')) return
62
63 return next()
64 }
65]
66
67export {
68 addVideoCaptionValidator,
69 listVideoCaptionsValidator,
70 deleteVideoCaptionValidator
71}
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
new file mode 100644
index 000000000..f039794e0
--- /dev/null
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -0,0 +1,175 @@
1import * as express from 'express'
2import { body, param } from 'express-validator/check'
3import { UserRight } from '../../../../shared'
4import { isAccountNameWithHostExist } from '../../../helpers/custom-validators/accounts'
5import {
6 isLocalVideoChannelNameExist,
7 isVideoChannelDescriptionValid,
8 isVideoChannelNameValid,
9 isVideoChannelNameWithHostExist,
10 isVideoChannelSupportValid
11} from '../../../helpers/custom-validators/video-channels'
12import { logger } from '../../../helpers/logger'
13import { UserModel } from '../../../models/account/user'
14import { VideoChannelModel } from '../../../models/video/video-channel'
15import { areValidationErrors } from '../utils'
16import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
17import { ActorModel } from '../../../models/activitypub/actor'
18
19const listVideoAccountChannelsValidator = [
20 param('accountName').exists().withMessage('Should have a valid account name'),
21
22 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
23 logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body })
24
25 if (areValidationErrors(req, res)) return
26 if (!await isAccountNameWithHostExist(req.params.accountName, res)) return
27
28 return next()
29 }
30]
31
32const videoChannelsAddValidator = [
33 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
34 body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
35 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
36 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
37
38 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
39 logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body })
40
41 if (areValidationErrors(req, res)) return
42
43 const actor = await ActorModel.loadLocalByName(req.body.name)
44 if (actor) {
45 res.status(409)
46 .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' })
47 .end()
48 return false
49 }
50
51 return next()
52 }
53]
54
55const videoChannelsUpdateValidator = [
56 param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
57 body('displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
58 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
59 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
60
61 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
62 logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
63
64 if (areValidationErrors(req, res)) return
65 if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
66
67 // We need to make additional checks
68 if (res.locals.videoChannel.Actor.isOwned() === false) {
69 return res.status(403)
70 .json({ error: 'Cannot update video channel of another server' })
71 .end()
72 }
73
74 if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) {
75 return res.status(403)
76 .json({ error: 'Cannot update video channel of another user' })
77 .end()
78 }
79
80 return next()
81 }
82]
83
84const videoChannelsRemoveValidator = [
85 param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
86
87 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
88 logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
89
90 if (areValidationErrors(req, res)) return
91 if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
92
93 if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
94 if (!await checkVideoChannelIsNotTheLastOne(res)) return
95
96 return next()
97 }
98]
99
100const videoChannelsNameWithHostValidator = [
101 param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
102
103 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
104 logger.debug('Checking videoChannelsNameWithHostValidator parameters', { parameters: req.params })
105
106 if (areValidationErrors(req, res)) return
107
108 if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
109
110 return next()
111 }
112]
113
114const localVideoChannelValidator = [
115 param('name').custom(isVideoChannelNameValid).withMessage('Should have a valid video channel name'),
116
117 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
118 logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params })
119
120 if (areValidationErrors(req, res)) return
121 if (!await isLocalVideoChannelNameExist(req.params.name, res)) return
122
123 return next()
124 }
125]
126
127// ---------------------------------------------------------------------------
128
129export {
130 listVideoAccountChannelsValidator,
131 videoChannelsAddValidator,
132 videoChannelsUpdateValidator,
133 videoChannelsRemoveValidator,
134 videoChannelsNameWithHostValidator,
135 localVideoChannelValidator
136}
137
138// ---------------------------------------------------------------------------
139
140function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) {
141 if (videoChannel.Actor.isOwned() === false) {
142 res.status(403)
143 .json({ error: 'Cannot remove video channel of another server.' })
144 .end()
145
146 return false
147 }
148
149 // Check if the user can delete the video channel
150 // The user can delete it if s/he is an admin
151 // Or if s/he is the video channel's account
152 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) {
153 res.status(403)
154 .json({ error: 'Cannot remove video channel of another user' })
155 .end()
156
157 return false
158 }
159
160 return true
161}
162
163async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
164 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
165
166 if (count <= 1) {
167 res.status(409)
168 .json({ error: 'Cannot remove the last channel of this user' })
169 .end()
170
171 return false
172 }
173
174 return true
175}
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
new file mode 100644
index 000000000..348d33082
--- /dev/null
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -0,0 +1,195 @@
1import * as express from 'express'
2import { body, param } from 'express-validator/check'
3import { UserRight } from '../../../../shared'
4import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
5import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
6import { isVideoExist } from '../../../helpers/custom-validators/videos'
7import { logger } from '../../../helpers/logger'
8import { UserModel } from '../../../models/account/user'
9import { VideoModel } from '../../../models/video/video'
10import { VideoCommentModel } from '../../../models/video/video-comment'
11import { areValidationErrors } from '../utils'
12
13const listVideoCommentThreadsValidator = [
14 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
15
16 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
17 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
18
19 if (areValidationErrors(req, res)) return
20 if (!await isVideoExist(req.params.videoId, res, 'only-video')) return
21
22 return next()
23 }
24]
25
26const listVideoThreadCommentsValidator = [
27 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
28 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
29
30 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
31 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
32
33 if (areValidationErrors(req, res)) return
34 if (!await isVideoExist(req.params.videoId, res, 'only-video')) return
35 if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
36
37 return next()
38 }
39]
40
41const addVideoCommentThreadValidator = [
42 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
43 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
44
45 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
46 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
47
48 if (areValidationErrors(req, res)) return
49 if (!await isVideoExist(req.params.videoId, res)) return
50 if (!isVideoCommentsEnabled(res.locals.video, res)) return
51
52 return next()
53 }
54]
55
56const addVideoCommentReplyValidator = [
57 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
58 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
59 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
60
61 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
62 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
63
64 if (areValidationErrors(req, res)) return
65 if (!await isVideoExist(req.params.videoId, res)) return
66 if (!isVideoCommentsEnabled(res.locals.video, res)) return
67 if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return
68
69 return next()
70 }
71]
72
73const videoCommentGetValidator = [
74 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
75 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
76
77 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
78 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
79
80 if (areValidationErrors(req, res)) return
81 if (!await isVideoExist(req.params.videoId, res, 'id')) return
82 if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return
83
84 return next()
85 }
86]
87
88const removeVideoCommentValidator = [
89 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
90 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
91
92 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
93 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
94
95 if (areValidationErrors(req, res)) return
96 if (!await isVideoExist(req.params.videoId, res)) return
97 if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return
98
99 // Check if the user who did the request is able to delete the video
100 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return
101
102 return next()
103 }
104]
105
106// ---------------------------------------------------------------------------
107
108export {
109 listVideoCommentThreadsValidator,
110 listVideoThreadCommentsValidator,
111 addVideoCommentThreadValidator,
112 addVideoCommentReplyValidator,
113 videoCommentGetValidator,
114 removeVideoCommentValidator
115}
116
117// ---------------------------------------------------------------------------
118
119async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) {
120 const videoComment = await VideoCommentModel.loadById(id)
121
122 if (!videoComment) {
123 res.status(404)
124 .json({ error: 'Video comment thread not found' })
125 .end()
126
127 return false
128 }
129
130 if (videoComment.videoId !== video.id) {
131 res.status(400)
132 .json({ error: 'Video comment is associated to this video.' })
133 .end()
134
135 return false
136 }
137
138 if (videoComment.inReplyToCommentId !== null) {
139 res.status(400)
140 .json({ error: 'Video comment is not a thread.' })
141 .end()
142
143 return false
144 }
145
146 res.locals.videoCommentThread = videoComment
147 return true
148}
149
150async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) {
151 const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
152
153 if (!videoComment) {
154 res.status(404)
155 .json({ error: 'Video comment thread not found' })
156 .end()
157
158 return false
159 }
160
161 if (videoComment.videoId !== video.id) {
162 res.status(400)
163 .json({ error: 'Video comment is associated to this video.' })
164 .end()
165
166 return false
167 }
168
169 res.locals.videoComment = videoComment
170 return true
171}
172
173function isVideoCommentsEnabled (video: VideoModel, res: express.Response) {
174 if (video.commentsEnabled !== true) {
175 res.status(409)
176 .json({ error: 'Video comments are disabled for this video.' })
177 .end()
178
179 return false
180 }
181
182 return true
183}
184
185function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) {
186 const account = videoComment.Account
187 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) {
188 res.status(403)
189 .json({ error: 'Cannot remove video comment of another user' })
190 .end()
191 return false
192 }
193
194 return true
195}
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
new file mode 100644
index 000000000..48d20f904
--- /dev/null
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -0,0 +1,75 @@
1import * as express from 'express'
2import { body } from 'express-validator/check'
3import { isIdValid } from '../../../helpers/custom-validators/misc'
4import { logger } from '../../../helpers/logger'
5import { areValidationErrors } from '../utils'
6import { getCommonVideoAttributes } from './videos'
7import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports'
8import { cleanUpReqFiles } from '../../../helpers/express-utils'
9import { isVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos'
10import { CONFIG } from '../../../initializers/constants'
11import { CONSTRAINTS_FIELDS } from '../../../initializers'
12
13const videoImportAddValidator = getCommonVideoAttributes().concat([
14 body('channelId')
15 .toInt()
16 .custom(isIdValid).withMessage('Should have correct video channel id'),
17 body('targetUrl')
18 .optional()
19 .custom(isVideoImportTargetUrlValid).withMessage('Should have a valid video import target URL'),
20 body('magnetUri')
21 .optional()
22 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'),
23 body('torrentfile')
24 .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage(
25 'This torrent file is not supported or too large. Please, make sure it is of the following type: '
26 + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ')
27 ),
28 body('name')
29 .optional()
30 .custom(isVideoNameValid).withMessage('Should have a valid name'),
31
32 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
33 logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body })
34
35 const user = res.locals.oauth.token.User
36 const torrentFile = req.files && req.files['torrentfile'] ? req.files['torrentfile'][0] : undefined
37
38 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
39
40 if (req.body.targetUrl && CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true) {
41 cleanUpReqFiles(req)
42 return res.status(409)
43 .json({ error: 'HTTP import is not enabled on this instance.' })
44 .end()
45 }
46
47 if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) {
48 cleanUpReqFiles(req)
49 return res.status(409)
50 .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' })
51 .end()
52 }
53
54 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
55
56 // Check we have at least 1 required param
57 if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) {
58 cleanUpReqFiles(req)
59
60 return res.status(400)
61 .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' })
62 .end()
63 }
64
65 return next()
66 }
67])
68
69// ---------------------------------------------------------------------------
70
71export {
72 videoImportAddValidator
73}
74
75// ---------------------------------------------------------------------------
diff --git a/server/middlewares/validators/videos/video-watch.ts b/server/middlewares/validators/videos/video-watch.ts
new file mode 100644
index 000000000..bca64662f
--- /dev/null
+++ b/server/middlewares/validators/videos/video-watch.ts
@@ -0,0 +1,28 @@
1import { body, param } from 'express-validator/check'
2import * as express from 'express'
3import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
4import { isVideoExist } from '../../../helpers/custom-validators/videos'
5import { areValidationErrors } from '../utils'
6import { logger } from '../../../helpers/logger'
7
8const videoWatchingValidator = [
9 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
10 body('currentTime')
11 .toInt()
12 .isInt().withMessage('Should have correct current time'),
13
14 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
15 logger.debug('Checking videoWatching parameters', { parameters: req.body })
16
17 if (areValidationErrors(req, res)) return
18 if (!await isVideoExist(req.params.videoId, res, 'id')) return
19
20 return next()
21 }
22]
23
24// ---------------------------------------------------------------------------
25
26export {
27 videoWatchingValidator
28}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
new file mode 100644
index 000000000..d6b8aa725
--- /dev/null
+++ b/server/middlewares/validators/videos/videos.ts
@@ -0,0 +1,399 @@
1import * as express from 'express'
2import 'express-validator'
3import { body, param, ValidationChain } from 'express-validator/check'
4import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
5import {
6 isBooleanValid,
7 isDateValid,
8 isIdOrUUIDValid,
9 isIdValid,
10 isUUIDValid,
11 toIntOrNull,
12 toValueOrNull
13} from '../../../helpers/custom-validators/misc'
14import {
15 checkUserCanManageVideo,
16 isScheduleVideoUpdatePrivacyValid,
17 isVideoCategoryValid,
18 isVideoChannelOfAccountExist,
19 isVideoDescriptionValid,
20 isVideoExist,
21 isVideoFile,
22 isVideoImage,
23 isVideoLanguageValid,
24 isVideoLicenceValid,
25 isVideoNameValid,
26 isVideoPrivacyValid,
27 isVideoRatingTypeValid,
28 isVideoSupportValid,
29 isVideoTagsValid
30} from '../../../helpers/custom-validators/videos'
31import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils'
32import { logger } from '../../../helpers/logger'
33import { CONSTRAINTS_FIELDS } from '../../../initializers'
34import { VideoShareModel } from '../../../models/video/video-share'
35import { authenticate } from '../../oauth'
36import { areValidationErrors } from '../utils'
37import { cleanUpReqFiles } from '../../../helpers/express-utils'
38import { VideoModel } from '../../../models/video/video'
39import { UserModel } from '../../../models/account/user'
40import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
41import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
42import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
43import { AccountModel } from '../../../models/account/account'
44import { VideoFetchType } from '../../../helpers/video'
45
46const videosAddValidator = getCommonVideoAttributes().concat([
47 body('videofile')
48 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
49 'This file is not supported or too large. Please, make sure it is of the following type: '
50 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
51 ),
52 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
53 body('channelId')
54 .toInt()
55 .custom(isIdValid).withMessage('Should have correct video channel id'),
56
57 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
58 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
59
60 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
61 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
62
63 const videoFile: Express.Multer.File = req.files['videofile'][0]
64 const user = res.locals.oauth.token.User
65
66 if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
67
68 const isAble = await user.isAbleToUploadVideo(videoFile)
69 if (isAble === false) {
70 res.status(403)
71 .json({ error: 'The user video quota is exceeded with this video.' })
72 .end()
73
74 return cleanUpReqFiles(req)
75 }
76
77 let duration: number
78
79 try {
80 duration = await getDurationFromVideoFile(videoFile.path)
81 } catch (err) {
82 logger.error('Invalid input file in videosAddValidator.', { err })
83 res.status(400)
84 .json({ error: 'Invalid input file.' })
85 .end()
86
87 return cleanUpReqFiles(req)
88 }
89
90 videoFile['duration'] = duration
91
92 return next()
93 }
94])
95
96const videosUpdateValidator = getCommonVideoAttributes().concat([
97 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
98 body('name')
99 .optional()
100 .custom(isVideoNameValid).withMessage('Should have a valid name'),
101 body('channelId')
102 .optional()
103 .toInt()
104 .custom(isIdValid).withMessage('Should have correct video channel id'),
105
106 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
107 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
108
109 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
110 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
111 if (!await isVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
112
113 const video = res.locals.video
114
115 // Check if the user who did the request is able to update the video
116 const user = res.locals.oauth.token.User
117 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
118
119 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
120 cleanUpReqFiles(req)
121 return res.status(409)
122 .json({ error: 'Cannot set "private" a video that was not private.' })
123 .end()
124 }
125
126 if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
127
128 return next()
129 }
130])
131
132const videosCustomGetValidator = (fetchType: VideoFetchType) => {
133 return [
134 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
135
136 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
137 logger.debug('Checking videosGet parameters', { parameters: req.params })
138
139 if (areValidationErrors(req, res)) return
140 if (!await isVideoExist(req.params.id, res, fetchType)) return
141
142 const video: VideoModel = res.locals.video
143
144 // Video private or blacklisted
145 if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
146 return authenticate(req, res, () => {
147 const user: UserModel = res.locals.oauth.token.User
148
149 // Only the owner or a user that have blacklist rights can see the video
150 if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) {
151 return res.status(403)
152 .json({ error: 'Cannot get this private or blacklisted video.' })
153 .end()
154 }
155
156 return next()
157 })
158 }
159
160 // Video is public, anyone can access it
161 if (video.privacy === VideoPrivacy.PUBLIC) return next()
162
163 // Video is unlisted, check we used the uuid to fetch it
164 if (video.privacy === VideoPrivacy.UNLISTED) {
165 if (isUUIDValid(req.params.id)) return next()
166
167 // Don't leak this unlisted video
168 return res.status(404).end()
169 }
170 }
171 ]
172}
173
174const videosGetValidator = videosCustomGetValidator('all')
175
176const videosRemoveValidator = [
177 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
178
179 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
180 logger.debug('Checking videosRemove parameters', { parameters: req.params })
181
182 if (areValidationErrors(req, res)) return
183 if (!await isVideoExist(req.params.id, res)) return
184
185 // Check if the user who did the request is able to delete the video
186 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
187
188 return next()
189 }
190]
191
192const videoRateValidator = [
193 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
194 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
195
196 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
197 logger.debug('Checking videoRate parameters', { parameters: req.body })
198
199 if (areValidationErrors(req, res)) return
200 if (!await isVideoExist(req.params.id, res)) return
201
202 return next()
203 }
204]
205
206const videosShareValidator = [
207 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
208 param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'),
209
210 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
211 logger.debug('Checking videoShare parameters', { parameters: req.params })
212
213 if (areValidationErrors(req, res)) return
214 if (!await isVideoExist(req.params.id, res)) return
215
216 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
217 if (!share) {
218 return res.status(404)
219 .end()
220 }
221
222 res.locals.videoShare = share
223 return next()
224 }
225]
226
227const videosChangeOwnershipValidator = [
228 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
229
230 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
231 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
232
233 if (areValidationErrors(req, res)) return
234 if (!await isVideoExist(req.params.videoId, res)) return
235
236 // Check if the user who did the request is able to change the ownership of the video
237 if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
238
239 const nextOwner = await AccountModel.loadLocalByName(req.body.username)
240 if (!nextOwner) {
241 res.status(400)
242 .type('json')
243 .end()
244 return
245 }
246 res.locals.nextOwner = nextOwner
247
248 return next()
249 }
250]
251
252const videosTerminateChangeOwnershipValidator = [
253 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
254
255 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
256 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
257
258 if (areValidationErrors(req, res)) return
259 if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
260
261 // Check if the user who did the request is able to change the ownership of the video
262 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
263
264 return next()
265 },
266 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
267 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
268
269 if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) {
270 return next()
271 } else {
272 res.status(403)
273 .json({ error: 'Ownership already accepted or refused' })
274 .end()
275 return
276 }
277 }
278]
279
280const videosAcceptChangeOwnershipValidator = [
281 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
282 const body = req.body as VideoChangeOwnershipAccept
283 if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
284
285 const user = res.locals.oauth.token.User
286 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel
287 const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile())
288 if (isAble === false) {
289 res.status(403)
290 .json({ error: 'The user video quota is exceeded with this video.' })
291 .end()
292 return
293 }
294
295 return next()
296 }
297]
298
299function getCommonVideoAttributes () {
300 return [
301 body('thumbnailfile')
302 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
303 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: '
304 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
305 ),
306 body('previewfile')
307 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
308 'This preview file is not supported or too large. Please, make sure it is of the following type: '
309 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
310 ),
311
312 body('category')
313 .optional()
314 .customSanitizer(toIntOrNull)
315 .custom(isVideoCategoryValid).withMessage('Should have a valid category'),
316 body('licence')
317 .optional()
318 .customSanitizer(toIntOrNull)
319 .custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
320 body('language')
321 .optional()
322 .customSanitizer(toValueOrNull)
323 .custom(isVideoLanguageValid).withMessage('Should have a valid language'),
324 body('nsfw')
325 .optional()
326 .toBoolean()
327 .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
328 body('waitTranscoding')
329 .optional()
330 .toBoolean()
331 .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'),
332 body('privacy')
333 .optional()
334 .toInt()
335 .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
336 body('description')
337 .optional()
338 .customSanitizer(toValueOrNull)
339 .custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
340 body('support')
341 .optional()
342 .customSanitizer(toValueOrNull)
343 .custom(isVideoSupportValid).withMessage('Should have a valid support text'),
344 body('tags')
345 .optional()
346 .customSanitizer(toValueOrNull)
347 .custom(isVideoTagsValid).withMessage('Should have correct tags'),
348 body('commentsEnabled')
349 .optional()
350 .toBoolean()
351 .custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
352
353 body('scheduleUpdate')
354 .optional()
355 .customSanitizer(toValueOrNull),
356 body('scheduleUpdate.updateAt')
357 .optional()
358 .custom(isDateValid).withMessage('Should have a valid schedule update date'),
359 body('scheduleUpdate.privacy')
360 .optional()
361 .toInt()
362 .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy')
363 ] as (ValidationChain | express.Handler)[]
364}
365
366// ---------------------------------------------------------------------------
367
368export {
369 videosAddValidator,
370 videosUpdateValidator,
371 videosGetValidator,
372 videosCustomGetValidator,
373 videosRemoveValidator,
374 videosShareValidator,
375
376 videoRateValidator,
377
378 videosChangeOwnershipValidator,
379 videosTerminateChangeOwnershipValidator,
380 videosAcceptChangeOwnershipValidator,
381
382 getCommonVideoAttributes
383}
384
385// ---------------------------------------------------------------------------
386
387function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) {
388 if (req.body.scheduleUpdate) {
389 if (!req.body.scheduleUpdate.updateAt) {
390 res.status(400)
391 .json({ error: 'Schedule update at is mandatory.' })
392 .end()
393
394 return true
395 }
396 }
397
398 return false
399}