From d4d07f33326b1a63ee0f648eca2f62a1b5cda498 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 9 Jan 2020 11:20:35 +0100 Subject: [PATCH 01/18] Add support for ownerKey in the hash (version 1 and 2) --- www/assert/main.js | 26 ++++++++++++++++++++++++++ www/common/common-hash.js | 16 ++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/www/assert/main.js b/www/assert/main.js index cdde441f0..ceff9f58b 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -254,6 +254,32 @@ define([ !secret.hashData.present); }, "test support for trailing slashes in version 1 hash failed to parse"); + // test support for ownerKey + assert(function (cb) { + var secret = Hash.parsePadUrl('/pad/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/present/uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA/embed'); + return cb(secret.hashData.version === 1 && + secret.hashData.mode === "edit" && + secret.hashData.channel === "3Ujt4F2Sjnjbis6CoYWpoQ" && + secret.hashData.key === "usn4+9CqVja8Q7RZOGTfRgqI" && + secret.hashData.ownerKey === "uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA" && + secret.hashData.embed && + secret.hashData.present); + }, "test support for owner key in version 1 hash failed to parse"); + assert(function (cb) { + var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/p/uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA/embed'); + var secret = Hash.getSecrets('pad', parsed.hash); + 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.ownerKey === "uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA" && + parsed.hashData.embed && + parsed.hashData.password); + }, "test support for owner key in version 2 hash failed to parse"); + assert(function (cb) { var secret = Hash.parsePadUrl('/invite/#/2/invite/edit/oRE0oLCtEXusRDyin7GyLGcS/p/'); var hd = secret.hashData; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 4ed1193e3..c26a097e4 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -158,9 +158,17 @@ Version 1 options = hashArr.slice(5); parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; + // Check if we have an ownerKey for this pad + hashArr.some(function (data) { + if (data.length === 86) { // XXX 88 characters - 2 trailing "="... + parsed.ownerKey = data; + return true; + } + }); parsed.getHash = function (opts) { var hash = hashArr.slice(0, 5).join('/') + '/'; + if (parsed.ownerKey) { hash += parsed.ownerKey + '/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } return hash; @@ -177,9 +185,17 @@ Version 1 parsed.password = options.indexOf('p') !== -1; parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; + // Check if we have a ownerKey for this pad + hashArr.some(function (data) { + if (data.length === 86) { // XXX 88 characters - 2 trailing "="... + parsed.ownerKey = data; + return true; + } + }); parsed.getHash = function (opts) { var hash = hashArr.slice(0, 5).join('/') + '/'; + if (parsed.ownerKey) { hash += parsed.ownerKey + '/'; } if (parsed.password) { hash += 'p/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } From 14905a5693e7c69f488fa5debec7ddb955da129c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 9 Jan 2020 11:51:28 +0100 Subject: [PATCH 02/18] Support ownerKey in file hash --- www/common/common-hash.js | 51 +++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index c26a097e4..8f6471f1b 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -15,6 +15,19 @@ var factory = function (Util, Crypto, Nacl) { .decodeUTF8(JSON.stringify(list)))); }; + // XXX move this code? + Hash.generateSignPair = function (safe) { + var ed = Nacl.sign.keyPair(); + var makeSafe = function (key) { + if (!safe) { return key; } + return Crypto.b64RemoveSlashes(key).replace(/=+$/g, ''); + }; + return { + validateKey: makeSafe(encode64(ed.publicKey)), + signKey: makeSafe(encode64(ed.secretKey)), + }; + }; + var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) { var version = secret.version; var data = secret.keys; @@ -134,6 +147,17 @@ Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI */ + var getOwnerKey = function (hashArr) { + var k; + // Check if we have a ownerKey for this pad + hashArr.some(function (data) { + if (data.length === 86) { // XXX 88 characters - 2 trailing "="... + k = data; + return true; + } + }); + return k; + }; var parseTypeHash = Hash.parseTypeHash = function (type, hash) { if (!hash) { return; } var options; @@ -158,17 +182,12 @@ Version 1 options = hashArr.slice(5); parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; - // Check if we have an ownerKey for this pad - hashArr.some(function (data) { - if (data.length === 86) { // XXX 88 characters - 2 trailing "="... - parsed.ownerKey = data; - return true; - } - }); + parsed.ownerKey = getOwnerKey(options); parsed.getHash = function (opts) { var hash = hashArr.slice(0, 5).join('/') + '/'; - if (parsed.ownerKey) { hash += parsed.ownerKey + '/'; } + var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; + if (owner) { hash += owner + '/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } return hash; @@ -185,17 +204,12 @@ Version 1 parsed.password = options.indexOf('p') !== -1; parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; - // Check if we have a ownerKey for this pad - hashArr.some(function (data) { - if (data.length === 86) { // XXX 88 characters - 2 trailing "="... - parsed.ownerKey = data; - return true; - } - }); + parsed.ownerKey = getOwnerKey(options); parsed.getHash = function (opts) { var hash = hashArr.slice(0, 5).join('/') + '/'; - if (parsed.ownerKey) { hash += parsed.ownerKey + '/'; } + var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; + if (owner) { hash += owner + '/'; } if (parsed.password) { hash += 'p/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } @@ -212,6 +226,8 @@ Version 1 parsed.version = 1; parsed.channel = hashArr[2].replace(/-/g, '/'); parsed.key = hashArr[3].replace(/-/g, '/'); + options = hashArr.slice(4); + parsed.ownerKey = getOwnerKey(options); return parsed; } if (hashArr[1] && hashArr[1] === '2') { // Version 2 @@ -223,9 +239,12 @@ Version 1 parsed.password = options.indexOf('p') !== -1; parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; + parsed.ownerKey = getOwnerKey(options); parsed.getHash = function (opts) { var hash = hashArr.slice(0, 4).join('/') + '/'; + var owner = typeof(opts.ownerKey) !== "undefined" ? opts.ownerKey : parsed.ownerKey; + if (owner) { hash += owner + '/'; } if (parsed.password) { hash += 'p/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } From 96a00f89dfa3abe9cc5580de933a34c470d7ae43 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 9 Jan 2020 15:16:07 +0100 Subject: [PATCH 03/18] Generate burn after reading link for pads --- www/assert/main.js | 8 + www/common/common-hash.js | 9 +- www/common/common-interface.js | 6 + www/common/common-ui-elements.js | 264 +++++++++++++++++++++--------- www/common/outer/invitation.js | 1 + www/common/sframe-common-outer.js | 23 ++- 6 files changed, 219 insertions(+), 92 deletions(-) diff --git a/www/assert/main.js b/www/assert/main.js index ceff9f58b..c29b3bfa3 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -279,6 +279,14 @@ define([ parsed.hashData.embed && parsed.hashData.password); }, "test support for owner key in version 2 hash failed to parse"); + assert(function (cb) { + var secret = Hash.parsePadUrl('/file/#/1/TRplGM-WsVkXR+LkJ0tD3D45A1YFZ-Cy/eO4RJwh8yHEEDhl1aHfuwQ2IzosPBZx-HDaWc1lW+hY=/uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA/'); + return cb(secret.hashData.version === 1 && + secret.hashData.channel === "TRplGM/WsVkXR+LkJ0tD3D45A1YFZ/Cy" && + secret.hashData.key === "eO4RJwh8yHEEDhl1aHfuwQ2IzosPBZx/HDaWc1lW+hY=" && + secret.hashData.ownerKey === "uPmJDtDJ9okhdIyQ-8zphYlpaAonJDOC6MAcYY6iBwWBQr+XmrQ9uGY9WkApJTfEfAu5QcqaDCw1Ul+JXKcYkA" && + !secret.hashData.present); + }, "test support for owner key in version 1 file hash failed to parse"); assert(function (cb) { var secret = Hash.parsePadUrl('/invite/#/2/invite/edit/oRE0oLCtEXusRDyin7GyLGcS/p/'); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 8f6471f1b..902a91793 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -16,15 +16,16 @@ var factory = function (Util, Crypto, Nacl) { }; // XXX move this code? - Hash.generateSignPair = function (safe) { + Hash.generateSignPair = function () { var ed = Nacl.sign.keyPair(); var makeSafe = function (key) { - if (!safe) { return key; } return Crypto.b64RemoveSlashes(key).replace(/=+$/g, ''); }; return { - validateKey: makeSafe(encode64(ed.publicKey)), - signKey: makeSafe(encode64(ed.secretKey)), + validateKey: Hash.encodeBase64(ed.publicKey), + signKey: Hash.encodeBase64(ed.secretKey), + safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)), + safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)), }; }; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 4528b7a1b..55d7e6939 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -209,10 +209,16 @@ define([ $(title).prepend(' ').prepend(icon); } $(title).click(function () { + var old = tabs[active]; + if (old.onHide) { old.onHide(); } titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); }); contents.forEach(function (c) { $(c).removeClass('alertify-tabs-content-active'); }); + if (tab.onShow) { + tab.onShow(); + } $(title).addClass('alertify-tabs-active'); $(content).addClass('alertify-tabs-content-active'); + active = i; }); titles.push(title); contents.push(content); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 6323be780..d9f0af36c 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -917,60 +917,79 @@ define([ className: 'primary cp-share-with-friends', name: Messages.share_withFriends, onClick: function () { - var href = Hash.getRelativeHref(linkGetter()); - var $friends = $div.find('.cp-usergrid-user.cp-selected'); - $friends.each(function (i, el) { - var curve = $(el).attr('data-curve'); - // Check if the selected element is a friend or a team - if (curve) { // Friend - if (!curve || !friends[curve]) { return; } - var friend = friends[curve]; - if (!friend.notifications || !friend.curvePublic) { return; } - common.mailbox.sendTo("SHARE_PAD", { + var href; + NThen(function (waitFor) { + var w = waitFor(); + // linkGetter can be async if this is a burn after reading URL + var res = linkGetter({}, function (url) { + if (!url) { + waitFor.abort(); + return; + } + console.warn('BAR'); + href = url; + setTimeout(w); + }); + if (res && /^http/.test(res)) { + href = Hash.getRelativeHref(res); + setTimeout(w); + return; + } + }).nThen(function () { + var $friends = $div.find('.cp-usergrid-user.cp-selected'); + $friends.each(function (i, el) { + var curve = $(el).attr('data-curve'); + // Check if the selected element is a friend or a team + if (curve) { // Friend + if (!curve || !friends[curve]) { return; } + var friend = friends[curve]; + if (!friend.notifications || !friend.curvePublic) { return; } + common.mailbox.sendTo("SHARE_PAD", { + href: href, + password: config.password, + isTemplate: config.isTemplate, + name: myName, + title: title + }, { + channel: friend.notifications, + curvePublic: friend.curvePublic + }); + return; + } + // Team + var ed = $(el).attr('data-ed'); + var team = teams[ed]; + if (!team) { return; } + sframeChan.query('Q_STORE_IN_TEAM', { href: href, password: config.password, - isTemplate: config.isTemplate, - name: myName, - title: title - }, { - channel: friend.notifications, - curvePublic: friend.curvePublic + path: config.isTemplate ? ['template'] : undefined, + title: title, + teamId: team.id + }, function (err) { + if (err) { return void console.error(err); } }); - return; - } - // Team - var ed = $(el).attr('data-ed'); - var team = teams[ed]; - if (!team) { return; } - sframeChan.query('Q_STORE_IN_TEAM', { - href: href, - password: config.password, - path: config.isTemplate ? ['template'] : undefined, - title: title, - teamId: team.id - }, function (err) { - if (err) { return void console.error(err); } }); - }); - UI.findCancelButton().click(); - - // Update the "recently shared with" array: - // Get the selected curves - var curves = $friends.toArray().map(function (el) { - return ($(el).attr('data-curve') || '').slice(0,8); - }).filter(function (x) { return x; }); - // Prepend them to the "order" array - Array.prototype.unshift.apply(order, curves); - order = Util.deduplicateString(order); - // Make sure we don't have "old" friends and save - order = order.filter(function (curve) { - return smallCurves.indexOf(curve) !== -1; + UI.findCancelButton().click(); + + // Update the "recently shared with" array: + // Get the selected curves + var curves = $friends.toArray().map(function (el) { + return ($(el).attr('data-curve') || '').slice(0,8); + }).filter(function (x) { return x; }); + // Prepend them to the "order" array + Array.prototype.unshift.apply(order, curves); + order = Util.deduplicateString(order); + // Make sure we don't have "old" friends and save + order = order.filter(function (curve) { + return smallCurves.indexOf(curve) !== -1; + }); + common.setAttribute(['general', 'share-friends'], order); + if (onShare) { + onShare.fire(); + } }); - common.setAttribute(['general', 'share-friends'], order); - if (onShare) { - onShare.fire(); - } }, keys: [13] }; @@ -1049,6 +1068,29 @@ define([ } }; + var makeBurnAfterReadingUrl = function (common, href, channel, cb) { + var keyPair = Hash.generateSignPair(); + var parsed = Hash.parsePadUrl(href); + console.error(href, parsed); + var newHref = parsed.getUrl({ + ownerKey: keyPair.safeSignKey + }); + var sframeChan = common.getSframeChannel(); + NThen(function (waitFor) { + sframeChan.query('Q_SET_PAD_METADATA', { + channel: channel, + command: 'ADD_OWNERS', + value: [keyPair.validateKey] + }, waitFor(function (err) { + if (err) { + waitFor.abort(); + UI.warn(Messages.error); + } + })); + }).nThen(function () { + cb(newHref); + }); + }; UIElements.createShareModal = function (config) { var origin = config.origin; var pathname = config.pathname; @@ -1078,6 +1120,7 @@ define([ var parsed = Hash.parsePadUrl(pathname); var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1; + var burnAfterReading; var rights = h('div.msg.cp-inline-radio-group', [ h('label', Messages.share_linkAccess), h('div.radio-group',[ @@ -1086,9 +1129,33 @@ define([ canPresent ? UI.createRadio('accessRights', 'cp-share-present', Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined, UI.createRadio('accessRights', 'cp-share-editable-true', - Messages.share_linkEdit, false, { mark: {tabindex:1} })]) + Messages.share_linkEdit, false, { mark: {tabindex:1} })]), + burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', + 'BAR', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX ]); + // Burn after reading + // Check if we are an owner of this pad. If we are, we can show the burn after reading option. + // When BAR is selected, display a red message indicating the consequence and add + // the options to generate the BAR url + var barAlert = h('div.alert.alert-danger.cp-alertify-bar-selected', { + style: 'display: none;' + }, " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server. "); // XXX + var channel = Hash.getSecrets('pad', hash, config.password).channel; + common.getPadMetadata({ + channel: channel + }, function (obj) { + if (!obj || obj.error) { return; } + var priv = common.getMetadataMgr().getPrivateData(); + // Not an owner: don't display the burn after reading option + if (!Array.isArray(obj.owners) || obj.owners.indexOf(priv.edPublic) === -1) { + $(burnAfterReading).remove(); + return; + } + // When the burn after reading option is selected, transform the modal buttons + $(burnAfterReading).show(); + }); + var $rights = $(rights); var saveValue = function () { @@ -1100,13 +1167,25 @@ define([ }); }; - var getLinkValue = function (initValue) { + var burnAfterReadingUrl; + + var getLinkValue = function (initValue, cb) { var val = initValue || {}; var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true')); var embed = val.embed; var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present')); + var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar')); + if (burnAfterReading && !burnAfterReadingUrl) { + if (cb) { // Called from the contacts tab, "share" button + var barHref = origin + pathname + '#' + (hashes.viewHash || hashes.editHash); + return makeBurnAfterReadingUrl(common, barHref, channel, function (url) { + cb(url); + }); + } + return 'XXX Click on the button below to generate a link'; // XXX + } var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash; - var href = origin + pathname + '#' + hash; + var href = burnAfterReading ? burnAfterReadingUrl : (origin + pathname + '#' + hash); var parsed = Hash.parsePadUrl(href); return origin + parsed.getUrl({embed: embed, present: present}); }; @@ -1160,8 +1239,8 @@ define([ }); }); - - + + linkContent.push($(barAlert).clone()[0]); // Burn after reading var link = h('div.cp-share-modal', linkContent); var $link = $(link); @@ -1169,7 +1248,7 @@ define([ var linkButtons = [ makeCancelButton(), !config.sharedFolder && { - className: 'secondary', + className: 'secondary cp-nobar', name: Messages.share_linkOpen, onClick: function () { saveValue(); @@ -1180,9 +1259,8 @@ define([ return true; }, keys: [[13, 'ctrl']] - }, - { - className: 'primary', + }, { + className: 'primary cp-nobar', name: Messages.share_linkCopy, onClick: function () { saveValue(); @@ -1193,26 +1271,26 @@ define([ if (success) { UI.log(Messages.shareSuccess); } }, keys: [13] + }, { + className: 'primary cp-bar', + name: 'GENERATE LINK', + onClick: function () { + var barHref = origin + pathname + '#' + (hashes.viewHash || hashes.editHash); + makeBurnAfterReadingUrl(common, barHref, channel, function (url) { + burnAfterReadingUrl = url; + $rights.find('input[type="radio"]').trigger('change'); + }); + return true; + }, + keys: [] } ]; - // update values for link preview when radio btns change - $link.find('#cp-share-link-preview').val(getLinkValue()); - $rights.find('input[type="radio"]').on('change', function () { - $link.find('#cp-share-link-preview').val(getLinkValue({ - embed: Util.isChecked($link.find('#cp-share-embed')) - })); - }); - $link.find('input[type="checkbox"]').on('change', function () { - $link.find('#cp-share-link-preview').val(getLinkValue({ - embed: Util.isChecked($link.find('#cp-share-embed')) - })); - }); - var frameLink = UI.dialog.customModal(link, { buttons: linkButtons, onClose: config.onClose, }); + $(frameLink).find('.cp-bar').hide(); // Share with contacts tab @@ -1240,6 +1318,7 @@ define([ ])); } + $(contactsContent).append($(barAlert).clone()); // Burn after reading var contactButtons = friendsObject.buttons; contactButtons.unshift(makeCancelButton()); @@ -1282,21 +1361,52 @@ define([ keys: [13] }]; + var onShowEmbed = function () { + $rights.find('#cp-share-bar').closest('label').hide(); + $rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked'); + $rights.find('input[type="radio"]').trigger('change'); + }; + var embed = h('div.cp-share-modal', embedContent); var $embed = $(embed); - // update values for link preview when radio btns change + var frameEmbed = UI.dialog.customModal(embed, { + buttons: embedButtons, + onClose: config.onClose, + }); + + // update values for link and embed preview when radio btns change $embed.find('#cp-embed-link-preview').val(getEmbedValue()); + $link.find('#cp-share-link-preview').val(getLinkValue()); $rights.find('input[type="radio"]').on('change', function () { + $link.find('#cp-share-link-preview').val(getLinkValue({ + embed: Util.isChecked($link.find('#cp-share-embed')) + })); + // Hide or show the burn after reading alert + if (Util.isChecked($rights.find('#cp-share-bar'))) { + $('.cp-alertify-bar-selected').show(); + // Show burn after reading button + $('.alertify').find('.cp-bar').show(); + $('.alertify').find('.cp-nobar').hide(); + return; + } $embed.find('#cp-embed-link-preview').val(getEmbedValue()); + // Hide burn after reading button + $('.alertify').find('.cp-nobar').show(); + $('.alertify').find('.cp-bar').hide(); + $('.cp-alertify-bar-selected').hide(); }); - - var frameEmbed = UI.dialog.customModal(embed, { - buttons: embedButtons, - onClose: config.onClose, + $link.find('input[type="checkbox"]').on('change', function () { + $link.find('#cp-share-link-preview').val(getLinkValue({ + embed: Util.isChecked($link.find('#cp-share-embed')) + })); }); + // Create modal + var resetTab = function () { + $rights.find('label.cp-radio').show(); + }; var tabs = [{ title: Messages.share_contactCategory, icon: "fa fa-address-book", @@ -1310,7 +1420,9 @@ define([ }, { title: Messages.share_embedCategory, icon: "fa fa-code", - content: frameEmbed + content: frameEmbed, + onShow: onShowEmbed, + onHide: resetTab }]; if (typeof(AppConfig.customizeShareOptions) === 'function') { AppConfig.customizeShareOptions(hashes, tabs, { diff --git a/www/common/outer/invitation.js b/www/common/outer/invitation.js index 603cae3c1..d84d7a81f 100644 --- a/www/common/outer/invitation.js +++ b/www/common/outer/invitation.js @@ -17,6 +17,7 @@ var factory = function (Util, Cred, Nacl) { }; }; + // XXX move this function? Invite.generateSignPair = function () { var ed = Nacl.sign.keyPair(); return { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 05cafbdec..9308f05cb 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -507,6 +507,17 @@ define([ } }); + sframeChan.on('Q_GET_PAD_METADATA', function (data, cb) { + if (!data || !data.channel) { + data = { + channel: secret.channel + }; + } + Cryptpad.getPadMetadata(data, cb); + }); + sframeChan.on('Q_SET_PAD_METADATA', function (data, cb) { + Cryptpad.setPadMetadata(data, cb); + }); }; addCommonRpc(sframeChan); @@ -1170,18 +1181,6 @@ define([ }); }); - sframeChan.on('Q_GET_PAD_METADATA', function (data, cb) { - if (!data || !data.channel) { - data = { - channel: secret.channel - }; - } - Cryptpad.getPadMetadata(data, cb); - }); - sframeChan.on('Q_SET_PAD_METADATA', function (data, cb) { - Cryptpad.setPadMetadata(data, cb); - }); - if (cfg.messaging) { Notifier.getPermission(); From 9ee9e4608716af91a655dcf02469484517aeb9a8 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 9 Jan 2020 17:30:15 +0100 Subject: [PATCH 04/18] Receiving a burn after reading URL --- customize.dist/src/less2/include/toolbar.less | 6 +++ www/code/inner.js | 1 + www/common/common-ui-elements.js | 25 +++++++++++ www/common/cryptpad-common.js | 4 ++ www/common/onlyoffice/inner.js | 1 + www/common/outer/async-store.js | 45 ++++++++++++++----- www/common/outer/store-rpc.js | 1 + www/common/sframe-common-outer.js | 15 +++++++ www/common/sframe-common.js | 8 ++++ www/kanban/inner.js | 2 + www/pad/inner.js | 1 + www/poll/inner.js | 1 + www/slide/inner.js | 1 + www/whiteboard/inner.js | 1 + 14 files changed, 100 insertions(+), 12 deletions(-) diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 0df5823a0..1d1ca0c47 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -97,6 +97,12 @@ .ckeditor_fix(); + .cp-burn-after-reading { + text-align: center; + font-size: @colortheme_app-font-size !important; + margin: 0 !important; + } + .cp-markdown-toolbar { height: @toolbar_line-height; background-color: @toolbar-bg-color-l20; diff --git a/www/code/inner.js b/www/code/inner.js index e2ed086de..e933bd4bd 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -98,6 +98,7 @@ define([ }; var mkHelpMenu = function (framework) { var $codeMirrorContainer = $('#cp-app-code-container'); + $codeMirrorContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning()); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'code']); $codeMirrorContainer.prepend(helpMenu.menu); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d9f0af36c..1964ed570 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3978,6 +3978,7 @@ define([ UIElements.onServerError = function (common, err, toolbar, cb) { if (["EDELETED", "EEXPIRED"].indexOf(err.type) === -1) { return; } + var priv = common.getMetadataMgr().getPrivateData(); var msg = err.type; if (err.type === 'EEXPIRED') { msg = Messages.expiredError; @@ -3985,6 +3986,7 @@ define([ msg += Messages.errorCopy; } } else if (err.type === 'EDELETED') { + if (priv.burnAfterReading) { return void cb(); } msg = Messages.deletedError; if (err.loaded) { msg += Messages.errorCopy; @@ -4034,6 +4036,26 @@ define([ $password.find('.cp-password-input').focus(); }; + UIElements.displayBurnAfterReadingPage = function (common, cb) { + var info = h('p.cp-password-info', 'XXX Burn after reading'); // XXX + var button = h('button', 'Proceed'); // XXX + + $(button).on('click', function () { + cb(); + }); + + var block = h('div#cp-loading-burn-after-reading', [ + info, + button + ]); + UI.errorLoadingScreen(block); + }; + UIElements.getBurnAfterReadingWarning = function (common) { + var priv = common.getMetadataMgr().getPrivateData(); + if (!priv.burnAfterReading) { return; } + return h('div.alert.alert-danger.cp-burn-after-reading', 'Pewpewpew'); // XXX + }; + var crowdfundingState = false; UIElements.displayCrowdfunding = function (common) { if (crowdfundingState) { return; } @@ -4091,6 +4113,9 @@ define([ if (data && data.stored) { return; } // We won't display the popup for dropped files var priv = common.getMetadataMgr().getPrivateData(); + // This pad will be deleted automatically, it shouldn't be stored + if (priv.burnAfterReading) { return; } + var typeMsg = priv.pathname.indexOf('/file/') !== -1 ? Messages.autostore_file : priv.pathname.indexOf('/drive/') !== -1 ? Messages.autostore_sf : Messages.autostore_pad; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 57815b3ef..a0057c59a 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -847,6 +847,10 @@ define([ postMessage('GET_PAD_METADATA', data, cb); }; + common.burnPad = function (data) { + postMessage('BURN_PAD', data); + }; + common.changePadPassword = function (Crypt, Crypto, data, cb) { var href = data.href; var newPassword = data.password; diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 0958b024d..a40ccccae 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -926,6 +926,7 @@ define([ $rightside.append($forget); var helpMenu = common.createHelpMenu(['beta', 'oo']); + $('#cp-app-oo-editor').prepend(common.getBurnAfterReadingWarning()); $('#cp-app-oo-editor').prepend(helpMenu.menu); toolbar.$drawer.append(helpMenu.button); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 870a539e7..1d1f26ba3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -9,6 +9,7 @@ define([ '/common/common-feedback.js', '/common/common-realtime.js', '/common/common-messaging.js', + '/common/pinpad.js', '/common/outer/sharedfolder.js', '/common/outer/cursor.js', '/common/outer/onlyoffice.js', @@ -26,7 +27,7 @@ define([ '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', ], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, - Realtime, Messaging, + Realtime, Messaging, Pinpad, SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, NetConfig, AppConfig, Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) { @@ -409,19 +410,17 @@ define([ var initRpc = function (clientId, data, cb) { if (!store.loggedIn) { return cb(); } if (store.rpc) { return void cb(account); } - require(['/common/pinpad.js'], function (Pinpad) { - Pinpad.create(store.network, store.proxy, function (e, call) { - if (e) { return void cb({error: e}); } + Pinpad.create(store.network, store.proxy, function (e, call) { + if (e) { return void cb({error: e}); } - store.rpc = call; + store.rpc = call; - Store.getPinLimit(null, null, function (obj) { - if (obj.error) { console.error(obj.error); } - account.limit = obj.limit; - account.plan = obj.plan; - account.note = obj.note; - cb(obj); - }); + Store.getPinLimit(null, null, function (obj) { + if (obj.error) { console.error(obj.error); } + account.limit = obj.limit; + account.plan = obj.plan; + account.note = obj.note; + cb(obj); }); }); }; @@ -1653,6 +1652,28 @@ define([ cb(); }; + // Delete a pad received with a burn after reading URL + Store.burnPad = function (clientId, data, cb) { + var channel = data.channel; + var ownerKey = Crypto.b64AddSlashes(data.ownerKey || ''); + if (!channel || !ownerKey) { return void console.error("Can't delete BAR pad"); } + try { + var signKey = Hash.decodeBase64(ownerKey); + var pair = Crypto.Nacl.sign.keyPair.fromSecretKey(signKey); + Pinpad.create(store.network, { + edPublic: Hash.encodeBase64(pair.publicKey), + edPrivate: Hash.encodeBase64(pair.secretKey) + }, function (e, rpc) { + if (e) { return void console.error(e); } + rpc.removeOwnedChannel(channel, function (err) { + if (err) { console.error(err); } + }); + }); + } catch (e) { + console.error(e); + } + }; + // Fetch the latest version of the metadata on the server and return it. // If the pad is stored in our drive, update the local values of "owners" and "expire" Store.getPadMetadata = function (clientId, data, cb) { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 49250582f..41963402b 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -80,6 +80,7 @@ define([ IS_NEW_CHANNEL: Store.isNewChannel, REQUEST_PAD_ACCESS: Store.requestPadAccess, GIVE_PAD_ACCESS: Store.givePadAccess, + BURN_PAD: Store.burnPad, GET_PAD_METADATA: Store.getPadMetadata, SET_PAD_METADATA: Store.setPadMetadata, CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin, diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 9308f05cb..c19e9e344 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -323,6 +323,7 @@ define([ } Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); var parsed = Utils.Hash.parsePadUrl(window.location.href); + var burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey; if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.UserObject.getDefaultName(parsed); var edPublic, curvePublic, notifications, isTemplate; @@ -376,6 +377,7 @@ define([ fromFileData: Cryptpad.fromFileData ? { title: Cryptpad.fromFileData.title } : undefined, + burnAfterReading: burnAfterReading, storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined) }; if (window.CryptPad_newSharedFolder) { @@ -1235,6 +1237,14 @@ define([ window.location.hash = hash; }; + if (burnAfterReading) { + Cryptpad.padRpc.onReadyEvent.reg(function () { + Cryptpad.burnPad({ + channel: secret.channel, + ownerKey: burnAfterReading + }); + }); + } var cpNfCfg = { sframeChan: sframeChan, channel: secret.channel, @@ -1358,12 +1368,17 @@ define([ }); }); + sframeChan.on('EV_BURN_AFTER_READING', function () { + startRealtime(); + }); + sframeChan.ready(); Utils.Feedback.reportAppUsage(); if (!realtime && !Test.testing) { return; } if (isNewFile && cfg.useCreationScreen && !Test.testing) { return; } + if (burnAfterReading) { return; } //if (isNewFile && Utils.LocalStore.isLoggedIn() // && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; } diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index f8bbb205b..9e51d46de 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -96,6 +96,7 @@ define([ funcs.createMarkdownToolbar = callWithCommon(UIElements.createMarkdownToolbar); funcs.createHelpMenu = callWithCommon(UIElements.createHelpMenu); funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen); + funcs.getBurnAfterReadingWarning = callWithCommon(UIElements.getBurnAfterReadingWarning); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.onServerError = callWithCommon(UIElements.onServerError); funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu); @@ -300,6 +301,13 @@ define([ } // If we display the pad creation screen, it will handle deleted pads directly funcs.getPadCreationScreen(c, config, waitFor()); + return; + } + if (priv.burnAfterReading) { + UIElements.displayBurnAfterReadingPage(funcs, waitFor(function () { + UI.addLoadingScreen(); + ctx.sframeChan.event('EV_BURN_AFTER_READING'); + })); } }; funcs.createPad = function (cfg, cb) { diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 5a9e10f47..621d56cae 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -350,6 +350,8 @@ define([ var mkHelpMenu = function (framework) { var $toolbarContainer = $('#cp-app-kanban-container'); + $toolbarContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning()); + var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']); $toolbarContainer.prepend(helpMenu.menu); diff --git a/www/pad/inner.js b/www/pad/inner.js index 5db93837c..78f027182 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -190,6 +190,7 @@ define([ var mkHelpMenu = function (framework) { var $toolbarContainer = $('.cke_toolbox_main'); + $toolbarContainer.before(framework._.sfCommon.getBurnAfterReadingWarning()); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'pad']); $toolbarContainer.before(helpMenu.menu); diff --git a/www/poll/inner.js b/www/poll/inner.js index ba8fd7d65..7420f5c09 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -1187,6 +1187,7 @@ define([ $drawer.append($export); var helpMenu = common.createHelpMenu(['poll']); + $('#cp-app-poll-form').prepend(common.getBurnAfterReadingWarning()); $('#cp-app-poll-form').prepend(helpMenu.menu); $drawer.append(helpMenu.button); diff --git a/www/slide/inner.js b/www/slide/inner.js index 6d97bf8e5..5736828d5 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -410,6 +410,7 @@ define([ var mkHelpMenu = function (framework) { var $codeMirrorContainer = $('#cp-app-slide-editor-container'); + $codeMirrorContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning()); var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'slide']); $codeMirrorContainer.prepend(helpMenu.menu); diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index a90dd4187..77057f505 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -270,6 +270,7 @@ define([ var mkHelpMenu = function (framework) { var $appContainer = $('#cp-app-whiteboard-container'); + $appContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning()); var helpMenu = framework._.sfCommon.createHelpMenu(['whiteboard']); $appContainer.prepend(helpMenu.menu); framework._.toolbar.$drawer.append(helpMenu.button); From 1bf48a5a8ccfadef44b6900584c534aa87383f2e Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 9 Jan 2020 17:30:53 +0100 Subject: [PATCH 05/18] lint compliance --- www/common/outer/async-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 1d1f26ba3..e39a2f980 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1653,7 +1653,7 @@ define([ }; // Delete a pad received with a burn after reading URL - Store.burnPad = function (clientId, data, cb) { + Store.burnPad = function (clientId, data) { var channel = data.channel; var ownerKey = Crypto.b64AddSlashes(data.ownerKey || ''); if (!channel || !ownerKey) { return void console.error("Can't delete BAR pad"); } From a3b3a9e4fbc7cee388385bcdf47ff06b2ad59eab Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 9 Jan 2020 17:47:10 +0100 Subject: [PATCH 06/18] Fix button not updating when generating a BAR url --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 1964ed570..415390909 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1383,7 +1383,7 @@ define([ embed: Util.isChecked($link.find('#cp-share-embed')) })); // Hide or show the burn after reading alert - if (Util.isChecked($rights.find('#cp-share-bar'))) { + if (Util.isChecked($rights.find('#cp-share-bar')) && !burnAfterReadingUrl) { $('.cp-alertify-bar-selected').show(); // Show burn after reading button $('.alertify').find('.cp-bar').show(); From 5ee12f8da7c4c98b31e474146da9ed003899fd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Fri, 10 Jan 2020 13:07:31 +0000 Subject: [PATCH 07/18] hard coded keys for testing --- www/common/common-ui-elements.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 415390909..310c7172e 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1131,7 +1131,7 @@ define([ UI.createRadio('accessRights', 'cp-share-editable-true', Messages.share_linkEdit, false, { mark: {tabindex:1} })]), burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', - 'BAR', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX + 'View once and self-destruct', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX KEY ]); // Burn after reading @@ -1140,7 +1140,7 @@ define([ // the options to generate the BAR url var barAlert = h('div.alert.alert-danger.cp-alertify-bar-selected', { style: 'display: none;' - }, " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server. "); // XXX + }, " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server. "); // XXX KEY var channel = Hash.getSecrets('pad', hash, config.password).channel; common.getPadMetadata({ channel: channel @@ -1182,7 +1182,7 @@ define([ cb(url); }); } - return 'XXX Click on the button below to generate a link'; // XXX + return 'Click on the button below to generate a link'; // XXX KEY } var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash; var href = burnAfterReading ? burnAfterReadingUrl : (origin + pathname + '#' + hash); @@ -4037,8 +4037,8 @@ define([ }; UIElements.displayBurnAfterReadingPage = function (common, cb) { - var info = h('p.cp-password-info', 'XXX Burn after reading'); // XXX - var button = h('button', 'Proceed'); // XXX + var info = h('p.cp-password-info', 'This document will self-destruct as soon as you open it. It will be removed form the server, once you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later. '); // XXX KEY + var button = h('button.primary', 'Proceed'); // XXX KEY $(button).on('click', function () { cb(); @@ -4053,7 +4053,7 @@ define([ UIElements.getBurnAfterReadingWarning = function (common) { var priv = common.getMetadataMgr().getPrivateData(); if (!priv.burnAfterReading) { return; } - return h('div.alert.alert-danger.cp-burn-after-reading', 'Pewpewpew'); // XXX + return h('div.alert.alert-danger.cp-burn-after-reading', 'This pad has been deleted from the server, once you close this window you will not be able to access it again.'); // XXX KEY }; var crowdfundingState = false; From 7042b9c2d73a1b9df98b5e48ec8a5767ae9f88ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Fri, 10 Jan 2020 13:07:52 +0000 Subject: [PATCH 08/18] style loading screen message --- customize.dist/loading.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 600f3e8d4..10db051ff 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -77,7 +77,7 @@ define([], function () { background: #FFF; padding: 20px; width: 100%; - color: #000; + color: #3F4141; text-align: center; display: none; } @@ -94,6 +94,9 @@ define([], function () { text-align: left; margin-bottom: 15px; } +p.cp-password-info{ + text-align: left; +} #cp-loading-password-prompt .cp-password-form { display: flex; justify-content: space-around; @@ -201,6 +204,19 @@ define([], function () { animation-timing-function: cubic-bezier(.6,0.15,0.4,0.85); } +button.primary{ + border: 1px solid #4591c4; + padding: 8px 12px; + text-transform: uppercase; + background-color: #4591c4; + color: white; + font-weight: bold; +} + +button.primary:hover{ + background-color: rgb(52, 118, 162); +} + */}).toString().slice(14, -3); var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; }); var elem = document.createElement('div'); From f1d1690cf88fb5139be50cc6071dd2f7a5c6629b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Fri, 10 Jan 2020 13:11:16 +0000 Subject: [PATCH 09/18] access rights buttons --- customize.dist/src/less2/include/modals-ui-elements.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 289cc4e69..cee774a98 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -14,6 +14,9 @@ .radio-group { display: flex; flex-direction: row; + &:not(:last-child){ + margin-bottom: 8px; + } .cp-radio { margin-right: 30px; } From bdd338902b6b27b5ee45b75715024a03db80cba8 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 10 Jan 2020 15:53:02 +0100 Subject: [PATCH 10/18] Hide radio buttons in share modal when no contacts --- www/common/common-ui-elements.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 310c7172e..41b929034 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1322,7 +1322,13 @@ define([ var contactButtons = friendsObject.buttons; contactButtons.unshift(makeCancelButton()); - + + var onShowContacts = function () { + if (!hasFriends) { + $rights.hide(); + } + }; + var frameContacts = UI.dialog.customModal(contactsContent, { buttons: contactButtons, onClose: config.onClose, @@ -1405,13 +1411,16 @@ define([ // Create modal var resetTab = function () { + $rights.show(); $rights.find('label.cp-radio').show(); }; var tabs = [{ title: Messages.share_contactCategory, icon: "fa fa-address-book", content: frameContacts, - active: hasFriends + active: hasFriends, + onShow: onShowContacts, + onHide: resetTab }, { title: Messages.share_linkCategory, icon: "fa fa-link", From ddb204def4b792491b26fef6afe716736068dd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 13 Jan 2020 10:32:22 +0000 Subject: [PATCH 11/18] Change temporary text --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 41b929034..90418a694 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -4047,7 +4047,7 @@ define([ UIElements.displayBurnAfterReadingPage = function (common, cb) { var info = h('p.cp-password-info', 'This document will self-destruct as soon as you open it. It will be removed form the server, once you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later. '); // XXX KEY - var button = h('button.primary', 'Proceed'); // XXX KEY + var button = h('button.primary', 'view and delete'); // XXX KEY $(button).on('click', function () { cb(); From e3aa814c0af1debd69239615dde630325cd0c608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 13 Jan 2020 10:53:48 +0000 Subject: [PATCH 12/18] style of loading screen messages and password form --- customize.dist/loading.js | 42 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 10db051ff..b4c3c4a3a 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -13,7 +13,8 @@ define([], function () { right: 0px; background: linear-gradient(to right, #326599 0%, #326599 50%, #4591c4 50%, #4591c4 100%); color: #fafafa; - font-size: 1.5em; + font-size: 1.3em; + line-height: 120%; opacity: 1; display: flex; flex-flow: column; @@ -78,12 +79,10 @@ define([], function () { padding: 20px; width: 100%; color: #3F4141; - text-align: center; + text-align: left; display: none; } -#cp-loading-password-prompt { - font-size: 18px; -} + #cp-loading-password-prompt .cp-password-error { color: white; background: #9e0000; @@ -99,22 +98,43 @@ p.cp-password-info{ } #cp-loading-password-prompt .cp-password-form { display: flex; - justify-content: space-around; flex-wrap: wrap; } -#cp-loading-password-prompt .cp-password-form button, -#cp-loading-password-prompt .cp-password-form .cp-password-input { +#cp-loading-password-prompt .cp-password-form button{ background-color: #4591c4; color: white; border: 1px solid #4591c4; } + +.cp-password-input{ + border: 1px solid #4591c4; + background-color: white; + border-radius 0; +} + +.cp-password-form button{ + padding: 8px 12px; + font-weight: bold; + text-transform: uppercase; +} + +#cp-loading-password-prompt .cp-password-form{ + width: 100%; +} + #cp-loading-password-prompt .cp-password-form .cp-password-container { flex-shrink: 1; min-width: 0; } + +#cp-loading-password-prompt .cp-password-form .cp-password-container .cp-password-reveal{ + color: #4591c4; + padding: 0px 24px; +} + #cp-loading-password-prompt .cp-password-form input { flex: 1; - padding: 0 5px; + padding: 12px; min-width: 0; text-overflow: ellipsis; } @@ -122,7 +142,7 @@ p.cp-password-info{ background-color: #326599; } #cp-loading-password-prompt ::placeholder { - color: #d9d9d9; + color: #999999; opacity: 1; } #cp-loading-password-prompt :-ms-input-placeholder { @@ -157,7 +177,7 @@ p.cp-password-info{ background: #222; color: #fafafa; text-align: center; - font-size: 1.5em; + font-size: 1.3em; opacity: 0.7; font-family: 'Open Sans', 'Helvetica Neue', sans-serif; padding: 15px; From ba43bb9f07abc0fc6e9cbf0248be690874ee7134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 13 Jan 2020 12:05:41 +0000 Subject: [PATCH 13/18] adjust font size of password --- customize.dist/loading.js | 1 + 1 file changed, 1 insertion(+) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index b4c3c4a3a..09d2060dc 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -107,6 +107,7 @@ p.cp-password-info{ } .cp-password-input{ + font-size:16px; border: 1px solid #4591c4; background-color: white; border-radius 0; From 09da8ac6a2fccc80de1cad078f32eca9d65fdc9e Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 13 Jan 2020 14:52:46 +0100 Subject: [PATCH 14/18] Warn the owners when deleting a BAR pad --- www/common/outer/async-store.js | 49 +++++++++++++++++++++++++++++-- www/common/sframe-common-outer.js | 2 ++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e39a2f980..f733e3adf 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1653,6 +1653,45 @@ define([ }; // Delete a pad received with a burn after reading URL + + var notifyOwnerPadRemoved = function (data, obj) { + var channel = data.channel; + var href = data.href; + var parsed = Hash.parsePadUrl(href); + var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); + if (obj && obj.error) { return; } + if (!obj.mailbox) { return; } + + // Decrypt the mailbox + var crypto = Crypto.createEncryptor(secret.keys); + var m = []; + try { + if (typeof (obj.mailbox) === "string") { + m.push(crypto.decrypt(obj.mailbox, true, true)); + } else { + Object.keys(obj.mailbox).forEach(function (k) { + m.push(crypto.decrypt(obj.mailbox[k], true, true)); + }); + } + } catch (e) { + console.error(e); + } + // Tell all the owners that the pad was deleted from the server + var curvePublic = store.proxy.curvePublic; + var myData = Messaging.createData(store.proxy, false); + m.forEach(function (obj) { + var mb = JSON.parse(obj); + if (mb.curvePublic === curvePublic) { return; } + store.mailbox.sendTo('OWNED_PAD_REMOVED', { + channel: channel, + user: myData + }, { + channel: mb.notifications, + curvePublic: mb.curvePublic + }, function () {}); + }); + }; + Store.burnPad = function (clientId, data) { var channel = data.channel; var ownerKey = Crypto.b64AddSlashes(data.ownerKey || ''); @@ -1665,8 +1704,14 @@ define([ edPrivate: Hash.encodeBase64(pair.secretKey) }, function (e, rpc) { if (e) { return void console.error(e); } - rpc.removeOwnedChannel(channel, function (err) { - if (err) { console.error(err); } + Store.getPadMetadata(null, { + channel: channel + }, function (md) { + rpc.removeOwnedChannel(channel, function (err) { + if (err) { return void console.error(err); } + // Notify owners that the pad was removed + notifyOwnerPadRemoved(data, md); + }); }); }); } catch (e) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index c19e9e344..d0a84816a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1240,6 +1240,8 @@ define([ if (burnAfterReading) { Cryptpad.padRpc.onReadyEvent.reg(function () { Cryptpad.burnPad({ + password: password, + href: window.location.href, channel: secret.channel, ownerKey: burnAfterReading }); From 1e6e9fd288835fe43b3717c4c7ab322c5f1e76ba Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 13 Jan 2020 15:58:46 +0100 Subject: [PATCH 15/18] Remove deleted pad from the drive --- www/common/outer/async-store.js | 5 +++++ www/common/outer/mailbox-handlers.js | 25 +++++++++++++++++++++++++ www/common/outer/mailbox.js | 1 + www/common/proxy-manager.js | 4 ++++ 4 files changed, 35 insertions(+) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index f733e3adf..681f1d575 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2175,6 +2175,11 @@ define([ updateMetadata: function () { broadcast([], "UPDATE_METADATA"); }, + updateDrive: function () { + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', 'filesData'] + }); + }, pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, }, waitFor, function (ev, data, clients, _cb) { var cb = Util.once(_cb || function () {}); diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 6e10cd5a2..253157361 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -482,6 +482,31 @@ define([ cb(true); }; + handlers['OWNED_PAD_REMOVED'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + + if (msg.author !== content.user.curvePublic) { return void cb(true); } + if (!content.channel) { + console.log('Remove invalid notification'); + return void cb(true); + } + + var channel = content.channel; + var res = ctx.store.manager.findChannel(channel); + + res.forEach(function (obj) { + var paths = ctx.store.manager.findFile(obj.id); + ctx.store.manager.delete({ + paths: paths + }, function () { + ctx.updateDrive(); + }); + }); + + cb(true); + }; + return { diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 1def668e1..b84e98dd7 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -422,6 +422,7 @@ proxy.mailboxes = { store: store, pinPads: cfg.pinPads, updateMetadata: cfg.updateMetadata, + updateDrive: cfg.updateDrive, emit: emit, clients: [], boxes: {}, diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 1bd88e90b..796c52898 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -119,6 +119,7 @@ define([ // If it's not a shared folder, check the pads if (!data) { data = Env.user.userObject.getFileData(id, editable); } ret.push({ + id: id, data: data, userObject: Env.user.userObject }); @@ -126,6 +127,7 @@ define([ Object.keys(Env.folders).forEach(function (fId) { Env.folders[fId].userObject.findChannels([channel]).forEach(function (id) { ret.push({ + id: id, fId: fId, data: Env.folders[fId].userObject.getFileData(id, editable), userObject: Env.folders[fId].userObject @@ -1095,9 +1097,11 @@ define([ // Store getChannelsList: callWithEnv(getChannelsList), addPad: callWithEnv(addPad), + delete: callWithEnv(_delete), // Tools findChannel: callWithEnv(findChannel), findHref: callWithEnv(findHref), + findFile: callWithEnv(findFile), getEditHash: callWithEnv(getEditHash), user: Env.user, folders: Env.folders From df03ddcca0b1351fe40ccea494fc390dd914f71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Mon, 13 Jan 2020 15:26:57 +0000 Subject: [PATCH 16/18] temporary translation keys --- www/common/common-ui-elements.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 90418a694..89ad52835 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1130,8 +1130,8 @@ define([ Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined, UI.createRadio('accessRights', 'cp-share-editable-true', Messages.share_linkEdit, false, { mark: {tabindex:1} })]), - burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', - 'View once and self-destruct', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX KEY + burnAfterReading = hashes.viewHash ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading || + 'View once and self-destruct', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX temp KEY ]); // Burn after reading @@ -1140,7 +1140,7 @@ define([ // the options to generate the BAR url var barAlert = h('div.alert.alert-danger.cp-alertify-bar-selected', { style: 'display: none;' - }, " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server. "); // XXX KEY + }, Messages.burnAfterReading_warningLink || " You have set this pad to self-destruct. Once a recipient opens this pad, it will be permanently deleted from the server."); // XXX temp KEY var channel = Hash.getSecrets('pad', hash, config.password).channel; common.getPadMetadata({ channel: channel @@ -1182,7 +1182,7 @@ define([ cb(url); }); } - return 'Click on the button below to generate a link'; // XXX KEY + return Messages.burnAfterReading_generateLink || 'Click on the button below to generate a link'; // XXX temp KEY } var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash; var href = burnAfterReading ? burnAfterReadingUrl : (origin + pathname + '#' + hash); @@ -4046,8 +4046,8 @@ define([ }; UIElements.displayBurnAfterReadingPage = function (common, cb) { - var info = h('p.cp-password-info', 'This document will self-destruct as soon as you open it. It will be removed form the server, once you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later. '); // XXX KEY - var button = h('button.primary', 'view and delete'); // XXX KEY + var info = h('p.cp-password-info', Messages.burnAfterReading_warning || 'This document will self-destruct as soon as you open it. It will be removed form the server, once you close this window you will not be able to access it again. If you are not ready to proceed you can close this window and come back later. '); // XXX temp KEY + var button = h('button.primary', Messages.burnAfterReading_proceed || 'view and delete'); // XXX temp KEY $(button).on('click', function () { cb(); @@ -4062,7 +4062,7 @@ define([ UIElements.getBurnAfterReadingWarning = function (common) { var priv = common.getMetadataMgr().getPrivateData(); if (!priv.burnAfterReading) { return; } - return h('div.alert.alert-danger.cp-burn-after-reading', 'This pad has been deleted from the server, once you close this window you will not be able to access it again.'); // XXX KEY + return h('div.alert.alert-danger.cp-burn-after-reading', Messages.burnAfterReading_warningDeleted || 'This pad has been deleted from the server, once you close this window you will not be able to access it again.'); // XXX temp KEY }; var crowdfundingState = false; From 73af078e4abeef087deba5a3d087872391576c1f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 13 Jan 2020 16:43:44 +0100 Subject: [PATCH 17/18] Hide share modal when pad is deleted --- www/common/common-ui-elements.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 41b929034..0ab7586e1 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -4001,6 +4001,8 @@ define([ msg += Messages.errorCopy; } } + var sframeChan = common.getSframeChannel(); + sframeChan.event('EV_SHARE_OPEN', {hidden: true}); if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } UI.errorLoadingScreen(msg, true, true); (cb || function () {})(); From d02092eb76e74b1467116776a3486f05c7ea0e7d Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 13 Jan 2020 17:06:49 +0100 Subject: [PATCH 18/18] Fix cache and storage issues in share and filepicker iframes --- www/common/sframe-common-outer.js | 4 ++-- www/filepicker/main.js | 2 +- www/share/main.js | 17 ++++++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index d0a84816a..f53a6b496 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -81,8 +81,8 @@ define([ }); localStorage.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs; } - var cache = {}; - var localStore = {}; + var cache = window.cpCache = {}; + var localStore = window.localStore = {}; Object.keys(localStorage).forEach(function (k) { if (k.indexOf('CRYPTPAD_CACHE|') === 0) { cache[k.slice(('CRYPTPAD_CACHE|').length)] = localStorage[k]; diff --git a/www/filepicker/main.js b/www/filepicker/main.js index d6017c2dd..cada394ba 100644 --- a/www/filepicker/main.js +++ b/www/filepicker/main.js @@ -58,7 +58,7 @@ define([ // Remove the listener once we've received the READY message window.removeEventListener('message', whenReady); // Answer with the requested data - postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage() })); + postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage(), localStore: window.localStore, cache: window.cpCache })); // Then start the channel window.addEventListener('message', function (msg) { diff --git a/www/share/main.js b/www/share/main.js index 43e514f58..28cc1a882 100644 --- a/www/share/main.js +++ b/www/share/main.js @@ -60,7 +60,7 @@ define([ // Remove the listener once we've received the READY message window.removeEventListener('message', whenReady); // Answer with the requested data - postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage() })); + postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage(), localStore: window.localStore, cache: window.cpCache })); // Then start the channel window.addEventListener('message', function (msg) { @@ -105,6 +105,21 @@ define([ config.addCommonRpc(sframeChan); + sframeChan.on('EV_CACHE_PUT', function (x) { + Object.keys(x).forEach(function (k) { + localStorage['CRYPTPAD_CACHE|' + k] = x[k]; + }); + }); + sframeChan.on('EV_LOCALSTORE_PUT', function (x) { + Object.keys(x).forEach(function (k) { + if (typeof(x[k]) === "undefined") { + delete localStorage['CRYPTPAD_STORE|' + k]; + return; + } + localStorage['CRYPTPAD_STORE|' + k] = x[k]; + }); + }); + sframeChan.on('Q_GET_FILES_LIST', function (types, cb) { Cryptpad.getSecureFilesList(types, function (err, data) { cb({