mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Revocation: add/update/revoke access (clientside)
This commit is contained in:
parent
935cb34f4b
commit
aa9d4e950b
@ -72,7 +72,7 @@ module.exports.create = function (Env, cb) {
|
||||
if (metadata.access) {
|
||||
if (!signature) { return void ('ERESTRICTED'); }
|
||||
let c = Revocable.checkRead(channelName, userId, signature, metadata);
|
||||
return void cb(!c ? 'EFORBIDDEN' : undefined);
|
||||
return void cb(!c ? 'ERESTRICTED' : undefined);
|
||||
}
|
||||
|
||||
if (!metadata.restricted) {
|
||||
|
||||
@ -352,6 +352,7 @@ define([
|
||||
var revocation = common.makeUniversal('revocation');
|
||||
|
||||
var updateAccess = function () {};
|
||||
var addAccess = function () {};
|
||||
|
||||
var TYPES = {
|
||||
user: { icon: '.fa.fa-user', order: 1 },
|
||||
@ -390,9 +391,16 @@ define([
|
||||
buttonCls: 'btn btn-secondary'
|
||||
};
|
||||
var select = UIElements.createDropdown(dropdownConfig);
|
||||
select.setValue(value);
|
||||
select.setValue(current);
|
||||
return select;
|
||||
};
|
||||
var getRights = function (dd, $d) {
|
||||
var r = dd.getValue() || 'r';
|
||||
var d = Util.isChecked($d);
|
||||
var rights = r === 'm' ? 'rwm' : (r === 'w' ? 'rw' : 'r');
|
||||
if (d) { rights += 'd'; }
|
||||
return rights;
|
||||
};
|
||||
var renderAccess = function (edPublic, accessData, editable, maxR, renderedAs) {
|
||||
var type = accessData.notes.type
|
||||
var icon = TYPES[type].icon;
|
||||
@ -407,32 +415,29 @@ define([
|
||||
h('span', 'REVOKE') // XXX
|
||||
]);
|
||||
if (!editable) { $(revoke).attr('disabled', 'disabled'); }
|
||||
|
||||
var getRights = function () {};
|
||||
else {
|
||||
$(revoke).click(function () {
|
||||
UI.confirm(Messages.areYouSure, function (yes) { // XXX REVOCATION message
|
||||
if (!yes) { return; }
|
||||
updateAccess(edPublic, false, renderedAs.key);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var $d = $(canDestroy).find('input');
|
||||
if (editable && maxR === 'm') {
|
||||
$d.on('change', function () {
|
||||
updateAccess(edPublic, getRights(), renderedAs.key);
|
||||
updateAccess(edPublic, getRights(dd, $d), renderedAs.key);
|
||||
});
|
||||
}
|
||||
|
||||
var dd = makeDD(rights, editable, maxR);
|
||||
if (dd.onChange) {
|
||||
dd.onChange.reg(function () {
|
||||
console.warn('ici');
|
||||
console.error(getRights());
|
||||
updateAccess(edPublic, getRights(), renderedAs.key);
|
||||
updateAccess(edPublic, getRights(dd, $d), renderedAs.key);
|
||||
});
|
||||
}
|
||||
|
||||
getRights = function () {
|
||||
var r = dd.getValue() || 'r';
|
||||
var d = Util.isChecked($d);
|
||||
var rights = r === 'm' ? 'rwm' : (r === 'w' ? 'rw' : 'r');
|
||||
if (d) { rights += 'd'; }
|
||||
return rights;
|
||||
};
|
||||
|
||||
return h('div.cp-share-access', {
|
||||
order: TYPES[type].order
|
||||
@ -446,18 +451,72 @@ define([
|
||||
]);
|
||||
};
|
||||
|
||||
var addAccessButton = function (maxR, renderedAs) {
|
||||
// new form
|
||||
var input = h('input', {placeholder:'Note'}); // XXX
|
||||
var dd = makeDD('r', true, maxR);
|
||||
var canDestroy = UI.createCheckbox('cp-share-can-destroy', h('i.fa.fa-trash'), false, {});
|
||||
var $d = $(canDestroy).find('input');
|
||||
var saveBtn = h('button.btn.btn-primary', [
|
||||
h('i.fa.fa-floppy-o'),
|
||||
h('span', 'SAVE') // XXX
|
||||
]);
|
||||
var cancelBtn = h('button.btn.btn-cancel', [
|
||||
h('i.fa.fa-times')
|
||||
]);
|
||||
|
||||
var temp = h('div.cp-share-access', {order:100, style: 'display:none;'}, [
|
||||
h('i.fa.fa-plus'),
|
||||
input,
|
||||
dd[0],
|
||||
canDestroy,
|
||||
saveBtn,
|
||||
cancelBtn
|
||||
]);
|
||||
var $temp = $(temp);
|
||||
|
||||
// show form btn
|
||||
var button = h('button.btn.btn-primary', [
|
||||
h('i.fa.fa-plus'),
|
||||
h('span', 'ADD') // XXX
|
||||
]);
|
||||
var $b = $(button);
|
||||
|
||||
$(saveBtn).click(function () {
|
||||
var access = getRights(dd, $d);
|
||||
var note = {
|
||||
type: 'link',
|
||||
note: $(input).val()
|
||||
};
|
||||
if (false) { note.edPublic = "edpublic"; } // XXX user access
|
||||
addAccess(access, note, renderedAs.key);
|
||||
});
|
||||
$(cancelBtn).click(function () {
|
||||
$b.show();
|
||||
$temp.hide();
|
||||
});
|
||||
$b.click(function () {
|
||||
$b.hide();
|
||||
$temp.css('display', 'flex');
|
||||
|
||||
});
|
||||
|
||||
$content.append(temp);
|
||||
$(content).append(h('div', button));
|
||||
};
|
||||
|
||||
var renderAll = function (obj, renderedAs) {
|
||||
$content.empty();
|
||||
var list = obj.list;
|
||||
var myAccess = list[renderedAs.key];
|
||||
var maxRights = myAccess.rights.includes('m') ? 'm' :
|
||||
(myAccess.rights.includes('w') ? 'w' : 'r');
|
||||
Object.keys(list || {}).forEach(function (ed) {
|
||||
var editable = renderedAs.moderator || renderedAs.key === list[ed].from;
|
||||
var myAccess = list[renderedAs.key];
|
||||
var maxRights = myAccess.rights.includes('m') ? 'm' :
|
||||
(myAccess.rights.includes('w') ? 'w' : 'r');
|
||||
var a = renderAccess(ed, list[ed], editable, maxRights, renderedAs);
|
||||
$content.append(a);
|
||||
});
|
||||
addAccessButton(maxRights, renderedAs);
|
||||
};
|
||||
var renderAs = function (obj) {
|
||||
$viewAs.empty();
|
||||
@ -491,6 +550,18 @@ define([
|
||||
renderAll(obj, myKeys[0]);
|
||||
};
|
||||
|
||||
addAccess = function (rights, note, updateAs) {
|
||||
revocation.execCommand('ADD_ACCESS', {
|
||||
type: priv.app,
|
||||
channel: channel,
|
||||
rights: rights,
|
||||
note: note,
|
||||
from: updateAs
|
||||
}, function (obj) {
|
||||
console.warn(obj);
|
||||
// XXX refresh view
|
||||
});
|
||||
};
|
||||
updateAccess = function (user, rights, updateAs) {
|
||||
var access = !rights ? false : {
|
||||
rights: rights
|
||||
@ -503,7 +574,7 @@ define([
|
||||
},
|
||||
from: updateAs
|
||||
}, function () {
|
||||
|
||||
// XXX refresh view
|
||||
});
|
||||
};
|
||||
|
||||
@ -520,9 +591,6 @@ define([
|
||||
renderAs(obj);
|
||||
});
|
||||
|
||||
|
||||
var b = h('button', 'button');
|
||||
|
||||
cb(void 0, {
|
||||
content: content,
|
||||
buttons: [{
|
||||
|
||||
@ -3138,6 +3138,7 @@ define([
|
||||
// initialize the chat (messenger) and the cursor modules.
|
||||
loadUniversal(Cursor, 'cursor', function () {});
|
||||
loadUniversal(Messenger, 'messenger', function () {});
|
||||
loadUniversal(Revocation, 'revocation', waitFor, clientId);
|
||||
store.messenger = store.modules['messenger'];
|
||||
|
||||
// And now we're ready
|
||||
|
||||
@ -659,6 +659,10 @@ proxy.mailboxes = {
|
||||
sendTo(ctx, type, msg, user, cb);
|
||||
};
|
||||
|
||||
mailbox.sendAs = function (type, msg, to, as, cb) {
|
||||
Mailbox.sendAs(ctx, as, type, msg, to, cb);
|
||||
};
|
||||
|
||||
mailbox.removeClient = function (clientId) {
|
||||
removeClient(ctx, clientId);
|
||||
};
|
||||
|
||||
@ -53,10 +53,32 @@ id: {
|
||||
return pw;
|
||||
};
|
||||
|
||||
var getPadMetadata = function (ctx, channel, cb) {
|
||||
var getPadMetadata = function (ctx, channel, cb, keys) {
|
||||
ctx.Store.getPadMetadata(null, {
|
||||
channel: channel
|
||||
}, function (md) {
|
||||
if (!keys || !md || md.error) { return void cb(md); }
|
||||
|
||||
var secret = Hash.getRevocableSecret({
|
||||
channel: '',
|
||||
viewerSeedStr: keys.doc.viewer,
|
||||
editorSeedStr: keys.doc.editor,
|
||||
}, keys.password);
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
|
||||
Object.keys(md.access || {}).forEach(function (ed) {
|
||||
var a = md.access[ed];
|
||||
if (a.mailbox) {
|
||||
try {
|
||||
a.mailbox = crypto.decrypt(a.mailbox, true, true);
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
if (a.notes) { // XXX use moderator keys!
|
||||
try {
|
||||
a.notes = JSON.parse(crypto.decrypt(a.notes, true, true));
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
});
|
||||
cb(md);
|
||||
});
|
||||
};
|
||||
@ -136,7 +158,9 @@ id: {
|
||||
return content && content.doc && content.doc.channel;
|
||||
};
|
||||
var getMailboxParser = function (ctx, onNewKeys) {
|
||||
var data = {};
|
||||
var data = {
|
||||
doc: {}
|
||||
};
|
||||
var channel;
|
||||
|
||||
var MAILBOX_COMMANDS = {
|
||||
@ -154,11 +178,23 @@ id: {
|
||||
if (!i || !data.doc) { return; }
|
||||
// Trust that all the keys you need are there
|
||||
// Make sure it was sent by the same moderator than in the moderators log
|
||||
isValidRotateMessage(ctx, log, msg, channel, waitFor(function (valid, password) {
|
||||
isValidRotateMessage(ctx, log, msg.content, channel, waitFor(function (valid, password) {
|
||||
if (!valid) { return; }
|
||||
data.doc = msg.content
|
||||
data.password = password;
|
||||
}));
|
||||
},
|
||||
UPDATE: function (msg, log, channel, i, waitFor) {
|
||||
if (!data || !data.doc) { return; }
|
||||
var doc = data.doc;
|
||||
var content = msg.content;
|
||||
if (content.uid !== doc.uid) { return; } // doesn't match our viewer key version
|
||||
if (content.editor && !doc.editor) {
|
||||
doc.editor = content.editor;
|
||||
}
|
||||
if (content.moderator && !doc.moderator) {
|
||||
doc.moderator = content.moderator;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -190,9 +226,15 @@ id: {
|
||||
}).nThen;
|
||||
});
|
||||
|
||||
|
||||
nn(waitFor(function () {
|
||||
data.channel = channel;
|
||||
onNewKeys(Util.clone(data));
|
||||
var clone = Util.clone(data);
|
||||
var myAccess = md.access && md.access[data.edPublic];
|
||||
//if (!myAccess) { return void onNewKeys(false); }
|
||||
if (!Revocable.isModerator(myAccess)) { delete data.doc.moderator; }
|
||||
if (!Revocable.isEditor(myAccess)) { delete data.doc.editor; }
|
||||
onNewKeys(clone);
|
||||
// XXX once messages handled, emit to client
|
||||
// XXX password change or keys rotation
|
||||
// XXX make sure only one notication per pad (in case of multiple mailboxes)
|
||||
@ -226,7 +268,7 @@ id: {
|
||||
|
||||
var box = ctx.mailboxes[secret.channel];
|
||||
if (box) {
|
||||
// XXX return existing box
|
||||
if (box.revoked) { return void cb({error: 'EFORBIDDEN'}); }
|
||||
if (!box.clients.includes(clientId)) { box.clients.push(clientId); }
|
||||
return void cb(box.parse.getContent());
|
||||
}
|
||||
@ -234,10 +276,10 @@ id: {
|
||||
box = ctx.mailboxes[secret.channel] = {
|
||||
messages: [],
|
||||
clients: [clientId],
|
||||
secret: secret,
|
||||
ready: false
|
||||
};
|
||||
|
||||
|
||||
if (boxData.link || !boxData.store) {
|
||||
box.origin = 'URL ' + seed; // XXX
|
||||
} else if (boxData.fId) {
|
||||
@ -265,6 +307,12 @@ id: {
|
||||
};
|
||||
|
||||
box.parse = getMailboxParser(ctx, function (keys) {
|
||||
if (keys === false) {
|
||||
// XXX revoked
|
||||
// XXX close mailbox, etc.
|
||||
box.revoked = true;
|
||||
cb({error: 'EFORBIDDEN'});
|
||||
}
|
||||
if (first) { cb(keys); }
|
||||
first = false;
|
||||
onNewKeys(keys);
|
||||
@ -441,34 +489,14 @@ id: {
|
||||
};
|
||||
});
|
||||
|
||||
var secret = Hash.getRevocableSecret({
|
||||
channel: '',
|
||||
viewerSeedStr: best.doc.viewer,
|
||||
editorSeedStr: best.doc.editor,
|
||||
}, best.password);
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
|
||||
getPadMetadata(ctx, channel, waitFor(function (md) {
|
||||
if (md && md.error) { return; }
|
||||
var access = md.access;
|
||||
Object.keys(access).forEach(function (ed) {
|
||||
var a = access[ed];
|
||||
if (a.mailbox) {
|
||||
try {
|
||||
a.mailbox = crypto.decrypt(a.mailbox, true, true);
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
if (a.notes) {
|
||||
try {
|
||||
a.notes = JSON.parse(crypto.decrypt(a.notes, true, true));
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
});
|
||||
cb({
|
||||
list: access,
|
||||
myKeys: myKeys,
|
||||
});
|
||||
}));
|
||||
}), best);
|
||||
});
|
||||
|
||||
};
|
||||
@ -505,6 +533,15 @@ updateAccess({
|
||||
return _box;
|
||||
};
|
||||
|
||||
|
||||
var sendMailboxMsg = function (ctx, type, content, to, from, cb) {
|
||||
if (!ctx.store.mailbox) { return void cb({error: 'NOT_READY'}); }
|
||||
ctx.store.mailbox.sendAs(type, content, to, from, cb);
|
||||
};
|
||||
var sendInitMsg = function (ctx, data, cb) {
|
||||
sendMailboxMsg(ctx, data.type, data.msg, data.user, data.keys, cb)
|
||||
};
|
||||
|
||||
/*
|
||||
ctx.Store.anonRpcMsg(clientId, {
|
||||
msg: 'SET_REVOCATION_METADATA',
|
||||
@ -523,14 +560,36 @@ updateAccess({
|
||||
}
|
||||
}, cb);
|
||||
*/
|
||||
|
||||
var sendUpdateMsg = function (ctx, target, newAccess, box, md, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var oldAccess = md.access[target];
|
||||
var keys = box.parse.getContent();
|
||||
var toSend = Revocable.getUpgradeMessage(keys.doc, newAccess, oldAccess);
|
||||
if (!toSend) { return void cb(false); }
|
||||
|
||||
toSend.uid = keys.doc.uid;
|
||||
var sendTo = {
|
||||
channel: oldAccess.mailbox, // decrypted from getPadMetadata
|
||||
curvePublic: oldAccess.curvePublic
|
||||
};
|
||||
var sendFrom = {
|
||||
curvePrivate: box.secret.curvePrivate,
|
||||
curvePublic: box.secret.curvePublic
|
||||
};
|
||||
sendMailboxMsg(ctx, 'UPDATE', toSend, sendTo, sendFrom, cb);
|
||||
};
|
||||
|
||||
var updateAccess = function (ctx, obj, clientId, cb) {
|
||||
var key = obj.from;
|
||||
var box = findBoxFromKey(ctx, key);
|
||||
if (!box) { return void cb({error: 'EINVAL'}); }
|
||||
var edPrivate = box.parse.getContent().edPrivate;
|
||||
var keys = box.parse.getContent();
|
||||
|
||||
getPadMetadata(ctx, obj.channel, function (md) {
|
||||
if (md && md.error) { return; }
|
||||
var edPrivate = keys.edPrivate;
|
||||
var log = Revocable.getSanitizedLog(md);
|
||||
var last = log[log.length-1];
|
||||
|
||||
@ -555,15 +614,6 @@ updateAccess({
|
||||
var netfluxId = getNetfluxId(ctx);
|
||||
var sig = Revocable.signLog([JSON.stringify(data), netfluxId], edPrivate);
|
||||
|
||||
console.warn({
|
||||
msg: 'SET_REVOCATION_METADATA',
|
||||
data: {
|
||||
data: data,
|
||||
signature: sig,
|
||||
key: key
|
||||
}
|
||||
});
|
||||
|
||||
ctx.Store.anonRpcMsg(clientId, {
|
||||
msg: 'SET_REVOCATION_METADATA',
|
||||
data: {
|
||||
@ -571,14 +621,150 @@ console.warn({
|
||||
signature: sig,
|
||||
key: key
|
||||
}
|
||||
}, function (obj) {
|
||||
console.error(obj);
|
||||
cb(obj);
|
||||
}, function (res) {
|
||||
if (obj.add) { return void cb(res); }
|
||||
|
||||
sendUpdateMsg(ctx, value.user, newRights, box, md, function (sent) {
|
||||
if (sent === false) { return; }
|
||||
console.log('UPDATE message sent');
|
||||
});
|
||||
cb(res);
|
||||
});
|
||||
|
||||
}, keys);
|
||||
};
|
||||
|
||||
var addAccess = function (ctx, obj, clientId, cb) {
|
||||
var key = obj.from;
|
||||
var box = findBoxFromKey(ctx, key);
|
||||
if (!box) { return void cb({error: 'EINVAL'}); }
|
||||
var keys = box.parse.getContent();
|
||||
|
||||
var type = obj.type;
|
||||
var rights = obj.rights;
|
||||
var note = obj.note;
|
||||
|
||||
var doc = Hash.getRevocableSecret({
|
||||
channel: keys.channel,
|
||||
viewerSeedStr: keys.doc.viewer,
|
||||
editorSeedStr: keys.doc.editor,
|
||||
moderatorSeedStr: keys.doc.moderator
|
||||
}, keys.password);
|
||||
var crypto = Crypto.createEncryptor(doc.keys);
|
||||
var cryptoSym = crypto.encrypt;
|
||||
var cryptoAsym = crypto.encrypt; // XXX
|
||||
|
||||
var mailboxData = Revocable.createMailbox(type, box.secret, keys.doc, rights);
|
||||
var access = Revocable.createAccess(type, mailboxData, note, cryptoSym, cryptoAsym);
|
||||
console.error('NEW HASH', Hash.getRevocableHashFromKeys(type, mailboxData.mailbox)); // XXX
|
||||
|
||||
sendInitMsg(ctx, mailboxData.initMsg, function () {
|
||||
updateAccess(ctx, {
|
||||
add: true,
|
||||
channel: keys.channel,
|
||||
value: {
|
||||
user: mailboxData.edPublic,
|
||||
access: access
|
||||
},
|
||||
from: obj.from,
|
||||
}, clientId, cb);
|
||||
});
|
||||
};
|
||||
|
||||
var createPad = function (ctx, obj, clientId, cb) {
|
||||
var type = obj.type;
|
||||
var password = obj.password;
|
||||
var edPublic = obj.edPublic;
|
||||
|
||||
// random curve keyPair for the first mailbox
|
||||
var randomKeys = Hash.getRevocable(type);
|
||||
|
||||
var doc = Hash.getRevocableSecret(undefined, password); // generate document keys
|
||||
var docKeys = {
|
||||
uid: Util.uid(),
|
||||
viewer: doc.keys.viewerSeed,
|
||||
editor: doc.keys.editorSeed,
|
||||
moderator: doc.keys.moderatorSeed,
|
||||
moderatorCurve: doc.keys.moderatorCurvePublic,
|
||||
channel: doc.channel
|
||||
};
|
||||
|
||||
var moderator = Revocable.createMailbox(type, randomKeys, docKeys, 'rwmd');
|
||||
var editor = Revocable.createMailbox(type, moderator.mailbox, docKeys, 'rw');
|
||||
|
||||
console.error('MODERATOR HASH', Hash.getRevocableHashFromKeys(type, moderator.mailbox)); // XXX
|
||||
|
||||
var crypto = Crypto.createEncryptor(doc.keys);
|
||||
|
||||
var cryptoSym = crypto.encrypt;
|
||||
var cryptoAsym = crypto.encrypt; // XXX
|
||||
|
||||
var access = {};
|
||||
var modNote = {
|
||||
type: edPublic ? 'user' : 'link',
|
||||
note: 'Document creator' // XXX
|
||||
};
|
||||
if (edPublic) { modNote.edPublic = edPublic; }
|
||||
access[moderator.edPublic] = Revocable.createAccess(type, moderator, modNote, cryptoSym, cryptoAsym);
|
||||
var editNote = {
|
||||
type: 'link',
|
||||
note: 'Initial editor access' // XXX
|
||||
};
|
||||
access[editor.edPublic] = Revocable.createAccess(type, editor, editNote, cryptoSym, cryptoAsym);
|
||||
|
||||
// Get a hash of the cryptKey for initial moderators log
|
||||
var keyHashStr = Revocable.hashBytes(doc.keys.cryptKey);
|
||||
|
||||
var first = Revocable.firstLog(moderator.edPublic);
|
||||
var prevHash = Revocable.hashMsg(first);
|
||||
var rotateMsg = Revocable.rotateLog(keyHashStr, doc.keys.validateKey, docKeys.uid, prevHash);
|
||||
var signature = Revocable.signLog(rotateMsg, moderator.mailbox.edPrivate);
|
||||
|
||||
var data = {
|
||||
// displayed hash
|
||||
newHash: Hash.getRevocableHashFromKeys(type, editor.mailbox, password),
|
||||
docKeys: docKeys,
|
||||
crypto: crypto,
|
||||
rtConfig: {
|
||||
creation: {
|
||||
creatorEdPrivate: doc.keys.creator,
|
||||
},
|
||||
authentication: {
|
||||
edPublic: moderator.mailbox.edPublic,
|
||||
edPrivate: moderator.mailbox.edPrivate
|
||||
},
|
||||
metadata: {
|
||||
validateKey: (doc.keys && doc.keys.validateKey) || undefined,
|
||||
access: access,
|
||||
revocableData: {
|
||||
creatorKey: moderator.mailbox.curvePublic,
|
||||
creatorVKey: moderator.mailbox.edPublic,
|
||||
rotate: {
|
||||
uid: docKeys.uid,
|
||||
hash: keyHashStr,
|
||||
validateKey: doc.keys.validateKey,
|
||||
signature: signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
console.error(doc);
|
||||
console.error(moderator);
|
||||
console.error(editor);
|
||||
console.error(access);
|
||||
console.error(keyHashStr);
|
||||
|
||||
nThen(function (waitFor) {
|
||||
sendInitMsg(ctx, moderator.initMsg, waitFor());
|
||||
sendInitMsg(ctx, editor.initMsg, waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
cb(data);
|
||||
});
|
||||
};
|
||||
|
||||
var leaveChannel = function (ctx, padChan) {
|
||||
// Leave channel and prevent reconnect when we leave a pad
|
||||
@ -647,9 +833,15 @@ console.warn({
|
||||
if (cmd === 'UPDATE_ACCESS') {
|
||||
return void updateAccess(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'ADD_ACCESS') {
|
||||
return void addAccess(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'LOAD_PAD') {
|
||||
return void loadPadFromBox(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'CREATE_PAD') {
|
||||
return void createPad(ctx, data, clientId, cb);
|
||||
}
|
||||
};
|
||||
|
||||
return revocation;
|
||||
|
||||
@ -4,9 +4,23 @@ var factory = function (Hash, Nacl) {
|
||||
|
||||
// Access metadata
|
||||
Revocable.isModerator = function (access) {
|
||||
if (typeof(access) === "string") {
|
||||
return access.includes('m');
|
||||
}
|
||||
if (!access || !access.rights) { return false; }
|
||||
return access.rights.includes('m');
|
||||
};
|
||||
Revocable.isEditor = function (access) {
|
||||
if (typeof(access) === "string") {
|
||||
return access.includes('w');
|
||||
}
|
||||
if (!access || !access.rights) { return false; }
|
||||
return access.rights.includes('w');
|
||||
};
|
||||
Revocable.isValidRights = function (rights) {
|
||||
if (typeof(rights) !== "string") { return false; }
|
||||
return /^rw?m?d?$/.test(rights);
|
||||
};
|
||||
Revocable.isValidAccessUpdate = function (oldValue, newValue) {
|
||||
if (oldValue && !newValue) { return true; } // Deletion
|
||||
if (oldValue) { // Update: I can only change "rights"
|
||||
@ -141,6 +155,67 @@ var factory = function (Hash, Nacl) {
|
||||
return result;
|
||||
};
|
||||
|
||||
// Mailbox
|
||||
var RIGHTS = {
|
||||
r: 'viewer',
|
||||
w: 'editor',
|
||||
m: 'moderator'
|
||||
};
|
||||
// Send missing keys to the user
|
||||
Revocable.getUpgradeMessage = function (docKeys, newAccess, oldAccess) {
|
||||
if (!newAccess) { return; } // Revoke
|
||||
if (!oldAccess) { return; } // Create
|
||||
var oldR = oldAccess.rights || 'r';
|
||||
var newR = newAccess.rights || 'r';
|
||||
var toSend = {};
|
||||
newR.split('').forEach(function (key) {
|
||||
if (oldR.includes(key)) { return; } // they already have this seed
|
||||
if (!RIGHTS[key]) { return; } // no destroy seed
|
||||
var type = RIGHTS[key];
|
||||
if (!docKeys[type]) { return; } // make sure I know this key
|
||||
toSend[type] = docKeys[type];
|
||||
});
|
||||
if (!Object.keys(toSend).length) { return; }
|
||||
return toSend;
|
||||
};
|
||||
|
||||
|
||||
Revocable.createMailbox = function (type, fromKeys, docKeys, rights) {
|
||||
if (!Revocable.isValidRights(rights)) { return; }
|
||||
var mailbox = Hash.getRevocable(type);
|
||||
var clone = JSON.parse(JSON.stringify(docKeys));
|
||||
if (!Revocable.isModerator(rights)) { delete clone.moderator; }
|
||||
if (!Revocable.isEditor(rights)) { delete clone.editor; }
|
||||
var initMsg = {
|
||||
type: "INIT",
|
||||
msg: {
|
||||
doc: clone,
|
||||
edPublic: mailbox.keys.edPublic,
|
||||
edPrivate: mailbox.keys.edPrivate,
|
||||
},
|
||||
user: mailbox, // send to
|
||||
keys: fromKeys // send from
|
||||
};
|
||||
return {
|
||||
rights: rights,
|
||||
initMsg: initMsg,
|
||||
mailbox: mailbox,
|
||||
edPublic: mailbox.keys.edPublic
|
||||
};
|
||||
};
|
||||
Revocable.createAccess = function (type, user, notes, encryptSym, encryptAsym, contact) {
|
||||
if (!Revocable.isValidRights(user.rights)) { return; }
|
||||
notes.hash = Hash.getRevocableHashFromKeys(type, user.mailbox);
|
||||
var access = {
|
||||
rights: user.rights,
|
||||
mailbox: encryptSym(user.mailbox.channel),
|
||||
curvePublic: user.mailbox.curvePublic,
|
||||
notes: encryptAsym(JSON.stringify(notes))
|
||||
};
|
||||
if (contact) { access.contact = encryptSym(contact); }
|
||||
return access;
|
||||
};
|
||||
|
||||
|
||||
// Authentication
|
||||
|
||||
|
||||
@ -81,6 +81,7 @@ define([
|
||||
var toolbar;
|
||||
var state = STATE.DISCONNECTED;
|
||||
var firstConnection = true;
|
||||
var restricted = false;
|
||||
|
||||
var toolbarContainer = options.toolbarContainer ||
|
||||
(function () { throw new Error("toolbarContainer must be specified"); }());
|
||||
@ -200,6 +201,7 @@ define([
|
||||
break;
|
||||
}
|
||||
case STATE.ERROR: {
|
||||
if (text === 'ERESTRICTED') { restricted = true; }
|
||||
evStart.reg(function () {
|
||||
if (text === 'ERESTRICTED') {
|
||||
toolbar.failed(true);
|
||||
@ -481,6 +483,7 @@ define([
|
||||
sframeChan.event("EV_CORRUPTED_CACHE");
|
||||
};
|
||||
var onCacheReady = function () {
|
||||
if (state === STATE.ERROR && restricted) { return; }
|
||||
stateChange(STATE.INITIALIZING);
|
||||
toolbar.offline(true);
|
||||
var newContentStr = cpNfInner.chainpad.getUserDoc();
|
||||
|
||||
@ -502,6 +502,9 @@ define([
|
||||
// XXX fix with password workflow
|
||||
w.abort();
|
||||
if (obj && obj.error) {
|
||||
if (obj.error === 'EFORBIDDEN') {
|
||||
sframeChan.event("EV_RESTRICTED_ERROR");
|
||||
}
|
||||
console.error(obj.error);
|
||||
return;
|
||||
}
|
||||
@ -2113,67 +2116,30 @@ define([
|
||||
password = data.password;
|
||||
var newHash = Utils.Hash.createRandomHash(parsed.type, password);
|
||||
|
||||
// XXX MAILBOX
|
||||
var randomKeys = Utils.Hash.getRevocable(parsed.type); // random curve keyPair for the first mailbox
|
||||
var moderator = Utils.Hash.getRevocable(parsed.type);
|
||||
var editor = Utils.Hash.getRevocable(parsed.type);
|
||||
newHash = Utils.Hash.getRevocableHashFromKeys(parsed.type, editor, password); // displayed hash
|
||||
var doc = Utils.Hash.getRevocableSecret(undefined, password); // generate document keys
|
||||
var serverCurvePublic;
|
||||
var rotateUid = Utils.Util.uid();
|
||||
|
||||
nThen(function (waitFor) {
|
||||
var creatorKeys = {
|
||||
curvePrivate: doc.keys.creator,
|
||||
curvePublic: doc.keys.channel
|
||||
};
|
||||
sendMailboxMsg({
|
||||
type: "INIT",
|
||||
msg: {
|
||||
doc: {
|
||||
uid: rotateUid,
|
||||
viewer: doc.keys.viewerSeed,
|
||||
editor: doc.keys.editorSeed,
|
||||
moderator: doc.keys.moderatorSeed,
|
||||
moderatorCurve: doc.keys.moderatorCurvePublic,
|
||||
channel: doc.channel
|
||||
},
|
||||
edPublic: moderator.keys.edPublic,
|
||||
edPrivate: moderator.keys.edPrivate,
|
||||
},
|
||||
user: moderator, // send to
|
||||
keys: randomKeys // send from
|
||||
}, waitFor());
|
||||
sendMailboxMsg({
|
||||
type: "INIT",
|
||||
msg: {
|
||||
doc: {
|
||||
uid: rotateUid,
|
||||
viewer: doc.keys.viewerSeed,
|
||||
editor: doc.keys.editorSeed,
|
||||
moderatorCurve: doc.keys.moderatorCurvePublic,
|
||||
channel: doc.channel
|
||||
},
|
||||
edPublic: editor.keys.edPublic,
|
||||
edPrivate: editor.keys.edPrivate,
|
||||
},
|
||||
user: editor, // send to
|
||||
keys: moderator // send from
|
||||
}, waitFor());
|
||||
require([
|
||||
'/api/broadcast?'+ (+new Date()),
|
||||
], waitFor(function (Broadcast) {
|
||||
serverCurvePublic = Broadcast.curvePublic;
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
|
||||
secret = Utils.secret = doc;
|
||||
var crypto = Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
|
||||
Cryptpad.universal.execCommand({
|
||||
type: 'revocation',
|
||||
data: {
|
||||
cmd: 'CREATE_PAD',
|
||||
data: {
|
||||
type: parsed.type,
|
||||
password: password,
|
||||
edPublic: edPublic
|
||||
}
|
||||
}
|
||||
}, function (data) {
|
||||
var docKeys = data.docKeys;
|
||||
secret = Utils.secret = Utils.Hash.getRevocableSecret({
|
||||
channel: docKeys.channel,
|
||||
viewerSeedStr: docKeys.viewer,
|
||||
editorSeedStr: docKeys.editor,
|
||||
}, password);
|
||||
var crypto = Utils.crypto = Crypto.createEncryptor(secret.keys);
|
||||
|
||||
// Update the hash in the address bar
|
||||
currentPad.hash = newHash;
|
||||
currentPad.href = '/' + parsed.type + '/#' + newHash;
|
||||
Cryptpad.setTabHash(newHash);
|
||||
currentPad.hash = data.newHash;
|
||||
currentPad.href = '/' + parsed.type + '/#' + data.newHash;
|
||||
Cryptpad.setTabHash(data.newHash);
|
||||
|
||||
parsed = Utils.Hash.parsePadUrl(currentPad.href);
|
||||
defaultTitle = Utils.UserObject.getDefaultName(parsed);
|
||||
@ -2181,72 +2147,9 @@ define([
|
||||
readOnly = false;
|
||||
updateMeta();
|
||||
|
||||
// Create initial accesses
|
||||
var access = {};
|
||||
var modData = curvePublic ? {
|
||||
value: curvePublic,
|
||||
type: 'user',
|
||||
note: 'Document creator' // XXX
|
||||
} : {
|
||||
value: Utils.Hash.getRevocableHashFromKeys(parsed.type, moderator),
|
||||
type: 'link',
|
||||
note: 'Initial moderator access' // XXX
|
||||
};
|
||||
console.error('MODERATOR HASH', Utils.Hash.getRevocableHashFromKeys(parsed.type, moderator));
|
||||
access[moderator.edPublic] = {
|
||||
rights: 'rwmd',
|
||||
mailbox: crypto.encrypt(moderator.channel),
|
||||
curvePublic: moderator.curvePublic,
|
||||
//contact: crypto.encrypt(), // XXX my contact mailbox
|
||||
notes: crypto.encrypt(JSON.stringify(modData)),
|
||||
};
|
||||
access[editor.edPublic] = {
|
||||
rights: 'rw',
|
||||
mailbox: crypto.encrypt(editor.channel),
|
||||
curvePublic: editor.curvePublic,
|
||||
notes: crypto.encrypt(JSON.stringify({
|
||||
value: Utils.Hash.getRevocableHashFromKeys(parsed.type, editor),
|
||||
type: 'link',
|
||||
note: 'Initial editor access' // XXX
|
||||
}))
|
||||
};
|
||||
|
||||
if (data.team) { Cryptpad.initialTeam = data.team.id; }
|
||||
|
||||
|
||||
// Get a hash of the cryptKeyfirst initial moderators log
|
||||
var keyHashStr = Revocable.hashBytes(doc.keys.cryptKey);
|
||||
|
||||
var first = Revocable.firstLog(moderator.keys.edPublic);
|
||||
var prevHash = Revocable.hashMsg(first);
|
||||
|
||||
var rotateMsg = Revocable.rotateLog(keyHashStr, doc.keys.validateKey, rotateUid, prevHash);
|
||||
var signature = Revocable.signLog(rotateMsg, moderator.keys.edPrivate);
|
||||
|
||||
var rtConfig = {
|
||||
creation: {
|
||||
creatorEdPrivate: doc.keys.creator,
|
||||
},
|
||||
authentication: {
|
||||
edPublic: moderator.edPublic,
|
||||
edPrivate: moderator.edPrivate
|
||||
},
|
||||
metadata: {
|
||||
validateKey: (secret.keys && secret.keys.validateKey) || undefined,
|
||||
access: access,
|
||||
revocableData: {
|
||||
creatorKey: moderator.curvePublic,
|
||||
creatorVKey: moderator.keys.edPublic,
|
||||
rotate: {
|
||||
uid: rotateUid,
|
||||
hash: keyHashStr,
|
||||
validateKey: doc.keys.validateKey,
|
||||
signature: signature,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var rtConfig = data.rtConfig;
|
||||
if (data.expire) { rtConfig.metadata.expire = data.expire; }
|
||||
|
||||
/*
|
||||
@ -2261,11 +2164,6 @@ define([
|
||||
}));
|
||||
}
|
||||
*/
|
||||
console.error(doc);
|
||||
console.error(moderator);
|
||||
console.error(editor);
|
||||
console.error(access);
|
||||
console.error(keyHashStr);
|
||||
|
||||
|
||||
startRealtime(rtConfig); // XXX
|
||||
|
||||
Loading…
Reference in New Issue
Block a user