aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/middlewares/validators/static.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/middlewares/validators/static.ts')
-rw-r--r--server/middlewares/validators/static.ts184
1 files changed, 0 insertions, 184 deletions
diff --git a/server/middlewares/validators/static.ts b/server/middlewares/validators/static.ts
deleted file mode 100644
index 86cc0a8d7..000000000
--- a/server/middlewares/validators/static.ts
+++ /dev/null
@@ -1,184 +0,0 @@
1import express from 'express'
2import { query } from 'express-validator'
3import { LRUCache } from 'lru-cache'
4import { basename, dirname } from 'path'
5import { exists, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc'
6import { logger } from '@server/helpers/logger'
7import { LRU_CACHE } from '@server/initializers/constants'
8import { VideoModel } from '@server/models/video/video'
9import { VideoFileModel } from '@server/models/video/video-file'
10import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models'
11import { HttpStatusCode } from '@shared/models'
12import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared'
13
14type LRUValue = {
15 allowed: boolean
16 video?: MVideoThumbnail
17 file?: MVideoFile
18 playlist?: MStreamingPlaylist }
19
20const staticFileTokenBypass = new LRUCache<string, LRUValue>({
21 max: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.MAX_SIZE,
22 ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL
23})
24
25const ensureCanAccessVideoPrivateWebVideoFiles = [
26 query('videoFileToken').optional().custom(exists),
27
28 isValidVideoPasswordHeader(),
29
30 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
31 if (areValidationErrors(req, res)) return
32
33 const token = extractTokenOrDie(req, res)
34 if (!token) return
35
36 const cacheKey = token + '-' + req.originalUrl
37
38 if (staticFileTokenBypass.has(cacheKey)) {
39 const { allowed, file, video } = staticFileTokenBypass.get(cacheKey)
40
41 if (allowed === true) {
42 res.locals.onlyVideo = video
43 res.locals.videoFile = file
44
45 return next()
46 }
47
48 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
49 }
50
51 const result = await isWebVideoAllowed(req, res)
52
53 staticFileTokenBypass.set(cacheKey, result)
54
55 if (result.allowed !== true) return
56
57 res.locals.onlyVideo = result.video
58 res.locals.videoFile = result.file
59
60 return next()
61 }
62]
63
64const ensureCanAccessPrivateVideoHLSFiles = [
65 query('videoFileToken')
66 .optional()
67 .custom(exists),
68
69 query('reinjectVideoFileToken')
70 .optional()
71 .customSanitizer(toBooleanOrNull)
72 .isBoolean().withMessage('Should be a valid reinjectVideoFileToken boolean'),
73
74 query('playlistName')
75 .optional()
76 .customSanitizer(isSafePeerTubeFilenameWithoutExtension),
77
78 isValidVideoPasswordHeader(),
79
80 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
81 if (areValidationErrors(req, res)) return
82
83 const videoUUID = basename(dirname(req.originalUrl))
84
85 if (!isUUIDValid(videoUUID)) {
86 logger.debug('Path does not contain valid video UUID to serve static file %s', req.originalUrl)
87
88 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
89 }
90
91 const token = extractTokenOrDie(req, res)
92 if (!token) return
93
94 const cacheKey = token + '-' + videoUUID
95
96 if (staticFileTokenBypass.has(cacheKey)) {
97 const { allowed, file, playlist, video } = staticFileTokenBypass.get(cacheKey)
98
99 if (allowed === true) {
100 res.locals.onlyVideo = video
101 res.locals.videoFile = file
102 res.locals.videoStreamingPlaylist = playlist
103
104 return next()
105 }
106
107 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
108 }
109
110 const result = await isHLSAllowed(req, res, videoUUID)
111
112 staticFileTokenBypass.set(cacheKey, result)
113
114 if (result.allowed !== true) return
115
116 res.locals.onlyVideo = result.video
117 res.locals.videoFile = result.file
118 res.locals.videoStreamingPlaylist = result.playlist
119
120 return next()
121 }
122]
123
124export {
125 ensureCanAccessVideoPrivateWebVideoFiles,
126 ensureCanAccessPrivateVideoHLSFiles
127}
128
129// ---------------------------------------------------------------------------
130
131async function isWebVideoAllowed (req: express.Request, res: express.Response) {
132 const filename = basename(req.path)
133
134 const file = await VideoFileModel.loadWithVideoByFilename(filename)
135 if (!file) {
136 logger.debug('Unknown static file %s to serve', req.originalUrl, { filename })
137
138 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
139 return { allowed: false }
140 }
141
142 const video = await VideoModel.load(file.getVideo().id)
143
144 return {
145 file,
146 video,
147 allowed: await checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
148 }
149}
150
151async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
152 const filename = basename(req.path)
153
154 const video = await VideoModel.loadWithFiles(videoUUID)
155
156 if (!video) {
157 logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })
158
159 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
160 return { allowed: false }
161 }
162
163 const file = await VideoFileModel.loadByFilename(filename)
164
165 return {
166 file,
167 video,
168 playlist: video.getHLSPlaylist(),
169 allowed: await checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
170 }
171}
172
173function extractTokenOrDie (req: express.Request, res: express.Response) {
174 const token = req.header('x-peertube-video-password') || req.query.videoFileToken || res.locals.oauth?.token.accessToken
175
176 if (!token) {
177 return res.fail({
178 message: 'Video password header, video file token query parameter and bearer token are all missing', //
179 status: HttpStatusCode.FORBIDDEN_403
180 })
181 }
182
183 return token
184}