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