diff --git a/customize.dist/src/less2/include/comments.less b/customize.dist/src/less2/include/comments.less index 479609580..dc2ef0461 100644 --- a/customize.dist/src/less2/include/comments.less +++ b/customize.dist/src/less2/include/comments.less @@ -8,6 +8,10 @@ overflow-y: auto; color: @cryptpad_text_col; + &:empty { + display: none !important; + } + &.cp-comments-readonly { .cp-comment-actions { display: none !important; @@ -61,10 +65,6 @@ } } - #cp-comments-label { - display: none; - } - .cp-comment-container { outline: none; &:not(:focus) { diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 0eb400587..72e34d7c6 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -44,10 +44,11 @@ } /* local mixins */ + @drive_icon-margin: 10px; .drive_fileIcon { li { display: inline-block; - margin: 10px 10px; + margin: @drive_icon-margin; width: 140px; height: 140px; text-align: center; @@ -471,6 +472,7 @@ margin-top: 10px; } .cp-app-drive-content-info-box { + order: 10; line-height: 2em; padding: 0.25em 0.75em; margin: 1em; @@ -493,6 +495,7 @@ } } #cp-app-drive-content-folder { + order: 20; li { &.cp-app-drive-search-result { display: flex; @@ -546,7 +549,10 @@ .cp-app-drive-element-truncated { display: none; } } div.cp-app-drive-content-grid { - padding: 20px; + padding: 1em; + ul { + margin: -(@drive_icon-margin); + } .drive_fileIcon; li { &.cp-app-drive-element { @@ -754,6 +760,7 @@ } & > .cp-app-drive-path { + order: 1; width: 100%; height: @variables_bar-height; line-height: @variables_bar-height; @@ -774,6 +781,7 @@ flex-grow: 1; justify-content: flex-end; .cp-app-drive-path-element { + .tools_unselectable(); display: inline-block; flex-shrink: 0; max-width: 100%; @@ -788,7 +796,6 @@ overflow: hidden; text-overflow: ellipsis; transition: all 0.15s; - cursor: pointer; &:first-child { flex-shrink: 1; @@ -806,12 +813,15 @@ &.cp-app-drive-element-droppable { background-color: @drive_droppable-bg; } - &:not(.cp-app-drive-element-droppable):hover { - &:not(.cp-app-drive-path-separator) { - text-decoration: underline; - } - & ~ .cp-app-drive-path-element:not(.cp-app-drive-path-separator) { - text-decoration: underline; + &.cp-app-drive-path-clickable { + cursor: pointer; + &:hover { + &:not(.cp-app-drive-path-separator) { + text-decoration: underline; + } + & ~ .cp-app-drive-path-element:not(.cp-app-drive-path-separator) { + text-decoration: underline; + } } } } @@ -927,5 +937,11 @@ text-transform: uppercase; cursor: default; } + + .cp-app-drive-button { + order: 15; + margin: 0 1em; + } + } diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index 96f419310..92a0f375e 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -9,7 +9,7 @@ @alertify-input-bg: @colortheme_modal-input; @alertify-input-fg: @colortheme_modal-input-fg; - input:not(.form-control), textarea, div.cp-textarea { + input:not(.form-control):not([type="checkbox"]), textarea, div.cp-textarea { // background-color: @alertify-input-fg; color: @cryptpad_text_col; border: 1px solid @alertify-input-bg; diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less index c698b91b9..7876195c1 100644 --- a/customize.dist/src/less2/include/framework.less +++ b/customize.dist/src/less2/include/framework.less @@ -19,7 +19,11 @@ @import (reference) "./forms.less"; @import (reference) "./modals-ui-elements.less"; -.framework_main(@bg-color, @warn-color, @color) { +.framework_main( + @bg-color: @colortheme_default-bg, // color of the toolbar background + @warn-color: @colortheme_default-warn, // color of the warning text in the toolbar + @color: @colortheme_default-color, // Color of the text for the toolbar +) { --LessLoader_require: LessLoader_currentFile(); // Set the HTML style for the apps which shouldn't have a body scrollbar .app-noscroll_main(); diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 985f6bd83..3f949a255 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -713,8 +713,8 @@ box-sizing: border-box; cursor: auto; width: 300px; - font-size: 30px; - padding: 0 5px; + font-size: 30px !important; + padding: 0 5px !important; height: 43px; } } @@ -1033,6 +1033,7 @@ flex-flow: column; z-index: 10000; //Z cp-toolbar-drawer-content color: black; + .tools_unselectable(); .fa { font-size: 17px; } diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index f6e163910..93be85083 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -102,7 +102,6 @@ server { set $unsafe 0; # the following assets are loaded via the sandbox domain # they unfortunately still require exceptions to the sandboxing to work correctly. - if ($uri = "/pad/inner.html") { set $unsafe 1; } if ($uri = "/sheet/inner.html") { set $unsafe 1; } if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; } diff --git a/lib/commands/quota.js b/lib/commands/quota.js index 5c39a8d93..849a30e17 100644 --- a/lib/commands/quota.js +++ b/lib/commands/quota.js @@ -2,7 +2,8 @@ /* globals Buffer*/ const Quota = module.exports; -const Util = require("../common-util"); +//const Util = require("../common-util"); +const Keys = require("../keys"); const Package = require('../../package.json'); const Https = require("https"); @@ -19,11 +20,18 @@ Quota.applyCustomLimits = function (Env) { var customLimits = (function (custom) { var limits = {}; Object.keys(custom).forEach(function (k) { - k.replace(/\/([^\/]+)$/, function (all, safeKey) { - var id = Util.unescapeKeyCharacters(safeKey || ''); - limits[id] = custom[k]; - return ''; - }); + var user; + try { + user = Keys.parseUser(k); + } catch (err) { + return void Env.Log.error("PARSE_CUSTOM_LIMIT_BLOCK", { + user: k, + error: err.message, + }); + } + + var unsafeKey = user.pubkey; + limits[unsafeKey] = custom[k]; }); return limits; }(Env.customLimits || {})); diff --git a/lib/keys.js b/lib/keys.js new file mode 100644 index 000000000..7eb4ce447 --- /dev/null +++ b/lib/keys.js @@ -0,0 +1 @@ +module.exports = require("../www/common/common-signing-keys"); diff --git a/scripts/expire-channels.js b/scripts/expire-channels.js index 9151398d2..8f9d9f87e 100644 --- a/scripts/expire-channels.js +++ b/scripts/expire-channels.js @@ -10,7 +10,7 @@ nThen(function (w) { config.log = _log; })); }).nThen(function (w) { - FileStorage.create(config, w(function (_store) { + FileStorage.create(config, w(function (err, _store) { config.store = _store; // config.taskPath diff --git a/server.js b/server.js index 8508eba83..2f8dce43b 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ 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"); @@ -128,7 +129,7 @@ var setHeaders = (function () { if (Object.keys(headers).length) { return function (req, res) { const h = [ - /^\/pad\/inner\.html.*/, + ///^\/pad\/inner\.html.*/, /^\/common\/onlyoffice\/.*\/index\.html.*/, /^\/(sheet|ooslide|oodoc)\/inner\.html.*/, ].some((regex) => { @@ -201,9 +202,11 @@ app.use(/^\/[^\/]*$/, Express.static('customize.dist')); var admins = []; try { admins = (config.adminKeys || []).map(function (k) { - k = k.replace(/\/+$/, ''); - var s = k.split('/'); - return s[s.length-1].replace(/-/g, '/'); + // return each admin's "unsafeKey" + // this might throw and invalidate all the other admin's keys + // but we want to get the admin's attention anyway. + // breaking everything is a good way to accomplish that. + return Keys.parseUser(k).pubkey; }); } catch (e) { console.error("Can't parse admin keys"); } diff --git a/www/common/common-hash.js b/www/common/common-hash.js index b60ab3306..cc4344413 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -1,5 +1,5 @@ (function (window) { -var factory = function (Util, Crypto, Nacl) { +var factory = function (Util, Crypto, Keys, Nacl) { var Hash = window.CryptPad_Hash = {}; var uint8ArrayToHex = Util.uint8ArrayToHex; @@ -92,9 +92,7 @@ var factory = function (Util, Crypto, Nacl) { } }; - Hash.getUserHrefFromKeys = function (origin, username, pubkey) { - return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); - }; + Hash.getPublicSigningKeyString = Keys.serialize; var fixDuplicateSlashes = function (s) { return s.replace(/\/+/g, '/'); @@ -568,14 +566,20 @@ Version 1 }; if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory(require("./common-util"), require("chainpad-crypto"), require("tweetnacl/nacl-fast")); + module.exports = factory( + require("./common-util"), + require("chainpad-crypto"), + require("./common-signing-keys"), + require("tweetnacl/nacl-fast") + ); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ '/common/common-util.js', '/bower_components/chainpad-crypto/crypto.js', + '/common/common-signing-keys.js', '/bower_components/tweetnacl/nacl-fast.min.js' - ], function (Util, Crypto) { - return factory(Util, Crypto, window.nacl); + ], function (Util, Crypto, Keys) { + return factory(Util, Crypto, Keys, window.nacl); }); } else { // unsupported initialization diff --git a/www/common/common-signing-keys.js b/www/common/common-signing-keys.js new file mode 100644 index 000000000..15adec7df --- /dev/null +++ b/www/common/common-signing-keys.js @@ -0,0 +1,89 @@ +(function () { +var factory = function () { + var Keys = {}; + +/* Parse the new format of "Signing Public Keys". + If anything about the input is found to be invalid, return; + this will fall back to the old parsing method + + +*/ + var parseNewUser = function (userString) { + if (!/^\[.*?@.*\]$/.test(userString)) { return; } + var temp = userString.slice(1, -1); + var domain, username, pubkey; + + temp = temp + .replace(/\/([a-zA-Z0-9+-]{43}=)$/, function (all, k) { + pubkey = k.replace(/-/g, '/'); + return ''; + }); + if (!pubkey) { return; } + + var index = temp.lastIndexOf('@'); + if (index < 1) { return; } + + domain = temp.slice(index + 1); + username = temp.slice(0, index); + + return { + domain: domain, + user: username, + pubkey: pubkey + }; + }; + + var isValidUser = function (parsed) { + if (!parsed) { return; } + if (!(parsed.domain && parsed.user && parsed.pubkey)) { return; } + return true; + }; + + Keys.parseUser = function (user) { + var parsed = parseNewUser(user); + if (isValidUser(parsed)) { return parsed; } + + var domain, username, pubkey; + user.replace(/^https*:\/\/([^\/]+)\/user\/#\/1\/([^\/]+)\/([a-zA-Z0-9+-]{43}=)$/, + function (a, d, u, k) { + domain = d; + username = u; + pubkey = k.replace(/-/g, '/'); + return ''; + }); + if (!domain) { throw new Error("Could not parse user id [" + user + "]"); } + return { + domain: domain, + user: username, + pubkey: pubkey + }; + }; + +/* + +0. usernames may contain spaces or many other wacky characters, so enclose the whole thing in square braces so we know its boundaries. If the formatted string does not include these we know it is either a _v1 public key string_ or _an incomplete string_. Start parsing by removing them. +1. public keys should have a fixed length, so slice them off of the end of the string. +2. domains cannot include `@`, so find the last occurence of it in the signing key and slice everything thereafter. +3. the username is everything before the `@`. + +*/ + Keys.serialize = function (origin, username, pubkey) { + return '[' + + username + + '@' + + origin.replace(/https*:\/\//, '') + + '/' + + pubkey.replace(/\//g, '-') + + ']'; + // return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); + }; + + return Keys; +}; + + if (typeof(module) !== 'undefined' && module.exports) { + module.exports = factory(); + } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { + define([], factory); + } +}()); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 386353339..fc514ef3b 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2299,7 +2299,9 @@ define([ value = val; var $val = $innerblock.find('[data-value="'+val+'"]'); var textValue = name || $val.html() || val; - $button.find('.cp-dropdown-button-title').html(textValue); + setTimeout(function () { + $button.find('.cp-dropdown-button-title').html(textValue); + }); }; $container.getValue = function () { return value || ''; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 19faf940b..af34260a9 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1604,6 +1604,26 @@ define([ var allocated = Login.allocateBytes(bytes); blockKeys = allocated.blockKeys; })); + }).nThen(function (waitFor) { + var blockUrl = Block.getBlockUrl(blockKeys); + // Check whether there is a block at that location + Util.fetch(blockUrl, waitFor(function (err, block) { + // If there is no block or the block is invalid, continue. + if (err) { + console.log("no block found"); + return; + } + + var decryptedBlock = Block.decrypt(block, blockKeys); + if (!decryptedBlock) { + console.error("Found a login block but failed to decrypt"); + return; + } + + // If there is already a valid block, abort! We risk overriding another user's data + waitFor.abort(); + cb({ error: 'EEXISTS' }); + })); }).nThen(function (waitFor) { // Write the new login block var temp = { @@ -1966,7 +1986,7 @@ define([ }).nThen(function (waitFor) { var blockHash = LocalStore.getBlockHash(); if (blockHash) { - console.log(blockHash); + console.debug("Block hash is present"); var parsed = Block.parseBlockHash(blockHash); if (typeof(parsed) !== 'object') { diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index cd3759d8d..9643a4b1b 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -114,7 +114,6 @@ define([ var $trashEmptyIcon = $('', {"class": "fa fa-trash-o"}); //var $collapseIcon = $('', {"class": "fa fa-minus-square-o cp-app-drive-icon-expcol"}); var $expandIcon = $('', {"class": "fa fa-plus-square-o cp-app-drive-icon-expcol"}); - var $emptyTrashIcon = $('