diff options
Diffstat (limited to 'server/middlewares/validators/shared/videos.ts')
-rw-r--r-- | server/middlewares/validators/shared/videos.ts | 311 |
1 files changed, 0 insertions, 311 deletions
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts deleted file mode 100644 index 9a7497007..000000000 --- a/server/middlewares/validators/shared/videos.ts +++ /dev/null | |||
@@ -1,311 +0,0 @@ | |||
1 | import { Request, Response } from 'express' | ||
2 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' | ||
3 | import { isAbleToUploadVideo } from '@server/lib/user' | ||
4 | import { VideoTokensManager } from '@server/lib/video-tokens-manager' | ||
5 | import { authenticatePromise } from '@server/middlewares/auth' | ||
6 | import { VideoModel } from '@server/models/video/video' | ||
7 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
8 | import { VideoFileModel } from '@server/models/video/video-file' | ||
9 | import { | ||
10 | MUser, | ||
11 | MUserAccountId, | ||
12 | MUserId, | ||
13 | MVideo, | ||
14 | MVideoAccountLight, | ||
15 | MVideoFormattableDetails, | ||
16 | MVideoFullLight, | ||
17 | MVideoId, | ||
18 | MVideoImmutable, | ||
19 | MVideoThumbnail, | ||
20 | MVideoWithRights | ||
21 | } from '@server/types/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' | ||
25 | |||
26 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') { | ||
27 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | ||
28 | |||
29 | const video = await loadVideo(id, fetchType, userId) | ||
30 | |||
31 | if (!video) { | ||
32 | res.fail({ | ||
33 | status: HttpStatusCode.NOT_FOUND_404, | ||
34 | message: 'Video not found' | ||
35 | }) | ||
36 | |||
37 | return false | ||
38 | } | ||
39 | |||
40 | switch (fetchType) { | ||
41 | case 'for-api': | ||
42 | res.locals.videoAPI = video as MVideoFormattableDetails | ||
43 | break | ||
44 | |||
45 | case 'all': | ||
46 | res.locals.videoAll = video as MVideoFullLight | ||
47 | break | ||
48 | |||
49 | case 'only-immutable-attributes': | ||
50 | res.locals.onlyImmutableVideo = video as MVideoImmutable | ||
51 | break | ||
52 | |||
53 | case 'id': | ||
54 | res.locals.videoId = video as MVideoId | ||
55 | break | ||
56 | |||
57 | case 'only-video': | ||
58 | res.locals.onlyVideo = video as MVideoThumbnail | ||
59 | break | ||
60 | } | ||
61 | |||
62 | return true | ||
63 | } | ||
64 | |||
65 | // --------------------------------------------------------------------------- | ||
66 | |||
67 | async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { | ||
68 | if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) { | ||
69 | res.fail({ | ||
70 | status: HttpStatusCode.NOT_FOUND_404, | ||
71 | message: 'VideoFile matching Video not found' | ||
72 | }) | ||
73 | return false | ||
74 | } | ||
75 | |||
76 | return true | ||
77 | } | ||
78 | |||
79 | // --------------------------------------------------------------------------- | ||
80 | |||
81 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { | ||
82 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | ||
83 | |||
84 | if (videoChannel === null) { | ||
85 | res.fail({ message: 'Unknown video "video channel" for this instance.' }) | ||
86 | return false | ||
87 | } | ||
88 | |||
89 | // Don't check account id if the user can update any video | ||
90 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | ||
91 | res.locals.videoChannel = videoChannel | ||
92 | return true | ||
93 | } | ||
94 | |||
95 | if (videoChannel.Account.id !== user.Account.id) { | ||
96 | res.fail({ | ||
97 | message: 'Unknown video "video channel" for this account.' | ||
98 | }) | ||
99 | return false | ||
100 | } | ||
101 | |||
102 | res.locals.videoChannel = videoChannel | ||
103 | return true | ||
104 | } | ||
105 | |||
106 | // --------------------------------------------------------------------------- | ||
107 | |||
108 | async function checkCanSeeVideo (options: { | ||
109 | req: Request | ||
110 | res: Response | ||
111 | paramId: string | ||
112 | video: MVideo | ||
113 | }) { | ||
114 | const { req, res, video, paramId } = options | ||
115 | |||
116 | if (video.requiresUserAuth({ urlParamId: paramId, checkBlacklist: true })) { | ||
117 | return checkCanSeeUserAuthVideo({ req, res, video }) | ||
118 | } | ||
119 | |||
120 | if (video.privacy === VideoPrivacy.PASSWORD_PROTECTED) { | ||
121 | return checkCanSeePasswordProtectedVideo({ req, res, video }) | ||
122 | } | ||
123 | |||
124 | if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) { | ||
125 | return true | ||
126 | } | ||
127 | |||
128 | throw new Error('Unknown video privacy when checking video right ' + video.url) | ||
129 | } | ||
130 | |||
131 | async function checkCanSeeUserAuthVideo (options: { | ||
132 | req: Request | ||
133 | res: Response | ||
134 | video: MVideoId | MVideoWithRights | ||
135 | }) { | ||
136 | const { req, res, video } = options | ||
137 | |||
138 | const fail = () => { | ||
139 | res.fail({ | ||
140 | status: HttpStatusCode.FORBIDDEN_403, | ||
141 | message: 'Cannot fetch information of private/internal/blocked video' | ||
142 | }) | ||
143 | |||
144 | return false | ||
145 | } | ||
146 | |||
147 | await authenticatePromise({ req, res }) | ||
148 | |||
149 | const user = res.locals.oauth?.token.User | ||
150 | if (!user) return fail() | ||
151 | |||
152 | const videoWithRights = await getVideoWithRights(video as MVideoWithRights) | ||
153 | |||
154 | const privacy = videoWithRights.privacy | ||
155 | |||
156 | if (privacy === VideoPrivacy.INTERNAL) { | ||
157 | // We know we have a user | ||
158 | return true | ||
159 | } | ||
160 | |||
161 | if (videoWithRights.isBlacklisted()) { | ||
162 | if (canUserAccessVideo(user, videoWithRights, UserRight.MANAGE_VIDEO_BLACKLIST)) return true | ||
163 | |||
164 | return fail() | ||
165 | } | ||
166 | |||
167 | if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) { | ||
168 | if (canUserAccessVideo(user, videoWithRights, UserRight.SEE_ALL_VIDEOS)) return true | ||
169 | |||
170 | return fail() | ||
171 | } | ||
172 | |||
173 | // Should not happen | ||
174 | return fail() | ||
175 | } | ||
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 | |||
230 | // --------------------------------------------------------------------------- | ||
231 | |||
232 | async function checkCanAccessVideoStaticFiles (options: { | ||
233 | video: MVideo | ||
234 | req: Request | ||
235 | res: Response | ||
236 | paramId: string | ||
237 | }) { | ||
238 | const { video, req, res } = options | ||
239 | |||
240 | if (res.locals.oauth?.token.User || exists(req.header('x-peertube-video-password'))) { | ||
241 | return checkCanSeeVideo(options) | ||
242 | } | ||
243 | |||
244 | const videoFileToken = req.query.videoFileToken | ||
245 | if (videoFileToken && VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) { | ||
246 | const user = VideoTokensManager.Instance.getUserFromToken({ token: videoFileToken }) | ||
247 | |||
248 | res.locals.videoFileToken = { user } | ||
249 | return true | ||
250 | } | ||
251 | |||
252 | if (!video.hasPrivateStaticPath()) return true | ||
253 | |||
254 | res.sendStatus(HttpStatusCode.FORBIDDEN_403) | ||
255 | return false | ||
256 | } | ||
257 | |||
258 | // --------------------------------------------------------------------------- | ||
259 | |||
260 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { | ||
261 | // Retrieve the user who did the request | ||
262 | if (onlyOwned && video.isOwned() === false) { | ||
263 | res.fail({ | ||
264 | status: HttpStatusCode.FORBIDDEN_403, | ||
265 | message: 'Cannot manage a video of another server.' | ||
266 | }) | ||
267 | return false | ||
268 | } | ||
269 | |||
270 | // Check if the user can delete the video | ||
271 | // The user can delete it if he has the right | ||
272 | // Or if s/he is the video's account | ||
273 | const account = video.VideoChannel.Account | ||
274 | if (user.hasRight(right) === false && account.userId !== user.id) { | ||
275 | res.fail({ | ||
276 | status: HttpStatusCode.FORBIDDEN_403, | ||
277 | message: 'Cannot manage a video of another user.' | ||
278 | }) | ||
279 | return false | ||
280 | } | ||
281 | |||
282 | return true | ||
283 | } | ||
284 | |||
285 | // --------------------------------------------------------------------------- | ||
286 | |||
287 | async function checkUserQuota (user: MUserId, videoFileSize: number, res: Response) { | ||
288 | if (await isAbleToUploadVideo(user.id, videoFileSize) === false) { | ||
289 | res.fail({ | ||
290 | status: HttpStatusCode.PAYLOAD_TOO_LARGE_413, | ||
291 | message: 'The user video quota is exceeded with this video.', | ||
292 | type: ServerErrorCode.QUOTA_REACHED | ||
293 | }) | ||
294 | return false | ||
295 | } | ||
296 | |||
297 | return true | ||
298 | } | ||
299 | |||
300 | // --------------------------------------------------------------------------- | ||
301 | |||
302 | export { | ||
303 | doesVideoChannelOfAccountExist, | ||
304 | doesVideoExist, | ||
305 | doesVideoFileOfVideoExist, | ||
306 | |||
307 | checkCanAccessVideoStaticFiles, | ||
308 | checkUserCanManageVideo, | ||
309 | checkCanSeeVideo, | ||
310 | checkUserQuota | ||
311 | } | ||