From 6e46de095d7169355dd83030f6ce4a582304153a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Oct 2018 11:15:06 +0200 Subject: Add user history and resume videos --- server/middlewares/cache.ts | 2 +- server/middlewares/validators/index.ts | 4 - server/middlewares/validators/video-abuses.ts | 71 ---- server/middlewares/validators/video-blacklist.ts | 62 ---- server/middlewares/validators/video-captions.ts | 71 ---- server/middlewares/validators/video-channels.ts | 175 --------- server/middlewares/validators/video-comments.ts | 195 ---------- server/middlewares/validators/video-imports.ts | 75 ---- server/middlewares/validators/videos.ts | 399 --------------------- server/middlewares/validators/videos/index.ts | 8 + .../middlewares/validators/videos/video-abuses.ts | 71 ++++ .../validators/videos/video-blacklist.ts | 62 ++++ .../validators/videos/video-captions.ts | 71 ++++ .../validators/videos/video-channels.ts | 175 +++++++++ .../validators/videos/video-comments.ts | 195 ++++++++++ .../middlewares/validators/videos/video-imports.ts | 75 ++++ .../middlewares/validators/videos/video-watch.ts | 28 ++ server/middlewares/validators/videos/videos.ts | 399 +++++++++++++++++++++ 18 files changed, 1085 insertions(+), 1053 deletions(-) delete mode 100644 server/middlewares/validators/video-abuses.ts delete mode 100644 server/middlewares/validators/video-blacklist.ts delete mode 100644 server/middlewares/validators/video-captions.ts delete mode 100644 server/middlewares/validators/video-channels.ts delete mode 100644 server/middlewares/validators/video-comments.ts delete mode 100644 server/middlewares/validators/video-imports.ts delete mode 100644 server/middlewares/validators/videos.ts create mode 100644 server/middlewares/validators/videos/index.ts create mode 100644 server/middlewares/validators/videos/video-abuses.ts create mode 100644 server/middlewares/validators/videos/video-blacklist.ts create mode 100644 server/middlewares/validators/videos/video-captions.ts create mode 100644 server/middlewares/validators/videos/video-channels.ts create mode 100644 server/middlewares/validators/videos/video-comments.ts create mode 100644 server/middlewares/validators/videos/video-imports.ts create mode 100644 server/middlewares/validators/videos/video-watch.ts create mode 100644 server/middlewares/validators/videos/videos.ts (limited to 'server/middlewares') diff --git a/server/middlewares/cache.ts b/server/middlewares/cache.ts index 1b44957d3..1e00fc731 100644 --- a/server/middlewares/cache.ts +++ b/server/middlewares/cache.ts @@ -8,7 +8,7 @@ const lock = new AsyncLock({ timeout: 5000 }) function cacheRoute (lifetimeArg: string | number) { return async function (req: express.Request, res: express.Response, next: express.NextFunction) { - const redisKey = Redis.Instance.buildCachedRouteKey(req) + const redisKey = Redis.Instance.generateCachedRouteKey(req) try { await lock.acquire(redisKey, async (done) => { diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 940547a3e..17226614c 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts @@ -8,9 +8,5 @@ export * from './sort' export * from './users' export * from './user-subscriptions' export * from './videos' -export * from './video-abuses' -export * from './video-blacklist' -export * from './video-channels' export * from './webfinger' export * from './search' -export * from './video-imports' diff --git a/server/middlewares/validators/video-abuses.ts b/server/middlewares/validators/video-abuses.ts deleted file mode 100644 index f15d55a75..000000000 --- a/server/middlewares/validators/video-abuses.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as express from 'express' -import 'express-validator' -import { body, param } from 'express-validator/check' -import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' -import { isVideoExist } from '../../helpers/custom-validators/videos' -import { logger } from '../../helpers/logger' -import { areValidationErrors } from './utils' -import { - isVideoAbuseExist, - isVideoAbuseModerationCommentValid, - isVideoAbuseReasonValid, - isVideoAbuseStateValid -} from '../../helpers/custom-validators/video-abuses' - -const videoAbuseReportValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - - return next() - } -] - -const videoAbuseGetValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return - - return next() - } -] - -const videoAbuseUpdateValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), - body('state') - .optional() - .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), - body('moderationComment') - .optional() - .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return - - return next() - } -] - -// --------------------------------------------------------------------------- - -export { - videoAbuseReportValidator, - videoAbuseGetValidator, - videoAbuseUpdateValidator -} diff --git a/server/middlewares/validators/video-blacklist.ts b/server/middlewares/validators/video-blacklist.ts deleted file mode 100644 index 95a2b9f17..000000000 --- a/server/middlewares/validators/video-blacklist.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as express from 'express' -import { body, param } from 'express-validator/check' -import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' -import { isVideoExist } from '../../helpers/custom-validators/videos' -import { logger } from '../../helpers/logger' -import { areValidationErrors } from './utils' -import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist' - -const videosBlacklistRemoveValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoBlacklistExist(res.locals.video.id, res)) return - - return next() - } -] - -const videosBlacklistAddValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - body('reason') - .optional() - .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - - return next() - } -] - -const videosBlacklistUpdateValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - body('reason') - .optional() - .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoBlacklistExist(res.locals.video.id, res)) return - - return next() - } -] - -// --------------------------------------------------------------------------- - -export { - videosBlacklistAddValidator, - videosBlacklistRemoveValidator, - videosBlacklistUpdateValidator -} diff --git a/server/middlewares/validators/video-captions.ts b/server/middlewares/validators/video-captions.ts deleted file mode 100644 index 51ffd7f3c..000000000 --- a/server/middlewares/validators/video-captions.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as express from 'express' -import { areValidationErrors } from './utils' -import { checkUserCanManageVideo, isVideoExist } from '../../helpers/custom-validators/videos' -import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' -import { body, param } from 'express-validator/check' -import { CONSTRAINTS_FIELDS } from '../../initializers' -import { UserRight } from '../../../shared' -import { logger } from '../../helpers/logger' -import { isVideoCaptionExist, isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' -import { cleanUpReqFiles } from '../../helpers/express-utils' - -const addVideoCaptionValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), - param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), - body('captionfile') - .custom((value, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage( - 'This caption file is not supported or too large. Please, make sure it is of the following type : ' - + CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME.join(', ') - ), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return cleanUpReqFiles(req) - if (!await isVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req) - - // Check if the user who did the request is able to update the video - const user = res.locals.oauth.token.User - if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) - - return next() - } -] - -const deleteVideoCaptionValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), - param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return - - // Check if the user who did the request is able to update the video - const user = res.locals.oauth.token.User - if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return - - return next() - } -] - -const listVideoCaptionsValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res, 'id')) return - - return next() - } -] - -export { - addVideoCaptionValidator, - listVideoCaptionsValidator, - deleteVideoCaptionValidator -} diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts deleted file mode 100644 index 56a347b39..000000000 --- a/server/middlewares/validators/video-channels.ts +++ /dev/null @@ -1,175 +0,0 @@ -import * as express from 'express' -import { body, param } from 'express-validator/check' -import { UserRight } from '../../../shared' -import { isAccountNameWithHostExist } from '../../helpers/custom-validators/accounts' -import { - isLocalVideoChannelNameExist, - isVideoChannelDescriptionValid, - isVideoChannelNameValid, - isVideoChannelNameWithHostExist, - isVideoChannelSupportValid -} from '../../helpers/custom-validators/video-channels' -import { logger } from '../../helpers/logger' -import { UserModel } from '../../models/account/user' -import { VideoChannelModel } from '../../models/video/video-channel' -import { areValidationErrors } from './utils' -import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' -import { ActorModel } from '../../models/activitypub/actor' - -const listVideoAccountChannelsValidator = [ - param('accountName').exists().withMessage('Should have a valid account name'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isAccountNameWithHostExist(req.params.accountName, res)) return - - return next() - } -] - -const videoChannelsAddValidator = [ - body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), - body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), - body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), - body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - - const actor = await ActorModel.loadLocalByName(req.body.name) - if (actor) { - res.status(409) - .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) - .end() - return false - } - - return next() - } -] - -const videoChannelsUpdateValidator = [ - param('nameWithHost').exists().withMessage('Should have an video channel name with host'), - body('displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), - body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), - body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return - - // We need to make additional checks - if (res.locals.videoChannel.Actor.isOwned() === false) { - return res.status(403) - .json({ error: 'Cannot update video channel of another server' }) - .end() - } - - if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { - return res.status(403) - .json({ error: 'Cannot update video channel of another user' }) - .end() - } - - return next() - } -] - -const videoChannelsRemoveValidator = [ - param('nameWithHost').exists().withMessage('Should have an video channel name with host'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return - - if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return - if (!await checkVideoChannelIsNotTheLastOne(res)) return - - return next() - } -] - -const videoChannelsNameWithHostValidator = [ - param('nameWithHost').exists().withMessage('Should have an video channel name with host'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoChannelsNameWithHostValidator parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - - if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return - - return next() - } -] - -const localVideoChannelValidator = [ - param('name').custom(isVideoChannelNameValid).withMessage('Should have a valid video channel name'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isLocalVideoChannelNameExist(req.params.name, res)) return - - return next() - } -] - -// --------------------------------------------------------------------------- - -export { - listVideoAccountChannelsValidator, - videoChannelsAddValidator, - videoChannelsUpdateValidator, - videoChannelsRemoveValidator, - videoChannelsNameWithHostValidator, - localVideoChannelValidator -} - -// --------------------------------------------------------------------------- - -function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { - if (videoChannel.Actor.isOwned() === false) { - res.status(403) - .json({ error: 'Cannot remove video channel of another server.' }) - .end() - - return false - } - - // Check if the user can delete the video channel - // The user can delete it if s/he is an admin - // Or if s/he is the video channel's account - if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { - res.status(403) - .json({ error: 'Cannot remove video channel of another user' }) - .end() - - return false - } - - return true -} - -async function checkVideoChannelIsNotTheLastOne (res: express.Response) { - const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) - - if (count <= 1) { - res.status(409) - .json({ error: 'Cannot remove the last channel of this user' }) - .end() - - return false - } - - return true -} diff --git a/server/middlewares/validators/video-comments.ts b/server/middlewares/validators/video-comments.ts deleted file mode 100644 index 693852499..000000000 --- a/server/middlewares/validators/video-comments.ts +++ /dev/null @@ -1,195 +0,0 @@ -import * as express from 'express' -import { body, param } from 'express-validator/check' -import { UserRight } from '../../../shared' -import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' -import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments' -import { isVideoExist } from '../../helpers/custom-validators/videos' -import { logger } from '../../helpers/logger' -import { UserModel } from '../../models/account/user' -import { VideoModel } from '../../models/video/video' -import { VideoCommentModel } from '../../models/video/video-comment' -import { areValidationErrors } from './utils' - -const listVideoCommentThreadsValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res, 'only-video')) return - - return next() - } -] - -const listVideoThreadCommentsValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res, 'only-video')) return - if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return - - return next() - } -] - -const addVideoCommentThreadValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!isVideoCommentsEnabled(res.locals.video, res)) return - - return next() - } -] - -const addVideoCommentReplyValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), - body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!isVideoCommentsEnabled(res.locals.video, res)) return - if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return - - return next() - } -] - -const videoCommentGetValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res, 'id')) return - if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return - - return next() - } -] - -const removeVideoCommentValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), - param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return - - // Check if the user who did the request is able to delete the video - if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return - - return next() - } -] - -// --------------------------------------------------------------------------- - -export { - listVideoCommentThreadsValidator, - listVideoThreadCommentsValidator, - addVideoCommentThreadValidator, - addVideoCommentReplyValidator, - videoCommentGetValidator, - removeVideoCommentValidator -} - -// --------------------------------------------------------------------------- - -async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { - const videoComment = await VideoCommentModel.loadById(id) - - if (!videoComment) { - res.status(404) - .json({ error: 'Video comment thread not found' }) - .end() - - return false - } - - if (videoComment.videoId !== video.id) { - res.status(400) - .json({ error: 'Video comment is associated to this video.' }) - .end() - - return false - } - - if (videoComment.inReplyToCommentId !== null) { - res.status(400) - .json({ error: 'Video comment is not a thread.' }) - .end() - - return false - } - - res.locals.videoCommentThread = videoComment - return true -} - -async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) { - const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) - - if (!videoComment) { - res.status(404) - .json({ error: 'Video comment thread not found' }) - .end() - - return false - } - - if (videoComment.videoId !== video.id) { - res.status(400) - .json({ error: 'Video comment is associated to this video.' }) - .end() - - return false - } - - res.locals.videoComment = videoComment - return true -} - -function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { - if (video.commentsEnabled !== true) { - res.status(409) - .json({ error: 'Video comments are disabled for this video.' }) - .end() - - return false - } - - return true -} - -function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) { - const account = videoComment.Account - if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { - res.status(403) - .json({ error: 'Cannot remove video comment of another user' }) - .end() - return false - } - - return true -} diff --git a/server/middlewares/validators/video-imports.ts b/server/middlewares/validators/video-imports.ts deleted file mode 100644 index b2063b8da..000000000 --- a/server/middlewares/validators/video-imports.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as express from 'express' -import { body } from 'express-validator/check' -import { isIdValid } from '../../helpers/custom-validators/misc' -import { logger } from '../../helpers/logger' -import { areValidationErrors } from './utils' -import { getCommonVideoAttributes } from './videos' -import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../helpers/custom-validators/video-imports' -import { cleanUpReqFiles } from '../../helpers/express-utils' -import { isVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../helpers/custom-validators/videos' -import { CONFIG } from '../../initializers/constants' -import { CONSTRAINTS_FIELDS } from '../../initializers' - -const videoImportAddValidator = getCommonVideoAttributes().concat([ - body('channelId') - .toInt() - .custom(isIdValid).withMessage('Should have correct video channel id'), - body('targetUrl') - .optional() - .custom(isVideoImportTargetUrlValid).withMessage('Should have a valid video import target URL'), - body('magnetUri') - .optional() - .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), - body('torrentfile') - .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage( - 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' - + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') - ), - body('name') - .optional() - .custom(isVideoNameValid).withMessage('Should have a valid name'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body }) - - const user = res.locals.oauth.token.User - const torrentFile = req.files && req.files['torrentfile'] ? req.files['torrentfile'][0] : undefined - - if (areValidationErrors(req, res)) return cleanUpReqFiles(req) - - if (req.body.targetUrl && CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true) { - cleanUpReqFiles(req) - return res.status(409) - .json({ error: 'HTTP import is not enabled on this instance.' }) - .end() - } - - if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { - cleanUpReqFiles(req) - return res.status(409) - .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) - .end() - } - - if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) - - // Check we have at least 1 required param - if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { - cleanUpReqFiles(req) - - return res.status(400) - .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) - .end() - } - - return next() - } -]) - -// --------------------------------------------------------------------------- - -export { - videoImportAddValidator -} - -// --------------------------------------------------------------------------- diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts deleted file mode 100644 index 67eabe468..000000000 --- a/server/middlewares/validators/videos.ts +++ /dev/null @@ -1,399 +0,0 @@ -import * as express from 'express' -import 'express-validator' -import { body, param, ValidationChain } from 'express-validator/check' -import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../shared' -import { - isBooleanValid, - isDateValid, - isIdOrUUIDValid, - isIdValid, - isUUIDValid, - toIntOrNull, - toValueOrNull -} from '../../helpers/custom-validators/misc' -import { - checkUserCanManageVideo, - isScheduleVideoUpdatePrivacyValid, - isVideoCategoryValid, - isVideoChannelOfAccountExist, - isVideoDescriptionValid, - isVideoExist, - isVideoFile, - isVideoImage, - isVideoLanguageValid, - isVideoLicenceValid, - isVideoNameValid, - isVideoPrivacyValid, - isVideoRatingTypeValid, - isVideoSupportValid, - isVideoTagsValid -} from '../../helpers/custom-validators/videos' -import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' -import { logger } from '../../helpers/logger' -import { CONSTRAINTS_FIELDS } from '../../initializers' -import { VideoShareModel } from '../../models/video/video-share' -import { authenticate } from '../oauth' -import { areValidationErrors } from './utils' -import { cleanUpReqFiles } from '../../helpers/express-utils' -import { VideoModel } from '../../models/video/video' -import { UserModel } from '../../models/account/user' -import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../helpers/custom-validators/video-ownership' -import { VideoChangeOwnershipAccept } from '../../../shared/models/videos/video-change-ownership-accept.model' -import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' -import { AccountModel } from '../../models/account/account' -import { VideoFetchType } from '../../helpers/video' - -const videosAddValidator = getCommonVideoAttributes().concat([ - body('videofile') - .custom((value, { req }) => isVideoFile(req.files)).withMessage( - 'This file is not supported or too large. Please, make sure it is of the following type: ' - + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') - ), - body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), - body('channelId') - .toInt() - .custom(isIdValid).withMessage('Should have correct video channel id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) - - if (areValidationErrors(req, res)) return cleanUpReqFiles(req) - if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) - - const videoFile: Express.Multer.File = req.files['videofile'][0] - const user = res.locals.oauth.token.User - - if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) - - const isAble = await user.isAbleToUploadVideo(videoFile) - if (isAble === false) { - res.status(403) - .json({ error: 'The user video quota is exceeded with this video.' }) - .end() - - return cleanUpReqFiles(req) - } - - let duration: number - - try { - duration = await getDurationFromVideoFile(videoFile.path) - } catch (err) { - logger.error('Invalid input file in videosAddValidator.', { err }) - res.status(400) - .json({ error: 'Invalid input file.' }) - .end() - - return cleanUpReqFiles(req) - } - - videoFile['duration'] = duration - - return next() - } -]) - -const videosUpdateValidator = getCommonVideoAttributes().concat([ - param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - body('name') - .optional() - .custom(isVideoNameValid).withMessage('Should have a valid name'), - body('channelId') - .optional() - .toInt() - .custom(isIdValid).withMessage('Should have correct video channel id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videosUpdate parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return cleanUpReqFiles(req) - if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) - if (!await isVideoExist(req.params.id, res)) return cleanUpReqFiles(req) - - const video = res.locals.video - - // Check if the user who did the request is able to update the video - const user = res.locals.oauth.token.User - if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) - - if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { - cleanUpReqFiles(req) - return res.status(409) - .json({ error: 'Cannot set "private" a video that was not private.' }) - .end() - } - - if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) - - return next() - } -]) - -const videosCustomGetValidator = (fetchType: VideoFetchType) => { - return [ - param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videosGet parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.id, res, fetchType)) return - - const video: VideoModel = res.locals.video - - // Video private or blacklisted - if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { - return authenticate(req, res, () => { - const user: UserModel = res.locals.oauth.token.User - - // Only the owner or a user that have blacklist rights can see the video - if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) { - return res.status(403) - .json({ error: 'Cannot get this private or blacklisted video.' }) - .end() - } - - return next() - }) - } - - // Video is public, anyone can access it - if (video.privacy === VideoPrivacy.PUBLIC) return next() - - // Video is unlisted, check we used the uuid to fetch it - if (video.privacy === VideoPrivacy.UNLISTED) { - if (isUUIDValid(req.params.id)) return next() - - // Don't leak this unlisted video - return res.status(404).end() - } - } - ] -} - -const videosGetValidator = videosCustomGetValidator('all') - -const videosRemoveValidator = [ - param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videosRemove parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.id, res)) return - - // Check if the user who did the request is able to delete the video - if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return - - return next() - } -] - -const videoRateValidator = [ - param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoRate parameters', { parameters: req.body }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.id, res)) return - - return next() - } -] - -const videosShareValidator = [ - param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking videoShare parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.id, res)) return - - const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined) - if (!share) { - return res.status(404) - .end() - } - - res.locals.videoShare = share - return next() - } -] - -const videosChangeOwnershipValidator = [ - param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking changeOwnership parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await isVideoExist(req.params.videoId, res)) return - - // Check if the user who did the request is able to change the ownership of the video - if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return - - const nextOwner = await AccountModel.loadLocalByName(req.body.username) - if (!nextOwner) { - res.status(400) - .type('json') - .end() - return - } - res.locals.nextOwner = nextOwner - - return next() - } -] - -const videosTerminateChangeOwnershipValidator = [ - param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), - - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - logger.debug('Checking changeOwnership parameters', { parameters: req.params }) - - if (areValidationErrors(req, res)) return - if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return - - // Check if the user who did the request is able to change the ownership of the video - if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return - - return next() - }, - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel - - if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { - return next() - } else { - res.status(403) - .json({ error: 'Ownership already accepted or refused' }) - .end() - return - } - } -] - -const videosAcceptChangeOwnershipValidator = [ - async (req: express.Request, res: express.Response, next: express.NextFunction) => { - const body = req.body as VideoChangeOwnershipAccept - if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return - - const user = res.locals.oauth.token.User - const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel - const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile()) - if (isAble === false) { - res.status(403) - .json({ error: 'The user video quota is exceeded with this video.' }) - .end() - return - } - - return next() - } -] - -function getCommonVideoAttributes () { - return [ - body('thumbnailfile') - .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( - 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' - + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') - ), - body('previewfile') - .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( - 'This preview file is not supported or too large. Please, make sure it is of the following type: ' - + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') - ), - - body('category') - .optional() - .customSanitizer(toIntOrNull) - .custom(isVideoCategoryValid).withMessage('Should have a valid category'), - body('licence') - .optional() - .customSanitizer(toIntOrNull) - .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), - body('language') - .optional() - .customSanitizer(toValueOrNull) - .custom(isVideoLanguageValid).withMessage('Should have a valid language'), - body('nsfw') - .optional() - .toBoolean() - .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), - body('waitTranscoding') - .optional() - .toBoolean() - .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'), - body('privacy') - .optional() - .toInt() - .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), - body('description') - .optional() - .customSanitizer(toValueOrNull) - .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), - body('support') - .optional() - .customSanitizer(toValueOrNull) - .custom(isVideoSupportValid).withMessage('Should have a valid support text'), - body('tags') - .optional() - .customSanitizer(toValueOrNull) - .custom(isVideoTagsValid).withMessage('Should have correct tags'), - body('commentsEnabled') - .optional() - .toBoolean() - .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), - - body('scheduleUpdate') - .optional() - .customSanitizer(toValueOrNull), - body('scheduleUpdate.updateAt') - .optional() - .custom(isDateValid).withMessage('Should have a valid schedule update date'), - body('scheduleUpdate.privacy') - .optional() - .toInt() - .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy') - ] as (ValidationChain | express.Handler)[] -} - -// --------------------------------------------------------------------------- - -export { - videosAddValidator, - videosUpdateValidator, - videosGetValidator, - videosCustomGetValidator, - videosRemoveValidator, - videosShareValidator, - - videoRateValidator, - - videosChangeOwnershipValidator, - videosTerminateChangeOwnershipValidator, - videosAcceptChangeOwnershipValidator, - - getCommonVideoAttributes -} - -// --------------------------------------------------------------------------- - -function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) { - if (req.body.scheduleUpdate) { - if (!req.body.scheduleUpdate.updateAt) { - res.status(400) - .json({ error: 'Schedule update at is mandatory.' }) - .end() - - return true - } - } - - return false -} 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 @@ +export * from './video-abuses' +export * from './video-blacklist' +export * from './video-captions' +export * from './video-channels' +export * from './video-comments' +export * from './video-imports' +export * from './video-watch' +export * 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 @@ +import * as express from 'express' +import 'express-validator' +import { body, param } from 'express-validator/check' +import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' +import { isVideoExist } from '../../../helpers/custom-validators/videos' +import { logger } from '../../../helpers/logger' +import { areValidationErrors } from '../utils' +import { + isVideoAbuseExist, + isVideoAbuseModerationCommentValid, + isVideoAbuseReasonValid, + isVideoAbuseStateValid +} from '../../../helpers/custom-validators/video-abuses' + +const videoAbuseReportValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + + return next() + } +] + +const videoAbuseGetValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return + + return next() + } +] + +const videoAbuseUpdateValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), + body('state') + .optional() + .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), + body('moderationComment') + .optional() + .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + videoAbuseReportValidator, + videoAbuseGetValidator, + videoAbuseUpdateValidator +} 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 @@ +import * as express from 'express' +import { body, param } from 'express-validator/check' +import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' +import { isVideoExist } from '../../../helpers/custom-validators/videos' +import { logger } from '../../../helpers/logger' +import { areValidationErrors } from '../utils' +import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../../helpers/custom-validators/video-blacklist' + +const videosBlacklistRemoveValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!await isVideoBlacklistExist(res.locals.video.id, res)) return + + return next() + } +] + +const videosBlacklistAddValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + body('reason') + .optional() + .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + + return next() + } +] + +const videosBlacklistUpdateValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + body('reason') + .optional() + .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!await isVideoBlacklistExist(res.locals.video.id, res)) return + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + videosBlacklistAddValidator, + videosBlacklistRemoveValidator, + videosBlacklistUpdateValidator +} 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 @@ +import * as express from 'express' +import { areValidationErrors } from '../utils' +import { checkUserCanManageVideo, isVideoExist } from '../../../helpers/custom-validators/videos' +import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' +import { body, param } from 'express-validator/check' +import { CONSTRAINTS_FIELDS } from '../../../initializers' +import { UserRight } from '../../../../shared' +import { logger } from '../../../helpers/logger' +import { isVideoCaptionExist, isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' +import { cleanUpReqFiles } from '../../../helpers/express-utils' + +const addVideoCaptionValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), + param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), + body('captionfile') + .custom((value, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage( + 'This caption file is not supported or too large. Please, make sure it is of the following type : ' + + CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME.join(', ') + ), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + if (!await isVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req) + + // Check if the user who did the request is able to update the video + const user = res.locals.oauth.token.User + if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) + + return next() + } +] + +const deleteVideoCaptionValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), + param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!await isVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return + + // Check if the user who did the request is able to update the video + const user = res.locals.oauth.token.User + if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return + + return next() + } +] + +const listVideoCaptionsValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res, 'id')) return + + return next() + } +] + +export { + addVideoCaptionValidator, + listVideoCaptionsValidator, + deleteVideoCaptionValidator +} 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 @@ +import * as express from 'express' +import { body, param } from 'express-validator/check' +import { UserRight } from '../../../../shared' +import { isAccountNameWithHostExist } from '../../../helpers/custom-validators/accounts' +import { + isLocalVideoChannelNameExist, + isVideoChannelDescriptionValid, + isVideoChannelNameValid, + isVideoChannelNameWithHostExist, + isVideoChannelSupportValid +} from '../../../helpers/custom-validators/video-channels' +import { logger } from '../../../helpers/logger' +import { UserModel } from '../../../models/account/user' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { areValidationErrors } from '../utils' +import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' +import { ActorModel } from '../../../models/activitypub/actor' + +const listVideoAccountChannelsValidator = [ + param('accountName').exists().withMessage('Should have a valid account name'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isAccountNameWithHostExist(req.params.accountName, res)) return + + return next() + } +] + +const videoChannelsAddValidator = [ + body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), + body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), + body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), + body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoChannelsAdd parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + + const actor = await ActorModel.loadLocalByName(req.body.name) + if (actor) { + res.status(409) + .send({ error: 'Another actor (account/channel) with this name on this instance already exists or has already existed.' }) + .end() + return false + } + + return next() + } +] + +const videoChannelsUpdateValidator = [ + param('nameWithHost').exists().withMessage('Should have an video channel name with host'), + body('displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), + body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), + body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return + + // We need to make additional checks + if (res.locals.videoChannel.Actor.isOwned() === false) { + return res.status(403) + .json({ error: 'Cannot update video channel of another server' }) + .end() + } + + if (res.locals.videoChannel.Account.userId !== res.locals.oauth.token.User.id) { + return res.status(403) + .json({ error: 'Cannot update video channel of another user' }) + .end() + } + + return next() + } +] + +const videoChannelsRemoveValidator = [ + param('nameWithHost').exists().withMessage('Should have an video channel name with host'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return + + if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return + if (!await checkVideoChannelIsNotTheLastOne(res)) return + + return next() + } +] + +const videoChannelsNameWithHostValidator = [ + param('nameWithHost').exists().withMessage('Should have an video channel name with host'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoChannelsNameWithHostValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + + if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return + + return next() + } +] + +const localVideoChannelValidator = [ + param('name').custom(isVideoChannelNameValid).withMessage('Should have a valid video channel name'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isLocalVideoChannelNameExist(req.params.name, res)) return + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + listVideoAccountChannelsValidator, + videoChannelsAddValidator, + videoChannelsUpdateValidator, + videoChannelsRemoveValidator, + videoChannelsNameWithHostValidator, + localVideoChannelValidator +} + +// --------------------------------------------------------------------------- + +function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { + if (videoChannel.Actor.isOwned() === false) { + res.status(403) + .json({ error: 'Cannot remove video channel of another server.' }) + .end() + + return false + } + + // Check if the user can delete the video channel + // The user can delete it if s/he is an admin + // Or if s/he is the video channel's account + if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) { + res.status(403) + .json({ error: 'Cannot remove video channel of another user' }) + .end() + + return false + } + + return true +} + +async function checkVideoChannelIsNotTheLastOne (res: express.Response) { + const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) + + if (count <= 1) { + res.status(409) + .json({ error: 'Cannot remove the last channel of this user' }) + .end() + + return false + } + + return true +} 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 @@ +import * as express from 'express' +import { body, param } from 'express-validator/check' +import { UserRight } from '../../../../shared' +import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' +import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' +import { isVideoExist } from '../../../helpers/custom-validators/videos' +import { logger } from '../../../helpers/logger' +import { UserModel } from '../../../models/account/user' +import { VideoModel } from '../../../models/video/video' +import { VideoCommentModel } from '../../../models/video/video-comment' +import { areValidationErrors } from '../utils' + +const listVideoCommentThreadsValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res, 'only-video')) return + + return next() + } +] + +const listVideoThreadCommentsValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res, 'only-video')) return + if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return + + return next() + } +] + +const addVideoCommentThreadValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!isVideoCommentsEnabled(res.locals.video, res)) return + + return next() + } +] + +const addVideoCommentReplyValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), + body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!isVideoCommentsEnabled(res.locals.video, res)) return + if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return + + return next() + } +] + +const videoCommentGetValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res, 'id')) return + if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return + + return next() + } +] + +const removeVideoCommentValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), + param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return + + // Check if the user who did the request is able to delete the video + if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + listVideoCommentThreadsValidator, + listVideoThreadCommentsValidator, + addVideoCommentThreadValidator, + addVideoCommentReplyValidator, + videoCommentGetValidator, + removeVideoCommentValidator +} + +// --------------------------------------------------------------------------- + +async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { + const videoComment = await VideoCommentModel.loadById(id) + + if (!videoComment) { + res.status(404) + .json({ error: 'Video comment thread not found' }) + .end() + + return false + } + + if (videoComment.videoId !== video.id) { + res.status(400) + .json({ error: 'Video comment is associated to this video.' }) + .end() + + return false + } + + if (videoComment.inReplyToCommentId !== null) { + res.status(400) + .json({ error: 'Video comment is not a thread.' }) + .end() + + return false + } + + res.locals.videoCommentThread = videoComment + return true +} + +async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) { + const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) + + if (!videoComment) { + res.status(404) + .json({ error: 'Video comment thread not found' }) + .end() + + return false + } + + if (videoComment.videoId !== video.id) { + res.status(400) + .json({ error: 'Video comment is associated to this video.' }) + .end() + + return false + } + + res.locals.videoComment = videoComment + return true +} + +function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { + if (video.commentsEnabled !== true) { + res.status(409) + .json({ error: 'Video comments are disabled for this video.' }) + .end() + + return false + } + + return true +} + +function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) { + const account = videoComment.Account + if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { + res.status(403) + .json({ error: 'Cannot remove video comment of another user' }) + .end() + return false + } + + return true +} 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 @@ +import * as express from 'express' +import { body } from 'express-validator/check' +import { isIdValid } from '../../../helpers/custom-validators/misc' +import { logger } from '../../../helpers/logger' +import { areValidationErrors } from '../utils' +import { getCommonVideoAttributes } from './videos' +import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' +import { cleanUpReqFiles } from '../../../helpers/express-utils' +import { isVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' +import { CONFIG } from '../../../initializers/constants' +import { CONSTRAINTS_FIELDS } from '../../../initializers' + +const videoImportAddValidator = getCommonVideoAttributes().concat([ + body('channelId') + .toInt() + .custom(isIdValid).withMessage('Should have correct video channel id'), + body('targetUrl') + .optional() + .custom(isVideoImportTargetUrlValid).withMessage('Should have a valid video import target URL'), + body('magnetUri') + .optional() + .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), + body('torrentfile') + .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage( + 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' + + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') + ), + body('name') + .optional() + .custom(isVideoNameValid).withMessage('Should have a valid name'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body }) + + const user = res.locals.oauth.token.User + const torrentFile = req.files && req.files['torrentfile'] ? req.files['torrentfile'][0] : undefined + + if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + + if (req.body.targetUrl && CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true) { + cleanUpReqFiles(req) + return res.status(409) + .json({ error: 'HTTP import is not enabled on this instance.' }) + .end() + } + + if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) { + cleanUpReqFiles(req) + return res.status(409) + .json({ error: 'Torrent/magnet URI import is not enabled on this instance.' }) + .end() + } + + if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) + + // Check we have at least 1 required param + if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { + cleanUpReqFiles(req) + + return res.status(400) + .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' }) + .end() + } + + return next() + } +]) + +// --------------------------------------------------------------------------- + +export { + videoImportAddValidator +} + +// --------------------------------------------------------------------------- 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 @@ +import { body, param } from 'express-validator/check' +import * as express from 'express' +import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' +import { isVideoExist } from '../../../helpers/custom-validators/videos' +import { areValidationErrors } from '../utils' +import { logger } from '../../../helpers/logger' + +const videoWatchingValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + body('currentTime') + .toInt() + .isInt().withMessage('Should have correct current time'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoWatching parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res, 'id')) return + + return next() + } +] + +// --------------------------------------------------------------------------- + +export { + videoWatchingValidator +} 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 @@ +import * as express from 'express' +import 'express-validator' +import { body, param, ValidationChain } from 'express-validator/check' +import { UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' +import { + isBooleanValid, + isDateValid, + isIdOrUUIDValid, + isIdValid, + isUUIDValid, + toIntOrNull, + toValueOrNull +} from '../../../helpers/custom-validators/misc' +import { + checkUserCanManageVideo, + isScheduleVideoUpdatePrivacyValid, + isVideoCategoryValid, + isVideoChannelOfAccountExist, + isVideoDescriptionValid, + isVideoExist, + isVideoFile, + isVideoImage, + isVideoLanguageValid, + isVideoLicenceValid, + isVideoNameValid, + isVideoPrivacyValid, + isVideoRatingTypeValid, + isVideoSupportValid, + isVideoTagsValid +} from '../../../helpers/custom-validators/videos' +import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils' +import { logger } from '../../../helpers/logger' +import { CONSTRAINTS_FIELDS } from '../../../initializers' +import { VideoShareModel } from '../../../models/video/video-share' +import { authenticate } from '../../oauth' +import { areValidationErrors } from '../utils' +import { cleanUpReqFiles } from '../../../helpers/express-utils' +import { VideoModel } from '../../../models/video/video' +import { UserModel } from '../../../models/account/user' +import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' +import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' +import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' +import { AccountModel } from '../../../models/account/account' +import { VideoFetchType } from '../../../helpers/video' + +const videosAddValidator = getCommonVideoAttributes().concat([ + body('videofile') + .custom((value, { req }) => isVideoFile(req.files)).withMessage( + 'This file is not supported or too large. Please, make sure it is of the following type: ' + + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') + ), + body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), + body('channelId') + .toInt() + .custom(isIdValid).withMessage('Should have correct video channel id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) + + if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) + + const videoFile: Express.Multer.File = req.files['videofile'][0] + const user = res.locals.oauth.token.User + + if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) + + const isAble = await user.isAbleToUploadVideo(videoFile) + if (isAble === false) { + res.status(403) + .json({ error: 'The user video quota is exceeded with this video.' }) + .end() + + return cleanUpReqFiles(req) + } + + let duration: number + + try { + duration = await getDurationFromVideoFile(videoFile.path) + } catch (err) { + logger.error('Invalid input file in videosAddValidator.', { err }) + res.status(400) + .json({ error: 'Invalid input file.' }) + .end() + + return cleanUpReqFiles(req) + } + + videoFile['duration'] = duration + + return next() + } +]) + +const videosUpdateValidator = getCommonVideoAttributes().concat([ + param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + body('name') + .optional() + .custom(isVideoNameValid).withMessage('Should have a valid name'), + body('channelId') + .optional() + .toInt() + .custom(isIdValid).withMessage('Should have correct video channel id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videosUpdate parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) + if (!await isVideoExist(req.params.id, res)) return cleanUpReqFiles(req) + + const video = res.locals.video + + // Check if the user who did the request is able to update the video + const user = res.locals.oauth.token.User + if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) + + if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { + cleanUpReqFiles(req) + return res.status(409) + .json({ error: 'Cannot set "private" a video that was not private.' }) + .end() + } + + if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) + + return next() + } +]) + +const videosCustomGetValidator = (fetchType: VideoFetchType) => { + return [ + param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videosGet parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.id, res, fetchType)) return + + const video: VideoModel = res.locals.video + + // Video private or blacklisted + if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { + return authenticate(req, res, () => { + const user: UserModel = res.locals.oauth.token.User + + // Only the owner or a user that have blacklist rights can see the video + if (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) { + return res.status(403) + .json({ error: 'Cannot get this private or blacklisted video.' }) + .end() + } + + return next() + }) + } + + // Video is public, anyone can access it + if (video.privacy === VideoPrivacy.PUBLIC) return next() + + // Video is unlisted, check we used the uuid to fetch it + if (video.privacy === VideoPrivacy.UNLISTED) { + if (isUUIDValid(req.params.id)) return next() + + // Don't leak this unlisted video + return res.status(404).end() + } + } + ] +} + +const videosGetValidator = videosCustomGetValidator('all') + +const videosRemoveValidator = [ + param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videosRemove parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.id, res)) return + + // Check if the user who did the request is able to delete the video + if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return + + return next() + } +] + +const videoRateValidator = [ + param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoRate parameters', { parameters: req.body }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.id, res)) return + + return next() + } +] + +const videosShareValidator = [ + param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + param('accountId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid account id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking videoShare parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.id, res)) return + + const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined) + if (!share) { + return res.status(404) + .end() + } + + res.locals.videoShare = share + return next() + } +] + +const videosChangeOwnershipValidator = [ + param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking changeOwnership parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await isVideoExist(req.params.videoId, res)) return + + // Check if the user who did the request is able to change the ownership of the video + if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return + + const nextOwner = await AccountModel.loadLocalByName(req.body.username) + if (!nextOwner) { + res.status(400) + .type('json') + .end() + return + } + res.locals.nextOwner = nextOwner + + return next() + } +] + +const videosTerminateChangeOwnershipValidator = [ + param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), + + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + logger.debug('Checking changeOwnership parameters', { parameters: req.params }) + + if (areValidationErrors(req, res)) return + if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return + + // Check if the user who did the request is able to change the ownership of the video + if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return + + return next() + }, + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel + + if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { + return next() + } else { + res.status(403) + .json({ error: 'Ownership already accepted or refused' }) + .end() + return + } + } +] + +const videosAcceptChangeOwnershipValidator = [ + async (req: express.Request, res: express.Response, next: express.NextFunction) => { + const body = req.body as VideoChangeOwnershipAccept + if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return + + const user = res.locals.oauth.token.User + const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel + const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile()) + if (isAble === false) { + res.status(403) + .json({ error: 'The user video quota is exceeded with this video.' }) + .end() + return + } + + return next() + } +] + +function getCommonVideoAttributes () { + return [ + body('thumbnailfile') + .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( + 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' + + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') + ), + body('previewfile') + .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( + 'This preview file is not supported or too large. Please, make sure it is of the following type: ' + + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') + ), + + body('category') + .optional() + .customSanitizer(toIntOrNull) + .custom(isVideoCategoryValid).withMessage('Should have a valid category'), + body('licence') + .optional() + .customSanitizer(toIntOrNull) + .custom(isVideoLicenceValid).withMessage('Should have a valid licence'), + body('language') + .optional() + .customSanitizer(toValueOrNull) + .custom(isVideoLanguageValid).withMessage('Should have a valid language'), + body('nsfw') + .optional() + .toBoolean() + .custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'), + body('waitTranscoding') + .optional() + .toBoolean() + .custom(isBooleanValid).withMessage('Should have a valid wait transcoding attribute'), + body('privacy') + .optional() + .toInt() + .custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), + body('description') + .optional() + .customSanitizer(toValueOrNull) + .custom(isVideoDescriptionValid).withMessage('Should have a valid description'), + body('support') + .optional() + .customSanitizer(toValueOrNull) + .custom(isVideoSupportValid).withMessage('Should have a valid support text'), + body('tags') + .optional() + .customSanitizer(toValueOrNull) + .custom(isVideoTagsValid).withMessage('Should have correct tags'), + body('commentsEnabled') + .optional() + .toBoolean() + .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), + + body('scheduleUpdate') + .optional() + .customSanitizer(toValueOrNull), + body('scheduleUpdate.updateAt') + .optional() + .custom(isDateValid).withMessage('Should have a valid schedule update date'), + body('scheduleUpdate.privacy') + .optional() + .toInt() + .custom(isScheduleVideoUpdatePrivacyValid).withMessage('Should have correct schedule update privacy') + ] as (ValidationChain | express.Handler)[] +} + +// --------------------------------------------------------------------------- + +export { + videosAddValidator, + videosUpdateValidator, + videosGetValidator, + videosCustomGetValidator, + videosRemoveValidator, + videosShareValidator, + + videoRateValidator, + + videosChangeOwnershipValidator, + videosTerminateChangeOwnershipValidator, + videosAcceptChangeOwnershipValidator, + + getCommonVideoAttributes +} + +// --------------------------------------------------------------------------- + +function areErrorsInScheduleUpdate (req: express.Request, res: express.Response) { + if (req.body.scheduleUpdate) { + if (!req.body.scheduleUpdate.updateAt) { + res.status(400) + .json({ error: 'Schedule update at is mandatory.' }) + .end() + + return true + } + } + + return false +} -- cgit v1.2.3