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.ts131
1 files changed, 131 insertions, 0 deletions
diff --git a/server/middlewares/validators/static.ts b/server/middlewares/validators/static.ts
new file mode 100644
index 000000000..ff9e6ae6e
--- /dev/null
+++ b/server/middlewares/validators/static.ts
@@ -0,0 +1,131 @@
1import express from 'express'
2import { query } from 'express-validator'
3import LRUCache from 'lru-cache'
4import { basename, dirname } from 'path'
5import { exists, isUUIDValid } 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 { HttpStatusCode } from '@shared/models'
11import { areValidationErrors, checkCanAccessVideoStaticFiles } from './shared'
12
13const staticFileTokenBypass = new LRUCache<string, boolean>({
14 max: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.MAX_SIZE,
15 ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL
16})
17
18const ensureCanAccessVideoPrivateWebTorrentFiles = [
19 query('videoFileToken').optional().custom(exists),
20
21 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
22 if (areValidationErrors(req, res)) return
23
24 const token = extractTokenOrDie(req, res)
25 if (!token) return
26
27 const cacheKey = token + '-' + req.originalUrl
28
29 if (staticFileTokenBypass.has(cacheKey)) {
30 const allowedFromCache = staticFileTokenBypass.get(cacheKey)
31
32 if (allowedFromCache === true) return next()
33
34 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
35 }
36
37 const allowed = await isWebTorrentAllowed(req, res)
38
39 staticFileTokenBypass.set(cacheKey, allowed)
40
41 if (allowed !== true) return
42
43 return next()
44 }
45]
46
47const ensureCanAccessPrivateVideoHLSFiles = [
48 query('videoFileToken').optional().custom(exists),
49
50 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
51 if (areValidationErrors(req, res)) return
52
53 const videoUUID = basename(dirname(req.originalUrl))
54
55 if (!isUUIDValid(videoUUID)) {
56 logger.debug('Path does not contain valid video UUID to serve static file %s', req.originalUrl)
57
58 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
59 }
60
61 const token = extractTokenOrDie(req, res)
62 if (!token) return
63
64 const cacheKey = token + '-' + videoUUID
65
66 if (staticFileTokenBypass.has(cacheKey)) {
67 const allowedFromCache = staticFileTokenBypass.get(cacheKey)
68
69 if (allowedFromCache === true) return next()
70
71 return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
72 }
73
74 const allowed = await isHLSAllowed(req, res, videoUUID)
75
76 staticFileTokenBypass.set(cacheKey, allowed)
77
78 if (allowed !== true) return
79
80 return next()
81 }
82]
83
84export {
85 ensureCanAccessVideoPrivateWebTorrentFiles,
86 ensureCanAccessPrivateVideoHLSFiles
87}
88
89// ---------------------------------------------------------------------------
90
91async function isWebTorrentAllowed (req: express.Request, res: express.Response) {
92 const filename = basename(req.path)
93
94 const file = await VideoFileModel.loadWithVideoByFilename(filename)
95 if (!file) {
96 logger.debug('Unknown static file %s to serve', req.originalUrl, { filename })
97
98 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
99 return false
100 }
101
102 const video = file.getVideo()
103
104 return checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
105}
106
107async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
108 const video = await VideoModel.load(videoUUID)
109
110 if (!video) {
111 logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })
112
113 res.sendStatus(HttpStatusCode.FORBIDDEN_403)
114 return false
115 }
116
117 return checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
118}
119
120function extractTokenOrDie (req: express.Request, res: express.Response) {
121 const token = res.locals.oauth?.token.accessToken || req.query.videoFileToken
122
123 if (!token) {
124 return res.fail({
125 message: 'Bearer token is missing in headers or video file token is missing in URL query parameters',
126 status: HttpStatusCode.FORBIDDEN_403
127 })
128 }
129
130 return token
131}