diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..880c21fe3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +data +Dockerfile +docker-compose.yml +.git +.gitignore \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 000000000..95961b566 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +VERSION=latest +USE_SSL=true +STORAGE='./storage/file' +LOG_TO_STDOUT=true \ No newline at end of file diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 000000000..4a58bdcde --- /dev/null +++ b/.flowconfig @@ -0,0 +1,7 @@ +[ignore] + +[include] + +[libs] + +[options] diff --git a/.gitignore b/.gitignore index 354096b87..fc1136152 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ customization /customize/ messages.log .DS_Store +www/scratch +data +npm-debug.log +pins/ +blob/ +blobstage/ +privileged.conf diff --git a/.jshintignore b/.jshintignore index 1eb999871..54d5ec4d7 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,11 +1,10 @@ node_modules/ www/bower_components/ -www/code/codemirror* -www/common/chainpad.js -storage/kad.js -www/common/otaml.js +www/common/pdfjs/ -NetFluxWebsocketSrv.js -NetFluxWebsocketServer.js -WebRTCSrv.js +server.js +www/common/media-tag.js +www/scratch +www/common/toolbar.js +www/common/hyperscript.js diff --git a/.jshintrc b/.jshintrc index c55ec0518..4928c524d 100644 --- a/.jshintrc +++ b/.jshintrc @@ -10,7 +10,7 @@ "notypeof": true, "shadow": false, "undef": true, - "unused": false, + "unused": true, "futurehostile":true, "browser": true, "predef": [ diff --git a/.travis.yml b/.travis.yml index bc9ee71df..09967f1f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,22 @@ language: node_js env: matrix: - - "BROWSER='firefox:19:Windows 2012'" - - "BROWSER='chrome::Windows 2008'" + - "BROWSER='firefox::Windows 10'" + - "BROWSER='chrome::Windows 10'" + #- "BROWSER='MicrosoftEdge:14.14393:Windows 10'" + #- "BROWSER='internet explorer:11.103:Windows 10'" + #- "BROWSER='safari:10.0:macOS 10.12'" + #- "BROWSER='safari:9.0:OS X 10.11'" branches: only: - master - - diffdom - - beta - - netflux + - soon + - staging node_js: - - "4.2.1" + - "6.6.0" before_script: - npm run-script lint - - cp config.js.dist config.js + - cp config.example.js config.js - npm install bower - ./node_modules/bower/bin/bower install - node ./server.js & diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..96a5ff520 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM node:6-alpine + +COPY . /cryptpad +WORKDIR /cryptpad + +RUN apk add --no-cache git tini \ + && npm install \ + && npm install -g bower \ + && bower install --allow-root + +EXPOSE 3000 + +VOLUME /cryptpad/datastore +VOLUME /cryptpad/customize + +ENV USE_SSL=false +ENV STORAGE='./storage/file' +ENV LOG_TO_STDOUT=true + +CMD ["/sbin/tini", "--", "/cryptpad/container-start.sh"] diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js deleted file mode 100644 index b15cdd33b..000000000 --- a/NetfluxWebsocketSrv.js +++ /dev/null @@ -1,309 +0,0 @@ -;(function () { 'use strict'; -const Crypto = require('crypto'); -const Nacl = require('tweetnacl'); - -const LAG_MAX_BEFORE_DISCONNECT = 30000; -const LAG_MAX_BEFORE_PING = 15000; -const HISTORY_KEEPER_ID = Crypto.randomBytes(8).toString('hex'); - -const USE_HISTORY_KEEPER = true; -const USE_FILE_BACKUP_STORAGE = true; - -let dropUser; -let historyKeeperKeys = {}; - -const now = function () { return (new Date()).getTime(); }; - -const socketSendable = function (socket) { - return socket && socket.readyState === 1; -}; - -const sendMsg = function (ctx, user, msg) { - if (!socketSendable(user.socket)) { return; } - try { - if (ctx.config.logToStdout) { console.log('<' + JSON.stringify(msg)); } - user.socket.send(JSON.stringify(msg)); - } catch (e) { - console.log(e.stack); - dropUser(ctx, user); - } -}; - -const storeMessage = function (ctx, channel, msg) { - ctx.store.message(channel.id, msg, function (err) { - if (err && typeof(err) !== 'function') { - // ignore functions because older datastores - // might pass waitFors into the callback - console.log("Error writing message: " + err); - } - }); -}; - -const sendChannelMessage = function (ctx, channel, msgStruct) { - msgStruct.unshift(0); - channel.forEach(function (user) { - if(msgStruct[2] !== 'MSG' || user.id !== msgStruct[1]) { // We don't want to send back a message to its sender, in order to save bandwidth - sendMsg(ctx, user, msgStruct); - } - }); - if (USE_HISTORY_KEEPER && msgStruct[2] === 'MSG') { - if (historyKeeperKeys[channel.id]) { - let signedMsg = msgStruct[4].replace(/^cp\|/, ''); - signedMsg = Nacl.util.decodeBase64(signedMsg); - let validateKey = Nacl.util.decodeBase64(historyKeeperKeys[channel.id]); - let validated = Nacl.sign.open(signedMsg, validateKey); - if (!validated) { - console.log("Signed message rejected"); - return; - } - } - storeMessage(ctx, channel, JSON.stringify(msgStruct)); - } -}; - -dropUser = function (ctx, user) { - if (user.socket.readyState !== 2 /* WebSocket.CLOSING */ - && user.socket.readyState !== 3 /* WebSocket.CLOSED */) - { - try { - user.socket.close(); - } catch (e) { - console.log("Failed to disconnect ["+user.id+"], attempting to terminate"); - try { - user.socket.terminate(); - } catch (ee) { - console.log("Failed to terminate ["+user.id+"] *shrug*"); - } - } - } - delete ctx.users[user.id]; - Object.keys(ctx.channels).forEach(function (chanName) { - let chan = ctx.channels[chanName]; - let idx = chan.indexOf(user); - if (idx < 0) { return; } - - if (ctx.config.verbose) { - console.log("Removing ["+user.id+"] from channel ["+chanName+"]"); - } - chan.splice(idx, 1); - if (chan.length === 0) { - if (ctx.config.verbose) { - console.log("Removing empty channel ["+chanName+"]"); - } - delete ctx.channels[chanName]; - delete historyKeeperKeys[chanName]; - - /* Call removeChannel if it is a function and channel removal is - set to true in the config file */ - if (ctx.config.removeChannels) { - if (typeof(ctx.store.removeChannel) === 'function') { - ctx.timeouts[chanName] = setTimeout(function () { - ctx.store.removeChannel(chanName, function (err) { - if (err) { console.error("[removeChannelErr]: %s", err); } - else { - if (ctx.config.verbose) { - console.log("Deleted channel [%s] history from database...", chanName); - } - } - }); - }, ctx.config.channelRemovalTimeout); - } else { - console.error("You have configured your server to remove empty channels, " + - "however, the database adaptor you are using has not implemented this behaviour."); - } - } - } else { - sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']); - } - }); -}; - -const getHistory = function (ctx, channelName, handler, cb) { - var messageBuf = []; - var messageKey; - ctx.store.getMessages(channelName, function (msgStr) { - var parsed = JSON.parse(msgStr); - if (parsed.validateKey) { - historyKeeperKeys[channelName] = parsed.validateKey; - handler(parsed); - return; - } - messageBuf.push(parsed); - }, function (err) { - if (err) { - console.log("Error getting messages " + err.stack); - // TODO: handle this better - } - var startPoint; - var cpCount = 0; - var msgBuff2 = []; - for (startPoint = messageBuf.length - 1; startPoint >= 0; startPoint--) { - var msg = messageBuf[startPoint]; - msgBuff2.push(msg); - if (msg[2] === 'MSG' && msg[4].indexOf('cp|') === 0) { - cpCount++; - if (cpCount >= 2) { - for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); } - break; - } - } - //console.log(messageBuf[startPoint]); - } - if (cpCount < 2) { - // no checkpoints. - for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); } - } - cb(messageBuf); - }); -}; - -const randName = function () { return Crypto.randomBytes(16).toString('hex'); }; - -const handleMessage = function (ctx, user, msg) { - let json = JSON.parse(msg); - let seq = json.shift(); - let cmd = json[0]; - let obj = json[1]; - - user.timeOfLastMessage = now(); - user.pingOutstanding = false; - - if (cmd === 'JOIN') { - if (obj && obj.length !== 32) { - sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); - return; - } - let chanName = obj || randName(); - sendMsg(ctx, user, [seq, 'JACK', chanName]); - let chan = ctx.channels[chanName] = ctx.channels[chanName] || []; - - // prevent removal of the channel if there is a pending timeout - if (ctx.config.removeChannels && ctx.timeouts[chanName]) { - clearTimeout(ctx.timeouts[chanName]); - } - - chan.id = chanName; - if (USE_HISTORY_KEEPER) { - sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); - } - chan.forEach(function (u) { sendMsg(ctx, user, [0, u.id, 'JOIN', chanName]); }); - chan.push(user); - sendChannelMessage(ctx, chan, [user.id, 'JOIN', chanName]); - return; - } - if (cmd === 'MSG') { - if (obj === HISTORY_KEEPER_ID) { - let parsed; - try { parsed = JSON.parse(json[2]); } catch (err) { console.error(err); return; } - if (parsed[0] === 'GET_HISTORY') { - sendMsg(ctx, user, [seq, 'ACK']); - getHistory(ctx, parsed[1], function (msg) { - sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]); - }, function (messages) { - // parsed[2] is a validation key if it exists - if (messages.length === 0 && parsed[2] && !historyKeeperKeys[parsed[1]]) { - var key = {channel: parsed[1], validateKey: parsed[2]}; - storeMessage(ctx, ctx.channels[parsed[1]], JSON.stringify(key)); - historyKeeperKeys[parsed[1]] = parsed[2]; - } - let parsedMsg = {state: 1, channel: parsed[1]}; - sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]); - }); - } - return; - } - if (obj && !ctx.channels[obj] && !ctx.users[obj]) { - sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]); - return; - } - sendMsg(ctx, user, [seq, 'ACK']); - let target; - json.unshift(user.id); - if ((target = ctx.channels[obj])) { - sendChannelMessage(ctx, target, json); - return; - } - if ((target = ctx.users[obj])) { - json.unshift(0); - sendMsg(ctx, target, json); - return; - } - } - if (cmd === 'LEAVE') { - let err; - let chan; - let idx; - if (!obj) { err = 'EINVAL'; obj = 'undefined';} - if (!err && !(chan = ctx.channels[obj])) { err = 'ENOENT'; } - if (!err && (idx = chan.indexOf(user)) === -1) { err = 'NOT_IN_CHAN'; } - if (err) { - sendMsg(ctx, user, [seq, 'ERROR', err, obj]); - return; - } - sendMsg(ctx, user, [seq, 'ACK']); - json.unshift(user.id); - sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chan.id]); - chan.splice(idx, 1); - } - if (cmd === 'PING') { - sendMsg(ctx, user, [seq, 'ACK']); - return; - } -}; - -let run = module.exports.run = function (storage, socketServer, config) { - /* Channel removal timeout defaults to 60000ms (one minute) */ - config.channelRemovalTimeout = - typeof(config.channelRemovalTimeout) === 'number'? - config.channelRemovalTimeout: - 60000; - - let ctx = { - users: {}, - channels: {}, - timeouts: {}, - store: storage, - config: config - }; - setInterval(function () { - Object.keys(ctx.users).forEach(function (userId) { - let u = ctx.users[userId]; - if (now() - u.timeOfLastMessage > LAG_MAX_BEFORE_DISCONNECT) { - dropUser(ctx, u); - } else if (!u.pingOutstanding && now() - u.timeOfLastMessage > LAG_MAX_BEFORE_PING) { - sendMsg(ctx, u, [0, '', 'PING', now()]); - u.pingOutstanding = true; - } - }); - }, 5000); - socketServer.on('connection', function(socket) { - if(socket.upgradeReq.url !== (config.websocketPath || '/cryptpad_websocket')) { return; } - let conn = socket.upgradeReq.connection; - let user = { - addr: conn.remoteAddress + '|' + conn.remotePort, - socket: socket, - id: randName(), - timeOfLastMessage: now(), - pingOutstanding: false - }; - ctx.users[user.id] = user; - sendMsg(ctx, user, [0, '', 'IDENT', user.id]); - socket.on('message', function(message) { - if (ctx.config.logToStdout) { console.log('>'+message); } - try { - handleMessage(ctx, user, message); - } catch (e) { - console.log(e.stack); - dropUser(ctx, user); - } - }); - socket.on('close', function (evt) { - for (let userId in ctx.users) { - if (ctx.users[userId].socket === socket) { - dropUser(ctx, ctx.users[userId]); - } - } - }); - }); -}; -}()); diff --git a/TestSelenium.js b/TestSelenium.js index 2195e9ce6..fccd4e067 100644 --- a/TestSelenium.js +++ b/TestSelenium.js @@ -1,6 +1,13 @@ /* global process */ var WebDriver = require("selenium-webdriver"); +var nThen = require('nthen'); +if (process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false') { + // We can't do saucelabs on pull requests so don't fail. + return; +} + +// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/ var driver; if (process.env.SAUCE_USERNAME !== undefined) { var browserArray = process.env.BROWSER.split(':'); @@ -16,15 +23,59 @@ if (process.env.SAUCE_USERNAME !== undefined) { driver = new WebDriver.Builder().withCapabilities({ browserName: "chrome" }).build(); } -driver.get('http://localhost:3000/assert/'); -var report = driver.wait(WebDriver.until.elementLocated(WebDriver.By.className("report")), 5000); -report.getAttribute("class").then(function (cls) { - driver.quit(); - if (!cls) { - throw new Error("cls is null"); - } else if (cls.indexOf("failure") !== -1) { - throw new Error("cls contains the word failure"); - } else if (cls.indexOf("success") === -1) { - throw new Error("cls does not contain the word success"); - } +var SC_GET_DATA = "return (window.__CRYPTPAD_TEST__) ? window.__CRYPTPAD_TEST__.getData() : '[]'"; + +var failed = false; +var nt = nThen; +[ + //'/register/#?test=test', + '/assert/#?test=test', + // '/auth/#?test=test' // TODO(cjd): Not working on automatic tests, understand why. +].forEach(function (path) { + if (failed) { return; } + var url = 'http://localhost:3000' + path; + nt = nt(function (waitFor) { + var done = waitFor(); + console.log('\n\n-----TEST ' + url + ' -----'); + var waitTo = setTimeout(function () { + console.log("no report in 20 seconds, timing out"); + failed = true; + done(); + done = undefined; + }, 20000); + var logMore = function () { + if (!done) { return; } + driver.executeScript(SC_GET_DATA).then(waitFor(function (dataS) { + if (!done) { return; } + var data = JSON.parse(dataS); + data.forEach(function (d) { + if (d.type !== 'log') { return; } + console.log('>' + d.val); + }); + data.forEach(function (d) { + if (d.type !== 'report') { return; } + console.log('RESULT: ' + d.val); + if (d.val !== 'passed') { + if (d.error) { + console.log(d.error.message); + console.log(d.error.stack); + } + failed = true; + } + clearTimeout(waitTo); + console.log('-----END TEST ' + url + ' -----'); + done(); + done = undefined; + }); + if (done) { setTimeout(logMore, 50); } + })); + }; + driver.get(url).then(waitFor(logMore)); + }).nThen; +}); + +nt(function (waitFor) { + driver.quit().then(waitFor(function () { + if (failed) { process.exit(100); } + })); }); diff --git a/WebRTCSrv.js b/WebRTCSrv.js deleted file mode 100644 index 2c1bc81e2..000000000 --- a/WebRTCSrv.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict' -let WebSocketServer = require('ws').Server -const UNSUPPORTED_DATA = 1007 -const POLICY_VIOLATION = 1008 -const CLOSE_UNSUPPORTED = 1003 - -var run = module.exports.run = function(server) { - server.on('connection', (socket) => { - if(socket.upgradeReq.url !== '/cryptpad_webrtc') { return; } - socket.on('message', (data) => { - try { - let msg = JSON.parse(data) - console.log(msg) - if (msg.hasOwnProperty('key')) { - for (let master of server.clients) { - if (master.key === msg.key) { - socket.close(POLICY_VIOLATION, 'The key already exists') - return - } - } - socket.key = msg.key - socket.joiningClients = [] - } else if (msg.hasOwnProperty('id')) { - for (let index in socket.joiningClients) { - if (index == msg.id) { - socket.joiningClients[index].send(JSON.stringify({data: msg.data})) - return - } - } - socket.close(POLICY_VIOLATION, 'Unknown id') - } else if (msg.hasOwnProperty('join')) { - for (let master of server.clients) { - if (master.key === msg.join) { - socket.master = master - master.joiningClients.push(socket) - let id = master.joiningClients.length - 1 - master.send(JSON.stringify({id, data: msg.data})) - return - } - } - socket.close(POLICY_VIOLATION, 'Unknown key') - } else if (msg.hasOwnProperty('data') && socket.hasOwnProperty('master')) { - let id = socket.master.joiningClients.indexOf(socket) - socket.master.send(JSON.stringify({id, data: msg.data})) - } else { - socket.close(UNSUPPORTED_DATA, 'Unsupported message format') - } - } catch (event) { - socket.close(CLOSE_UNSUPPORTED, 'Server accepts only JSON') - } - }) - - socket.on('close', (event) => { - if (socket.hasOwnProperty('joiningClients')) { - for (let client of socket.joiningClients) { - client.close(POLICY_VIOLATION, 'The peer is no longer available') - } - } - }); - }) -} \ No newline at end of file diff --git a/bower.json b/bower.json index c7100f505..b04ff3a6b 100644 --- a/bower.json +++ b/bower.json @@ -19,28 +19,26 @@ ], "dependencies": { "jquery": "~2.1.3", - "tweetnacl": "~0.12.2", + "tweetnacl": "0.12.2", "components-font-awesome": "^4.6.3", - "ckeditor": "~4.5.6", + "ckeditor": "~4.7", "codemirror": "^5.19.0", - "requirejs": "~2.1.15", - "reconnectingWebsocket": "", - "marked": "~0.3.5", + "requirejs": "2.1.15", + "marked": "0.3.5", "rangy": "rangy-release#~1.3.0", "json.sortify": "~2.1.0", - "fabric.js": "fabric#~1.6.0", - "hyperjson": "~1.3.1", + "secure-fabric.js": "secure-v1.7.9", + "hyperjson": "~1.4.0", "textpatcher": "^1.3.0", - "proxy-polyfill": "^0.1.5", - "chainpad": "^0.2.2", - "chainpad-json-validator": "^0.1.1", + "chainpad-json-validator": "^0.2.0", "chainpad-crypto": "^0.1.3", - "chainpad-listmap": "^0.2.0", - "lil-uri": "^0.2.1", - "file-saver": "^1.3.1", - "diff-dom": "#gh-pages", - "alertifyjs": "^1.0.11", - "spin.js": "^2.3.2", - "scrypt-async": "^1.2.0" + "chainpad-listmap": "^0.3.0", + "file-saver": "1.3.1", + "alertifyjs": "1.0.11", + "scrypt-async": "1.2.0", + "require-css": "0.1.10", + "less": "^2.7.2", + "bootstrap": "#v4.0.0-alpha.6", + "diff-dom": "2.1.1" } } diff --git a/config.example.js b/config.example.js new file mode 100644 index 000000000..081f2a04b --- /dev/null +++ b/config.example.js @@ -0,0 +1,293 @@ +/*@flow*/ +/* + globals module +*/ +module.exports = { + + // the address you want to bind to, :: means all ipv4 and ipv6 addresses + // this may not work on all operating systems + httpAddress: '::', + + // the port on which your httpd will listen + + /* CryptPad can be configured to send customized HTTP Headers + * These settings may vary widely depending on your needs + * Examples are provided below + */ + + httpHeaders: { + "X-XSS-Protection": "1; mode=block", + "X-Content-Type-Options": "nosniff", + // 'X-Frame-Options': 'SAMEORIGIN', + }, + + contentSecurity: [ + "default-src 'none'", + "style-src 'unsafe-inline' 'self'", + "script-src 'self'", + "font-src 'self' data:", + + /* child-src is used to restrict iframes to a set of allowed domains. + * connect-src is used to restrict what domains can connect to the websocket. + * + * it is recommended that you configure these fields to match the + * domain which will serve your CryptPad instance. + */ + "child-src 'self' blob: *", + + "media-src * blob:", + + /* this allows connections over secure or insecure websockets + if you are deploying to production, you'll probably want to remove + the ws://* directive, and change '*' to your domain + */ + "connect-src 'self' ws: wss: blob:", + + // data: is used by codemirror + "img-src 'self' data: blob:", + + // for accounts.cryptpad.fr authentication + "frame-ancestors 'self' accounts.cryptpad.fr", + ].join('; '), + + // CKEditor requires significantly more lax content security policy in order to function. + padContentSecurity: [ + "default-src 'none'", + "style-src 'unsafe-inline' 'self'", + // Unsafe inline, unsafe-eval are needed for ckeditor :( + "script-src 'self' 'unsafe-eval' 'unsafe-inline'", + "font-src 'self'", + + /* See above under 'contentSecurity' as to how these values should be + * configured for best effect. + */ + "child-src 'self' *", + + // see the comment above in the 'contentSecurity' section + "connect-src 'self' ws: wss:", + + // (insecure remote) images are included by users of the wysiwyg who embed photos in their pads + "img-src * blob:", + ].join('; '), + + httpPort: 3000, + + /* your server's websocket url is configurable + * (default: '/cryptpad_websocket') + * + * websocketPath can be relative, of the form '/path/to/websocket' + * or absolute, specifying a particular URL + * + * 'wss://cryptpad.fr:3000/cryptpad_websocket' + */ + websocketPath: '/cryptpad_websocket', + + /* it is assumed that your websocket will bind to the same port as http + * you can override this behaviour by supplying a number via websocketPort + */ + //websocketPort: 3000, + + /* if you want to run a different version of CryptPad but using the same websocket + * server, you should use the other server port as websocketPort and disable + * the websockets on that server + */ + //useExternalWebsocket: false, + + /* If CryptPad is proxied without using https, the server needs to know. + * Specify 'useSecureWebsockets: true' so that it can send + * Content Security Policy Headers that prevent http and https from mixing + */ + useSecureWebsockets: false, + + /* CryptPad can log activity to stdout + * This may be useful for debugging + */ + logToStdout: false, + + /* CryptPad supports verbose logging + * (false by default) + */ + verbose: false, + + /* Main pages + * add exceptions to the router so that we can access /privacy.html + * and other odd pages + */ + mainPages: [ + 'index', + 'privacy', + 'terms', + 'about', + 'contact', + ], + + /* Limits, Donations, Subscriptions and Contact + * + * By default, CryptPad limits every registered user to 50MB of storage. It also shows a + * donate button which allows for making a donation to support CryptPad development. + * + * You can either: + * A: Leave it exactly as it is. + * B: Hide the donate button. + * C: Change the donate button to a subscribe button, people who subscribe will get more + * storage on your instance and you get 50% of the revenue earned. + * + * CryptPad is developed by people who need to live and who deserve an equivilent life to + * what they would get at a company which monitizes user data. However, we intend to have + * a mutually positive relationship with every one of our users, including you. If you are + * getting value from CryptPad, you should be giving equal value back. + * + * If you are using CryptPad in a business context, please consider taking a support contract + * by contacting sales@cryptpad.fr + * + * If you choose A then there's nothing to do. + * + * If you choose B, set this variable to true and it will remove the donate button. + */ + removeDonateButton: false, + /* + * If you choose C, set allowSubscriptions to true, then set myDomain to the domain which people + * use to reach your CryptPad instance. Then contact sales@cryptpad.fr and tell us your domain. + * We will tell you what is needed to get paid. + */ + allowSubscriptions: false, + myDomain: 'i.did.not.read.my.config.myserver.tld', + + /* + * If you are using CryptPad internally and you want to increase the per-user storage limit, + * change the following value. + * + * Please note: This limit is what makes people subscribe and what pays for CryptPad + * development. Running a public instance that provides a "better deal" than cryptpad.fr + * is effectively using the project against itself. + */ + defaultStorageLimit: 50 * 1024 * 1024, + + /* + * By default, CryptPad also contacts our accounts server once a day to check for changes in + * the people who have accounts. This check-in will also send the version of your CryptPad + * instance and your email so we can reach you if we are aware of a serious problem. We will + * never sell it or send you marketing mail. If you want to block this check-in and remain + * completely invisible, set this and allowSubscriptions both to false. + */ + adminEmail: 'i.did.not.read.my.config@cryptpad.fr', + + + /* + You have the option of specifying an alternative storage adaptor. + These status of these alternatives are specified in their READMEs, + which are available at the following URLs: + + mongodb: a noSQL database + https://github.com/xwiki-labs/cryptpad-mongo-store + amnesiadb: in memory storage + https://github.com/xwiki-labs/cryptpad-amnesia-store + leveldb: a simple, fast, key-value store + https://github.com/xwiki-labs/cryptpad-level-store + sql: an adaptor for a variety of sql databases via knexjs + https://github.com/xwiki-labs/cryptpad-sql-store + + For the most up to date solution, use the default storage adaptor. + */ + storage: './storage/file', + + /* + CryptPad stores each document in an individual file on your hard drive. + Specify a directory where files should be stored. + It will be created automatically if it does not already exist. + */ + filePath: './datastore/', + + /* CryptPad allows logged in users to request that particular documents be + * stored by the server indefinitely. This is called 'pinning'. + * Pin requests are stored in a pin-store. The location of this store is + * defined here. + */ + pinPath: './pins', + + /* CryptPad allows logged in users to upload encrypted files. Files/blobs + * are stored in a 'blob-store'. Set its location here. + */ + blobPath: './blob', + + /* CryptPad stores incomplete blobs in a 'staging' area until they are + * fully uploaded. Set its location here. + */ + blobStagingPath: './blobstage', + + /* CryptPad's file storage adaptor closes unused files after a configurale + * number of milliseconds (default 30000 (30 seconds)) + */ + channelExpirationMs: 30000, + + /* CryptPad's file storage adaptor is limited by the number of open files. + * When the adaptor reaches openFileLimit, it will clean up older files + */ + openFileLimit: 2048, + + /* CryptPad's socket server can be extended to respond to RPC calls + * you can configure it to respond to custom RPC calls if you like. + * provide the path to your RPC module here, or `false` if you would + * like to disable the RPC interface completely + */ + rpc: './rpc.js', + + /* RPC errors are shown by default, but if you really don't care, + * you can suppress them + */ + suppressRPCErrors: false, + + + /* WARNING: EXPERIMENTAL + * + * CryptPad features experimental support for encrypted file upload. + * Our encryption format is still liable to change. As such, we do not + * guarantee that files uploaded now will be supported in the future + */ + + /* Setting this value to anything other than true will cause file upload + * attempts to be rejected outright. + */ + enableUploads: false, + + /* If you have enabled file upload, you have the option of restricting it + * to a list of users identified by their public keys. If this value is set + * to true, your server will query a file (cryptpad/privileged.conf) when + * users connect via RPC. Only users whose public keys can be found within + * the file will be allowed to upload. + * + * privileged.conf uses '#' for line comments, and splits keys by newline. + * This is a temporary measure until a better quota system is in place. + * registered users' public keys can be found on the settings page. + */ + //restrictUploads: false, + + /* Max Upload Size (bytes) + * this sets the maximum size of any one file uploaded to the server. + * anything larger than this size will be rejected + */ + maxUploadSize: 20 * 1024 * 1024, + + /* clients can use the /settings/ app to opt out of usage feedback + * which informs the server of things like how much each app is being + * used, and whether certain clientside features are supported by + * the client's browser. The intent is to provide feedback to the admin + * such that the service can be improved. Enable this with `true` + * and ignore feedback with `false` or by commenting the attribute + */ + //logFeedback: true, + + /* If you wish to see which remote procedure calls clients request, + * set this to true + */ + //logRPC: true, + + /* it is recommended that you serve CryptPad over https + * the filepaths below are used to configure your certificates + */ + //privKeyAndCertFiles: [ + // '/etc/apache2/ssl/my_secret.key', + // '/etc/apache2/ssl/my_public_cert.crt', + // '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca' + //], +}; diff --git a/config.js.dist b/config.js.dist deleted file mode 100644 index 76518baea..000000000 --- a/config.js.dist +++ /dev/null @@ -1,113 +0,0 @@ -/* - globals module -*/ -module.exports = { - - // the address you want to bind to, :: means all ipv4 and ipv6 addresses - // this may not work on all operating systems - httpAddress: '::', - - // the port on which your httpd will listen - - /* Cryptpad can be configured to send customized HTTP Headers - * These settings may vary widely depending on your needs - * Examples are provided below - */ - -/* - httpHeaders: { - "Content-Security-Policy": [ - "default-serc 'none'", - "style-src 'unsafe-inline' 'self'", - "script-src 'self' 'unsafe-eval' 'unsafe-inline'", - "child-src 'self' cryptpad.fr *.cryptpad.fr", - "font-src 'self'", - "connect-src 'self' wss://cryptpad.fr", - // data: is used by codemirror, (insecure remote) images are included by - // users of the wysiwyg who embed photos in their pads - "img-src data: *", - ].join('; '), - - "X-XSS-Protection": "1; mode=block", - "X-Content-Type-Options": "nosniff", - // 'X-Frame-Options': 'SAMEORIGIN', - },*/ - - httpPort: 3000, - - /* your server's websocket url is configurable - * (default: '/cryptpad_websocket') - * - * websocketPath can be relative, of the form '/path/to/websocket' - * or absolute, specifying a particular URL - * - * 'wss://cryptpad.fr:3000/cryptpad_websocket' - */ - websocketPath: '/cryptpad_websocket', - - /* it is assumed that your websocket will bind to the same port as http - * you can override this behaviour by supplying a number via websocketPort - */ - //websocketPort: 3000, - - /* If Cryptpad is proxied without using https, the server needs to know. - * Specify 'useSecureWebsockets: true' so that it can send - * Content Security Policy Headers that prevent http and https from mixing - */ - useSecureWebsockets: false, - - /* Cryptpad can log activity to stdout - * This may be useful for debugging - */ - logToStdout: false, - - /* Cryptpad supports verbose logging - * (false by default) - */ - verbose: false, - - - /* - You have the option of specifying an alternative storage adaptor. - These status of these alternatives are specified in their READMEs, - which are available at the following URLs: - - mongodb: a noSQL database - https://github.com/xwiki-labs/cryptpad-mongo-store - amnesiadb: in memory storage - https://github.com/xwiki-labs/cryptpad-amnesia-store - leveldb: a simple, fast, key-value store - https://github.com/xwiki-labs/cryptpad-level-store - sql: an adaptor for a variety of sql databases via knexjs - https://github.com/xwiki-labs/cryptpad-sql-store - - For the most up to date solution, use the default storage adaptor. - */ - storage: './storage/file', - - /* - Cryptpad stores each document in an individual file on your hard drive. - Specify a directory where files should be stored. - It will be created automatically if it does not already exist. - */ - filePath: './datastore/', - - /* Cryptpad's file storage adaptor closes unused files after a configurale - * number of milliseconds (default 30000 (30 seconds)) - */ - channelExpirationMs: 30000, - - /* Cryptpad's file storage adaptor is limited by the number of open files. - * When the adaptor reaches openFileLimit, it will clean up older files - */ - openFileLimit: 2048, - - /* it is recommended that you serve cryptpad over https - * the filepaths below are used to configure your certificates - */ - //privKeyAndCertFiles: [ - // '/etc/apache2/ssl/my_secret.key', - // '/etc/apache2/ssl/my_public_cert.crt', - // '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca' - //], -}; diff --git a/container-start.sh b/container-start.sh new file mode 100755 index 000000000..2aa4ae10f --- /dev/null +++ b/container-start.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# Creating customize folder +mkdir -p customize +[ -z "$(ls -A customize)" ] && echo "Creating customize folder" \ + && cp -R customize.dist/* customize/ \ + && cp config.example.js customize/config.js + +# Linking config.js +[ ! -h config.js ] && echo "Linking config.js" && ln -s customize/config.js config.js + +# Configure +[ -n "$USE_SSL" ] && echo "Using secure websockets: $USE_SSL" \ + && sed -i "s/useSecureWebsockets: .*/useSecureWebsockets: ${USE_SSL},/g" customize/config.js + +[ -n "$STORAGE" ] && echo "Using storage adapter: $STORAGE" \ + && sed -i "s/storage: .*/storage: ${STORAGE},/g" customize/config.js + +[ -n "$LOG_TO_STDOUT" ] && echo "Logging to stdout: $LOG_TO_STDOUT" \ + && sed -i "s/logToStdout: .*/logToStdout: ${LOG_TO_STDOUT},/g" customize/config.js + + +exec node ./server.js diff --git a/customize.dist/BottomBar.html b/customize.dist/BottomBar.html deleted file mode 100644 index c8d43dfce..000000000 --- a/customize.dist/BottomBar.html +++ /dev/null @@ -1,16 +0,0 @@ - -
-
-
- - - -

-

-
-
-

-

-
-
-
diff --git a/customize.dist/DecorateToolbar.js b/customize.dist/DecorateToolbar.js deleted file mode 100644 index 49a30c79f..000000000 --- a/customize.dist/DecorateToolbar.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - globals define -*/ -define([ - '/customize/languageSelector.js', - '/customize/messages.js', - '/bower_components/jquery/dist/jquery.min.js' -], function (LS, Messages) { - var $ = window.jQuery; - var main = function () { - var url = window.location.pathname; - var isHtml = /\.html/.test(url) || url === '/' || url === ''; - var isPoll = /\/poll\//.test(url); - if (!isHtml && !isPoll) { - Messages._applyTranslation(); - return; - } - $.ajax({ - url: isHtml ? '/customize/BottomBar.html' : '/customize/Header.html', - success: function (ret) { - var $bar = $(ret); - $('body').append($bar); - - var $sel = $bar.find('#language-selector'); - - Object.keys(Messages._languages).forEach(function (code) { - $sel.append($('