]>
Commit | Line | Data |
---|---|---|
3545e72c C |
1 | import express from 'express' |
2 | import { query } from 'express-validator' | |
3 | import LRUCache from 'lru-cache' | |
4 | import { basename, dirname } from 'path' | |
5 | import { exists, isUUIDValid } from '@server/helpers/custom-validators/misc' | |
6 | import { logger } from '@server/helpers/logger' | |
7 | import { LRU_CACHE } from '@server/initializers/constants' | |
8 | import { VideoModel } from '@server/models/video/video' | |
9 | import { VideoFileModel } from '@server/models/video/video-file' | |
10 | import { HttpStatusCode } from '@shared/models' | |
11 | import { areValidationErrors, checkCanAccessVideoStaticFiles } from './shared' | |
12 | ||
13 | const 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 | ||
18 | const 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 | ||
47 | const 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 | ||
84 | export { | |
85 | ensureCanAccessVideoPrivateWebTorrentFiles, | |
86 | ensureCanAccessPrivateVideoHLSFiles | |
87 | } | |
88 | ||
89 | // --------------------------------------------------------------------------- | |
90 | ||
91 | async 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 | ||
107 | async 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 | ||
120 | function 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 | } |