Merge branch 'staging' into trim-history

pull/1/head
ansuz 5 years ago
commit 6a52b97cd8

@ -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;
@ -77,14 +78,12 @@ define([], function () {
background: #FFF;
padding: 20px;
width: 100%;
color: #000;
text-align: center;
color: #3F4141;
text-align: left;
display: none;
}
#cp-loading-password-prompt {
font-size: 18px;
}
#cp-loading-password-prompt .cp-password-error {
#cp-loading-password-prompt p.cp-password-error {
color: white;
background: #9e0000;
padding: 5px;
@ -94,24 +93,53 @@ define([], function () {
text-align: left;
margin-bottom: 15px;
}
#cp-loading-burn-after-reading .cp-password-info {
margin-bottom: 15px;
}
p.cp-password-info{
text-align: left;
}
#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{
font-size:16px;
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;
}
@ -119,7 +147,7 @@ define([], function () {
background-color: #326599;
}
#cp-loading-password-prompt ::placeholder {
color: #d9d9d9;
color: #999999;
opacity: 1;
}
#cp-loading-password-prompt :-ms-input-placeholder {
@ -154,7 +182,7 @@ define([], function () {
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;
@ -201,6 +229,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');

@ -14,6 +14,9 @@
.radio-group {
display: flex;
flex-direction: row;
&:not(:last-child){
margin-bottom: 8px;
}
.cp-radio {
margin-right: 30px;
}

@ -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;

@ -41,7 +41,7 @@ nThen((waitFor) => {
pinned = Pins.calculateFromLog(content.toString('utf8'), f);
}));
}).nThen((waitFor) => {
Pinned.load(waitFor((d) => {
Pinned.load(waitFor((err, d) => {
data = Object.keys(d);
}), {
exclude: [edPublic + '.ndjson']

@ -254,6 +254,40 @@ 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('/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/');
var hd = secret.hashData;

@ -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);

@ -15,6 +15,20 @@ var factory = function (Util, Crypto, Nacl) {
.decodeUTF8(JSON.stringify(list))));
};
// XXX move this code?
Hash.generateSignPair = function () {
var ed = Nacl.sign.keyPair();
var makeSafe = function (key) {
return Crypto.b64RemoveSlashes(key).replace(/=+$/g, '');
};
return {
validateKey: Hash.encodeBase64(ed.publicKey),
signKey: Hash.encodeBase64(ed.secretKey),
safeValidateKey: makeSafe(Hash.encodeBase64(ed.publicKey)),
safeSignKey: makeSafe(Hash.encodeBase64(ed.secretKey)),
};
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
@ -134,6 +148,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,9 +183,12 @@ Version 1
options = hashArr.slice(5);
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, 5).join('/') + '/';
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;
@ -177,9 +205,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, 5).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/'; }
@ -196,6 +227,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
@ -207,9 +240,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/'; }

@ -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", {
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', Messages.burnAfterReading_linkBurnAfterReading ||
'View once and self-destruct', false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined // XXX temp KEY
]);
// 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;'
}, 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
}, 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 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 = 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,10 +1318,17 @@ define([
]));
}
$(contactsContent).append($(barAlert).clone()); // Burn after reading
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,
@ -1282,26 +1367,60 @@ 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')) && !burnAfterReadingUrl) {
$('.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.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",
@ -1310,7 +1429,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, {
@ -3866,6 +3987,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;
@ -3873,11 +3995,14 @@ define([
msg += Messages.errorCopy;
}
} else if (err.type === 'EDELETED') {
if (priv.burnAfterReading) { return void cb(); }
msg = Messages.deletedError;
if (err.loaded) {
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 () {})();
@ -3922,6 +4047,26 @@ define([
$password.find('.cp-password-input').focus();
};
UIElements.displayBurnAfterReadingPage = function (common, cb) {
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();
});
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', 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;
UIElements.displayCrowdfunding = function (common) {
if (crowdfundingState) { return; }
@ -3979,6 +4124,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;

@ -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;

@ -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);

@ -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,73 @@ define([
cb();
};
// 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 || '');
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); }
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) {
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) {
@ -2109,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 () {});

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

@ -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 {

@ -422,6 +422,7 @@ proxy.mailboxes = {
store: store,
pinPads: cfg.pinPads,
updateMetadata: cfg.updateMetadata,
updateDrive: cfg.updateDrive,
emit: emit,
clients: [],
boxes: {},

@ -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,

@ -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

@ -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];
@ -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) {
@ -507,6 +509,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 +1183,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();
@ -1236,6 +1237,16 @@ define([
window.location.hash = hash;
};
if (burnAfterReading) {
Cryptpad.padRpc.onReadyEvent.reg(function () {
Cryptpad.burnPad({
password: password,
href: window.location.href,
channel: secret.channel,
ownerKey: burnAfterReading
});
});
}
var cpNfCfg = {
sframeChan: sframeChan,
channel: secret.channel,
@ -1359,12 +1370,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; }

@ -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) {

@ -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) {

@ -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);

@ -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);

@ -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);

@ -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({

@ -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);

@ -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);

Loading…
Cancel
Save