mirror of https://github.com/xwiki-labs/cryptpad
Generate burn after reading link for pads
This commit is contained in:
parent
14905a5693
commit
96a00f89df
|
@ -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/');
|
||||
|
|
|
@ -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)),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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", {
|
||||
href: href,
|
||||
password: config.password,
|
||||
isTemplate: config.isTemplate,
|
||||
name: myName,
|
||||
title: title
|
||||
}, {
|
||||
channel: friend.notifications,
|
||||
curvePublic: friend.curvePublic
|
||||
});
|
||||
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;
|
||||
}
|
||||
// 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); }
|
||||
}).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,
|
||||
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:
|
||||
// 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;
|
||||
// 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
|
||||
$embed.find('#cp-embed-link-preview').val(getEmbedValue());
|
||||
$rights.find('input[type="radio"]').on('change', function () {
|
||||
$embed.find('#cp-embed-link-preview').val(getEmbedValue());
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
$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, {
|
||||
|
|
|
@ -17,6 +17,7 @@ var factory = function (Util, Cred, Nacl) {
|
|||
};
|
||||
};
|
||||
|
||||
// XXX move this function?
|
||||
Invite.generateSignPair = function () {
|
||||
var ed = Nacl.sign.keyPair();
|
||||
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);
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue