]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - 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
41fb13c3 1import express from 'express'
0f8d00e3 2import { body, param, query } from 'express-validator'
26d6bf65 3import { MUserAccountUrl } from '@server/types/models'
6e46de09 4import { UserRight } from '../../../../shared'
c0e8b12e 5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
d4a8e7a6 6import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
10363c74 7import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
6e46de09 8import { logger } from '../../../helpers/logger'
ceba0e65
C
9import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
10import { Hooks } from '../../../lib/plugins/hooks'
57f6896f 11import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
84c8d986
C
12import {
13 areValidationErrors,
14 checkCanSeeVideoIfPrivate,
15 doesVideoCommentExist,
16 doesVideoCommentThreadExist,
17 doesVideoExist,
18 isValidVideoIdParam
19} from '../shared'
bf1f6508 20
0f8d00e3
C
21const listVideoCommentsValidator = [
22 query('isLocal')
23 .optional()
f1273314 24 .customSanitizer(toBooleanOrNull)
0f8d00e3
C
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
bf1f6508 49const listVideoCommentThreadsValidator = [
d4a8e7a6 50 isValidVideoIdParam('videoId'),
bf1f6508
C
51
52 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
d3ea8975 53 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
bf1f6508
C
54
55 if (areValidationErrors(req, res)) return
0f6acda1 56 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
bf1f6508 57
84c8d986
C
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
bf1f6508
C
65 return next()
66 }
67]
68
69const listVideoThreadCommentsValidator = [
d4a8e7a6
C
70 isValidVideoIdParam('videoId'),
71
72 param('threadId')
73 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
bf1f6508
C
74
75 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
d3ea8975 76 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
bf1f6508
C
77
78 if (areValidationErrors(req, res)) return
0f6acda1 79 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
453e83ea 80 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
bf1f6508 81
84c8d986
C
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
bf1f6508
C
89 return next()
90 }
91]
92
93const addVideoCommentThreadValidator = [
d4a8e7a6
C
94 isValidVideoIdParam('videoId'),
95
96 body('text')
97 .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
bf1f6508
C
98
99 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
5de8a55a 100 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
bf1f6508
C
101
102 if (areValidationErrors(req, res)) return
0f6acda1 103 if (!await doesVideoExist(req.params.videoId, res)) return
453e83ea 104 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
a1587156 105 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
bf1f6508
C
106
107 return next()
108 }
109]
110
111const addVideoCommentReplyValidator = [
d4a8e7a6
C
112 isValidVideoIdParam('videoId'),
113
bf1f6508 114 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
d4a8e7a6 115
bf1f6508
C
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) => {
5de8a55a 119 logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
bf1f6508
C
120
121 if (areValidationErrors(req, res)) return
0f6acda1 122 if (!await doesVideoExist(req.params.videoId, res)) return
453e83ea
C
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
bf1f6508
C
126
127 return next()
128 }
129]
130
da854ddd 131const videoCommentGetValidator = [
d4a8e7a6
C
132 isValidVideoIdParam('videoId'),
133
134 param('commentId')
135 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
da854ddd
C
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
0f6acda1 141 if (!await doesVideoExist(req.params.videoId, res, 'id')) return
453e83ea 142 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
da854ddd
C
143
144 return next()
145 }
146]
147
4cb6d457 148const removeVideoCommentValidator = [
d4a8e7a6
C
149 isValidVideoIdParam('videoId'),
150
4cb6d457
C
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
0f6acda1 157 if (!await doesVideoExist(req.params.videoId, res)) return
453e83ea 158 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
4cb6d457
C
159
160 // Check if the user who did the request is able to delete the video
453e83ea 161 if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
4cb6d457
C
162
163 return next()
164 }
165]
166
bf1f6508
C
167// ---------------------------------------------------------------------------
168
169export {
170 listVideoCommentThreadsValidator,
171 listVideoThreadCommentsValidator,
172 addVideoCommentThreadValidator,
0f8d00e3 173 listVideoCommentsValidator,
da854ddd 174 addVideoCommentReplyValidator,
4cb6d457
C
175 videoCommentGetValidator,
176 removeVideoCommentValidator
bf1f6508
C
177}
178
179// ---------------------------------------------------------------------------
180
453e83ea 181function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
47564bbe 182 if (video.commentsEnabled !== true) {
76148b27
RK
183 res.fail({
184 status: HttpStatusCode.CONFLICT_409,
185 message: 'Video comments are disabled for this video.'
186 })
47564bbe
C
187 return false
188 }
189
190 return true
191}
4cb6d457 192
fde37dc9 193function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) {
c883db6d 194 if (videoComment.isDeleted()) {
76148b27
RK
195 res.fail({
196 status: HttpStatusCode.CONFLICT_409,
197 message: 'This comment is already deleted'
198 })
c883db6d
C
199 return false
200 }
201
fde37dc9
C
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 ) {
76148b27
RK
209 res.fail({
210 status: HttpStatusCode.FORBIDDEN_403,
211 message: 'Cannot remove video comment of another user'
212 })
4cb6d457
C
213 return false
214 }
215
216 return true
217}
b4055e1c 218
453e83ea 219async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
b4055e1c 220 const acceptParameters = {
453e83ea 221 video,
b4055e1c
C
222 commentBody: req.body,
223 user: res.locals.oauth.token.User
224 }
225
226 let acceptedResult: AcceptResult
227
228 if (isReply) {
453e83ea 229 const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
b4055e1c 230
6691c522
C
231 acceptedResult = await Hooks.wrapFun(
232 isLocalVideoCommentReplyAccepted,
233 acceptReplyParameters,
b4055e1c
C
234 'filter:api.video-comment-reply.create.accept.result'
235 )
236 } else {
6691c522
C
237 acceptedResult = await Hooks.wrapFun(
238 isLocalVideoThreadAccepted,
239 acceptParameters,
b4055e1c
C
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 })
b4055e1c 246
76148b27
RK
247 res.fail({
248 status: HttpStatusCode.FORBIDDEN_403,
249 message: acceptedResult?.errorMessage || 'Refused local comment'
250 })
b4055e1c
C
251 return false
252 }
253
254 return true
255}