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 7db3f5173..c86b81e36 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -145,16 +145,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; @@ -172,6 +174,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; @@ -317,7 +335,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 3454d6242..8b7eec775 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"; @@ -1098,6 +1099,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..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"; @@ -1144,6 +1145,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/assert/main.js b/www/assert/main.js index eb470eb35..f8177a03f 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -223,6 +223,33 @@ define([ hd.type === 'invite'); }, "test support for invite urls"); + // test support for V2 + assert(function (cb) { + 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 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) { 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/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 870b19dfe..5ba9ec5b3 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 @@ -56,25 +117,56 @@ Version 1 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; + parsed.getHash = function () { return hash; }; 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; + + 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 + parsed.version = 2; + parsed.app = hashArr[2]; + parsed.mode = hashArr[3]; + parsed.key = hashArr[4]; + + 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') { @@ -125,17 +217,9 @@ 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 = ret.hashData.getHash(options); + url += '#' + hash; return url; }; @@ -153,12 +237,13 @@ Version 1 return ''; }); idx = href.indexOf('/#'); + if (idx === -1) { return ret; } ret.hash = href.slice(idx + 2); ret.hashData = parseTypeHash(ret.type, ret.hash); return ret; }; - var getRelativeHref = Hash.getRelativeHref = function (href) { + Hash.getRelativeHref = function (href) { if (!href) { return; } if (href.indexOf('#') === -1) { return; } var parsed = parsePadUrl(href); @@ -170,11 +255,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(); @@ -191,7 +278,6 @@ Version 1 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(); @@ -203,9 +289,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,49 +316,63 @@ 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 = password; + if (parsed.type === "pad") { + if (parsed.mode === 'edit') { + 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) { + throw new Error("The channel key and/or the encryption key is invalid"); + } + } + else if (parsed.mode === 'view') { + 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"); + } + } + } 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); - var parsed = parsePadUrl(rHref); + Hash.findWeaker = function (href, channel, recents) { + var parsed = parsePadUrl(href); if (!parsed.hash) { return false; } var weaker; Object.keys(recents).some(function (id) { @@ -279,6 +380,8 @@ Version 1 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; } @@ -287,18 +390,16 @@ 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.href; + weaker = pad; return true; } return; }); return weaker; }; - var findStronger = Hash.findStronger = function (href, recents) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); + 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; } @@ -308,6 +409,8 @@ Version 1 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; } @@ -316,37 +419,20 @@ 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.href; + stronger = pad; return true; } return; }); return stronger; }; - Hash.isNotStrongestStored = function (href, recents) { - return findStronger(href, recents); - }; - Hash.hrefToHexChannelId = function (href) { + Hash.hrefToHexChannelId = function (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 !== 1 && parsed.version !== 2) { - 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 ea2773e37..0e4124430 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) { diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 13d132219..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 = parsed.hashData.channel; + var chan = Hash.hrefToHexChannelId(window.location.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..ab6d3e6d4 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); + 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 2b9487a73..ce4504c2d 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; @@ -71,15 +75,19 @@ 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; 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('channel', waitFor(function (err, val) { + data.channel = val; + })); common.getPadAttribute('atime', waitFor(function (err, val) { data.atime = val; })); @@ -176,7 +184,7 @@ define([ if (common.isLoggedIn() && AppConfig.enablePinning) { // check the size of this file... - common.getFileSize(data.href, function (e, bytes) { + common.getFileSize(data.channel, function (e, bytes) { if (e) { // there was a problem with the RPC console.error(e); @@ -926,14 +934,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) { @@ -1135,12 +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); - Common.getFileSize(href, function (e, data) { + Common.getFileSize(hexFileName, function (e, data) { if (e) { displayDefault(); return void console.error(e); @@ -1907,6 +1916,17 @@ define([ createHelper('/faq.html#keywords-expiring', Messages.creation_expire2), ]); + // Password + var password = h('div.cp-creation-password', [ + 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 + ]); + 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', [ @@ -1932,6 +1952,7 @@ define([ $(h('div#cp-creation-form', [ owned, expire, + password, settings, templates, createDiv @@ -2042,6 +2063,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')) { @@ -2090,12 +2124,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 }; @@ -2165,5 +2203,39 @@ 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 686c69884..623f5946e 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(), @@ -47,8 +47,10 @@ define([ 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; @@ -64,9 +66,11 @@ define([ 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..daf033274 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); }); @@ -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,8 +395,10 @@ define([ common.saveAsTemplate = function (Cryptput, data, cb) { var p = Hash.parsePadUrl(window.location.href); if (!p.type) { return; } - var hash = Hash.createRandomHash(); + // PPP: password for the new template? + var hash = Hash.createRandomHash(p.type); var href = '/' + p.type + '/#' + hash; + // PPP: add password as cryptput option Cryptput(hash, data.toSave, function (e) { if (e) { throw new Error(e); } postMessage("ADD_PAD", { @@ -419,16 +421,33 @@ 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 + 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 () { + Crypt.get(parsed.hash, function (err, val) { + if (err) { throw new Error(err); } + Crypt.put(parsed2.hash, val, cb, optsPut); + }, optsGet); }); }; @@ -439,20 +458,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'); } + + 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); @@ -473,10 +490,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); - }; // Messaging (manage friends from the userlist) common.inviteFromUserlist = function (netfluxId, cb) { @@ -556,15 +569,15 @@ 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) { + 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 +589,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); @@ -813,18 +827,18 @@ 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 && ( - parsedOld.type !== parsedNew.type - || parsedOld.channel !== parsedNew.channel - || parsedOld.mode !== parsedNew.mode - || parsedOld.key !== parsedNew.key)) { - if (!parsedOld.channel) { oldHref = newHref; return; } + + // 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()) { + if (!parsedOld.hashData.key) { oldHref = newHref; return; } + // If different, reload 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/mergeDrive.js b/www/common/mergeDrive.js index c51428b2a..f491643f9 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, 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); + var weaker = Hash.findWeaker(href, oldRecentPads[id].channel, newRecentPads); 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 1ab2db1b1..8dfe19991 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,16 +82,16 @@ 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 d.channel; }) .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; + 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); } } @@ -99,7 +100,7 @@ define([ list = list.concat(fList); } - list.push(Util.base64ToHex(userChannel)); + list.push(userChannel); list.sort(); return list; @@ -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(data.channel); } }); 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') { @@ -316,7 +317,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') { @@ -416,6 +417,8 @@ 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; } + 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']; @@ -433,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(data.channel); } }); 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 profile + list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null)); } return list; }; @@ -461,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', @@ -524,25 +530,19 @@ 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}); } 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); - }); + Store.addPad(fileData, cb); }); }); }; @@ -679,20 +679,22 @@ define([ Store.setPadTitle = function (data, cb) { var title = data.title; var href = data.href; + var channel = data.channel; var p = Hash.parsePadUrl(href); var h = p.hashData; 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; @@ -712,13 +714,13 @@ define([ // 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 === 1 && h2.version === 1 && - 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 @@ -755,10 +757,12 @@ define([ if (!contains) { Store.addPad({ href: href, + channel: channel, title: title, owners: owners, expire: expire, - path: data.path || (store.data && store.data.initialPath) + password: data.password, + path: data.path }, cb); return; } @@ -798,10 +802,7 @@ define([ Store.getPadData = function (id, cb) { cb(store.userObject.getFileData(id)); }; - Store.setInitialPath = function (path) { - if (!store.data) { return; } - store.data.initialPath = path; - }; + // Messaging (manage friends from the userlist) var getMessagingCfg = function () { @@ -833,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); + var stronger = Hash.findStronger(data.href, data.channel, allPads); if (stronger) { - var parsed2 = Hash.parsePadUrl(stronger); + var parsed2 = Hash.parsePadUrl(stronger.href); return void cb(parsed2.hash); } cb(); @@ -1123,11 +1124,12 @@ 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...'); } + // No password for drive var secret = Hash.getSecrets('drive', hash); var listmapConfig = { data: {}, @@ -1150,7 +1152,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/store-rpc.js b/www/common/outer/store-rpc.js index 1713d7a2a..b134435b2 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -120,9 +120,6 @@ define([ case 'GET_PAD_DATA': { Store.getPadData(data, cb); break; } - case 'SET_INITIAL_PATH': { - Store.setInitialPath(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 cce3e6b3c..7f3874511 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -51,14 +51,28 @@ 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; 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 dadbfb463..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)], 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); + 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) { @@ -552,32 +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; } 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-app-framework.js b/www/common/sframe-app-framework.js index 080daaa81..0777d2fb6 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); - AppConfig.textAnalyzer(textContentGetter, channelId); + AppConfig.textAnalyzer(textContentGetter, privateDat.channel); } if (options.thumbnail && privateDat.thumbnails) { 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 014914b50..bc9d50d00 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -24,6 +24,8 @@ define([ var Utils = {}; var AppConfig; var Test; + var password; + var initialPathInDrive; nThen(function (waitFor) { // Load #2, the loading screen is up so grab whatever you need... @@ -113,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) { @@ -121,19 +124,54 @@ define([ }); })); } else { - secret = Utils.Hash.getSecrets(); - if (!secret.channel) { - // New pad: create a new random channel id - secret.channel = Utils.Hash.createChannelId(); + 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"); + } + }), parsed.getUrl()); + return; } - Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); + // If no password, continue... + todo(); } }).nThen(function (waitFor) { // Check if the pad exists on server if (!window.location.hash) { isNewFile = true; return; } if (realtime) { - Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) { + Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) { if (e) { return console.error(e); } isNewFile = Boolean(isNew); })); @@ -188,7 +226,9 @@ define([ }, isNewFile: isNewFile, isDeleted: isNewFile && window.location.hash.length > 0, - forceCreationScreen: forceCreationScreen + forceCreationScreen: forceCreationScreen, + password: password, + channel: secret.channel }; for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } @@ -256,7 +296,13 @@ define([ sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) { currentTitle = newTitle; setDocumentTitle(); - 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); }); }); @@ -473,9 +519,9 @@ define([ cb(templates.length > 0); }); }); - var getKey = function (href) { + 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({ @@ -488,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, @@ -635,7 +681,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 +690,7 @@ define([ return; } if (readOnly || cfg.noHash) { return; } - replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys)); + replaceHash(Utils.Hash.getEditHashFromKeys(secret)); } }; @@ -671,8 +717,9 @@ 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); + 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 +731,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(); @@ -698,7 +745,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) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index ab3c2389d..a1b9a4221 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -113,6 +113,7 @@ define([ return ''; }; funcs.getMediatagFromHref = function (href) { + // PASSWORD_FILES var parsed = Hash.parsePadUrl(href); var secret = Hash.getSecrets('file', parsed.hash); var data = ctx.metadataMgr.getPrivateData(); @@ -126,8 +127,7 @@ define([ } return; }; - funcs.getFileSize = function (href, cb) { - var channelId = Hash.hrefToHexChannelId(href); + 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); } @@ -197,6 +197,7 @@ define([ ctx.sframeChan.query("Q_CREATE_PAD", { owned: cfg.owned, expire: cfg.expire, + password: cfg.password, template: cfg.template, templateId: cfg.templateId }, cb); @@ -378,6 +379,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"); } @@ -428,6 +430,10 @@ define([ UI.log(data.logText); }); + ctx.sframeChan.on("EV_PAD_PASSWORD", function () { + UIElements.displayPasswordPrompt(funcs); + }); + 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/common/toolbar3.js b/www/common/toolbar3.js index 905551d77..b351002fc 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -577,8 +577,7 @@ 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 = Hash.hrefToHexChannelId(url); + 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 4f7f03b23..6192c4236 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -384,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); - 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 df9708d97..5975d1e26 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1296,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 @@ -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/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 7a12c22b1..3d752d95e 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -61,6 +61,7 @@ define([ if (!priv.filehash) { uploadMode = true; } else { + // PASSWORD_FILES secret = Hash.getSecrets('file', priv.filehash); if (!secret.keys) { throw new Error("You need a hash"); } hexFileName = Util.base64ToHex(secret.channel); @@ -233,8 +234,7 @@ 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(href, 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 fa844ab27..9dd05076a 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", { @@ -138,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/pad/inner.js b/www/pad/inner.js index 88d9f5011..5b150c419 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -552,6 +552,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/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, diff --git a/www/profile/main.js b/www/profile/main.js index ccbfafe3a..3afab9077 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,7 +60,8 @@ define([ window.location.href = '/drive/'; return void cb(); } - var hash = Hash.createRandomHash(); + // No password for profile + var hash = Hash.createRandomHash('profile'); var secret = Hash.getSecrets('profile', hash); Cryptpad.pinPads([secret.channel], function (e) { if (e) { @@ -69,8 +72,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); @@ -79,7 +82,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 +90,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; diff --git a/www/todo/main.js b/www/todo/main.js index b86546bf6..7cd69223e 100644 --- a/www/todo/main.js +++ b/www/todo/main.js @@ -38,7 +38,8 @@ define([ }).nThen(function (/*waitFor*/) { var getSecrets = function (Cryptpad, Utils, cb) { Cryptpad.getTodoHash(function (hash) { - var nHash = hash || Utils.Hash.createRandomHash(); + // 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);