cryptpad/server.js
ansuz d9b6d94580 use consistent capitalization for CryptPad
run docs/ARCHITECTURE.md:[XWiki-Labs](https://labs.xwiki.com/) has published an open source suite (called [Cryptpad](https://github.com/xwiki-labs/cryptpad)) of collaborative editors  which employ end to end encryption.
docs/ARCHITECTURE.md:Cryptpad is capable of using a variety of data stores.
docs/ARCHITECTURE.md:Cryptpad was initially written to use [websockets](https://en.wikipedia.org/wiki/WebSocket) for transportation of messages.
docs/ARCHITECTURE.md:The encryption scheme employed by Cryptpad is a [symmetric encryption](https://en.wikipedia.org/wiki/Symmetric-key_algorithm) which utilizes a single [pre-shared-key](https://en.wikipedia.org/wiki/Pre-shared_key) known by all participants.
readme.md:See [Cryptpad-Docker](https://github.com/xwiki-labs/cryptpad-docker) repository for details on how to get up-and-running with Cryptpad in Docker. This repository is maintained by the community and not officially supported.
readme.md:If you have any questions or comments, or if you're interested in contributing to Cryptpad, come say hi in our [Matrix channel](https://app.element.io/#/room/#cryptpad:matrix.xwiki.com).
www/common/translations/README.md:To illustrate the process of translating, this guide will make an english-pirate translation of Cryptpad.
www/common/translations/README.md:We'll assume that you have a work locally-installed, properly functioning installation of Cryptpad.
www/common/translations/README.md:If you don't have Cryptpad installed locally, start by following the steps in the main readme.
www/common/translations/README.md:    out.main_title = "Cryptpad: Zero Knowledge, Collaborative Real Time Editing";
www/common/translations/README.md:    out.main_title = "Cryptpad: Knowledge lost at sea while ye scribble with yer mateys";
www/common/translations/README.md:It's advisable to save your translation file frequently, and reload Cryptpad in your browser to check that there are no errors in your translation file.
www/common/translations/README.md:When you're happy with your translation file, you can visit http://localhost:3000/assert/translations/ to view Cryptpad's tests.
www/common/translations/messages.ca.json:    "topbar_whatIsCryptpad": "Què és CryptPad",
www/common/translations/messages.de.json:    "topbar_whatIsCryptpad": "Was ist CryptPad",
www/common/translations/messages.el.json:    "topbar_whatIsCryptpad": "Τι είναι το CryptPad",
www/common/translations/messages.es.json:    "main_title": "Cryptpad: Zero Knowledge, Editor Colaborativo en Tiempo Real",
www/common/translations/messages.es.json:    "tos_title": "Condiciones de servicio Cryptpad",
www/common/translations/messages.es.json:    "tos_e2ee": "Los documentos Cryptpad pueden ser leídos o modificados por cualquiera que pueda adivinar o que pueda tener el enlace. Recomendamos que utilices mensajes cifrados de punto a punto (e2ee) para compartir URLs, no asumimos ninguna responsabilidad en el evento de alguna fuga.",
www/common/translations/messages.es.json:    "topbar_whatIsCryptpad": "Qué es CryptPad",
www/common/translations/messages.es.json:    "settings_autostoreHint": "<b> Automático </b> Todos los pads que visita se almacenan en su CryptDrive. <br> <b> Manual (siempre pregunte) </b> Si aún no ha guardado un pad, se le preguntará si desea para almacenarlos en su CryptDrive. <br> <b> Manual (nunca preguntar) </b> Los Pads no se almacenan automáticamente en su Cryptpad. La opción para almacenarlos estará oculta.",
www/common/translations/messages.fi.json:    "home_host": "Tämä on itsenäinen yhteisön ylläpitämä Cryptpad-instanssi.",
www/common/translations/messages.fi.json:    "topbar_whatIsCryptpad": "Mikä on CryptPad",
www/common/translations/messages.fr.json:    "topbar_whatIsCryptpad": "Qu'est-ce que CryptPad",
www/common/translations/messages.fr.json:    "admin_updateAvailableHint": "Une nouvelle version de Cryptpad est disponible",
www/common/translations/messages.id.json:    "main_title": "Cryptpad: Informasi Aman, Kolaborasi Waktu Nyata"
www/common/translations/messages.it.json:    "topbar_whatIsCryptpad": "Cos'è CryptPad",
www/common/translations/messages.it.json:    "settings_autostoreHint": "<b>Automatico</b> Tutti i pad che visiti sono conservati nel tuo CryptDrive.<br><b>Manuale (chiedi sempre)</b> Se non hai ancora conservato alcun pad ti verrà chiesto se vuoi conservarli nel tuo CryptDrive.<br><b>Manuale (non chiedere mai)</b> I pads non sono conservati automaticamente nel tuo Cryptpad. L'opzione di conservarli sarà nascosta.",
www/common/translations/messages.it.json:    "survey": "Sondaggio Cryptpad",
www/common/translations/messages.it.json:    "crowdfunding_button": "Supporta Cryptpad",
www/common/translations/messages.ja.json:    "topbar_whatIsCryptpad": "CryptPadとは何か",
www/common/translations/messages.json:    "settings_autostoreHint": "<b>Automatic</b> All the pads you visit are stored in your CryptDrive.<br><b>Manual (always ask)</b> If you have not stored a pad yet, you will be asked if you want to store them in your CryptDrive.<br><b>Manual (never ask)</b> Pads are not stored automatically in your Cryptpad. The option to store them will be hidden.",
www/common/translations/messages.json:    "topbar_whatIsCryptpad": "What is CryptPad",
www/common/translations/messages.nb.json:    "topbar_whatIsCryptpad": "Hva er CryptPad",
www/common/translations/messages.nl.json:    "settings_autostoreHint": "<b>Automatisch</b> Alle geopende werkomgevingen worden automatisch opgeslagen in uw CryptDrive.<br><b>Handmatig (altijd vragen)</b> Als u een werkomgeving nog niet hebt opgeslagen, zult u gevraagd worden of u het in uw CryptDrive wilt opslaan.<br><b>Handmatig (nooit vragen)</b> Werkomgevingen worden niet automatisch opgeslagen in uw Cryptpad. The optie om op te slaan wordt verborgen.",
www/common/translations/messages.pl.json:    "main_title": "Cryptpad: Wspólne edytowanie w czasie rzeczywistym, bez wiedzy specjalistycznej",
www/common/translations/messages.pl.json:    "tos_title": "Warunki korzystania z usług Cryptpad",
www/common/translations/messages.pl.json:    "tos_e2ee": "Dokumenty Cryptpad mogą być odczytywane i modyfikowane przez każdego kto może zgadnąć lub w inny sposób uzyskać identyfikator dokumentu. Polecamy korzystania z oprogramowania szyfrującego end-to-end (e2ee) do udostępniania linków URL. Nie będziesz rościł sobie żadnych wierzytelności w wypadku gdy taki URL dostanie się w niepowołane ręce.",
www/common/translations/messages.pt-br.json:    "main_title": "Cryptpad: Zero Knowledge, Edição Colaborativa em Tempo Real",
www/common/translations/messages.pt-br.json:    "tos_title": "Termos de serviço doCryptpad",
www/common/translations/messages.pt-br.json:    "topbar_whatIsCryptpad": "O que é CryptPad",
www/common/translations/messages.ro.json:    "settings_autostoreHint": "<b>Automat</b> Toate documentele accesate sunt stocate în CryptDrive-ul dumneavoastră.<br><b>Manual (întreabă întotdeauna)</b> Dacă nu ai stocat încă un document, vei fi întrebat dacă dorești să îl stochezi în Cryptdrive-ul tău.<br><b>Manual (nu mai întreba)</b> Documentele nu sunt stocate automat în Cryptpad-ul tău. Opțiunea de a le stoca ulterior va fi ascunsă.",
www/common/translations/messages.ru.json:    "topbar_whatIsCryptpad": "Что такое CryptPad",
www/common/translations/messages.zh.json:    "footer_aboutUs": "關於 Cryptpad", for many more examples
2021-08-04 14:18:07 +05:30

357 lines
13 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
globals require console
*/
var Express = require('express');
var Http = require('http');
var Fs = require('fs');
var Path = require("path");
var nThen = require("nthen");
var Util = require("./lib/common-util");
var Default = require("./lib/defaults");
var Keys = require("./lib/keys");
var config = require("./lib/load-config");
var Env = require("./lib/env").create(config);
var app = Express();
var fancyURL = function (domain, path) {
try {
if (domain && path) { return new URL(path, domain).href; }
return new URL(domain);
} catch (err) {}
return false;
};
(function () {
// you absolutely must provide an 'httpUnsafeOrigin' (a truthy string)
if (!Env.httpUnsafeOrigin || typeof(Env.httpUnsafeOrigin) !== 'string') {
throw new Error("No 'httpUnsafeOrigin' provided");
}
// fall back to listening on a local address
// if httpAddress is not a string
if (typeof(config.httpAddress) !== 'string') {
config.httpAddress = '127.0.0.1';
}
// listen on port 3000 if a valid port number was not provided
if (typeof(config.httpPort) !== 'number' || config.httpPort > 65535) {
config.httpPort = 3000;
}
if (typeof(Env.httpSafeOrigin) !== 'string') {
Env.NO_SANDBOX = true;
if (typeof(config.httpSafePort) !== 'number') {
config.httpSafePort = config.httpPort + 1;
}
}
}());
var applyHeaderMap = function (res, map) {
for (let header in map) { res.setHeader(header, map[header]); }
};
var setHeaders = (function () {
// load the default http headers unless the admin has provided their own via the config file
var headers;
var custom = config.httpHeaders;
// if the admin provided valid http headers then use them
if (custom && typeof(custom) === 'object' && !Array.isArray(custom)) {
headers = Util.clone(custom);
} else {
// otherwise use the default
headers = Default.httpHeaders();
}
// next define the base Content Security Policy (CSP) headers
if (typeof(config.contentSecurity) === 'string') {
headers['Content-Security-Policy'] = config.contentSecurity;
if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' }
if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) {
// backward compat for those who do not merge the new version of the config
// when updating. This prevents endless spinner if someone clicks donate.
// It also fixes the cross-domain iframe.
headers['Content-Security-Policy'] += "frame-ancestors *;";
}
} else {
// use the default CSP headers constructed with your domain
headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin);
}
const padHeaders = Util.clone(headers);
if (typeof(config.padContentSecurity) === 'string') {
padHeaders['Content-Security-Policy'] = config.padContentSecurity;
} else {
padHeaders['Content-Security-Policy'] = Default.padContentSecurity(Env.httpUnsafeOrigin);
}
if (Object.keys(headers).length) {
return function (req, res) {
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
applyHeaderMap(res, {
"Cross-Origin-Opener-Policy": /^\/(sheet|presentation|doc|convert)\//.test(req.url)? 'same-origin': '',
});
if (Env.NO_SANDBOX) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Embedder-Policy": 'require-corp',
});
}
// Don't set CSP headers on /api/ endpoints
// because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments
if (/^\/api\/(broadcast|config)/.test(req.url)) { return; }
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
});
// targeted CSP, generic policies, maybe custom headers
const h = [
/^\/common\/onlyoffice\/.*\/index\.html.*/,
/^\/(sheet|presentation|doc)\/inner\.html.*/,
].some((regex) => {
return regex.test(req.url);
}) ? padHeaders : headers;
applyHeaderMap(res, h);
};
}
return function () {};
}());
(function () {
if (!config.logFeedback) { return; }
const logFeedback = function (url) {
url.replace(/\?(.*?)=/, function (all, fb) {
if (!config.log) { return; }
config.log.feedback(fb, '');
});
};
app.head(/^\/common\/feedback\.html/, function (req, res, next) {
logFeedback(req.url);
next();
});
}());
app.use('/blob', function (req, res, next) {
if (req.method === 'HEAD') {
Express.static(Path.join(__dirname, Env.paths.blob), {
setHeaders: function (res, path, stat) {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Headers', 'Content-Length');
res.set('Access-Control-Expose-Headers', 'Content-Length');
}
})(req, res, next);
return;
}
next();
});
app.use(function (req, res, next) {
if (req.method === 'OPTIONS' && /\/blob\//.test(req.url)) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range');
res.setHeader('Access-Control-Max-Age', 1728000);
res.setHeader('Content-Type', 'application/octet-stream; charset=utf-8');
res.setHeader('Content-Length', 0);
res.statusCode = 204;
return void res.end();
}
setHeaders(req, res);
if (/[\?\&]ver=[^\/]+$/.test(req.url)) { res.setHeader("Cache-Control", "max-age=31536000"); }
else { res.setHeader("Cache-Control", "no-cache"); }
next();
});
app.use(Express.static(__dirname + '/www'));
// FIXME I think this is a regression caused by a recent PR
// correct this hack without breaking the contributor's intended behaviour.
var mainPages = config.mainPages || Default.mainPages();
var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$');
app.get(mainPagePattern, Express.static(__dirname + '/customize'));
app.get(mainPagePattern, Express.static(__dirname + '/customize.dist'));
app.use("/blob", Express.static(Path.join(__dirname, Env.paths.blob), {
maxAge: Env.DEV_MODE? "0d": "365d"
}));
app.use("/datastore", Express.static(Path.join(__dirname, Env.paths.data), {
maxAge: "0d"
}));
app.use("/block", Express.static(Path.join(__dirname, Env.paths.block), {
maxAge: "0d",
}));
app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist'));
app.use("/customize.dist", Express.static(__dirname + '/customize.dist'));
app.use(/^\/[^\/]*$/, Express.static('customize'));
app.use(/^\/[^\/]*$/, Express.static('customize.dist'));
// if dev mode: never cache
var cacheString = function () {
return (Env.FRESH_KEY? '-' + Env.FRESH_KEY: '') + (Env.DEV_MODE? '-' + (+new Date()): '');
};
var makeRouteCache = function (template, cacheName) {
var cleanUp = {};
var cache = Env[cacheName] = Env[cacheName] || {};
return function (req, res) {
var host = req.headers.host.replace(/\:[0-9]+/, '');
res.setHeader('Content-Type', 'text/javascript');
// don't cache anything if you're in dev mode
if (Env.DEV_MODE) {
return void res.send(template(host));
}
// generate a lookup key for the cache
var cacheKey = host + ':' + cacheString();
// FIXME mutable
// we must be able to clear the cache when updating any mutable key
// if there's nothing cached for that key...
if (!cache[cacheKey]) {
// generate the response and cache it in memory
cache[cacheKey] = template(host);
// and create a function to conditionally evict cache entries
// which have not been accessed in the last 20 seconds
cleanUp[cacheKey] = Util.throttle(function () {
delete cleanUp[cacheKey];
delete cache[cacheKey];
}, 20000);
}
// successive calls to this function
cleanUp[cacheKey]();
return void res.send(cache[cacheKey]);
};
};
var serveConfig = makeRouteCache(function (host) {
return [
'define(function(){',
'var obj = ' + JSON.stringify({
requireConf: {
waitSeconds: 600,
urlArgs: 'ver=' + Env.version + cacheString(),
},
removeDonateButton: (Env.removeDonateButton === true),
allowSubscriptions: (Env.allowSubscriptions === true),
websocketPath: config.externalWebsocketURL,
httpUnsafeOrigin: Env.httpUnsafeOrigin,
adminEmail: Env.adminEmail,
adminKeys: Env.admins,
inactiveTime: Env.inactiveTime,
supportMailbox: Env.supportMailbox,
defaultStorageLimit: Env.defaultStorageLimit,
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
}, null, '\t'),
'obj.httpSafeOrigin = ' + (function () {
if (Env.httpSafeOrigin) { return '"' + Env.httpSafeOrigin + '"'; }
if (config.httpSafePort) {
return "(function () { return window.location.origin.replace(/\:[0-9]+$/, ':" +
config.httpSafePort + "'); }())";
}
return 'window.location.origin';
}()),
'return obj',
'});'
].join(';\n')
}, 'configCache');
var serveBroadcast = makeRouteCache(function (host) {
var maintenance = Env.maintenance;
if (maintenance && maintenance.end && maintenance.end < (+new Date())) {
maintenance = undefined;
}
return [
'define(function(){',
'return ' + JSON.stringify({
lastBroadcastHash: Env.lastBroadcastHash,
surveyURL: Env.surveyURL,
maintenance: maintenance
}, null, '\t'),
'});'
].join(';\n')
}, 'broadcastCache');
app.get('/api/config', serveConfig);
app.get('/api/broadcast', serveBroadcast);
var four04_path = Path.resolve(__dirname + '/customize.dist/404.html');
var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
var send404 = function (res, path) {
if (!path && path !== four04_path) { path = four04_path; }
Fs.exists(path, function (exists) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
if (exists) { return Fs.createReadStream(path).pipe(res); }
send404(res);
});
};
app.use(function (req, res, next) {
res.status(404);
send404(res, custom_four04_path);
});
var httpServer = Env.httpServer = Http.createServer(app);
nThen(function (w) {
Fs.exists(__dirname + "/customize", w(function (e) {
if (e) { return; }
console.log("CryptPad is customizable, see customize.dist/readme.md for details");
}));
}).nThen(function (w) {
httpServer.listen(config.httpPort,config.httpAddress,function(){
var host = config.httpAddress;
var hostName = !host.indexOf(':') ? '[' + host + ']' : host;
var port = config.httpPort;
var ps = port === 80? '': ':' + port;
var roughAddress = 'http://' + hostName + ps;
var betterAddress = fancyURL(Env.httpUnsafeOrigin);
if (betterAddress) {
console.log('Serving content for %s via %s.\n', betterAddress, roughAddress);
} else {
console.log('Serving content via %s.\n', roughAddress);
}
if (!Env.admins.length) {
console.log("Your instance is not correctly configured for safe use in production.\nSee %s for more information.\n",
fancyURL(Env.httpUnsafeOrigin, '/checkup/') || 'https://your-domain.com/checkup/'
);
}
});
if (config.httpSafePort) {
Http.createServer(app).listen(config.httpSafePort, config.httpAddress, w());
}
}).nThen(function () {
var wsConfig = { server: httpServer };
// Initialize logging then start the API server
require("./lib/log").create(config, function (_log) {
Env.Log = _log;
config.log = _log;
if (Env.OFFLINE_MODE) { return; }
if (config.externalWebsocketURL) { return; }
require("./lib/api").create(Env);
});
});