static.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import cors from 'cors'
  2. import express from 'express'
  3. import { join } from 'path'
  4. import { serveIndexHTML } from '@server/lib/client-html'
  5. import { ServerConfigManager } from '@server/lib/server-config-manager'
  6. import { HttpStatusCode } from '@shared/models'
  7. import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
  8. import { root } from '../helpers/core-utils'
  9. import { CONFIG, isEmailEnabled } from '../initializers/config'
  10. import {
  11. CONSTRAINTS_FIELDS,
  12. DEFAULT_THEME_NAME,
  13. HLS_STREAMING_PLAYLIST_DIRECTORY,
  14. PEERTUBE_VERSION,
  15. ROUTE_CACHE_LIFETIME,
  16. STATIC_MAX_AGE,
  17. STATIC_PATHS,
  18. WEBSERVER
  19. } from '../initializers/constants'
  20. import { getThemeOrDefault } from '../lib/plugins/theme-utils'
  21. import { asyncMiddleware } from '../middlewares'
  22. import { cacheRoute } from '../middlewares/cache/cache'
  23. import { UserModel } from '../models/user/user'
  24. import { VideoModel } from '../models/video/video'
  25. import { VideoCommentModel } from '../models/video/video-comment'
  26. const staticRouter = express.Router()
  27. staticRouter.use(cors())
  28. /*
  29. Cors is very important to let other servers access torrent and video files
  30. */
  31. // FIXME: deprecated in 3.2, use lazy-statics instead
  32. // Due to historical reasons, we can't really remove this controller
  33. const torrentsPhysicalPath = CONFIG.STORAGE.TORRENTS_DIR
  34. staticRouter.use(
  35. STATIC_PATHS.TORRENTS,
  36. express.static(torrentsPhysicalPath, { maxAge: 0 }) // Don't cache because we could regenerate the torrent file
  37. )
  38. // Videos path for webseed
  39. staticRouter.use(
  40. STATIC_PATHS.WEBSEED,
  41. express.static(CONFIG.STORAGE.VIDEOS_DIR, { fallthrough: false }) // 404 because we don't have this video
  42. )
  43. staticRouter.use(
  44. STATIC_PATHS.REDUNDANCY,
  45. express.static(CONFIG.STORAGE.REDUNDANCY_DIR, { fallthrough: false }) // 404 because we don't have this video
  46. )
  47. // HLS
  48. staticRouter.use(
  49. STATIC_PATHS.STREAMING_PLAYLISTS.HLS,
  50. cors(),
  51. express.static(HLS_STREAMING_PLAYLIST_DIRECTORY, { fallthrough: false }) // 404 if the file does not exist
  52. )
  53. // Thumbnails path for express
  54. const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR
  55. staticRouter.use(
  56. STATIC_PATHS.THUMBNAILS,
  57. express.static(thumbnailsPhysicalPath, { maxAge: STATIC_MAX_AGE.SERVER, fallthrough: false }) // 404 if the file does not exist
  58. )
  59. // robots.txt service
  60. staticRouter.get('/robots.txt',
  61. cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS),
  62. (_, res: express.Response) => {
  63. res.type('text/plain')
  64. return res.send(CONFIG.INSTANCE.ROBOTS)
  65. }
  66. )
  67. staticRouter.all('/teapot',
  68. getCup,
  69. asyncMiddleware(serveIndexHTML)
  70. )
  71. // security.txt service
  72. staticRouter.get('/security.txt',
  73. (_, res: express.Response) => {
  74. return res.redirect(HttpStatusCode.MOVED_PERMANENTLY_301, '/.well-known/security.txt')
  75. }
  76. )
  77. staticRouter.get('/.well-known/security.txt',
  78. cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT),
  79. (_, res: express.Response) => {
  80. res.type('text/plain')
  81. return res.send(CONFIG.INSTANCE.SECURITYTXT + CONFIG.INSTANCE.SECURITYTXT_CONTACT)
  82. }
  83. )
  84. // nodeinfo service
  85. staticRouter.use('/.well-known/nodeinfo',
  86. cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
  87. (_, res: express.Response) => {
  88. return res.json({
  89. links: [
  90. {
  91. rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
  92. href: WEBSERVER.URL + '/nodeinfo/2.0.json'
  93. }
  94. ]
  95. })
  96. }
  97. )
  98. staticRouter.use('/nodeinfo/:version.json',
  99. cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO),
  100. asyncMiddleware(generateNodeinfo)
  101. )
  102. // dnt-policy.txt service (see https://www.eff.org/dnt-policy)
  103. staticRouter.use('/.well-known/dnt-policy.txt',
  104. cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY),
  105. (_, res: express.Response) => {
  106. res.type('text/plain')
  107. return res.sendFile(join(root(), 'dist/server/static/dnt-policy/dnt-policy-1.0.txt'))
  108. }
  109. )
  110. // dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource)
  111. staticRouter.use('/.well-known/dnt/',
  112. (_, res: express.Response) => {
  113. res.json({ tracking: 'N' })
  114. }
  115. )
  116. staticRouter.use('/.well-known/change-password',
  117. (_, res: express.Response) => {
  118. res.redirect('/my-account/settings')
  119. }
  120. )
  121. staticRouter.use('/.well-known/host-meta',
  122. (_, res: express.Response) => {
  123. res.type('application/xml')
  124. const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' +
  125. '<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n' +
  126. ` <Link rel="lrdd" type="application/xrd+xml" template="${WEBSERVER.URL}/.well-known/webfinger?resource={uri}"/>\n` +
  127. '</XRD>'
  128. res.send(xml).end()
  129. }
  130. )
  131. // ---------------------------------------------------------------------------
  132. export {
  133. staticRouter
  134. }
  135. // ---------------------------------------------------------------------------
  136. async function generateNodeinfo (req: express.Request, res: express.Response) {
  137. const { totalVideos } = await VideoModel.getStats()
  138. const { totalLocalVideoComments } = await VideoCommentModel.getStats()
  139. const { totalUsers, totalMonthlyActiveUsers, totalHalfYearActiveUsers } = await UserModel.getStats()
  140. if (!req.params.version || req.params.version !== '2.0') {
  141. return res.fail({
  142. status: HttpStatusCode.NOT_FOUND_404,
  143. message: 'Nodeinfo schema version not handled'
  144. })
  145. }
  146. const json = {
  147. version: '2.0',
  148. software: {
  149. name: 'peertube',
  150. version: PEERTUBE_VERSION
  151. },
  152. protocols: [
  153. 'activitypub'
  154. ],
  155. services: {
  156. inbound: [],
  157. outbound: [
  158. 'atom1.0',
  159. 'rss2.0'
  160. ]
  161. },
  162. openRegistrations: CONFIG.SIGNUP.ENABLED,
  163. usage: {
  164. users: {
  165. total: totalUsers,
  166. activeMonth: totalMonthlyActiveUsers,
  167. activeHalfyear: totalHalfYearActiveUsers
  168. },
  169. localPosts: totalVideos,
  170. localComments: totalLocalVideoComments
  171. },
  172. metadata: {
  173. taxonomy: {
  174. postsName: 'Videos'
  175. },
  176. nodeName: CONFIG.INSTANCE.NAME,
  177. nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  178. nodeConfig: {
  179. search: {
  180. remoteUri: {
  181. users: CONFIG.SEARCH.REMOTE_URI.USERS,
  182. anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
  183. }
  184. },
  185. plugin: {
  186. registered: ServerConfigManager.Instance.getRegisteredPlugins()
  187. },
  188. theme: {
  189. registered: ServerConfigManager.Instance.getRegisteredThemes(),
  190. default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
  191. },
  192. email: {
  193. enabled: isEmailEnabled()
  194. },
  195. contactForm: {
  196. enabled: CONFIG.CONTACT_FORM.ENABLED
  197. },
  198. transcoding: {
  199. hls: {
  200. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  201. },
  202. webtorrent: {
  203. enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
  204. },
  205. enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('vod')
  206. },
  207. live: {
  208. enabled: CONFIG.LIVE.ENABLED,
  209. transcoding: {
  210. enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
  211. enabledResolutions: ServerConfigManager.Instance.getEnabledResolutions('live')
  212. }
  213. },
  214. import: {
  215. videos: {
  216. http: {
  217. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  218. },
  219. torrent: {
  220. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  221. }
  222. }
  223. },
  224. autoBlacklist: {
  225. videos: {
  226. ofUsers: {
  227. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  228. }
  229. }
  230. },
  231. avatar: {
  232. file: {
  233. size: {
  234. max: CONSTRAINTS_FIELDS.ACTORS.IMAGE.FILE_SIZE.max
  235. },
  236. extensions: CONSTRAINTS_FIELDS.ACTORS.IMAGE.EXTNAME
  237. }
  238. },
  239. video: {
  240. image: {
  241. extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
  242. size: {
  243. max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
  244. }
  245. },
  246. file: {
  247. extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
  248. }
  249. },
  250. videoCaption: {
  251. file: {
  252. size: {
  253. max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
  254. },
  255. extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
  256. }
  257. },
  258. user: {
  259. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  260. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  261. },
  262. trending: {
  263. videos: {
  264. intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
  265. }
  266. },
  267. tracker: {
  268. enabled: CONFIG.TRACKER.ENABLED
  269. }
  270. }
  271. }
  272. } as HttpNodeinfoDiasporaSoftwareNsSchema20
  273. res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"')
  274. .send(json)
  275. .end()
  276. }
  277. function getCup (req: express.Request, res: express.Response, next: express.NextFunction) {
  278. res.status(HttpStatusCode.I_AM_A_TEAPOT_418)
  279. res.setHeader('Accept-Additions', 'Non-Dairy;1,Sugar;1')
  280. res.setHeader('Safe', 'if-sepia-awake')
  281. return next()
  282. }