Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
This commit is contained in:
@@ -104,6 +104,8 @@ define([
|
|||||||
var channel = data.channel;
|
var channel = data.channel;
|
||||||
var owners = data.owners || [];
|
var owners = data.owners || [];
|
||||||
var pending_owners = data.pending_owners || [];
|
var pending_owners = data.pending_owners || [];
|
||||||
|
var teams = priv.teams;
|
||||||
|
var teamOwner = data.teamId;
|
||||||
|
|
||||||
var redrawAll = function () {};
|
var redrawAll = function () {};
|
||||||
|
|
||||||
@@ -124,6 +126,12 @@ define([
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Object.keys(teams).some(function (id) {
|
||||||
|
if (teams[id].edPublic === ed) {
|
||||||
|
f = teams[id];
|
||||||
|
f.teamId = id;
|
||||||
|
}
|
||||||
|
});
|
||||||
if (ed === edPublic) {
|
if (ed === edPublic) {
|
||||||
f = f || user;
|
f = f || user;
|
||||||
if (f.name) { f.edPublic = edPublic; }
|
if (f.name) { f.edPublic = edPublic; }
|
||||||
@@ -155,6 +163,7 @@ define([
|
|||||||
var toRemove = sel.map(function (el) {
|
var toRemove = sel.map(function (el) {
|
||||||
var ed = $(el).attr('data-ed');
|
var ed = $(el).attr('data-ed');
|
||||||
if (!ed) { return; }
|
if (!ed) { return; }
|
||||||
|
if (teamOwner && teams[teamOwner] && teams[teamOwner].edPublic === ed) { me = true; }
|
||||||
if (ed === edPublic) { me = true; }
|
if (ed === edPublic) { me = true; }
|
||||||
return ed;
|
return ed;
|
||||||
}).filter(function (x) { return x; });
|
}).filter(function (x) { return x; });
|
||||||
@@ -171,7 +180,8 @@ define([
|
|||||||
sframeChan.query('Q_SET_PAD_METADATA', {
|
sframeChan.query('Q_SET_PAD_METADATA', {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
|
command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
|
||||||
value: toRemove
|
value: toRemove,
|
||||||
|
teamId: teamOwner
|
||||||
}, waitFor(function (err, res) {
|
}, waitFor(function (err, res) {
|
||||||
err = err || (res && res.error);
|
err = err || (res && res.error);
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -214,6 +224,7 @@ define([
|
|||||||
|
|
||||||
// Add owners column
|
// Add owners column
|
||||||
var drawAdd = function () {
|
var drawAdd = function () {
|
||||||
|
var $div = $(h('div.cp-share-column'));
|
||||||
var _friends = JSON.parse(JSON.stringify(friends));
|
var _friends = JSON.parse(JSON.stringify(friends));
|
||||||
Object.keys(_friends).forEach(function (curve) {
|
Object.keys(_friends).forEach(function (curve) {
|
||||||
if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
|
if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
|
||||||
@@ -228,16 +239,44 @@ define([
|
|||||||
}, function () {
|
}, function () {
|
||||||
//console.log(arguments);
|
//console.log(arguments);
|
||||||
});
|
});
|
||||||
$div2 = $(addCol.div);
|
$div.append(addCol.div);
|
||||||
|
|
||||||
|
if (priv.enableTeams) {
|
||||||
|
var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};
|
||||||
|
Object.keys(teamsData).forEach(function (id) {
|
||||||
|
var t = teamsData[id];
|
||||||
|
t.teamId = id;
|
||||||
|
if (owners.indexOf(t.edPublic) !== -1 || pending_owners.indexOf(t.edPublic) !== -1) {
|
||||||
|
delete teamsData[id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var teamsList = UIElements.getUserGrid('Or a team?', { // XXX
|
||||||
|
common: common,
|
||||||
|
noFilter: true,
|
||||||
|
data: teamsData
|
||||||
|
}, function () {});
|
||||||
|
$div.append(teamsList.div);
|
||||||
|
}
|
||||||
|
|
||||||
// When clicking on the add button, we get the selected users.
|
// When clicking on the add button, we get the selected users.
|
||||||
var addButton = h('button.no-margin', Messages.owner_addButton);
|
var addButton = h('button.no-margin', Messages.owner_addButton);
|
||||||
$(addButton).click(function () {
|
$(addButton).click(function () {
|
||||||
// Check selection
|
// Check selection
|
||||||
var $sel = $div2.find('.cp-usergrid-user.cp-selected');
|
var $sel = $div.find('.cp-usergrid-user.cp-selected');
|
||||||
var sel = $sel.toArray();
|
var sel = $sel.toArray();
|
||||||
if (!sel.length) { return; }
|
if (!sel.length) { return; }
|
||||||
var toAdd = sel.map(function (el) {
|
var toAdd = sel.map(function (el) {
|
||||||
return friends[$(el).attr('data-curve')].edPublic;
|
var friend = friends[$(el).attr('data-curve')];
|
||||||
|
if (!friend) { return; }
|
||||||
|
return friend.edPublic;
|
||||||
|
}).filter(function (x) { return x; });
|
||||||
|
var toAddTeams = sel.map(function (el) {
|
||||||
|
var team = teamsData[$(el).attr('data-teamid')];
|
||||||
|
if (!team || !team.edPublic) { return; }
|
||||||
|
return {
|
||||||
|
edPublic: team.edPublic,
|
||||||
|
id: $(el).attr('data-teamid')
|
||||||
|
};
|
||||||
}).filter(function (x) { return x; });
|
}).filter(function (x) { return x; });
|
||||||
|
|
||||||
NThen(function (waitFor) {
|
NThen(function (waitFor) {
|
||||||
@@ -249,21 +288,58 @@ define([
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
// Send the command
|
// Add one of our teams as an owner
|
||||||
sframeChan.query('Q_SET_PAD_METADATA', {
|
if (toAddTeams.length) {
|
||||||
channel: channel,
|
// Send the command
|
||||||
command: 'ADD_PENDING_OWNERS',
|
sframeChan.query('Q_SET_PAD_METADATA', {
|
||||||
value: toAdd
|
channel: channel,
|
||||||
}, waitFor(function (err, res) {
|
command: 'ADD_OWNERS',
|
||||||
err = err || (res && res.error);
|
value: toAddTeams.map(function (obj) { return obj.edPublic; }),
|
||||||
if (err) {
|
teamId: teamOwner
|
||||||
waitFor.abort();
|
}, waitFor(function (err, res) {
|
||||||
redrawAll();
|
err = err || (res && res.error);
|
||||||
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
|
if (err) {
|
||||||
: Messages.error;
|
waitFor.abort();
|
||||||
return void UI.warn(text);
|
redrawAll();
|
||||||
}
|
var text = err === "INSUFFICIENT_PERMISSIONS" ?
|
||||||
}));
|
Messages.fm_forbidden : Messages.error;
|
||||||
|
return void UI.warn(text);
|
||||||
|
}
|
||||||
|
var isTemplate = priv.isTemplate || data.isTemplate;
|
||||||
|
toAddTeams.forEach(function (obj) {
|
||||||
|
sframeChan.query('Q_STORE_IN_TEAM', {
|
||||||
|
href: data.href || data.rohref,
|
||||||
|
password: data.password,
|
||||||
|
path: isTemplate ? ['template'] : undefined,
|
||||||
|
title: data.title || '',
|
||||||
|
teamId: obj.id
|
||||||
|
}, waitFor(function (err) {
|
||||||
|
if (err) { return void console.error(err); }
|
||||||
|
console.warn(obj.id);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Offer ownership to a friend
|
||||||
|
if (toAdd.length) {
|
||||||
|
// Send the command
|
||||||
|
sframeChan.query('Q_SET_PAD_METADATA', {
|
||||||
|
channel: channel,
|
||||||
|
command: 'ADD_PENDING_OWNERS',
|
||||||
|
value: toAdd,
|
||||||
|
teamId: teamOwner
|
||||||
|
}, waitFor(function (err, res) {
|
||||||
|
err = err || (res && res.error);
|
||||||
|
if (err) {
|
||||||
|
waitFor.abort();
|
||||||
|
redrawAll();
|
||||||
|
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
|
||||||
|
: Messages.error;
|
||||||
|
return void UI.warn(text);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
sel.forEach(function (el) {
|
sel.forEach(function (el) {
|
||||||
var friend = friends[$(el).attr('data-curve')];
|
var friend = friends[$(el).attr('data-curve')];
|
||||||
@@ -291,8 +367,8 @@ define([
|
|||||||
UI.log(Messages.saved);
|
UI.log(Messages.saved);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$div2.append(h('p', addButton));
|
$div.append(h('p', addButton));
|
||||||
return $div2;
|
return $div;
|
||||||
};
|
};
|
||||||
|
|
||||||
redrawAll = function (md) {
|
redrawAll = function (md) {
|
||||||
@@ -430,10 +506,10 @@ define([
|
|||||||
if (data.href || data.roHref) {
|
if (data.href || data.roHref) {
|
||||||
parsed = Hash.parsePadUrl(data.href || data.roHref);
|
parsed = Hash.parsePadUrl(data.href || data.roHref);
|
||||||
}
|
}
|
||||||
// XXX Teams owner: transfer ownership
|
|
||||||
if (owned && data.roHref && parsed.type !== 'drive' && parsed.hashData.type === 'pad') {
|
if (owned && data.roHref && parsed.type !== 'drive' && parsed.hashData.type === 'pad') {
|
||||||
var manageOwners = h('button.no-margin', Messages.owner_openModalButton);
|
var manageOwners = h('button.no-margin', Messages.owner_openModalButton);
|
||||||
$(manageOwners).click(function () {
|
$(manageOwners).click(function () {
|
||||||
|
data.teamId = typeof(owned) !== "boolean" ? owned : undefined;
|
||||||
var modal = createOwnerModal(common, data);
|
var modal = createOwnerModal(common, data);
|
||||||
UI.openCustomModal(modal, {
|
UI.openCustomModal(modal, {
|
||||||
wide: true,
|
wide: true,
|
||||||
@@ -665,6 +741,7 @@ define([
|
|||||||
UIElements.displayAvatar(common, $(avatar), data.avatar, name);
|
UIElements.displayAvatar(common, $(avatar), data.avatar, name);
|
||||||
return h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
|
return h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), {
|
||||||
'data-ed': data.edPublic,
|
'data-ed': data.edPublic,
|
||||||
|
'data-teamid': data.teamId,
|
||||||
'data-curve': data.curvePublic || '',
|
'data-curve': data.curvePublic || '',
|
||||||
'data-name': name.toLowerCase(),
|
'data-name': name.toLowerCase(),
|
||||||
'data-order': i,
|
'data-order': i,
|
||||||
@@ -1218,7 +1295,7 @@ define([
|
|||||||
var team = privateData.teams[config.teamId];
|
var team = privateData.teams[config.teamId];
|
||||||
if (!team) { return void UI.warn(Messages.error); }
|
if (!team) { return void UI.warn(Messages.error); }
|
||||||
|
|
||||||
var module = config.module || common.makeUniversal('team', { onEvent: function () {} });
|
var module = config.module || common.makeUniversal('team');
|
||||||
|
|
||||||
var $div;
|
var $div;
|
||||||
var refreshButton = function () {
|
var refreshButton = function () {
|
||||||
@@ -3710,6 +3787,130 @@ define([
|
|||||||
|
|
||||||
UI.proposal(div, todo);
|
UI.proposal(div, todo);
|
||||||
};
|
};
|
||||||
|
UIElements.displayAddTeamOwnerModal = function (common, data) {
|
||||||
|
var priv = common.getMetadataMgr().getPrivateData();
|
||||||
|
var user = common.getMetadataMgr().getUserData();
|
||||||
|
var sframeChan = common.getSframeChannel();
|
||||||
|
var msg = data.content.msg;
|
||||||
|
|
||||||
|
var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
|
||||||
|
var title = Util.fixHTML(msg.content.title);
|
||||||
|
|
||||||
|
//var text = Messages._getKey('owner_team_add', [name, title]); // XXX
|
||||||
|
var text = name + ' wants you to be an owner of the team ' + title; // XXX
|
||||||
|
|
||||||
|
var div = h('div', [
|
||||||
|
UI.setHTML(h('p'), text),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var answer = function (yes) {
|
||||||
|
common.mailbox.sendTo("ADD_OWNER_ANSWER", {
|
||||||
|
teamChannel: msg.content.teamChannel,
|
||||||
|
title: msg.content.title,
|
||||||
|
answer: yes,
|
||||||
|
user: {
|
||||||
|
displayName: user.name,
|
||||||
|
avatar: user.avatar,
|
||||||
|
profile: user.profile,
|
||||||
|
notifications: user.notifications,
|
||||||
|
curvePublic: user.curvePublic,
|
||||||
|
edPublic: priv.edPublic
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
channel: msg.content.user.notifications,
|
||||||
|
curvePublic: msg.content.user.curvePublic
|
||||||
|
});
|
||||||
|
common.mailbox.dismiss(data, function (err) {
|
||||||
|
if (err) { console.log(err); }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var module = common.makeUniversal('team');
|
||||||
|
|
||||||
|
var addOwner = function (chan, waitFor, cb) {
|
||||||
|
// Remove yourself from the pending owners
|
||||||
|
sframeChan.query('Q_SET_PAD_METADATA', {
|
||||||
|
channel: chan,
|
||||||
|
command: 'ADD_OWNERS',
|
||||||
|
value: [priv.edPublic]
|
||||||
|
}, function (err, res) {
|
||||||
|
err = err || (res && res.error);
|
||||||
|
if (!err) { return; }
|
||||||
|
waitFor.abort();
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var removePending = function (chan, waitFor, cb) {
|
||||||
|
// Remove yourself from the pending owners
|
||||||
|
sframeChan.query('Q_SET_PAD_METADATA', {
|
||||||
|
channel: chan,
|
||||||
|
command: 'RM_PENDING_OWNERS',
|
||||||
|
value: [priv.edPublic]
|
||||||
|
}, waitFor(function (err, res) {
|
||||||
|
err = err || (res && res.error);
|
||||||
|
if (!err) { return; }
|
||||||
|
waitFor.abort();
|
||||||
|
cb(err);
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
var changeAll = function (add, _cb) {
|
||||||
|
var f = add ? addOwner : removePending;
|
||||||
|
var cb = Util.once(_cb);
|
||||||
|
NThen(function (waitFor) {
|
||||||
|
f(msg.content.teamChannel, waitFor, cb);
|
||||||
|
f(msg.content.chatChannel, waitFor, cb);
|
||||||
|
f(msg.content.rosterChannel, waitFor, cb);
|
||||||
|
}).nThen(function () { cb(); });
|
||||||
|
};
|
||||||
|
|
||||||
|
var todo = function (yes) {
|
||||||
|
if (yes) {
|
||||||
|
// ACCEPT
|
||||||
|
changeAll(true, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
|
||||||
|
: Messages.error;
|
||||||
|
return void UI.warn(text);
|
||||||
|
}
|
||||||
|
UI.log(Messages.saved);
|
||||||
|
|
||||||
|
// Send notification to the sender
|
||||||
|
answer(true);
|
||||||
|
|
||||||
|
// Mark ourselves as "owner" in our local team data
|
||||||
|
module.execCommand("ANSWER_OWNERSHIP", {
|
||||||
|
teamChannel: msg.content.teamChannel,
|
||||||
|
answer: true
|
||||||
|
}, function (obj) {
|
||||||
|
if (obj && obj.error) { console.error(obj.error); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove yourself from the pending owners
|
||||||
|
changeAll(false, function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DECLINE
|
||||||
|
// Remove yourself from the pending owners
|
||||||
|
changeAll(false, function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
// Send notification to the sender
|
||||||
|
answer(false);
|
||||||
|
// Set our role back to ADMIN
|
||||||
|
module.execCommand("ANSWER_OWNERSHIP", {
|
||||||
|
teamChannel: msg.content.teamChannel,
|
||||||
|
answer: false
|
||||||
|
}, function (obj) {
|
||||||
|
if (obj && obj.error) { console.error(obj.error); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
UI.proposal(div, todo);
|
||||||
|
};
|
||||||
|
|
||||||
UIElements.getVerifiedFriend = function (common, curve, name) {
|
UIElements.getVerifiedFriend = function (common, curve, name) {
|
||||||
var priv = common.getMetadataMgr().getPrivateData();
|
var priv = common.getMetadataMgr().getPrivateData();
|
||||||
|
|||||||
@@ -838,7 +838,6 @@ define([
|
|||||||
postMessage('GET_PAD_METADATA', data, cb);
|
postMessage('GET_PAD_METADATA', data, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
// XXX Teams: change the password of a pad owned by the team
|
|
||||||
common.changePadPassword = function (Crypt, Crypto, data, cb) {
|
common.changePadPassword = function (Crypt, Crypto, data, cb) {
|
||||||
var href = data.href;
|
var href = data.href;
|
||||||
var newPassword = data.password;
|
var newPassword = data.password;
|
||||||
|
|||||||
@@ -3729,6 +3729,10 @@ define([
|
|||||||
data.roHref = base + data.roHref;
|
data.roHref = base + data.roHref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentPath[0] === TEMPLATE) {
|
||||||
|
data.isTemplate = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (manager.isSharedFolder(el)) {
|
if (manager.isSharedFolder(el)) {
|
||||||
delete data.roHref;
|
delete data.roHref;
|
||||||
//data.noPassword = true;
|
//data.noPassword = true;
|
||||||
|
|||||||
@@ -216,6 +216,9 @@ define([
|
|||||||
// if not archived, add handlers
|
// if not archived, add handlers
|
||||||
if (!content.archived) {
|
if (!content.archived) {
|
||||||
content.handler = function () {
|
content.handler = function () {
|
||||||
|
if (msg.content.teamChannel) {
|
||||||
|
return void UIElements.displayAddTeamOwnerModal(common, data);
|
||||||
|
}
|
||||||
UIElements.displayAddOwnerModal(common, data);
|
UIElements.displayAddOwnerModal(common, data);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1552,7 +1552,6 @@ define([
|
|||||||
|
|
||||||
var href, title;
|
var href, title;
|
||||||
|
|
||||||
// XXX TEAMOWNER
|
|
||||||
if (!res.some(function (obj) {
|
if (!res.some(function (obj) {
|
||||||
if (obj.data &&
|
if (obj.data &&
|
||||||
Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 &&
|
Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 &&
|
||||||
@@ -1612,11 +1611,8 @@ define([
|
|||||||
Store.setPadMetadata = function (clientId, data, cb) {
|
Store.setPadMetadata = function (clientId, data, cb) {
|
||||||
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
|
if (!data.channel) { return void cb({ error: 'ENOTFOUND'}); }
|
||||||
if (!data.command) { return void cb({ error: 'EINVAL' }); }
|
if (!data.command) { return void cb({ error: 'EINVAL' }); }
|
||||||
// XXX TEAMOWNER
|
var s = getStore(data.teamId);
|
||||||
// If owned by a team, we should use the team rpc here
|
s.rpc.setMetadata(data, function (err, res) {
|
||||||
// We'll need common-ui-elements to tell us the "owners" value or we can
|
|
||||||
// call getPadMetadata first
|
|
||||||
store.rpc.setMetadata(data, function (err, res) {
|
|
||||||
if (err) { return void cb({ error: err }); }
|
if (err) { return void cb({ error: err }); }
|
||||||
if (!Array.isArray(res) || !res.length) { return void cb({}); }
|
if (!Array.isArray(res) || !res.length) { return void cb({}); }
|
||||||
cb(res[0]);
|
cb(res[0]);
|
||||||
|
|||||||
@@ -270,12 +270,12 @@ define([
|
|||||||
var content = msg.content;
|
var content = msg.content;
|
||||||
|
|
||||||
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
||||||
if (!content.href || !content.title || !content.channel) {
|
if (!content.teamChannel && !(content.href && content.title && content.channel)) {
|
||||||
console.log('Remove invalid notification');
|
console.log('Remove invalid notification');
|
||||||
return void cb(true);
|
return void cb(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = content.channel;
|
var channel = content.channel || content.teamChannel;
|
||||||
|
|
||||||
if (addOwners[channel]) { return void cb(true); }
|
if (addOwners[channel]) { return void cb(true); }
|
||||||
addOwners[channel] = {
|
addOwners[channel] = {
|
||||||
@@ -286,7 +286,7 @@ define([
|
|||||||
cb(false);
|
cb(false);
|
||||||
};
|
};
|
||||||
removeHandlers['ADD_OWNER'] = function (ctx, box, data) {
|
removeHandlers['ADD_OWNER'] = function (ctx, box, data) {
|
||||||
var channel = data.content.channel;
|
var channel = data.content.channel || data.content.teamChannel;
|
||||||
if (addOwners[channel]) {
|
if (addOwners[channel]) {
|
||||||
delete addOwners[channel];
|
delete addOwners[channel];
|
||||||
}
|
}
|
||||||
@@ -297,12 +297,23 @@ define([
|
|||||||
var content = msg.content;
|
var content = msg.content;
|
||||||
|
|
||||||
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
||||||
if (!content.channel) {
|
if (!content.channel && !content.teamChannel) {
|
||||||
console.log('Remove invalid notification');
|
console.log('Remove invalid notification');
|
||||||
return void cb(true);
|
return void cb(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = content.channel;
|
var channel = content.channel || content.teamChannel;
|
||||||
|
|
||||||
|
// If our ownership rights for a team have been removed, update the owner flag
|
||||||
|
if (content.teamChannel) {
|
||||||
|
var teams = ctx.store.proxy.teams || {};
|
||||||
|
Object.keys(teams).some(function (id) {
|
||||||
|
if (teams[id].channel === channel) {
|
||||||
|
teams[id].owner = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (addOwners[channel] && content.pending) {
|
if (addOwners[channel] && content.pending) {
|
||||||
return void cb(false, addOwners[channel]);
|
return void cb(false, addOwners[channel]);
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ define([
|
|||||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||||
'/bower_components/chainpad/chainpad.dist.js',
|
'/bower_components/chainpad/chainpad.dist.js',
|
||||||
'/bower_components/nthen/index.js',
|
'/bower_components/nthen/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,
|
ProxyManager, UserObject, SF, Roster, Messaging,
|
||||||
Listmap, Crypto, CpNetflux, ChainPad, nThen) {
|
Listmap, Crypto, CpNetflux, ChainPad, nThen, Saferphore) {
|
||||||
var Team = {};
|
var Team = {};
|
||||||
|
|
||||||
var Nacl = window.nacl;
|
var Nacl = window.nacl;
|
||||||
@@ -73,8 +74,8 @@ define([
|
|||||||
var closeTeam = function (ctx, teamId) {
|
var closeTeam = function (ctx, teamId) {
|
||||||
var team = ctx.teams[teamId];
|
var team = ctx.teams[teamId];
|
||||||
if (!team) { return; }
|
if (!team) { return; }
|
||||||
team.listmap.stop();
|
try { team.listmap.stop(); } catch (e) {}
|
||||||
team.roster.stop();
|
try { team.roster.stop(); } catch (e) {}
|
||||||
team.proxy = {};
|
team.proxy = {};
|
||||||
delete ctx.teams[teamId];
|
delete ctx.teams[teamId];
|
||||||
delete ctx.store.proxy.teams[teamId];
|
delete ctx.store.proxy.teams[teamId];
|
||||||
@@ -99,18 +100,6 @@ define([
|
|||||||
if (membersChannel) { list.push(membersChannel); }
|
if (membersChannel) { list.push(membersChannel); }
|
||||||
if (mailboxChannel) { list.push(mailboxChannel); }
|
if (mailboxChannel) { list.push(mailboxChannel); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// XXX Add the team mailbox
|
|
||||||
/*
|
|
||||||
if (store.proxy.mailboxes) {
|
|
||||||
var mList = Object.keys(store.proxy.mailboxes).map(function (m) {
|
|
||||||
return store.proxy.mailboxes[m].channel;
|
|
||||||
});
|
|
||||||
list = list.concat(mList);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
list.sort();
|
list.sort();
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
@@ -185,7 +174,6 @@ define([
|
|||||||
channel: secret.channel,
|
channel: secret.channel,
|
||||||
secret: secret,
|
secret: secret,
|
||||||
validateKey: secret.keys.validateKey
|
validateKey: secret.keys.validateKey
|
||||||
// XXX owners: team owner + all admins?
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,7 +278,9 @@ define([
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var openChannel = function (ctx, teamData, id, cb) {
|
var openChannel = function (ctx, teamData, id, _cb) {
|
||||||
|
var cb = Util.once(_cb);
|
||||||
|
|
||||||
var secret = Hash.getSecrets('team', teamData.hash, teamData.password);
|
var secret = Hash.getSecrets('team', teamData.hash, teamData.password);
|
||||||
var crypto = Crypto.createEncryptor(secret.keys);
|
var crypto = Crypto.createEncryptor(secret.keys);
|
||||||
|
|
||||||
@@ -298,7 +288,34 @@ define([
|
|||||||
|
|
||||||
var roster;
|
var roster;
|
||||||
var lm;
|
var lm;
|
||||||
|
|
||||||
|
// Roster keys
|
||||||
|
var myKeys = {
|
||||||
|
curvePublic: ctx.store.proxy.curvePublic,
|
||||||
|
curvePrivate: ctx.store.proxy.curvePrivate
|
||||||
|
};
|
||||||
|
var rosterData = keys.roster || {};
|
||||||
|
var rosterKeys = rosterData.edit ? Crypto.Team.deriveMemberKeys(rosterData.edit, myKeys)
|
||||||
|
: Crypto.Team.deriveGuestKeys(rosterData.view || '');
|
||||||
|
|
||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
|
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", secret.channel, waitFor(function (e, response) {
|
||||||
|
if (response && response.length && typeof(response[0]) === 'boolean' && response[0]) {
|
||||||
|
// Channel is empty: remove this team
|
||||||
|
delete ctx.store.proxy.teams[id];
|
||||||
|
waitFor.abort();
|
||||||
|
cb({error: 'ENOENT'});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", rosterKeys.channel, waitFor(function (e, response) {
|
||||||
|
if (response && response.length && typeof(response[0]) === 'boolean' && response[0]) {
|
||||||
|
// Channel is empty: remove this team
|
||||||
|
delete ctx.store.proxy.teams[id];
|
||||||
|
waitFor.abort();
|
||||||
|
cb({error: 'ENOENT'});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
// Load the proxy
|
// Load the proxy
|
||||||
var cfg = {
|
var cfg = {
|
||||||
data: {},
|
data: {},
|
||||||
@@ -313,17 +330,25 @@ define([
|
|||||||
userName: 'team',
|
userName: 'team',
|
||||||
classic: true
|
classic: true
|
||||||
};
|
};
|
||||||
|
cfg.onMetadataUpdate = function () {
|
||||||
|
var team = ctx.teams[id];
|
||||||
|
if (!team) { return; }
|
||||||
|
ctx.emit('ROSTER_CHANGE', id, team.clients);
|
||||||
|
};
|
||||||
lm = Listmap.create(cfg);
|
lm = Listmap.create(cfg);
|
||||||
lm.proxy.on('ready', waitFor());
|
lm.proxy.on('ready', waitFor());
|
||||||
|
lm.proxy.on('error', function (info) {
|
||||||
|
if (info && typeof (info.loaded) !== "undefined" && !info.loaded) {
|
||||||
|
cb({error:'ECONNECT'});
|
||||||
|
}
|
||||||
|
if (info && info.error) {
|
||||||
|
if (info.error === "EDELETED" ) {
|
||||||
|
closeTeam(ctx, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Load the roster
|
// Load the roster
|
||||||
var myKeys = {
|
|
||||||
curvePublic: ctx.store.proxy.curvePublic,
|
|
||||||
curvePrivate: ctx.store.proxy.curvePrivate
|
|
||||||
};
|
|
||||||
var rosterData = keys.roster || {};
|
|
||||||
var rosterKeys = rosterData.edit ? Crypto.Team.deriveMemberKeys(rosterData.edit, myKeys)
|
|
||||||
: Crypto.Team.deriveGuestKeys(rosterData.view || '');
|
|
||||||
Roster.create({
|
Roster.create({
|
||||||
network: ctx.store.network,
|
network: ctx.store.network,
|
||||||
channel: rosterKeys.channel,
|
channel: rosterKeys.channel,
|
||||||
@@ -462,10 +487,15 @@ define([
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}).nThen(function () {
|
}).nThen(function () {
|
||||||
|
var id = Util.createRandomInteger();
|
||||||
|
config.onMetadataUpdate = function () {
|
||||||
|
var team = ctx.teams[id];
|
||||||
|
if (!team) { return; }
|
||||||
|
ctx.emit('ROSTER_CHANGE', id, team.clients);
|
||||||
|
};
|
||||||
var lm = Listmap.create(config);
|
var lm = Listmap.create(config);
|
||||||
var proxy = lm.proxy;
|
var proxy = lm.proxy;
|
||||||
proxy.on('ready', function () {
|
proxy.on('ready', function () {
|
||||||
var id = Util.createRandomInteger();
|
|
||||||
// Store keys in our drive
|
// Store keys in our drive
|
||||||
var keys = {
|
var keys = {
|
||||||
drive: {
|
drive: {
|
||||||
@@ -505,10 +535,91 @@ define([
|
|||||||
if (info && typeof (info.loaded) !== "undefined" && !info.loaded) {
|
if (info && typeof (info.loaded) !== "undefined" && !info.loaded) {
|
||||||
cb({error:'ECONNECT'});
|
cb({error:'ECONNECT'});
|
||||||
}
|
}
|
||||||
|
if (info && info.error) {
|
||||||
|
if (info.error === "EDELETED") {
|
||||||
|
closeTeam(ctx, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var deleteTeam = function (ctx, data, cId, cb) {
|
||||||
|
var teamId = data.teamId;
|
||||||
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
|
var team = ctx.teams[teamId];
|
||||||
|
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||||
|
if (!team || !teamData) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
var state = team.roster.getState();
|
||||||
|
var curvePublic = Util.find(ctx, ['store', 'proxy', 'curvePublic']);
|
||||||
|
var me = state.members[curvePublic];
|
||||||
|
if (!me || me.role !== "OWNER") { return cb({ error: "EFORBIDDEN"}); }
|
||||||
|
|
||||||
|
var edPublic = Util.find(ctx, ['store', 'proxy', 'edPublic']);
|
||||||
|
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
ctx.Store.anonRpcMsg(null, {
|
||||||
|
msg: 'GET_METADATA',
|
||||||
|
data: teamData.channel
|
||||||
|
}, waitFor(function (obj) {
|
||||||
|
// If we can't get owners, abort
|
||||||
|
if (obj && obj.error) {
|
||||||
|
waitFor.abort();
|
||||||
|
return cb({ error: obj.error});
|
||||||
|
}
|
||||||
|
// Check if we're an owner of the team drive
|
||||||
|
var metadata = obj[0];
|
||||||
|
if (metadata && Array.isArray(metadata.owners) &&
|
||||||
|
metadata.owners.indexOf(edPublic) !== -1) { return; }
|
||||||
|
// If w'e're not an owner, abort
|
||||||
|
waitFor.abort();
|
||||||
|
cb({error: 'EFORBIDDEN'});
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
team.proxy.delete = true;
|
||||||
|
// Delete the owned pads
|
||||||
|
var ownedPads = team.manager.getChannelsList('owned');
|
||||||
|
var sem = Saferphore.create(10);
|
||||||
|
ownedPads.forEach(function (c) {
|
||||||
|
var w = waitFor();
|
||||||
|
sem.take(function (give) {
|
||||||
|
team.rpc.removeOwnedChannel(c, give(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
w();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Delete the pins log
|
||||||
|
team.rpc.removePins(waitFor(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
console.error(err);
|
||||||
|
}));
|
||||||
|
// Delete the roster
|
||||||
|
var rosterChan = Util.find(teamData, ['keys', 'roster', 'channel']);
|
||||||
|
ctx.store.rpc.removeOwnedChannel(rosterChan, waitFor(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
console.error(err);
|
||||||
|
}));
|
||||||
|
// Delete the chat
|
||||||
|
var chatChan = Util.find(teamData, ['keys', 'chat', 'channel']);
|
||||||
|
/*
|
||||||
|
ctx.store.rpc.removeOwnedChannel(chatChan, waitFor(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
console.error(err);
|
||||||
|
}));
|
||||||
|
*/ // XXX
|
||||||
|
// Delete the team drive
|
||||||
|
ctx.store.rpc.removeOwnedChannel(teamData.channel, waitFor(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
console.error(err);
|
||||||
|
}));
|
||||||
|
}).nThen(function () {
|
||||||
|
cb();
|
||||||
|
closeTeam(ctx, teamId);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var joinTeam = function (ctx, data, cId, cb) {
|
var joinTeam = function (ctx, data, cId, cb) {
|
||||||
var team = data.team;
|
var team = data.team;
|
||||||
if (!team.hash || !team.channel || !team.password
|
if (!team.hash || !team.channel || !team.password
|
||||||
@@ -540,12 +651,43 @@ define([
|
|||||||
var getTeamRoster = function (ctx, data, cId, cb) {
|
var getTeamRoster = function (ctx, data, cId, cb) {
|
||||||
var teamId = data.teamId;
|
var teamId = data.teamId;
|
||||||
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
|
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||||
|
if (!teamData) { return void cb ({error: 'ENOENT'}); }
|
||||||
var team = ctx.teams[teamId];
|
var team = ctx.teams[teamId];
|
||||||
if (!team) { return void cb ({error: 'ENOENT'}); }
|
if (!team) { return void cb ({error: 'ENOENT'}); }
|
||||||
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
|
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
|
||||||
var state = team.roster.getState() || {};
|
var state = team.roster.getState() || {};
|
||||||
var members = state.members || {};
|
var members = state.members || {};
|
||||||
|
|
||||||
|
// Get pending owners
|
||||||
|
var md = team.listmap.metadata || {};
|
||||||
|
if (Array.isArray(md.pending_owners)) {
|
||||||
|
// Get the members associated to the pending_owners' edPublic and mark them as such
|
||||||
|
md.pending_owners.forEach(function (ed) {
|
||||||
|
var member;
|
||||||
|
Object.keys(members).some(function (curve) {
|
||||||
|
if (members[curve].edPublic === ed) {
|
||||||
|
member = members[curve];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ((!member || member.role !== 'OWNER') && teamData.owner) {
|
||||||
|
var removeOwnership = function (chan) {
|
||||||
|
ctx.Store.setPadMetadata(null, {
|
||||||
|
channel: chan,
|
||||||
|
command: 'RM_PENDING_OWNERS',
|
||||||
|
value: [ed],
|
||||||
|
}, function () {});
|
||||||
|
};
|
||||||
|
removeOwnership(teamData.channel);
|
||||||
|
removeOwnership(Util.find(teamData, ['keys', 'roster', 'channel']));
|
||||||
|
removeOwnership(Util.find(teamData, ['keys', 'chat', 'channel']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
member.pendingOwner = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add online status (using messenger data)
|
// Add online status (using messenger data)
|
||||||
var chatData = team.getChatData();
|
var chatData = team.getChatData();
|
||||||
var online = ctx.store.messenger.getOnlineList(chatData.channel) || [];
|
var online = ctx.store.messenger.getOnlineList(chatData.channel) || [];
|
||||||
@@ -584,6 +726,159 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var offerOwnership = function (ctx, data, cId, _cb) {
|
||||||
|
var cb = Util.once(_cb);
|
||||||
|
var teamId = data.teamId;
|
||||||
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
|
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||||
|
if (!teamData) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
var team = ctx.teams[teamId];
|
||||||
|
if (!team) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
|
||||||
|
if (!data.curvePublic) { return void cb({error: 'MISSING_DATA'}); }
|
||||||
|
var state = team.roster.getState();
|
||||||
|
var user = state.members[data.curvePublic];
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
// Offer ownership to a friend
|
||||||
|
var onError = function (res) {
|
||||||
|
var err = res && res.error;
|
||||||
|
if (err) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb({error:err});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var addPendingOwner = function (chan) {
|
||||||
|
ctx.Store.setPadMetadata(null, {
|
||||||
|
channel: chan,
|
||||||
|
command: 'ADD_PENDING_OWNERS',
|
||||||
|
value: [user.edPublic],
|
||||||
|
}, waitFor(onError));
|
||||||
|
};
|
||||||
|
// Team proxy
|
||||||
|
addPendingOwner(teamData.channel);
|
||||||
|
// Team roster
|
||||||
|
addPendingOwner(Util.find(teamData, ['keys', 'roster', 'channel']));
|
||||||
|
// Team chat
|
||||||
|
addPendingOwner(Util.find(teamData, ['keys', 'chat', 'channel']));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
var obj = {};
|
||||||
|
obj[user.curvePublic] = {
|
||||||
|
role: 'OWNER'
|
||||||
|
};
|
||||||
|
team.roster.describe(obj, waitFor(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Send mailbox to offer ownership
|
||||||
|
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||||
|
ctx.store.mailbox.sendTo("ADD_OWNER", {
|
||||||
|
teamChannel: teamData.channel,
|
||||||
|
chatChannel: Util.find(teamData, ['keys', 'chat', 'channel']),
|
||||||
|
rosterChannel: Util.find(teamData, ['keys', 'roster', 'channel']),
|
||||||
|
title: teamData.metadata.name,
|
||||||
|
user: myData
|
||||||
|
}, {
|
||||||
|
channel: user.notifications,
|
||||||
|
curvePublic: user.curvePublic
|
||||||
|
}, waitFor());
|
||||||
|
}).nThen(function () {
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var revokeOwnership = function (ctx, teamId, user, _cb) {
|
||||||
|
var cb = Util.once(_cb);
|
||||||
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
|
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||||
|
if (!teamData) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
var team = ctx.teams[teamId];
|
||||||
|
if (!team) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
var md = team.listmap.metadata || {};
|
||||||
|
var isPendingOwner = (md.pending_owners || []).indexOf(user.edPublic) !== -1;
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
var cmd = isPendingOwner ? 'RM_PENDING_OWNERS' : 'RM_OWNERS';
|
||||||
|
|
||||||
|
var onError = function (res) {
|
||||||
|
var err = res && res.error;
|
||||||
|
if (err) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var removeOwnership = function (chan) {
|
||||||
|
ctx.Store.setPadMetadata(null, {
|
||||||
|
channel: chan,
|
||||||
|
command: cmd,
|
||||||
|
value: [user.edPublic],
|
||||||
|
}, waitFor(onError));
|
||||||
|
};
|
||||||
|
// Team proxy
|
||||||
|
removeOwnership(teamData.channel);
|
||||||
|
// Team roster
|
||||||
|
removeOwnership(Util.find(teamData, ['keys', 'roster', 'channel']));
|
||||||
|
// Team chat
|
||||||
|
removeOwnership(Util.find(teamData, ['keys', 'chat', 'channel']));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
var obj = {};
|
||||||
|
obj[user.curvePublic] = {
|
||||||
|
role: 'ADMIN',
|
||||||
|
pendingOwner: false
|
||||||
|
};
|
||||||
|
team.roster.describe(obj, waitFor(function (err) {
|
||||||
|
if (err) { console.error(err); }
|
||||||
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
// Send mailbox to offer ownership
|
||||||
|
var myData = Messaging.createData(ctx.store.proxy, false);
|
||||||
|
ctx.store.mailbox.sendTo("RM_OWNER", {
|
||||||
|
teamChannel: teamData.channel,
|
||||||
|
title: teamData.metadata.name,
|
||||||
|
pending: isPendingOwner,
|
||||||
|
user: myData
|
||||||
|
}, {
|
||||||
|
channel: user.notifications,
|
||||||
|
curvePublic: user.curvePublic
|
||||||
|
}, waitFor());
|
||||||
|
}).nThen(function () {
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// We've received an offer to be an owner of the team.
|
||||||
|
// If we accept, we need to set the "owner" flag in our team data
|
||||||
|
// If we decline, we need to change our role back to "ADMIN"
|
||||||
|
var answerOwnership = function (ctx, data, cId, cb) {
|
||||||
|
var myTeams = ctx.store.proxy.teams;
|
||||||
|
var teamId;
|
||||||
|
Object.keys(myTeams).forEach(function (id) {
|
||||||
|
if (myTeams[id].channel === data.teamChannel) {
|
||||||
|
teamId = id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
|
var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
|
||||||
|
if (!teamData) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
var team = ctx.teams[teamId];
|
||||||
|
if (!team) { return void cb ({error: 'ENOENT'}); }
|
||||||
|
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
|
||||||
|
var obj = {};
|
||||||
|
|
||||||
|
// Accept
|
||||||
|
if (data.answer) {
|
||||||
|
teamData.owner = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Decline
|
||||||
|
obj[ctx.store.proxy.curvePublic] = {
|
||||||
|
role: 'ADMIN',
|
||||||
|
};
|
||||||
|
team.roster.describe(obj, function (err) {
|
||||||
|
if (err) { return void cb({error: err}); }
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var describeUser = function (ctx, data, cId, cb) {
|
var describeUser = function (ctx, data, cId, cb) {
|
||||||
var teamId = data.teamId;
|
var teamId = data.teamId;
|
||||||
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
@@ -591,6 +886,19 @@ define([
|
|||||||
if (!team) { return void cb ({error: 'ENOENT'}); }
|
if (!team) { return void cb ({error: 'ENOENT'}); }
|
||||||
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
|
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
|
||||||
if (!data.curvePublic || !data.data) { return void cb({error: 'MISSING_DATA'}); }
|
if (!data.curvePublic || !data.data) { return void cb({error: 'MISSING_DATA'}); }
|
||||||
|
var state = team.roster.getState();
|
||||||
|
var user = state.members[data.curvePublic];
|
||||||
|
|
||||||
|
// It it is an ownership revocation, we have to set it in pad metadata first
|
||||||
|
if (user.role === "OWNER" && data.data.role !== "OWNER") {
|
||||||
|
revokeOwnership(ctx, teamId, user, function (err) {
|
||||||
|
if (!err) { return; }
|
||||||
|
console.error(err);
|
||||||
|
return void cb({error: err});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var obj = {};
|
var obj = {};
|
||||||
obj[data.curvePublic] = data.data;
|
obj[data.curvePublic] = data.data;
|
||||||
team.roster.describe(obj, function (err) {
|
team.roster.describe(obj, function (err) {
|
||||||
@@ -825,6 +1133,12 @@ define([
|
|||||||
if (cmd === 'SET_TEAM_METADATA') {
|
if (cmd === 'SET_TEAM_METADATA') {
|
||||||
return void setTeamMetadata(ctx, data, clientId, cb);
|
return void setTeamMetadata(ctx, data, clientId, cb);
|
||||||
}
|
}
|
||||||
|
if (cmd === 'OFFER_OWNERSHIP') {
|
||||||
|
return void offerOwnership(ctx, data, clientId, cb);
|
||||||
|
}
|
||||||
|
if (cmd === 'ANSWER_OWNERSHIP') {
|
||||||
|
return void answerOwnership(ctx, data, clientId, cb);
|
||||||
|
}
|
||||||
if (cmd === 'DESCRIBE_USER') {
|
if (cmd === 'DESCRIBE_USER') {
|
||||||
return void describeUser(ctx, data, clientId, cb);
|
return void describeUser(ctx, data, clientId, cb);
|
||||||
}
|
}
|
||||||
@@ -840,6 +1154,9 @@ define([
|
|||||||
if (cmd === 'REMOVE_USER') {
|
if (cmd === 'REMOVE_USER') {
|
||||||
return void removeUser(ctx, data, clientId, cb);
|
return void removeUser(ctx, data, clientId, cb);
|
||||||
}
|
}
|
||||||
|
if (cmd === 'DELETE_TEAM') {
|
||||||
|
return void deleteTeam(ctx, data, clientId, cb);
|
||||||
|
}
|
||||||
if (cmd === 'CREATE_TEAM') {
|
if (cmd === 'CREATE_TEAM') {
|
||||||
return void createTeam(ctx, data, clientId, cb);
|
return void createTeam(ctx, data, clientId, cb);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -827,7 +827,7 @@ define([
|
|||||||
// Don't push duplicates
|
// Don't push duplicates
|
||||||
if (result.indexOf(data.channel) !== -1) { return; }
|
if (result.indexOf(data.channel) !== -1) { return; }
|
||||||
// Return owned pads
|
// Return owned pads
|
||||||
if (_ownedByMe(Env, data.owners)) {
|
if (_ownedByMe(Env, data.owners) && data.owners.length === 1) {
|
||||||
result.push(data.channel);
|
result.push(data.channel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1197,5 +1197,9 @@
|
|||||||
"team_nameHint": "Name des Teams festlegen",
|
"team_nameHint": "Name des Teams festlegen",
|
||||||
"team_avatarTitle": "Teamavatar",
|
"team_avatarTitle": "Teamavatar",
|
||||||
"team_avatarHint": "Maximale Größe ist 500 KB (png, jpg, jpeg, gif)",
|
"team_avatarHint": "Maximale Größe ist 500 KB (png, jpg, jpeg, gif)",
|
||||||
"team_infoContent": "Jedes Team hat eigene CryptDrives, Speicherplatzbegrenzungen, Chats und Mitgliederlisten. Eigentümer können das gesamte Team löschen. Admins können Mitglieder einladen oder entfernen. Mitglieder können das Team verlassen."
|
"team_infoContent": "Jedes Team hat eigene CryptDrives, Speicherplatzbegrenzungen, Chats und Mitgliederlisten. Eigentümer können das gesamte Team löschen. Admins können Mitglieder einladen oder entfernen. Mitglieder können das Team verlassen.",
|
||||||
|
"team_maxOwner": "Jeder Benutzer kann nur Eigentümer eines Teams sein.",
|
||||||
|
"team_maxTeams": "Jeder Benutzer kann nur Mitglied von {0} Teams sein.",
|
||||||
|
"team_listTitle": "Deine Teams",
|
||||||
|
"team_listSlot": "Verfügbare Teamplätze"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"saved": "Enregistré",
|
"saved": "Enregistré",
|
||||||
"synced": "Tout est enregistré",
|
"synced": "Tout est enregistré",
|
||||||
"deleted": "Pad supprimé de votre CryptDrive",
|
"deleted": "Supprimé",
|
||||||
"deletedFromServer": "Pad supprimé du serveur",
|
"deletedFromServer": "Pad supprimé du serveur",
|
||||||
"mustLogin": "Vous devez être enregistré pour avoir accès à cette page",
|
"mustLogin": "Vous devez être enregistré pour avoir accès à cette page",
|
||||||
"disabledApp": "Cette application a été désactivée. Pour plus d'information, veuillez contacter l'administrateur de ce CryptPad.",
|
"disabledApp": "Cette application a été désactivée. Pour plus d'information, veuillez contacter l'administrateur de ce CryptPad.",
|
||||||
@@ -1201,5 +1201,16 @@
|
|||||||
"team_maxOwner": "Chaque compte utilisateur ne peut être propriétaire que d'une seule équipe.",
|
"team_maxOwner": "Chaque compte utilisateur ne peut être propriétaire que d'une seule équipe.",
|
||||||
"team_maxTeams": "Chaque compte utilisateur ne peut être membre que de {0} équipes.",
|
"team_maxTeams": "Chaque compte utilisateur ne peut être membre que de {0} équipes.",
|
||||||
"team_listTitle": "Vos équipes",
|
"team_listTitle": "Vos équipes",
|
||||||
"team_listSlot": "Emplacement d'équipe disponible"
|
"team_listSlot": "Emplacement d'équipe disponible",
|
||||||
|
"owner_addTeamText": "...ou à une équipe",
|
||||||
|
"owner_team_add": "{0} souhaite que vous soyez propriétaire de l'équipe <b>{1}</b>. Acceptez-vous ?",
|
||||||
|
"team_rosterPromoteOwner": "Proposer d'être propriétaire",
|
||||||
|
"team_ownerConfirm": "Les co-propriétaires seront en mesure de modifier ou supprimer l'équipe et pourront supprimer vos droits de propriétaire. Continuer ?",
|
||||||
|
"team_kickConfirm": "{0} sera informé que vous l'avez expulsé de l'équipe. Êtes-vous sûr ?",
|
||||||
|
"sent": "Message envoyé",
|
||||||
|
"team_pending": "Invité",
|
||||||
|
"team_deleteTitle": "Suppression de l'équipe",
|
||||||
|
"team_deleteHint": "Supprimer l'équipe et tous les documents dont elle est exclusivement propriétaire.",
|
||||||
|
"team_deleteButton": "Supprimer",
|
||||||
|
"team_deleteConfirm": "Vous êtes sur le point de supprimer les données d'une équipe entière. Cette action peut impacter l'accès à leur données pour d'autres membres de l'équipe. La suppression est irréversible. Êtes-vous sûr de vouloir continuer ?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"synced": "Everything is saved",
|
"synced": "Everything is saved",
|
||||||
"deleted": "Pad deleted from your CryptDrive",
|
"deleted": "Deleted",
|
||||||
"deletedFromServer": "Pad deleted from the server",
|
"deletedFromServer": "Pad deleted from the server",
|
||||||
"mustLogin": "You must be logged in to access this page",
|
"mustLogin": "You must be logged in to access this page",
|
||||||
"disabledApp": "This application has been disabled. Contact the administrator of this CryptPad for more information.",
|
"disabledApp": "This application has been disabled. Contact the administrator of this CryptPad for more information.",
|
||||||
@@ -1201,5 +1201,16 @@
|
|||||||
"team_maxOwner": "Each user account is restricted to owning a single team.",
|
"team_maxOwner": "Each user account is restricted to owning a single team.",
|
||||||
"team_maxTeams": "Each user account can only be a member of {0} teams.",
|
"team_maxTeams": "Each user account can only be a member of {0} teams.",
|
||||||
"team_listTitle": "Your teams",
|
"team_listTitle": "Your teams",
|
||||||
"team_listSlot": "Available team slot"
|
"team_listSlot": "Available team slot",
|
||||||
|
"owner_addTeamText": "...or a team",
|
||||||
|
"owner_team_add": "{0} wants you to be an owner of the team <b>{1}</b>. Do you accept?",
|
||||||
|
"team_rosterPromoteOwner": "Offer ownership",
|
||||||
|
"team_ownerConfirm": "Co-owners can modify or delete the team and remove you as an owner. Are you sure?",
|
||||||
|
"team_kickConfirm": "{0} will know that you removed them from the team. Are you sure?",
|
||||||
|
"sent": "Message sent",
|
||||||
|
"team_pending": "Invited",
|
||||||
|
"team_deleteTitle": "Team deletion",
|
||||||
|
"team_deleteHint": "Delete the team and all documents owned exclusively by the team.",
|
||||||
|
"team_deleteButton": "Delete",
|
||||||
|
"team_deleteConfirm": "You are about to delete all of an entire team's data. This may impact other team members access to their data. This cannot be undone. Are you sure you want to proceed?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ define([
|
|||||||
],
|
],
|
||||||
'admin': [
|
'admin': [
|
||||||
'cp-team-name',
|
'cp-team-name',
|
||||||
'cp-team-avatar'
|
'cp-team-avatar',
|
||||||
|
'cp-team-delete',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -332,7 +333,7 @@ define([
|
|||||||
|
|
||||||
var isOwner = Object.keys(privateData.teams || {}).some(function (id) {
|
var isOwner = Object.keys(privateData.teams || {}).some(function (id) {
|
||||||
return privateData.teams[id].owner;
|
return privateData.teams[id].owner;
|
||||||
});
|
}) && !privateData.devMode; // XXX
|
||||||
if (Object.keys(privateData.teams || {}).length >= 3 || isOwner) {
|
if (Object.keys(privateData.teams || {}).length >= 3 || isOwner) {
|
||||||
content.push(h('div.alert.alert-warning', {
|
content.push(h('div.alert.alert-warning', {
|
||||||
role:'alert'
|
role:'alert'
|
||||||
@@ -429,6 +430,9 @@ define([
|
|||||||
common.displayAvatar($(avatar), data.avatar, data.displayName);
|
common.displayAvatar($(avatar), data.avatar, data.displayName);
|
||||||
// Name
|
// Name
|
||||||
var name = h('span.cp-team-member-name', data.displayName);
|
var name = h('span.cp-team-member-name', data.displayName);
|
||||||
|
if (data.pendingOwner) {
|
||||||
|
$(name).append(h('em', " PENDING"));
|
||||||
|
}
|
||||||
// Status
|
// Status
|
||||||
var status = h('span.cp-team-member-status'+(data.online ? '.online' : ''));
|
var status = h('span.cp-team-member-status'+(data.online ? '.online' : ''));
|
||||||
// Actions
|
// Actions
|
||||||
@@ -437,6 +441,29 @@ define([
|
|||||||
var isMe = me && me.curvePublic === data.curvePublic;
|
var isMe = me && me.curvePublic === data.curvePublic;
|
||||||
var myRole = me ? (ROLES.indexOf(me.role) || 0) : -1;
|
var myRole = me ? (ROLES.indexOf(me.role) || 0) : -1;
|
||||||
var theirRole = ROLES.indexOf(data.role) || 0;
|
var theirRole = ROLES.indexOf(data.role) || 0;
|
||||||
|
// If they're an admin and I am an owner, I can promote them to owner
|
||||||
|
if (!isMe && myRole > theirRole && theirRole === 1 && !data.pending) {
|
||||||
|
var promoteOwner = h('span.fa.fa-angle-double-up', {
|
||||||
|
title: "Offer ownership" // XXX
|
||||||
|
});
|
||||||
|
$(promoteOwner).click(function () {
|
||||||
|
$(promoteOwner).hide();
|
||||||
|
UI.confirm("Are you sure???", function (yes) { // XXX
|
||||||
|
if (!yes) { return; }
|
||||||
|
APP.module.execCommand('OFFER_OWNERSHIP', {
|
||||||
|
teamId: APP.team,
|
||||||
|
curvePublic: data.curvePublic
|
||||||
|
}, function (obj) {
|
||||||
|
if (obj && obj.error) {
|
||||||
|
console.error(obj.error);
|
||||||
|
return void UI.warn(Messages.error);
|
||||||
|
}
|
||||||
|
UI.log("DONE"); // XXX
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$actions.append(promoteOwner);
|
||||||
|
}
|
||||||
// If they're a member and I have a higher role than them, I can promote them to admin
|
// If they're a member and I have a higher role than them, I can promote them to admin
|
||||||
if (!isMe && myRole > theirRole && theirRole === 0 && !data.pending) {
|
if (!isMe && myRole > theirRole && theirRole === 0 && !data.pending) {
|
||||||
var promote = h('span.fa.fa-angle-double-up', {
|
var promote = h('span.fa.fa-angle-double-up', {
|
||||||
@@ -466,7 +493,8 @@ define([
|
|||||||
$actions.append(demote);
|
$actions.append(demote);
|
||||||
}
|
}
|
||||||
// If I'm not a member and I have an equal or higher role than them, I can remove them
|
// If I'm not a member and I have an equal or higher role than them, I can remove them
|
||||||
if (!isMe && myRole > 0 && myRole >= theirRole) {
|
// Note: we can't remove owners, we have to demote them first
|
||||||
|
if (!isMe && myRole > 0 && myRole >= theirRole && theirRole !== 2) {
|
||||||
var remove = h('span.fa.fa-times', {
|
var remove = h('span.fa.fa-times', {
|
||||||
title: Messages.team_rosterKick
|
title: Messages.team_rosterKick
|
||||||
});
|
});
|
||||||
@@ -513,7 +541,7 @@ define([
|
|||||||
var me = roster[userData.curvePublic] || {};
|
var me = roster[userData.curvePublic] || {};
|
||||||
var owner = Object.keys(roster).filter(function (k) {
|
var owner = Object.keys(roster).filter(function (k) {
|
||||||
if (roster[k].pending) { return; }
|
if (roster[k].pending) { return; }
|
||||||
return roster[k].role === "OWNER";
|
return roster[k].role === "OWNER" || roster[k].pendingOwner;
|
||||||
}).map(function (k) {
|
}).map(function (k) {
|
||||||
return makeMember(common, roster[k], me);
|
return makeMember(common, roster[k], me);
|
||||||
});
|
});
|
||||||
@@ -718,6 +746,40 @@ define([
|
|||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
makeBlock('delete', function (common, cb) { // XXX makeBlock keys
|
||||||
|
var deleteTeam = h('button.btn.btn-danger', Messages.team_delete || "DELETE"); // XXX
|
||||||
|
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide();
|
||||||
|
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide();
|
||||||
|
|
||||||
|
var deleting = false;
|
||||||
|
$(deleteTeam).click(function () {
|
||||||
|
if (deleting) { return; }
|
||||||
|
UI.confirm("Are you sure", function (yes) { // XXX
|
||||||
|
if (!yes) { return; }
|
||||||
|
if (deleting) { return; }
|
||||||
|
deleting = true;
|
||||||
|
$spinner.show();
|
||||||
|
APP.module.execCommand("DELETE_TEAM", {
|
||||||
|
teamId: APP.team
|
||||||
|
}, function (obj) {
|
||||||
|
$spinner.hide();
|
||||||
|
deleting = false;
|
||||||
|
if (obj && obj.error) {
|
||||||
|
return void UI.warn(obj.error);
|
||||||
|
}
|
||||||
|
$ok.show();
|
||||||
|
UI.log('DELETED'); // XXX
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cb([
|
||||||
|
deleteTeam,
|
||||||
|
$ok[0],
|
||||||
|
$spinner[0]
|
||||||
|
]);
|
||||||
|
}, true);
|
||||||
|
|
||||||
var main = function () {
|
var main = function () {
|
||||||
var common;
|
var common;
|
||||||
var readOnly;
|
var readOnly;
|
||||||
|
|||||||
@@ -36,31 +36,7 @@ define([
|
|||||||
};
|
};
|
||||||
window.addEventListener('message', onMsg);
|
window.addEventListener('message', onMsg);
|
||||||
}).nThen(function (/*waitFor*/) {
|
}).nThen(function (/*waitFor*/) {
|
||||||
var teamId; // XXX
|
var teamId;
|
||||||
var afterSecrets = function (Cryptpad, Utils, secret, cb) {
|
|
||||||
return void cb();
|
|
||||||
/*
|
|
||||||
var hash = window.location.hash.slice(1);
|
|
||||||
if (hash && Utils.LocalStore.isLoggedIn()) {
|
|
||||||
return; // XXX How to add a shared folder?
|
|
||||||
// Add a shared folder!
|
|
||||||
Cryptpad.addSharedFolder(teamId, secret, function (id) {
|
|
||||||
window.CryptPad_newSharedFolder = id;
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else if (hash) {
|
|
||||||
var id = Utils.Util.createRandomInteger();
|
|
||||||
window.CryptPad_newSharedFolder = id;
|
|
||||||
var data = {
|
|
||||||
href: Utils.Hash.getRelativeHref(window.location.href),
|
|
||||||
password: secret.password
|
|
||||||
};
|
|
||||||
return void Cryptpad.loadSharedFolder(id, data, cb);
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
var addRpc = function (sframeChan, Cryptpad) {
|
var addRpc = function (sframeChan, Cryptpad) {
|
||||||
sframeChan.on('Q_SET_TEAM', function (data, cb) {
|
sframeChan.on('Q_SET_TEAM', function (data, cb) {
|
||||||
teamId = data;
|
teamId = data;
|
||||||
@@ -72,11 +48,6 @@ define([
|
|||||||
data.teamId = teamId;
|
data.teamId = teamId;
|
||||||
Cryptpad.userObjectCommand(data, cb);
|
Cryptpad.userObjectCommand(data, cb);
|
||||||
});
|
});
|
||||||
// XXX no drive restore in teams? you could restore old keys...
|
|
||||||
/*sframeChan.on('Q_DRIVE_RESTORE', function (data, cb) {
|
|
||||||
data.teamId = teamId;
|
|
||||||
Cryptpad.restoreDrive(data, cb);
|
|
||||||
});*/
|
|
||||||
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {
|
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {
|
||||||
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
if (!teamId) { return void cb({error: 'EINVAL'}); }
|
||||||
if (data && data.sharedFolder) {
|
if (data && data.sharedFolder) {
|
||||||
@@ -109,7 +80,6 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
SFCommonO.start({
|
SFCommonO.start({
|
||||||
afterSecrets: afterSecrets,
|
|
||||||
noHash: true,
|
noHash: true,
|
||||||
noRealtime: true,
|
noRealtime: true,
|
||||||
//driveEvents: true,
|
//driveEvents: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user