aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/auth.ts15
-rw-r--r--server/middlewares/validators/config.ts7
-rw-r--r--server/middlewares/validators/shared/index.ts1
-rw-r--r--server/middlewares/validators/shared/video-passwords.ts80
-rw-r--r--server/middlewares/validators/shared/videos.ts85
-rw-r--r--server/middlewares/validators/sort.ts1
-rw-r--r--server/middlewares/validators/static.ts18
-rw-r--r--server/middlewares/validators/videos/index.ts2
-rw-r--r--server/middlewares/validators/videos/video-captions.ts5
-rw-r--r--server/middlewares/validators/videos/video-comments.ts7
-rw-r--r--server/middlewares/validators/videos/video-files.ts26
-rw-r--r--server/middlewares/validators/videos/video-imports.ts12
-rw-r--r--server/middlewares/validators/videos/video-live.ts13
-rw-r--r--server/middlewares/validators/videos/video-passwords.ts77
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts2
-rw-r--r--server/middlewares/validators/videos/video-rates.ts3
-rw-r--r--server/middlewares/validators/videos/video-token.ts24
-rw-r--r--server/middlewares/validators/videos/videos.ts52
18 files changed, 374 insertions, 56 deletions
diff --git a/server/middlewares/auth.ts b/server/middlewares/auth.ts
index 0eefa2a8e..39a7b2998 100644
--- a/server/middlewares/auth.ts
+++ b/server/middlewares/auth.ts
@@ -5,6 +5,7 @@ import { RunnerModel } from '@server/models/runner/runner'
5import { HttpStatusCode } from '../../shared/models/http/http-error-codes' 5import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
6import { logger } from '../helpers/logger' 6import { logger } from '../helpers/logger'
7import { handleOAuthAuthenticate } from '../lib/auth/oauth' 7import { handleOAuthAuthenticate } from '../lib/auth/oauth'
8import { ServerErrorCode } from '@shared/models'
8 9
9function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) { 10function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) {
10 handleOAuthAuthenticate(req, res) 11 handleOAuthAuthenticate(req, res)
@@ -48,15 +49,23 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
48 .catch(err => logger.error('Cannot get access token.', { err })) 49 .catch(err => logger.error('Cannot get access token.', { err }))
49} 50}
50 51
51function authenticatePromise (req: express.Request, res: express.Response) { 52function authenticatePromise (options: {
53 req: express.Request
54 res: express.Response
55 errorMessage?: string
56 errorStatus?: HttpStatusCode
57 errorType?: ServerErrorCode
58}) {
59 const { req, res, errorMessage = 'Not authenticated', errorStatus = HttpStatusCode.UNAUTHORIZED_401, errorType } = options
52 return new Promise<void>(resolve => { 60 return new Promise<void>(resolve => {
53 // Already authenticated? (or tried to) 61 // Already authenticated? (or tried to)
54 if (res.locals.oauth?.token.User) return resolve() 62 if (res.locals.oauth?.token.User) return resolve()
55 63
56 if (res.locals.authenticated === false) { 64 if (res.locals.authenticated === false) {
57 return res.fail({ 65 return res.fail({
58 status: HttpStatusCode.UNAUTHORIZED_401, 66 status: errorStatus,
59 message: 'Not authenticated' 67 type: errorType,
68 message: errorMessage
60 }) 69 })
61 } 70 }
62 71
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts
index a0074cb24..a6dbba524 100644
--- a/server/middlewares/validators/config.ts
+++ b/server/middlewares/validators/config.ts
@@ -25,6 +25,7 @@ const customConfigUpdateValidator = [
25 body('cache.previews.size').isInt(), 25 body('cache.previews.size').isInt(),
26 body('cache.captions.size').isInt(), 26 body('cache.captions.size').isInt(),
27 body('cache.torrents.size').isInt(), 27 body('cache.torrents.size').isInt(),
28 body('cache.storyboards.size').isInt(),
28 29
29 body('signup.enabled').isBoolean(), 30 body('signup.enabled').isBoolean(),
30 body('signup.limit').isInt(), 31 body('signup.limit').isInt(),
@@ -58,7 +59,7 @@ const customConfigUpdateValidator = [
58 59
59 body('transcoding.alwaysTranscodeOriginalResolution').isBoolean(), 60 body('transcoding.alwaysTranscodeOriginalResolution').isBoolean(),
60 61
61 body('transcoding.webtorrent.enabled').isBoolean(), 62 body('transcoding.webVideos.enabled').isBoolean(),
62 body('transcoding.hls.enabled').isBoolean(), 63 body('transcoding.hls.enabled').isBoolean(),
63 64
64 body('videoStudio.enabled').isBoolean(), 65 body('videoStudio.enabled').isBoolean(),
@@ -152,8 +153,8 @@ function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: exp
152function checkInvalidTranscodingConfig (customConfig: CustomConfig, res: express.Response) { 153function checkInvalidTranscodingConfig (customConfig: CustomConfig, res: express.Response) {
153 if (customConfig.transcoding.enabled === false) return true 154 if (customConfig.transcoding.enabled === false) return true
154 155
155 if (customConfig.transcoding.webtorrent.enabled === false && customConfig.transcoding.hls.enabled === false) { 156 if (customConfig.transcoding.webVideos.enabled === false && customConfig.transcoding.hls.enabled === false) {
156 res.fail({ message: 'You need to enable at least webtorrent transcoding or hls transcoding' }) 157 res.fail({ message: 'You need to enable at least web_videos transcoding or hls transcoding' })
157 return false 158 return false
158 } 159 }
159 160
diff --git a/server/middlewares/validators/shared/index.ts b/server/middlewares/validators/shared/index.ts
index de98cd442..e5cff2dda 100644
--- a/server/middlewares/validators/shared/index.ts
+++ b/server/middlewares/validators/shared/index.ts
@@ -10,4 +10,5 @@ export * from './video-comments'
10export * from './video-imports' 10export * from './video-imports'
11export * from './video-ownerships' 11export * from './video-ownerships'
12export * from './video-playlists' 12export * from './video-playlists'
13export * from './video-passwords'
13export * from './videos' 14export * from './videos'
diff --git a/server/middlewares/validators/shared/video-passwords.ts b/server/middlewares/validators/shared/video-passwords.ts
new file mode 100644
index 000000000..efcc95dc4
--- /dev/null
+++ b/server/middlewares/validators/shared/video-passwords.ts
@@ -0,0 +1,80 @@
1import express from 'express'
2import { HttpStatusCode, UserRight, VideoPrivacy } from '@shared/models'
3import { forceNumber } from '@shared/core-utils'
4import { VideoPasswordModel } from '@server/models/video/video-password'
5import { header } from 'express-validator'
6import { getVideoWithAttributes } from '@server/helpers/video'
7
8function isValidVideoPasswordHeader () {
9 return header('x-peertube-video-password')
10 .optional()
11 .isString()
12}
13
14function checkVideoIsPasswordProtected (res: express.Response) {
15 const video = getVideoWithAttributes(res)
16 if (video.privacy !== VideoPrivacy.PASSWORD_PROTECTED) {
17 res.fail({
18 status: HttpStatusCode.BAD_REQUEST_400,
19 message: 'Video is not password protected'
20 })
21 return false
22 }
23
24 return true
25}
26
27async function doesVideoPasswordExist (idArg: number | string, res: express.Response) {
28 const video = getVideoWithAttributes(res)
29 const id = forceNumber(idArg)
30 const videoPassword = await VideoPasswordModel.loadByIdAndVideo({ id, videoId: video.id })
31
32 if (!videoPassword) {
33 res.fail({
34 status: HttpStatusCode.NOT_FOUND_404,
35 message: 'Video password not found'
36 })
37 return false
38 }
39
40 res.locals.videoPassword = videoPassword
41
42 return true
43}
44
45async function isVideoPasswordDeletable (res: express.Response) {
46 const user = res.locals.oauth.token.User
47 const userAccount = user.Account
48 const video = res.locals.videoAll
49
50 // Check if the user who did the request is able to delete the video passwords
51 if (
52 user.hasRight(UserRight.UPDATE_ANY_VIDEO) === false && // Not a moderator
53 video.VideoChannel.accountId !== userAccount.id // Not the video owner
54 ) {
55 res.fail({
56 status: HttpStatusCode.FORBIDDEN_403,
57 message: 'Cannot remove passwords of another user\'s video'
58 })
59 return false
60 }
61
62 const passwordCount = await VideoPasswordModel.countByVideoId(video.id)
63
64 if (passwordCount <= 1) {
65 res.fail({
66 status: HttpStatusCode.BAD_REQUEST_400,
67 message: 'Cannot delete the last password of the protected video'
68 })
69 return false
70 }
71
72 return true
73}
74
75export {
76 isValidVideoPasswordHeader,
77 checkVideoIsPasswordProtected as isVideoPasswordProtected,
78 doesVideoPasswordExist,
79 isVideoPasswordDeletable
80}
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts
index 0033a32ff..9a7497007 100644
--- a/server/middlewares/validators/shared/videos.ts
+++ b/server/middlewares/validators/shared/videos.ts
@@ -20,6 +20,8 @@ import {
20 MVideoWithRights 20 MVideoWithRights
21} from '@server/types/models' 21} from '@server/types/models'
22import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models' 22import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models'
23import { VideoPasswordModel } from '@server/models/video/video-password'
24import { exists } from '@server/helpers/custom-validators/misc'
23 25
24async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { 26async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') {
25 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined 27 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
@@ -111,8 +113,12 @@ async function checkCanSeeVideo (options: {
111}) { 113}) {
112 const { req, res, video, paramId } = options 114 const { req, res, video, paramId } = options
113 115
114 if (video.requiresAuth({ urlParamId: paramId, checkBlacklist: true })) { 116 if (video.requiresUserAuth({ urlParamId: paramId, checkBlacklist: true })) {
115 return checkCanSeeAuthVideo(req, res, video) 117 return checkCanSeeUserAuthVideo({ req, res, video })
118 }
119
120 if (video.privacy === VideoPrivacy.PASSWORD_PROTECTED) {
121 return checkCanSeePasswordProtectedVideo({ req, res, video })
116 } 122 }
117 123
118 if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) { 124 if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) {
@@ -122,7 +128,13 @@ async function checkCanSeeVideo (options: {
122 throw new Error('Unknown video privacy when checking video right ' + video.url) 128 throw new Error('Unknown video privacy when checking video right ' + video.url)
123} 129}
124 130
125async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoId | MVideoWithRights) { 131async function checkCanSeeUserAuthVideo (options: {
132 req: Request
133 res: Response
134 video: MVideoId | MVideoWithRights
135}) {
136 const { req, res, video } = options
137
126 const fail = () => { 138 const fail = () => {
127 res.fail({ 139 res.fail({
128 status: HttpStatusCode.FORBIDDEN_403, 140 status: HttpStatusCode.FORBIDDEN_403,
@@ -132,14 +144,12 @@ async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoI
132 return false 144 return false
133 } 145 }
134 146
135 await authenticatePromise(req, res) 147 await authenticatePromise({ req, res })
136 148
137 const user = res.locals.oauth?.token.User 149 const user = res.locals.oauth?.token.User
138 if (!user) return fail() 150 if (!user) return fail()
139 151
140 const videoWithRights = (video as MVideoWithRights).VideoChannel?.Account?.userId 152 const videoWithRights = await getVideoWithRights(video as MVideoWithRights)
141 ? video as MVideoWithRights
142 : await VideoModel.loadFull(video.id)
143 153
144 const privacy = videoWithRights.privacy 154 const privacy = videoWithRights.privacy
145 155
@@ -148,16 +158,14 @@ async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoI
148 return true 158 return true
149 } 159 }
150 160
151 const isOwnedByUser = videoWithRights.VideoChannel.Account.userId === user.id
152
153 if (videoWithRights.isBlacklisted()) { 161 if (videoWithRights.isBlacklisted()) {
154 if (isOwnedByUser || user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) return true 162 if (canUserAccessVideo(user, videoWithRights, UserRight.MANAGE_VIDEO_BLACKLIST)) return true
155 163
156 return fail() 164 return fail()
157 } 165 }
158 166
159 if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) { 167 if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) {
160 if (isOwnedByUser || user.hasRight(UserRight.SEE_ALL_VIDEOS)) return true 168 if (canUserAccessVideo(user, videoWithRights, UserRight.SEE_ALL_VIDEOS)) return true
161 169
162 return fail() 170 return fail()
163 } 171 }
@@ -166,6 +174,59 @@ async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoI
166 return fail() 174 return fail()
167} 175}
168 176
177async function checkCanSeePasswordProtectedVideo (options: {
178 req: Request
179 res: Response
180 video: MVideo
181}) {
182 const { req, res, video } = options
183
184 const videoWithRights = await getVideoWithRights(video as MVideoWithRights)
185
186 const videoPassword = req.header('x-peertube-video-password')
187
188 if (!exists(videoPassword)) {
189 const errorMessage = 'Please provide a password to access this password protected video'
190 const errorType = ServerErrorCode.VIDEO_REQUIRES_PASSWORD
191
192 if (req.header('authorization')) {
193 await authenticatePromise({ req, res, errorMessage, errorStatus: HttpStatusCode.FORBIDDEN_403, errorType })
194 const user = res.locals.oauth?.token.User
195
196 if (canUserAccessVideo(user, videoWithRights, UserRight.SEE_ALL_VIDEOS)) return true
197 }
198
199 res.fail({
200 status: HttpStatusCode.FORBIDDEN_403,
201 type: errorType,
202 message: errorMessage
203 })
204 return false
205 }
206
207 if (await VideoPasswordModel.isACorrectPassword({ videoId: video.id, password: videoPassword })) return true
208
209 res.fail({
210 status: HttpStatusCode.FORBIDDEN_403,
211 type: ServerErrorCode.INCORRECT_VIDEO_PASSWORD,
212 message: 'Incorrect video password. Access to the video is denied.'
213 })
214
215 return false
216}
217
218function canUserAccessVideo (user: MUser, video: MVideoWithRights | MVideoAccountLight, right: UserRight) {
219 const isOwnedByUser = video.VideoChannel.Account.userId === user.id
220
221 return isOwnedByUser || user.hasRight(right)
222}
223
224async function getVideoWithRights (video: MVideoWithRights): Promise<MVideoWithRights> {
225 return video.VideoChannel?.Account?.userId
226 ? video
227 : VideoModel.loadFull(video.id)
228}
229
169// --------------------------------------------------------------------------- 230// ---------------------------------------------------------------------------
170 231
171async function checkCanAccessVideoStaticFiles (options: { 232async function checkCanAccessVideoStaticFiles (options: {
@@ -176,7 +237,7 @@ async function checkCanAccessVideoStaticFiles (options: {
176}) { 237}) {
177 const { video, req, res } = options 238 const { video, req, res } = options
178 239
179 if (res.locals.oauth?.token.User) { 240 if (res.locals.oauth?.token.User || exists(req.header('x-peertube-video-password'))) {
180 return checkCanSeeVideo(options) 241 return checkCanSeeVideo(options)
181 } 242 }
182 243
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index 959f663ac..07d6cba82 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -28,6 +28,7 @@ export const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS)
28export const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) 28export const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
29export const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) 29export const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES)
30export const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS) 30export const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS)
31export const videoPasswordsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PASSWORDS)
31 32
32export const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) 33export const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS)
33export const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) 34export const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS)
diff --git a/server/middlewares/validators/static.ts b/server/middlewares/validators/static.ts
index 9c2d890ba..86cc0a8d7 100644
--- a/server/middlewares/validators/static.ts
+++ b/server/middlewares/validators/static.ts
@@ -9,7 +9,7 @@ import { VideoModel } from '@server/models/video/video'
9import { VideoFileModel } from '@server/models/video/video-file' 9import { VideoFileModel } from '@server/models/video/video-file'
10import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models' 10import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models'
11import { HttpStatusCode } from '@shared/models' 11import { HttpStatusCode } from '@shared/models'
12import { areValidationErrors, checkCanAccessVideoStaticFiles } from './shared' 12import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared'
13 13
14type LRUValue = { 14type LRUValue = {
15 allowed: boolean 15 allowed: boolean
@@ -22,9 +22,11 @@ const staticFileTokenBypass = new LRUCache<string, LRUValue>({
22 ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL 22 ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL
23}) 23})
24 24
25const ensureCanAccessVideoPrivateWebTorrentFiles = [ 25const ensureCanAccessVideoPrivateWebVideoFiles = [
26 query('videoFileToken').optional().custom(exists), 26 query('videoFileToken').optional().custom(exists),
27 27
28 isValidVideoPasswordHeader(),
29
28 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 30 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
29 if (areValidationErrors(req, res)) return 31 if (areValidationErrors(req, res)) return
30 32
@@ -46,7 +48,7 @@ const ensureCanAccessVideoPrivateWebTorrentFiles = [
46 return res.sendStatus(HttpStatusCode.FORBIDDEN_403) 48 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
47 } 49 }
48 50
49 const result = await isWebTorrentAllowed(req, res) 51 const result = await isWebVideoAllowed(req, res)
50 52
51 staticFileTokenBypass.set(cacheKey, result) 53 staticFileTokenBypass.set(cacheKey, result)
52 54
@@ -73,6 +75,8 @@ const ensureCanAccessPrivateVideoHLSFiles = [
73 .optional() 75 .optional()
74 .customSanitizer(isSafePeerTubeFilenameWithoutExtension), 76 .customSanitizer(isSafePeerTubeFilenameWithoutExtension),
75 77
78 isValidVideoPasswordHeader(),
79
76 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 80 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
77 if (areValidationErrors(req, res)) return 81 if (areValidationErrors(req, res)) return
78 82
@@ -118,13 +122,13 @@ const ensureCanAccessPrivateVideoHLSFiles = [
118] 122]
119 123
120export { 124export {
121 ensureCanAccessVideoPrivateWebTorrentFiles, 125 ensureCanAccessVideoPrivateWebVideoFiles,
122 ensureCanAccessPrivateVideoHLSFiles 126 ensureCanAccessPrivateVideoHLSFiles
123} 127}
124 128
125// --------------------------------------------------------------------------- 129// ---------------------------------------------------------------------------
126 130
127async function isWebTorrentAllowed (req: express.Request, res: express.Response) { 131async function isWebVideoAllowed (req: express.Request, res: express.Response) {
128 const filename = basename(req.path) 132 const filename = basename(req.path)
129 133
130 const file = await VideoFileModel.loadWithVideoByFilename(filename) 134 const file = await VideoFileModel.loadWithVideoByFilename(filename)
@@ -167,11 +171,11 @@ async function isHLSAllowed (req: express.Request, res: express.Response, videoU
167} 171}
168 172
169function extractTokenOrDie (req: express.Request, res: express.Response) { 173function extractTokenOrDie (req: express.Request, res: express.Response) {
170 const token = res.locals.oauth?.token.accessToken || req.query.videoFileToken 174 const token = req.header('x-peertube-video-password') || req.query.videoFileToken || res.locals.oauth?.token.accessToken
171 175
172 if (!token) { 176 if (!token) {
173 return res.fail({ 177 return res.fail({
174 message: 'Bearer token is missing in headers or video file token is missing in URL query parameters', 178 message: 'Video password header, video file token query parameter and bearer token are all missing', //
175 status: HttpStatusCode.FORBIDDEN_403 179 status: HttpStatusCode.FORBIDDEN_403
176 }) 180 })
177 } 181 }
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts
index d225dfe45..0c824c314 100644
--- a/server/middlewares/validators/videos/index.ts
+++ b/server/middlewares/validators/videos/index.ts
@@ -12,6 +12,8 @@ export * from './video-shares'
12export * from './video-source' 12export * from './video-source'
13export * from './video-stats' 13export * from './video-stats'
14export * from './video-studio' 14export * from './video-studio'
15export * from './video-token'
15export * from './video-transcoding' 16export * from './video-transcoding'
16export * from './videos' 17export * from './videos'
17export * from './video-channel-sync' 18export * from './video-channel-sync'
19export * from './video-passwords'
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 72b2febc3..077a58d2e 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -10,7 +10,8 @@ import {
10 checkUserCanManageVideo, 10 checkUserCanManageVideo,
11 doesVideoCaptionExist, 11 doesVideoCaptionExist,
12 doesVideoExist, 12 doesVideoExist,
13 isValidVideoIdParam 13 isValidVideoIdParam,
14 isValidVideoPasswordHeader
14} from '../shared' 15} from '../shared'
15 16
16const addVideoCaptionValidator = [ 17const addVideoCaptionValidator = [
@@ -62,6 +63,8 @@ const deleteVideoCaptionValidator = [
62const listVideoCaptionsValidator = [ 63const listVideoCaptionsValidator = [
63 isValidVideoIdParam('videoId'), 64 isValidVideoIdParam('videoId'),
64 65
66 isValidVideoPasswordHeader(),
67
65 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 68 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
66 if (areValidationErrors(req, res)) return 69 if (areValidationErrors(req, res)) return
67 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 70 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 133feb7bd..70689b02e 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -14,7 +14,8 @@ import {
14 doesVideoCommentExist, 14 doesVideoCommentExist,
15 doesVideoCommentThreadExist, 15 doesVideoCommentThreadExist,
16 doesVideoExist, 16 doesVideoExist,
17 isValidVideoIdParam 17 isValidVideoIdParam,
18 isValidVideoPasswordHeader
18} from '../shared' 19} from '../shared'
19 20
20const listVideoCommentsValidator = [ 21const listVideoCommentsValidator = [
@@ -51,6 +52,7 @@ const listVideoCommentsValidator = [
51 52
52const listVideoCommentThreadsValidator = [ 53const listVideoCommentThreadsValidator = [
53 isValidVideoIdParam('videoId'), 54 isValidVideoIdParam('videoId'),
55 isValidVideoPasswordHeader(),
54 56
55 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 57 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
56 if (areValidationErrors(req, res)) return 58 if (areValidationErrors(req, res)) return
@@ -67,6 +69,7 @@ const listVideoThreadCommentsValidator = [
67 69
68 param('threadId') 70 param('threadId')
69 .custom(isIdValid), 71 .custom(isIdValid),
72 isValidVideoPasswordHeader(),
70 73
71 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 74 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
72 if (areValidationErrors(req, res)) return 75 if (areValidationErrors(req, res)) return
@@ -84,6 +87,7 @@ const addVideoCommentThreadValidator = [
84 87
85 body('text') 88 body('text')
86 .custom(isValidVideoCommentText), 89 .custom(isValidVideoCommentText),
90 isValidVideoPasswordHeader(),
87 91
88 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 92 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
89 if (areValidationErrors(req, res)) return 93 if (areValidationErrors(req, res)) return
@@ -102,6 +106,7 @@ const addVideoCommentReplyValidator = [
102 isValidVideoIdParam('videoId'), 106 isValidVideoIdParam('videoId'),
103 107
104 param('commentId').custom(isIdValid), 108 param('commentId').custom(isIdValid),
109 isValidVideoPasswordHeader(),
105 110
106 body('text').custom(isValidVideoCommentText), 111 body('text').custom(isValidVideoCommentText),
107 112
diff --git a/server/middlewares/validators/videos/video-files.ts b/server/middlewares/validators/videos/video-files.ts
index 92c5b9483..6c0ecda42 100644
--- a/server/middlewares/validators/videos/video-files.ts
+++ b/server/middlewares/validators/videos/video-files.ts
@@ -5,7 +5,7 @@ import { MVideo } from '@server/types/models'
5import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' 6import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
7 7
8const videoFilesDeleteWebTorrentValidator = [ 8const videoFilesDeleteWebVideoValidator = [
9 isValidVideoIdParam('id'), 9 isValidVideoIdParam('id'),
10 10
11 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 11 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -16,17 +16,17 @@ const videoFilesDeleteWebTorrentValidator = [
16 16
17 if (!checkLocalVideo(video, res)) return 17 if (!checkLocalVideo(video, res)) return
18 18
19 if (!video.hasWebTorrentFiles()) { 19 if (!video.hasWebVideoFiles()) {
20 return res.fail({ 20 return res.fail({
21 status: HttpStatusCode.BAD_REQUEST_400, 21 status: HttpStatusCode.BAD_REQUEST_400,
22 message: 'This video does not have WebTorrent files' 22 message: 'This video does not have Web Video files'
23 }) 23 })
24 } 24 }
25 25
26 if (!video.getHLSPlaylist()) { 26 if (!video.getHLSPlaylist()) {
27 return res.fail({ 27 return res.fail({
28 status: HttpStatusCode.BAD_REQUEST_400, 28 status: HttpStatusCode.BAD_REQUEST_400,
29 message: 'Cannot delete WebTorrent files since this video does not have HLS playlist' 29 message: 'Cannot delete Web Video files since this video does not have HLS playlist'
30 }) 30 })
31 } 31 }
32 32
@@ -34,7 +34,7 @@ const videoFilesDeleteWebTorrentValidator = [
34 } 34 }
35] 35]
36 36
37const videoFilesDeleteWebTorrentFileValidator = [ 37const videoFilesDeleteWebVideoFileValidator = [
38 isValidVideoIdParam('id'), 38 isValidVideoIdParam('id'),
39 39
40 param('videoFileId') 40 param('videoFileId')
@@ -52,14 +52,14 @@ const videoFilesDeleteWebTorrentFileValidator = [
52 if (!files.find(f => f.id === +req.params.videoFileId)) { 52 if (!files.find(f => f.id === +req.params.videoFileId)) {
53 return res.fail({ 53 return res.fail({
54 status: HttpStatusCode.NOT_FOUND_404, 54 status: HttpStatusCode.NOT_FOUND_404,
55 message: 'This video does not have this WebTorrent file id' 55 message: 'This video does not have this Web Video file id'
56 }) 56 })
57 } 57 }
58 58
59 if (files.length === 1 && !video.getHLSPlaylist()) { 59 if (files.length === 1 && !video.getHLSPlaylist()) {
60 return res.fail({ 60 return res.fail({
61 status: HttpStatusCode.BAD_REQUEST_400, 61 status: HttpStatusCode.BAD_REQUEST_400,
62 message: 'Cannot delete WebTorrent files since this video does not have HLS playlist' 62 message: 'Cannot delete Web Video files since this video does not have HLS playlist'
63 }) 63 })
64 } 64 }
65 65
@@ -87,10 +87,10 @@ const videoFilesDeleteHLSValidator = [
87 }) 87 })
88 } 88 }
89 89
90 if (!video.hasWebTorrentFiles()) { 90 if (!video.hasWebVideoFiles()) {
91 return res.fail({ 91 return res.fail({
92 status: HttpStatusCode.BAD_REQUEST_400, 92 status: HttpStatusCode.BAD_REQUEST_400,
93 message: 'Cannot delete HLS playlist since this video does not have WebTorrent files' 93 message: 'Cannot delete HLS playlist since this video does not have Web Video files'
94 }) 94 })
95 } 95 }
96 96
@@ -128,10 +128,10 @@ const videoFilesDeleteHLSFileValidator = [
128 } 128 }
129 129
130 // Last file to delete 130 // Last file to delete
131 if (hlsFiles.length === 1 && !video.hasWebTorrentFiles()) { 131 if (hlsFiles.length === 1 && !video.hasWebVideoFiles()) {
132 return res.fail({ 132 return res.fail({
133 status: HttpStatusCode.BAD_REQUEST_400, 133 status: HttpStatusCode.BAD_REQUEST_400,
134 message: 'Cannot delete last HLS playlist file since this video does not have WebTorrent files' 134 message: 'Cannot delete last HLS playlist file since this video does not have Web Video files'
135 }) 135 })
136 } 136 }
137 137
@@ -140,8 +140,8 @@ const videoFilesDeleteHLSFileValidator = [
140] 140]
141 141
142export { 142export {
143 videoFilesDeleteWebTorrentValidator, 143 videoFilesDeleteWebVideoValidator,
144 videoFilesDeleteWebTorrentFileValidator, 144 videoFilesDeleteWebVideoFileValidator,
145 145
146 videoFilesDeleteHLSValidator, 146 videoFilesDeleteHLSValidator,
147 videoFilesDeleteHLSFileValidator 147 videoFilesDeleteHLSFileValidator
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index 72442aeb6..a1cb65b70 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -9,7 +9,11 @@ import { HttpStatusCode, UserRight, VideoImportState } from '@shared/models'
9import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' 9import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model'
10import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' 10import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc'
11import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' 11import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports'
12import { isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' 12import {
13 isValidPasswordProtectedPrivacy,
14 isVideoMagnetUriValid,
15 isVideoNameValid
16} from '../../../helpers/custom-validators/videos'
13import { cleanUpReqFiles } from '../../../helpers/express-utils' 17import { cleanUpReqFiles } from '../../../helpers/express-utils'
14import { logger } from '../../../helpers/logger' 18import { logger } from '../../../helpers/logger'
15import { CONFIG } from '../../../initializers/config' 19import { CONFIG } from '../../../initializers/config'
@@ -38,6 +42,10 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
38 .custom(isVideoNameValid).withMessage( 42 .custom(isVideoNameValid).withMessage(
39 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long` 43 `Should have a video name between ${CONSTRAINTS_FIELDS.VIDEOS.NAME.min} and ${CONSTRAINTS_FIELDS.VIDEOS.NAME.max} characters long`
40 ), 44 ),
45 body('videoPasswords')
46 .optional()
47 .isArray()
48 .withMessage('Video passwords should be an array.'),
41 49
42 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
43 const user = res.locals.oauth.token.User 51 const user = res.locals.oauth.token.User
@@ -45,6 +53,8 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
45 53
46 if (areValidationErrors(req, res)) return cleanUpReqFiles(req) 54 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
47 55
56 if (!isValidPasswordProtectedPrivacy(req, res)) return cleanUpReqFiles(req)
57
48 if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) { 58 if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true && req.body.targetUrl) {
49 cleanUpReqFiles(req) 59 cleanUpReqFiles(req)
50 60
diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts
index 2aff831a8..ec69a3011 100644
--- a/server/middlewares/validators/videos/video-live.ts
+++ b/server/middlewares/validators/videos/video-live.ts
@@ -17,7 +17,7 @@ import {
17 VideoState 17 VideoState
18} from '@shared/models' 18} from '@shared/models'
19import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' 19import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc'
20import { isVideoNameValid, isVideoPrivacyValid } from '../../../helpers/custom-validators/videos' 20import { isValidPasswordProtectedPrivacy, isVideoNameValid, isVideoReplayPrivacyValid } from '../../../helpers/custom-validators/videos'
21import { cleanUpReqFiles } from '../../../helpers/express-utils' 21import { cleanUpReqFiles } from '../../../helpers/express-utils'
22import { logger } from '../../../helpers/logger' 22import { logger } from '../../../helpers/logger'
23import { CONFIG } from '../../../initializers/config' 23import { CONFIG } from '../../../initializers/config'
@@ -69,7 +69,7 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
69 body('replaySettings.privacy') 69 body('replaySettings.privacy')
70 .optional() 70 .optional()
71 .customSanitizer(toIntOrNull) 71 .customSanitizer(toIntOrNull)
72 .custom(isVideoPrivacyValid), 72 .custom(isVideoReplayPrivacyValid),
73 73
74 body('permanentLive') 74 body('permanentLive')
75 .optional() 75 .optional()
@@ -81,9 +81,16 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
81 .customSanitizer(toIntOrNull) 81 .customSanitizer(toIntOrNull)
82 .custom(isLiveLatencyModeValid), 82 .custom(isLiveLatencyModeValid),
83 83
84 body('videoPasswords')
85 .optional()
86 .isArray()
87 .withMessage('Video passwords should be an array.'),
88
84 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 89 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
85 if (areValidationErrors(req, res)) return cleanUpReqFiles(req) 90 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
86 91
92 if (!isValidPasswordProtectedPrivacy(req, res)) return cleanUpReqFiles(req)
93
87 if (CONFIG.LIVE.ENABLED !== true) { 94 if (CONFIG.LIVE.ENABLED !== true) {
88 cleanUpReqFiles(req) 95 cleanUpReqFiles(req)
89 96
@@ -170,7 +177,7 @@ const videoLiveUpdateValidator = [
170 body('replaySettings.privacy') 177 body('replaySettings.privacy')
171 .optional() 178 .optional()
172 .customSanitizer(toIntOrNull) 179 .customSanitizer(toIntOrNull)
173 .custom(isVideoPrivacyValid), 180 .custom(isVideoReplayPrivacyValid),
174 181
175 body('latencyMode') 182 body('latencyMode')
176 .optional() 183 .optional()
diff --git a/server/middlewares/validators/videos/video-passwords.ts b/server/middlewares/validators/videos/video-passwords.ts
new file mode 100644
index 000000000..200e496f6
--- /dev/null
+++ b/server/middlewares/validators/videos/video-passwords.ts
@@ -0,0 +1,77 @@
1import express from 'express'
2import {
3 areValidationErrors,
4 doesVideoExist,
5 isVideoPasswordProtected,
6 isValidVideoIdParam,
7 doesVideoPasswordExist,
8 isVideoPasswordDeletable,
9 checkUserCanManageVideo
10} from '../shared'
11import { body, param } from 'express-validator'
12import { isIdValid } from '@server/helpers/custom-validators/misc'
13import { isValidPasswordProtectedPrivacy } from '@server/helpers/custom-validators/videos'
14import { UserRight } from '@shared/models'
15
16const listVideoPasswordValidator = [
17 isValidVideoIdParam('videoId'),
18
19 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
20 if (areValidationErrors(req, res)) return
21
22 if (!await doesVideoExist(req.params.videoId, res)) return
23 if (!isVideoPasswordProtected(res)) return
24
25 // Check if the user who did the request is able to access video password list
26 const user = res.locals.oauth.token.User
27 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.SEE_ALL_VIDEOS, res)) return
28
29 return next()
30 }
31]
32
33const updateVideoPasswordListValidator = [
34 body('passwords')
35 .optional()
36 .isArray()
37 .withMessage('Video passwords should be an array.'),
38
39 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
40 if (areValidationErrors(req, res)) return
41
42 if (!await doesVideoExist(req.params.videoId, res)) return
43 if (!isValidPasswordProtectedPrivacy(req, res)) return
44
45 // Check if the user who did the request is able to update video passwords
46 const user = res.locals.oauth.token.User
47 if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return
48
49 return next()
50 }
51]
52
53const removeVideoPasswordValidator = [
54 isValidVideoIdParam('videoId'),
55
56 param('passwordId')
57 .custom(isIdValid),
58
59 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
60 if (areValidationErrors(req, res)) return
61
62 if (!await doesVideoExist(req.params.videoId, res)) return
63 if (!isVideoPasswordProtected(res)) return
64 if (!await doesVideoPasswordExist(req.params.passwordId, res)) return
65 if (!await isVideoPasswordDeletable(res)) return
66
67 return next()
68 }
69]
70
71// ---------------------------------------------------------------------------
72
73export {
74 listVideoPasswordValidator,
75 updateVideoPasswordListValidator,
76 removeVideoPasswordValidator
77}
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index c631a16f8..95a5ba63a 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -153,7 +153,7 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
153 } 153 }
154 154
155 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { 155 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
156 await authenticatePromise(req, res) 156 await authenticatePromise({ req, res })
157 157
158 const user = res.locals.oauth ? res.locals.oauth.token.User : null 158 const user = res.locals.oauth ? res.locals.oauth.token.User : null
159 159
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 275634d5b..c837b047b 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -7,13 +7,14 @@ import { isIdValid } from '../../../helpers/custom-validators/misc'
7import { isRatingValid } from '../../../helpers/custom-validators/video-rates' 7import { isRatingValid } from '../../../helpers/custom-validators/video-rates'
8import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' 8import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos'
9import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 9import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
10import { areValidationErrors, checkCanSeeVideo, doesVideoExist, isValidVideoIdParam } from '../shared' 10import { areValidationErrors, checkCanSeeVideo, doesVideoExist, isValidVideoIdParam, isValidVideoPasswordHeader } from '../shared'
11 11
12const videoUpdateRateValidator = [ 12const videoUpdateRateValidator = [
13 isValidVideoIdParam('id'), 13 isValidVideoIdParam('id'),
14 14
15 body('rating') 15 body('rating')
16 .custom(isVideoRatingTypeValid), 16 .custom(isVideoRatingTypeValid),
17 isValidVideoPasswordHeader(),
17 18
18 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 19 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
19 if (areValidationErrors(req, res)) return 20 if (areValidationErrors(req, res)) return
diff --git a/server/middlewares/validators/videos/video-token.ts b/server/middlewares/validators/videos/video-token.ts
new file mode 100644
index 000000000..d4253e21d
--- /dev/null
+++ b/server/middlewares/validators/videos/video-token.ts
@@ -0,0 +1,24 @@
1import express from 'express'
2import { VideoPrivacy } from '../../../../shared/models/videos'
3import { HttpStatusCode } from '@shared/models'
4import { exists } from '@server/helpers/custom-validators/misc'
5
6const videoFileTokenValidator = [
7 (req: express.Request, res: express.Response, next: express.NextFunction) => {
8 const video = res.locals.onlyVideo
9 if (video.privacy !== VideoPrivacy.PASSWORD_PROTECTED && !exists(res.locals.oauth.token.User)) {
10 return res.fail({
11 status: HttpStatusCode.UNAUTHORIZED_401,
12 message: 'Not authenticated'
13 })
14 }
15
16 return next()
17 }
18]
19
20// ---------------------------------------------------------------------------
21
22export {
23 videoFileTokenValidator
24}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 794e1d4f1..b39d13a23 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -2,6 +2,7 @@ import express from 'express'
2import { body, header, param, query, ValidationChain } from 'express-validator' 2import { body, header, param, query, ValidationChain } from 'express-validator'
3import { isTestInstance } from '@server/helpers/core-utils' 3import { isTestInstance } from '@server/helpers/core-utils'
4import { getResumableUploadPath } from '@server/helpers/upload' 4import { getResumableUploadPath } from '@server/helpers/upload'
5import { uploadx } from '@server/lib/uploadx'
5import { Redis } from '@server/lib/redis' 6import { Redis } from '@server/lib/redis'
6import { getServerActor } from '@server/models/application/application' 7import { getServerActor } from '@server/models/application/application'
7import { ExpressPromiseHandler } from '@server/types/express-handler' 8import { ExpressPromiseHandler } from '@server/types/express-handler'
@@ -23,6 +24,7 @@ import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../
23import { 24import {
24 areVideoTagsValid, 25 areVideoTagsValid,
25 isScheduleVideoUpdatePrivacyValid, 26 isScheduleVideoUpdatePrivacyValid,
27 isValidPasswordProtectedPrivacy,
26 isVideoCategoryValid, 28 isVideoCategoryValid,
27 isVideoDescriptionValid, 29 isVideoDescriptionValid,
28 isVideoFileMimeTypeValid, 30 isVideoFileMimeTypeValid,
@@ -39,7 +41,6 @@ import {
39} from '../../../helpers/custom-validators/videos' 41} from '../../../helpers/custom-validators/videos'
40import { cleanUpReqFiles } from '../../../helpers/express-utils' 42import { cleanUpReqFiles } from '../../../helpers/express-utils'
41import { logger } from '../../../helpers/logger' 43import { logger } from '../../../helpers/logger'
42import { deleteFileAndCatch } from '../../../helpers/utils'
43import { getVideoWithAttributes } from '../../../helpers/video' 44import { getVideoWithAttributes } from '../../../helpers/video'
44import { CONFIG } from '../../../initializers/config' 45import { CONFIG } from '../../../initializers/config'
45import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants' 46import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
@@ -55,7 +56,8 @@ import {
55 doesVideoChannelOfAccountExist, 56 doesVideoChannelOfAccountExist,
56 doesVideoExist, 57 doesVideoExist,
57 doesVideoFileOfVideoExist, 58 doesVideoFileOfVideoExist,
58 isValidVideoIdParam 59 isValidVideoIdParam,
60 isValidVideoPasswordHeader
59} from '../shared' 61} from '../shared'
60 62
61const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ 63const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
@@ -70,6 +72,10 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
70 body('channelId') 72 body('channelId')
71 .customSanitizer(toIntOrNull) 73 .customSanitizer(toIntOrNull)
72 .custom(isIdValid), 74 .custom(isIdValid),
75 body('videoPasswords')
76 .optional()
77 .isArray()
78 .withMessage('Video passwords should be an array.'),
73 79
74 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 80 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
75 if (areValidationErrors(req, res)) return cleanUpReqFiles(req) 81 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
@@ -81,6 +87,8 @@ const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
81 return cleanUpReqFiles(req) 87 return cleanUpReqFiles(req)
82 } 88 }
83 89
90 if (!isValidPasswordProtectedPrivacy(req, res)) return cleanUpReqFiles(req)
91
84 try { 92 try {
85 if (!videoFile.duration) await addDurationToVideo(videoFile) 93 if (!videoFile.duration) await addDurationToVideo(videoFile)
86 } catch (err) { 94 } catch (err) {
@@ -107,7 +115,7 @@ const videosAddResumableValidator = [
107 const user = res.locals.oauth.token.User 115 const user = res.locals.oauth.token.User
108 const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body 116 const body: express.CustomUploadXFile<express.UploadXFileMetadata> = req.body
109 const file = { ...body, duration: undefined, path: getResumableUploadPath(body.name), filename: body.metadata.filename } 117 const file = { ...body, duration: undefined, path: getResumableUploadPath(body.name), filename: body.metadata.filename }
110 const cleanup = () => deleteFileAndCatch(file.path) 118 const cleanup = () => uploadx.storage.delete(file).catch(err => logger.error('Cannot delete the file %s', file.name, { err }))
111 119
112 const uploadId = req.query.upload_id 120 const uploadId = req.query.upload_id
113 const sessionExists = await Redis.Instance.doesUploadSessionExist(uploadId) 121 const sessionExists = await Redis.Instance.doesUploadSessionExist(uploadId)
@@ -124,11 +132,15 @@ const videosAddResumableValidator = [
124 }) 132 })
125 } 133 }
126 134
127 if (isTestInstance()) { 135 const videoStillExists = await VideoModel.load(sessionResponse.video.id)
128 res.setHeader('x-resumable-upload-cached', 'true') 136
129 } 137 if (videoStillExists) {
138 if (isTestInstance()) {
139 res.setHeader('x-resumable-upload-cached', 'true')
140 }
130 141
131 return res.json(sessionResponse) 142 return res.json(sessionResponse)
143 }
132 } 144 }
133 145
134 await Redis.Instance.setUploadSession(uploadId) 146 await Redis.Instance.setUploadSession(uploadId)
@@ -174,6 +186,10 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
174 body('channelId') 186 body('channelId')
175 .customSanitizer(toIntOrNull) 187 .customSanitizer(toIntOrNull)
176 .custom(isIdValid), 188 .custom(isIdValid),
189 body('videoPasswords')
190 .optional()
191 .isArray()
192 .withMessage('Video passwords should be an array.'),
177 193
178 header('x-upload-content-length') 194 header('x-upload-content-length')
179 .isNumeric() 195 .isNumeric()
@@ -205,10 +221,14 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
205 const files = { videofile: [ videoFileMetadata ] } 221 const files = { videofile: [ videoFileMetadata ] }
206 if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup() 222 if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup()
207 223
208 // multer required unsetting the Content-Type, now we can set it for node-uploadx 224 if (!isValidPasswordProtectedPrivacy(req, res)) return cleanup()
225
226 // Multer required unsetting the Content-Type, now we can set it for node-uploadx
209 req.headers['content-type'] = 'application/json; charset=utf-8' 227 req.headers['content-type'] = 'application/json; charset=utf-8'
210 // place previewfile in metadata so that uploadx saves it in .META 228
229 // Place thumbnail/previewfile in metadata so that uploadx saves it in .META
211 if (req.files?.['previewfile']) req.body.previewfile = req.files['previewfile'] 230 if (req.files?.['previewfile']) req.body.previewfile = req.files['previewfile']
231 if (req.files?.['thumbnailfile']) req.body.thumbnailfile = req.files['thumbnailfile']
212 232
213 return next() 233 return next()
214 } 234 }
@@ -227,12 +247,18 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
227 .optional() 247 .optional()
228 .customSanitizer(toIntOrNull) 248 .customSanitizer(toIntOrNull)
229 .custom(isIdValid), 249 .custom(isIdValid),
250 body('videoPasswords')
251 .optional()
252 .isArray()
253 .withMessage('Video passwords should be an array.'),
230 254
231 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 255 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
232 if (areValidationErrors(req, res)) return cleanUpReqFiles(req) 256 if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
233 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) 257 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
234 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req) 258 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
235 259
260 if (!isValidPasswordProtectedPrivacy(req, res)) return cleanUpReqFiles(req)
261
236 const video = getVideoWithAttributes(res) 262 const video = getVideoWithAttributes(res)
237 if (video.isLive && video.privacy !== req.body.privacy && video.state !== VideoState.WAITING_FOR_LIVE) { 263 if (video.isLive && video.privacy !== req.body.privacy && video.state !== VideoState.WAITING_FOR_LIVE) {
238 return res.fail({ message: 'Cannot update privacy of a live that has already started' }) 264 return res.fail({ message: 'Cannot update privacy of a live that has already started' })
@@ -281,6 +307,8 @@ const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' |
281 return [ 307 return [
282 isValidVideoIdParam('id'), 308 isValidVideoIdParam('id'),
283 309
310 isValidVideoPasswordHeader(),
311
284 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 312 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
285 if (areValidationErrors(req, res)) return 313 if (areValidationErrors(req, res)) return
286 if (!await doesVideoExist(req.params.id, res, fetchType)) return 314 if (!await doesVideoExist(req.params.id, res, fetchType)) return
@@ -478,10 +506,14 @@ const commonVideosFiltersValidator = [
478 .optional() 506 .optional()
479 .customSanitizer(toBooleanOrNull) 507 .customSanitizer(toBooleanOrNull)
480 .custom(isBooleanValid).withMessage('Should have a valid hasHLSFiles boolean'), 508 .custom(isBooleanValid).withMessage('Should have a valid hasHLSFiles boolean'),
481 query('hasWebtorrentFiles') 509 query('hasWebtorrentFiles') // TODO: remove in v7
482 .optional() 510 .optional()
483 .customSanitizer(toBooleanOrNull) 511 .customSanitizer(toBooleanOrNull)
484 .custom(isBooleanValid).withMessage('Should have a valid hasWebtorrentFiles boolean'), 512 .custom(isBooleanValid).withMessage('Should have a valid hasWebtorrentFiles boolean'),
513 query('hasWebVideoFiles')
514 .optional()
515 .customSanitizer(toBooleanOrNull)
516 .custom(isBooleanValid).withMessage('Should have a valid hasWebVideoFiles boolean'),
485 query('skipCount') 517 query('skipCount')
486 .optional() 518 .optional()
487 .customSanitizer(toBooleanOrNull) 519 .customSanitizer(toBooleanOrNull)