config.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import * as express from 'express'
  2. import { snakeCase } from 'lodash'
  3. import { ServerConfig, UserRight } from '../../../shared'
  4. import { About } from '../../../shared/models/server/about.model'
  5. import { CustomConfig } from '../../../shared/models/server/custom-config.model'
  6. import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
  7. import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
  8. import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
  9. import { customConfigUpdateValidator } from '../../middlewares/validators/config'
  10. import { ClientHtml } from '../../lib/client-html'
  11. import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
  12. import { remove, writeJSON } from 'fs-extra'
  13. import { getServerCommit } from '../../helpers/utils'
  14. import { Emailer } from '../../lib/emailer'
  15. import { isNumeric } from 'validator'
  16. import { objectConverter } from '../../helpers/core-utils'
  17. import { CONFIG, reloadConfig } from '../../initializers/config'
  18. const packageJSON = require('../../../../package.json')
  19. const configRouter = express.Router()
  20. const auditLogger = auditLoggerFactory('config')
  21. configRouter.get('/about', getAbout)
  22. configRouter.get('/',
  23. asyncMiddleware(getConfig)
  24. )
  25. configRouter.get('/custom',
  26. authenticate,
  27. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  28. asyncMiddleware(getCustomConfig)
  29. )
  30. configRouter.put('/custom',
  31. authenticate,
  32. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  33. asyncMiddleware(customConfigUpdateValidator),
  34. asyncMiddleware(updateCustomConfig)
  35. )
  36. configRouter.delete('/custom',
  37. authenticate,
  38. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  39. asyncMiddleware(deleteCustomConfig)
  40. )
  41. let serverCommit: string
  42. async function getConfig (req: express.Request, res: express.Response) {
  43. const allowed = await isSignupAllowed()
  44. const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
  45. if (serverCommit === undefined) serverCommit = await getServerCommit()
  46. const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
  47. .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
  48. .map(r => parseInt(r, 10))
  49. const json: ServerConfig = {
  50. instance: {
  51. name: CONFIG.INSTANCE.NAME,
  52. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  53. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  54. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  55. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  56. customizations: {
  57. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
  58. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
  59. }
  60. },
  61. email: {
  62. enabled: Emailer.isEnabled()
  63. },
  64. contactForm: {
  65. enabled: CONFIG.CONTACT_FORM.ENABLED
  66. },
  67. serverVersion: packageJSON.version,
  68. serverCommit,
  69. signup: {
  70. allowed,
  71. allowedForCurrentIP,
  72. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  73. },
  74. transcoding: {
  75. hls: {
  76. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  77. },
  78. enabledResolutions
  79. },
  80. import: {
  81. videos: {
  82. http: {
  83. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  84. },
  85. torrent: {
  86. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  87. }
  88. }
  89. },
  90. autoBlacklist: {
  91. videos: {
  92. ofUsers: {
  93. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  94. }
  95. }
  96. },
  97. avatar: {
  98. file: {
  99. size: {
  100. max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
  101. },
  102. extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
  103. }
  104. },
  105. video: {
  106. image: {
  107. extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
  108. size: {
  109. max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
  110. }
  111. },
  112. file: {
  113. extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
  114. }
  115. },
  116. videoCaption: {
  117. file: {
  118. size: {
  119. max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
  120. },
  121. extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
  122. }
  123. },
  124. user: {
  125. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  126. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  127. },
  128. trending: {
  129. videos: {
  130. intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
  131. }
  132. },
  133. tracker: {
  134. enabled: CONFIG.TRACKER.ENABLED
  135. }
  136. }
  137. return res.json(json)
  138. }
  139. function getAbout (req: express.Request, res: express.Response) {
  140. const about: About = {
  141. instance: {
  142. name: CONFIG.INSTANCE.NAME,
  143. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  144. description: CONFIG.INSTANCE.DESCRIPTION,
  145. terms: CONFIG.INSTANCE.TERMS
  146. }
  147. }
  148. return res.json(about).end()
  149. }
  150. async function getCustomConfig (req: express.Request, res: express.Response) {
  151. const data = customConfig()
  152. return res.json(data).end()
  153. }
  154. async function deleteCustomConfig (req: express.Request, res: express.Response) {
  155. await remove(CONFIG.CUSTOM_FILE)
  156. auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
  157. reloadConfig()
  158. ClientHtml.invalidCache()
  159. const data = customConfig()
  160. return res.json(data).end()
  161. }
  162. async function updateCustomConfig (req: express.Request, res: express.Response) {
  163. const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
  164. // camelCase to snake_case key + Force number conversion
  165. const toUpdateJSON = convertCustomConfigBody(req.body)
  166. await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
  167. reloadConfig()
  168. ClientHtml.invalidCache()
  169. const data = customConfig()
  170. auditLogger.update(
  171. getAuditIdFromRes(res),
  172. new CustomConfigAuditView(data),
  173. oldCustomConfigAuditKeys
  174. )
  175. return res.json(data).end()
  176. }
  177. // ---------------------------------------------------------------------------
  178. export {
  179. configRouter
  180. }
  181. // ---------------------------------------------------------------------------
  182. function customConfig (): CustomConfig {
  183. return {
  184. instance: {
  185. name: CONFIG.INSTANCE.NAME,
  186. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  187. description: CONFIG.INSTANCE.DESCRIPTION,
  188. terms: CONFIG.INSTANCE.TERMS,
  189. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  190. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  191. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  192. customizations: {
  193. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
  194. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
  195. }
  196. },
  197. services: {
  198. twitter: {
  199. username: CONFIG.SERVICES.TWITTER.USERNAME,
  200. whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
  201. }
  202. },
  203. cache: {
  204. previews: {
  205. size: CONFIG.CACHE.PREVIEWS.SIZE
  206. },
  207. captions: {
  208. size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
  209. }
  210. },
  211. signup: {
  212. enabled: CONFIG.SIGNUP.ENABLED,
  213. limit: CONFIG.SIGNUP.LIMIT,
  214. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  215. },
  216. admin: {
  217. email: CONFIG.ADMIN.EMAIL
  218. },
  219. contactForm: {
  220. enabled: CONFIG.CONTACT_FORM.ENABLED
  221. },
  222. user: {
  223. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  224. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  225. },
  226. transcoding: {
  227. enabled: CONFIG.TRANSCODING.ENABLED,
  228. allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
  229. allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
  230. threads: CONFIG.TRANSCODING.THREADS,
  231. resolutions: {
  232. '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
  233. '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ],
  234. '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
  235. '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
  236. '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ],
  237. '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ]
  238. },
  239. hls: {
  240. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  241. }
  242. },
  243. import: {
  244. videos: {
  245. http: {
  246. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  247. },
  248. torrent: {
  249. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  250. }
  251. }
  252. },
  253. autoBlacklist: {
  254. videos: {
  255. ofUsers: {
  256. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  257. }
  258. }
  259. },
  260. followers: {
  261. instance: {
  262. enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
  263. manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
  264. }
  265. }
  266. }
  267. }
  268. function convertCustomConfigBody (body: CustomConfig) {
  269. function keyConverter (k: string) {
  270. // Transcoding resolutions exception
  271. if (/^\d{3,4}p$/.exec(k)) return k
  272. return snakeCase(k)
  273. }
  274. function valueConverter (v: any) {
  275. if (isNumeric(v + '')) return parseInt('' + v, 10)
  276. return v
  277. }
  278. return objectConverter(body, keyConverter, valueConverter)
  279. }