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

pull/1/head
yflory 5 years ago
commit 906b315487

@ -29,8 +29,15 @@ define([
}; };
var makeConfig = function (hash, opt) { var makeConfig = function (hash, opt) {
var secret;
if (typeof(hash) === 'string') {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type // We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Hash.getSecrets('pad', hash, opt.password); secret = Hash.getSecrets('pad', hash, opt.password);
} else if (typeof(hash) === 'object') {
// we may want to just supply options directly
// and this is the easiest place to do it
secret = hash;
}
if (!secret.keys) { secret.keys = secret.key; } // support old hashses if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = { var config = {
websocketURL: NetConfig.getWebsocketURL(opt.origin), websocketURL: NetConfig.getWebsocketURL(opt.origin),
@ -109,8 +116,9 @@ define([
Realtime.whenRealtimeSyncs(realtime, function () { Realtime.whenRealtimeSyncs(realtime, function () {
clearTimeout(to); clearTimeout(to);
var doc = realtime.getAuthDoc();
realtime.abort(); realtime.abort();
finish(Session, void 0); finish(Session, void 0, doc);
}); });
}; };
overwrite(config, opt); overwrite(config, opt);

@ -18,16 +18,6 @@ var factory = function (Hash, Util, Crypt, Nacl, Scrypt/*, Cred, nThen */) {
}; };
}; };
Invite.derivePreviewHash = function (seeds) {
return '#/2/invite/view/' +
Nacl.util.encodeBase64(seeds.preview.slice(0, 18)).replace('/', '-')
+ '/';
};
Invite.derivePreviewSecrets = function (seeds) {
return Hash.getSecrets('pad', Invite.derivePreviewHash(seeds));
};
Invite.deriveSalt = function (password, instance_salt) { Invite.deriveSalt = function (password, instance_salt) {
return (password || '') + (instance_salt || ''); return (password || '') + (instance_salt || '');
}; };
@ -44,44 +34,6 @@ var factory = function (Hash, Util, Crypt, Nacl, Scrypt/*, Cred, nThen */) {
'base64'); // format, could be 'base64' 'base64'); // format, could be 'base64'
}; };
Invite.getPreviewContent = function (seeds, cryptgetOpts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
// XXX test data
cb(void 0, {
author: {
displayName: 'Bob',
curvePublic: 'pewpewpew'
},
team: 'CryptPad',
message: 'Hello bob'
});
/*
var secrets = Invite.derivePreviewSecrets(seeds);
secrets = secrets;
*/
var hash = Invite.derivePreviewHash(seeds);
Crypt.get(hash, function (err, val) {
if (err) { return void cb(err); }
if (!val) { return void cb('DELETED'); }
try {
cb(void 0, JSON.parse(val));
} catch (e) {
console.error(e);
cb(e);
}
}, cryptgetOpts);
// cb("NOT_IMPLEMENTED"); // XXX cryptget
};
// XXX remember to pin invites...
Invite.setPreviewContent = function (seeds, cb) {
var hash = Invite.derivePreviewHash(seeds);
Crypt.put(hash, '', function (err) { // value?
cb(err);
});
//cb = cb;
};
return Invite; return Invite;
}; };
if (typeof(module) !== 'undefined' && module.exports) { if (typeof(module) !== 'undefined' && module.exports) {

@ -18,11 +18,19 @@ var factory = function (Util, Cred, nThen, Nacl) {
}; };
}; };
Invite.generateSignPair = function () {
var ed = Nacl.sign.keyPair();
return {
validateKey: encode64(ed.publicKey),
signKey: encode64(ed.secretKey),
};
};
var b64ToChannelKeys = function (b64) { var b64ToChannelKeys = function (b64) {
var dispense = Cred.dispenser(decode64(b64)); var dispense = Cred.dispenser(decode64(b64));
return { return {
channel: Util.uint8ArrayToHex(dispense(16)), channel: Util.uint8ArrayToHex(dispense(16)),
cryptKey: encode64(dispense(Nacl.secretbox.keyLength)), cryptKey: dispense(Nacl.secretbox.keyLength),
}; };
}; };
@ -36,35 +44,6 @@ var factory = function (Util, Cred, nThen, Nacl) {
// derived from the link seed alone. // derived from the link seed alone.
Invite.derivePreviewKeys = b64ToChannelKeys; Invite.derivePreviewKeys = b64ToChannelKeys;
// what the invite link alone will allow you to see
Invite.createPreviewContent = function (data, keys, cb) {
cb = cb;
/* should include:
{
message: "", // personal message
author: "", // author public key
from: "", // author pretty name
}
*/
};
// the remaining data available with the invite link + password
Invite.createInviteContent = function (data, keys, cb) {
cb = cb;
/* should include:
{
teamData: {
// everything you need to join the team
},
ephemeral: {
curve: "", // for the roster
ed: "" // for deleting the preview content
}
}
*/
};
Invite.createRosterEntry = function (roster, data, cb) { Invite.createRosterEntry = function (roster, data, cb) {
var toInvite = {}; var toInvite = {};
toInvite[data.curvePublic] = data.content; toInvite[data.curvePublic] = data.content;

@ -11,6 +11,7 @@ define([
'/common/common-messaging.js', '/common/common-messaging.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/outer/invitation.js', '/common/outer/invitation.js',
'/common/cryptget.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
@ -20,7 +21,7 @@ define([
'/bower_components/saferphore/index.js', '/bower_components/saferphore/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function (Util, Hash, Constants, Realtime, ], function (Util, Hash, Constants, Realtime,
ProxyManager, UserObject, SF, Roster, Messaging, Feedback, Invite, ProxyManager, UserObject, SF, Roster, Messaging, Feedback, Invite, Crypt,
Listmap, Crypto, CpNetflux, ChainPad, nThen, Saferphore) { Listmap, Crypto, CpNetflux, ChainPad, nThen, Saferphore) {
var Team = {}; var Team = {};
@ -1273,17 +1274,25 @@ define([
var createInviteLink = function (ctx, data, cId, _cb) { var createInviteLink = function (ctx, data, cId, _cb) {
var cb = Util.mkAsync(Util.once(_cb)); var cb = Util.mkAsync(Util.once(_cb));
var teamId = data.teamId;
var team = ctx.teams[data.teamId]; var team = ctx.teams[data.teamId];
var seeds = data.seeds; // {scrypt, preview} var seeds = data.seeds; // {scrypt, preview}
var bytes64 = data.bytes64; var bytes64 = data.bytes64;
team = team;
/*
var roster = team.roster; var roster = team.roster;
var teamName;
try {
teamName = roster.getState().metadata.name;
} catch (err) {
return void cb("TEAM_NAME_ERR");
}
var message = data.message;
var name = data.name; var name = data.name;
/*
var password = data.password; var password = data.password;
var msg = data.message;
var hash = data.hash; var hash = data.hash;
*/ */
@ -1298,33 +1307,81 @@ define([
var ephemeralKeys = Invite.generateKeys(); var ephemeralKeys = Invite.generateKeys();
nThen(function (w) { nThen(function (w) {
w = w;
// XXX Invite.createPreviewContent
// XXX cryptput the preview content
/* PUT
{
message: data.message,
// XXX authorName
// XXX authorInfo {
profile,
etc,
}
}
/// XXX callback if error
*/
// Invite.createInviteContent var putOpts = {
// XXX cryptput the secret team credentials initialState: '{}',
/* PUT network: ctx.store.network,
{ };
ephemeralKeys.edPrivate,
ephemeralKeys.curvePrivate, (function () {
teamData: { // a random signing keypair to prevent further writes to the channel
... // we don't need to remember it cause we're only writing once
} var sign = Invite.generateSignPair(); // { validateKey, signKey}
// visible with only the invite link
var previewContent = {
teamName: teamName,
message: message,
author: Messaging.createData(ctx.store.proxy, false),
displayName: name,
curvePublic: ephemeralKeys.curvePublic,
};
var cryptput_config = {
channel: previewKeys.channel,
type: 'pad',
version: 2,
keys: { // what would normally be provided by getSecrets
cryptKey: previewKeys.cryptKey,
validateKey: sign.validateKey, // sent to historyKeeper
signKey: sign.signKey, // b64EdPrivate
},
};
Crypt.put(cryptput_config, JSON.stringify(previewContent), w(function (err /*, doc */) {
if (err) {
console.error("CRYPTPUT_ERR", err);
w.abort();
return void cb("SET_PREVIEW_CONTENT");
}
}), putOpts);
}());
(function () {
// a different random signing key so that the server can't correlate these documents
// as components of an invite
var sign = Invite.generateSignPair(); // { validateKey, signKey}
// available only with the link and the content
var inviteContent = {
teamData: getInviteData(ctx, teamId, false),
ephemeral: {
edPublic: ephemeralKeys.edPublic,
edPrivate: ephemeralKeys.edPrivate,
curvePublic: ephemeralKeys.curvePublic,
curvePrivate: ephemeralKeys.curvePrivate,
},
};
var cryptput_config = {
channel: previewKeys.channel,
type: 'pad',
version: 2,
keys: {
cryptKey: inviteKeys.cryptKey,
validateKey: sign.validateKey,
signKey: sign.signKey,
},
};
Crypt.put(cryptput_config, JSON.stringify(inviteContent), w(function (err /*, doc */) {
if (err) {
console.error("CRYPTPUT_ERR", err);
w.abort();
return void cb("SET_PREVIEW_CONTENT");
} }
/// XXX callback if error }), putOpts);
*/ }());
}).nThen(function (w) { }).nThen(function (w) {
team.pin([inviteKeys.channel, previewKeys.channel], function (obj) { team.pin([inviteKeys.channel, previewKeys.channel], function (obj) {
if (obj && obj.error) { console.error(obj.error); } if (obj && obj.error) { console.error(obj.error); }
@ -1332,6 +1389,7 @@ define([
Invite.createRosterEntry(team.roster, { Invite.createRosterEntry(team.roster, {
curvePublic: ephemeralKeys.curvePublic, curvePublic: ephemeralKeys.curvePublic,
content: { content: {
curvePublic: ephemeralKeys.curvePublic,
displayName: data.name, displayName: data.name,
pending: true, pending: true,
inviteChannel: inviteKeys.channel, // XXX keep this channel pinned until the invite is accepted inviteChannel: inviteKeys.channel, // XXX keep this channel pinned until the invite is accepted
@ -1352,27 +1410,64 @@ define([
}).nThen(function () { }).nThen(function () {
// call back empty if everything worked // call back empty if everything worked
cb(); cb();
/*
cb({
error: 'NOT_IMPLEMENTED'
}); });
*/ };
var getPreviewContent = function (ctx, data, cId, cb) {
var seeds = data.seeds;
var previewKeys;
try {
previewKeys = Invite.derivePreviewKeys(seeds.preview);
} catch (err) {
return void cb("INVALID_SEEDS");
}
Crypt.get({ // secrets
channel: previewKeys.channel,
type: 'pad',
version: 2,
keys: {
cryptKey: previewKeys.cryptKey,
},
}, function (err, val) {
if (err) { return void cb(err); }
if (!val) { return void cb('DELETED'); }
var json = Util.tryParse(val);
if (!json) { return void cb("parseError"); }
console.error("JSON", json);
cb(void 0, json);
}, { // cryptget opts
network: ctx.store.network,
initialState: '{}',
}); });
}; };
// XXX ansuz var getInviteContent = function (ctx, data, cId, cb) {
var getLinkData = function (ctx, data, cId, cb) {
/*
var password = data.password;
var hash = data.hash;
var bytes64 = data.bytes64; var bytes64 = data.bytes64;
*/ var previewKeys;
return void cb(); try {
/* previewKeys = Invite.deriveInviteKeys(bytes64);
cb({ } catch (err) {
error: 'NOT_IMPLEMENTED' return void cb("INVALID_SEEDS");
}
Crypt.get({ // secrets
channel: previewKeys.channel,
type: 'pad',
version: 2,
keys: {
cryptKey: previewKeys.cryptKey,
},
}, function (err, val) {
if (err) { return void cb(err); }
if (!val) { return void cb('DELETED'); }
var json = Util.tryParse(val);
if (!json) { return void cb("parseError"); }
cb(void 0, json);
}, { // cryptget opts
network: ctx.store.network,
initialState: '{}',
}); });
*/
}; };
@ -1532,10 +1627,12 @@ define([
if (cmd === 'CREATE_INVITE_LINK') { if (cmd === 'CREATE_INVITE_LINK') {
return void createInviteLink(ctx, data, clientId, cb); return void createInviteLink(ctx, data, clientId, cb);
} }
if (cmd === 'GET_LINK_DATA') { if (cmd === 'GET_INVITE_CONTENT') {
return void getLinkData(ctx, data, clientId, cb); return void getInviteContent(ctx, data, clientId, cb);
}
if (cmd === 'GET_PREVIEW_CONTENT') {
return void getPreviewContent(ctx, data, clientId, cb);
} }
// XXX ansuz
}; };
return team; return team;

@ -1091,7 +1091,7 @@ define([
bytes64 = bytes; bytes64 = bytes;
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
APP.module.execCommand('GET_LINK_DATA', { APP.module.execCommand('GET_INVITE_CONTENT', {
bytes64: bytes64, bytes64: bytes64,
hash: hash, hash: hash,
password: pw, password: pw,
@ -1104,10 +1104,11 @@ define([
}; };
nThen(function (waitFor) { nThen(function (waitFor) {
InviteInner.getPreviewContent(seeds, { APP.module.execCommand("GET_PREVIEW_CONTENT", {
origin: privateData.origin seeds: seeds,
}, waitFor(function (err, json) { }, waitFor(function (err, json) {
if (err) { if (err) { // XXX this is failing with "team is disabled"
// XXX APP.module is not ready yet?
// err === DELETED: different message? // err === DELETED: different message?
$(errorBlock).text('ERROR'+err).show(); // XXX $(errorBlock).text('ERROR'+err).show(); // XXX
waitFor.abort(); waitFor.abort();
@ -1115,7 +1116,6 @@ define([
return; return;
// XXX handle errors // XXX handle errors
} }
json = json; // XXX {message: "", author: "", ???}
$div.empty(); $div.empty();
$div.append(h('div.cp-teams-invite-from', [ $div.append(h('div.cp-teams-invite-from', [
'From', // XXX 'From', // XXX

Loading…
Cancel
Save