config.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. import { Hooks } from '@server/lib/plugins/hooks'
  2. import * as express from 'express'
  3. import { remove, writeJSON } from 'fs-extra'
  4. import { snakeCase } from 'lodash'
  5. import validator from 'validator'
  6. import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared'
  7. import { About } from '../../../shared/models/server/about.model'
  8. import { CustomConfig } from '../../../shared/models/server/custom-config.model'
  9. import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
  10. import { objectConverter } from '../../helpers/core-utils'
  11. import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
  12. import { getServerCommit } from '../../helpers/utils'
  13. import { getEnabledResolutions } from '../../lib/video-transcoding'
  14. import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
  15. import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
  16. import { ClientHtml } from '../../lib/client-html'
  17. import { PluginManager } from '../../lib/plugins/plugin-manager'
  18. import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
  19. import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
  20. import { customConfigUpdateValidator } from '../../middlewares/validators/config'
  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. getCustomConfig
  31. )
  32. configRouter.put('/custom',
  33. authenticate,
  34. ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
  35. 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. ip: req.ip
  49. },
  50. 'filter:api.user.signup.allowed.result'
  51. )
  52. const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
  53. const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
  54. if (serverCommit === undefined) serverCommit = await getServerCommit()
  55. const json: ServerConfig = {
  56. instance: {
  57. name: CONFIG.INSTANCE.NAME,
  58. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  59. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  60. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  61. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  62. customizations: {
  63. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
  64. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
  65. }
  66. },
  67. search: {
  68. remoteUri: {
  69. users: CONFIG.SEARCH.REMOTE_URI.USERS,
  70. anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
  71. },
  72. searchIndex: {
  73. enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
  74. url: CONFIG.SEARCH.SEARCH_INDEX.URL,
  75. disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
  76. isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
  77. }
  78. },
  79. plugin: {
  80. registered: getRegisteredPlugins(),
  81. registeredExternalAuths: getExternalAuthsPlugins(),
  82. registeredIdAndPassAuths: getIdAndPassAuthPlugins()
  83. },
  84. theme: {
  85. registered: getRegisteredThemes(),
  86. default: defaultTheme
  87. },
  88. email: {
  89. enabled: isEmailEnabled()
  90. },
  91. contactForm: {
  92. enabled: CONFIG.CONTACT_FORM.ENABLED
  93. },
  94. serverVersion: PEERTUBE_VERSION,
  95. serverCommit,
  96. signup: {
  97. allowed,
  98. allowedForCurrentIP,
  99. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  100. },
  101. transcoding: {
  102. hls: {
  103. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  104. },
  105. webtorrent: {
  106. enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
  107. },
  108. enabledResolutions: getEnabledResolutions('vod')
  109. },
  110. live: {
  111. enabled: CONFIG.LIVE.ENABLED,
  112. allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
  113. maxDuration: CONFIG.LIVE.MAX_DURATION,
  114. maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
  115. maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
  116. transcoding: {
  117. enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
  118. enabledResolutions: getEnabledResolutions('live')
  119. },
  120. rtmp: {
  121. port: CONFIG.LIVE.RTMP.PORT
  122. }
  123. },
  124. import: {
  125. videos: {
  126. http: {
  127. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  128. },
  129. torrent: {
  130. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  131. }
  132. }
  133. },
  134. autoBlacklist: {
  135. videos: {
  136. ofUsers: {
  137. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  138. }
  139. }
  140. },
  141. avatar: {
  142. file: {
  143. size: {
  144. max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
  145. },
  146. extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
  147. }
  148. },
  149. video: {
  150. image: {
  151. extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
  152. size: {
  153. max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
  154. }
  155. },
  156. file: {
  157. extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
  158. }
  159. },
  160. videoCaption: {
  161. file: {
  162. size: {
  163. max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
  164. },
  165. extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
  166. }
  167. },
  168. user: {
  169. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  170. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  171. },
  172. trending: {
  173. videos: {
  174. intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
  175. }
  176. },
  177. tracker: {
  178. enabled: CONFIG.TRACKER.ENABLED
  179. },
  180. followings: {
  181. instance: {
  182. autoFollowIndex: {
  183. indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
  184. }
  185. }
  186. },
  187. broadcastMessage: {
  188. enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
  189. message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
  190. level: CONFIG.BROADCAST_MESSAGE.LEVEL,
  191. dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
  192. }
  193. }
  194. return res.json(json)
  195. }
  196. function getAbout (req: express.Request, res: express.Response) {
  197. const about: About = {
  198. instance: {
  199. name: CONFIG.INSTANCE.NAME,
  200. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  201. description: CONFIG.INSTANCE.DESCRIPTION,
  202. terms: CONFIG.INSTANCE.TERMS,
  203. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  204. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  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. languages: CONFIG.INSTANCE.LANGUAGES,
  211. categories: CONFIG.INSTANCE.CATEGORIES
  212. }
  213. }
  214. return res.json(about).end()
  215. }
  216. function getCustomConfig (req: express.Request, res: express.Response) {
  217. const data = customConfig()
  218. return res.json(data).end()
  219. }
  220. async function deleteCustomConfig (req: express.Request, res: express.Response) {
  221. await remove(CONFIG.CUSTOM_FILE)
  222. auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
  223. reloadConfig()
  224. ClientHtml.invalidCache()
  225. const data = customConfig()
  226. return res.json(data)
  227. }
  228. async function updateCustomConfig (req: express.Request, res: express.Response) {
  229. const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
  230. // camelCase to snake_case key + Force number conversion
  231. const toUpdateJSON = convertCustomConfigBody(req.body)
  232. await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
  233. reloadConfig()
  234. ClientHtml.invalidCache()
  235. const data = customConfig()
  236. auditLogger.update(
  237. getAuditIdFromRes(res),
  238. new CustomConfigAuditView(data),
  239. oldCustomConfigAuditKeys
  240. )
  241. return res.json(data)
  242. }
  243. function getRegisteredThemes () {
  244. return PluginManager.Instance.getRegisteredThemes()
  245. .map(t => ({
  246. name: t.name,
  247. version: t.version,
  248. description: t.description,
  249. css: t.css,
  250. clientScripts: t.clientScripts
  251. }))
  252. }
  253. function getRegisteredPlugins () {
  254. return PluginManager.Instance.getRegisteredPlugins()
  255. .map(p => ({
  256. name: p.name,
  257. version: p.version,
  258. description: p.description,
  259. clientScripts: p.clientScripts
  260. }))
  261. }
  262. function getIdAndPassAuthPlugins () {
  263. const result: RegisteredIdAndPassAuthConfig[] = []
  264. for (const p of PluginManager.Instance.getIdAndPassAuths()) {
  265. for (const auth of p.idAndPassAuths) {
  266. result.push({
  267. npmName: p.npmName,
  268. name: p.name,
  269. version: p.version,
  270. authName: auth.authName,
  271. weight: auth.getWeight()
  272. })
  273. }
  274. }
  275. return result
  276. }
  277. function getExternalAuthsPlugins () {
  278. const result: RegisteredExternalAuthConfig[] = []
  279. for (const p of PluginManager.Instance.getExternalAuths()) {
  280. for (const auth of p.externalAuths) {
  281. result.push({
  282. npmName: p.npmName,
  283. name: p.name,
  284. version: p.version,
  285. authName: auth.authName,
  286. authDisplayName: auth.authDisplayName()
  287. })
  288. }
  289. }
  290. return result
  291. }
  292. // ---------------------------------------------------------------------------
  293. export {
  294. configRouter,
  295. getRegisteredPlugins,
  296. getRegisteredThemes
  297. }
  298. // ---------------------------------------------------------------------------
  299. function customConfig (): CustomConfig {
  300. return {
  301. instance: {
  302. name: CONFIG.INSTANCE.NAME,
  303. shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
  304. description: CONFIG.INSTANCE.DESCRIPTION,
  305. terms: CONFIG.INSTANCE.TERMS,
  306. codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
  307. creationReason: CONFIG.INSTANCE.CREATION_REASON,
  308. moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
  309. administrator: CONFIG.INSTANCE.ADMINISTRATOR,
  310. maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
  311. businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
  312. hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
  313. languages: CONFIG.INSTANCE.LANGUAGES,
  314. categories: CONFIG.INSTANCE.CATEGORIES,
  315. isNSFW: CONFIG.INSTANCE.IS_NSFW,
  316. defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
  317. defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
  318. customizations: {
  319. css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
  320. javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
  321. }
  322. },
  323. theme: {
  324. default: CONFIG.THEME.DEFAULT
  325. },
  326. services: {
  327. twitter: {
  328. username: CONFIG.SERVICES.TWITTER.USERNAME,
  329. whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
  330. }
  331. },
  332. cache: {
  333. previews: {
  334. size: CONFIG.CACHE.PREVIEWS.SIZE
  335. },
  336. captions: {
  337. size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
  338. }
  339. },
  340. signup: {
  341. enabled: CONFIG.SIGNUP.ENABLED,
  342. limit: CONFIG.SIGNUP.LIMIT,
  343. requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
  344. },
  345. admin: {
  346. email: CONFIG.ADMIN.EMAIL
  347. },
  348. contactForm: {
  349. enabled: CONFIG.CONTACT_FORM.ENABLED
  350. },
  351. user: {
  352. videoQuota: CONFIG.USER.VIDEO_QUOTA,
  353. videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
  354. },
  355. transcoding: {
  356. enabled: CONFIG.TRANSCODING.ENABLED,
  357. allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
  358. allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
  359. threads: CONFIG.TRANSCODING.THREADS,
  360. resolutions: {
  361. '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
  362. '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
  363. '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
  364. '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
  365. '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
  366. '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
  367. '1440p': CONFIG.TRANSCODING.RESOLUTIONS['1440p'],
  368. '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
  369. },
  370. webtorrent: {
  371. enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
  372. },
  373. hls: {
  374. enabled: CONFIG.TRANSCODING.HLS.ENABLED
  375. }
  376. },
  377. live: {
  378. enabled: CONFIG.LIVE.ENABLED,
  379. allowReplay: CONFIG.LIVE.ALLOW_REPLAY,
  380. maxDuration: CONFIG.LIVE.MAX_DURATION,
  381. maxInstanceLives: CONFIG.LIVE.MAX_INSTANCE_LIVES,
  382. maxUserLives: CONFIG.LIVE.MAX_USER_LIVES,
  383. transcoding: {
  384. enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
  385. threads: CONFIG.LIVE.TRANSCODING.THREADS,
  386. resolutions: {
  387. '240p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['240p'],
  388. '360p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['360p'],
  389. '480p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['480p'],
  390. '720p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['720p'],
  391. '1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
  392. '1440p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1440p'],
  393. '2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
  394. }
  395. }
  396. },
  397. import: {
  398. videos: {
  399. http: {
  400. enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
  401. },
  402. torrent: {
  403. enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
  404. }
  405. }
  406. },
  407. autoBlacklist: {
  408. videos: {
  409. ofUsers: {
  410. enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
  411. }
  412. }
  413. },
  414. followers: {
  415. instance: {
  416. enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
  417. manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
  418. }
  419. },
  420. followings: {
  421. instance: {
  422. autoFollowBack: {
  423. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
  424. },
  425. autoFollowIndex: {
  426. enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
  427. indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
  428. }
  429. }
  430. },
  431. broadcastMessage: {
  432. enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
  433. message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
  434. level: CONFIG.BROADCAST_MESSAGE.LEVEL,
  435. dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
  436. },
  437. search: {
  438. remoteUri: {
  439. users: CONFIG.SEARCH.REMOTE_URI.USERS,
  440. anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
  441. },
  442. searchIndex: {
  443. enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
  444. url: CONFIG.SEARCH.SEARCH_INDEX.URL,
  445. disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
  446. isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
  447. }
  448. }
  449. }
  450. }
  451. function convertCustomConfigBody (body: CustomConfig) {
  452. function keyConverter (k: string) {
  453. // Transcoding resolutions exception
  454. if (/^\d{3,4}p$/.exec(k)) return k
  455. if (k === '0p') return k
  456. return snakeCase(k)
  457. }
  458. function valueConverter (v: any) {
  459. if (validator.isNumeric(v + '')) return parseInt('' + v, 10)
  460. return v
  461. }
  462. return objectConverter(body, keyConverter, valueConverter)
  463. }