mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 02:52:33 -05:00
#916 (decided 2026-10-14): HTTPS_PORT with HTTPS_CERT_FILE and HTTPS_KEY_FILE (a path or the PEM itself) makes WeKan serve HTTPS; without them nothing changes, and a proxy stays the recommendation for production. Meteor's HTTP server - pages, DDP, websockets - is not replaced: a TLS listener hands each decrypted connection to it, so everything works the same over HTTPS. A renewed certificate is loaded within a minute without a restart (a broken one keeps the old). HTTPS_REDIRECT=true redirects plain HTTP to ROOT_URL when that is https, else the same host on the HTTPS port, keeping only the path. The snap gets https-port, https-cert-file, https-key-file, https-bind-ip and https-redirect. tests/httpsServer.test.cjs makes a real certificate with openssl, serves a page and a websocket upgrade through the bridge, swaps the certificate without a restart, and refuses incomplete settings, an untrusted certificate, an injected host and a scheme-relative redirect. Thanks to 68040 and xet7 ! Fixes #916.
45 lines
1.6 KiB
JavaScript
45 lines
1.6 KiB
JavaScript
'use strict';
|
|
|
|
// The TLS listener of optional HTTPS (#916): encrypted connections in, each
|
|
// decrypted socket handed to an existing HTTP server - Meteor's, in WeKan -
|
|
// which serves it exactly as a plain connection, websocket upgrades included.
|
|
// No Meteor here, so tests/httpsServer.test.cjs runs it with a real
|
|
// certificate and a plain http.Server.
|
|
const fs = require('fs');
|
|
const tls = require('tls');
|
|
|
|
function startHttpsBridge(httpServer, config, { readConfig = null, log = console } = {}) {
|
|
const server = tls.createServer({ cert: config.cert, key: config.key, ALPNProtocols: ['http/1.1'] }, socket => {
|
|
httpServer.emit('connection', socket);
|
|
});
|
|
server.on('tlsClientError', () => { /* a client that cannot speak TLS: its problem */ });
|
|
server.on('error', error => log.error(`HTTPS server: ${error.message}`));
|
|
|
|
// Renewed certificates are picked up without a restart.
|
|
const watched = [];
|
|
if (readConfig) {
|
|
const reload = () => {
|
|
try {
|
|
const next = readConfig();
|
|
if (next) server.setSecureContext({ cert: next.cert, key: next.key });
|
|
log.log('HTTPS certificate reloaded.');
|
|
} catch (error) {
|
|
log.error(`HTTPS certificate not reloaded, keeping the current one: ${error.message}`);
|
|
}
|
|
};
|
|
for (const file of [config.certPath, config.keyPath].filter(Boolean)) {
|
|
fs.watchFile(file, { interval: 60000 }, (now, before) => {
|
|
if (now.mtimeMs !== before.mtimeMs) reload();
|
|
});
|
|
watched.push(file);
|
|
}
|
|
}
|
|
const close = () => {
|
|
for (const file of watched) fs.unwatchFile(file);
|
|
server.close();
|
|
};
|
|
return { server, close };
|
|
}
|
|
|
|
module.exports = { startHttpsBridge };
|