2020-12-08 03:20:59 -05:00
|
|
|
|
'use strict';
|
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
import _ from 'underscore';
|
|
|
|
|
import cookieParser from 'cookie-parser';
|
|
|
|
|
import events from "events";
|
|
|
|
|
|
|
|
|
|
import express from "express";
|
|
|
|
|
|
|
|
|
|
import fs from "fs";
|
|
|
|
|
|
|
|
|
|
import expressSession from "express-session";
|
|
|
|
|
|
|
|
|
|
import hooks from "../../static/js/pluginfw/hooks";
|
|
|
|
|
|
|
|
|
|
import log4js from "log4js";
|
|
|
|
|
|
|
|
|
|
import SessionStore from "../db/SessionStore";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
cookie,
|
|
|
|
|
exposeVersion,
|
|
|
|
|
getEpVersion,
|
|
|
|
|
getGitCommit,
|
|
|
|
|
ip,
|
|
|
|
|
loglevel,
|
|
|
|
|
port,
|
|
|
|
|
sessionKey,
|
|
|
|
|
ssl, sslKeys,
|
|
|
|
|
trustProxy,
|
|
|
|
|
users
|
|
|
|
|
} from "../utils/Settings";
|
|
|
|
|
|
|
|
|
|
import {createCollection} from "../stats";
|
|
|
|
|
|
|
|
|
|
import util from "util";
|
|
|
|
|
|
|
|
|
|
import {checkAccess, checkAccess2} from "./express/webaccess";
|
|
|
|
|
import {Socket} from "net";
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2020-10-03 18:10:00 -04:00
|
|
|
|
const logger = log4js.getLogger('http');
|
2020-10-03 16:52:01 -04:00
|
|
|
|
let serverName;
|
2021-12-23 03:34:52 -05:00
|
|
|
|
let sessionStore;
|
2023-06-23 18:57:36 +02:00
|
|
|
|
const sockets = new Set<Socket>();
|
2021-02-13 01:10:56 -05:00
|
|
|
|
const socketsEvents = new events.EventEmitter();
|
2023-06-23 18:57:36 +02:00
|
|
|
|
const startTime = createCollection.settableGauge('httpStartTime');
|
2020-09-21 00:42:29 -04:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
export let server = null;
|
|
|
|
|
export let sessionMiddleware;
|
2020-12-22 00:41:13 -05:00
|
|
|
|
const closeServer = async () => {
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (server != null) {
|
2021-12-23 03:34:52 -05:00
|
|
|
|
logger.info('Closing HTTP server...');
|
|
|
|
|
// Call exports.server.close() to reject new connections but don't await just yet because the
|
|
|
|
|
// Promise won't resolve until all preexisting connections are closed.
|
2023-06-23 18:57:36 +02:00
|
|
|
|
const p = util.promisify(server.close.bind(server))();
|
2021-12-23 03:34:52 -05:00
|
|
|
|
await hooks.aCallAll('expressCloseServer');
|
|
|
|
|
// Give existing connections some time to close on their own before forcibly terminating. The
|
|
|
|
|
// time should be long enough to avoid interrupting most preexisting transmissions but short
|
|
|
|
|
// enough to avoid a noticeable outage.
|
|
|
|
|
const timeout = setTimeout(async () => {
|
|
|
|
|
logger.info(`Forcibly terminating remaining ${sockets.size} HTTP connections...`);
|
|
|
|
|
for (const socket of sockets) socket.destroy(new Error('HTTP server is closing'));
|
|
|
|
|
}, 5000);
|
|
|
|
|
let lastLogged = 0;
|
|
|
|
|
while (sockets.size > 0) {
|
|
|
|
|
if (Date.now() - lastLogged > 1000) { // Rate limit to avoid filling logs.
|
|
|
|
|
logger.info(`Waiting for ${sockets.size} HTTP clients to disconnect...`);
|
|
|
|
|
lastLogged = Date.now();
|
|
|
|
|
}
|
|
|
|
|
await events.once(socketsEvents, 'updated');
|
2021-02-13 01:10:56 -05:00
|
|
|
|
}
|
2021-12-23 03:34:52 -05:00
|
|
|
|
await p;
|
|
|
|
|
clearTimeout(timeout);
|
2023-06-23 18:57:36 +02:00
|
|
|
|
server = null;
|
2021-12-23 03:34:52 -05:00
|
|
|
|
startTime.setValue(0);
|
|
|
|
|
logger.info('HTTP server closed');
|
2021-02-13 01:10:56 -05:00
|
|
|
|
}
|
2021-12-23 03:34:52 -05:00
|
|
|
|
if (sessionStore) sessionStore.shutdown();
|
|
|
|
|
sessionStore = null;
|
2020-12-22 00:41:13 -05:00
|
|
|
|
};
|
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
export const createServer = async () => {
|
2020-11-23 13:24:19 -05:00
|
|
|
|
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
serverName = `Etherpad ${getGitCommit()} (https://etherpad.org)`;
|
2019-04-16 00:34:29 +02:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
console.log(`Your Etherpad version is ${getEpVersion()} (${getGitCommit()})`);
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
await restartServer();
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (ip.length===0) {
|
2020-03-29 22:27:22 +00:00
|
|
|
|
// using Unix socket for connectivity
|
2023-06-23 18:57:36 +02:00
|
|
|
|
console.log(`You can access your Etherpad instance using the Unix socket at ${port}`);
|
2020-03-29 22:27:22 +00:00
|
|
|
|
} else {
|
2023-06-23 18:57:36 +02:00
|
|
|
|
console.log(`You can access your Etherpad instance at http://${ip}:${port}/`);
|
2020-03-29 22:27:22 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (!_.isEmpty(users)) {
|
|
|
|
|
console.log(`The plugin admin page is at http://${ip}:${port}/admin/plugins`);
|
2019-04-16 00:17:56 +02:00
|
|
|
|
} else {
|
2020-12-08 03:20:59 -05:00
|
|
|
|
console.warn('Admin username and password not set in settings.json. ' +
|
|
|
|
|
'To access admin please uncomment and edit "users" in settings.json');
|
2012-07-03 23:30:40 +02:00
|
|
|
|
}
|
2019-04-16 00:17:56 +02:00
|
|
|
|
|
2020-10-03 16:52:01 -04:00
|
|
|
|
const env = process.env.NODE_ENV || 'development';
|
2019-04-16 00:17:56 +02:00
|
|
|
|
|
|
|
|
|
if (env !== 'production') {
|
2020-12-08 03:20:59 -05:00
|
|
|
|
console.warn('Etherpad is running in Development mode. This mode is slower for users and ' +
|
|
|
|
|
'less secure than production mode. You should set the NODE_ENV environment ' +
|
|
|
|
|
'variable to production by using: export NODE_ENV=production');
|
2018-04-03 10:59:10 +01:00
|
|
|
|
}
|
2020-11-23 13:24:19 -05:00
|
|
|
|
};
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
export const restartServer = async () => {
|
2020-12-22 00:41:13 -05:00
|
|
|
|
await closeServer();
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2020-10-03 16:52:01 -04:00
|
|
|
|
const app = express(); // New syntax for express v3
|
2012-11-22 10:12:58 +01:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (ssl) {
|
2020-11-23 13:24:19 -05:00
|
|
|
|
console.log('SSL -- enabled');
|
2023-06-23 18:57:36 +02:00
|
|
|
|
console.log(`SSL -- server key file: ${sslKeys.key}`);
|
|
|
|
|
console.log(`SSL -- Certificate Authority's certificate file: ${sslKeys.cert}`);
|
2019-04-16 00:34:29 +02:00
|
|
|
|
|
2020-10-03 16:52:01 -04:00
|
|
|
|
const options = {
|
2023-06-23 18:57:36 +02:00
|
|
|
|
key: fs.readFileSync(sslKeys.key),
|
|
|
|
|
cert: fs.readFileSync(sslKeys.cert),
|
|
|
|
|
ca: undefined
|
2012-11-22 10:12:58 +01:00
|
|
|
|
};
|
2019-04-16 00:17:56 +02:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (sslKeys.ca) {
|
2015-04-22 20:29:19 +02:00
|
|
|
|
options.ca = [];
|
2023-06-23 18:57:36 +02:00
|
|
|
|
for (let i = 0; i < sslKeys.ca.length; i++) {
|
|
|
|
|
const caFileName = sslKeys.ca[i];
|
2015-04-22 20:29:19 +02:00
|
|
|
|
options.ca.push(fs.readFileSync(caFileName));
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-16 00:34:29 +02:00
|
|
|
|
|
2020-10-03 16:52:01 -04:00
|
|
|
|
const https = require('https');
|
2023-06-23 18:57:36 +02:00
|
|
|
|
server = https.createServer(options, app);
|
2012-11-22 10:12:58 +01:00
|
|
|
|
} else {
|
2020-10-03 16:52:01 -04:00
|
|
|
|
const http = require('http');
|
2023-06-23 18:57:36 +02:00
|
|
|
|
server = http.createServer(app);
|
2012-11-22 10:12:58 +01:00
|
|
|
|
}
|
2012-07-03 23:30:40 +02:00
|
|
|
|
|
2020-11-23 13:24:19 -05:00
|
|
|
|
app.use((req, res, next) => {
|
2014-06-17 13:21:38 +02:00
|
|
|
|
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (ssl) {
|
2019-04-16 00:17:56 +02:00
|
|
|
|
// we use SSL
|
2020-11-23 13:24:19 -05:00
|
|
|
|
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
2013-03-14 19:03:20 -03:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-24 14:17:49 +01:00
|
|
|
|
// Stop IE going into compatability mode
|
|
|
|
|
// https://github.com/ether/etherpad-lite/issues/2547
|
2020-11-23 13:24:19 -05:00
|
|
|
|
res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
|
2019-04-15 16:02:46 +02:00
|
|
|
|
|
referer: change referrer policy. Stop sending referers as much as possible
Pull request with discussion: https://github.com/ether/etherpad-lite/pull/3636
What's already there:
* `meta name=referrer`: already done in 1.6.1:
https://github.com/ether/etherpad-lite/pull/3044
https://caniuse.com/#feat=referrer-policy
https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-meta
(Chrome>=78, Firefox>=70, Safari>=13, Opera>=64, ~IE[1], ~Edge[1])
The previous two commits (by @joelpurra) I backported in this batch:
* `<a rel=noreferrer>`: a pull request denied before:
https://github.com/ether/etherpad-lite/pull/2498
https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types
(Firefox>=37, I can't find more info about support)
This commit adds the following:
* `<a rel="noopener">`: fixing a not-so-well-known way to extract referer
https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
(Chrome>=49, Firefox>=52, Safari>=10.1, Opera>=36, !IE, !Edge)
* `Referrer-Policy: same-origin`: the last bastion of referrer security
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
(Chrome>=61, Firefox>=52, Safari>=11.1, Opera>=48, !IE, !Edge)
meta name=referrer wasn't enough. I happened to leak a few referrers with my
Firefox browser, though for some browsers it could have been enough.
[1] IE>=11, Edge>=18 use a different syntax for meta name=referrer, making it
most probably incompatible (but I may be wrong on that, they may support
both, but I have no way to test it currently). The next Edge release will be
based on Chromium, so for that the Chrome version applies.
2019-11-23 08:18:07 +01:00
|
|
|
|
// Enable a strong referrer policy. Same-origin won't drop Referers when
|
|
|
|
|
// loading local resources, but it will drop them when loading foreign resources.
|
|
|
|
|
// It's still a last bastion of referrer security. External URLs should be
|
|
|
|
|
// already marked with rel="noreferer" and user-generated content pages are already
|
|
|
|
|
// marked with <meta name="referrer" content="no-referrer">
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
|
|
|
|
// https://github.com/ether/etherpad-lite/pull/3636
|
2020-11-23 13:24:19 -05:00
|
|
|
|
res.header('Referrer-Policy', 'same-origin');
|
referer: change referrer policy. Stop sending referers as much as possible
Pull request with discussion: https://github.com/ether/etherpad-lite/pull/3636
What's already there:
* `meta name=referrer`: already done in 1.6.1:
https://github.com/ether/etherpad-lite/pull/3044
https://caniuse.com/#feat=referrer-policy
https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-delivery-meta
(Chrome>=78, Firefox>=70, Safari>=13, Opera>=64, ~IE[1], ~Edge[1])
The previous two commits (by @joelpurra) I backported in this batch:
* `<a rel=noreferrer>`: a pull request denied before:
https://github.com/ether/etherpad-lite/pull/2498
https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types
(Firefox>=37, I can't find more info about support)
This commit adds the following:
* `<a rel="noopener">`: fixing a not-so-well-known way to extract referer
https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
(Chrome>=49, Firefox>=52, Safari>=10.1, Opera>=36, !IE, !Edge)
* `Referrer-Policy: same-origin`: the last bastion of referrer security
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
(Chrome>=61, Firefox>=52, Safari>=11.1, Opera>=48, !IE, !Edge)
meta name=referrer wasn't enough. I happened to leak a few referrers with my
Firefox browser, though for some browsers it could have been enough.
[1] IE>=11, Edge>=18 use a different syntax for meta name=referrer, making it
most probably incompatible (but I may be wrong on that, they may support
both, but I have no way to test it currently). The next Edge release will be
based on Chromium, so for that the Chrome version applies.
2019-11-23 08:18:07 +01:00
|
|
|
|
|
2019-04-15 16:02:46 +02:00
|
|
|
|
// send git version in the Server response header if exposeVersion is true.
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (exposeVersion) {
|
2020-11-23 13:24:19 -05:00
|
|
|
|
res.header('Server', serverName);
|
2019-04-15 16:02:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2012-07-03 23:30:40 +02:00
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (trustProxy) {
|
2020-04-14 01:10:19 +02:00
|
|
|
|
/*
|
|
|
|
|
* If 'trust proxy' === true, the client’s IP address in req.ip will be the
|
|
|
|
|
* left-most entry in the X-Forwarded-* header.
|
|
|
|
|
*
|
|
|
|
|
* Source: https://expressjs.com/en/guide/behind-proxies.html
|
|
|
|
|
*/
|
2013-04-24 12:19:41 +02:00
|
|
|
|
app.enable('trust proxy');
|
|
|
|
|
}
|
2015-04-07 07:55:05 -05:00
|
|
|
|
|
2020-10-03 18:10:00 -04:00
|
|
|
|
// Measure response time
|
|
|
|
|
app.use((req, res, next) => {
|
2023-06-23 18:57:36 +02:00
|
|
|
|
const stopWatch = createCollection.timer('httpRequests').start();
|
2020-10-03 18:10:00 -04:00
|
|
|
|
const sendFn = res.send.bind(res);
|
2023-06-23 18:57:36 +02:00
|
|
|
|
// FIXME Check if this is still needed
|
|
|
|
|
// @ts-ignore
|
2020-10-03 18:10:00 -04:00
|
|
|
|
res.send = (...args) => { stopWatch.end(); sendFn(...args); };
|
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If the log level specified in the config file is WARN or ERROR the application server never
|
|
|
|
|
// starts listening to requests as reported in issue #158. Not installing the log4js connect
|
|
|
|
|
// logger when the log level has a higher severity than INFO since it would not log at that level
|
|
|
|
|
// anyway.
|
2023-06-23 18:57:36 +02:00
|
|
|
|
if (!(loglevel === 'WARN') && loglevel === 'ERROR') {
|
2020-10-03 18:10:00 -04:00
|
|
|
|
app.use(log4js.connectLogger(logger, {
|
2023-06-23 18:57:36 +02:00
|
|
|
|
level: loglevel,
|
2020-10-03 18:10:00 -04:00
|
|
|
|
format: ':status, :method :url',
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
app.use(cookieParser(sessionKey, {}));
|
2022-01-12 18:29:29 -05:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
sessionStore = new SessionStore(cookie.sessionRefreshInterval);
|
|
|
|
|
sessionMiddleware = expressSession({
|
2021-12-23 01:45:38 -05:00
|
|
|
|
propagateTouch: true,
|
|
|
|
|
rolling: true,
|
2023-06-23 18:57:36 +02:00
|
|
|
|
secret: sessionKey,
|
2021-12-23 03:34:52 -05:00
|
|
|
|
store: sessionStore,
|
2020-10-03 18:10:00 -04:00
|
|
|
|
resave: false,
|
2021-12-18 17:51:17 -05:00
|
|
|
|
saveUninitialized: false,
|
2020-10-03 18:10:00 -04:00
|
|
|
|
// Set the cookie name to a javascript identifier compatible string. Makes code handling it
|
|
|
|
|
// cleaner :)
|
|
|
|
|
name: 'express_sid',
|
|
|
|
|
cookie: {
|
2023-06-23 18:57:36 +02:00
|
|
|
|
maxAge: cookie.sessionLifetime || null, // Convert 0 to null.
|
|
|
|
|
sameSite: cookie.sameSite,
|
2020-10-03 18:10:00 -04:00
|
|
|
|
|
|
|
|
|
// The automatic express-session mechanism for determining if the application is being served
|
|
|
|
|
// over ssl is similar to the one used for setting the language cookie, which check if one of
|
|
|
|
|
// these conditions is true:
|
|
|
|
|
//
|
|
|
|
|
// 1. we are directly serving the nodejs application over SSL, using the "ssl" options in
|
|
|
|
|
// settings.json
|
|
|
|
|
//
|
|
|
|
|
// 2. we are serving the nodejs application in plaintext, but we are using a reverse proxy
|
|
|
|
|
// that terminates SSL for us. In this case, the user has to set trustProxy = true in
|
|
|
|
|
// settings.json, and the information wheter the application is over SSL or not will be
|
|
|
|
|
// extracted from the X-Forwarded-Proto HTTP header
|
|
|
|
|
//
|
|
|
|
|
// Please note that this will not be compatible with applications being served over http and
|
|
|
|
|
// https at the same time.
|
|
|
|
|
//
|
|
|
|
|
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
|
|
|
|
|
secure: 'auto',
|
2020-11-23 13:24:19 -05:00
|
|
|
|
},
|
2020-10-03 18:10:00 -04:00
|
|
|
|
});
|
|
|
|
|
|
2022-01-12 18:59:10 -05:00
|
|
|
|
// Give plugins an opportunity to install handlers/middleware before the express-session
|
|
|
|
|
// middleware. This allows plugins to avoid creating an express-session record in the database
|
|
|
|
|
// when it is not needed (e.g., public static content).
|
2021-12-17 16:29:45 -05:00
|
|
|
|
await hooks.aCallAll('expressPreSession', {app});
|
2023-06-23 18:57:36 +02:00
|
|
|
|
app.use(sessionMiddleware);
|
2022-01-12 18:59:10 -05:00
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
app.use(checkAccess2);
|
2020-10-03 18:10:00 -04:00
|
|
|
|
|
2021-02-09 16:46:37 -05:00
|
|
|
|
await Promise.all([
|
|
|
|
|
hooks.aCallAll('expressConfigure', {app}),
|
2023-06-23 18:57:36 +02:00
|
|
|
|
hooks.aCallAll('expressCreateServer', {app, server: server}),
|
2021-02-09 16:46:37 -05:00
|
|
|
|
]);
|
2023-06-23 18:57:36 +02:00
|
|
|
|
server.on('connection', (socket) => {
|
2021-02-13 01:10:56 -05:00
|
|
|
|
sockets.add(socket);
|
|
|
|
|
socketsEvents.emit('updated');
|
|
|
|
|
socket.on('close', () => {
|
|
|
|
|
sockets.delete(socket);
|
|
|
|
|
socketsEvents.emit('updated');
|
|
|
|
|
});
|
|
|
|
|
});
|
2023-06-23 18:57:36 +02:00
|
|
|
|
await util.promisify(server.listen).bind(server)(port, ip);
|
2021-02-14 02:50:10 -05:00
|
|
|
|
startTime.setValue(Date.now());
|
2021-02-13 01:40:54 -05:00
|
|
|
|
logger.info('HTTP server listening for connections');
|
2020-09-21 00:42:29 -04:00
|
|
|
|
};
|
|
|
|
|
|
2023-06-23 18:57:36 +02:00
|
|
|
|
export const shutdown = async (hookName, context) => {
|
2020-12-22 00:41:13 -05:00
|
|
|
|
await closeServer();
|
2020-09-21 00:42:29 -04:00
|
|
|
|
};
|