diff options
Diffstat (limited to 'server/middlewares')
-rw-r--r-- | server/middlewares/auth.ts | 4 | ||||
-rw-r--r-- | server/middlewares/index.ts | 1 | ||||
-rw-r--r-- | server/middlewares/rate-limiter.ts | 31 | ||||
-rw-r--r-- | server/middlewares/validators/feeds.ts | 6 | ||||
-rw-r--r-- | server/middlewares/validators/shared/videos.ts | 87 | ||||
-rw-r--r-- | server/middlewares/validators/sort.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/index.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-captions.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-comments.ts | 10 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-playlists.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-rates.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-source.ts | 37 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-view.ts | 13 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 30 |
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 | ||
50 | function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { | 50 | function 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 | |||
76 | export { | 76 | export { |
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' | |||
4 | export * from './async' | 4 | export * from './async' |
5 | export * from './auth' | 5 | export * from './auth' |
6 | export * from './pagination' | 6 | export * from './pagination' |
7 | export * from './rate-limiter' | ||
7 | export * from './robots' | 8 | export * from './robots' |
8 | export * from './servers' | 9 | export * from './servers' |
9 | export * from './sort' | 10 | export * 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 @@ | |||
1 | import { UserRole } from '@shared/models' | ||
2 | import RateLimit from 'express-rate-limit' | ||
3 | import { optionalAuthenticate } from './auth' | ||
4 | |||
5 | const whitelistRoles = new Set([ UserRole.ADMINISTRATOR, UserRole.MODERATOR ]) | ||
6 | |||
7 | function 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 | |||
29 | export { | ||
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 | |||
6 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
7 | import { | 7 | import { |
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 @@ | |||
1 | import { Request, Response } from 'express' | 1 | import { Request, Response } from 'express' |
2 | import { isUUIDValid } from '@server/helpers/custom-validators/misc' | ||
2 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' | 3 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' |
3 | import { isAbleToUploadVideo } from '@server/lib/user' | 4 | import { isAbleToUploadVideo } from '@server/lib/user' |
4 | import { authenticatePromiseIfNeeded } from '@server/middlewares/auth' | 5 | import { authenticatePromise } from '@server/middlewares/auth' |
5 | import { VideoModel } from '@server/models/video/video' | 6 | import { VideoModel } from '@server/models/video/video' |
6 | import { VideoChannelModel } from '@server/models/video/video-channel' | 7 | import { VideoChannelModel } from '@server/models/video/video-channel' |
7 | import { VideoFileModel } from '@server/models/video/video-file' | 8 | import { 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' |
21 | import { HttpStatusCode, ServerErrorCode, UserRight } from '@shared/models' | 22 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models' |
22 | 23 | ||
23 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { | 24 | async 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 | |||
61 | async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { | 65 | async 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 | |||
73 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { | 79 | async 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 | ||
98 | async 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) | 106 | async 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 | ||
106 | async 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 | 130 | async 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 | |||
124 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { | 176 | function 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 | |||
149 | async function checkUserQuota (user: MUserId, videoFileSize: number, res: Response) { | 203 | async 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 | ||
31 | const usersSortValidator = checkSortFactory(SORTABLE_COLUMNS.USERS) | 31 | const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS) |
32 | const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) | 32 | const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) |
33 | const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) | 33 | const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) |
34 | const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) | 34 | const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) |
@@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH | |||
59 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
60 | 60 | ||
61 | export { | 61 | export { |
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' | |||
9 | export * from './video-view' | 9 | export * from './video-view' |
10 | export * from './video-rates' | 10 | export * from './video-rates' |
11 | export * from './video-shares' | 11 | export * from './video-shares' |
12 | export * from './video-source' | ||
12 | export * from './video-stats' | 13 | export * from './video-stats' |
13 | export * from './video-studio' | 14 | export * from './video-studio' |
14 | export * from './video-transcoding' | 15 | export * 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' | |||
7 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' |
8 | import { | 8 | import { |
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' | |||
10 | import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video' | 10 | import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video' |
11 | import { | 11 | import { |
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' | |||
33 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 33 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
34 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' | 34 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' |
35 | import { MVideoPlaylist } from '../../../types/models/video/video-playlist' | 35 | import { MVideoPlaylist } from '../../../types/models/video/video-playlist' |
36 | import { authenticatePromiseIfNeeded } from '../../auth' | 36 | import { authenticatePromise } from '../../auth' |
37 | import { | 37 | import { |
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' | |||
8 | import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' | 8 | import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' |
9 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
10 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 10 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
11 | import { areValidationErrors, checkCanSeeVideoIfPrivate, doesVideoExist, isValidVideoIdParam } from '../shared' | 11 | import { areValidationErrors, checkCanSeeVideo, doesVideoExist, isValidVideoIdParam } from '../shared' |
12 | 12 | ||
13 | const videoUpdateRateValidator = [ | 13 | const 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 @@ | |||
1 | import express from 'express' | ||
2 | import { getVideoWithAttributes } from '@server/helpers/video' | ||
3 | import { VideoSourceModel } from '@server/models/video/video-source' | ||
4 | import { MVideoFullLight } from '@server/types/models' | ||
5 | import { HttpStatusCode, UserRight } from '@shared/models' | ||
6 | import { logger } from '../../../helpers/logger' | ||
7 | import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVideoIdParam } from '../shared' | ||
8 | |||
9 | const 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 | |||
35 | export { | ||
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' | |||
6 | import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | 6 | import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' | 8 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' |
9 | import { getCachedVideoDuration } from '@server/lib/video' | ||
9 | 10 | ||
10 | const getVideoLocalViewerValidator = [ | 11 | const 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' | |||
7 | import { ExpressPromiseHandler } from '@server/types/express-handler' | 7 | import { ExpressPromiseHandler } from '@server/types/express-handler' |
8 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' | 8 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' |
9 | import { getAllPrivacies } from '@shared/core-utils' | 9 | import { getAllPrivacies } from '@shared/core-utils' |
10 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude, VideoPrivacy } from '@shared/models' | 10 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude } from '@shared/models' |
11 | import { | 11 | import { |
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' | |||
50 | import { VideoModel } from '../../../models/video/video' | 49 | import { VideoModel } from '../../../models/video/video' |
51 | import { | 50 | import { |
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 | } |