cryptpad/www/common/outer/sharedfolder.js
2019-10-22 17:26:32 +02:00

283 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/userObject.js',
'/bower_components/nthen/index.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad/chainpad.dist.js',
], function (Hash, Util, UserObject,
nThen, Crypto, Listmap, ChainPad) {
var SF = {};
/* load
create and load a proxy using listmap for a given shared folder
- config: network and "manager" (either the user one or a team manager)
- id: shared folder id
*/
var allSharedFolders = {};
// No version: visible edit
// Version 2: encrypted edit links
SF.checkMigration = function (secondaryKey, proxy, uo, cb) {
if (true) { // XXX remove this block to enable migration at load time
// FIXME history
return void cb();
}
var drive = proxy.drive || proxy;
// View access: can't migrate
if (!secondaryKey) { return void cb(); }
// Already migrated: nothing to do
if (drive.version >= 2) { return void cb(); }
// Not yet migrating: migrate
if (!drive.migrateRo) { return void uo.migrateReadOnly(cb); }
// Already migrating: wait for the end...
var done = false;
var to;
var it = setInterval(function () {
if (drive.version >= 2) {
done = true;
clearTimeout(to);
clearInterval(it);
return void cb();
}
}, 100);
to = setTimeout(function () {
clearInterval(it);
uo.migrateReadOnly(function () {
done = true;
cb();
});
}, 20000);
var path = proxy.drive ? ['drive', 'version'] : ['version'];
proxy.on('change', path, function () {
if (done) { return; }
if (drive.version >= 2) {
done = true;
clearTimeout(to);
clearInterval(it);
cb();
}
});
};
SF.load = function (config, id, data, _cb) {
var cb = Util.once(_cb);
var network = config.network;
var store = config.store;
var isNew = config.isNew;
var isNewChannel = config.isNewChannel;
var teamId = store.id;
var handler = store.handleSharedFolder;
var href = store.manager.user.userObject.getHref(data);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('drive', parsed.hash, data.password);
var secondaryKey = secret.keys.secondaryKey;
// If we try to load an existing shared folder (isNew === false) but this folder
// doesn't exist in the database, abort and cb
nThen(function (waitFor) {
isNewChannel(null, { channel: secret.channel }, waitFor(function (obj) {
if (obj.isNew && !isNew) {
store.manager.deprecateProxy(id, secret.channel);
waitFor.abort();
return void cb(null);
}
}));
}).nThen(function () {
var sf = allSharedFolders[secret.channel];
if (sf && sf.readOnly && secondaryKey) {
// We were in readOnly mode and now we know the edit keys!
SF.upgrade(secret.channel, secret);
}
if (sf && sf.ready && sf.rt) {
// The shared folder is already loaded, return its data
setTimeout(function () {
var leave = function () { SF.leave(secret.channel, teamId); };
var uo = store.manager.addProxy(id, sf.rt, leave, secondaryKey);
SF.checkMigration(secondaryKey, sf.rt.proxy, uo, function () {
cb(sf.rt, sf.metadata);
});
});
sf.teams.push({
cb: cb,
store: store,
id: id
});
if (handler) { handler(id, sf.rt); }
return;
}
if (sf && !sf.ready && sf.rt) {
// The shared folder is loading, add our callbacks to the queue
sf.teams.push({
cb: cb,
store: store,
id: id
});
if (handler) { handler(id, sf.rt); }
return;
}
sf = allSharedFolders[secret.channel] = {
teams: [{
cb: cb,
store: store,
id: id
}],
readOnly: Boolean(secondaryKey)
};
var owners = data.owners;
var listmapConfig = {
data: {},
channel: secret.channel,
readOnly: Boolean(secondaryKey),
crypto: Crypto.createEncryptor(secret.keys),
userName: 'sharedFolder',
logLevel: 1,
ChainPad: ChainPad,
classic: true,
network: network,
metadata: {
validateKey: secret.keys.validateKey || undefined,
owners: owners
}
};
var rt = sf.rt = Listmap.create(listmapConfig);
rt.proxy.on('ready', function (info) {
if (isNew && !Object.keys(rt.proxy).length) {
// New Shared folder: no migration required
rt.proxy.version = 2;
}
if (!sf.teams) {
return;
}
sf.teams.forEach(function (obj) {
var leave = function () { SF.leave(secret.channel, teamId); };
var uo = obj.store.manager.addProxy(obj.id, rt, leave, secondaryKey);
SF.checkMigration(secondaryKey, rt.proxy, uo, function () {
obj.cb(sf.rt, info.metadata);
});
});
sf.metadata = info.metadata;
sf.ready = true;
});
rt.proxy.on('error', function (info) {
if (info && info.error) {
if (info.error === "EDELETED" ) {
try {
// Deprecate the shared folder from each team
// XXX We can't deprecate a read-only proxy: the read-only seed will change...
// We can only remove it
sf.teams.forEach(function (obj) {
console.log(obj.store.id, obj.store, obj.id);
obj.store.manager.deprecateProxy(obj.id, secret.channel);
});
} catch (e) {}
delete allSharedFolders[secret.channel];
}
}
});
if (handler) { handler(id, rt); }
});
};
SF.upgrade = function (channel, secret) {
var sf = allSharedFolders[channel];
if (!sf || !sf.readOnly) { return; }
if (!sf.rt.setReadOnly) { return; }
if (!secret.keys || !secret.keys.editKeyStr) { return; }
var crypto = Crypto.createEncryptor(secret.keys);
sf.readOnly = false;
sf.rt.setReadOnly(false, crypto);
};
SF.leave = function (channel, teamId) {
var sf = allSharedFolders[channel];
if (!sf) { return; }
var clients = sf.teams;
if (!Array.isArray(clients)) { return; }
var idx;
clients.some(function (obj, i) {
if (obj.store.id === teamId) {
idx = i;
return true;
}
});
if (typeof (idx) === "undefined") { return; }
// Remove the selected team
clients.splice(idx, 1);
//If all the teams have closed this shared folder, stop it
if (clients.length) { return; }
if (sf.rt && sf.rt.stop) {
sf.rt.stop();
}
};
// Update the password locally
SF.updatePassword = function (Store, data, network, cb) {
var oldChannel = data.oldChannel;
var href = data.href;
var password = data.password;
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
var sf = allSharedFolders[oldChannel];
if (!sf) { return void cb({ error: 'ENOTFOUND' }); }
if (sf.rt && sf.rt.stop) {
sf.rt.stop();
}
var nt = nThen;
sf.teams.forEach(function (obj) {
// XXX if we're a viewer in this team, we can't update the keys
nt = nt(function (waitFor) {
var s = obj.store;
var sfId = obj.id;
var shared = Util.find(s.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {};
if (!sfId || !shared[sfId]) { return; }
var sf = JSON.parse(JSON.stringify(shared[sfId]));
sf.password = password;
sf.channel = secret.channel;
sf.href = '/drive/#'+Hash.getEditHashFromKeys(secret); // XXX encrypt
sf.roHref = '/drive/#'+Hash.getViewHashFromKeys(secret);
SF.load({
network: network,
store: s,
isNewChannel: Store.isNewChannel
}, sfId, sf, waitFor());
if (!s.rpc) { return; }
s.rpc.unpin([oldChannel], waitFor());
s.rpc.pin([secret.channel], waitFor());
}).nThen;
});
nt(cb);
};
/* loadSharedFolders
load all shared folder stored in a given drive
- store: user or team main store
- userObject: userObject associated to the main drive
- handler: a function (sfid, rt) called for each shared folder loaded
*/
SF.loadSharedFolders = function (Store, network, store, userObject, waitFor) {
var shared = Util.find(store.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {};
nThen(function (waitFor) {
Object.keys(shared).forEach(function (id) {
var sf = shared[id];
SF.load({
network: network,
store: store,
isNewChannel: Store.isNewChannel
}, id, sf, waitFor());
});
}).nThen(waitFor());
};
return SF;
});