mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
Revocation: safe links & multiple tabs
This commit is contained in:
parent
9156096629
commit
de448f0fab
@ -763,7 +763,7 @@ define([
|
||||
};
|
||||
common.getPadAttribute = function (attr, cb, href, channel) {
|
||||
href = Hash.getRelativeHref(href || currentPad.href);
|
||||
if (!href) {
|
||||
if (!href && !channel) {
|
||||
return void cb('E404');
|
||||
}
|
||||
postMessage("GET_PAD_ATTRIBUTE", {
|
||||
|
||||
@ -1188,10 +1188,7 @@ console.error(el, data, app, isRo);
|
||||
|
||||
var obj = { t: APP.team };
|
||||
|
||||
if (!data.roHref && parsed.revocable) { // Revocable
|
||||
if (isRo) { obj.mode = 'view'; } // force view VS use best rights
|
||||
return void common.openURL(Hash.getNewPadURL(href, obj));
|
||||
}
|
||||
if (isRo && parsed.revocable) { obj.mode = 'view'; }
|
||||
|
||||
|
||||
var priv = metadataMgr.getPrivateData();
|
||||
@ -1201,7 +1198,8 @@ console.error(el, data, app, isRo);
|
||||
}
|
||||
|
||||
// Get hidden hash
|
||||
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
|
||||
var secret = parsed.revocable ? { key: true, channel: data.channel }
|
||||
: Hash.getSecrets(parsed.type, parsed.hash, data.password);
|
||||
var opts = {};
|
||||
if (isRo) { opts.view = true; }
|
||||
var hash = Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
|
||||
|
||||
@ -1244,7 +1244,7 @@ define([
|
||||
}
|
||||
}
|
||||
|
||||
if (h.mode === 'view') { return; }
|
||||
if (h.mode === 'view' || accessType) { return; }
|
||||
|
||||
// If we only have rohref, it means we have a stronger href
|
||||
if (!pad.href) {
|
||||
@ -1388,6 +1388,13 @@ define([
|
||||
return chans.some(function (pad) {
|
||||
if (!pad || !pad.data) { return; }
|
||||
var data = pad.data;
|
||||
|
||||
// Revocable pads: the revocation module will try to get the best access later
|
||||
if (data.r) {
|
||||
res = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We've found a match: return the value and stop the loops
|
||||
if ((edit && data.href) || (!edit && data.roHref) || isFile) {
|
||||
res = data;
|
||||
|
||||
@ -85,7 +85,21 @@ id: {
|
||||
});
|
||||
};
|
||||
|
||||
var isValidRotateMessage = function (ctx, log, newSeeds, channel, _cb) {
|
||||
|
||||
var askPassword = function (ctx, clients, cb) {
|
||||
var uid = Util.uid();
|
||||
ctx.passwords.expect(uid, cb)
|
||||
ctx.emit('ASK_PASSWORD', {
|
||||
uid: uid
|
||||
}, clients);
|
||||
};
|
||||
var onPassword = function (ctx, data) {
|
||||
var uid = data.uid;
|
||||
var pw = data.pw;
|
||||
ctx.passwords.handle(uid, pw);
|
||||
};
|
||||
|
||||
var isValidRotateMessage = function (ctx, clients, log, newSeeds, channel, _cb) {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var rotateUid = newSeeds.uid;
|
||||
@ -132,8 +146,20 @@ id: {
|
||||
} else {
|
||||
valid = isValidPw(pw);
|
||||
}
|
||||
if (valid) { return void cb(true, pw); }
|
||||
return void cb(false);
|
||||
if (valid) {
|
||||
if (!knownPasswords.includes(pw)) {
|
||||
return ctx.Store.setPadAttribute(null, {
|
||||
channel: channel,
|
||||
attr: 'password',
|
||||
value: pw
|
||||
}, function () {
|
||||
console.error('stored');
|
||||
cb(true, pw);
|
||||
});
|
||||
}
|
||||
return void cb(true, pw);
|
||||
}
|
||||
//return void cb(false);
|
||||
/*
|
||||
// XXX check last password from drive
|
||||
// XXX then start asking passwords to the user
|
||||
@ -143,23 +169,24 @@ id: {
|
||||
|
||||
|
||||
// TODO since we need to support old pads, we may have to move all the password logic to the worker, even for old pads, to avoid duplicate code
|
||||
askPassword(ctx, function (pw) {
|
||||
*/
|
||||
askPassword(ctx, clients, function (pw) {
|
||||
console.error(pw);
|
||||
check(pw);
|
||||
});
|
||||
*/
|
||||
};
|
||||
check(false);
|
||||
};
|
||||
|
||||
|
||||
var getChannelFromBox = function (messages) {
|
||||
if (!Array.isArray(messages) || !messages.length) { return void cb('EEMPTY'); }
|
||||
if (!Array.isArray(messages) || !messages.length) { return; }
|
||||
var init = messages[0];
|
||||
if (init.type !== 'INIT') { return; }
|
||||
var content = init.content;
|
||||
return content && content.doc && content.doc.channel;
|
||||
};
|
||||
var getMailboxParser = function (ctx, onNewKeys) {
|
||||
var getMailboxParser = function (ctx, clients, onNewKeys) {
|
||||
var data = {
|
||||
doc: {}
|
||||
};
|
||||
@ -169,7 +196,7 @@ id: {
|
||||
// Only the INIT message can add a private key AND must be line 0
|
||||
INIT: function (msg, log, channel, i, waitFor) {
|
||||
if (i || !msg.content || !msg.content.doc || !msg.content.edPrivate) { return; }
|
||||
isValidRotateMessage(ctx, log, msg.content.doc, channel,
|
||||
isValidRotateMessage(ctx, clients, log, msg.content.doc, channel,
|
||||
waitFor(function (valid, password) {
|
||||
if (!valid) { return; }
|
||||
data = JSON.parse(JSON.stringify(msg.content));
|
||||
@ -180,7 +207,7 @@ 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.content, channel, waitFor(function (valid, password) {
|
||||
isValidRotateMessage(ctx, clients, log, msg.content, channel, waitFor(function (valid, password) {
|
||||
if (!valid) { return; }
|
||||
data.doc = msg.content
|
||||
data.password = password;
|
||||
@ -264,7 +291,7 @@ id: {
|
||||
var cb = Util.once(Util.mkAsync(_cb));
|
||||
|
||||
var seed = boxData.seed;
|
||||
|
||||
console.error(seed);
|
||||
|
||||
var secret = Hash.getRevocable('pad', seed);
|
||||
|
||||
@ -308,15 +335,18 @@ id: {
|
||||
cb({ error: info.type });
|
||||
};
|
||||
|
||||
box.parse = getMailboxParser(ctx, function (keys) {
|
||||
box.parse = getMailboxParser(ctx, box.clients, function (keys) {
|
||||
if (keys === false) {
|
||||
// XXX revoked
|
||||
// XXX close mailbox, etc.
|
||||
box.revoked = true;
|
||||
cb({error: 'EFORBIDDEN'});
|
||||
}
|
||||
if (first) { cb(keys); }
|
||||
first = false;
|
||||
if (first) {
|
||||
cb(keys);
|
||||
first = false;
|
||||
return;
|
||||
}
|
||||
onNewKeys(keys);
|
||||
});
|
||||
|
||||
@ -329,6 +359,7 @@ id: {
|
||||
if (box.ready) { return; }
|
||||
box.ready = true;
|
||||
|
||||
console.error(box.messages);
|
||||
var padChan = getChannelFromBox(box.messages);
|
||||
if (!padChan) { return void cb({error: 'ENOCHAN'}); }
|
||||
|
||||
@ -346,7 +377,11 @@ id: {
|
||||
box.messages.push(parsed);
|
||||
if (box.ready) { box.parse.addMessages([parsed]); }
|
||||
};
|
||||
CPNetflux.start(config);
|
||||
config.onConnectionChange = function (info) {
|
||||
console.warn(secret.channel, info.state, info.myID, ctx.store.network.myID);
|
||||
};
|
||||
|
||||
box.cpNf = CPNetflux.start(config);
|
||||
|
||||
};
|
||||
|
||||
@ -427,8 +462,10 @@ id: {
|
||||
accesses.map(function (obj) {
|
||||
n = n(function (waitFor) {
|
||||
loadMailbox(ctx, clientId, obj, function (newKeys) {
|
||||
// XXX called when we received new keys in real time
|
||||
}, waitFor());
|
||||
data.onNewKeysEvt.fire(newKeys);
|
||||
}, waitFor(function () {
|
||||
|
||||
}));
|
||||
}).nThen;
|
||||
});
|
||||
|
||||
@ -454,18 +491,50 @@ id: {
|
||||
loadMailbox(ctx, clientId, {
|
||||
seed: seed
|
||||
}, function (newKeys) {
|
||||
// XXX called when we received new keys in real time
|
||||
data.onNewKeysEvt.fire(newKeys);
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { return void cb(obj); }
|
||||
// XXX we now know the channel ID and we can load the other mailboxes on this channel
|
||||
console.error(obj);
|
||||
var chan = obj.channel;
|
||||
loadMailboxesFromChannel(ctx, {channel: chan}, clientId, function (bestKeys) {
|
||||
loadMailboxesFromChannel(ctx, {
|
||||
channel: chan,
|
||||
onNewKeysEvt: data.onNewKeysEvt
|
||||
}, clientId, function (bestKeys) {
|
||||
cb(bestKeys);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var loadPad = function (ctx, data, clientId, cb) {
|
||||
var seed = data.seed;
|
||||
var chan = data.chan;
|
||||
var onNewKeysEvt = Util.mkEvent();
|
||||
onNewKeysEvt.reg(function (newKeys) {
|
||||
// XXX REVOCATION all mailboxes can fire at once
|
||||
console.error('NEW KEYS', newKeys);
|
||||
});
|
||||
|
||||
|
||||
if (chan) {
|
||||
loadMailboxesFromChannel(ctx, {
|
||||
channel: chan,
|
||||
onNewKeysEvt: onNewKeysEvt
|
||||
}, clientId, function (bestKeys) {
|
||||
cb(bestKeys);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
loadPadFromBox(ctx, {
|
||||
seed: seed,
|
||||
onNewKeysEvt: onNewKeysEvt
|
||||
}, clientId, function (bestKeys) {
|
||||
cb(bestKeys);
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
var listPadAccess = function (ctx, data, clientId, cb) {
|
||||
@ -770,17 +839,17 @@ console.error(keyHashStr);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var leaveBox = function (ctx, box, boxChannel) {
|
||||
if (box.cpNf) { box.cpNf.stop(); }
|
||||
delete ctx.mailboxes[boxChannel];
|
||||
};
|
||||
var leaveChannel = function (ctx, padChan) {
|
||||
// Leave channel and prevent reconnect when we leave a pad
|
||||
Object.keys(ctx.mailboxes).some(function (boxChannel) {
|
||||
var box = ctx.mailboxes[boxChannel];
|
||||
if (box.padChan !== padChan) { return; }
|
||||
if (box.wc) { box.wc.leave(); }
|
||||
if (box.onReconnect) {
|
||||
var network = ctx.store.network;
|
||||
network.off('reconnect', box.onReconnect);
|
||||
}
|
||||
delete ctx.mailboxes[boxChannel];
|
||||
leaveBox(ctx, box, boxChannel);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
@ -796,14 +865,7 @@ console.error(keyHashStr);
|
||||
for (var k in ctx.mailboxes) {
|
||||
box = ctx.mailboxes[k];
|
||||
box.clients = box.clients.filter(filter);
|
||||
if (box.clients.length === 0) {
|
||||
if (box.wc) { box.wc.leave(); }
|
||||
if (box.onReconnect) {
|
||||
var network = ctx.store.network;
|
||||
network.off('reconnect', box.onReconnect);
|
||||
}
|
||||
delete ctx.mailboxes[k];
|
||||
}
|
||||
if (box.clients.length === 0) { leaveBox(ctx, box, k); }
|
||||
}
|
||||
};
|
||||
|
||||
@ -819,6 +881,7 @@ console.error(keyHashStr);
|
||||
store: cfg.store,
|
||||
Store: cfg.Store,
|
||||
emit: emit,
|
||||
passwords: Util.response(function (l, i) { console.error('REV_PW_' + l, i); }),
|
||||
mailboxes: {},
|
||||
};
|
||||
|
||||
@ -841,11 +904,14 @@ console.error(keyHashStr);
|
||||
return void addAccess(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'LOAD_PAD') {
|
||||
return void loadPadFromBox(ctx, data, clientId, cb);
|
||||
return void loadPad(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'CREATE_PAD') {
|
||||
return void createPad(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'PASSWORD') {
|
||||
return void onPassword(ctx, data, clientId, cb);
|
||||
}
|
||||
};
|
||||
|
||||
return revocation;
|
||||
|
||||
@ -1089,7 +1089,7 @@ define([
|
||||
}
|
||||
Env.user.proxy[UserObject.SHARED_FOLDERS][sfId][data.attr] = data.value;
|
||||
}
|
||||
var datas = findHref(Env, data.href);
|
||||
var datas = data.channel ? findChannel(Env, data.channel) : findHref(Env, data.href);
|
||||
var nt = nThen;
|
||||
datas.forEach(function (d) {
|
||||
nt = nt(function (waitFor) {
|
||||
|
||||
@ -479,28 +479,46 @@ define([
|
||||
value: ''
|
||||
};
|
||||
|
||||
// Hidden hash: can't find the channel in our drives: abort
|
||||
var noPadData = function (err) {
|
||||
sframeChan.event("EV_PAD_NODATA", err);
|
||||
};
|
||||
var getRevocable = function (w) {
|
||||
var correctPassword = waitFor();
|
||||
|
||||
var newHref;
|
||||
var expire;
|
||||
nThen(function (w) {
|
||||
// XXX REVOCATION
|
||||
if (parsed.hashData.version !== 5) { return; }
|
||||
var seed = parsed.hashData.key;
|
||||
Cryptpad.universal.execCommand({
|
||||
type: 'revocation',
|
||||
data: {
|
||||
cmd: 'LOAD_PAD',
|
||||
var revCommand = function (cmd, data, cb) {
|
||||
Cryptpad.universal.execCommand({
|
||||
type: 'revocation',
|
||||
data: {
|
||||
seed: seed
|
||||
cmd: cmd,
|
||||
data: data
|
||||
}
|
||||
}
|
||||
}, cb)
|
||||
};
|
||||
|
||||
var lastUid;
|
||||
sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
|
||||
password = data;
|
||||
revCommand('PASSWORD', {
|
||||
uid: lastUid,
|
||||
pw: password
|
||||
});
|
||||
});
|
||||
var onEvent = function (obj) {
|
||||
if (obj.type !== 'revocation') { return; }
|
||||
var q = obj.data;
|
||||
if (q.ev !== 'ASK_PASSWORD') { return; }
|
||||
lastUid = q.data && q.data.uid;
|
||||
var oldPw = q.data && q.data.pw;
|
||||
if (oldPw) { passwordCfg.value = oldPw; }
|
||||
|
||||
sframeChan.event("EV_PAD_PASSWORD", passwordCfg);
|
||||
};
|
||||
Cryptpad.universal.onEvent.reg(onEvent);
|
||||
|
||||
var seed = parsed.revocable && parsed.hashData.key;
|
||||
var chan = parsed.hashData && parsed.hashData.channel;
|
||||
revCommand('LOAD_PAD', {
|
||||
seed: seed,
|
||||
chan: chan
|
||||
}, w(function (obj) {
|
||||
// XXX fix with password workflow
|
||||
w.abort();
|
||||
if (obj && obj.error) {
|
||||
if (obj.error === 'EFORBIDDEN') {
|
||||
sframeChan.event("EV_RESTRICTED_ERROR");
|
||||
@ -509,6 +527,9 @@ define([
|
||||
return;
|
||||
}
|
||||
|
||||
Cryptpad.universal.onEvent.unreg(onEvent);
|
||||
correctPassword();
|
||||
|
||||
// XXX get access type (sf/team/user/link)
|
||||
currentPad.type = 'link';
|
||||
|
||||
@ -530,8 +551,22 @@ define([
|
||||
keys: secret.revocation
|
||||
}
|
||||
};
|
||||
done();
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
// Hidden hash: can't find the channel in our drives: abort
|
||||
var noPadData = function (err) {
|
||||
sframeChan.event("EV_PAD_NODATA", err);
|
||||
};
|
||||
|
||||
var newHref;
|
||||
var expire;
|
||||
var revocable;
|
||||
nThen(function (w) {
|
||||
return;
|
||||
// XXX REVOCATION
|
||||
if (parsed.hashData.version !== 5) { return; }
|
||||
}).nThen(function (w) {
|
||||
// If we're using an unsafe link, get pad attribute
|
||||
if (parsed.hashData.key || !parsed.hashData.channel) {
|
||||
@ -548,6 +583,7 @@ define([
|
||||
edit: edit,
|
||||
file: parsed.hashData.type === 'file'
|
||||
}, w(function (err, res) {
|
||||
console.error(res);
|
||||
// Error while getting data? abort
|
||||
if (err || !res || res.error) {
|
||||
w.abort();
|
||||
@ -560,6 +596,9 @@ define([
|
||||
}
|
||||
// Data found but weaker? warn
|
||||
expire = res.expire;
|
||||
|
||||
if (res.r) { revocable = true; return; } // Revocable
|
||||
|
||||
if (edit && !res.href) {
|
||||
newHref = res.roHref;
|
||||
return;
|
||||
@ -576,13 +615,25 @@ define([
|
||||
currentPad.href = parsed.getUrl(opts);
|
||||
currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts);
|
||||
}
|
||||
var chan = parsed.hashData.version === 3 && parsed.hashData.channel;
|
||||
var url = revocable ? null : parsed.getUrl();
|
||||
Cryptpad.getPadAttribute('channel', w(function (err, data) {
|
||||
console.error(err, data);
|
||||
stored = (!err && typeof (data) === "string");
|
||||
}));
|
||||
}), null, chan);
|
||||
Cryptpad.getPadAttribute('password', w(function (err, val) {
|
||||
console.error(err, val);
|
||||
password = val;
|
||||
}), parsed.getUrl());
|
||||
}), url, chan);
|
||||
}).nThen(function (w) {
|
||||
console.error(expire, password);
|
||||
// Revocable pad, safe (revocable) or unsafe (parsed.revocable) link
|
||||
if (parsed.revocable || revocable) {
|
||||
console.error(parsed.revocable, revocable);
|
||||
return void getRevocable(w);
|
||||
}
|
||||
|
||||
|
||||
// If we've already tested this password and this is a redirect, force
|
||||
if (typeof(newPadPassword) !== "undefined" && newPadPasswordForce) {
|
||||
password = newPadPassword;
|
||||
@ -1244,7 +1295,8 @@ define([
|
||||
var useUnsafe = Utils.Util.find(settings, ['security', 'unsafeLinks']);
|
||||
if (useUnsafe !== true && window.history && window.history.replaceState) {
|
||||
if (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
window.history.replaceState({}, window.document.title, hash);
|
||||
//window.history.replaceState({}, window.document.title, hash);
|
||||
// XXX REVOCATION USE SAFE HERE
|
||||
}
|
||||
}
|
||||
cb({error: err});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user