]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server.ts
Fix CSP
[github/Chocobozzz/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 }