config.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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, DEFAULT_THEME_NAME, PEERTUBE_VERSION } 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. import { PluginManager } from '../../lib/plugins/plugin-manager'
  19. import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
  20. const configRouter = express.Router()
  21. const auditLogger = auditLoggerFactory('config')
  22. configRouter.get('/about', getAbout)
  23. configRouter.get('/',
  24. asyncMiddleware(getConfig)
  25. )
  26. configRouter.get('/custom',
  27. authenticate,
  28. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  29. asyncMiddleware(getCustomConfig)
  30. )
  31. configRouter.put('/custom',
  32. authenticate,
  33. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  34. asyncMiddleware(customConfigUpdateValidator),
  35. asyncMiddleware(updateCustomConfig)
  36. )
  37. configRouter.delete('/custom',
  38. authenticate,
  39. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  40. asyncMiddleware(deleteCustomConfig)
  41. )
  42. let serverCommit: string
  43. async function getConfig (req: express.Request, res: express.Response) {
  44. const allowed = await isSignupAllowed()
  45. const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
  46. const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
  47. if (serverCommit === undefined) serverCommit = await getServerCommit()
  48. const json: ServerConfig = {
  49. instance: {
  50. name: CONFIG.INSTANCE.NAME,
  51. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  52. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  53. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  54. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  55. customizations: {
  56. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
  57. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
  58. }
  59. },
  60. plugin: {
  61. registered: getRegisteredPlugins()
  62. },
  63. theme: {
  64. registered: getRegisteredThemes(),
  65. default: defaultTheme
  66. },
  67. email: {
  68. enabled: Emailer.isEnabled()
  69. },
  70. contactForm: {
  71. enabled: CONFIG.CONTACT_FORM.ENABLED
  72. },
  73. serverVersion: PEERTUBE_VERSION,
  74. serverCommit,
  75. signup: {
  76. allowed,
  77. allowedForCurrentIP,
  78. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  79. },
  80. transcoding: {
  81. hls: {
  82. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  83. },
  84. enabledResolutions: getEnabledResolutions()
  85. },
  86. import: {
  87. videos: {
  88. http: {
  89. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  90. },
  91. torrent: {
  92. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  93. }
  94. }
  95. },
  96. autoBlacklist: {
  97. videos: {
  98. ofUsers: {
  99. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  100. }
  101. }
  102. },
  103. avatar: {
  104. file: {
  105. size: {
  106. max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
  107. },
  108. extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
  109. }
  110. },
  111. video: {
  112. image: {
  113. extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
  114. size: {
  115. max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
  116. }
  117. },
  118. file: {
  119. extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
  120. }
  121. },
  122. videoCaption: {
  123. file: {
  124. size: {
  125. max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
  126. },
  127. extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
  128. }
  129. },
  130. user: {
  131. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  132. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  133. },
  134. trending: {
  135. videos: {
  136. intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
  137. }
  138. },
  139. tracker: {
  140. enabled: CONFIG.TRACKER.ENABLED
  141. }
  142. }
  143. return res.json(json)
  144. }
  145. function getAbout (req: express.Request, res: express.Response) {
  146. const about: About = {
  147. instance: {
  148. name: CONFIG.INSTANCE.NAME,
  149. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  150. description: CONFIG.INSTANCE.DESCRIPTION,
  151. terms: CONFIG.INSTANCE.TERMS,
  152. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  153. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  154. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  155. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  156. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  157. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  158. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  159. languages: CONFIG.INSTANCE.LANGUAGES,
  160. categories: CONFIG.INSTANCE.CATEGORIES
  161. }
  162. }
  163. return res.json(about).end()
  164. }
  165. async function getCustomConfig (req: express.Request, res: express.Response) {
  166. const data = customConfig()
  167. return res.json(data).end()
  168. }
  169. async function deleteCustomConfig (req: express.Request, res: express.Response) {
  170. await remove(CONFIG.CUSTOM_FILE)
  171. auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
  172. reloadConfig()
  173. ClientHtml.invalidCache()
  174. const data = customConfig()
  175. return res.json(data).end()
  176. }
  177. async function updateCustomConfig (req: express.Request, res: express.Response) {
  178. const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
  179. // camelCase to snake_case key + Force number conversion
  180. const toUpdateJSON = convertCustomConfigBody(req.body)
  181. await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
  182. reloadConfig()
  183. ClientHtml.invalidCache()
  184. const data = customConfig()
  185. auditLogger.update(
  186. getAuditIdFromRes(res),
  187. new CustomConfigAuditView(data),
  188. oldCustomConfigAuditKeys
  189. )
  190. return res.json(data).end()
  191. }
  192. // ---------------------------------------------------------------------------
  193. export {
  194. configRouter
  195. }
  196. // ---------------------------------------------------------------------------
  197. function customConfig (): CustomConfig {
  198. return {
  199. instance: {
  200. name: CONFIG.INSTANCE.NAME,
  201. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  202. description: CONFIG.INSTANCE.DESCRIPTION,
  203. terms: CONFIG.INSTANCE.TERMS,
  204. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  205. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  206. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  207. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  208. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  209. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  210. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  211. languages: CONFIG.INSTANCE.LANGUAGES,
  212. categories: CONFIG.INSTANCE.CATEGORIES,
  213. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  214. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  215. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  216. customizations: {
  217. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
  218. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
  219. }
  220. },
  221. theme: {
  222. default: CONFIG.THEME.DEFAULT
  223. },
  224. services: {
  225. twitter: {
  226. username: CONFIG.SERVICES.TWITTER.USERNAME,
  227. whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
  228. }
  229. },
  230. cache: {
  231. previews: {
  232. size: CONFIG.CACHE.PREVIEWS.SIZE
  233. },
  234. captions: {
  235. size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
  236. }
  237. },
  238. signup: {
  239. enabled: CONFIG.SIGNUP.ENABLED,
  240. limit: CONFIG.SIGNUP.LIMIT,
  241. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  242. },
  243. admin: {
  244. email: CONFIG.ADMIN.EMAIL
  245. },
  246. contactForm: {
  247. enabled: CONFIG.CONTACT_FORM.ENABLED
  248. },
  249. user: {
  250. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  251. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  252. },
  253. transcoding: {
  254. enabled: CONFIG.TRANSCODING.ENABLED,
  255. allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
  256. allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
  257. threads: CONFIG.TRANSCODING.THREADS,
  258. resolutions: {
  259. '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
  260. '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ],
  261. '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
  262. '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
  263. '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ],
  264. '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ]
  265. },
  266. hls: {
  267. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  268. }
  269. },
  270. import: {
  271. videos: {
  272. http: {
  273. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  274. },
  275. torrent: {
  276. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  277. }
  278. }
  279. },
  280. autoBlacklist: {
  281. videos: {
  282. ofUsers: {
  283. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  284. }
  285. }
  286. },
  287. followers: {
  288. instance: {
  289. enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
  290. manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
  291. }
  292. },
  293. followings: {
  294. instance: {
  295. autoFollowBack: {
  296. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
  297. },
  298. autoFollowIndex: {
  299. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
  300. indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
  301. }
  302. }
  303. }
  304. }
  305. }
  306. function convertCustomConfigBody (body: CustomConfig) {
  307. function keyConverter (k: string) {
  308. // Transcoding resolutions exception
  309. if (/^\d{3,4}p$/.exec(k)) return k
  310. return snakeCase(k)
  311. }
  312. function valueConverter (v: any) {
  313. if (isNumeric(v + '')) return parseInt('' + v, 10)
  314. return v
  315. }
  316. return objectConverter(body, keyConverter, valueConverter)
  317. }
  318. function getRegisteredThemes () {
  319. return PluginManager.Instance.getRegisteredThemes()
  320. .map(t => ({
  321. name: t.name,
  322. version: t.version,
  323. description: t.description,
  324. css: t.css,
  325. clientScripts: t.clientScripts
  326. }))
  327. }
  328. function getEnabledResolutions () {
  329. return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
  330. .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[ key ] === true)
  331. .map(r => parseInt(r, 10))
  332. }
  333. function getRegisteredPlugins () {
  334. return PluginManager.Instance.getRegisteredPlugins()
  335. .map(p => ({
  336. name: p.name,
  337. version: p.version,
  338. description: p.description,
  339. clientScripts: p.clientScripts
  340. }))
  341. }