]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/middlewares/validators/shared/videos.ts
Merge branch 'release/4.3.0' into develop
[github/Chocobozzz/PeerTube.git] / server / middlewares / validators / shared / videos.ts
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
24 async function doesVideoExist (id: number | string, res: Response, fetchType: VideoLoadType = 'all') {
25 const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
26
27 const video = await loadVideo(id, fetchType, userId)
28
29 if (!video) {
30 res.fail({
31 status: HttpStatusCode.NOT_FOUND_404,
32 message: 'Video not found'
33 })
34
35 return false
36 }
37
38 switch (fetchType) {
39 case 'for-api':
40 res.locals.videoAPI = video as MVideoFormattableDetails
41 break
42
43 case 'all':
44 res.locals.videoAll = video as MVideoFullLight
45 break
46
47 case 'only-immutable-attributes':
48 res.locals.onlyImmutableVideo = video as MVideoImmutable
49 break
50
51 case 'id':
52 res.locals.videoId = video as MVideoId
53 break
54
55 case 'only-video':
56 res.locals.onlyVideo = video as MVideoThumbnail
57 break
58 }
59
60 return true
61 }
62
63 // ---------------------------------------------------------------------------
64
65 async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) {
66 if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) {
67 res.fail({
68 status: HttpStatusCode.NOT_FOUND_404,
69 message: 'VideoFile matching Video not found'
70 })
71 return false
72 }
73
74 return true
75 }
76
77 // ---------------------------------------------------------------------------
78
79 async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) {
80 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
81
82 if (videoChannel === null) {
83 res.fail({ message: 'Unknown video "video channel" for this instance.' })
84 return false
85 }
86
87 // Don't check account id if the user can update any video
88 if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
89 res.locals.videoChannel = videoChannel
90 return true
91 }
92
93 if (videoChannel.Account.id !== user.Account.id) {
94 res.fail({
95 message: 'Unknown video "video channel" for this account.'
96 })
97 return false
98 }
99
100 res.locals.videoChannel = videoChannel
101 return true
102 }
103
104 // ---------------------------------------------------------------------------
105
106 async function checkCanSeeVideo (options: {
107 req: Request
108 res: Response
109 paramId: string
110 video: MVideo
111 }) {
112 const { req, res, video, paramId } = options
113
114 if (video.requiresAuth({ urlParamId: paramId, checkBlacklist: true })) {
115 return checkCanSeeAuthVideo(req, res, video)
116 }
117
118 if (video.privacy === VideoPrivacy.UNLISTED || video.privacy === VideoPrivacy.PUBLIC) {
119 return true
120 }
121
122 throw new Error('Unknown video privacy when checking video right ' + video.url)
123 }
124
125 async function checkCanSeeAuthVideo (req: Request, res: Response, video: MVideoId | MVideoWithRights) {
126 const fail = () => {
127 res.fail({
128 status: HttpStatusCode.FORBIDDEN_403,
129 message: 'Cannot fetch information of private/internal/blocked video'
130 })
131
132 return false
133 }
134
135 await authenticatePromise(req, res)
136
137 const user = res.locals.oauth?.token.User
138 if (!user) return fail()
139
140 const videoWithRights = (video as MVideoWithRights).VideoChannel?.Account?.userId
141 ? video as MVideoWithRights
142 : await VideoModel.loadFull(video.id)
143
144 const privacy = videoWithRights.privacy
145
146 if (privacy === VideoPrivacy.INTERNAL) {
147 // We know we have a user
148 return true
149 }
150
151 const isOwnedByUser = videoWithRights.VideoChannel.Account.userId === user.id
152
153 if (videoWithRights.isBlacklisted()) {
154 if (isOwnedByUser || user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) return true
155
156 return fail()
157 }
158
159 if (privacy === VideoPrivacy.PRIVATE || privacy === VideoPrivacy.UNLISTED) {
160 if (isOwnedByUser || user.hasRight(UserRight.SEE_ALL_VIDEOS)) return true
161
162 return fail()
163 }
164
165 // Should not happen
166 return fail()
167 }
168
169 // ---------------------------------------------------------------------------
170
171 async function checkCanAccessVideoStaticFiles (options: {
172 video: MVideo
173 req: Request
174 res: Response
175 paramId: string
176 }) {
177 const { video, req, res } = options
178
179 if (res.locals.oauth?.token.User) {
180 return checkCanSeeVideo(options)
181 }
182
183 if (!video.hasPrivateStaticPath()) return true
184
185 const videoFileToken = req.query.videoFileToken
186 if (!videoFileToken) {
187 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
188 return false
189 }
190
191 if (VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) {
192 return true
193 }
194
195 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
196 return false
197 }
198
199 // ---------------------------------------------------------------------------
200
201 function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
202 // Retrieve the user who did the request
203 if (onlyOwned && video.isOwned() === false) {
204 res.fail({
205 status: HttpStatusCode.FORBIDDEN_403,
206 message: 'Cannot manage a video of another server.'
207 })
208 return false
209 }
210
211 // Check if the user can delete the video
212 // The user can delete it if he has the right
213 // Or if s/he is the video's account
214 const account = video.VideoChannel.Account
215 if (user.hasRight(right) === false && account.userId !== user.id) {
216 res.fail({
217 status: HttpStatusCode.FORBIDDEN_403,
218 message: 'Cannot manage a video of another user.'
219 })
220 return false
221 }
222
223 return true
224 }
225
226 // ---------------------------------------------------------------------------
227
228 async function checkUserQuota (user: MUserId, videoFileSize: number, res: Response) {
229 if (await isAbleToUploadVideo(user.id, videoFileSize) === false) {
230 res.fail({
231 status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
232 message: 'The user video quota is exceeded with this video.',
233 type: ServerErrorCode.QUOTA_REACHED
234 })
235 return false
236 }
237
238 return true
239 }
240
241 // ---------------------------------------------------------------------------
242
243 export {
244 doesVideoChannelOfAccountExist,
245 doesVideoExist,
246 doesVideoFileOfVideoExist,
247
248 checkCanAccessVideoStaticFiles,
249 checkUserCanManageVideo,
250 checkCanSeeVideo,
251 checkUserQuota
252 }