Merge remote-tracking branch 'origin/inviteUI' into inviteUI
This commit is contained in:
commit
e406bf05e8
@ -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'
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// XXX ansuz
|
var getPreviewContent = function (ctx, data, cId, cb) {
|
||||||
var getLinkData = function (ctx, data, cId, cb) {
|
var seeds = data.seeds;
|
||||||
/*
|
var previewKeys;
|
||||||
var password = data.password;
|
try {
|
||||||
var hash = data.hash;
|
previewKeys = Invite.derivePreviewKeys(seeds.preview);
|
||||||
var bytes64 = data.bytes64;
|
} catch (err) {
|
||||||
*/
|
return void cb("INVALID_SEEDS");
|
||||||
return void cb();
|
}
|
||||||
/*
|
Crypt.get({ // secrets
|
||||||
cb({
|
channel: previewKeys.channel,
|
||||||
error: 'NOT_IMPLEMENTED'
|
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: '{}',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var getInviteContent = function (ctx, data, cId, cb) {
|
||||||
|
var bytes64 = data.bytes64;
|
||||||
|
var previewKeys;
|
||||||
|
try {
|
||||||
|
previewKeys = Invite.deriveInviteKeys(bytes64);
|
||||||
|
} 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"); }
|
||||||
|
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…
x
Reference in New Issue
Block a user