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 { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
14 import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
15 import { ClientHtml } from '../../lib/client-html'
16 import { PluginManager } from '../../lib/plugins/plugin-manager'
17 import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
18 import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
19 import { customConfigUpdateValidator } from '../../middlewares/validators/config'
21 const configRouter = express.Router()
23 const auditLogger = auditLoggerFactory('config')
25 configRouter.get('/about', getAbout)
27 asyncMiddleware(getConfig)
30 configRouter.get('/custom',
32 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
35 configRouter.put('/custom',
37 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
38 customConfigUpdateValidator,
39 asyncMiddleware(updateCustomConfig)
41 configRouter.delete('/custom',
43 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
44 asyncMiddleware(deleteCustomConfig)
47 let serverCommit: string
49 async function getConfig (req: express.Request, res: express.Response) {
50 const { allowed } = await Hooks.wrapPromiseFun(
55 'filter:api.user.signup.allowed.result'
58 const allowedForCurrentIP = isSignupAllowedForCurrentIP(req.ip)
59 const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
61 if (serverCommit === undefined) serverCommit = await getServerCommit()
63 const json: ServerConfig = {
65 name: CONFIG.INSTANCE.NAME,
66 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
67 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
68 isNSFW: CONFIG.INSTANCE.IS_NSFW,
69 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
71 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
72 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
77 users: CONFIG.SEARCH.REMOTE_URI.USERS,
78 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
81 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
82 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
83 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
84 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
88 registered: getRegisteredPlugins(),
89 registeredExternalAuths: getExternalAuthsPlugins(),
90 registeredIdAndPassAuths: getIdAndPassAuthPlugins()
93 registered: getRegisteredThemes(),
97 enabled: isEmailEnabled()
100 enabled: CONFIG.CONTACT_FORM.ENABLED
102 serverVersion: PEERTUBE_VERSION,
107 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
111 enabled: CONFIG.TRANSCODING.HLS.ENABLED
114 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
116 enabledResolutions: getEnabledResolutions('vod')
119 enabled: CONFIG.LIVE.ENABLED,
122 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
123 enabledResolutions: getEnabledResolutions('live')
129 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
132 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
139 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
146 max: CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max
148 extensions: CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME
153 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
155 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
159 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
165 max: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max
167 extensions: CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.EXTNAME
171 videoQuota: CONFIG.USER.VIDEO_QUOTA,
172 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
176 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
180 enabled: CONFIG.TRACKER.ENABLED
186 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
192 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
193 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
194 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
195 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
199 return res.json(json)
202 function getAbout (req: express.Request, res: express.Response) {
203 const about: About = {
205 name: CONFIG.INSTANCE.NAME,
206 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
207 description: CONFIG.INSTANCE.DESCRIPTION,
208 terms: CONFIG.INSTANCE.TERMS,
209 codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
211 hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
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,
219 languages: CONFIG.INSTANCE.LANGUAGES,
220 categories: CONFIG.INSTANCE.CATEGORIES
224 return res.json(about).end()
227 function getCustomConfig (req: express.Request, res: express.Response) {
228 const data = customConfig()
230 return res.json(data).end()
233 async function deleteCustomConfig (req: express.Request, res: express.Response) {
234 await remove(CONFIG.CUSTOM_FILE)
236 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
239 ClientHtml.invalidCache()
241 const data = customConfig()
243 return res.json(data)
246 async function updateCustomConfig (req: express.Request, res: express.Response) {
247 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
249 // camelCase to snake_case key + Force number conversion
250 const toUpdateJSON = convertCustomConfigBody(req.body)
252 await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
255 ClientHtml.invalidCache()
257 const data = customConfig()
260 getAuditIdFromRes(res),
261 new CustomConfigAuditView(data),
262 oldCustomConfigAuditKeys
265 return res.json(data)
268 function getRegisteredThemes () {
269 return PluginManager.Instance.getRegisteredThemes()
273 description: t.description,
275 clientScripts: t.clientScripts
279 function getEnabledResolutions (type: 'vod' | 'live') {
280 const transcoding = type === 'vod'
282 : CONFIG.LIVE.TRANSCODING
284 return Object.keys(transcoding.RESOLUTIONS)
285 .filter(key => transcoding.ENABLED && transcoding.RESOLUTIONS[key] === true)
286 .map(r => parseInt(r, 10))
289 function getRegisteredPlugins () {
290 return PluginManager.Instance.getRegisteredPlugins()
294 description: p.description,
295 clientScripts: p.clientScripts
299 function getIdAndPassAuthPlugins () {
300 const result: RegisteredIdAndPassAuthConfig[] = []
302 for (const p of PluginManager.Instance.getIdAndPassAuths()) {
303 for (const auth of p.idAndPassAuths) {
308 authName: auth.authName,
309 weight: auth.getWeight()
317 function getExternalAuthsPlugins () {
318 const result: RegisteredExternalAuthConfig[] = []
320 for (const p of PluginManager.Instance.getExternalAuths()) {
321 for (const auth of p.externalAuths) {
326 authName: auth.authName,
327 authDisplayName: auth.authDisplayName()
335 // ---------------------------------------------------------------------------
339 getEnabledResolutions,
340 getRegisteredPlugins,
344 // ---------------------------------------------------------------------------
346 function customConfig (): CustomConfig {
349 name: CONFIG.INSTANCE.NAME,
350 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
351 description: CONFIG.INSTANCE.DESCRIPTION,
352 terms: CONFIG.INSTANCE.TERMS,
353 codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT,
355 creationReason: CONFIG.INSTANCE.CREATION_REASON,
356 moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION,
357 administrator: CONFIG.INSTANCE.ADMINISTRATOR,
358 maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME,
359 businessModel: CONFIG.INSTANCE.BUSINESS_MODEL,
360 hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION,
362 languages: CONFIG.INSTANCE.LANGUAGES,
363 categories: CONFIG.INSTANCE.CATEGORIES,
365 isNSFW: CONFIG.INSTANCE.IS_NSFW,
366 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
367 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
369 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
370 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
374 default: CONFIG.THEME.DEFAULT
378 username: CONFIG.SERVICES.TWITTER.USERNAME,
379 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
384 size: CONFIG.CACHE.PREVIEWS.SIZE
387 size: CONFIG.CACHE.VIDEO_CAPTIONS.SIZE
391 enabled: CONFIG.SIGNUP.ENABLED,
392 limit: CONFIG.SIGNUP.LIMIT,
393 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
396 email: CONFIG.ADMIN.EMAIL
399 enabled: CONFIG.CONTACT_FORM.ENABLED
402 videoQuota: CONFIG.USER.VIDEO_QUOTA,
403 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
406 enabled: CONFIG.TRANSCODING.ENABLED,
407 allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
408 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
409 threads: CONFIG.TRANSCODING.THREADS,
411 '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
412 '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
413 '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
414 '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
415 '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
416 '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
417 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
420 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
423 enabled: CONFIG.TRANSCODING.HLS.ENABLED
427 enabled: CONFIG.LIVE.ENABLED,
429 enabled: CONFIG.LIVE.TRANSCODING.ENABLED,
430 threads: CONFIG.LIVE.TRANSCODING.THREADS,
432 '240p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['240p'],
433 '360p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['360p'],
434 '480p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['480p'],
435 '720p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['720p'],
436 '1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
437 '2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
444 enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
447 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
454 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
460 enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
461 manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
467 enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED
471 enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED,
472 indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL
477 enabled: CONFIG.BROADCAST_MESSAGE.ENABLED,
478 message: CONFIG.BROADCAST_MESSAGE.MESSAGE,
479 level: CONFIG.BROADCAST_MESSAGE.LEVEL,
480 dismissable: CONFIG.BROADCAST_MESSAGE.DISMISSABLE
484 users: CONFIG.SEARCH.REMOTE_URI.USERS,
485 anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS
488 enabled: CONFIG.SEARCH.SEARCH_INDEX.ENABLED,
489 url: CONFIG.SEARCH.SEARCH_INDEX.URL,
490 disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH,
491 isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH
497 function convertCustomConfigBody (body: CustomConfig) {
498 function keyConverter (k: string) {
499 // Transcoding resolutions exception
500 if (/^\d{3,4}p$/.exec(k)) return k
501 if (k === '0p') return k
506 function valueConverter (v: any) {
507 if (validator.isNumeric(v + '')) return parseInt('' + v, 10)
512 return objectConverter(body, keyConverter, valueConverter)