]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/middlewares/validators/videos/video-comments.ts
Don't display comments of private/internal videos
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / videos / video-comments.ts
... / ...
CommitLineData
1import express from 'express'
2import { body, param, query } from 'express-validator'
3import { MUserAccountUrl } from '@server/types/models'
4import { UserRight } from '../../../../shared'
5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
7import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
8import { logger } from '../../../helpers/logger'
9import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
10import { Hooks } from '../../../lib/plugins/hooks'
11import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
12import {
13 areValidationErrors,
14 checkCanSeeVideoIfPrivate,
15 doesVideoCommentExist,
16 doesVideoCommentThreadExist,
17 doesVideoExist,
18 isValidVideoIdParam
19} from '../shared'
20
21const listVideoCommentsValidator = [
22 query('isLocal')
23 .optional()
24 .customSanitizer(toBooleanOrNull)
25 .custom(isBooleanValid)
26 .withMessage('Should have a valid is local boolean'),
27
28 query('search')
29 .optional()
30 .custom(exists).withMessage('Should have a valid search'),
31
32 query('searchAccount')
33 .optional()
34 .custom(exists).withMessage('Should have a valid account search'),
35
36 query('searchVideo')
37 .optional()
38 .custom(exists).withMessage('Should have a valid video search'),
39
40 (req: express.Request, res: express.Response, next: express.NextFunction) => {
41 logger.debug('Checking listVideoCommentsValidator parameters.', { parameters: req.query })
42
43 if (areValidationErrors(req, res)) return
44
45 return next()
46 }
47]
48
49const listVideoCommentThreadsValidator = [
50 isValidVideoIdParam('videoId'),
51
52 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
53 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
54
55 if (areValidationErrors(req, res)) return
56 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
57
58 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) {
59 return res.fail({
60 status: HttpStatusCode.FORBIDDEN_403,
61 message: 'Cannot list comments of private/internal/blocklisted video'
62 })
63 }
64
65 return next()
66 }
67]
68
69const listVideoThreadCommentsValidator = [
70 isValidVideoIdParam('videoId'),
71
72 param('threadId')
73 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
74
75 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
76 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
77
78 if (areValidationErrors(req, res)) return
79 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
80 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
81
82 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) {
83 return res.fail({
84 status: HttpStatusCode.FORBIDDEN_403,
85 message: 'Cannot list threads of private/internal/blocklisted video'
86 })
87 }
88
89 return next()
90 }
91]
92
93const addVideoCommentThreadValidator = [
94 isValidVideoIdParam('videoId'),
95
96 body('text')
97 .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
98
99 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
100 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
101
102 if (areValidationErrors(req, res)) return
103 if (!await doesVideoExist(req.params.videoId, res)) return
104 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
105 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
106
107 return next()
108 }
109]
110
111const addVideoCommentReplyValidator = [
112 isValidVideoIdParam('videoId'),
113
114 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
115
116 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
117
118 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
119 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
120
121 if (areValidationErrors(req, res)) return
122 if (!await doesVideoExist(req.params.videoId, res)) return
123 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
124 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
125 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
126
127 return next()
128 }
129]
130
131const videoCommentGetValidator = [
132 isValidVideoIdParam('videoId'),
133
134 param('commentId')
135 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
136
137 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
138 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
139
140 if (areValidationErrors(req, res)) return
141 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
142 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
143
144 return next()
145 }
146]
147
148const removeVideoCommentValidator = [
149 isValidVideoIdParam('videoId'),
150
151 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
152
153 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
154 logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params })
155
156 if (areValidationErrors(req, res)) return
157 if (!await doesVideoExist(req.params.videoId, res)) return
158 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
159
160 // Check if the user who did the request is able to delete the video
161 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
162
163 return next()
164 }
165]
166
167// ---------------------------------------------------------------------------
168
169export {
170 listVideoCommentThreadsValidator,
171 listVideoThreadCommentsValidator,
172 addVideoCommentThreadValidator,
173 listVideoCommentsValidator,
174 addVideoCommentReplyValidator,
175 videoCommentGetValidator,
176 removeVideoCommentValidator
177}
178
179// ---------------------------------------------------------------------------
180
181function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
182 if (video.commentsEnabled !== true) {
183 res.fail({
184 status: HttpStatusCode.CONFLICT_409,
185 message: 'Video comments are disabled for this video.'
186 })
187 return false
188 }
189
190 return true
191}
192
193function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
194 if (videoComment.isDeleted()) {
195 res.fail({
196 status: HttpStatusCode.CONFLICT_409,
197 message: 'This comment is already deleted'
198 })
199 return false
200 }
201
202 const userAccount = user.Account
203
204 if (
205 user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && // Not a moderator
206 videoComment.accountId !== userAccount.id && // Not the comment owner
207 videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner
208 ) {
209 res.fail({
210 status: HttpStatusCode.FORBIDDEN_403,
211 message: 'Cannot remove video comment of another user'
212 })
213 return false
214 }
215
216 return true
217}
218
219async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
220 const acceptParameters = {
221 video,
222 commentBody: req.body,
223 user: res.locals.oauth.token.User
224 }
225
226 let acceptedResult: AcceptResult
227
228 if (isReply) {
229 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
230
231 acceptedResult = await Hooks.wrapFun(
232 isLocalVideoCommentReplyAccepted,
233 acceptReplyParameters,
234 'filter:api.video-comment-reply.create.accept.result'
235 )
236 } else {
237 acceptedResult = await Hooks.wrapFun(
238 isLocalVideoThreadAccepted,
239 acceptParameters,
240 'filter:api.video-thread.create.accept.result'
241 )
242 }
243
244 if (!acceptedResult || acceptedResult.accepted !== true) {
245 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
246
247 res.fail({
248 status: HttpStatusCode.FORBIDDEN_403,
249 message: acceptedResult?.errorMessage || 'Refused local comment'
250 })
251 return false
252 }
253
254 return true
255}