aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares')
-rw-r--r--server/middlewares/auth.ts4
-rw-r--r--server/middlewares/index.ts1
-rw-r--r--server/middlewares/rate-limiter.ts31
-rw-r--r--server/middlewares/validators/feeds.ts6
-rw-r--r--server/middlewares/validators/shared/videos.ts87
-rw-r--r--server/middlewares/validators/sort.ts4
-rw-r--r--server/middlewares/validators/users.ts2
-rw-r--r--server/middlewares/validators/videos/index.ts1
-rw-r--r--server/middlewares/validators/videos/video-captions.ts4
-rw-r--r--server/middlewares/validators/videos/video-comments.ts10
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts4
-rw-r--r--server/middlewares/validators/videos/video-rates.ts4
-rw-r--r--server/middlewares/validators/videos/video-source.ts37
-rw-r--r--server/middlewares/validators/videos/video-view.ts13
-rw-r--r--server/middlewares/validators/videos/videos.ts30
15 files changed, 172 insertions, 66 deletions
diff --git a/server/middlewares/auth.ts b/server/middlewares/auth.ts
index c5424be97..ad3b24ab2 100644
--- a/server/middlewares/auth.ts
+++ b/server/middlewares/auth.ts
@@ -47,7 +47,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
47 .catch(err => logger.error('Cannot get access token.', { err })) 47 .catch(err => logger.error('Cannot get access token.', { err }))
48} 48}
49 49
50function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { 50function authenticatePromise (req: express.Request, res: express.Response, authenticateInQuery = false) {
51 return new Promise<void>(resolve => { 51 return new Promise<void>(resolve => {
52 // Already authenticated? (or tried to) 52 // Already authenticated? (or tried to)
53 if (res.locals.oauth?.token.User) return resolve() 53 if (res.locals.oauth?.token.User) return resolve()
@@ -76,6 +76,6 @@ function optionalAuthenticate (req: express.Request, res: express.Response, next
76export { 76export {
77 authenticate, 77 authenticate,
78 authenticateSocket, 78 authenticateSocket,
79 authenticatePromiseIfNeeded, 79 authenticatePromise,
80 optionalAuthenticate 80 optionalAuthenticate
81} 81}
diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts
index d2ed079b6..b40f864ce 100644
--- a/server/middlewares/index.ts
+++ b/server/middlewares/index.ts
@@ -4,6 +4,7 @@ export * from './activitypub'
4export * from './async' 4export * from './async'
5export * from './auth' 5export * from './auth'
6export * from './pagination' 6export * from './pagination'
7export * from './rate-limiter'
7export * from './robots' 8export * from './robots'
8export * from './servers' 9export * from './servers'
9export * from './sort' 10export * from './sort'
diff --git a/server/middlewares/rate-limiter.ts b/server/middlewares/rate-limiter.ts
new file mode 100644
index 000000000..bc9513969
--- /dev/null
+++ b/server/middlewares/rate-limiter.ts
@@ -0,0 +1,31 @@
1import { UserRole } from '@shared/models'
2import RateLimit from 'express-rate-limit'
3import { optionalAuthenticate } from './auth'
4
5const whitelistRoles = new Set([ UserRole.ADMINISTRATOR, UserRole.MODERATOR ])
6
7function buildRateLimiter (options: {
8 windowMs: number
9 max: number
10 skipFailedRequests?: boolean
11}) {
12 return RateLimit({
13 windowMs: options.windowMs,
14 max: options.max,
15 skipFailedRequests: options.skipFailedRequests,
16
17 handler: (req, res, next, options) => {
18 return optionalAuthenticate(req, res, () => {
19 if (res.locals.authenticated === true && whitelistRoles.has(res.locals.oauth.token.User.role)) {
20 return next()
21 }
22
23 return res.status(options.statusCode).send(options.message)
24 })
25 }
26 })
27}
28
29export {
30 buildRateLimiter
31}
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index f8ebaf6ed..04b4e00c9 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -6,6 +6,7 @@ import { exists, isIdOrUUIDValid, isIdValid, toCompleteUUID } from '../../helper
6import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
7import { 7import {
8 areValidationErrors, 8 areValidationErrors,
9 checkCanSeeVideo,
9 doesAccountIdExist, 10 doesAccountIdExist,
10 doesAccountNameWithHostExist, 11 doesAccountNameWithHostExist,
11 doesUserFeedTokenCorrespond, 12 doesUserFeedTokenCorrespond,
@@ -112,7 +113,10 @@ const videoCommentsFeedsValidator = [
112 return res.fail({ message: 'videoId cannot be mixed with a channel filter' }) 113 return res.fail({ message: 'videoId cannot be mixed with a channel filter' })
113 } 114 }
114 115
115 if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return 116 if (req.query.videoId) {
117 if (!await doesVideoExist(req.query.videoId, res)) return
118 if (!await checkCanSeeVideo({ req, res, paramId: req.query.videoId, video: res.locals.videoAll })) return
119 }
116 120
117 return next() 121 return next()
118 } 122 }
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts
index 8807435f6..2c2ae3811 100644
--- a/server/middlewares/validators/shared/videos.ts
+++ b/server/middlewares/validators/shared/videos.ts
@@ -1,7 +1,8 @@
1import { Request, Response } from 'express' 1import { Request, Response } from 'express'
2import { isUUIDValid } from '@server/helpers/custom-validators/misc'
2import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' 3import { loadVideo, VideoLoadType } from '@server/lib/model-loaders'
3import { isAbleToUploadVideo } from '@server/lib/user' 4import { isAbleToUploadVideo } from '@server/lib/user'
4import { authenticatePromiseIfNeeded } from '@server/middlewares/auth' 5import { authenticatePromise } from '@server/middlewares/auth'
5import { VideoModel } from '@server/models/video/video' 6import { VideoModel } from '@server/models/video/video'
6import { VideoChannelModel } from '@server/models/video/video-channel' 7import { VideoChannelModel } from '@server/models/video/video-channel'
7import { VideoFileModel } from '@server/models/video/video-file' 8import { VideoFileModel } from '@server/models/video/video-file'
@@ -18,18 +19,19 @@ import {
18 MVideoThumbnail, 19 MVideoThumbnail,
19 MVideoWithRights 20 MVideoWithRights
20} from '@server/types/models' 21} from '@server/types/models'
21import { HttpStatusCode, ServerErrorCode, UserRight } from '@shared/models' 22import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models'
22 23
23async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { 24async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') {
24 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined 25 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
25 26
26 const video = await loadVideo(id, fetchType, userId) 27 const video = await loadVideo(id, fetchType, userId)
27 28
28 if (video === null) { 29 if (!video) {
29 res.fail({ 30 res.fail({
30 status: HttpStatusCode.NOT_FOUND_404, 31 status: HttpStatusCode.NOT_FOUND_404,
31 message: 'Video not found' 32 message: 'Video not found'
32 }) 33 })
34
33 return false 35 return false
34 } 36 }
35 37
@@ -58,6 +60,8 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
58 return true 60 return true
59} 61}
60 62
63// ---------------------------------------------------------------------------
64
61async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { 65async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) {
62 if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) { 66 if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) {
63 res.fail({ 67 res.fail({
@@ -70,6 +74,8 @@ async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | st
70 return true 74 return true
71} 75}
72 76
77// ---------------------------------------------------------------------------
78
73async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { 79async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) {
74 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) 80 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
75 81
@@ -95,32 +101,78 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
95 return true 101 return true
96} 102}
97 103
98async function checkCanSeeVideoIfPrivate (req: Request, res: Response, video: MVideo, authenticateInQuery = false) { 104// ---------------------------------------------------------------------------
99 if (!video.requiresAuth()) return true
100 105
101 const videoWithRights = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id) 106async function checkCanSeeVideo (options: {
107 req: Request
108 res: Response
109 paramId: string
110 video: MVideo
111 authenticateInQuery?: boolean // default false
112}) {
113 const { req, res, video, paramId, authenticateInQuery = false } = options
114
115 if (video.requiresAuth()) {
116 return checkCanSeeAuthVideo(req, res, video, authenticateInQuery)
117 }
102 118
103 return checkCanSeePrivateVideo(req, res, videoWithRights, authenticateInQuery) 119 if (video.privacy === VideoPrivacy.UNLISTED) {
104} 120 if (isUUIDValid(paramId)) return true
105 121
106async function checkCanSeePrivateVideo (req: Request, res: Response, video: MVideoWithRights, authenticateInQuery = false) { 122 return checkCanSeeAuthVideo(req, res, video, authenticateInQuery)
107 await authenticatePromiseIfNeeded(req, res, authenticateInQuery) 123 }
124
125 if (video.privacy === VideoPrivacy.PUBLIC) return true
108 126
109 const user = res.locals.oauth ? res.locals.oauth.token.User : null 127 throw new Error('Fatal error when checking video right ' + video.url)
128}
110 129
111 // Only the owner or a user that have blocklist rights can see the video 130async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoId | MVideoWithRights, authenticateInQuery = false) {
112 if (!user || !user.canGetVideo(video)) { 131 const fail = () => {
113 res.fail({ 132 res.fail({
114 status: HttpStatusCode.FORBIDDEN_403, 133 status: HttpStatusCode.FORBIDDEN_403,
115 message: 'Cannot fetch information of private/internal/blocklisted video' 134 message: 'Cannot fetch information of private/internal/blocked video'
116 }) 135 })
117 136
118 return false 137 return false
119 } 138 }
120 139
121 return true 140 await authenticatePromise(req, res, authenticateInQuery)
141
142 const user = res.locals.oauth?.token.User
143 if (!user) return fail()
144
145 const videoWithRights = (video as MVideoWithRights).VideoChannel?.Account?.userId
146 ? video as MVideoWithRights
147 : await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)
148
149 const privacy = videoWithRights.privacy
150
151 if (privacy === VideoPrivacy.INTERNAL) {
152 // We know we have a user
153 return true
154 }
155
156 const isOwnedByUser = videoWithRights.VideoChannel.Account.userId === user.id
157
158 if (videoWithRights.isBlacklisted()) {
159 if (isOwnedByUser || user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) return true
160
161 return fail()
162 }
163
164 if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) {
165 if (isOwnedByUser || user.hasRight(UserRight.SEE_ALL_VIDEOS)) return true
166
167 return fail()
168 }
169
170 // Should not happen
171 return fail()
122} 172}
123 173
174// ---------------------------------------------------------------------------
175
124function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { 176function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
125 // Retrieve the user who did the request 177 // Retrieve the user who did the request
126 if (onlyOwned && video.isOwned() === false) { 178 if (onlyOwned && video.isOwned() === false) {
@@ -146,6 +198,8 @@ function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right:
146 return true 198 return true
147} 199}
148 200
201// ---------------------------------------------------------------------------
202
149async function checkUserQuota (user: MUserId, videoFileSize: number, res: Response) { 203async function checkUserQuota (user: MUserId, videoFileSize: number, res: Response) {
150 if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { 204 if (await isAbleToUploadVideo(user.id, videoFileSize) === false) {
151 res.fail({ 205 res.fail({
@@ -167,7 +221,6 @@ export {
167 doesVideoFileOfVideoExist, 221 doesVideoFileOfVideoExist,
168 222
169 checkUserCanManageVideo, 223 checkUserCanManageVideo,
170 checkCanSeeVideoIfPrivate, 224 checkCanSeeVideo,
171 checkCanSeePrivateVideo,
172 checkUserQuota 225 checkUserQuota
173} 226}
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index 3ba668460..c9978e3b4 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -28,7 +28,7 @@ function createSortableColumns (sortableColumns: string[]) {
28 return sortableColumns.concat(sortableColumnDesc) 28 return sortableColumns.concat(sortableColumnDesc)
29} 29}
30 30
31const usersSortValidator = checkSortFactory(SORTABLE_COLUMNS.USERS) 31const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS)
32const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) 32const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS)
33const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) 33const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ])
34const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) 34const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES)
@@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH
59// --------------------------------------------------------------------------- 59// ---------------------------------------------------------------------------
60 60
61export { 61export {
62 usersSortValidator, 62 adminUsersSortValidator,
63 abusesSortValidator, 63 abusesSortValidator,
64 videoChannelsSortValidator, 64 videoChannelsSortValidator,
65 videoImportsSortValidator, 65 videoImportsSortValidator,
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index bc6007c6d..6d306121e 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -486,7 +486,7 @@ const ensureAuthUserOwnsAccountValidator = [
486 if (res.locals.account.id !== user.Account.id) { 486 if (res.locals.account.id !== user.Account.id) {
487 return res.fail({ 487 return res.fail({
488 status: HttpStatusCode.FORBIDDEN_403, 488 status: HttpStatusCode.FORBIDDEN_403,
489 message: 'Only owner of this account can access this ressource.' 489 message: 'Only owner of this account can access this resource.'
490 }) 490 })
491 } 491 }
492 492
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts
index bd2590bc5..1dd7b5d2e 100644
--- a/server/middlewares/validators/videos/index.ts
+++ b/server/middlewares/validators/videos/index.ts
@@ -9,6 +9,7 @@ export * from './video-ownership-changes'
9export * from './video-view' 9export * from './video-view'
10export * from './video-rates' 10export * from './video-rates'
11export * from './video-shares' 11export * from './video-shares'
12export * from './video-source'
12export * from './video-stats' 13export * from './video-stats'
13export * from './video-studio' 14export * from './video-studio'
14export * from './video-transcoding' 15export * from './video-transcoding'
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 441c6b4be..dfb8fefc5 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -7,7 +7,7 @@ import { logger } from '../../../helpers/logger'
7import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' 7import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants'
8import { 8import {
9 areValidationErrors, 9 areValidationErrors,
10 checkCanSeeVideoIfPrivate, 10 checkCanSeeVideo,
11 checkUserCanManageVideo, 11 checkUserCanManageVideo,
12 doesVideoCaptionExist, 12 doesVideoCaptionExist,
13 doesVideoExist, 13 doesVideoExist,
@@ -74,7 +74,7 @@ const listVideoCaptionsValidator = [
74 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 74 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
75 75
76 const video = res.locals.onlyVideo 76 const video = res.locals.onlyVideo
77 if (!await checkCanSeeVideoIfPrivate(req, res, video)) return 77 if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.videoId })) return
78 78
79 return next() 79 return next()
80 } 80 }
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 698afdbd1..b22a4e3b7 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -10,7 +10,7 @@ import { Hooks } from '../../../lib/plugins/hooks'
10import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video' 10import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
11import { 11import {
12 areValidationErrors, 12 areValidationErrors,
13 checkCanSeeVideoIfPrivate, 13 checkCanSeeVideo,
14 doesVideoCommentExist, 14 doesVideoCommentExist,
15 doesVideoCommentThreadExist, 15 doesVideoCommentThreadExist,
16 doesVideoExist, 16 doesVideoExist,
@@ -54,7 +54,7 @@ const listVideoCommentThreadsValidator = [
54 if (areValidationErrors(req, res)) return 54 if (areValidationErrors(req, res)) return
55 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 55 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
56 56
57 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) return 57 if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
58 58
59 return next() 59 return next()
60 } 60 }
@@ -73,7 +73,7 @@ const listVideoThreadCommentsValidator = [
73 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 73 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
74 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return 74 if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
75 75
76 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.onlyVideo)) return 76 if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
77 77
78 return next() 78 return next()
79 } 79 }
@@ -91,7 +91,7 @@ const addVideoCommentThreadValidator = [
91 if (areValidationErrors(req, res)) return 91 if (areValidationErrors(req, res)) return
92 if (!await doesVideoExist(req.params.videoId, res)) return 92 if (!await doesVideoExist(req.params.videoId, res)) return
93 93
94 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return 94 if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.videoAll })) return
95 95
96 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return 96 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
97 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return 97 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
@@ -113,7 +113,7 @@ const addVideoCommentReplyValidator = [
113 if (areValidationErrors(req, res)) return 113 if (areValidationErrors(req, res)) return
114 if (!await doesVideoExist(req.params.videoId, res)) return 114 if (!await doesVideoExist(req.params.videoId, res)) return
115 115
116 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return 116 if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.videoAll })) return
117 117
118 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return 118 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
119 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return 119 if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 241b9ed7b..d514ae0ad 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -33,7 +33,7 @@ import { logger } from '../../../helpers/logger'
33import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' 33import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
34import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' 34import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
35import { MVideoPlaylist } from '../../../types/models/video/video-playlist' 35import { MVideoPlaylist } from '../../../types/models/video/video-playlist'
36import { authenticatePromiseIfNeeded } from '../../auth' 36import { authenticatePromise } from '../../auth'
37import { 37import {
38 areValidationErrors, 38 areValidationErrors,
39 doesVideoChannelIdExist, 39 doesVideoChannelIdExist,
@@ -161,7 +161,7 @@ const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
161 } 161 }
162 162
163 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { 163 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
164 await authenticatePromiseIfNeeded(req, res) 164 await authenticatePromise(req, res)
165 165
166 const user = res.locals.oauth ? res.locals.oauth.token.User : null 166 const user = res.locals.oauth ? res.locals.oauth.token.User : null
167 167
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 1a9736034..8b8eeedb6 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -8,7 +8,7 @@ import { isRatingValid } from '../../../helpers/custom-validators/video-rates'
8import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' 8import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
10import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 10import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
11import { areValidationErrors, checkCanSeeVideoIfPrivate, doesVideoExist, isValidVideoIdParam } from '../shared' 11import { areValidationErrors, checkCanSeeVideo, doesVideoExist, isValidVideoIdParam } from '../shared'
12 12
13const videoUpdateRateValidator = [ 13const videoUpdateRateValidator = [
14 isValidVideoIdParam('id'), 14 isValidVideoIdParam('id'),
@@ -21,7 +21,7 @@ const videoUpdateRateValidator = [
21 if (areValidationErrors(req, res)) return 21 if (areValidationErrors(req, res)) return
22 if (!await doesVideoExist(req.params.id, res)) return 22 if (!await doesVideoExist(req.params.id, res)) return
23 23
24 if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) return 24 if (!await checkCanSeeVideo({ req, res, paramId: req.params.id, video: res.locals.videoAll })) return
25 25
26 return next() 26 return next()
27 } 27 }
diff --git a/server/middlewares/validators/videos/video-source.ts b/server/middlewares/validators/videos/video-source.ts
new file mode 100644
index 000000000..31a2f16b3
--- /dev/null
+++ b/server/middlewares/validators/videos/video-source.ts
@@ -0,0 +1,37 @@
1import express from 'express'
2import { getVideoWithAttributes } from '@server/helpers/video'
3import { VideoSourceModel } from '@server/models/video/video-source'
4import { MVideoFullLight } from '@server/types/models'
5import { HttpStatusCode, UserRight } from '@shared/models'
6import { logger } from '../../../helpers/logger'
7import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVideoIdParam } from '../shared'
8
9const videoSourceGetValidator = [
10 isValidVideoIdParam('id'),
11
12 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 logger.debug('Checking videoSourceGet parameters', { parameters: req.params })
14
15 if (areValidationErrors(req, res)) return
16 if (!await doesVideoExist(req.params.id, res, 'for-api')) return
17
18 const video = getVideoWithAttributes(res) as MVideoFullLight
19
20 res.locals.videoSource = await VideoSourceModel.loadByVideoId(video.id)
21 if (!res.locals.videoSource) {
22 return res.fail({
23 status: HttpStatusCode.NOT_FOUND_404,
24 message: 'Video source not found'
25 })
26 }
27
28 const user = res.locals.oauth.token.User
29 if (!checkUserCanManageVideo(user, video, UserRight.UPDATE_ANY_VIDEO, res)) return
30
31 return next()
32 }
33]
34
35export {
36 videoSourceGetValidator
37}
diff --git a/server/middlewares/validators/videos/video-view.ts b/server/middlewares/validators/videos/video-view.ts
index 7a4994e8a..2edcd140f 100644
--- a/server/middlewares/validators/videos/video-view.ts
+++ b/server/middlewares/validators/videos/video-view.ts
@@ -6,6 +6,7 @@ import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' 6import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc'
7import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
8import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' 8import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
9import { getCachedVideoDuration } from '@server/lib/video'
9 10
10const getVideoLocalViewerValidator = [ 11const getVideoLocalViewerValidator = [
11 param('localViewerId') 12 param('localViewerId')
@@ -42,20 +43,18 @@ const videoViewValidator = [
42 logger.debug('Checking videoView parameters', { parameters: req.body }) 43 logger.debug('Checking videoView parameters', { parameters: req.body })
43 44
44 if (areValidationErrors(req, res)) return 45 if (areValidationErrors(req, res)) return
45 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return 46 if (!await doesVideoExist(req.params.videoId, res, 'only-immutable-attributes')) return
46 47
47 const video = res.locals.onlyVideo 48 const video = res.locals.onlyImmutableVideo
48 const videoDuration = video.isLive 49 const { duration } = await getCachedVideoDuration(video.id)
49 ? undefined
50 : video.duration
51 50
52 if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2 51 if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2
53 req.body.currentTime = Math.min(videoDuration ?? 0, 30) 52 req.body.currentTime = Math.min(duration ?? 0, 30)
54 } 53 }
55 54
56 const currentTime: number = req.body.currentTime 55 const currentTime: number = req.body.currentTime
57 56
58 if (!isVideoTimeValid(currentTime, videoDuration)) { 57 if (!isVideoTimeValid(currentTime, duration)) {
59 return res.fail({ 58 return res.fail({
60 status: HttpStatusCode.BAD_REQUEST_400, 59 status: HttpStatusCode.BAD_REQUEST_400,
61 message: 'Current time is invalid' 60 message: 'Current time is invalid'
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 0b6b8bfe5..c6d31f8f0 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -7,14 +7,13 @@ import { getServerActor } from '@server/models/application/application'
7import { ExpressPromiseHandler } from '@server/types/express-handler' 7import { ExpressPromiseHandler } from '@server/types/express-handler'
8import { MUserAccountId, MVideoFullLight } from '@server/types/models' 8import { MUserAccountId, MVideoFullLight } from '@server/types/models'
9import { getAllPrivacies } from '@shared/core-utils' 9import { getAllPrivacies } from '@shared/core-utils'
10import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude, VideoPrivacy } from '@shared/models' 10import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude } from '@shared/models'
11import { 11import {
12 exists, 12 exists,
13 isBooleanValid, 13 isBooleanValid,
14 isDateValid, 14 isDateValid,
15 isFileValid, 15 isFileValid,
16 isIdValid, 16 isIdValid,
17 isUUIDValid,
18 toArray, 17 toArray,
19 toBooleanOrNull, 18 toBooleanOrNull,
20 toIntOrNull, 19 toIntOrNull,
@@ -50,7 +49,7 @@ import { Hooks } from '../../../lib/plugins/hooks'
50import { VideoModel } from '../../../models/video/video' 49import { VideoModel } from '../../../models/video/video'
51import { 50import {
52 areValidationErrors, 51 areValidationErrors,
53 checkCanSeePrivateVideo, 52 checkCanSeeVideo,
54 checkUserCanManageVideo, 53 checkUserCanManageVideo,
55 checkUserQuota, 54 checkUserQuota,
56 doesVideoChannelOfAccountExist, 55 doesVideoChannelOfAccountExist,
@@ -152,7 +151,7 @@ const videosAddResumableValidator = [
152 151
153 if (!await isVideoAccepted(req, res, file)) return cleanup() 152 if (!await isVideoAccepted(req, res, file)) return cleanup()
154 153
155 res.locals.videoFileResumable = file 154 res.locals.videoFileResumable = { ...file, originalname: file.filename }
156 155
157 return next() 156 return next()
158 } 157 }
@@ -297,28 +296,9 @@ const videosCustomGetValidator = (
297 296
298 const video = getVideoWithAttributes(res) as MVideoFullLight 297 const video = getVideoWithAttributes(res) as MVideoFullLight
299 298
300 // Video private or blacklisted 299 if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.id, authenticateInQuery })) return
301 if (video.requiresAuth()) {
302 if (await checkCanSeePrivateVideo(req, res, video, authenticateInQuery)) {
303 return next()
304 }
305 300
306 return 301 return next()
307 }
308
309 // Video is public, anyone can access it
310 if (video.privacy === VideoPrivacy.PUBLIC) return next()
311
312 // Video is unlisted, check we used the uuid to fetch it
313 if (video.privacy === VideoPrivacy.UNLISTED) {
314 if (isUUIDValid(req.params.id)) return next()
315
316 // Don't leak this unlisted video
317 return res.fail({
318 status: HttpStatusCode.NOT_FOUND_404,
319 message: 'Video not found'
320 })
321 }
322 } 302 }
323 ] 303 ]
324} 304}