Fix CSP
[oweals/peertube.git] / server.ts
1 // FIXME: https://github.com/nodejs/node/pull/16853
2 import { VideosCaptionCache } from './server/lib/cache/videos-caption-cache'
3
4 require('tls').DEFAULT_ECDH_CURVE = 'auto'
5
6 import { isTestInstance } from './server/helpers/core-utils'
7
8 if (isTestInstance()) {
9   require('source-map-support').install()
10 }
11
12 // ----------- Node modules -----------
13 import * as bodyParser from 'body-parser'
14 import * as express from 'express'
15 import * as morgan from 'morgan'
16 import * as cors from 'cors'
17 import * as cookieParser from 'cookie-parser'
18 import * as helmet from 'helmet'
19
20 process.title = 'peertube'
21
22 // Create our main app
23 const app = express()
24
25 // ----------- Core checker -----------
26 import { checkMissedConfig, checkFFmpeg, checkConfig, checkActivityPubUrls } from './server/initializers/checker'
27
28 // Do not use barrels because we don't want to load all modules here (we need to initialize database first)
29 import { logger } from './server/helpers/logger'
30 import { API_VERSION, CONFIG, STATIC_PATHS, CACHE, REMOTE_SCHEME } from './server/initializers/constants'
31
32 const missed = checkMissedConfig()
33 if (missed.length !== 0) {
34   logger.error('Your configuration files miss keys: ' + missed)
35   process.exit(-1)
36 }
37
38 checkFFmpeg(CONFIG)
39   .catch(err => {
40     logger.error('Error in ffmpeg check.', { err })
41     process.exit(-1)
42   })
43
44 const errorMessage = checkConfig()
45 if (errorMessage !== null) {
46   throw new Error(errorMessage)
47 }
48
49 // Trust our proxy (IP forwarding...)
50 app.set('trust proxy', CONFIG.TRUST_PROXY)
51
52 // Security middlewares
53 app.use(helmet({
54   frameguard: {
55     action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
56   },
57   dnsPrefetchControl: {
58     allow: true
59   },
60   contentSecurityPolicy: {
61     directives: {
62       defaultSrc: ['*', 'data:', REMOTE_SCHEME.WS + ':', REMOTE_SCHEME.HTTP + ':'],
63       fontSrc: ["'self'", 'data:'],
64       frameSrc: ["'none'"],
65       mediaSrc: ['*', REMOTE_SCHEME.HTTP + ':'],
66       objectSrc: ["'none'"],
67       scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
68       styleSrc: ["'self'", "'unsafe-inline'"],
69       upgradeInsecureRequests: false
70     },
71     browserSniff: false // assumes a modern browser, but allows CDN in front
72   },
73   referrerPolicy: {
74     policy: 'strict-origin-when-cross-origin'
75   }
76 }))
77 app.use((_, res, next) => {
78   [
79     "vibrate 'none'",
80     "geolocation 'none'",
81     "camera 'none'",
82     "microphone 'none'",
83     "magnetometer 'none'",
84     "payment 'none'",
85     "accelerometer 'none'"
86   ].forEach(e => res.append('Feature-Policy', e + ';'))
87   next()
88 })
89
90 // ----------- Database -----------
91
92 // Initialize database and models
93 import { initDatabaseModels } from './server/initializers/database'
94 import { migrate } from './server/initializers/migrator'
95 migrate()
96   .then(() => initDatabaseModels(false))
97   .then(() => startApplication())
98   .catch(err => {
99     logger.error('Cannot start application.', { err })
100     process.exit(-1)
101   })
102
103 // ----------- PeerTube modules -----------
104 import { installApplication } from './server/initializers'
105 import { Emailer } from './server/lib/emailer'
106 import { JobQueue } from './server/lib/job-queue'
107 import { VideosPreviewCache } from './server/lib/cache'
108 import {
109   activityPubRouter,
110   apiRouter,
111   clientsRouter,
112   feedsRouter,
113   staticRouter,
114   servicesRouter,
115   webfingerRouter,
116   trackerRouter,
117   createWebsocketServer
118 } from './server/controllers'
119 import { Redis } from './server/lib/redis'
120 import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler'
121 import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
122 import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
123
124 // ----------- Command line -----------
125
126 // ----------- App -----------
127
128 // Enable CORS for develop
129 if (isTestInstance()) {
130   app.use(cors({
131     origin: '*',
132     exposedHeaders: 'Retry-After',
133     credentials: true
134   }))
135 }
136
137 // For the logger
138 app.use(morgan('combined', {
139   stream: { write: logger.info.bind(logger) }
140 }))
141 // For body requests
142 app.use(bodyParser.urlencoded({ extended: false }))
143 app.use(bodyParser.json({
144   type: [ 'application/json', 'application/*+json' ],
145   limit: '500kb'
146 }))
147 // Cookies
148 app.use(cookieParser())
149
150 // ----------- Views, routes and static files -----------
151
152 // API
153 const apiRoute = '/api/' + API_VERSION
154 app.use(apiRoute, apiRouter)
155
156 // Services (oembed...)
157 app.use('/services', servicesRouter)
158
159 app.use('/', activityPubRouter)
160 app.use('/', feedsRouter)
161 app.use('/', webfingerRouter)
162 app.use('/', trackerRouter)
163
164 // Static files
165 app.use('/', staticRouter)
166
167 // Client files, last valid routes!
168 app.use('/', clientsRouter)
169
170 // ----------- Errors -----------
171
172 // Catch 404 and forward to error handler
173 app.use(function (req, res, next) {
174   const err = new Error('Not Found')
175   err['status'] = 404
176   next(err)
177 })
178
179 app.use(function (err, req, res, next) {
180   let error = 'Unknown error.'
181   if (err) {
182     error = err.stack || err.message || err
183   }
184
185   logger.error('Error in controller.', { error })
186   return res.status(err.status || 500).end()
187 })
188
189 const server = createWebsocketServer(app)
190
191 // ----------- Run -----------
192
193 async function startApplication () {
194   const port = CONFIG.LISTEN.PORT
195   const hostname = CONFIG.LISTEN.HOSTNAME
196
197   await installApplication()
198
199   // Check activity pub urls are valid
200   checkActivityPubUrls()
201     .catch(err => {
202       logger.error('Error in ActivityPub URLs checker.', { err })
203       process.exit(-1)
204     })
205
206   // Email initialization
207   Emailer.Instance.init()
208   await Emailer.Instance.checkConnectionOrDie()
209
210   await JobQueue.Instance.init()
211
212   // Caches initializations
213   VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, CACHE.PREVIEWS.MAX_AGE)
214   VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, CACHE.VIDEO_CAPTIONS.MAX_AGE)
215
216   // Enable Schedulers
217   BadActorFollowScheduler.Instance.enable()
218   RemoveOldJobsScheduler.Instance.enable()
219   UpdateVideosScheduler.Instance.enable()
220
221   // Redis initialization
222   Redis.Instance.init()
223
224   // Make server listening
225   server.listen(port, hostname, () => {
226     logger.info('Server listening on %s:%d', hostname, port)
227     logger.info('Web server: %s', CONFIG.WEBSERVER.URL)
228   })
229 }