From 811463b870c4abaa1a9a89a137817ab731a03a00 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 24 Apr 2018 17:22:33 +0200 Subject: [PATCH 1/6] Add support for version 2 hashes needed for password-protected pads --- www/assert/main.js | 24 ++++ www/common/common-hash.js | 215 +++++++++++++++++++++++------- www/common/common-ui-elements.js | 8 +- www/common/cryptget.js | 14 +- www/common/cryptpad-common.js | 10 +- www/common/outer/async-store.js | 10 +- www/common/outer/local-store.js | 2 +- www/common/outer/upload.js | 9 +- www/common/sframe-common-outer.js | 30 +++-- www/common/sframe-common.js | 1 + www/drive/inner.js | 22 +-- www/file/inner.js | 1 + www/profile/main.js | 6 +- www/todo/main.js | 2 +- 14 files changed, 254 insertions(+), 100 deletions(-) diff --git a/www/assert/main.js b/www/assert/main.js index eb470eb35..c50360e53 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -223,6 +223,30 @@ define([ hd.type === 'invite'); }, "test support for invite urls"); + // test support for V2 + assert(function (cb) { + var secret = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/'); + return cb(secret.hashData.version === 2 && + secret.hashData.mode === "edit" && + secret.hashData.type === "pad" && + secret.hashData.channel === "2NUbSuqGPz8FD0f4rSYXUw" && + secret.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" && + window.nacl.util.encodeBase64(secret.hashData.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" && + secret.hashData.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" && + !secret.hashData.present); + }, "test support for version 2 hash failed to parse"); + assert(function (cb) { + var secret = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew'); + return cb(secret.hashData.version === 2 && + secret.hashData.mode === "edit" && + secret.hashData.type === "pad" && + secret.hashData.channel === "P7bck4B9kDr-OQtfeYySyQ" && + secret.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" && + window.nacl.util.encodeBase64(secret.hashData.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" && + secret.hashData.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" && + secret.hashData.embed); + }, "test support for password in version 2 hash failed to parse"); + assert(function (cb) { var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/'; var secret = Hash.parsePadUrl(url); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 870b19dfe..852260acc 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -19,7 +19,50 @@ define([ .decodeUTF8(JSON.stringify(list)))); }; - var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) { + var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) { + var version = secret.version; + var data = secret.keys; + if (version === 0) { + return secret.channel + secret.key; + } + if (version === 1) { + if (!data.editKeyStr) { return; } + return '/1/edit/' + hexToBase64(secret.channel) + + '/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/'; + } + if (version === 2) { + if (!data.editKeyStr) { return; } + var pass = secret.password ? 'p/' : ''; + return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass; + } + }; + var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) { + var version = secret.version; + var data = secret.keys; + if (version === 0) { return; } + if (version === 1) { + if (!data.viewKeyStr) { return; } + return '/1/view/' + hexToBase64(secret.channel) + + '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/'; + } + if (version === 2) { + if (!data.viewKeyStr) { return; } + var pass = secret.password ? 'p/' : ''; + return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass; + } + }; + var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) { + var version = secret.version; + var data = secret.keys; + if (version === 0) { return; } + if (version === 1) { + return '/1/' + hexToBase64(secret.channel) + '/' + + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/'; + } + }; + + // V1 + /*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) { if (typeof keys === 'string') { return chanKey + keys; } @@ -34,7 +77,7 @@ define([ }; var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) { return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/'; - }; + };*/ Hash.getUserHrefFromKeys = function (origin, username, pubkey) { return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-'); }; @@ -43,6 +86,24 @@ define([ return s.replace(/\/+/g, '/'); }; + Hash.createChannelId = function () { + var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); + if (id.length !== 32 || /[^a-f0-9]/.test(id)) { + throw new Error('channel ids must consist of 32 hex characters'); + } + return id; + }; + + Hash.createRandomHash = function (type, password) { + var cryptor = Crypto.createEditCryptor2(void 0, void 0, password); + return getEditHashFromKeys({ + password: Boolean(password), + version: 2, + type: type, + keys: { editKeyStr: cryptor.editKeyStr } + }); + }; + /* Version 0 /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy @@ -50,25 +111,49 @@ Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI */ - var parseTypeHash = Hash.parseTypeHash = function (type, hash) { + var parseTypeHash = Hash.parseTypeHash = function (type, hash, password) { if (!hash) { return; } var parsed = {}; var hashArr = fixDuplicateSlashes(hash).split('/'); if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { parsed.type = 'pad'; - if (hash.slice(0,1) !== '/' && hash.length >= 56) { + if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 // Old hash parsed.channel = hash.slice(0, 32); parsed.key = hash.slice(32, 56); parsed.version = 0; return parsed; } - if (hashArr[1] && hashArr[1] === '1') { + var options; + if (hashArr[1] && hashArr[1] === '1') { // Version 1 parsed.version = 1; parsed.mode = hashArr[2]; parsed.channel = hashArr[3]; - parsed.key = hashArr[4].replace(/-/g, '/'); - var options = hashArr.slice(5); + parsed.key = Crypto.b64AddSlashes(hashArr[4]); + + options = hashArr.slice(5); + parsed.present = options.indexOf('present') !== -1; + parsed.embed = options.indexOf('embed') !== -1; + return parsed; + } + if (hashArr[1] && hashArr[1] === '2') { // Version 2 + parsed.version = 2; + parsed.app = hashArr[2]; + parsed.mode = hashArr[3]; + parsed.key = hashArr[4]; + + var cryptor; + if (parsed.mode === "edit") { + cryptor = Crypto.createEditCryptor2(parsed.key, void 0, password); + } else if (parsed.mode === "view") { + cryptor = Crypto.createViewCryptor2(parsed.key, password); + } + parsed.channel = cryptor.chanId; + parsed.cryptKey = cryptor.cryptKey; + parsed.validateKey = cryptor.validateKey; + + options = hashArr.slice(5); + parsed.password = options.indexOf('p') !== -1; parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; return parsed; @@ -107,7 +192,7 @@ Version 1 } return; }; - var parsePadUrl = Hash.parsePadUrl = function (href) { + var parsePadUrl = Hash.parsePadUrl = function (href, password) { var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; var ret = {}; @@ -125,17 +210,33 @@ Version 1 url += ret.type + '/'; if (!ret.hashData) { return url; } if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } - if (ret.hashData.version !== 1) { return url + '#' + ret.hash; } - url += '#/' + ret.hashData.version + - '/' + ret.hashData.mode + - '/' + ret.hashData.channel.replace(/\//g, '-') + - '/' + ret.hashData.key.replace(/\//g, '-') +'/'; - if (options.embed) { - url += 'embed/'; - } - if (options.present) { - url += 'present/'; + if (ret.hashData.version === 0) { return url + '#' + ret.hash; } + var hash; + if (options.readOnly === true || + (typeof (options.readOnly === "undefined") && ret.hashData.mode === "view")) { + hash = getViewHashFromKeys({ + version: ret.hashData.version, + type: ret.hashData.app, + channel: base64ToHex(ret.hashData.channel || ''), + password: ret.hashData.password, + keys: { + viewKeyStr: ret.hashData.key + } + }); + } else { + hash = getEditHashFromKeys({ + version: ret.hashData.version, + type: ret.hashData.app, + channel: base64ToHex(ret.hashData.channel || ''), + password: ret.hashData.password, + keys: { + editKeyStr: ret.hashData.key + } + }); } + url += '#' + hash; + if (options.embed) { url += 'embed/'; } + if (options.present) { url += 'present/'; } return url; }; @@ -143,7 +244,7 @@ Version 1 idx = href.indexOf('/#'); ret.type = href.slice(1, idx); ret.hash = href.slice(idx + 2); - ret.hashData = parseTypeHash(ret.type, ret.hash); + ret.hashData = parseTypeHash(ret.type, ret.hash, password); return ret; } @@ -154,7 +255,7 @@ Version 1 }); idx = href.indexOf('/#'); ret.hash = href.slice(idx + 2); - ret.hashData = parseTypeHash(ret.type, ret.hash); + ret.hashData = parseTypeHash(ret.type, ret.hash, password); return ret; }; @@ -170,11 +271,13 @@ Version 1 * - no argument: use the URL hash or create one if it doesn't exist * - secretHash provided: use secretHash to find the keys */ - Hash.getSecrets = function (type, secretHash) { + Hash.getSecrets = function (type, secretHash, password) { var secret = {}; var generate = function () { - secret.keys = Crypto.createEditCryptor(); - secret.key = Crypto.createEditCryptor().editKeyStr; + secret.keys = Crypto.createEditCryptor2(void 0, void 0, password); + secret.channel = base64ToHex(secret.keys.chanId); + secret.version = 2; + secret.type = type; }; if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) { generate(); @@ -203,9 +306,10 @@ Version 1 // Old hash secret.channel = parsed.channel; secret.key = parsed.key; - } - else if (parsed.version === 1) { + secret.version = 0; + } else if (parsed.version === 1) { // New hash + secret.version = 1; if (parsed.type === "pad") { secret.channel = base64ToHex(parsed.channel); if (parsed.mode === 'edit') { @@ -229,45 +333,60 @@ Version 1 // version 2 hashes are to be used for encrypted blobs throw new Error("User hashes can't be opened (yet)"); } + } else if (parsed.version === 2) { + // New hash + secret.version = 2; + secret.type = type; + secret.password = Boolean(password); + if (parsed.type === "pad") { + if (parsed.mode === 'edit') { + secret.keys = Crypto.createEditCryptor2(parsed.key); + secret.channel = base64ToHex(secret.keys.chanId); + secret.key = secret.keys.editKeyStr; + if (secret.channel.length !== 32 || secret.key.length !== 24) { + throw new Error("The channel key and/or the encryption key is invalid"); + } + } + else if (parsed.mode === 'view') { + secret.keys = Crypto.createViewCryptor2(parsed.key); + secret.channel = base64ToHex(secret.keys.chanId); + if (secret.channel.length !== 32) { + throw new Error("The channel key is invalid"); + } + } + } else if (parsed.type === "file") { + throw new Error("File hashes should be version 1"); + } else if (parsed.type === "user") { + throw new Error("User hashes can't be opened (yet)"); + } } } return secret; }; - Hash.getHashes = function (channel, secret) { + Hash.getHashes = function (secret) { var hashes = {}; - if (!secret.keys) { + secret = JSON.parse(JSON.stringify(secret)); + + if (!secret.keys && !secret.key) { console.error('e'); return hashes; + } else if (!secret.keys) { + secret.keys = {}; } - if (secret.keys.editKeyStr) { - hashes.editHash = getEditHashFromKeys(channel, secret.keys); + + if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) { + hashes.editHash = getEditHashFromKeys(secret); } if (secret.keys.viewKeyStr) { - hashes.viewHash = getViewHashFromKeys(channel, secret.keys); + hashes.viewHash = getViewHashFromKeys(secret); } if (secret.keys.fileKeyStr) { - hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr); + hashes.fileHash = getFileHashFromKeys(secret); } return hashes; }; - var createChannelId = Hash.createChannelId = function () { - var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); - if (id.length !== 32 || /[^a-f0-9]/.test(id)) { - throw new Error('channel ids must consist of 32 hex characters'); - } - return id; - }; - - Hash.createRandomHash = function () { - // 16 byte channel Id - var channelId = Util.hexToBase64(createChannelId()); - // 18 byte encryption key - var key = Crypto.b64RemoveSlashes(Crypto.rand64(18)); - return '/1/edit/' + [channelId, key].join('/') + '/'; - }; - // STORAGE Hash.findWeaker = function (href, recents) { var rHref = href || getRelativeHref(window.location.href); @@ -336,7 +455,7 @@ Version 1 parsed = parsed.hashData; if (parsed.version === 0) { return parsed.channel; - } else if (parsed.version !== 1 && parsed.version !== 2) { + } else if (!parsed.version) { console.error("parsed href had no version"); console.error(parsed); return; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 2b9487a73..2d09b2abe 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -60,6 +60,10 @@ define([ var getPropertiesData = function (common, cb) { var data = {}; NThen(function (waitFor) { + common.getPadAttribute('password', waitFor(function (err, val) { + data.password = val; + })); + }).nThen(function (waitFor) { common.getPadAttribute('href', waitFor(function (err, val) { var base = common.getMetadataMgr().getPrivateData().origin; @@ -75,9 +79,9 @@ define([ if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; var hBase = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash); + var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password); if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); + var viewHash = Hash.getViewHashFromKeys(hrefsecret); data.roHref = hBase + viewHash; })); common.getPadAttribute('atime', waitFor(function (err, val) { diff --git a/www/common/cryptget.js b/www/common/cryptget.js index 686c69884..d5cb3edc1 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -20,9 +20,9 @@ define([ } }; - var makeConfig = function (hash) { + var makeConfig = function (hash, password) { // We can't use cryptget with a file or a user so we can use 'pad' as hash type - var secret = Hash.getSecrets('pad', hash); + var secret = Hash.getSecrets('pad', hash, password); if (!secret.keys) { secret.keys = secret.key; } // support old hashses var config = { websocketURL: NetConfig.getWebsocketURL(), @@ -43,12 +43,15 @@ define([ Object.keys(b).forEach(function (k) { a[k] = b[k]; }); }; + // XXX make sure we pass the password here in opt var get = function (hash, cb, opt) { if (typeof(cb) !== 'function') { throw new Error('Cryptget expects a callback'); } + opt = opt || {}; + + var config = makeConfig(hash, opt.password); var Session = { cb: cb, }; - var config = makeConfig(hash); config.onReady = function (info) { var rt = Session.session = info.realtime; @@ -60,13 +63,16 @@ define([ Session.realtime = CPNetflux.start(config); }; + // XXX make sure we pass the password here in opt var put = function (hash, doc, cb, opt) { if (typeof(cb) !== 'function') { throw new Error('Cryptput expects a callback'); } + opt = opt || {}; - var config = makeConfig(hash); + var config = makeConfig(hash, opt.password); var Session = { cb: cb, }; + config.onReady = function (info) { var realtime = Session.session = info.realtime; Session.network = info.network; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d4adb1ee0..e473b397c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -260,8 +260,8 @@ define([ }); }; - common.isNewChannel = function (href, cb) { - postMessage('IS_NEW_CHANNEL', {href: href}, function (obj) { + common.isNewChannel = function (href, password, cb) { + postMessage('IS_NEW_CHANNEL', {href: href, password: password}, function (obj) { if (obj.error) { return void cb(obj.error); } if (!obj) { return void cb('INVALID_RESPONSE'); } cb(undefined, obj.isNew); @@ -395,7 +395,7 @@ define([ common.saveAsTemplate = function (Cryptput, data, cb) { var p = Hash.parsePadUrl(window.location.href); if (!p.type) { return; } - var hash = Hash.createRandomHash(); + var hash = Hash.createRandomHash(p.type); var href = '/' + p.type + '/#' + hash; Cryptput(hash, data.toSave, function (e) { if (e) { throw new Error(e); } @@ -556,13 +556,13 @@ define([ common.getShareHashes = function (secret, cb) { var hashes; if (!window.location.hash) { - hashes = Hash.getHashes(secret.channel, secret); + hashes = Hash.getHashes(secret); return void cb(null, hashes); } var parsed = Hash.parsePadUrl(window.location.href); if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } - hashes = Hash.getHashes(secret.channel, secret); + hashes = Hash.getHashes(secret); if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { // It means we're using an old hash diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 0125e6018..676277475 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -316,7 +316,7 @@ define([ Store.isNewChannel = function (data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } - var channelId = Hash.hrefToHexChannelId(data.href); + var channelId = Hash.hrefToHexChannelId(data.href, data.password); store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) { if (e) { return void cb({error: e}); } if (response && response.length && typeof(response[0]) === 'boolean') { @@ -524,7 +524,7 @@ define([ */ Store.createReadme = function (data, cb) { require(['/common/cryptget.js'], function (Crypt) { - var hash = Hash.createRandomHash(); + var hash = Hash.createRandomHash('pad'); Crypt.put(hash, data.driveReadme, function (e) { if (e) { return void cb({ error: "Error while creating the default pad:"+ e}); @@ -717,7 +717,7 @@ define([ // If the hash is different but represents the same channel, check if weaker or stronger if (!shouldUpdate && - h.version === 1 && h2.version === 1 && + h.version === h2.version && h.channel === h2.channel) { // We had view & now we have edit, update if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } @@ -1123,7 +1123,7 @@ define([ }; var connect = function (data, cb) { - var hash = data.userHash || data.anonHash || Hash.createRandomHash(); + var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive'); storeHash = hash; if (!hash) { throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); @@ -1150,7 +1150,7 @@ define([ store.realtime = info.realtime; store.network = info.network; if (!data.userHash) { - returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys); + returned.anonHash = Hash.getEditHashFromKeys(secret); } }).on('ready', function () { if (store.userObject) { return; } // the store is already ready, it is a reconnection diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index 7e93d34e7..a40a3e6f5 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -108,7 +108,7 @@ define([ // Make sure we have an FS_hash in localStorage before reloading all the tabs // so that we don't end up with tabs using different anon hashes if (!LocalStore.getFSHash()) { - LocalStore.setFSHash(Hash.createRandomHash()); + LocalStore.setFSHash(Hash.createRandomHash('drive')); } eraseTempSessionValues(); diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index cce3e6b3c..ee9f4d77c 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -51,7 +51,14 @@ define([ var b64Key = Nacl.util.encodeBase64(key); - var hash = Hash.getFileHashFromKeys(id, b64Key); + var secret = { + version: 1, + channel: id, + keys: { + fileKeyStr: b64Key + } + }; + var hash = Hash.getFileHashFromKeys(secret); var href = '/file/#' + hash; var title = metadata.name; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 014914b50..e6543bb30 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -121,11 +121,17 @@ define([ }); })); } else { - secret = Utils.Hash.getSecrets(); - if (!secret.channel) { + var parsedType = Utils.Hash.parsePadUrl(window.location.href).type; + // XXX prompt the password here if we have a hash containing /p/ + // OR get it from the pad attributes + secret = Utils.Hash.getSecrets(parsedType); + + // TODO: New hashes V2 already contain a channel ID so we can probably remove the following lines + //if (!secret.channel) { // New pad: create a new random channel id - secret.channel = Utils.Hash.createChannelId(); - } + //secret.channel = Utils.Hash.createChannelId(); + //} + Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); } }).nThen(function (waitFor) { @@ -133,7 +139,9 @@ define([ if (!window.location.hash) { isNewFile = true; return; } if (realtime) { - Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) { + // XXX get password + var password; + Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) { if (e) { return console.error(e); } isNewFile = Boolean(isNew); })); @@ -635,7 +643,7 @@ define([ isNewHash: isNewHash, readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), - onConnect: function (wc) { + onConnect: function () { if (window.location.hash && window.location.hash !== '#') { window.location = parsed.getUrl({ present: parsed.hashData.present, @@ -644,7 +652,7 @@ define([ return; } if (readOnly || cfg.noHash) { return; } - replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys)); + replaceHash(Utils.Hash.getEditHashFromKeys(secret)); } }; @@ -671,8 +679,10 @@ define([ sframeChan.on('Q_CREATE_PAD', function (data, cb) { if (!isNewFile || rtStarted) { return; } // Create a new hash - var newHash = Utils.Hash.createRandomHash(); - secret = Utils.Hash.getSecrets(parsed.type, newHash); + // XXX add password here + var password = data.password; + var newHash = Utils.Hash.createRandomHash(parsed.type, password); + secret = Utils.Hash.getSecrets(parsed.type, newHash, password); // Update the hash in the address bar var ohc = window.onhashchange; @@ -684,7 +694,7 @@ define([ // Update metadata values and send new metadata inside parsed = Utils.Hash.parsePadUrl(window.location.href); defaultTitle = Utils.Hash.getDefaultName(parsed); - hashes = Utils.Hash.getHashes(secret.channel, secret); + hashes = Utils.Hash.getHashes(secret); readOnly = false; updateMeta(); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index ab3c2389d..a56afa382 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -114,6 +114,7 @@ define([ }; funcs.getMediatagFromHref = function (href) { var parsed = Hash.parsePadUrl(href); + // FILE_HASHES2 var secret = Hash.getSecrets('file', parsed.hash); var data = ctx.metadataMgr.getPrivateData(); if (secret.keys && secret.channel) { diff --git a/www/drive/inner.js b/www/drive/inner.js index df9708d97..08c067183 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2653,9 +2653,9 @@ define([ if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; var base = data.href.slice(0, i); - var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash); + var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password); if (!hrefsecret.keys) { return; } - var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys); + var viewHash = Hash.getViewHashFromKeys(hrefsecret); return base + viewHash; }; @@ -2720,24 +2720,6 @@ define([ $(window).focus(); if (!res) { return; } filesOp.delete(pathsList, refresh); - /* - // Try to delete each selected pad from server, and delete from drive if no error - var n = nThen(function () {}); - pathsList.forEach(function (p) { - var el = filesOp.find(p); - var data = filesOp.getFileData(el); - var parsed = Hash.parsePadUrl(data.href); - var channel = Util.base64ToHex(parsed.hashData.channel); - n = n.nThen(function (waitFor) { - sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel, - waitFor(function (e) { - if (e) { return void console.error(e); } - filesOp.delete([p], function () {}, false, true); - })); - }); - }); - n.nThen(function () { refresh(); }); - */ }); }; $contextMenu.on("click", "a", function(e) { diff --git a/www/file/inner.js b/www/file/inner.js index 8ad9532ad..98a77f647 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -61,6 +61,7 @@ define([ if (!priv.filehash) { uploadMode = true; } else { + // FILE_HASHES2 secret = Hash.getSecrets('file', priv.filehash); if (!secret.keys) { throw new Error("You need a hash"); } hexFileName = Util.base64ToHex(secret.channel); diff --git a/www/profile/main.js b/www/profile/main.js index c4b3847a4..90557d793 100644 --- a/www/profile/main.js +++ b/www/profile/main.js @@ -58,7 +58,7 @@ define([ window.location.href = '/drive'; return void cb(); } - var hash = Hash.createRandomHash(); + var hash = Hash.createRandomHash('profile'); var secret = Hash.getSecrets('profile', hash); Cryptpad.pinPads([secret.channel], function (e) { if (e) { @@ -69,8 +69,8 @@ define([ //return void UI.log(Messages._getKey('profile_error', [e])) // TODO } var profile = {}; - profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys); - profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys); + profile.edit = Utils.Hash.getEditHashFromKeys(secret); + profile.view = Utils.Hash.getViewHashFromKeys(secret); Cryptpad.setNewProfile(profile); }); cb(null, secret); diff --git a/www/todo/main.js b/www/todo/main.js index b86546bf6..3b1db1ef2 100644 --- a/www/todo/main.js +++ b/www/todo/main.js @@ -38,7 +38,7 @@ define([ }).nThen(function (/*waitFor*/) { var getSecrets = function (Cryptpad, Utils, cb) { Cryptpad.getTodoHash(function (hash) { - var nHash = hash || Utils.Hash.createRandomHash(); + var nHash = hash || Utils.Hash.createRandomHash('todo'); if (!hash) { Cryptpad.setTodoHash(nHash); } cb(null, Utils.Hash.getSecrets('todo', nHash)); }); From 1879c1829c670777dbe7778f028d503d4582d060 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Apr 2018 19:03:58 +0200 Subject: [PATCH 2/6] Add passwod prompt to access protected pads --- customize.dist/loading.js | 32 ++++++++ .../src/less2/include/creation.less | 34 ++++++-- customize.dist/translations/messages.fr.js | 6 ++ customize.dist/translations/messages.js | 6 ++ www/code/inner.js | 1 + www/common/common-hash.js | 34 ++++---- www/common/common-interface.js | 8 +- www/common/common-ui-elements.js | 81 +++++++++++++++++-- www/common/cryptget.js | 2 - www/common/cryptpad-common.js | 44 ++++++++-- www/common/mergeDrive.js | 14 +--- www/common/outer/async-store.js | 12 ++- www/common/outer/store-rpc.js | 3 + www/common/sframe-common-outer.js | 59 ++++++++++---- www/common/sframe-common.js | 10 +++ www/common/sframe-protocol.js | 4 + www/poll/inner.js | 2 - 17 files changed, 282 insertions(+), 70 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index a2c1dcdfc..80e064a67 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -81,6 +81,38 @@ define([], function () { text-align: center; display: none; } +#cp-loading-password-prompt { + font-size: 18px; +} +#cp-loading-password-prompt .cp-password-error { + color: white; + background: #9e0000; + padding: 5px; + margin-bottom: 15px; +} +#cp-loading-password-prompt .cp-password-info { + text-align: left; + margin-bottom: 15px; +} +#cp-loading-password-prompt .cp-password-form { + display: flex; + justify-content: space-around; +} +#cp-loading-password-prompt .cp-password-form * { + background-color: #4591c4; + color: white; + border: 1px solid #4591c4; +} +#cp-loading-password-prompt .cp-password-form input { + flex: 1; + margin-right: 15px; + padding: 0 5px; + min-width: 0; + text-overflow: ellipsis; +} +#cp-loading-password-prompt .cp-password-form button:hover { + background-color: #326599; +} #cp-loading .cp-loading-spinner-container { position: relative; height: 100px; diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index b2f6fa46b..f37fbdb35 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -144,16 +144,18 @@ max-height: 100px; } } + + input, select { + font-size: 14px; + border: 1px solid @colortheme_form-border; + height: 26px; + background-color: @colortheme_form-bg; + color: @colortheme_form-color; + } + .cp-creation-expire { .cp-creation-expire-picker { text-align: center; - input, select { - font-size: 14px; - border: 1px solid @colortheme_form-border; - height: 26px; - background-color: @colortheme_form-bg; - color: @colortheme_form-color; - } input { width: 50px; margin: 0 5px; @@ -171,6 +173,22 @@ } } } + .cp-creation-password { + .cp-creation-password-picker { + text-align: center; + input { + width: 150px; + } + } + &.active { + label { + flex: unset; + } + .cp-creation-slider { + flex: 1; + } + } + } .cp-creation-settings { button { margin: 0; @@ -314,7 +332,7 @@ width: 95%; margin: 10px auto; } - .cp-creation-expire { + .cp-creation-expire, .cp-creation-password { &.active { label { flex: 1; diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 86f960bcb..e43cad6a1 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -1098,6 +1098,12 @@ define(function () { out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur Espace pour changer sa valeur."; out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads"; + // Password prompt on the loadind screen + out.password_info = "Le pad auquel vous essayez d'accéder est protégé par un mot de passe. Entrez le bon mot de passe pour accéder à son contenu."; + out.password_error = "Pad introuvable !
Cette erreur peut provenir de deux facteurs. Soit le mot de passe est faux, soit le pad a été supprimé du serveur."; + out.password_placeholder = "Tapez le mot de passe ici..."; + out.password_submit = "Valider"; + // New share modal out.share_linkCategory = "Partage"; out.share_linkAccess = "Droits d'accès"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index ddaa1158f..4c3720151 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -1144,6 +1144,12 @@ define(function () { out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.)."; out.creation_newPadModalAdvanced = "Display the pad creation screen"; + // Password prompt on the loadind screen + out.password_info = "The pad you're tyring to open is protected with a password. Enter the correct password to access its content."; + out.password_error = "Pad not found!
This error can be caused by two factors: either the password in invalid, or the pad has been deleted from the server."; + out.password_placeholder = "Type the password here..."; + out.password_submit = "Submit"; + // New share modal out.share_linkCategory = "Share link"; out.share_linkAccess = "Access rights"; diff --git a/www/code/inner.js b/www/code/inner.js index c2e5851bf..0a6b11abc 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -334,6 +334,7 @@ define([ //var cursor = editor.getCursor(); //var cleanName = data.name.replace(/[\[\]]/g, ''); //var text = '!['+cleanName+']('+data.url+')'; + // PASSWORD_FILES var parsed = Hash.parsePadUrl(data.url); var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 852260acc..48d0e622f 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -96,6 +96,7 @@ define([ Hash.createRandomHash = function (type, password) { var cryptor = Crypto.createEditCryptor2(void 0, void 0, password); + console.log(cryptor); return getEditHashFromKeys({ password: Boolean(password), version: 2, @@ -262,6 +263,7 @@ Version 1 var getRelativeHref = Hash.getRelativeHref = function (href) { if (!href) { return; } if (href.indexOf('#') === -1) { return; } + // Password not needed to get the type or the hash var parsed = parsePadUrl(href); return '/' + parsed.type + '/#' + parsed.hash; }; @@ -287,14 +289,15 @@ Version 1 var hash; if (secretHash) { if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); } + // Password not needed here, we only use the hash key parsed = parseTypeHash(type, secretHash); hash = secretHash; } else { + // Password not needed here, we only use the hash key var pHref = parsePadUrl(window.location.href); parsed = pHref.hashData; hash = pHref.hash; } - //var parsed = parsePadUrl(window.location.href); //var hash = secretHash || window.location.hash.slice(1); if (hash.length === 0) { generate(); @@ -337,10 +340,10 @@ Version 1 // New hash secret.version = 2; secret.type = type; - secret.password = Boolean(password); + secret.password = password; if (parsed.type === "pad") { if (parsed.mode === 'edit') { - secret.keys = Crypto.createEditCryptor2(parsed.key); + secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password); secret.channel = base64ToHex(secret.keys.chanId); secret.key = secret.keys.editKeyStr; if (secret.channel.length !== 32 || secret.key.length !== 24) { @@ -348,7 +351,7 @@ Version 1 } } else if (parsed.mode === 'view') { - secret.keys = Crypto.createViewCryptor2(parsed.key); + secret.keys = Crypto.createViewCryptor2(parsed.key, password); secret.channel = base64ToHex(secret.keys.chanId); if (secret.channel.length !== 32) { throw new Error("The channel key is invalid"); @@ -388,14 +391,14 @@ Version 1 }; // STORAGE - Hash.findWeaker = function (href, recents) { + Hash.findWeaker = function (href, recents, password) { var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); + var parsed = parsePadUrl(rHref, password); if (!parsed.hash) { return false; } var weaker; Object.keys(recents).some(function (id) { var pad = recents[id]; - var p = parsePadUrl(pad.href); + var p = parsePadUrl(pad.href, pad.password); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger var pHash = p.hashData; @@ -408,23 +411,23 @@ Version 1 if (pHash.version !== parsedHash.version) { return; } if (pHash.channel !== parsedHash.channel) { return; } if (pHash.mode === 'view' && parsedHash.mode === 'edit') { - weaker = pad.href; + weaker = pad; return true; } return; }); return weaker; }; - var findStronger = Hash.findStronger = function (href, recents) { + var findStronger = Hash.findStronger = function (href, recents, password) { var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); + var parsed = parsePadUrl(rHref, password); if (!parsed.hash) { return false; } // We can't have a stronger hash if we're already in edit mode if (parsed.hashData && parsed.hashData.mode === 'edit') { return; } var stronger; Object.keys(recents).some(function (id) { var pad = recents[id]; - var p = parsePadUrl(pad.href); + var p = parsePadUrl(pad.href, pad.password); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger var pHash = p.hashData; @@ -437,19 +440,16 @@ Version 1 if (pHash.version !== parsedHash.version) { return; } if (pHash.channel !== parsedHash.channel) { return; } if (pHash.mode === 'edit' && parsedHash.mode === 'view') { - stronger = pad.href; + stronger = pad; return true; } return; }); return stronger; }; - Hash.isNotStrongestStored = function (href, recents) { - return findStronger(href, recents); - }; - Hash.hrefToHexChannelId = function (href) { - var parsed = Hash.parsePadUrl(href); + Hash.hrefToHexChannelId = function (href, password) { + var parsed = Hash.parsePadUrl(href, password); if (!parsed || !parsed.hash) { return; } parsed = parsed.hashData; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index ea2773e37..6f4850010 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -594,7 +594,12 @@ define([ $('.cp-loading-spinner-container').hide(); $('#cp-loading-tip').remove(); if (transparent) { $('#' + LOADING).css('opacity', 0.9); } - $('#' + LOADING).find('p').show().html(error || Messages.error); + var $error = $('#' + LOADING).find('p').show(); + if (error instanceof Element) { + $error.html('').append(error); + } else { + $error.html(error || Messages.error); + } if (exitable) { $(window).focus(); $(window).keydown(function (e) { @@ -624,6 +629,7 @@ define([ var type = data.type; if (!href && !type) { return $icon; } + // Password not needed to get the type if (!type) { type = Hash.parsePadUrl(href).type; } $icon = UI.getIcon(type); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 2d09b2abe..9cfe64076 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -67,7 +67,7 @@ define([ common.getPadAttribute('href', waitFor(function (err, val) { var base = common.getMetadataMgr().getPrivateData().origin; - var parsed = Hash.parsePadUrl(val); + var parsed = Hash.parsePadUrl(val, data.password); if (parsed.hashData.mode === "view") { data.roHref = base + val; return; @@ -285,6 +285,7 @@ define([ var hash = (edit && hashes.editHash) ? hashes.editHash : hashes.viewHash; var href = origin + pathname + '#' + hash; + // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(href); return origin + parsed.getUrl({embed: embed, present: present}); }; @@ -322,6 +323,7 @@ define([ var getEmbedValue = function () { var hash = hashes.viewHash || hashes.editHash; var href = origin + pathname + '#' + hash; + // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(href); var url = origin + parsed.getUrl({embed: true, present: true}); return ''; @@ -930,14 +932,14 @@ define([ }; }; + var setHTML = function (e, html) { + e.innerHTML = html; + return e; + }; + UIElements.createHelpMenu = function (common, categories) { var type = common.getMetadataMgr().getMetadata().type || 'pad'; - var setHTML = function (e, html) { - e.innerHTML = html; - return e; - }; - var elements = []; if (Messages.help && Messages.help.generic) { Object.keys(Messages.help.generic).forEach(function (el) { @@ -1911,6 +1913,17 @@ define([ createHelper('/faq.html#keywords-expiring', Messages.creation_expire2), ]); + // Password + var password = h('div.cp-creation-password', [ + UI.createCheckbox('cp-creation-password', 'TODO Add a password', false), //XXX + h('span.cp-creation-password-picker.cp-creation-slider', [ + h('input#cp-creation-password-val', { + type: "text" // TODO type password with click to show + }), + ]), + createHelper('#', "TODO: password protection adds another layer of security ........") // TODO + ]); + var right = h('span.fa.fa-chevron-right.cp-creation-template-more'); var left = h('span.fa.fa-chevron-left.cp-creation-template-more'); var templates = h('div.cp-creation-template', [ @@ -1936,6 +1949,7 @@ define([ $(h('div#cp-creation-form', [ owned, expire, + password, settings, templates, createDiv @@ -2046,6 +2060,19 @@ define([ $creation.focus(); }); + // Display expiration form when checkbox checked + $creation.find('#cp-creation-password').on('change', function () { + if ($(this).is(':checked')) { + $creation.find('.cp-creation-password-picker:not(.active)').addClass('active'); + $creation.find('.cp-creation-password:not(.active)').addClass('active'); + $creation.find('#cp-creation-password-val').focus(); + return; + } + $creation.find('.cp-creation-password-picker').removeClass('active'); + $creation.find('.cp-creation-password').removeClass('active'); + $creation.focus(); + }); + // Display settings help when checkbox checked $creation.find('#cp-creation-remember').on('change', function () { if ($(this).is(':checked')) { @@ -2094,12 +2121,16 @@ define([ } expireVal = ($('#cp-creation-expire-val').val() || 0) * unit; } + // Password + var passwordVal = $('#cp-creation-password').is(':checked') ? + $('#cp-creation-password-val').val() : undefined; var $template = $creation.find('.cp-creation-template-selected'); var templateId = $template.data('id') || undefined; return { owned: ownedVal, + password: passwordVal, expire: expireVal, templateId: templateId }; @@ -2169,5 +2200,43 @@ define([ (cb || function () {})(); }; + UIElements.displayPasswordPrompt = function (common, isError) { + var error; + if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); } + var info = h('p.cp-password-info', Messages.password_info); + var input = h('input', { + type: "password", + placeholder: Messages.password_placeholder + }); + var button = h('button', Messages.password_submit); + + var submit = function () { + var value = $(input).val(); + UI.addLoadingScreen(); + common.getSframeChannel().query('Q_PAD_PASSWORD_VALUE', value, function (err, data) { + if (!data) { + UIElements.displayPasswordPrompt(common, true); + } + }); + }; + $(input).on('keydown', function (e) { + if (e.which === 13) { submit(); } + }) + $(button).on('click', function () { + submit(); + }) + + + var block = h('div#cp-loading-password-prompt', [ + error, + info, + h('p.cp-password-form', [ + input, + button + ]) + ]); + UI.errorLoadingScreen(block); + }; + return UIElements; }); diff --git a/www/common/cryptget.js b/www/common/cryptget.js index d5cb3edc1..623f5946e 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -43,7 +43,6 @@ define([ Object.keys(b).forEach(function (k) { a[k] = b[k]; }); }; - // XXX make sure we pass the password here in opt var get = function (hash, cb, opt) { if (typeof(cb) !== 'function') { throw new Error('Cryptget expects a callback'); @@ -63,7 +62,6 @@ define([ Session.realtime = CPNetflux.start(config); }; - // XXX make sure we pass the password here in opt var put = function (hash, doc, cb, opt) { if (typeof(cb) !== 'function') { throw new Error('Cryptput expects a callback'); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e473b397c..dbc69198b 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -385,6 +385,7 @@ define([ if (!type) { return void cb(null, obj); } var templates = obj.filter(function (f) { + // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(f.href); return parsed.type === type; }); @@ -393,10 +394,13 @@ define([ }; common.saveAsTemplate = function (Cryptput, data, cb) { + // Password not needed here since we don't access hashData var p = Hash.parsePadUrl(window.location.href); if (!p.type) { return; } + // XXX PPP var hash = Hash.createRandomHash(p.type); var href = '/' + p.type + '/#' + hash; + // XXX PPP Cryptput(hash, data.toSave, function (e) { if (e) { throw new Error(e); } postMessage("ADD_PAD", { @@ -419,16 +423,35 @@ define([ }); }; - common.useTemplate = function (href, Crypt, cb, opts) { + common.useTemplate = function (href, Crypt, cb, optsPut) { // opts is used to overrides options for chainpad-netflux in cryptput // it allows us to add owners and expiration time if it is a new file + + // Password not needed here, we only need the hash and to know if + // we need to get the password var parsed = Hash.parsePadUrl(href); + var parsed2 = Hash.parsePadUrl(window.location.href); if(!parsed) { throw new Error("Cannot get template hash"); } postMessage("INCREMENT_TEMPLATE_USE", href); - Crypt.get(parsed.hash, function (err, val) { - if (err) { throw new Error(err); } - var p = Hash.parsePadUrl(window.location.href); - Crypt.put(p.hash, val, cb, opts); + + optsPut = optsPut || {}; + var optsGet = {}; + Nthen(function (waitFor) { + if (parsed.hashData && parsed.hashData.password) { + common.getPadAttribute('password', waitFor(function (err, password) { + optsGet.password = password; + }), href); + } + if (parsed2.hashData && parsed2.hashData.password) { + common.getPadAttribute('password', waitFor(function (err, password) { + optsPut.password = password; + })); + } + }).nThen(function (waitFor) { + Crypt.get(parsed.hash, function (err, val) { + if (err) { throw new Error(err); } + Crypt.put(parsed2.hash, val, cb, optsPut); + }, optsGet); }); }; @@ -441,6 +464,8 @@ define([ // When opening a new pad or renaming it, store the new title common.setPadTitle = function (title, padHref, path, cb) { var href = padHref || window.location.href; + + // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return; } href = parsed.getUrl({present: parsed.present}); @@ -477,6 +502,9 @@ define([ common.setInitialPath = function (path) { postMessage("SET_INITIAL_PATH", path); }; + common.setNewPadPassword = function (password) { + postMessage("SET_NEW_PAD_PASSWORD", password); + }; // Messaging (manage friends from the userlist) common.inviteFromUserlist = function (netfluxId, cb) { @@ -559,12 +587,13 @@ define([ hashes = Hash.getHashes(secret); return void cb(null, hashes); } + // Password not needed here since only want the type var parsed = Hash.parsePadUrl(window.location.href); if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } hashes = Hash.getHashes(secret); - if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { + if (secret.version === 0) { // It means we're using an old hash hashes.editHash = window.location.hash.slice(1); return void cb(null, hashes); @@ -576,7 +605,8 @@ define([ } postMessage("GET_STRONGER_HASH", { - href: window.location.href + href: window.location.href, + password: secret.password }, function (hash) { if (hash) { hashes.editHash = hash; } cb(null, hashes); diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index c51428b2a..a20cd798b 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -122,21 +122,15 @@ define([ // Do not migrate a pad if we already have it, it would create a duplicate in the drive if (newHrefs.indexOf(href) !== -1) { return; } // If we have a stronger version, do not add the current href - if (Hash.findStronger(href, newRecentPads)) { return; } + if (Hash.findStronger(href, newRecentPads, oldRecentPads[id].password)) { return; } // If we have a weaker version, replace the href by the new one // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted - var weaker = Hash.findWeaker(href, newRecentPads); + var weaker = Hash.findWeaker(href, newRecentPads, oldRecentPads[id].password); if (weaker) { // Update RECENTPADS - newRecentPads.some(function (pad) { - if (pad.href === weaker) { - pad.href = href; - return true; - } - return; - }); + weaker.href = href; // Update the file in the drive - newFo.replace(weaker, href); + newFo.replace(weaker.href, href); return; } // Here it means we have a new href, so we should add it to the drive at its old location diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 676277475..2ce6d58f2 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -416,6 +416,7 @@ define([ var pad = makePad(data.href, data.title); if (data.owners) { pad.owners = data.owners; } if (data.expire) { pad.expire = data.expire; } + if (data.password) { pad.password = data.password; } store.userObject.pushData(pad, function (e, id) { if (e) { return void cb({error: "Error while adding a template:"+ e}); } var path = data.path || ['root']; @@ -758,8 +759,10 @@ define([ title: title, owners: owners, expire: expire, + password: store.data && store.data.newPadPassword, path: data.path || (store.data && store.data.initialPath) }, cb); + delete store.data.newPadPassword; return; } onSync(cb); @@ -802,6 +805,11 @@ define([ if (!store.data) { return; } store.data.initialPath = path; }; + Store.setNewPadPassword = function (password) { + if (!store.data) { return; } + store.data.newPadPassword = password; + }; + // Messaging (manage friends from the userlist) var getMessagingCfg = function () { @@ -833,9 +841,9 @@ define([ var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; // If we have a stronger version in drive, add it and add a redirect button - var stronger = Hash.findStronger(data.href, allPads); + var stronger = Hash.findStronger(data.href, allPads, data.password); if (stronger) { - var parsed2 = Hash.parsePadUrl(stronger); + var parsed2 = Hash.parsePadUrl(stronger.href, stronger.password); return void cb(parsed2.hash); } cb(); diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 1713d7a2a..e95f6e227 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -123,6 +123,9 @@ define([ case 'SET_INITIAL_PATH': { Store.setInitialPath(data); break; } + case 'SET_NEW_PAD_PASSWORD': { + Store.setNewPadPassword(data); break; + } case 'GET_STRONGER_HASH': { Store.getStrongerHash(data, cb); break; } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e6543bb30..990208ee3 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -24,6 +24,7 @@ define([ var Utils = {}; var AppConfig; var Test; + var password; nThen(function (waitFor) { // Load #2, the loading screen is up so grab whatever you need... @@ -121,26 +122,53 @@ define([ }); })); } else { - var parsedType = Utils.Hash.parsePadUrl(window.location.href).type; - // XXX prompt the password here if we have a hash containing /p/ - // OR get it from the pad attributes - secret = Utils.Hash.getSecrets(parsedType); - - // TODO: New hashes V2 already contain a channel ID so we can probably remove the following lines - //if (!secret.channel) { - // New pad: create a new random channel id - //secret.channel = Utils.Hash.createChannelId(); - //} - - Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); + var parsed = Utils.Hash.parsePadUrl(window.location.href); + var todo = function () { + secret = Utils.Hash.getSecrets(parsed.type, void 0, password); + Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); + }; + + // Prompt the password here if we have a hash containing /p/ + // or get it from the pad attributes + var needPassword = parsed.hashData && parsed.hashData.password; + if (needPassword) { + Cryptpad.getPadAttribute('password', waitFor(function (err, val) { + if (val) { + // We already know the password, use it! + password = val; + todo(); + } else { + // Ask for the password and check if the pad exists + // If the pad doesn't exist, it means the password is oncorrect + // or the pad has been deleted + var correctPassword = waitFor(); + sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) { + password = data; + Cryptpad.isNewChannel(window.location.href, password, function (e, isNew) { + if (Boolean(isNew)) { + // Ask again in the inner iframe + // We should receive a new Q_PAD_PASSWORD_VALUE + cb(false); + } else { + todo(); + correctPassword(); + cb(true); + } + }); + }); + sframeChan.event("EV_PAD_PASSWORD"); + } + })); + return; + } + // If no password, continue... + todo(); } }).nThen(function (waitFor) { // Check if the pad exists on server if (!window.location.hash) { isNewFile = true; return; } if (realtime) { - // XXX get password - var password; Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) { if (e) { return console.error(e); } isNewFile = Boolean(isNew); @@ -679,11 +707,12 @@ define([ sframeChan.on('Q_CREATE_PAD', function (data, cb) { if (!isNewFile || rtStarted) { return; } // Create a new hash - // XXX add password here var password = data.password; var newHash = Utils.Hash.createRandomHash(parsed.type, password); secret = Utils.Hash.getSecrets(parsed.type, newHash, password); + Cryptpad.setNewPadPassword(password); + // Update the hash in the address bar var ohc = window.onhashchange; window.onhashchange = function () {}; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index a56afa382..444ee40e7 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -198,6 +198,7 @@ define([ ctx.sframeChan.query("Q_CREATE_PAD", { owned: cfg.owned, expire: cfg.expire, + password: cfg.password, template: cfg.template, templateId: cfg.templateId }, cb); @@ -379,6 +380,7 @@ define([ Object.freeze(funcs); return { create: function (cb) { + console.log('create'); if (window.CryptPad_sframe_common) { throw new Error("Sframe-common should only be created once"); } @@ -429,6 +431,14 @@ define([ UI.log(data.logText); }); + ctx.sframeChan.on("EV_PAD_PASSWORD", function (data) { + UIElements.displayPasswordPrompt(funcs); + /*UI.prompt("Password?", "", function (val) { + ctx.sframeChan.event("EV_PAD_PASSWORD_VALUE", val); + }); + $('div.alertify').last().css("z-index", Number.MAX_SAFE_INTEGER);*/ + }); + ctx.metadataMgr.onReady(waitFor()); }).nThen(function () { try { diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 8142694ce..970ec5524 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -230,4 +230,8 @@ define({ // Critical error outside the iframe during loading screen 'EV_LOADING_ERROR': true, + + // Ask for the pad password when a pad is protected + 'EV_PAD_PASSWORD': true, + 'Q_PAD_PASSWORD_VALUE': true, }); diff --git a/www/poll/inner.js b/www/poll/inner.js index fabcad7e4..ad0ea5ada 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -2,7 +2,6 @@ define([ 'jquery', '/common/toolbar3.js', '/common/common-util.js', - '/common/cryptget.js', '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/common-realtime.js', @@ -32,7 +31,6 @@ define([ $, Toolbar, Util, - Cryptget, nThen, SFCommon, CommonRealtime, From b26ae67df50a6686a33d171ed57583227ec08731 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 26 Apr 2018 15:10:31 +0200 Subject: [PATCH 3/6] Fix issues with channel ID when using a password --- www/common/common-hash.js | 2 +- www/common/common-ui-elements.js | 13 +++++------- www/common/cryptpad-common.js | 21 ++++++++++--------- www/common/diffMarked.js | 1 + www/common/outer/async-store.js | 28 ++++++++++++++++---------- www/common/outer/userObject.js | 5 +++-- www/common/sframe-app-framework.js | 2 +- www/common/sframe-common-codemirror.js | 1 + www/common/sframe-common-outer.js | 17 +++++++++++----- www/common/sframe-common.js | 12 ++++------- www/common/toolbar3.js | 2 +- www/common/userObject.js | 3 ++- www/drive/inner.js | 5 +++++ www/file/inner.js | 3 ++- www/filepicker/inner.js | 1 + www/pad/inner.js | 1 + www/profile/main.js | 4 ++-- www/slide/inner.js | 4 +--- 18 files changed, 72 insertions(+), 53 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 48d0e622f..3a58a24b6 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -418,7 +418,7 @@ Version 1 }); return weaker; }; - var findStronger = Hash.findStronger = function (href, recents, password) { + Hash.findStronger = function (href, recents, password) { var rHref = href || getRelativeHref(window.location.href); var parsed = parsePadUrl(rHref, password); if (!parsed.hash) { return false; } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 9cfe64076..3cf902a1a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -180,7 +180,7 @@ define([ if (common.isLoggedIn() && AppConfig.enablePinning) { // check the size of this file... - common.getFileSize(data.href, function (e, bytes) { + common.getFileSize(data.href, data.password, function (e, bytes) { if (e) { // there was a problem with the RPC console.error(e); @@ -1146,7 +1146,8 @@ define([ var cryptKey = secret.keys && secret.keys.fileKeyStr; var hexFileName = Util.base64ToHex(secret.channel); var src = Hash.getBlobPathFromHex(hexFileName); - Common.getFileSize(href, function (e, data) { + // No password for avatars + Common.getFileSize(href, null, function (e, data) { if (e) { displayDefault(); return void console.error(e); @@ -2219,12 +2220,8 @@ define([ } }); }; - $(input).on('keydown', function (e) { - if (e.which === 13) { submit(); } - }) - $(button).on('click', function () { - submit(); - }) + $(input).on('keydown', function (e) { if (e.which === 13) { submit(); } }); + $(button).on('click', function () { submit(); }); var block = h('div#cp-loading-password-prompt', [ diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index dbc69198b..cbc469610 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -246,8 +246,8 @@ define([ }); }; - common.getFileSize = function (href, cb) { - postMessage("GET_FILE_SIZE", {href: href}, function (obj) { + common.getFileSize = function (href, password, cb) { + postMessage("GET_FILE_SIZE", {href: href, password: password}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(undefined, obj.size); }); @@ -447,7 +447,7 @@ define([ optsPut.password = password; })); } - }).nThen(function (waitFor) { + }).nThen(function () { Crypt.get(parsed.hash, function (err, val) { if (err) { throw new Error(err); } Crypt.put(parsed2.hash, val, cb, optsPut); @@ -843,18 +843,21 @@ define([ window.onhashchange = function (ev) { if (ev && ev.reset) { oldHref = document.location.href; return; } var newHref = document.location.href; - var parsedOld = Hash.parsePadUrl(oldHref).hashData; - var parsedNew = Hash.parsePadUrl(newHref).hashData; - if (parsedOld && parsedNew && ( + // Password not needed here since we don't access hashData + var parsedOld = Hash.parsePadUrl(oldHref); + var parsedNew = Hash.parsePadUrl(newHref); + if (parsedOld.hashData && parsedNew.hashData && + parsedOld.getUrl() !== parsedNew.getUrl()) { + /*parseOld && parsedNew && ( parsedOld.type !== parsedNew.type || parsedOld.channel !== parsedNew.channel || parsedOld.mode !== parsedNew.mode - || parsedOld.key !== parsedNew.key)) { - if (!parsedOld.channel) { oldHref = newHref; return; } + || parsedOld.key !== parsedNew.key)) {*/ + if (!parsedOld.hashData.channel) { oldHref = newHref; return; } document.location.reload(); return; } - if (parsedNew) { oldHref = newHref; } + if (parsedNew.hashData) { oldHref = newHref; } }; // Listen for login/logout in other tabs window.addEventListener('storage', function (e) { diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 47e869fc9..80c9c4b79 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -41,6 +41,7 @@ define([ }; renderer.image = function (href, title, text) { if (href.slice(0,6) === '/file/') { + // PASSWORD_FILES var parsed = Hash.parsePadUrl(href); var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 2ce6d58f2..e6cb10955 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -81,16 +81,17 @@ define([ var d = store.userObject.getFileData(id); if (d.owners && d.owners.length && edPublic && d.owners.indexOf(edPublic) === -1) { return; } - return Hash.hrefToHexChannelId(d.href); + return Hash.hrefToHexChannelId(d.href, d.password); }) .filter(function (x) { return x; }); // Get the avatar var profile = store.proxy.profile; if (profile) { - var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit) : null; + // No password for profile or avatar + var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; if (profileChan) { list.push(profileChan); } - var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null; + var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null; if (avatarChan) { list.push(avatarChan); } } @@ -115,7 +116,7 @@ define([ // because of the expiration time if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || (data.expire && data.expire < (+new Date()))) { - list.push(Hash.hrefToHexChannelId(data.href)); + list.push(Hash.hrefToHexChannelId(data.href, data.password)); } }); return list; @@ -303,7 +304,7 @@ define([ Store.getFileSize = function (data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } - var channelId = Hash.hrefToHexChannelId(data.href); + var channelId = Hash.hrefToHexChannelId(data.href, data.password); store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { if (e) { return void cb({error: e}); } if (response && response.length && typeof(response[0]) === 'number') { @@ -403,6 +404,7 @@ define([ var makePad = function (href, title) { var now = +new Date(); + // Password not needed here since we only need the type return { href: href, atime: now, @@ -434,14 +436,16 @@ define([ // Push channels owned by someone else or channel that should have expired // because of the expiration time if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) { - list.push(Hash.hrefToHexChannelId(data.href)); + list.push(Hash.hrefToHexChannelId(data.href, data.password)); } }); if (store.proxy.todo) { - list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo)); + // No password for todo + list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null)); } if (store.proxy.profile && store.proxy.profile.edit) { - list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit)); + // No password for todo + list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null)); } return list; }; @@ -615,6 +619,7 @@ define([ }); }; Store.getPadAttribute = function (data, cb) { + console.log(data.href, data.attr); store.userObject.getPadAttribute(data.href, data.attr, function (err, val) { if (err) { return void cb({error: err}); } cb(val); @@ -680,7 +685,8 @@ define([ Store.setPadTitle = function (data, cb) { var title = data.title; var href = data.href; - var p = Hash.parsePadUrl(href); + var padData = store.userObject.getFileData(store.userObject.getIdFromHref(href)); + var p = Hash.parsePadUrl(href, padData && padData.password); var h = p.hashData; if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } @@ -707,7 +713,7 @@ define([ var pad = allPads[id]; if (!pad.href) { continue; } - var p2 = Hash.parsePadUrl(pad.href); + var p2 = Hash.parsePadUrl(pad.href, pad.password); var h2 = p2.hashData; // Different types, proceed to the next one @@ -788,7 +794,7 @@ define([ }; store.userObject.getFiles(where).forEach(function (id) { var data = store.userObject.getFileData(id); - var parsed = Hash.parsePadUrl(data.href); + var parsed = Hash.parsePadUrl(data.href, data.password); if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && hashes.indexOf(parsed.hash) === -1 && !isFiltered(parsed.type, data)) { diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index dadbfb463..0fc4099ec 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -75,7 +75,7 @@ define([ return void todo(); } if (!pinPads) { return; } - pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) { + pinPads([Hash.hrefToHexChannelId(data.href, data.password)], function (obj) { if (obj && obj.error) { return void cb(obj.error); } todo(); }); @@ -98,7 +98,7 @@ define([ exp.getFiles([FILES_DATA]).forEach(function (id) { if (filesList.indexOf(id) === -1) { var fd = exp.getFileData(id); - var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href); + var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href, fd.password); // If trying to remove an owned pad, remove it from server also if (!isOwnPadRemoved && fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { @@ -565,6 +565,7 @@ define([ if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); } if (!el.ctime) { el.ctime = el.atime; } + // Password not needed here since we only need the type and hash var parsed = Hash.parsePadUrl(el.href); if (!el.title) { el.title = Hash.getDefaultName(parsed); } if (!parsed.hash) { diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index b7fb0b286..274069507 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -315,7 +315,7 @@ define([ privateDat.availableHashes.viewHash; var href = privateDat.pathname + '#' + hash; if (AppConfig.textAnalyzer && textContentGetter) { - var channelId = Hash.hrefToHexChannelId(href); + var channelId = Hash.hrefToHexChannelId(href, privateDat.password); AppConfig.textAnalyzer(textContentGetter, channelId); } diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index 4386e22f0..19b4c7ae0 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -332,6 +332,7 @@ define([ //var cursor = editor.getCursor(); //var cleanName = data.name.replace(/[\[\]]/g, ''); //var text = '!['+cleanName+']('+data.url+')'; + // PASSWORD_FILES var parsed = Hash.parsePadUrl(data.url); var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 990208ee3..6ab878a27 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -122,6 +122,7 @@ define([ }); })); } else { + // Password not needed here since we only want to know if we need a password var parsed = Utils.Hash.parsePadUrl(window.location.href); var todo = function () { secret = Utils.Hash.getSecrets(parsed.type, void 0, password); @@ -133,6 +134,7 @@ define([ var needPassword = parsed.hashData && parsed.hashData.password; if (needPassword) { Cryptpad.getPadAttribute('password', waitFor(function (err, val) { + console.log(val); if (val) { // We already know the password, use it! password = val; @@ -158,7 +160,7 @@ define([ }); sframeChan.event("EV_PAD_PASSWORD"); } - })); + }), parsed.getUrl()); return; } // If no password, continue... @@ -182,7 +184,7 @@ define([ secret.keys = secret.key; readOnly = false; } - var parsed = Utils.Hash.parsePadUrl(window.location.href); + var parsed = Utils.Hash.parsePadUrl(window.location.href, password); if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.Hash.getDefaultName(parsed); var edPublic; @@ -224,7 +226,8 @@ define([ }, isNewFile: isNewFile, isDeleted: isNewFile && window.location.hash.length > 0, - forceCreationScreen: forceCreationScreen + forceCreationScreen: forceCreationScreen, + password: password }; for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } @@ -292,6 +295,7 @@ define([ sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) { currentTitle = newTitle; setDocumentTitle(); + Cryptpad.setNewPadPassword(password); Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) { cb(err); }); @@ -414,10 +418,12 @@ define([ // Present mode URL sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) { + // Password not needed here since we only need something directly in the hash var parsed = Utils.Hash.parsePadUrl(window.location.href); cb(parsed.hashData && parsed.hashData.present); }); sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) { + // Password not needed here var parsed = Utils.Hash.parsePadUrl(window.location.href); window.location.href = parsed.getUrl({ embed: parsed.hashData.embed, @@ -510,6 +516,7 @@ define([ }); }); var getKey = function (href) { + // Password not needed here. We use the fake channel id for thumbnails at the moment var parsed = Utils.Hash.parsePadUrl(href); return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel; }; @@ -707,7 +714,7 @@ define([ sframeChan.on('Q_CREATE_PAD', function (data, cb) { if (!isNewFile || rtStarted) { return; } // Create a new hash - var password = data.password; + password = data.password; var newHash = Utils.Hash.createRandomHash(parsed.type, password); secret = Utils.Hash.getSecrets(parsed.type, newHash, password); @@ -721,7 +728,7 @@ define([ ohc({reset: true}); // Update metadata values and send new metadata inside - parsed = Utils.Hash.parsePadUrl(window.location.href); + parsed = Utils.Hash.parsePadUrl(window.location.href, password); defaultTitle = Utils.Hash.getDefaultName(parsed); hashes = Utils.Hash.getHashes(secret); readOnly = false; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 444ee40e7..77308aacd 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -113,8 +113,8 @@ define([ return ''; }; funcs.getMediatagFromHref = function (href) { + // PASSWORD_FILES var parsed = Hash.parsePadUrl(href); - // FILE_HASHES2 var secret = Hash.getSecrets('file', parsed.hash); var data = ctx.metadataMgr.getPrivateData(); if (secret.keys && secret.channel) { @@ -127,8 +127,8 @@ define([ } return; }; - funcs.getFileSize = function (href, cb) { - var channelId = Hash.hrefToHexChannelId(href); + funcs.getFileSize = function (href, password, cb) { + var channelId = Hash.hrefToHexChannelId(href, password); funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) { if (!data) { return void cb("No response"); } if (data.error) { return void cb(data.error); } @@ -431,12 +431,8 @@ define([ UI.log(data.logText); }); - ctx.sframeChan.on("EV_PAD_PASSWORD", function (data) { + ctx.sframeChan.on("EV_PAD_PASSWORD", function () { UIElements.displayPasswordPrompt(funcs); - /*UI.prompt("Password?", "", function (val) { - ctx.sframeChan.event("EV_PAD_PASSWORD_VALUE", val); - }); - $('div.alertify').last().css("z-index", Number.MAX_SAFE_INTEGER);*/ }); ctx.metadataMgr.onReady(waitFor()); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 905551d77..54cc6df6c 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -578,7 +578,7 @@ define([ var o = pd.origin; var hashes = pd.availableHashes; var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash); - var cid = Hash.hrefToHexChannelId(url); + var cid = Hash.hrefToHexChannelId(url, pd.password); Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) { if (x.error || !Array.isArray(x.response)) { return void console.log(x); } if (x.response[0] === true) { diff --git a/www/common/userObject.js b/www/common/userObject.js index 4f7f03b23..f38c7d219 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -78,6 +78,7 @@ define([ exp.isReadOnlyFile = function (element) { if (!isFile(element)) { return false; } var data = exp.getFileData(element); + // Password not needed var parsed = Hash.parsePadUrl(data.href); if (!parsed) { return false; } var pHash = parsed.hashData; @@ -387,7 +388,7 @@ define([ var channels64 = channels.slice().map(Util.hexToBase64); return getFiles([FILES_DATA]).filter(function (k) { var data = allFilesList[k]; - var parsed = Hash.parsePadUrl(data.href); + var parsed = Hash.parsePadUrl(data.href, data.password); return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1; }); }; diff --git a/www/drive/inner.js b/www/drive/inner.js index 08c067183..5ec1cf901 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1264,6 +1264,7 @@ define([ var data = filesOp.getFileData(element); if (!data) { return void logError("No data for the file", element); } + // Password not needed var hrefData = Hash.parsePadUrl(data.href); if (hrefData.type) { $span.addClass('cp-border-color-'+hrefData.type); @@ -1835,6 +1836,7 @@ define([ var data = filesOp.getFileData(id); if (!data) { return ''; } if (prop === 'type') { + // Password not needed var hrefData = Hash.parsePadUrl(data.href); return hrefData.type; } @@ -1870,6 +1872,7 @@ define([ }; } if (prop === 'type') { + // Password not needed var hrefData = Hash.parsePadUrl(e.href); return hrefData.type; } @@ -2093,6 +2096,7 @@ define([ filesList.forEach(function (r) { r.paths.forEach(function (path) { var href = r.data.href; + // Password not needed var parsed = Hash.parsePadUrl(href); var $table = $(''); var $icon = $('
', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'}) @@ -2649,6 +2653,7 @@ define([ if (!filesOp.isFile(id)) { return; } var data = filesOp.getFileData(id); if (!data) { return; } + // Password not needed var parsed = Hash.parsePadUrl(data.href); if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; diff --git a/www/file/inner.js b/www/file/inner.js index 98a77f647..e7cedab71 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -232,7 +232,8 @@ define([ $dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick); }; var href = priv.origin + priv.pathname + priv.filehash; - common.getFileSize(href, function (e, data) { + // PASSWORD_FILES + common.getFileSize(href, null, function (e, data) { if (e) { return void UI.errorLoadingScreen(e); } diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index fa844ab27..194b56bd0 100644 --- a/www/filepicker/inner.js +++ b/www/filepicker/inner.js @@ -40,6 +40,7 @@ define([ var parsed = Hash.parsePadUrl(data.url); hideFileDialog(); if (parsed.type === 'file') { + // PASSWORD_FILES var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; sframeChan.event("EV_FILE_PICKED", { diff --git a/www/pad/inner.js b/www/pad/inner.js index d77770ce9..b144c0462 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -459,6 +459,7 @@ define([ ckeditor: editor, body: $('body'), onUploaded: function (ev, data) { + // PASSWORD_FILES var parsed = Hash.parsePadUrl(data.url); var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; diff --git a/www/profile/main.js b/www/profile/main.js index 90557d793..1d6e2974d 100644 --- a/www/profile/main.js +++ b/www/profile/main.js @@ -79,7 +79,7 @@ define([ var addRpc = function (sframeChan, Cryptpad, Utils) { // Adding a new avatar from the profile: pin it and store it in the object sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) { - var chanId = Utils.Hash.hrefToHexChannelId(data); + var chanId = Utils.Hash.hrefToHexChannelId(data, null); Cryptpad.pinPads([chanId], function (e) { if (e) { return void cb(e); } Cryptpad.setAvatar(data, cb); @@ -87,7 +87,7 @@ define([ }); // Removing the avatar from the profile: unpin it sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) { - var chanId = Utils.Hash.hrefToHexChannelId(data); + var chanId = Utils.Hash.hrefToHexChannelId(data, null); Cryptpad.unpinPads([chanId], function () { Cryptpad.setAvatar(undefined, cb); }); diff --git a/www/slide/inner.js b/www/slide/inner.js index 76dfeceda..18b7da4e3 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -500,9 +500,7 @@ define([ dropArea: $('.CodeMirror'), body: $('body'), onUploaded: function (ev, data) { - //var cursor = editor.getCursor(); - //var cleanName = data.name.replace(/[\[\]]/g, ''); - //var text = '!['+cleanName+']('+data.url+')'; + // PASSWORD_FILES var parsed = Hash.parsePadUrl(data.url); var hexFileName = Util.base64ToHex(parsed.hashData.channel); var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName; From 43d046406f736a2f2d16b10dbde7da139ee89e02 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 27 Apr 2018 11:54:23 +0200 Subject: [PATCH 4/6] Store the channel id as pad attribute --- www/common/common-hash.js | 3 +-- www/common/cryptpad-common.js | 27 ++++++++------------------- www/common/outer/async-store.js | 27 ++++++++++----------------- www/common/outer/store-rpc.js | 6 ------ www/common/outer/upload.js | 9 ++++++++- www/common/outer/userObject.js | 27 +++++++++++++++++++++++---- www/common/sframe-common-outer.js | 14 +++++++++----- 7 files changed, 59 insertions(+), 54 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 3a58a24b6..2f7171fbf 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -213,8 +213,7 @@ Version 1 if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } if (ret.hashData.version === 0) { return url + '#' + ret.hash; } var hash; - if (options.readOnly === true || - (typeof (options.readOnly === "undefined") && ret.hashData.mode === "view")) { + if (typeof (options.readOnly === "undefined") && ret.hashData.mode === "view") { hash = getViewHashFromKeys({ version: ret.hashData.version, type: ret.hashData.app, diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index cbc469610..d6bae315a 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -462,22 +462,18 @@ define([ }; // When opening a new pad or renaming it, store the new title - common.setPadTitle = function (title, padHref, path, cb) { - var href = padHref || window.location.href; + common.setPadTitle = function (data, cb) { + if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); } - // Password not needed here since we don't access hashData + var href = data.href || window.location.href; var parsed = Hash.parsePadUrl(href); - if (!parsed.hash) { return; } - href = parsed.getUrl({present: parsed.present}); + if (!parsed.hash) { return cb ('Invalid hash'); } + data.href = parsed.getUrl({present: parsed.present}); - if (title === null) { return; } - if (title.trim() === "") { title = Hash.getDefaultName(parsed); } + if (typeof (data.title) !== "string") { return cb('Missing title'); } + if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); } - postMessage("SET_PAD_TITLE", { - href: href, - title: title, - path: path - }, function (obj) { + postMessage("SET_PAD_TITLE", data, function (obj) { if (obj && obj.error) { console.log("unable to set pad title"); return void cb(obj.error); @@ -498,13 +494,6 @@ define([ cb(void 0, data); }); }; - // Set initial path when creating a pad from pad creation screen - common.setInitialPath = function (path) { - postMessage("SET_INITIAL_PATH", path); - }; - common.setNewPadPassword = function (password) { - postMessage("SET_NEW_PAD_PASSWORD", password); - }; // Messaging (manage friends from the userlist) common.inviteFromUserlist = function (netfluxId, cb) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e6cb10955..776843812 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -685,10 +685,11 @@ define([ Store.setPadTitle = function (data, cb) { var title = data.title; var href = data.href; - var padData = store.userObject.getFileData(store.userObject.getIdFromHref(href)); - var p = Hash.parsePadUrl(href, padData && padData.password); + var channel = data.channel; + var p = Hash.parsePadUrl(href); var h = p.hashData; + console.log(channel, data); if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } var owners; @@ -713,19 +714,19 @@ define([ var pad = allPads[id]; if (!pad.href) { continue; } - var p2 = Hash.parsePadUrl(pad.href, pad.password); + var p2 = Hash.parsePadUrl(pad.href); var h2 = p2.hashData; // Different types, proceed to the next one // No hash data: corrupted pad? if (p.type !== p2.type || !h2) { continue; } + // Different channel: continue + if (pad.channel !== channel) { continue; } var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, ''); // If the hash is different but represents the same channel, check if weaker or stronger - if (!shouldUpdate && - h.version === h2.version && - h.channel === h2.channel) { + if (!shouldUpdate && h.version !== 0) { // We had view & now we have edit, update if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } // Same mode and we had present URL, update @@ -762,13 +763,13 @@ define([ if (!contains) { Store.addPad({ href: href, + channel: channel, title: title, owners: owners, expire: expire, - password: store.data && store.data.newPadPassword, - path: data.path || (store.data && store.data.initialPath) + password: data.password, + path: data.path }, cb); - delete store.data.newPadPassword; return; } onSync(cb); @@ -807,14 +808,6 @@ define([ Store.getPadData = function (id, cb) { cb(store.userObject.getFileData(id)); }; - Store.setInitialPath = function (path) { - if (!store.data) { return; } - store.data.initialPath = path; - }; - Store.setNewPadPassword = function (password) { - if (!store.data) { return; } - store.data.newPadPassword = password; - }; // Messaging (manage friends from the userlist) diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index e95f6e227..b134435b2 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -120,12 +120,6 @@ define([ case 'GET_PAD_DATA': { Store.getPadData(data, cb); break; } - case 'SET_INITIAL_PATH': { - Store.setInitialPath(data); break; - } - case 'SET_NEW_PAD_PASSWORD': { - Store.setNewPadPassword(data); break; - } case 'GET_STRONGER_HASH': { Store.getStrongerHash(data, cb); break; } diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index ee9f4d77c..7f3874511 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -65,7 +65,14 @@ define([ if (noStore) { return void onComplete(href); } - common.setPadTitle(title || "", href, path, function (err) { + // PASSWORD_FILES + var data = { + title: title || "", + href: href, + path: path, + channel: id + }; + common.setPadTitle(data, function (err) { if (err) { return void console.error(err); } onComplete(href); common.setPadAttribute('fileType', metadata.type, null, href); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 0fc4099ec..06b62d696 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -552,33 +552,52 @@ define([ for (var id in fd) { id = Number(id); var el = fd[id]; + + // Clean corrupted data if (!el || typeof(el) !== "object") { debug("An element in filesData was not an object.", el); toClean.push(id); continue; } + // Clean missing href if (!el.href) { debug("Removing an element in filesData with a missing href.", el); toClean.push(id); continue; } - if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); } - if (!el.ctime) { el.ctime = el.atime; } - // Password not needed here since we only need the type and hash var parsed = Hash.parsePadUrl(el.href); - if (!el.title) { el.title = Hash.getDefaultName(parsed); } + // Clean invalid hash if (!parsed.hash) { debug("Removing an element in filesData with a invalid href.", el); toClean.push(id); continue; } + // Clean invalid type if (!parsed.type) { debug("Removing an element in filesData with a invalid type.", el); toClean.push(id); continue; } + // Fix href + if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); } + // Fix creation time + if (!el.ctime) { el.ctime = el.atime; } + // Fix title + if (!el.title) { el.title = Hash.getDefaultName(parsed); } + // Fix channel + if (!el.channel) { + if (parsed.hashData && parsed.hashData.type === "file") { + // PASSWORD_FILES + el.channel = Util.base64ToHex(parsed.hashData.channel); + } else { + var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password); + el.channel = secret.channel; + } + console.log('Adding missing channel in filesData ', el.channel); + } + if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) { debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el); var newName = Hash.createChannelId(); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 6ab878a27..f1cc789fb 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -25,6 +25,7 @@ define([ var AppConfig; var Test; var password; + var initialPathInDrive; nThen(function (waitFor) { // Load #2, the loading screen is up so grab whatever you need... @@ -295,8 +296,13 @@ define([ sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) { currentTitle = newTitle; setDocumentTitle(); - Cryptpad.setNewPadPassword(password); - Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) { + var data = { + password: password, + title: newTitle, + channel: secret.channel, + path: initialPathInDrive // Where to store the pad if we don't have it in our drive + }; + Cryptpad.setPadTitle(data, function (err) { cb(err); }); }); @@ -718,8 +724,6 @@ define([ var newHash = Utils.Hash.createRandomHash(parsed.type, password); secret = Utils.Hash.getSecrets(parsed.type, newHash, password); - Cryptpad.setNewPadPassword(password); - // Update the hash in the address bar var ohc = window.onhashchange; window.onhashchange = function () {}; @@ -744,7 +748,7 @@ define([ nThen(function(waitFor) { if (data.templateId) { if (data.templateId === -1) { - Cryptpad.setInitialPath(['template']); + initialPathInDrive = ['template']; return; } Cryptpad.getPadData(data.templateId, waitFor(function (err, d) { From 64c85fe548461f7a8077fb23c1ed4ee0771dfbf7 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 27 Apr 2018 17:23:23 +0200 Subject: [PATCH 5/6] Don't return channel in parsePadUrl --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/assert/main.js | 39 ++++---- www/common/common-hash.js | 104 +++++++-------------- www/common/common-interface.js | 1 - www/common/common-messaging.js | 6 +- www/common/common-thumbnail.js | 20 ++-- www/common/common-ui-elements.js | 18 ++-- www/common/cryptpad-common.js | 20 ++-- www/common/mergeDrive.js | 4 +- www/common/outer/async-store.js | 45 ++++----- www/common/outer/userObject.js | 4 +- www/common/sframe-app-framework.js | 3 +- www/common/sframe-common-outer.js | 19 ++-- www/common/sframe-common.js | 3 +- www/common/toolbar3.js | 2 +- www/common/userObject.js | 5 +- www/drive/inner.js | 7 +- www/drive/main.js | 1 + www/file/inner.js | 5 +- www/filepicker/inner.js | 2 +- www/profile/main.js | 3 + www/todo/main.js | 1 + www/whiteboard/inner.js | 2 +- 24 files changed, 135 insertions(+), 181 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index e43cad6a1..5147baaa8 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -1081,6 +1081,7 @@ define(function () { out.creation_expireMonths = "Mois"; out.creation_expire1 = "Un pad illimité ne sera pas supprimé du serveur à moins que son propriétaire ne le décide."; out.creation_expire2 = "Un pad à durée de vie sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée."; + out.creation_password = "Ajouter un mot de passe"; out.creation_noTemplate = "Pas de modèle"; out.creation_newTemplate = "Nouveau modèle"; out.creation_create = "Créer"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 4c3720151..f51e552dc 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -1127,6 +1127,7 @@ define(function () { out.creation_expireMonths = "Month(s)"; out.creation_expire1 = "An unlimited pad will not be removed from the server until its owner deletes it."; out.creation_expire2 = "An expiring pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives."; + out.creation_password = "Add a password"; out.creation_noTemplate = "No template"; out.creation_newTemplate = "New template"; out.creation_create = "Create"; diff --git a/www/assert/main.js b/www/assert/main.js index c50360e53..f8177a03f 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -225,26 +225,29 @@ define([ // test support for V2 assert(function (cb) { - var secret = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/'); - return cb(secret.hashData.version === 2 && - secret.hashData.mode === "edit" && - secret.hashData.type === "pad" && - secret.hashData.channel === "2NUbSuqGPz8FD0f4rSYXUw" && - secret.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" && - window.nacl.util.encodeBase64(secret.hashData.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" && - secret.hashData.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" && - !secret.hashData.present); + var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/'); + var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/'); + return cb(parsed.hashData.version === 2 && + parsed.hashData.mode === "edit" && + parsed.hashData.type === "pad" && + parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" && + secret.channel === "d8d51b4aea863f3f050f47f8ad261753" && + window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" && + secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" && + !parsed.hashData.present); }, "test support for version 2 hash failed to parse"); assert(function (cb) { - var secret = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew'); - return cb(secret.hashData.version === 2 && - secret.hashData.mode === "edit" && - secret.hashData.type === "pad" && - secret.hashData.channel === "P7bck4B9kDr-OQtfeYySyQ" && - secret.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" && - window.nacl.util.encodeBase64(secret.hashData.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" && - secret.hashData.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" && - secret.hashData.embed); + var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed'); + var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew'); + return cb(parsed.hashData.version === 2 && + parsed.hashData.mode === "edit" && + parsed.hashData.type === "pad" && + parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" && + secret.channel === "3fb6dc93807d903aff390b5f798c92c9" && + window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" && + secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" && + parsed.hashData.embed && + parsed.hashData.password); }, "test support for password in version 2 hash failed to parse"); assert(function (cb) { diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 2f7171fbf..95b80e989 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -96,7 +96,6 @@ define([ Hash.createRandomHash = function (type, password) { var cryptor = Crypto.createEditCryptor2(void 0, void 0, password); - console.log(cryptor); return getEditHashFromKeys({ password: Boolean(password), version: 2, @@ -112,7 +111,7 @@ Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI */ - var parseTypeHash = Hash.parseTypeHash = function (type, hash, password) { + var parseTypeHash = Hash.parseTypeHash = function (type, hash) { if (!hash) { return; } var parsed = {}; var hashArr = fixDuplicateSlashes(hash).split('/'); @@ -123,6 +122,7 @@ Version 1 parsed.channel = hash.slice(0, 32); parsed.key = hash.slice(32, 56); parsed.version = 0; + parsed.getHash = function () { return hash; }; return parsed; } var options; @@ -135,6 +135,13 @@ Version 1 options = hashArr.slice(5); parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; + + parsed.getHash = function (opts) { + var hash = hashArr.slice(0, 5).join('/') + '/'; + if (opts.embed) { hash += 'embed/'; } + if (opts.present) { hash += 'present/'; } + return hash; + }; return parsed; } if (hashArr[1] && hashArr[1] === '2') { // Version 2 @@ -143,24 +150,23 @@ Version 1 parsed.mode = hashArr[3]; parsed.key = hashArr[4]; - var cryptor; - if (parsed.mode === "edit") { - cryptor = Crypto.createEditCryptor2(parsed.key, void 0, password); - } else if (parsed.mode === "view") { - cryptor = Crypto.createViewCryptor2(parsed.key, password); - } - parsed.channel = cryptor.chanId; - parsed.cryptKey = cryptor.cryptKey; - parsed.validateKey = cryptor.validateKey; - options = hashArr.slice(5); parsed.password = options.indexOf('p') !== -1; parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; + + parsed.getHash = function (opts) { + var hash = hashArr.slice(0, 5).join('/') + '/'; + if (parsed.password) { hash += 'p/'; } + if (opts.embed) { hash += 'embed/'; } + if (opts.present) { hash += 'present/'; } + return hash; + }; return parsed; } return parsed; } + parsed.getHash = function () { return hashArr.join('/'); }; if (['media', 'file'].indexOf(type) !== -1) { parsed.type = 'file'; if (hashArr[1] && hashArr[1] === '1') { @@ -193,7 +199,7 @@ Version 1 } return; }; - var parsePadUrl = Hash.parsePadUrl = function (href, password) { + var parsePadUrl = Hash.parsePadUrl = function (href) { var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; var ret = {}; @@ -212,31 +218,8 @@ Version 1 if (!ret.hashData) { return url; } if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } if (ret.hashData.version === 0) { return url + '#' + ret.hash; } - var hash; - if (typeof (options.readOnly === "undefined") && ret.hashData.mode === "view") { - hash = getViewHashFromKeys({ - version: ret.hashData.version, - type: ret.hashData.app, - channel: base64ToHex(ret.hashData.channel || ''), - password: ret.hashData.password, - keys: { - viewKeyStr: ret.hashData.key - } - }); - } else { - hash = getEditHashFromKeys({ - version: ret.hashData.version, - type: ret.hashData.app, - channel: base64ToHex(ret.hashData.channel || ''), - password: ret.hashData.password, - keys: { - editKeyStr: ret.hashData.key - } - }); - } + var hash = ret.hashData.getHash(options); url += '#' + hash; - if (options.embed) { url += 'embed/'; } - if (options.present) { url += 'present/'; } return url; }; @@ -244,7 +227,7 @@ Version 1 idx = href.indexOf('/#'); ret.type = href.slice(1, idx); ret.hash = href.slice(idx + 2); - ret.hashData = parseTypeHash(ret.type, ret.hash, password); + ret.hashData = parseTypeHash(ret.type, ret.hash); return ret; } @@ -254,15 +237,15 @@ Version 1 return ''; }); idx = href.indexOf('/#'); + if (idx === -1) { return ret; } ret.hash = href.slice(idx + 2); - ret.hashData = parseTypeHash(ret.type, ret.hash, password); + ret.hashData = parseTypeHash(ret.type, ret.hash); return ret; }; var getRelativeHref = Hash.getRelativeHref = function (href) { if (!href) { return; } if (href.indexOf('#') === -1) { return; } - // Password not needed to get the type or the hash var parsed = parsePadUrl(href); return '/' + parsed.type + '/#' + parsed.hash; }; @@ -288,11 +271,9 @@ Version 1 var hash; if (secretHash) { if (!type) { throw new Error("getSecrets with a hash requires a type parameter"); } - // Password not needed here, we only use the hash key parsed = parseTypeHash(type, secretHash); hash = secretHash; } else { - // Password not needed here, we only use the hash key var pHref = parsePadUrl(window.location.href); parsed = pHref.hashData; hash = pHref.hash; @@ -390,16 +371,17 @@ Version 1 }; // STORAGE - Hash.findWeaker = function (href, recents, password) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref, password); + Hash.findWeaker = function (href, channel, recents) { + var parsed = parsePadUrl(href); if (!parsed.hash) { return false; } var weaker; Object.keys(recents).some(function (id) { var pad = recents[id]; - var p = parsePadUrl(pad.href, pad.password); + var p = parsePadUrl(pad.href); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger + if (channel !== pad.channel) { return; } // Not the same channel + var pHash = p.hashData; var parsedHash = parsed.hashData; if (!parsedHash || !pHash) { return; } @@ -408,7 +390,6 @@ Version 1 if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; } if (pHash.version !== parsedHash.version) { return; } - if (pHash.channel !== parsedHash.channel) { return; } if (pHash.mode === 'view' && parsedHash.mode === 'edit') { weaker = pad; return true; @@ -417,18 +398,19 @@ Version 1 }); return weaker; }; - Hash.findStronger = function (href, recents, password) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref, password); + Hash.findStronger = function (href, channel, recents) { + var parsed = parsePadUrl(href); if (!parsed.hash) { return false; } // We can't have a stronger hash if we're already in edit mode if (parsed.hashData && parsed.hashData.mode === 'edit') { return; } var stronger; Object.keys(recents).some(function (id) { var pad = recents[id]; - var p = parsePadUrl(pad.href, pad.password); + var p = parsePadUrl(pad.href); if (p.type !== parsed.type) { return; } // Not the same type if (p.hash === parsed.hash) { return; } // Same hash, not stronger + if (channel !== pad.channel) { return; } // Not the same channel + var pHash = p.hashData; var parsedHash = parsed.hashData; if (!parsedHash || !pHash) { return; } @@ -437,7 +419,6 @@ Version 1 if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; } if (pHash.version !== parsedHash.version) { return; } - if (pHash.channel !== parsedHash.channel) { return; } if (pHash.mode === 'edit' && parsedHash.mode === 'view') { stronger = pad; return true; @@ -448,23 +429,10 @@ Version 1 }; Hash.hrefToHexChannelId = function (href, password) { - var parsed = Hash.parsePadUrl(href, password); + var parsed = Hash.parsePadUrl(href); if (!parsed || !parsed.hash) { return; } - - parsed = parsed.hashData; - if (parsed.version === 0) { - return parsed.channel; - } else if (!parsed.version) { - console.error("parsed href had no version"); - console.error(parsed); - return; - } - - var channel = parsed.channel; - if (!channel) { return; } - - var hex = base64ToHex(channel); - return hex; + var secret = Hash.getSecrets(parsed.type, parsed.hash, password); + return secret.channel; }; Hash.getBlobPathFromHex = function (id) { diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 6f4850010..0e4124430 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -629,7 +629,6 @@ define([ var type = data.type; if (!href && !type) { return $icon; } - // Password not needed to get the type if (!type) { type = Hash.parsePadUrl(href).type; } $icon = UI.getIcon(type); diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 13d132219..e98cfb84f 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -99,7 +99,7 @@ define([ try { var parsed = Hash.parsePadUrl(window.location.href); if (!parsed.hashData) { return; } - var chan = parsed.hashData.channel; + var chan = Hash.hrefToHexChannelId(data.href); // Decrypt var keyStr = parsed.hashData.key; var cryptor = Crypto.createEditCryptor(keyStr); @@ -113,7 +113,7 @@ define([ if (!decryptMsg) { return; } // Parse msg = JSON.parse(decryptMsg); - if (msg[1] !== parsed.hashData.channel) { return; } + if (msg[1] !== chan) { return; } var msgData = msg[2]; var msgStr; if (msg[0] === "FRIEND_REQ") { @@ -199,7 +199,7 @@ define([ var parsed = Hash.parsePadUrl(data.href); if (!parsed.hashData) { return; } // Message - var chan = parsed.hashData.channel; + var chan = Hash.hrefToHexChannelId(data.href); var myData = createData(cfg.proxy); var msg = ["FRIEND_REQ", chan, myData]; // Encryption diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 455b095c9..2495b2aac 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -205,7 +205,7 @@ define([ if (content === oldThumbnailState) { return; } oldThumbnailState = content; Thumb.fromDOM(opts, function (err, b64) { - Thumb.setPadThumbnail(common, opts.href, b64); + Thumb.setPadThumbnail(common, opts.href, null, b64); }); }; var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL); @@ -240,20 +240,22 @@ define([ Thumb.addThumbnail = function(thumb, $span, cb) { return addThumbnail(null, thumb, $span, cb); }; - var getKey = function (href) { - var parsed = Hash.parsePadUrl(href); - return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel; + var getKey = function (type, channel) { + return 'thumbnail-' + type + '-' + channel; }; - Thumb.setPadThumbnail = function (common, href, b64, cb) { + Thumb.setPadThumbnail = function (common, href, channel, b64, cb) { cb = cb || function () {}; - var k = getKey(href); + var parsed = Hash.parsePadUrl(href); + var channel = channel || common.getMetadataMgr().getPrivateData().channel; + var k = getKey(parsed.type, channel); common.setThumbnail(k, b64, cb); }; - Thumb.displayThumbnail = function (common, href, $container, cb) { + Thumb.displayThumbnail = function (common, href, channel, $container, cb) { cb = cb || function () {}; var parsed = Hash.parsePadUrl(href); - var k = getKey(href); + var k = getKey(parsed.type, channel); var whenNewThumb = function () { + // PASSWORD_FILES var secret = Hash.getSecrets('file', parsed.hash); var hexFileName = Util.base64ToHex(secret.channel); var src = Hash.getBlobPathFromHex(hexFileName); @@ -270,7 +272,7 @@ define([ if (!v) { v = 'EMPTY'; } - Thumb.setPadThumbnail(common, href, v, function (err) { + Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) { if (!metadata.thumbnail) { return; } addThumbnail(err, metadata.thumbnail, $container, cb); }); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 3cf902a1a..ce4504c2d 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -67,7 +67,7 @@ define([ common.getPadAttribute('href', waitFor(function (err, val) { var base = common.getMetadataMgr().getPrivateData().origin; - var parsed = Hash.parsePadUrl(val, data.password); + var parsed = Hash.parsePadUrl(val); if (parsed.hashData.mode === "view") { data.roHref = base + val; return; @@ -75,6 +75,7 @@ define([ // We're not in a read-only pad data.href = base + val; + // Get Read-only href if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; @@ -84,6 +85,9 @@ define([ var viewHash = Hash.getViewHashFromKeys(hrefsecret); data.roHref = hBase + viewHash; })); + common.getPadAttribute('channel', waitFor(function (err, val) { + data.channel = val; + })); common.getPadAttribute('atime', waitFor(function (err, val) { data.atime = val; })); @@ -180,7 +184,7 @@ define([ if (common.isLoggedIn() && AppConfig.enablePinning) { // check the size of this file... - common.getFileSize(data.href, data.password, function (e, bytes) { + common.getFileSize(data.channel, function (e, bytes) { if (e) { // there was a problem with the RPC console.error(e); @@ -285,7 +289,6 @@ define([ var hash = (edit && hashes.editHash) ? hashes.editHash : hashes.viewHash; var href = origin + pathname + '#' + hash; - // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(href); return origin + parsed.getUrl({embed: embed, present: present}); }; @@ -323,7 +326,6 @@ define([ var getEmbedValue = function () { var hash = hashes.viewHash || hashes.editHash; var href = origin + pathname + '#' + hash; - // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(href); var url = origin + parsed.getUrl({embed: true, present: true}); return ''; @@ -1141,13 +1143,13 @@ define([ }; return; } + // No password for avatars var secret = Hash.getSecrets('file', parsed.hash); if (secret.keys && secret.channel) { var cryptKey = secret.keys && secret.keys.fileKeyStr; var hexFileName = Util.base64ToHex(secret.channel); var src = Hash.getBlobPathFromHex(hexFileName); - // No password for avatars - Common.getFileSize(href, null, function (e, data) { + Common.getFileSize(hexFileName, function (e, data) { if (e) { displayDefault(); return void console.error(e); @@ -1916,13 +1918,13 @@ define([ // Password var password = h('div.cp-creation-password', [ - UI.createCheckbox('cp-creation-password', 'TODO Add a password', false), //XXX + UI.createCheckbox('cp-creation-password', Messages.creation_password, false), h('span.cp-creation-password-picker.cp-creation-slider', [ h('input#cp-creation-password-val', { type: "text" // TODO type password with click to show }), ]), - createHelper('#', "TODO: password protection adds another layer of security ........") // TODO + //createHelper('#', "TODO: password protection adds another layer of security ........") // TODO ]); var right = h('span.fa.fa-chevron-right.cp-creation-template-more'); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d6bae315a..daf033274 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -385,7 +385,6 @@ define([ if (!type) { return void cb(null, obj); } var templates = obj.filter(function (f) { - // Password not needed here since we don't access hashData var parsed = Hash.parsePadUrl(f.href); return parsed.type === type; }); @@ -394,13 +393,12 @@ define([ }; common.saveAsTemplate = function (Cryptput, data, cb) { - // Password not needed here since we don't access hashData var p = Hash.parsePadUrl(window.location.href); if (!p.type) { return; } - // XXX PPP + // PPP: password for the new template? var hash = Hash.createRandomHash(p.type); var href = '/' + p.type + '/#' + hash; - // XXX PPP + // PPP: add password as cryptput option Cryptput(hash, data.toSave, function (e) { if (e) { throw new Error(e); } postMessage("ADD_PAD", { @@ -427,8 +425,6 @@ define([ // opts is used to overrides options for chainpad-netflux in cryptput // it allows us to add owners and expiration time if it is a new file - // Password not needed here, we only need the hash and to know if - // we need to get the password var parsed = Hash.parsePadUrl(href); var parsed2 = Hash.parsePadUrl(window.location.href); if(!parsed) { throw new Error("Cannot get template hash"); } @@ -576,7 +572,6 @@ define([ hashes = Hash.getHashes(secret); return void cb(null, hashes); } - // Password not needed here since only want the type var parsed = Hash.parsePadUrl(window.location.href); if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } @@ -832,17 +827,14 @@ define([ window.onhashchange = function (ev) { if (ev && ev.reset) { oldHref = document.location.href; return; } var newHref = document.location.href; - // Password not needed here since we don't access hashData + + // Compare the URLs without /embed and /present var parsedOld = Hash.parsePadUrl(oldHref); var parsedNew = Hash.parsePadUrl(newHref); if (parsedOld.hashData && parsedNew.hashData && parsedOld.getUrl() !== parsedNew.getUrl()) { - /*parseOld && parsedNew && ( - parsedOld.type !== parsedNew.type - || parsedOld.channel !== parsedNew.channel - || parsedOld.mode !== parsedNew.mode - || parsedOld.key !== parsedNew.key)) {*/ - if (!parsedOld.hashData.channel) { oldHref = newHref; return; } + if (!parsedOld.hashData.key) { oldHref = newHref; return; } + // If different, reload document.location.reload(); return; } diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index a20cd798b..f491643f9 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -122,10 +122,10 @@ define([ // Do not migrate a pad if we already have it, it would create a duplicate in the drive if (newHrefs.indexOf(href) !== -1) { return; } // If we have a stronger version, do not add the current href - if (Hash.findStronger(href, newRecentPads, oldRecentPads[id].password)) { return; } + if (Hash.findStronger(href, oldRecentPads[id].channel, newRecentPads)) { return; } // If we have a weaker version, replace the href by the new one // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted - var weaker = Hash.findWeaker(href, newRecentPads, oldRecentPads[id].password); + var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads); if (weaker) { // Update RECENTPADS weaker.href = href; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 776843812..fb520918d 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -68,8 +68,9 @@ define([ var userHash = storeHash; if (!userHash) { return null; } - var userParsedHash = Hash.parseTypeHash('drive', userHash); - var userChannel = userParsedHash && userParsedHash.channel; + // No password for drive + var secret = Hash.getSecrets('drive', userHash); + var userChannel = secret.channel; if (!userChannel) { return null; } // Get the list of pads' channel ID in your drive @@ -81,14 +82,13 @@ define([ var d = store.userObject.getFileData(id); if (d.owners && d.owners.length && edPublic && d.owners.indexOf(edPublic) === -1) { return; } - return Hash.hrefToHexChannelId(d.href, d.password); + return d.channel; }) .filter(function (x) { return x; }); // Get the avatar var profile = store.proxy.profile; if (profile) { - // No password for profile or avatar var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; if (profileChan) { list.push(profileChan); } var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null; @@ -100,7 +100,7 @@ define([ list = list.concat(fList); } - list.push(Util.base64ToHex(userChannel)); + list.push(userChannel); list.sort(); return list; @@ -116,7 +116,7 @@ define([ // because of the expiration time if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || (data.expire && data.expire < (+new Date()))) { - list.push(Hash.hrefToHexChannelId(data.href, data.password)); + list.push(data.channel); } }); return list; @@ -404,7 +404,6 @@ define([ var makePad = function (href, title) { var now = +new Date(); - // Password not needed here since we only need the type return { href: href, atime: now, @@ -419,6 +418,7 @@ define([ if (data.owners) { pad.owners = data.owners; } if (data.expire) { pad.expire = data.expire; } if (data.password) { pad.password = data.password; } + if (data.channel) { pad.channel = data.channel; } store.userObject.pushData(pad, function (e, id) { if (e) { return void cb({error: "Error while adding a template:"+ e}); } var path = data.path || ['root']; @@ -436,7 +436,7 @@ define([ // Push channels owned by someone else or channel that should have expired // because of the expiration time if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) { - list.push(Hash.hrefToHexChannelId(data.href, data.password)); + list.push(data.channel); } }); if (store.proxy.todo) { @@ -444,7 +444,7 @@ define([ list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null)); } if (store.proxy.profile && store.proxy.profile.edit) { - // No password for todo + // No password for profile list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null)); } return list; @@ -466,6 +466,7 @@ define([ Store.deleteAccount = function (data, cb) { var edPublic = store.proxy.edPublic; + // No password for drive var secret = Hash.getSecrets('drive', storeHash); Store.anonRpcMsg({ msg: 'GET_METADATA', @@ -535,19 +536,13 @@ define([ return void cb({ error: "Error while creating the default pad:"+ e}); } var href = '/pad/#' + hash; + var channel = Hash.hrefToHexChannelId(href, null); var fileData = { href: href, + channel: channel, title: data.driveReadmeTitle, - atime: +new Date(), - ctime: +new Date() }; - store.userObject.pushData(fileData, function (e, id) { - if (e) { - return void cb({ error: "Error while creating the default pad:"+ e}); - } - store.userObject.add(id); - onSync(cb); - }); + addPad(fileData, cb); }); }); }; @@ -619,7 +614,6 @@ define([ }); }; Store.getPadAttribute = function (data, cb) { - console.log(data.href, data.attr); store.userObject.getPadAttribute(data.href, data.attr, function (err, val) { if (err) { return void cb({error: err}); } cb(val); @@ -689,18 +683,18 @@ define([ var p = Hash.parsePadUrl(href); var h = p.hashData; - console.log(channel, data); if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } var owners; - if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { owners = Store.channel.data.owners || undefined; } var expire; - if (Store.channel && Store.channel.wc && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { expire = +Store.channel.data.expire || undefined; } + console.log(owners, expire); var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; var isStronger; @@ -795,7 +789,7 @@ define([ }; store.userObject.getFiles(where).forEach(function (id) { var data = store.userObject.getFileData(id); - var parsed = Hash.parsePadUrl(data.href, data.password); + var parsed = Hash.parsePadUrl(data.href); if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && hashes.indexOf(parsed.hash) === -1 && !isFiltered(parsed.type, data)) { @@ -840,9 +834,9 @@ define([ var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; // If we have a stronger version in drive, add it and add a redirect button - var stronger = Hash.findStronger(data.href, allPads, data.password); + var stronger = Hash.findStronger(data.href, data.channel, allPads); if (stronger) { - var parsed2 = Hash.parsePadUrl(stronger.href, stronger.password); + var parsed2 = Hash.parsePadUrl(stronger.href); return void cb(parsed2.hash); } cb(); @@ -1135,6 +1129,7 @@ define([ if (!hash) { throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); } + // No password for drive var secret = Hash.getSecrets('drive', hash); var listmapConfig = { data: {}, diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 06b62d696..be718562c 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -75,7 +75,7 @@ define([ return void todo(); } if (!pinPads) { return; } - pinPads([Hash.hrefToHexChannelId(data.href, data.password)], function (obj) { + pinPads([data.channel], function (obj) { if (obj && obj.error) { return void cb(obj.error); } todo(); }); @@ -98,7 +98,7 @@ define([ exp.getFiles([FILES_DATA]).forEach(function (id) { if (filesList.indexOf(id) === -1) { var fd = exp.getFileData(id); - var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href, fd.password); + var channelId = fd.channel; // If trying to remove an owned pad, remove it from server also if (!isOwnPadRemoved && fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 274069507..40c3d1d7e 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -315,8 +315,7 @@ define([ privateDat.availableHashes.viewHash; var href = privateDat.pathname + '#' + hash; if (AppConfig.textAnalyzer && textContentGetter) { - var channelId = Hash.hrefToHexChannelId(href, privateDat.password); - AppConfig.textAnalyzer(textContentGetter, channelId); + AppConfig.textAnalyzer(textContentGetter, privateDat.channel); } if (options.thumbnail && privateDat.thumbnails) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index f1cc789fb..bc9d50d00 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -115,6 +115,7 @@ define([ if (cfg.getSecrets) { var w = waitFor(); + // No password for drive, profile and todo cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) { secret = s; Cryptpad.getShareHashes(secret, function (err, h) { @@ -123,7 +124,6 @@ define([ }); })); } else { - // Password not needed here since we only want to know if we need a password var parsed = Utils.Hash.parsePadUrl(window.location.href); var todo = function () { secret = Utils.Hash.getSecrets(parsed.type, void 0, password); @@ -135,7 +135,6 @@ define([ var needPassword = parsed.hashData && parsed.hashData.password; if (needPassword) { Cryptpad.getPadAttribute('password', waitFor(function (err, val) { - console.log(val); if (val) { // We already know the password, use it! password = val; @@ -185,7 +184,7 @@ define([ secret.keys = secret.key; readOnly = false; } - var parsed = Utils.Hash.parsePadUrl(window.location.href, password); + var parsed = Utils.Hash.parsePadUrl(window.location.href); if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.Hash.getDefaultName(parsed); var edPublic; @@ -228,7 +227,8 @@ define([ isNewFile: isNewFile, isDeleted: isNewFile && window.location.hash.length > 0, forceCreationScreen: forceCreationScreen, - password: password + password: password, + channel: secret.channel }; for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } @@ -424,12 +424,10 @@ define([ // Present mode URL sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) { - // Password not needed here since we only need something directly in the hash var parsed = Utils.Hash.parsePadUrl(window.location.href); cb(parsed.hashData && parsed.hashData.present); }); sframeChan.on('EV_PRESENT_URL_SET_VALUE', function (data) { - // Password not needed here var parsed = Utils.Hash.parsePadUrl(window.location.href); window.location.href = parsed.getUrl({ embed: parsed.hashData.embed, @@ -521,10 +519,9 @@ define([ cb(templates.length > 0); }); }); - var getKey = function (href) { - // Password not needed here. We use the fake channel id for thumbnails at the moment + var getKey = function (href, channel) { var parsed = Utils.Hash.parsePadUrl(href); - return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel; + return 'thumbnail-' + parsed.type + '-' + channel; }; sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) { Cryptpad.getSecureFilesList({ @@ -537,7 +534,7 @@ define([ var res = []; nThen(function (waitFor) { Object.keys(data).map(function (el) { - var k = getKey(data[el].href); + var k = getKey(data[el].href, data[el].channel); Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) { res.push({ id: el, @@ -732,7 +729,7 @@ define([ ohc({reset: true}); // Update metadata values and send new metadata inside - parsed = Utils.Hash.parsePadUrl(window.location.href, password); + parsed = Utils.Hash.parsePadUrl(window.location.href); defaultTitle = Utils.Hash.getDefaultName(parsed); hashes = Utils.Hash.getHashes(secret); readOnly = false; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 77308aacd..a1b9a4221 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -127,8 +127,7 @@ define([ } return; }; - funcs.getFileSize = function (href, password, cb) { - var channelId = Hash.hrefToHexChannelId(href, password); + funcs.getFileSize = function (channelId, cb) { funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) { if (!data) { return void cb("No response"); } if (data.error) { return void cb(data.error); } diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 54cc6df6c..1cecffe0c 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -578,7 +578,7 @@ define([ var o = pd.origin; var hashes = pd.availableHashes; var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash); - var cid = Hash.hrefToHexChannelId(url, pd.password); + var cid = pd.channel; Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) { if (x.error || !Array.isArray(x.response)) { return void console.log(x); } if (x.response[0] === true) { diff --git a/www/common/userObject.js b/www/common/userObject.js index f38c7d219..6192c4236 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -78,7 +78,6 @@ define([ exp.isReadOnlyFile = function (element) { if (!isFile(element)) { return false; } var data = exp.getFileData(element); - // Password not needed var parsed = Hash.parsePadUrl(data.href); if (!parsed) { return false; } var pHash = parsed.hashData; @@ -385,11 +384,9 @@ define([ // Get drive ids of files from their channel ids exp.findChannels = function (channels) { var allFilesList = files[FILES_DATA]; - var channels64 = channels.slice().map(Util.hexToBase64); return getFiles([FILES_DATA]).filter(function (k) { var data = allFilesList[k]; - var parsed = Hash.parsePadUrl(data.href, data.password); - return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1; + return channels.indexOf(data.channel) !== -1; }); }; diff --git a/www/drive/inner.js b/www/drive/inner.js index 5ec1cf901..5975d1e26 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1264,7 +1264,6 @@ define([ var data = filesOp.getFileData(element); if (!data) { return void logError("No data for the file", element); } - // Password not needed var hrefData = Hash.parsePadUrl(data.href); if (hrefData.type) { $span.addClass('cp-border-color-'+hrefData.type); @@ -1297,7 +1296,7 @@ define([ $span.attr('title', name); var type = Messages.type[hrefData.type] || hrefData.type; - common.displayThumbnail(data.href, $span, function ($thumb) { + common.displayThumbnail(data.href, data.channel, $span, function ($thumb) { // Called only if the thumbnail exists // Remove the .hide() added by displayThumnail() because it hides the icon in // list mode too @@ -1836,7 +1835,6 @@ define([ var data = filesOp.getFileData(id); if (!data) { return ''; } if (prop === 'type') { - // Password not needed var hrefData = Hash.parsePadUrl(data.href); return hrefData.type; } @@ -1872,7 +1870,6 @@ define([ }; } if (prop === 'type') { - // Password not needed var hrefData = Hash.parsePadUrl(e.href); return hrefData.type; } @@ -2096,7 +2093,6 @@ define([ filesList.forEach(function (r) { r.paths.forEach(function (path) { var href = r.data.href; - // Password not needed var parsed = Hash.parsePadUrl(href); var $table = $(''); var $icon = $('
', {'rowspan': '3', 'class': 'cp-app-drive-search-icon'}) @@ -2653,7 +2649,6 @@ define([ if (!filesOp.isFile(id)) { return; } var data = filesOp.getFileData(id); if (!data) { return; } - // Password not needed var parsed = Hash.parsePadUrl(data.href); if (parsed.hashData.type !== "pad") { return; } var i = data.href.indexOf('#') + 1; diff --git a/www/drive/main.js b/www/drive/main.js index 61afafb2f..79347695c 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -39,6 +39,7 @@ define([ var getSecrets = function (Cryptpad, Utils, cb) { var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash(); + // No password for drive cb(null, Utils.Hash.getSecrets('drive', hash)); }; var addRpc = function (sframeChan, Cryptpad, Utils) { diff --git a/www/file/inner.js b/www/file/inner.js index e7cedab71..44a51d5ba 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -61,7 +61,7 @@ define([ if (!priv.filehash) { uploadMode = true; } else { - // FILE_HASHES2 + // PASSWORD_FILES secret = Hash.getSecrets('file', priv.filehash); if (!secret.keys) { throw new Error("You need a hash"); } hexFileName = Util.base64ToHex(secret.channel); @@ -232,8 +232,7 @@ define([ $dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick); }; var href = priv.origin + priv.pathname + priv.filehash; - // PASSWORD_FILES - common.getFileSize(href, null, function (e, data) { + common.getFileSize(hexFileName, function (e, data) { if (e) { return void UI.errorLoadingScreen(e); } diff --git a/www/filepicker/inner.js b/www/filepicker/inner.js index 194b56bd0..9dd05076a 100644 --- a/www/filepicker/inner.js +++ b/www/filepicker/inner.js @@ -139,7 +139,7 @@ define([ }); // Add thumbnail if it exists - common.displayThumbnail(data.href, $span); + common.displayThumbnail(data.href, data.channel, $span); }); $input.focus(); }; diff --git a/www/profile/main.js b/www/profile/main.js index 1d6e2974d..1bc34e902 100644 --- a/www/profile/main.js +++ b/www/profile/main.js @@ -40,6 +40,7 @@ define([ var Hash = Utils.Hash; // 1st case: visiting someone else's profile with hash in the URL if (window.location.hash) { + // No password for profiles return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1))); } var editHash; @@ -50,6 +51,7 @@ define([ })); }).nThen(function () { if (editHash) { + // No password for profile return void cb(null, Hash.getSecrets('profile', editHash)); } // 3rd case: profile creation (create a new random hash, store it later if needed) @@ -58,6 +60,7 @@ define([ window.location.href = '/drive'; return void cb(); } + // No password for profile var hash = Hash.createRandomHash('profile'); var secret = Hash.getSecrets('profile', hash); Cryptpad.pinPads([secret.channel], function (e) { diff --git a/www/todo/main.js b/www/todo/main.js index 3b1db1ef2..7cd69223e 100644 --- a/www/todo/main.js +++ b/www/todo/main.js @@ -38,6 +38,7 @@ define([ }).nThen(function (/*waitFor*/) { var getSecrets = function (Cryptpad, Utils, cb) { Cryptpad.getTodoHash(function (hash) { + // No password for todo var nHash = hash || Utils.Hash.createRandomHash('todo'); if (!hash) { Cryptpad.setTodoHash(nHash); } cb(null, Utils.Hash.getSecrets('todo', nHash)); diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 9082836b1..4949343ba 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -389,7 +389,7 @@ define([ var D = Thumb.getResizedDimensions($canvas[0], 'pad'); Thumb.fromCanvas($canvas[0], D, function (err, b64) { oldThumbnailState = content; - Thumb.setPadThumbnail(common, href, b64); + Thumb.setPadThumbnail(common, href, privateDat.channel, b64); }); }; window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL); From 24511748701663b8929e3bea1788372f4445a350 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 27 Apr 2018 18:04:21 +0200 Subject: [PATCH 6/6] Lint compliance --- www/common/common-hash.js | 2 +- www/common/common-messaging.js | 2 +- www/common/common-thumbnail.js | 2 +- www/common/outer/async-store.js | 2 +- www/common/toolbar3.js | 1 - www/file/inner.js | 1 - 6 files changed, 4 insertions(+), 6 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 95b80e989..5ba9ec5b3 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -243,7 +243,7 @@ Version 1 return ret; }; - var getRelativeHref = Hash.getRelativeHref = function (href) { + Hash.getRelativeHref = function (href) { if (!href) { return; } if (href.indexOf('#') === -1) { return; } var parsed = parsePadUrl(href); diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index e98cfb84f..b5a12da0b 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -99,7 +99,7 @@ define([ try { var parsed = Hash.parsePadUrl(window.location.href); if (!parsed.hashData) { return; } - var chan = Hash.hrefToHexChannelId(data.href); + var chan = Hash.hrefToHexChannelId(window.location.href); // Decrypt var keyStr = parsed.hashData.key; var cryptor = Crypto.createEditCryptor(keyStr); diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 2495b2aac..ab6d3e6d4 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -246,7 +246,7 @@ define([ Thumb.setPadThumbnail = function (common, href, channel, b64, cb) { cb = cb || function () {}; var parsed = Hash.parsePadUrl(href); - var channel = channel || common.getMetadataMgr().getPrivateData().channel; + channel = channel || common.getMetadataMgr().getPrivateData().channel; var k = getKey(parsed.type, channel); common.setThumbnail(k, b64, cb); }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index fb520918d..cbac4557d 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -542,7 +542,7 @@ define([ channel: channel, title: data.driveReadmeTitle, }; - addPad(fileData, cb); + Store.addPad(fileData, cb); }); }); }; diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 1cecffe0c..b351002fc 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -577,7 +577,6 @@ define([ var pd = config.metadataMgr.getPrivateData(); var o = pd.origin; var hashes = pd.availableHashes; - var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash); var cid = pd.channel; Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) { if (x.error || !Array.isArray(x.response)) { return void console.log(x); } diff --git a/www/file/inner.js b/www/file/inner.js index 44a51d5ba..8e00d9096 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -231,7 +231,6 @@ define([ if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); } $dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick); }; - var href = priv.origin + priv.pathname + priv.filehash; common.getFileSize(hexFileName, function (e, data) { if (e) { return void UI.errorLoadingScreen(e);