Merge branch 'communities-comments' of github.com:xwiki-labs/cryptpad into communities-comments

pull/1/head
yflory 5 years ago
commit bafe4718ef

@ -19,6 +19,7 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 0px 3px;
} }
&.cp-mentions-clickable { &.cp-mentions-clickable {
outline: none; outline: none;

@ -9,6 +9,16 @@ module.exports.create = function (config) {
var log = config.log; var log = config.log;
var noop = function () {};
var special_errors = {};
['EPIPE', 'ECONNRESET'].forEach(function (k) { special_errors[k] = noop; });
special_errors.NF_ENOENT = function (error, label, info) {
log.error(label, {
info: info,
});
};
// spawn ws server and attach netflux event handlers // spawn ws server and attach netflux event handlers
NetfluxSrv.create(new WebSocketServer({ server: config.httpServer})) NetfluxSrv.create(new WebSocketServer({ server: config.httpServer}))
.on('channelClose', historyKeeper.channelClose) .on('channelClose', historyKeeper.channelClose)
@ -17,11 +27,17 @@ module.exports.create = function (config) {
.on('sessionClose', historyKeeper.sessionClose) .on('sessionClose', historyKeeper.sessionClose)
.on('error', function (error, label, info) { .on('error', function (error, label, info) {
if (!error) { return; } if (!error) { return; }
if (['EPIPE', 'ECONNRESET'].indexOf(error && error.code) !== -1) { return; } if (error && error.code) {
/* EPIPE,ECONNERESET, NF_ENOENT */
if (typeof(special_errors[error.code]) === 'function') {
return void special_errors[error.code](error, label, info);
}
}
/* labels: /* labels:
SEND_MESSAGE_FAIL, SEND_MESSAGE_FAIL_2, FAIL_TO_DISCONNECT, SEND_MESSAGE_FAIL, SEND_MESSAGE_FAIL_2, FAIL_TO_DISCONNECT,
FAIL_TO_TERMINATE, HANDLE_CHANNEL_LEAVE, NETFLUX_BAD_MESSAGE, FAIL_TO_TERMINATE, HANDLE_CHANNEL_LEAVE, NETFLUX_BAD_MESSAGE,
NETFLUX_WEBSOCKET_ERROR, NF_ENOENT NETFLUX_WEBSOCKET_ERROR
*/ */
log.error(label, { log.error(label, {
code: error.code, code: error.code,

@ -75,8 +75,21 @@ Upload.upload = function (Env, safeKey, chunk, cb) {
Env.blobStore.upload(safeKey, chunk, cb); Env.blobStore.upload(safeKey, chunk, cb);
}; };
var reportStatus = function (Env, label, safeKey, err, id) {
var data = {
safeKey: safeKey,
err: err && err.message || err,
id: id,
};
var method = err? 'error': 'info';
Env.Log[method](label, data);
};
Upload.complete = function (Env, safeKey, arg, cb) { Upload.complete = function (Env, safeKey, arg, cb) {
Env.blobStore.complete(safeKey, arg, cb); Env.blobStore.complete(safeKey, arg, function (err, id) {
reportStatus(Env, 'UPLOAD_COMPLETE', safeKey, err, id);
cb(err, id);
});
}; };
Upload.cancel = function (Env, safeKey, arg, cb) { Upload.cancel = function (Env, safeKey, arg, cb) {
@ -84,6 +97,9 @@ Upload.cancel = function (Env, safeKey, arg, cb) {
}; };
Upload.complete_owned = function (Env, safeKey, arg, cb) { Upload.complete_owned = function (Env, safeKey, arg, cb) {
Env.blobStore.completeOwned(safeKey, arg, cb); Env.blobStore.completeOwned(safeKey, arg, function (err, id) {
reportStatus(Env, 'UPLOAD_COMPLETE_OWNED', safeKey, err, id);
cb(err, id);
});
}; };

@ -622,7 +622,8 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
}, (err) => { }, (err) => {
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", { if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", {
err: err && err.message, channel: channelName,
err: err && err.message || err,
stack: err && err.stack, stack: err && err.stack,
}); } }); }
const parsedMsg = {error:err.message, channel: channelName, txid: txid}; const parsedMsg = {error:err.message, channel: channelName, txid: txid};

@ -23,29 +23,13 @@ var write = function (ctx, content) {
// various degrees of logging // various degrees of logging
const logLevels = Logger.levels = ['silly', 'verbose', 'debug', 'feedback', 'info', 'warn', 'error']; const logLevels = Logger.levels = ['silly', 'verbose', 'debug', 'feedback', 'info', 'warn', 'error'];
var handlers = { var handlers = {};
silly: function (ctx, time, tag, info) { ['silly', 'debug', 'verbose', 'feedback', 'info'].forEach(function (level) {
console.log('[SILLY]', time, tag, info); handlers[level] = function (ctx, content) { console.log(content); };
}, });
debug: function (ctx, time, tag, info) { ['warn', 'error'].forEach(function (level) {
console.log('[DEBUG]', time, tag, info); handlers[level] = function (ctx, content) { console.error(content); };
}, });
verbose: function (ctx, time, tag, info) {
console.log('[VERBOSE]', time, tag, info);
},
feedback: function (ctx, time, tag, info) {
console.log('[FEEDBACK]', time, tag, info);
},
info: function (ctx, time, tag, info) {
console.info('[INFO]', time, tag, info);
},
warn: function (ctx, time, tag, info) {
console.warn('[WARN]', time, tag, info);
},
error: function (ctx, time, tag, info) {
console.error('[ERROR]', time, tag, info);
}
};
var noop = function () {}; var noop = function () {};
@ -65,7 +49,7 @@ var createLogType = function (ctx, type) {
return; return;
} }
if (ctx.logToStdout && typeof(handlers[type]) === 'function') { if (ctx.logToStdout && typeof(handlers[type]) === 'function') {
handlers[type](ctx, time, tag, info); handlers[type](ctx, content);
} }
write(ctx, content); write(ctx, content);
}; };

@ -8,7 +8,7 @@ const Workers = module.exports;
const PID = process.pid; const PID = process.pid;
const DB_PATH = 'lib/workers/db-worker'; const DB_PATH = 'lib/workers/db-worker';
const MAX_JOBS = 16; const MAX_JOBS = 8;
Workers.initialize = function (Env, config, _cb) { Workers.initialize = function (Env, config, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
@ -97,7 +97,7 @@ Workers.initialize = function (Env, config, _cb) {
// track which worker is doing which jobs // track which worker is doing which jobs
state.tasks[txid] = msg; state.tasks[txid] = msg;
response.expect(txid, cb, 60000); response.expect(txid, cb, 180000);
state.worker.send(msg); state.worker.send(msg);
}; };

@ -32,6 +32,14 @@
.cp-support-list-message { .cp-support-list-message {
&:last-child:not(.cp-support-fromadmin) { &:last-child:not(.cp-support-fromadmin) {
color: @colortheme_cp-red; color: @colortheme_cp-red;
background-color: lighten(@colortheme_form-warning, 25%);
.cp-support-showdata {
background-color: lighten(@colortheme_form-warning, 30%);
}
}
&:last-child {
&.cp-support-frompremium {
background-color: lighten(@colortheme_cp-red, 25%); background-color: lighten(@colortheme_cp-red, 25%);
.cp-support-showdata { .cp-support-showdata {
background-color: lighten(@colortheme_cp-red, 30%); background-color: lighten(@colortheme_cp-red, 30%);
@ -39,6 +47,7 @@
} }
} }
} }
}
.cp-support-fromadmin { .cp-support-fromadmin {
color: @colortheme_logo-2; color: @colortheme_logo-2;

@ -110,8 +110,11 @@ define([
c2.height = D.dim; c2.height = D.dim;
var ctx = c2.getContext('2d'); var ctx = c2.getContext('2d');
try {
ctx.drawImage(canvas, D.x, D.y, D.w, D.h); ctx.drawImage(canvas, D.x, D.y, D.w, D.h);
} catch (e) {
return void cb('ERROR');
}
cb(void 0, c2.toDataURL()); cb(void 0, c2.toDataURL());
}; };
@ -157,6 +160,8 @@ define([
viewport: page.getViewport(scale) viewport: page.getViewport(scale)
}).promise.then(function () { }).promise.then(function () {
return canvas; return canvas;
}).catch(function () {
cb('ERROR');
}); });
}; };
PDFJS.getDocument(url).promise PDFJS.getDocument(url).promise
@ -190,7 +195,8 @@ define([
}); });
reader.readAsText(blob); reader.readAsText(blob);
}; };
Thumb.fromBlob = function (blob, cb) { Thumb.fromBlob = function (blob, _cb) {
var cb = Util.once(_cb);
if (blob.type.indexOf('video/') !== -1) { if (blob.type.indexOf('video/') !== -1) {
return void Thumb.fromVideoBlob(blob, cb); return void Thumb.fromVideoBlob(blob, cb);
} }
@ -200,7 +206,10 @@ define([
if (Util.isPlainTextFile(blob.type, blob.name)) { if (Util.isPlainTextFile(blob.type, blob.name)) {
return void Thumb.fromPlainTextBlob(blob, cb); return void Thumb.fromPlainTextBlob(blob, cb);
} }
Thumb.fromImageBlob(blob, cb); if (blob.type.indexOf('image/') !== -1) {
return void Thumb.fromImageBlob(blob, cb);
}
return void cb('NO_THUMBNAIL');
}; };
window.html2canvas = undefined; window.html2canvas = undefined;

@ -395,7 +395,7 @@ define([
var forceCreationScreen = cfg.useCreationScreen && var forceCreationScreen = cfg.useCreationScreen &&
sessionStorage[Utils.Constants.displayPadCreationScreen]; sessionStorage[Utils.Constants.displayPadCreationScreen];
delete sessionStorage[Utils.Constants.displayPadCreationScreen]; delete sessionStorage[Utils.Constants.displayPadCreationScreen];
var isSafe = ['debug', 'profile', 'drive'].indexOf(currentPad.app) !== -1; var isSafe = ['debug', 'profile', 'drive', 'teams'].indexOf(currentPad.app) !== -1;
var updateMeta = function () { var updateMeta = function () {
//console.log('EV_METADATA_UPDATE'); //console.log('EV_METADATA_UPDATE');
var metaObj; var metaObj;
@ -620,6 +620,172 @@ define([
}, href); }, href);
}); });
// Add or remove our mailbox from the list if we're an owner
sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) {
var metadata = data.metadata;
var add = data.add;
var _secret = secret;
if (metadata && (metadata.href || metadata.roHref)) {
var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// If we already have metadata, use it, otherwise, try to get it
if (metadata) { return; }
Cryptpad.getPadMetadata({
channel: secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) {
waitFor.abort();
return void cb(obj);
}
metadata = obj;
}));
}).nThen(function () {
// Get and maybe migrate the existing mailbox object
var owners = metadata.owners;
if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
}
// Remove a mailbox
if (!add) {
// Old format: this is the mailbox of the first owner
if (typeof (metadata.mailbox) === "string" && metadata.mailbox) {
// Not our mailbox? abort
if (owners[0] !== edPublic) {
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
}
// Remove it
return void Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'RM_MAILBOX',
value: []
}, cb);
} else if (metadata.mailbox) { // New format
return void Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'RM_MAILBOX',
value: [edPublic]
}, cb);
}
return void cb({
error: 'NO_MAILBOX'
});
}
// Add a mailbox
var toAdd = {};
toAdd[edPublic] = crypto.encrypt(JSON.stringify({
notifications: notifications,
curvePublic: curvePublic
}));
Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'ADD_MAILBOX',
value: toAdd
}, cb);
});
});
// REQUEST_ACCESS is used both to check IF we can contact an owner (send === false)
// AND also to send the request if we want (send === true)
sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
if (readOnly && hashes.editHash) {
return void cb({error: 'ALREADYKNOWN'});
}
var send = data.send;
var metadata = data.metadata;
var owner, owners;
var _secret = secret;
if (metadata && metadata.roHref) {
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// Try to get the owner's mailbox from the pad metadata first.
// If it's is an older owned pad, check if the owner is a friend
// or an acquaintance (from async-store directly in requestAccess)
var todo = function (obj) {
owners = obj.owners;
var mailbox;
// Get the first available mailbox (the field can be an string or an object)
// TODO maybe we should send the request to all the owners?
if (typeof (obj.mailbox) === "string") {
mailbox = obj.mailbox;
} else if (obj.mailbox && obj.owners && obj.owners.length) {
mailbox = obj.mailbox[obj.owners[0]];
}
if (mailbox) {
try {
var dataStr = crypto.decrypt(mailbox, true, true);
var data = JSON.parse(dataStr);
if (!data.notifications || !data.curvePublic) { return; }
owner = data;
} catch (e) { console.error(e); }
}
};
// If we already have metadata, use it, otherwise, try to get it
if (metadata) { return void todo(metadata); }
Cryptpad.getPadMetadata({
channel: _secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) { return; }
todo(obj);
}));
}).nThen(function () {
// If we are just checking (send === false) and there is a mailbox field, cb state true
// If there is no mailbox, we'll have to check if an owner is a friend in the worker
if (!send) { return void cb({state: Boolean(owner)}); }
Cryptpad.padRpc.requestAccess({
send: send,
channel: _secret.channel,
owner: owner,
owners: owners
}, cb);
});
});
sframeChan.on('Q_BLOB_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || currentPad.href;
var onPending = function (cb) {
sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) {
if (obj && obj.cancel) { cb(); }
});
};
var updateProgress = function (p) {
sframeChan.event('EV_BLOB_PASSWORD_CHANGE_PROGRESS', p);
};
Cryptpad.changeBlobPassword(data, {
onPending: onPending,
updateProgress: updateProgress
}, cb);
});
sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href;
Cryptpad.changeOOPassword(data, cb);
});
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href;
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
});
}; };
addCommonRpc(sframeChan, isSafe); addCommonRpc(sframeChan, isSafe);
@ -1126,32 +1292,6 @@ define([
}); });
}); });
sframeChan.on('Q_BLOB_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href || currentPad.href;
var onPending = function (cb) {
sframeChan.query('Q_BLOB_PASSWORD_CHANGE_PENDING', null, function (err, obj) {
if (obj && obj.cancel) { cb(); }
});
};
var updateProgress = function (p) {
sframeChan.event('EV_BLOB_PASSWORD_CHANGE_PROGRESS', p);
};
Cryptpad.changeBlobPassword(data, {
onPending: onPending,
updateProgress: updateProgress
}, cb);
});
sframeChan.on('Q_OO_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href;
Cryptpad.changeOOPassword(data, cb);
});
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
data.href = data.href;
Cryptpad.changePadPassword(Cryptget, Crypto, data, cb);
});
sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) { sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) {
Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb); Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb);
}); });
@ -1248,145 +1388,6 @@ define([
sframeChan.on('EV_GIVE_ACCESS', function (data, cb) { sframeChan.on('EV_GIVE_ACCESS', function (data, cb) {
Cryptpad.padRpc.giveAccess(data, cb); Cryptpad.padRpc.giveAccess(data, cb);
}); });
// REQUEST_ACCESS is used both to check IF we can contact an owner (send === false)
// AND also to send the request if we want (send === true)
sframeChan.on('Q_REQUEST_ACCESS', function (data, cb) {
if (readOnly && hashes.editHash) {
return void cb({error: 'ALREADYKNOWN'});
}
var send = data.send;
var metadata = data.metadata;
var owner, owners;
var _secret = secret;
if (metadata && metadata.roHref) {
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// Try to get the owner's mailbox from the pad metadata first.
// If it's is an older owned pad, check if the owner is a friend
// or an acquaintance (from async-store directly in requestAccess)
var todo = function (obj) {
owners = obj.owners;
var mailbox;
// Get the first available mailbox (the field can be an string or an object)
// TODO maybe we should send the request to all the owners?
if (typeof (obj.mailbox) === "string") {
mailbox = obj.mailbox;
} else if (obj.mailbox && obj.owners && obj.owners.length) {
mailbox = obj.mailbox[obj.owners[0]];
}
if (mailbox) {
try {
var dataStr = crypto.decrypt(mailbox, true, true);
var data = JSON.parse(dataStr);
if (!data.notifications || !data.curvePublic) { return; }
owner = data;
} catch (e) { console.error(e); }
}
};
// If we already have metadata, use it, otherwise, try to get it
if (metadata) { return void todo(metadata); }
Cryptpad.getPadMetadata({
channel: _secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) { return; }
todo(obj);
}));
}).nThen(function () {
// If we are just checking (send === false) and there is a mailbox field, cb state true
// If there is no mailbox, we'll have to check if an owner is a friend in the worker
if (!send) { return void cb({state: Boolean(owner)}); }
Cryptpad.padRpc.requestAccess({
send: send,
channel: _secret.channel,
owner: owner,
owners: owners
}, cb);
});
});
// Add or remove our mailbox from the list if we're an owner
sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) {
var metadata = data.metadata;
var add = data.add;
var _secret = secret;
if (metadata && (metadata.href || metadata.roHref)) {
var _parsed = Utils.Hash.parsePadUrl(metadata.href || metadata.roHref);
_secret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, metadata.password);
}
if (_secret.channel.length !== 32) {
return void cb({error: 'EINVAL'});
}
var crypto = Crypto.createEncryptor(_secret.keys);
nThen(function (waitFor) {
// If we already have metadata, use it, otherwise, try to get it
if (metadata) { return; }
Cryptpad.getPadMetadata({
channel: secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) {
waitFor.abort();
return void cb(obj);
}
metadata = obj;
}));
}).nThen(function () {
// Get and maybe migrate the existing mailbox object
var owners = metadata.owners;
if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
}
// Remove a mailbox
if (!add) {
// Old format: this is the mailbox of the first owner
if (typeof (metadata.mailbox) === "string" && metadata.mailbox) {
// Not our mailbox? abort
if (owners[0] !== edPublic) {
return void cb({ error: 'INSUFFICIENT_PERMISSIONS' });
}
// Remove it
return void Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'RM_MAILBOX',
value: []
}, cb);
} else if (metadata.mailbox) { // New format
return void Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'RM_MAILBOX',
value: [edPublic]
}, cb);
}
return void cb({
error: 'NO_MAILBOX'
});
}
// Add a mailbox
var toAdd = {};
toAdd[edPublic] = crypto.encrypt(JSON.stringify({
notifications: notifications,
curvePublic: curvePublic
}));
Cryptpad.setPadMetadata({
channel: _secret.channel,
command: 'ADD_MAILBOX',
value: toAdd
}, cb);
});
});
sframeChan.on('EV_BURN_PAD', function (channel) { sframeChan.on('EV_BURN_PAD', function (channel) {
if (!burnAfterReading) { return; } if (!burnAfterReading) { return; }

@ -84,7 +84,7 @@ body.cp-app-pad {
} }
#cp-app-pad-comments { #cp-app-pad-comments {
order: 3; order: 3;
width: 300px; width: 330px;
//background-color: white; //background-color: white;
margin: 0px 20px; margin: 0px 20px;
.comments_main(); .comments_main();

@ -188,6 +188,7 @@ define([
var senderKey = content.sender && content.sender.edPublic; var senderKey = content.sender && content.sender.edPublic;
var fromMe = senderKey === privateData.edPublic; var fromMe = senderKey === privateData.edPublic;
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1; var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1;
var fromPremium = Boolean(content.sender.plan);
var userData = h('div.cp-support-showdata', [ var userData = h('div.cp-support-showdata', [
Messages.support_showData, Messages.support_showData,
@ -199,8 +200,10 @@ define([
ev.stopPropagation(); ev.stopPropagation();
}); });
var adminClass = (fromAdmin? '.cp-support-fromadmin': '');
var premiumClass = (fromPremium && !fromAdmin? '.cp-support-frompremium': '');
var name = Util.fixHTML(content.sender.name) || Messages.anonymous; var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
return h('div.cp-support-list-message' + (fromAdmin? '.cp-support-fromadmin': ''), { return h('div.cp-support-list-message' + adminClass + premiumClass, {
'data-hash': hash 'data-hash': hash
}, [ }, [
h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [ h('div.cp-support-message-from' + (fromMe ? '.cp-support-fromme' : ''), [

Loading…
Cancel
Save