|
1 | 1 | 'use strict' |
2 | | -// app |
3 | | -// external modules |
4 | | -var express = require('express') |
5 | 2 |
|
6 | | -var ejs = require('ejs') |
7 | | -var passport = require('passport') |
8 | | -var methodOverride = require('method-override') |
9 | | -var cookieParser = require('cookie-parser') |
10 | | -var session = require('express-session') |
11 | | -var SequelizeStore = require('connect-session-sequelize')(session.Store) |
12 | | -var fs = require('fs') |
13 | | -var path = require('path') |
14 | | - |
15 | | -var morgan = require('morgan') |
16 | | -var passportSocketIo = require('passport.socketio') |
17 | | -var helmet = require('helmet') |
18 | | -var i18n = require('i18n') |
19 | | -var flash = require('connect-flash') |
20 | | -var apiMetrics = require('prometheus-api-metrics') |
21 | | - |
22 | | -// core |
23 | | -var config = require('./dist/config') |
24 | | -var logger = require('./dist/logger') |
25 | | -var response = require('./dist/response') |
26 | | -var models = require('./dist/models') |
27 | | -var csp = require('./dist/csp') |
28 | | -const { Environment } = require('./dist/config/enum') |
29 | | - |
30 | | -const { versionCheckMiddleware, checkVersion } = require('./dist/web/middleware/checkVersion') |
31 | | - |
32 | | -function createHttpServer () { |
33 | | - if (config.useSSL) { |
34 | | - const ca = (function () { |
35 | | - let i, len |
36 | | - const results = [] |
37 | | - for (i = 0, len = config.sslCAPath.length; i < len; i++) { |
38 | | - results.push(fs.readFileSync(config.sslCAPath[i], 'utf8')) |
39 | | - } |
40 | | - return results |
41 | | - })() |
42 | | - const options = { |
43 | | - key: fs.readFileSync(config.sslKeyPath, 'utf8'), |
44 | | - cert: fs.readFileSync(config.sslCertPath, 'utf8'), |
45 | | - ca: ca, |
46 | | - dhparam: fs.readFileSync(config.dhParamPath, 'utf8'), |
47 | | - requestCert: false, |
48 | | - rejectUnauthorized: false |
49 | | - } |
50 | | - return require('https').createServer(options, app) |
51 | | - } else { |
52 | | - return require('http').createServer(app) |
53 | | - } |
54 | | -} |
55 | | - |
56 | | -// server setup |
57 | | -var app = express() |
58 | | -var server = createHttpServer() |
59 | | - |
60 | | -// API and process monitoring with Prometheus for Node.js micro-service |
61 | | -app.use(apiMetrics({ |
62 | | - metricsPath: '/metrics/router', |
63 | | - excludeRoutes: ['/metrics/codimd'] |
64 | | -})) |
65 | | - |
66 | | -// logger |
67 | | -app.use(morgan('combined', { |
68 | | - stream: logger.stream |
69 | | -})) |
70 | | - |
71 | | -// socket io |
72 | | -var io = require('socket.io')(server) |
73 | | -io.engine.ws = new (require('ws').Server)({ |
74 | | - noServer: true, |
75 | | - perMessageDeflate: false |
76 | | -}) |
77 | | - |
78 | | -// others |
79 | | -var realtime = require('./dist/realtime/realtime.js') |
80 | | - |
81 | | -// assign socket io to realtime |
82 | | -realtime.io = io |
83 | | - |
84 | | -// methodOverride |
85 | | -app.use(methodOverride('_method')) |
86 | | - |
87 | | -// session store |
88 | | -var sessionStore = new SequelizeStore({ |
89 | | - db: models.sequelize |
90 | | -}) |
91 | | - |
92 | | -// use hsts to tell https users stick to this |
93 | | -if (config.hsts.enable) { |
94 | | - app.use(helmet.hsts({ |
95 | | - maxAge: config.hsts.maxAgeSeconds, |
96 | | - includeSubdomains: config.hsts.includeSubdomains, |
97 | | - preload: config.hsts.preload |
98 | | - })) |
99 | | -} else if (config.useSSL) { |
100 | | - logger.info('Consider enabling HSTS for extra security:') |
101 | | - logger.info('https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security') |
102 | | -} |
103 | | - |
104 | | -// Add referrer policy to improve privacy |
105 | | -app.use( |
106 | | - helmet.referrerPolicy({ |
107 | | - policy: 'same-origin' |
108 | | - }) |
109 | | -) |
110 | | - |
111 | | -// Generate a random nonce per request, for CSP with inline scripts |
112 | | -app.use(csp.addNonceToLocals) |
113 | | - |
114 | | -// use Content-Security-Policy to limit XSS, dangerous plugins, etc. |
115 | | -// https://helmetjs.github.io/docs/csp/ |
116 | | -if (config.csp.enable) { |
117 | | - app.use(helmet.contentSecurityPolicy({ |
118 | | - directives: csp.computeDirectives() |
119 | | - })) |
120 | | -} else { |
121 | | - logger.info('Content-Security-Policy is disabled. This may be a security risk.') |
122 | | -} |
123 | | - |
124 | | -i18n.configure({ |
125 | | - locales: ['en', 'zh-CN', 'zh-TW', 'fr', 'de', 'ja', 'es', 'ca', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo', 'da', 'ko', 'id', 'sr'], |
126 | | - cookie: 'locale', |
127 | | - directory: path.join(__dirname, '/locales'), |
128 | | - updateFiles: config.updateI18nFiles |
129 | | -}) |
130 | | - |
131 | | -app.use(cookieParser()) |
132 | | - |
133 | | -app.use(i18n.init) |
134 | | - |
135 | | -// routes without sessions |
136 | | -// static files |
137 | | -app.use('/', express.static(path.join(__dirname, '/public'), { maxAge: config.staticCacheTime, index: false })) |
138 | | -app.use('/docs', express.static(path.resolve(__dirname, config.docsPath), { maxAge: config.staticCacheTime })) |
139 | | -app.use('/uploads', express.static(path.resolve(__dirname, config.uploadsPath), { maxAge: config.staticCacheTime })) |
140 | | -app.use('/default.md', express.static(path.resolve(__dirname, config.defaultNotePath), { maxAge: config.staticCacheTime })) |
141 | | -app.use(require('./dist/metrics').router) |
142 | | - |
143 | | -// session |
144 | | -app.use(session({ |
145 | | - name: config.sessionName, |
146 | | - secret: config.sessionSecret, |
147 | | - resave: false, // don't save session if unmodified |
148 | | - saveUninitialized: true, // always create session to ensure the origin |
149 | | - rolling: true, // reset maxAge on every response |
150 | | - cookie: { |
151 | | - maxAge: config.sessionLife |
152 | | - }, |
153 | | - store: sessionStore |
154 | | -})) |
155 | | - |
156 | | -// session resumption |
157 | | -var tlsSessionStore = {} |
158 | | -server.on('newSession', function (id, data, cb) { |
159 | | - tlsSessionStore[id.toString('hex')] = data |
160 | | - cb() |
161 | | -}) |
162 | | -server.on('resumeSession', function (id, cb) { |
163 | | - cb(null, tlsSessionStore[id.toString('hex')] || null) |
164 | | -}) |
165 | | - |
166 | | -// middleware which blocks requests when we're too busy |
167 | | -app.use(require('./dist/middleware/tooBusy')) |
168 | | - |
169 | | -app.use(flash()) |
170 | | - |
171 | | -// passport |
172 | | -app.use(passport.initialize()) |
173 | | -app.use(passport.session()) |
174 | | - |
175 | | -// check uri is valid before going further |
176 | | -app.use(require('./dist/middleware/checkURIValid')) |
177 | | -// redirect url without trailing slashes |
178 | | -app.use(require('./dist/middleware/redirectWithoutTrailingSlashes')) |
179 | | -app.use(require('./dist/middleware/codiMDVersion')) |
180 | | - |
181 | | -if (config.autoVersionCheck && process.env.NODE_ENV === Environment.production) { |
182 | | - checkVersion(app) |
183 | | - app.use(versionCheckMiddleware) |
184 | | -} |
185 | | - |
186 | | -// routes need sessions |
187 | | -// template files |
188 | | -app.set('views', config.viewPath) |
189 | | -// set render engine |
190 | | -app.engine('ejs', ejs.renderFile) |
191 | | -// set view engine |
192 | | -app.set('view engine', 'ejs') |
193 | | -// set generally available variables for all views |
194 | | -app.locals.useCDN = config.useCDN |
195 | | -app.locals.serverURL = config.serverURL |
196 | | -app.locals.sourceURL = config.sourceURL |
197 | | -app.locals.privacyPolicyURL = config.privacyPolicyURL |
198 | | -app.locals.allowAnonymous = config.allowAnonymous |
199 | | -app.locals.allowAnonymousEdits = config.allowAnonymousEdits |
200 | | -app.locals.permission = config.permission |
201 | | -app.locals.allowPDFExport = config.allowPDFExport |
202 | | -app.locals.authProviders = { |
203 | | - facebook: config.isFacebookEnable, |
204 | | - twitter: config.isTwitterEnable, |
205 | | - github: config.isGitHubEnable, |
206 | | - bitbucket: config.isBitbucketEnable, |
207 | | - gitlab: config.isGitLabEnable, |
208 | | - mattermost: config.isMattermostEnable, |
209 | | - dropbox: config.isDropboxEnable, |
210 | | - google: config.isGoogleEnable, |
211 | | - ldap: config.isLDAPEnable, |
212 | | - ldapProviderName: config.ldap.providerName, |
213 | | - saml: config.isSAMLEnable, |
214 | | - oauth2: config.isOAuth2Enable, |
215 | | - oauth2ProviderName: config.oauth2.providerName, |
216 | | - openID: config.isOpenIDEnable, |
217 | | - email: config.isEmailEnable, |
218 | | - allowEmailRegister: config.allowEmailRegister |
219 | | -} |
220 | | -app.locals.versionInfo = { |
221 | | - latest: true, |
222 | | - versionItem: null |
223 | | -} |
224 | | - |
225 | | -// Export/Import menu items |
226 | | -app.locals.enableDropBoxSave = config.isDropboxEnable |
227 | | -app.locals.enableGitHubGist = config.isGitHubEnable |
228 | | -app.locals.enableGitlabSnippets = config.isGitlabSnippetsEnable |
229 | | - |
230 | | -app.use(require('./dist/routes').router) |
231 | | - |
232 | | -// response not found if no any route matxches |
233 | | -app.get('*', function (req, res) { |
234 | | - response.errorNotFound(req, res) |
235 | | -}) |
236 | | - |
237 | | -// socket.io secure |
238 | | -io.use(realtime.secure) |
239 | | -// socket.io auth |
240 | | -io.use(passportSocketIo.authorize({ |
241 | | - cookieParser: cookieParser, |
242 | | - key: config.sessionName, |
243 | | - secret: config.sessionSecret, |
244 | | - store: sessionStore, |
245 | | - success: realtime.onAuthorizeSuccess, |
246 | | - fail: realtime.onAuthorizeFail |
247 | | -})) |
248 | | -// socket.io heartbeat |
249 | | -io.set('heartbeat interval', config.heartbeatInterval) |
250 | | -io.set('heartbeat timeout', config.heartbeatTimeout) |
251 | | -// socket.io connection |
252 | | -io.sockets.on('connection', realtime.connection) |
253 | | - |
254 | | -// listen |
255 | | -function startListen () { |
256 | | - var address |
257 | | - var listenCallback = function () { |
258 | | - var schema = config.useSSL ? 'HTTPS' : 'HTTP' |
259 | | - logger.info('%s Server listening at %s', schema, address) |
260 | | - realtime.maintenance = false |
261 | | - } |
262 | | - |
263 | | - // use unix domain socket if 'path' is specified |
264 | | - if (config.path) { |
265 | | - address = config.path |
266 | | - server.listen(config.path, listenCallback) |
267 | | - } else { |
268 | | - address = config.host + ':' + config.port |
269 | | - server.listen(config.port, config.host, listenCallback) |
270 | | - } |
271 | | -} |
272 | | - |
273 | | -// sync db then start listen |
274 | | -models.sequelize.sync().then(function () { |
275 | | - // check if realtime is ready |
276 | | - if (realtime.isReady()) { |
277 | | - models.Revision.checkAllNotesRevision(function (err, notes) { |
278 | | - if (err) throw new Error(err) |
279 | | - if (!notes || notes.length <= 0) return startListen() |
280 | | - }) |
281 | | - } else { |
282 | | - throw new Error('server still not ready after db synced') |
283 | | - } |
284 | | -}).catch(err => { |
285 | | - logger.error('Can\'t sync database') |
286 | | - logger.error(err.stack) |
287 | | - logger.error('Process will exit now.') |
288 | | - process.exit(1) |
289 | | -}) |
290 | | - |
291 | | -// log uncaught exception |
292 | | -process.on('uncaughtException', function (err) { |
293 | | - logger.error('An uncaught exception has occured.') |
294 | | - logger.error(err) |
295 | | - console.error(err) |
296 | | - logger.error('Process will exit now.') |
297 | | - process.exit(1) |
298 | | -}) |
299 | | - |
300 | | -// install exit handler |
301 | | -function handleTermSignals () { |
302 | | - logger.info('CodiMD has been killed by signal, try to exit gracefully...') |
303 | | - realtime.maintenance = true |
304 | | - realtime.terminate() |
305 | | - // disconnect all socket.io clients |
306 | | - Object.keys(io.sockets.sockets).forEach(function (key) { |
307 | | - var socket = io.sockets.sockets[key] |
308 | | - // notify client server going into maintenance status |
309 | | - socket.emit('maintenance') |
310 | | - setTimeout(function () { |
311 | | - socket.disconnect(true) |
312 | | - }, 0) |
313 | | - }) |
314 | | - var checkCleanTimer = setInterval(function () { |
315 | | - if (realtime.isReady()) { |
316 | | - models.Revision.checkAllNotesRevision(function (err, notes) { |
317 | | - if (err) return logger.error(err) |
318 | | - if (!notes || notes.length <= 0) { |
319 | | - clearInterval(checkCleanTimer) |
320 | | - return process.exit(0) |
321 | | - } |
322 | | - }) |
323 | | - } |
324 | | - }, 100) |
325 | | - setTimeout(() => { |
326 | | - process.exit(1) |
327 | | - }, 5000) |
328 | | -} |
329 | | -process.on('SIGINT', handleTermSignals) |
330 | | -process.on('SIGTERM', handleTermSignals) |
331 | | -process.on('SIGQUIT', handleTermSignals) |
| 3 | +require('./dist/app.js') |
0 commit comments