diff options
author | Wicklow <123956049+wickloww@users.noreply.github.com> | 2023-06-29 07:48:55 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-29 09:48:55 +0200 |
commit | 40346ead2b0b7afa475aef057d3673b6c7574b7a (patch) | |
tree | 24ffdc23c3a9d987334842e0d400b5bd44500cf7 /server/middlewares | |
parent | ae22c59f14d0d553f60b281948b6c232c2aca178 (diff) | |
download | PeerTube-40346ead2b0b7afa475aef057d3673b6c7574b7a.tar.gz PeerTube-40346ead2b0b7afa475aef057d3673b6c7574b7a.tar.zst PeerTube-40346ead2b0b7afa475aef057d3673b6c7574b7a.zip |
Feature/password protected videos (#5836)
* Add server endpoints
* Refactoring test suites
* Update server and add openapi documentation
* fix compliation and tests
* upload/import password protected video on client
* add server error code
* Add video password to update resolver
* add custom message when sharing pw protected video
* improve confirm component
* Add new alert in component
* Add ability to watch protected video on client
* Cannot have password protected replay privacy
* Add migration
* Add tests
* update after review
* Update check params tests
* Add live videos test
* Add more filter test
* Update static file privacy test
* Update object storage tests
* Add test on feeds
* Add missing word
* Fix tests
* Fix tests on live videos
* add embed support on password protected videos
* fix style
* Correcting data leaks
* Unable to add password protected privacy on replay
* Updated code based on review comments
* fix validator and command
* Updated code based on review comments
Diffstat (limited to 'server/middlewares')
-rw-r--r-- | server/middlewares/auth.ts | 15 | ||||
-rw-r--r-- | server/middlewares/validators/shared/index.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/shared/video-passwords.ts | 80 | ||||
-rw-r--r-- | server/middlewares/validators/shared/videos.ts | 85 | ||||
-rw-r--r-- | server/middlewares/validators/sort.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/static.ts | 10 | ||||
-rw-r--r-- | server/middlewares/validators/videos/index.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-captions.ts | 5 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-comments.ts | 7 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-imports.ts | 12 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-live.ts | 13 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-passwords.ts | 77 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-playlists.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-rates.ts | 3 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-token.ts | 24 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 24 |
16 files changed, 334 insertions, 27 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' | |||
5 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' | 5 | import { HttpStatusCode } from '../../shared/models/http/http-error-codes' |
6 | import { logger } from '../helpers/logger' | 6 | import { logger } from '../helpers/logger' |
7 | import { handleOAuthAuthenticate } from '../lib/auth/oauth' | 7 | import { handleOAuthAuthenticate } from '../lib/auth/oauth' |
8 | import { ServerErrorCode } from '@shared/models' | ||
8 | 9 | ||
9 | function authenticate (req: express.Request, res: express.Response, next: express.NextFunction) { | 10 | function 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 | ||
51 | function authenticatePromise (req: express.Request, res: express.Response) { | 52 | function 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/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' | |||
10 | export * from './video-imports' | 10 | export * from './video-imports' |
11 | export * from './video-ownerships' | 11 | export * from './video-ownerships' |
12 | export * from './video-playlists' | 12 | export * from './video-playlists' |
13 | export * from './video-passwords' | ||
13 | export * from './videos' | 14 | export * 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 @@ | |||
1 | import express from 'express' | ||
2 | import { HttpStatusCode, UserRight, VideoPrivacy } from '@shared/models' | ||
3 | import { forceNumber } from '@shared/core-utils' | ||
4 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
5 | import { header } from 'express-validator' | ||
6 | import { getVideoWithAttributes } from '@server/helpers/video' | ||
7 | |||
8 | function isValidVideoPasswordHeader () { | ||
9 | return header('x-peertube-video-password') | ||
10 | .optional() | ||
11 | .isString() | ||
12 | } | ||
13 | |||
14 | function 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 | |||
27 | async 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 | |||
45 | async 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 | |||
75 | export { | ||
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' |
22 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models' | 22 | import { HttpStatusCode, ServerErrorCode, UserRight, VideoPrivacy } from '@shared/models' |
23 | import { VideoPasswordModel } from '@server/models/video/video-password' | ||
24 | import { exists } from '@server/helpers/custom-validators/misc' | ||
23 | 25 | ||
24 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { | 26 | async 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 | ||
125 | async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoId | MVideoWithRights) { | 131 | async 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 | ||
177 | async 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 | |||
218 | function 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 | |||
224 | async 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 | ||
171 | async function checkCanAccessVideoStaticFiles (options: { | 232 | async 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) | |||
28 | export const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) | 28 | export const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) |
29 | export const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) | 29 | export const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) |
30 | export const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS) | 30 | export const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS) |
31 | export const videoPasswordsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PASSWORDS) | ||
31 | 32 | ||
32 | export const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) | 33 | export const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) |
33 | export const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) | 34 | export const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) |
diff --git a/server/middlewares/validators/static.ts b/server/middlewares/validators/static.ts index 9c2d890ba..36a94080c 100644 --- a/server/middlewares/validators/static.ts +++ b/server/middlewares/validators/static.ts | |||
@@ -9,7 +9,7 @@ import { VideoModel } from '@server/models/video/video' | |||
9 | import { VideoFileModel } from '@server/models/video/video-file' | 9 | import { VideoFileModel } from '@server/models/video/video-file' |
10 | import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models' | 10 | import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models' |
11 | import { HttpStatusCode } from '@shared/models' | 11 | import { HttpStatusCode } from '@shared/models' |
12 | import { areValidationErrors, checkCanAccessVideoStaticFiles } from './shared' | 12 | import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared' |
13 | 13 | ||
14 | type LRUValue = { | 14 | type LRUValue = { |
15 | allowed: boolean | 15 | allowed: boolean |
@@ -25,6 +25,8 @@ const staticFileTokenBypass = new LRUCache<string, LRUValue>({ | |||
25 | const ensureCanAccessVideoPrivateWebTorrentFiles = [ | 25 | const ensureCanAccessVideoPrivateWebTorrentFiles = [ |
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 | ||
@@ -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 | ||
@@ -167,11 +171,11 @@ async function isHLSAllowed (req: express.Request, res: express.Response, videoU | |||
167 | } | 171 | } |
168 | 172 | ||
169 | function extractTokenOrDie (req: express.Request, res: express.Response) { | 173 | function 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' | |||
12 | export * from './video-source' | 12 | export * from './video-source' |
13 | export * from './video-stats' | 13 | export * from './video-stats' |
14 | export * from './video-studio' | 14 | export * from './video-studio' |
15 | export * from './video-token' | ||
15 | export * from './video-transcoding' | 16 | export * from './video-transcoding' |
16 | export * from './videos' | 17 | export * from './videos' |
17 | export * from './video-channel-sync' | 18 | export * from './video-channel-sync' |
19 | export * 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 | ||
16 | const addVideoCaptionValidator = [ | 17 | const addVideoCaptionValidator = [ |
@@ -62,6 +63,8 @@ const deleteVideoCaptionValidator = [ | |||
62 | const listVideoCaptionsValidator = [ | 63 | const 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 | ||
20 | const listVideoCommentsValidator = [ | 21 | const listVideoCommentsValidator = [ |
@@ -51,6 +52,7 @@ const listVideoCommentsValidator = [ | |||
51 | 52 | ||
52 | const listVideoCommentThreadsValidator = [ | 53 | const 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-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' | |||
9 | import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' | 9 | import { VideoImportCreate } from '@shared/models/videos/import/video-import-create.model' |
10 | import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' | 10 | import { isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' |
11 | import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' | 11 | import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' |
12 | import { isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' | 12 | import { |
13 | isValidPasswordProtectedPrivacy, | ||
14 | isVideoMagnetUriValid, | ||
15 | isVideoNameValid | ||
16 | } from '../../../helpers/custom-validators/videos' | ||
13 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 17 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
14 | import { logger } from '../../../helpers/logger' | 18 | import { logger } from '../../../helpers/logger' |
15 | import { CONFIG } from '../../../initializers/config' | 19 | import { 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' |
19 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | 19 | import { exists, isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' |
20 | import { isVideoNameValid, isVideoPrivacyValid } from '../../../helpers/custom-validators/videos' | 20 | import { isValidPasswordProtectedPrivacy, isVideoNameValid, isVideoReplayPrivacyValid } from '../../../helpers/custom-validators/videos' |
21 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 21 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
22 | import { logger } from '../../../helpers/logger' | 22 | import { logger } from '../../../helpers/logger' |
23 | import { CONFIG } from '../../../initializers/config' | 23 | import { 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 @@ | |||
1 | import express from 'express' | ||
2 | import { | ||
3 | areValidationErrors, | ||
4 | doesVideoExist, | ||
5 | isVideoPasswordProtected, | ||
6 | isValidVideoIdParam, | ||
7 | doesVideoPasswordExist, | ||
8 | isVideoPasswordDeletable, | ||
9 | checkUserCanManageVideo | ||
10 | } from '../shared' | ||
11 | import { body, param } from 'express-validator' | ||
12 | import { isIdValid } from '@server/helpers/custom-validators/misc' | ||
13 | import { isValidPasswordProtectedPrivacy } from '@server/helpers/custom-validators/videos' | ||
14 | import { UserRight } from '@shared/models' | ||
15 | |||
16 | const 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 | |||
33 | const 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 | |||
53 | const 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 | |||
73 | export { | ||
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' | |||
7 | import { isRatingValid } from '../../../helpers/custom-validators/video-rates' | 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 { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 9 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
10 | import { areValidationErrors, checkCanSeeVideo, doesVideoExist, isValidVideoIdParam } from '../shared' | 10 | import { areValidationErrors, checkCanSeeVideo, doesVideoExist, isValidVideoIdParam, isValidVideoPasswordHeader } from '../shared' |
11 | 11 | ||
12 | const videoUpdateRateValidator = [ | 12 | const 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 @@ | |||
1 | import express from 'express' | ||
2 | import { VideoPrivacy } from '../../../../shared/models/videos' | ||
3 | import { HttpStatusCode } from '@shared/models' | ||
4 | import { exists } from '@server/helpers/custom-validators/misc' | ||
5 | |||
6 | const 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 | |||
22 | export { | ||
23 | videoFileTokenValidator | ||
24 | } | ||
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 794e1d4f1..7f1f39b11 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -23,6 +23,7 @@ import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../ | |||
23 | import { | 23 | import { |
24 | areVideoTagsValid, | 24 | areVideoTagsValid, |
25 | isScheduleVideoUpdatePrivacyValid, | 25 | isScheduleVideoUpdatePrivacyValid, |
26 | isValidPasswordProtectedPrivacy, | ||
26 | isVideoCategoryValid, | 27 | isVideoCategoryValid, |
27 | isVideoDescriptionValid, | 28 | isVideoDescriptionValid, |
28 | isVideoFileMimeTypeValid, | 29 | isVideoFileMimeTypeValid, |
@@ -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 | ||
61 | const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ | 63 | const 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) { |
@@ -174,6 +182,10 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([ | |||
174 | body('channelId') | 182 | body('channelId') |
175 | .customSanitizer(toIntOrNull) | 183 | .customSanitizer(toIntOrNull) |
176 | .custom(isIdValid), | 184 | .custom(isIdValid), |
185 | body('videoPasswords') | ||
186 | .optional() | ||
187 | .isArray() | ||
188 | .withMessage('Video passwords should be an array.'), | ||
177 | 189 | ||
178 | header('x-upload-content-length') | 190 | header('x-upload-content-length') |
179 | .isNumeric() | 191 | .isNumeric() |
@@ -205,6 +217,8 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([ | |||
205 | const files = { videofile: [ videoFileMetadata ] } | 217 | const files = { videofile: [ videoFileMetadata ] } |
206 | if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup() | 218 | if (!await commonVideoChecksPass({ req, res, user, videoFileSize: videoFileMetadata.size, files })) return cleanup() |
207 | 219 | ||
220 | if (!isValidPasswordProtectedPrivacy(req, res)) return cleanup() | ||
221 | |||
208 | // multer required unsetting the Content-Type, now we can set it for node-uploadx | 222 | // multer required unsetting the Content-Type, now we can set it for node-uploadx |
209 | req.headers['content-type'] = 'application/json; charset=utf-8' | 223 | req.headers['content-type'] = 'application/json; charset=utf-8' |
210 | // place previewfile in metadata so that uploadx saves it in .META | 224 | // place previewfile in metadata so that uploadx saves it in .META |
@@ -227,12 +241,18 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ | |||
227 | .optional() | 241 | .optional() |
228 | .customSanitizer(toIntOrNull) | 242 | .customSanitizer(toIntOrNull) |
229 | .custom(isIdValid), | 243 | .custom(isIdValid), |
244 | body('videoPasswords') | ||
245 | .optional() | ||
246 | .isArray() | ||
247 | .withMessage('Video passwords should be an array.'), | ||
230 | 248 | ||
231 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 249 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
232 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | 250 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) |
233 | if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) | 251 | if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) |
234 | if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req) | 252 | if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req) |
235 | 253 | ||
254 | if (!isValidPasswordProtectedPrivacy(req, res)) return cleanUpReqFiles(req) | ||
255 | |||
236 | const video = getVideoWithAttributes(res) | 256 | const video = getVideoWithAttributes(res) |
237 | if (video.isLive && video.privacy !== req.body.privacy && video.state !== VideoState.WAITING_FOR_LIVE) { | 257 | 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' }) | 258 | return res.fail({ message: 'Cannot update privacy of a live that has already started' }) |
@@ -281,6 +301,8 @@ const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' | | |||
281 | return [ | 301 | return [ |
282 | isValidVideoIdParam('id'), | 302 | isValidVideoIdParam('id'), |
283 | 303 | ||
304 | isValidVideoPasswordHeader(), | ||
305 | |||
284 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 306 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
285 | if (areValidationErrors(req, res)) return | 307 | if (areValidationErrors(req, res)) return |
286 | if (!await doesVideoExist(req.params.id, res, fetchType)) return | 308 | if (!await doesVideoExist(req.params.id, res, fetchType)) return |