config.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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 validator 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. function getRegisteredThemes () {
  201. return PluginManager.Instance.getRegisteredThemes()
  202. .map(t => ({
  203. name: t.name,
  204. version: t.version,
  205. description: t.description,
  206. css: t.css,
  207. clientScripts: t.clientScripts
  208. }))
  209. }
  210. function getEnabledResolutions () {
  211. return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
  212. .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[ key ] === true)
  213. .map(r => parseInt(r, 10))
  214. }
  215. function getRegisteredPlugins () {
  216. return PluginManager.Instance.getRegisteredPlugins()
  217. .map(p => ({
  218. name: p.name,
  219. version: p.version,
  220. description: p.description,
  221. clientScripts: p.clientScripts
  222. }))
  223. }
  224. // ---------------------------------------------------------------------------
  225. export {
  226. configRouter,
  227. getEnabledResolutions,
  228. getRegisteredPlugins,
  229. getRegisteredThemes
  230. }
  231. // ---------------------------------------------------------------------------
  232. function customConfig (): CustomConfig {
  233. return {
  234. instance: {
  235. name: CONFIG.INSTANCE.NAME,
  236. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  237. description: CONFIG.INSTANCE.DESCRIPTION,
  238. terms: CONFIG.INSTANCE.TERMS,
  239. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  240. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  241. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  242. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  243. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  244. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  245. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  246. languages: CONFIG.INSTANCE.LANGUAGES,
  247. categories: CONFIG.INSTANCE.CATEGORIES,
  248. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  249. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  250. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  251. customizations: {
  252. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
  253. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
  254. }
  255. },
  256. theme: {
  257. default: CONFIG.THEME.DEFAULT
  258. },
  259. services: {
  260. twitter: {
  261. username: CONFIG.SERVICES.TWITTER.USERNAME,
  262. whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
  263. }
  264. },
  265. cache: {
  266. previews: {
  267. size: CONFIG.CACHE.PREVIEWS.SIZE
  268. },
  269. captions: {
  270. size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
  271. }
  272. },
  273. signup: {
  274. enabled: CONFIG.SIGNUP.ENABLED,
  275. limit: CONFIG.SIGNUP.LIMIT,
  276. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  277. },
  278. admin: {
  279. email: CONFIG.ADMIN.EMAIL
  280. },
  281. contactForm: {
  282. enabled: CONFIG.CONTACT_FORM.ENABLED
  283. },
  284. user: {
  285. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  286. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  287. },
  288. transcoding: {
  289. enabled: CONFIG.TRANSCODING.ENABLED,
  290. allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
  291. allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
  292. threads: CONFIG.TRANSCODING.THREADS,
  293. resolutions: {
  294. '0p': CONFIG.TRANSCODING.RESOLUTIONS[ '0p' ],
  295. '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
  296. '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ],
  297. '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
  298. '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
  299. '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ],
  300. '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ]
  301. },
  302. webtorrent: {
  303. enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
  304. },
  305. hls: {
  306. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  307. }
  308. },
  309. import: {
  310. videos: {
  311. http: {
  312. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  313. },
  314. torrent: {
  315. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  316. }
  317. }
  318. },
  319. autoBlacklist: {
  320. videos: {
  321. ofUsers: {
  322. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  323. }
  324. }
  325. },
  326. followers: {
  327. instance: {
  328. enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
  329. manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
  330. }
  331. },
  332. followings: {
  333. instance: {
  334. autoFollowBack: {
  335. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
  336. },
  337. autoFollowIndex: {
  338. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
  339. indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
  340. }
  341. }
  342. }
  343. }
  344. }
  345. function convertCustomConfigBody (body: CustomConfig) {
  346. function keyConverter (k: string) {
  347. // Transcoding resolutions exception
  348. if (/^\d{3,4}p$/.exec(k)) return k
  349. if (k === '0p') return k
  350. return snakeCase(k)
  351. }
  352. function valueConverter (v: any) {
  353. if (validator.isNumeric(v + '')) return parseInt('' + v, 10)
  354. return v
  355. }
  356. return objectConverter(body, keyConverter, valueConverter)
  357. }