Generate burn after reading link for pads

pull/1/head
yflory 5 years ago
parent 14905a5693
commit 96a00f89df

@ -279,6 +279,14 @@ define([
parsed.hashData.embed && parsed.hashData.embed &&
parsed.hashData.password); parsed.hashData.password);
}, "test support for owner key in version 2 hash failed to parse"); }, "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) { assert(function (cb) {
var secret = Hash.parsePadUrl('/invite/#/2/invite/edit/oRE0oLCtEXusRDyin7GyLGcS/p/'); var secret = Hash.parsePadUrl('/invite/#/2/invite/edit/oRE0oLCtEXusRDyin7GyLGcS/p/');

@ -16,15 +16,16 @@ var factory = function (Util, Crypto, Nacl) {
}; };
// XXX move this code? // XXX move this code?
Hash.generateSignPair = function (safe) { Hash.generateSignPair = function () {
var ed = Nacl.sign.keyPair(); var ed = Nacl.sign.keyPair();
var makeSafe = function (key) { var makeSafe = function (key) {
if (!safe) { return key; }
return Crypto.b64RemoveSlashes(key).replace(/=+$/g, ''); return Crypto.b64RemoveSlashes(key).replace(/=+$/g, '');
}; };
return { return {
validateKey: makeSafe(encode64(ed.publicKey)), validateKey: Hash.encodeBase64(ed.publicKey),
signKey: makeSafe(encode64(ed.secretKey)), signKey: Hash.encodeBase64(ed.secretKey),
safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)),
safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)),
}; };
}; };

@ -209,10 +209,16 @@ define([
$(title).prepend(' ').prepend(icon); $(title).prepend(' ').prepend(icon);
} }
$(title).click(function () { $(title).click(function () {
var old = tabs[active];
if (old.onHide) { old.onHide(); }
titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); }); titles.forEach(function (t) { $(t).removeClass('alertify-tabs-active'); });
contents.forEach(function (c) { $(c).removeClass('alertify-tabs-content-active'); }); contents.forEach(function (c) { $(c).removeClass('alertify-tabs-content-active'); });
if (tab.onShow) {
tab.onShow();
}
$(title).addClass('alertify-tabs-active'); $(title).addClass('alertify-tabs-active');
$(content).addClass('alertify-tabs-content-active'); $(content).addClass('alertify-tabs-content-active');
active = i;
}); });
titles.push(title); titles.push(title);
contents.push(content); contents.push(content);

@ -917,60 +917,79 @@ define([
className: 'primary cp-share-with-friends', className: 'primary cp-share-with-friends',
name: Messages.share_withFriends, name: Messages.share_withFriends,
onClick: function () { onClick: function () {
var href = Hash.getRelativeHref(linkGetter()); var href;
var $friends = $div.find('.cp-usergrid-user.cp-selected'); NThen(function (waitFor) {
$friends.each(function (i, el) { var w = waitFor();
var curve = $(el).attr('data-curve'); // linkGetter can be async if this is a burn after reading URL
// Check if the selected element is a friend or a team var res = linkGetter({}, function (url) {
if (curve) { // Friend if (!url) {
if (!curve || !friends[curve]) { return; } waitFor.abort();
var friend = friends[curve]; return;
if (!friend.notifications || !friend.curvePublic) { return; } }
common.mailbox.sendTo("SHARE_PAD", { 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, href: href,
password: config.password, password: config.password,
isTemplate: config.isTemplate, path: config.isTemplate ? ['template'] : undefined,
name: myName, title: title,
title: title teamId: team.id
}, { }, function (err) {
channel: friend.notifications, if (err) { return void console.error(err); }
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,
path: config.isTemplate ? ['template'] : undefined,
title: title,
teamId: team.id
}, function (err) {
if (err) { return void console.error(err); }
}); });
});
UI.findCancelButton().click(); UI.findCancelButton().click();
// Update the "recently shared with" array: // Update the "recently shared with" array:
// Get the selected curves // Get the selected curves
var curves = $friends.toArray().map(function (el) { var curves = $friends.toArray().map(function (el) {
return ($(el).attr('data-curve') || '').slice(0,8); return ($(el).attr('data-curve') || '').slice(0,8);
}).filter(function (x) { return x; }); }).filter(function (x) { return x; });
// Prepend them to the "order" array // Prepend them to the "order" array
Array.prototype.unshift.apply(order, curves); Array.prototype.unshift.apply(order, curves);
order = Util.deduplicateString(order); order = Util.deduplicateString(order);
// Make sure we don't have "old" friends and save // Make sure we don't have "old" friends and save
order = order.filter(function (curve) { order = order.filter(function (curve) {
return smallCurves.indexOf(curve) !== -1; 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] 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) { UIElements.createShareModal = function (config) {
var origin = config.origin; var origin = config.origin;
var pathname = config.pathname; var pathname = config.pathname;
@ -1078,6 +1120,7 @@ define([
var parsed = Hash.parsePadUrl(pathname); var parsed = Hash.parsePadUrl(pathname);
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1; var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
var burnAfterReading;
var rights = h('div.msg.cp-inline-radio-group', [ var rights = h('div.msg.cp-inline-radio-group', [
h('label', Messages.share_linkAccess), h('label', Messages.share_linkAccess),
h('div.radio-group',[ h('div.radio-group',[
@ -1086,9 +1129,33 @@ define([
canPresent ? UI.createRadio('accessRights', 'cp-share-present', canPresent ? UI.createRadio('accessRights', 'cp-share-present',
Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined, Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined,
UI.createRadio('accessRights', 'cp-share-editable-true', 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 $rights = $(rights);
var saveValue = function () { var saveValue = function () {
@ -1100,13 +1167,25 @@ define([
}); });
}; };
var getLinkValue = function (initValue) { var burnAfterReadingUrl;
var getLinkValue = function (initValue, cb) {
var val = initValue || {}; var val = initValue || {};
var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true')); var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true'));
var embed = val.embed; var embed = val.embed;
var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present')); 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 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); var parsed = Hash.parsePadUrl(href);
return origin + parsed.getUrl({embed: embed, present: present}); 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 = h('div.cp-share-modal', linkContent);
var $link = $(link); var $link = $(link);
@ -1169,7 +1248,7 @@ define([
var linkButtons = [ var linkButtons = [
makeCancelButton(), makeCancelButton(),
!config.sharedFolder && { !config.sharedFolder && {
className: 'secondary', className: 'secondary cp-nobar',
name: Messages.share_linkOpen, name: Messages.share_linkOpen,
onClick: function () { onClick: function () {
saveValue(); saveValue();
@ -1180,9 +1259,8 @@ define([
return true; return true;
}, },
keys: [[13, 'ctrl']] keys: [[13, 'ctrl']]
}, }, {
{ className: 'primary cp-nobar',
className: 'primary',
name: Messages.share_linkCopy, name: Messages.share_linkCopy,
onClick: function () { onClick: function () {
saveValue(); saveValue();
@ -1193,26 +1271,26 @@ define([
if (success) { UI.log(Messages.shareSuccess); } if (success) { UI.log(Messages.shareSuccess); }
}, },
keys: [13] 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, { var frameLink = UI.dialog.customModal(link, {
buttons: linkButtons, buttons: linkButtons,
onClose: config.onClose, onClose: config.onClose,
}); });
$(frameLink).find('.cp-bar').hide();
// Share with contacts tab // Share with contacts tab
@ -1240,6 +1318,7 @@ define([
])); ]));
} }
$(contactsContent).append($(barAlert).clone()); // Burn after reading
var contactButtons = friendsObject.buttons; var contactButtons = friendsObject.buttons;
contactButtons.unshift(makeCancelButton()); contactButtons.unshift(makeCancelButton());
@ -1282,21 +1361,52 @@ define([
keys: [13] 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 = h('div.cp-share-modal', embedContent);
var $embed = $(embed); 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()); $embed.find('#cp-embed-link-preview').val(getEmbedValue());
$link.find('#cp-share-link-preview').val(getLinkValue());
$rights.find('input[type="radio"]').on('change', function () { $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()); $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();
}); });
$link.find('input[type="checkbox"]').on('change', function () {
var frameEmbed = UI.dialog.customModal(embed, { $link.find('#cp-share-link-preview').val(getLinkValue({
buttons: embedButtons, embed: Util.isChecked($link.find('#cp-share-embed'))
onClose: config.onClose, }));
}); });
// Create modal // Create modal
var resetTab = function () {
$rights.find('label.cp-radio').show();
};
var tabs = [{ var tabs = [{
title: Messages.share_contactCategory, title: Messages.share_contactCategory,
icon: "fa fa-address-book", icon: "fa fa-address-book",
@ -1310,7 +1420,9 @@ define([
}, { }, {
title: Messages.share_embedCategory, title: Messages.share_embedCategory,
icon: "fa fa-code", icon: "fa fa-code",
content: frameEmbed content: frameEmbed,
onShow: onShowEmbed,
onHide: resetTab
}]; }];
if (typeof(AppConfig.customizeShareOptions) === 'function') { if (typeof(AppConfig.customizeShareOptions) === 'function') {
AppConfig.customizeShareOptions(hashes, tabs, { AppConfig.customizeShareOptions(hashes, tabs, {

@ -17,6 +17,7 @@ var factory = function (Util, Cred, Nacl) {
}; };
}; };
// XXX move this function?
Invite.generateSignPair = function () { Invite.generateSignPair = function () {
var ed = Nacl.sign.keyPair(); var ed = Nacl.sign.keyPair();
return { return {

@ -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); 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) { if (cfg.messaging) {
Notifier.getPermission(); Notifier.getPermission();

Loading…
Cancel
Save