config.ts 12 KB

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