Merge branch 'unifiedMetadata' into staging

This commit is contained in:
yflory 2019-08-19 15:27:59 +02:00
commit c1656d7b5b
8 changed files with 136 additions and 77 deletions

View File

@ -396,32 +396,35 @@ module.exports.create = function (cfg) {
// parsed[3] is the last known hash (optionnal) // parsed[3] is the last known hash (optionnal)
sendMsg(ctx, user, [seq, 'ACK']); sendMsg(ctx, user, [seq, 'ACK']);
channelName = parsed[1]; channelName = parsed[1];
var validateKey = parsed[2]; var config = parsed[2];
var lastKnownHash = parsed[3]; var metadata = {};
var owners; var lastKnownHash;
var expire; if (config && typeof config === "object") {
if (parsed[2] && typeof parsed[2] === "object") { lastKnownHash = config.lastKnownHash;
validateKey = parsed[2].validateKey; metadata = config.metadata || {};
lastKnownHash = parsed[2].lastKnownHash; if (metadata.expire) {
owners = parsed[2].owners; metadata.expire = +metadata.expire * 1000 + (+new Date());
if (parsed[2].expire) {
expire = +parsed[2].expire * 1000 + (+new Date());
} }
} else if (config) {
// This is the old way: parsed[2] is the validateKey and parsed[3] is the last known hash
lastKnownHash = parsed[3];
metadata.validateKey = parsed[2];
} }
metadata.channel = channelName;
nThen(function (waitFor) { nThen(function (waitFor) {
if (!tasks) { return; } // tasks are not supported if (!tasks) { return; } // tasks are not supported
if (typeof(expire) !== 'number' || !expire) { return; } if (typeof(metadata.expire) !== 'number' || !metadata.expire) { return; }
// the fun part... // the fun part...
// the user has said they want this pad to expire at some point // the user has said they want this pad to expire at some point
tasks.write(expire, "EXPIRE", [ channelName ], waitFor(function (err) { tasks.write(metadata.expire, "EXPIRE", [ channelName ], waitFor(function (err) {
if (err) { if (err) {
// if there is an error, we don't want to crash the whole server... // if there is an error, we don't want to crash the whole server...
// just log it, and if there's a problem you'll be able to fix it // just log it, and if there's a problem you'll be able to fix it
// at a later date with the provided information // at a later date with the provided information
Log.error('HK_CREATE_EXPIRE_TASK', err); Log.error('HK_CREATE_EXPIRE_TASK', err);
Log.info('HK_INVALID_EXPIRE_TASK', JSON.stringify([expire, 'EXPIRE', channelName])); Log.info('HK_INVALID_EXPIRE_TASK', JSON.stringify([metadata.expire, 'EXPIRE', channelName]));
} }
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
@ -482,20 +485,9 @@ module.exports.create = function (cfg) {
// the first message in the file // the first message in the file
const chan = ctx.channels[channelName]; const chan = ctx.channels[channelName];
if (msgCount === 0 && !historyKeeperKeys[channelName] && chan && chan.indexOf(user) > -1) { if (msgCount === 0 && !historyKeeperKeys[channelName] && chan && chan.indexOf(user) > -1) {
var key = {}; historyKeeperKeys[channelName] = metadata;
key.channel = channelName; storeMessage(ctx, chan, JSON.stringify(metadata), false, undefined);
if (validateKey) { sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(metadata)]);
key.validateKey = validateKey;
}
if (owners) {
key.owners = owners;
}
if (expire) {
key.expire = expire;
}
historyKeeperKeys[channelName] = key;
storeMessage(ctx, chan, JSON.stringify(key), false, undefined);
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(key)]);
} }
// End of history message: // End of history message:

View File

@ -422,8 +422,10 @@ define([
var friend = getFriendFromChannel(chan.id) || {}; var friend = getFriendFromChannel(chan.id) || {};
var cfg = { var cfg = {
validateKey: keys ? keys.validateKey : undefined, metadata: {
owners: [proxy.edPublic, friend.edPublic], validateKey: keys ? keys.validateKey : undefined,
owners: [proxy.edPublic, friend.edPublic],
},
lastKnownHash: data.lastKnownHash lastKnownHash: data.lastKnownHash
}; };
var msg = ['GET_HISTORY', chan.id, cfg]; var msg = ['GET_HISTORY', chan.id, cfg];

View File

@ -764,6 +764,10 @@ define([
postMessage("GIVE_PAD_ACCESS", data, cb); postMessage("GIVE_PAD_ACCESS", data, cb);
}; };
common.getPadMetadata = function (data, cb) {
postMessage('GET_PAD_METADATA', data, cb);
};
common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) { common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) {
if (!href) { return void cb({ error: 'EINVAL_HREF' }); } if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);

View File

@ -1209,10 +1209,7 @@ define([
}, },
noChainPad: true, noChainPad: true,
channel: data.channel, channel: data.channel,
validateKey: data.validateKey, metadata: data.metadata,
owners: data.owners,
password: data.password,
expire: data.expire,
network: store.network, network: store.network,
//readOnly: data.readOnly, //readOnly: data.readOnly,
onConnect: function (wc, sendMessage) { onConnect: function (wc, sendMessage) {
@ -1262,11 +1259,14 @@ define([
channel.sendMessage(msg, clientId, cb); channel.sendMessage(msg, clientId, cb);
}; };
// requestPadAccess is used to check if we have a way to contact the owner
// of the pad AND to send the request if we want
// data.send === false ==> check if we can contact them
// data.send === true ==> send the request
Store.requestPadAccess = function (clientId, data, cb) { Store.requestPadAccess = function (clientId, data, cb) {
// Get owners from pad metadata var owner = data.owner;
// Try to find an owner in our friend list
// Mailbox...
var channel = channels[data.channel]; var channel = channels[data.channel];
if (!channel) { return void cb({error: 'ENOTFOUND'}); }
if (!data.send && channel && (!channel.data || !channel.data.channel)) { if (!data.send && channel && (!channel.data || !channel.data.channel)) {
var i = 0; var i = 0;
var it = setInterval(function () { var it = setInterval(function () {
@ -1277,16 +1277,19 @@ define([
} }
if (i >= 300) { // One minute timeout if (i >= 300) { // One minute timeout
clearInterval(it); clearInterval(it);
return void cb({error: 'ETIMEOUT'});
} }
i++; i++;
}, 200); }, 200);
return; return;
} }
// If the owner was not is the pad metadata, check if it is a friend.
// We'll contact the first owner for whom we know the mailbox
var fData = channel.data || {}; var fData = channel.data || {};
if (fData.owners) { if (!owner && fData.owners) {
var friends = store.proxy.friends || {}; var friends = store.proxy.friends || {};
if (Object.keys(friends).length > 1) { if (Object.keys(friends).length > 1) {
var owner;
fData.owners.some(function (edPublic) { fData.owners.some(function (edPublic) {
return Object.keys(friends).some(function (curve) { return Object.keys(friends).some(function (curve) {
if (curve === "me") { return; } if (curve === "me") { return; }
@ -1297,26 +1300,28 @@ define([
} }
}); });
}); });
if (owner) {
if (data.send) {
var myData = Messaging.createData(store.proxy);
delete myData.channel;
store.mailbox.sendTo('REQUEST_PAD_ACCESS', {
channel: data.channel,
user: myData
}, {
channel: owner.notifications,
curvePublic: owner.curvePublic
}, function () {
cb({state: true});
});
return;
}
return void cb({state: true});
}
} }
} }
cb({sent: false});
// If send is true, send the request to the owner.
if (owner) {
if (data.send) {
var myData = Messaging.createData(store.proxy);
delete myData.channel;
store.mailbox.sendTo('REQUEST_PAD_ACCESS', {
channel: data.channel,
user: myData
}, {
channel: owner.notifications,
curvePublic: owner.curvePublic
}, function () {
cb({state: true});
});
return;
}
return void cb({state: true});
}
cb({state: false});
}; };
Store.givePadAccess = function (clientId, data, cb) { Store.givePadAccess = function (clientId, data, cb) {
var edPublic = store.proxy.edPublic; var edPublic = store.proxy.edPublic;
@ -1353,6 +1358,29 @@ define([
cb(); cb();
}; };
Store.getPadMetadata = function (clientId, data, cb) {
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
var channel = channels[data.channel];
if (!channel) { return void cb({ error: 'ENOTFOUND' }); }
if (!channel.data || !channel.data.channel) {
var i = 0;
var it = setInterval(function () {
if (channel.data && channel.data.channel) {
clearInterval(it);
Store.getPadMetadata(clientId, data, cb);
return;
}
if (i >= 300) { // One minute timeout
clearInterval(it);
return void cb({error: 'ETIMEOUT'});
}
i++;
}, 200);
return;
}
cb(channel.data || {});
};
// GET_FULL_HISTORY from sframe-common-outer // GET_FULL_HISTORY from sframe-common-outer
Store.getFullHistory = function (clientId, data, cb) { Store.getFullHistory = function (clientId, data, cb) {
var network = store.network; var network = store.network;
@ -1459,14 +1487,16 @@ define([
websocketURL: NetConfig.getWebsocketURL(), websocketURL: NetConfig.getWebsocketURL(),
channel: secret.channel, channel: secret.channel,
readOnly: false, readOnly: false,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys), crypto: Crypto.createEncryptor(secret.keys),
userName: 'sharedFolder', userName: 'sharedFolder',
logLevel: 1, logLevel: 1,
ChainPad: ChainPad, ChainPad: ChainPad,
classic: true, classic: true,
network: store.network, network: store.network,
owners: owners metadata: {
validateKey: secret.keys.validateKey || undefined,
owners: owners
}
}; };
var rt = Listmap.create(listmapConfig); var rt = Listmap.create(listmapConfig);
store.sharedFolders[id] = rt; store.sharedFolders[id] = rt;

View File

@ -92,9 +92,11 @@ define([
var hk = network.historyKeeper; var hk = network.historyKeeper;
var cfg = { var cfg = {
validateKey: obj.validateKey, validateKey: obj.validateKey,
lastKnownHash: chan.lastKnownHash || chan.lastCpHash, metadata: {
owners: obj.owners, lastKnownHash: chan.lastKnownHash || chan.lastCpHash,
expire: obj.expire owners: obj.owners,
expire: obj.expire
}
}; };
var msg = ['GET_HISTORY', wc.id, cfg]; var msg = ['GET_HISTORY', wc.id, cfg];
// Add the validateKey if we are the channel creator and we have a validateKey // Add the validateKey if we are the channel creator and we have a validateKey

View File

@ -80,6 +80,7 @@ define([
IS_NEW_CHANNEL: Store.isNewChannel, IS_NEW_CHANNEL: Store.isNewChannel,
REQUEST_PAD_ACCESS: Store.requestPadAccess, REQUEST_PAD_ACCESS: Store.requestPadAccess,
GIVE_PAD_ACCESS: Store.givePadAccess, GIVE_PAD_ACCESS: Store.givePadAccess,
GET_PAD_METADATA: Store.getPadMetadata,
// Drive // Drive
DRIVE_USEROBJECT: Store.userObjectCommand, DRIVE_USEROBJECT: Store.userObjectCommand,
// Settings, // Settings,

View File

@ -23,14 +23,12 @@ define([], function () {
var start = function (conf) { var start = function (conf) {
var channel = conf.channel; var channel = conf.channel;
var Crypto = conf.crypto; var Crypto = conf.crypto;
var validateKey = conf.validateKey;
var isNewHash = conf.isNewHash; var isNewHash = conf.isNewHash;
var readOnly = conf.readOnly || false; var readOnly = conf.readOnly || false;
var padRpc = conf.padRpc; var padRpc = conf.padRpc;
var sframeChan = conf.sframeChan; var sframeChan = conf.sframeChan;
var password = conf.password; var metadata= conf.metadata || {};
var owners = conf.owners; var validateKey = metadata.validateKey;
var expire = conf.expire;
var onConnect = conf.onConnect || function () { }; var onConnect = conf.onConnect || function () { };
conf = undefined; conf = undefined;
@ -127,11 +125,8 @@ define([], function () {
// join the netflux network, promise to handle opening of the channel // join the netflux network, promise to handle opening of the channel
padRpc.joinPad({ padRpc.joinPad({
channel: channel || null, channel: channel || null,
validateKey: validateKey,
readOnly: readOnly, readOnly: readOnly,
owners: owners, metadata: metadata
password: password,
expire: expire
}); });
}; };

View File

@ -272,7 +272,7 @@ define([
var parsed = Utils.Hash.parsePadUrl(window.location.href); var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); } if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.Hash.getDefaultName(parsed); var defaultTitle = Utils.Hash.getDefaultName(parsed);
var edPublic, isTemplate; var edPublic, curvePublic, notifications, isTemplate;
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];
@ -284,6 +284,8 @@ define([
if (err) { console.log(err); } if (err) { console.log(err); }
metaObj = m; metaObj = m;
edPublic = metaObj.priv.edPublic; // needed to create an owned pad edPublic = metaObj.priv.edPublic; // needed to create an owned pad
curvePublic = metaObj.user.curvePublic;
notifications = metaObj.user.notifications;
})); }));
if (typeof(isTemplate) === "undefined") { if (typeof(isTemplate) === "undefined") {
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) { Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
@ -973,10 +975,33 @@ define([
if (readOnly && hashes.editHash) { if (readOnly && hashes.editHash) {
return void cb({error: 'ALREADYKNOWN'}); return void cb({error: 'ALREADYKNOWN'});
} }
Cryptpad.padRpc.requestAccess({ var owner;
send: data, var crypto = Crypto.createEncryptor(secret.keys);
channel: secret.channel nThen(function (waitFor) {
}, cb); // 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)
Cryptpad.getPadMetadata({
channel: secret.channel
}, waitFor(function (obj) {
obj = obj || {};
if (obj.error) { return; }
if (obj.mailbox) {
try {
var dataStr = crypto.decrypt(obj.mailbox, true, true);
var data = JSON.parse(dataStr);
if (!data.notifications || !data.curvePublic) { return; }
owner = data;
} catch (e) { console.error(e); }
}
}));
}).nThen(function () {
Cryptpad.padRpc.requestAccess({
send: data,
channel: secret.channel,
owner: owner
}, cb);
});
}); });
if (cfg.messaging) { if (cfg.messaging) {
@ -1099,13 +1124,21 @@ define([
readOnly = false; readOnly = false;
updateMeta(); updateMeta();
var rtConfig = {}; var rtConfig = {
metadata: {}
};
if (data.owned) { if (data.owned) {
rtConfig.owners = [edPublic]; rtConfig.metadata.owners = [edPublic];
rtConfig.metadata.mailbox = Utils.crypto.encrypt(JSON.stringify({
notifications: notifications,
curvePublic: curvePublic
}));
} }
if (data.expire) { if (data.expire) {
rtConfig.expire = data.expire; rtConfig.metadata.expire = data.expire;
} }
rtConfig.metadata.validateKey = (secret.keys && secret.keys.validateKey) || undefined;
Utils.rtConfig = rtConfig; Utils.rtConfig = rtConfig;
nThen(function(waitFor) { nThen(function(waitFor) {
if (data.templateId) { if (data.templateId) {