From 01a8e45307707fb4e8902c932c3ad04e2f9f52fc Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Oct 2020 14:06:05 +0200 Subject: [PATCH 1/8] Fix duplicate team bug --- www/common/common-interface.js | 10 +++++++++- www/common/outer/team.js | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 76dec3dd8..2e6c3a609 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -736,14 +736,20 @@ define([ UI.proposal = function (content, cb) { + var clicked = false; var buttons = [{ name: Messages.friendRequest_later, - onClick: function () {}, + onClick: function () { + if (clicked) { return; } + clicked = true; + }, keys: [27] }, { className: 'primary', name: Messages.friendRequest_accept, onClick: function () { + if (clicked) { return; } + clicked = true; cb(true); }, keys: [13] @@ -751,6 +757,8 @@ define([ className: 'primary', name: Messages.friendRequest_decline, onClick: function () { + if (clicked) { return; } + clicked = true; cb(false); }, keys: [[13, 'ctrl']] diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 2ba4e30ee..4ecfc09b6 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -1668,7 +1668,33 @@ define([ updateMyRights(ctx, p[1]); }); + // Remove duplicate teams + var _teams = {}; + Object.keys(teams).forEach(function (id) { + var t = teams[id]; + var _t = _teams[t.channel]; + + // Not found yet? add to the list + if (!_t) { + _teams[t.channel] = { edit: Boolean(t.hash), id:id }; + return; + } + + // Team already found. If this one has better access rights, keep it. + // Otherwise, delete it + + // No edit right or we already have edit rights? delete + if (!t.hash || _t.edit) { + delete teams[id]; + return; + } + + // We didn't have edit rights and now we have them: replace + delete teams[_t.id]; + _teams[t.channel] = { edit: Boolean(t.hash), id:id }; + }); + // Load teams Object.keys(teams).forEach(function (id) { ctx.onReadyHandlers[id] = []; if (!Util.find(teams, [id, 'keys', 'mailbox'])) { From 79378c536ee92fbaf384c2434830fc56020d61ea Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Oct 2020 14:17:53 +0200 Subject: [PATCH 2/8] Fix duplicate team script to support owners --- www/common/outer/team.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 4ecfc09b6..f340c156e 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -1676,22 +1676,22 @@ define([ // Not found yet? add to the list if (!_t) { - _teams[t.channel] = { edit: Boolean(t.hash), id:id }; + _teams[t.channel] = { edit: Boolean(t.hash), owner: t.owner, id:id }; return; } // Team already found. If this one has better access rights, keep it. // Otherwise, delete it - // No edit right or we already have edit rights? delete - if (!t.hash || _t.edit) { + // No edit right or we already had edit rights? delete + if (!t.hash || (!t.owner && _t.edit) || _t.owner) { delete teams[id]; return; } // We didn't have edit rights and now we have them: replace delete teams[_t.id]; - _teams[t.channel] = { edit: Boolean(t.hash), id:id }; + _teams[t.channel] = { edit: Boolean(t.hash), owner: t.owner, id:id }; }); // Load teams From abeadb59e1a034bb91657bcedc4bac3b07064c9c Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Oct 2020 16:02:53 +0200 Subject: [PATCH 3/8] Fix a race condition that could break team access rights --- www/common/outer/team.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/www/common/outer/team.js b/www/common/outer/team.js index f340c156e..7370b1589 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -1122,6 +1122,20 @@ define([ var onReady = ctx.onReadyHandlers[teamId]; var team = ctx.teams[teamId]; + if (teamData.channel !== data.channel || teamData.password !== data.password) { return void cb(false); } + + // Update our proxy + if (state) { + teamData.hash = data.hash; + teamData.keys.drive.edPrivate = data.keys.drive.edPrivate; + teamData.keys.chat.edit = data.keys.chat.edit; + } else { + delete teamData.hash; + delete teamData.keys.drive.edPrivate; + delete teamData.keys.chat.edit; + } + + // Team not ready yet: try again onReady if (!team && Array.isArray(onReady)) { onReady.push({ cb: function () { @@ -1131,14 +1145,11 @@ define([ return; } + // No team and not initialized at all... if (!team) { return void cb(false); } - if (teamData.channel !== data.channel || teamData.password !== data.password) { return void cb(false); } - + // Team is initialized and ready: update the loaded elements if (state) { - teamData.hash = data.hash; - teamData.keys.drive.edPrivate = data.keys.drive.edPrivate; - teamData.keys.chat.edit = data.keys.chat.edit; initRpc(ctx, team, teamData.keys.drive, function () { team.manager.addPin(team.pin, team.unpin); }); @@ -1149,9 +1160,6 @@ define([ var crypto = Crypto.createEncryptor(secret.keys); team.listmap.setReadOnly(false, crypto); } else { - delete teamData.hash; - delete teamData.keys.drive.edPrivate; - delete teamData.keys.chat.edit; delete team.secondaryKey; if (team.rpc && team.rpc.destroy) { team.rpc.destroy(); From 98579cfa250a0de13777936b2de88cec9a5c624a Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 20 Oct 2020 14:02:35 +0200 Subject: [PATCH 4/8] Fix team access rights --- www/common/outer/roster.js | 2 ++ www/common/outer/team.js | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/www/common/outer/roster.js b/www/common/outer/roster.js index 1db1bf5c7..fa3a700e2 100644 --- a/www/common/outer/roster.js +++ b/www/common/outer/roster.js @@ -67,6 +67,8 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { if (authorRole === 'OWNER') { return true; } // admins can add other admins or members or viewers if (authorRole === "ADMIN") { return ['ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1; } + // members can demote themselves to viewer (they can only describe themselves) + if (authorRole === "MEMBER") { return role === 'VIEWER'; } // (MEMBER, other) can't add anyone of any role return false; }; diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 7370b1589..2dacca689 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -491,6 +491,7 @@ define([ Feedback.send("ROSTER_CORRUPTED"); return; } + // Kicked from the team if (!state.members[me]) { lm.stop(); roster.stop(); @@ -499,6 +500,25 @@ define([ ctx.updateMetadata(); cb({error: 'EFORBIDDEN'}); waitFor.abort(); + return; + } + // Check access rights + // If we're not a viewer, make sure we have edit rights + var s = state.members[me]; + if (!teamData.hash && ['ADMIN', 'MEMBER'].indexOf(s.role) !== -1) { + console.warn("Missing edit rights: demote to viewer"); + var data = {}; + data[ctx.store.proxy.curvePublic] = { + role: "VIEWER" + }; + roster.describe(data, function (err) { + Feedback.send("TEAM_RIGHTS_FIXED"); + if (!err) { return; } + if (err === 'NO_CHANGE') { return; } + console.error(err); + }); + } else if (!teamData.hash && s.role === "OWNER") { + Feedback.send("TEAM_RIGHTS_OWNER"); } }).nThen(function () { onReady(ctx, id, lm, roster, keys, null, cb); @@ -1690,14 +1710,17 @@ define([ // Team already found. If this one has better access rights, keep it. // Otherwise, delete it + ctx.store.proxy.duplicateTeams = ctx.store.proxy.duplicateTeams || {}; // No edit right or we already had edit rights? delete if (!t.hash || (!t.owner && _t.edit) || _t.owner) { + ctx.store.proxy.duplicateTeams[id] = teams[id]; delete teams[id]; return; } // We didn't have edit rights and now we have them: replace + ctx.store.proxy.duplicateTeams[_t.id] = teams[_t.id]; delete teams[_t.id]; _teams[t.channel] = { edit: Boolean(t.hash), owner: t.owner, id:id }; }); From 7b989784b84504a4fff572d0fd9e58fd89e1e888 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 20 Oct 2020 15:02:36 +0200 Subject: [PATCH 5/8] Make sure team MEMBERS can't invite VIEWERS --- www/common/outer/roster.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/www/common/outer/roster.js b/www/common/outer/roster.js index fa3a700e2..2c2675037 100644 --- a/www/common/outer/roster.js +++ b/www/common/outer/roster.js @@ -56,6 +56,16 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { return ['OWNER', 'ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1; }; + var isSelfDowngrade = function (author, curve, role, state) { + // Make sure you want to describe yourself + var selfDescribe = author === curve && state[curve]; + if (!selfDescribe) { return false; } + // ADMIN and OWNER can always update roles + // we only need to allow MEMBER to downgrade themselves to VIEWER + var authorRole = Util.find(state, [author, 'role']); + if (authorRole === "MEMBER") { return role === 'VIEWER'; } + }; + var canAddRole = function (author, role, members) { var authorRole = Util.find(members, [author, 'role']); if (!authorRole) { return false; } @@ -67,8 +77,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { if (authorRole === 'OWNER') { return true; } // admins can add other admins or members or viewers if (authorRole === "ADMIN") { return ['ADMIN', 'MEMBER', 'VIEWER'].indexOf(role) !== -1; } - // members can demote themselves to viewer (they can only describe themselves) - if (authorRole === "MEMBER") { return role === 'VIEWER'; } // (MEMBER, other) can't add anyone of any role return false; }; @@ -246,7 +254,10 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { if (typeof(data.role) === 'string') { // they're trying to change the role... // throw if they're trying to upgrade to something greater - if (!canAddRole(author, data.role, members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); } + if (!isSelfDowngrade(author, curve, data.role, members) && + !canAddRole(author, data.role, members)) { + throw new Error("INSUFFICIENT_PERMISSIONS"); + } } // DESCRIBE commands must initialize a displayName if it isn't already present if (typeof(current.displayName) !== 'string' && typeof(data.displayName) !== 'string') { From a2523cd3d468955dacee5b4c38b6d7a117a7de96 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 20 Oct 2020 15:44:19 +0200 Subject: [PATCH 6/8] Exclude acconts app from the new pad menu --- www/common/common-ui-elements.js | 3 ++- www/common/drive-ui.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index f4630ae17..8b9415220 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1239,8 +1239,8 @@ define([ } else if (!plan) { // user is logged in and subscriptions are allowed // and they don't have one. show upgrades - makeDonateButton(); makeUpgradeButton(); + makeDonateButton(); } else { // they have a plan. show nothing } @@ -1934,6 +1934,7 @@ define([ if (p === 'contacts') { return; } if (p === 'todo') { return; } if (p === 'file') { return; } + if (p === 'accounts') { return; } if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes && AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; } return true; diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 6e2b29b7b..649b5fe86 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -2520,6 +2520,7 @@ define([ if (type === 'contacts') { return; } if (type === 'todo') { return; } if (type === 'file') { return; } + if (type === 'accounts') { return; } if (!APP.loggedIn && AppConfig.registeredOnlyTypes && AppConfig.registeredOnlyTypes.indexOf(type) !== -1) { return; From 66e04f620450948eede3b50a445dfa2209d1c930 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 22 Oct 2020 12:09:48 +0200 Subject: [PATCH 7/8] Improve script to remote duplicate teams --- www/common/outer/team.js | 95 +++++++++++++++++++---------- www/common/sframe-common-history.js | 2 +- 2 files changed, 65 insertions(+), 32 deletions(-) diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 7d85cea1f..a206b07dc 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -505,7 +505,8 @@ define([ // Check access rights // If we're not a viewer, make sure we have edit rights var s = state.members[me]; - if (!teamData.hash && ['ADMIN', 'MEMBER'].indexOf(s.role) !== -1) { + var teamEdPrivate = Util.find(teamData, ['keys', 'drive', 'edPrivate']); + if ((!teamData.hash || !teamEdPrivate) && ['ADMIN', 'MEMBER'].indexOf(s.role) !== -1) { console.warn("Missing edit rights: demote to viewer"); var data = {}; data[ctx.store.proxy.curvePublic] = { @@ -513,11 +514,15 @@ define([ }; roster.describe(data, function (err) { Feedback.send("TEAM_RIGHTS_FIXED"); + // Make sure we've removed all the keys + delete teamData.hash; + delete teamData.keys.drive.edPrivate; + delete teamData.keys.chat.edit; if (!err) { return; } if (err === 'NO_CHANGE') { return; } console.error(err); }); - } else if (!teamData.hash && s.role === "OWNER") { + } else if ((!teamData.hash || !teamEdPrivate) && s.role === "OWNER") { Feedback.send("TEAM_RIGHTS_OWNER"); } }).nThen(function () { @@ -1696,33 +1701,71 @@ define([ updateMyRights(ctx, p[1]); }); + var checkKeyPair = function (edPrivate, edPublic) { + if (!edPrivate || !edPublic) { return true; } + try { + var secretKey = Nacl.util.decodeBase64(edPrivate); + var pair = Nacl.sign.keyPair.fromSecretKey(secretKey); + return Nacl.util.encodeBase64(pair.publicKey) === edPublic; + } catch (e) { + return false; + } + }; + // Remove duplicate teams var _teams = {}; Object.keys(teams).forEach(function (id) { - var t = teams[id]; - var _t = _teams[t.channel]; + try { + var t = teams[id]; + var _t = _teams[t.channel]; + + var edPrivate = Util.find(t, ['keys', 'drive', 'edPrivate']); + var edPublic = Util.find(t, ['keys', 'drive', 'edPublic']); + + // If the edPrivate is corrupted, remove it + if (!edPublic) { + Feedback.send("TEAM_CORRUPTED_EDPUBLIC"); + } else if (edPrivate && edPublic && !checkKeyPair(edPrivate, edPublic)) { + Feedback.send("TEAM_CORRUPTED_EDPUBLIC"); + delete teams[id].keys.drive.edPrivate; + edPrivate = undefined; + } - // Not found yet? add to the list - if (!_t) { - _teams[t.channel] = { edit: Boolean(t.hash), owner: t.owner, id:id }; - return; - } + // If the hash is corrupted, feedback + if (t.hash) { + var parsed = Hash.parseTypeHash('drive', t.hash); + if (parsed.version === 2 && t.hash.length !== 40) { + Feedback.send("TEAM_CORRUPTED_HASH"); + // FIXME ? + } + } + + // Not found yet? add to the list + if (!_t) { + _teams[t.channel] = id; + return; + } - // Team already found. If this one has better access rights, keep it. - // Otherwise, delete it - ctx.store.proxy.duplicateTeams = ctx.store.proxy.duplicateTeams || {}; + // Duplicate found: update our team to add missing data + var best = teams[_t]; // This is a proxy! + var bestPrivate = Util.find(best, ['keys', 'drive', 'edPrivate']); + var bestChat = Util.find(best, ['keys', 'chat', 'edit']); + var chat = Util.find(t, ['keys', 'chat', 'edit']); + if (!best.hash && t.hash) { + best.hash = t.hash; + } + if (!bestPrivate && edPrivate) { + best.keys.drive.edPrivate = edPrivate; + } + if (!bestChat && chat) { + best.keys.chat.edit = chat; + } - // No edit right or we already had edit rights? delete - if (!t.hash || (!t.owner && _t.edit) || _t.owner) { + // Deprecate the duplicate + ctx.store.proxy.duplicateTeams = ctx.store.proxy.duplicateTeams || {}; ctx.store.proxy.duplicateTeams[id] = teams[id]; delete teams[id]; - return; - } - - // We didn't have edit rights and now we have them: replace - ctx.store.proxy.duplicateTeams[_t.id] = teams[_t.id]; - delete teams[_t.id]; - _teams[t.channel] = { edit: Boolean(t.hash), owner: t.owner, id:id }; + } catch (e) { console.error(e); } }); // Load teams @@ -1740,16 +1783,6 @@ define([ team.getTeam = function (id) { return ctx.teams[id]; }; - var checkKeyPair = function (edPrivate, edPublic) { - if (!edPrivate || !edPublic) { return true; } - try { - var secretKey = Nacl.util.decodeBase64(edPrivate); - var pair = Nacl.sign.keyPair.fromSecretKey(secretKey); - return Nacl.util.encodeBase64(pair.publicKey) === edPublic; - } catch (e) { - return false; - } - }; team.getTeamsData = function (app) { var t = {}; var safe = false; diff --git a/www/common/sframe-common-history.js b/www/common/sframe-common-history.js index 9362c7899..c0c8ef678 100644 --- a/www/common/sframe-common-history.js +++ b/www/common/sframe-common-history.js @@ -254,7 +254,7 @@ define([ // goal of having snapshots if (config.getLastMetadata) { var metadataMgr = common.getMetadataMgr(); - var lastMd = config.getLastMetadata(); + var lastMd = config.getLastMetadata() || {}; var _snapshots = lastMd.snapshots; var _users = lastMd.users; var md = Util.clone(metadataMgr.getMetadata()); From 7763f966c62cbf234a2b3cc942ad379a90b7747f Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 22 Oct 2020 12:14:52 +0200 Subject: [PATCH 8/8] Fix feedback --- www/common/outer/team.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/outer/team.js b/www/common/outer/team.js index a206b07dc..e5a40505e 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -1726,7 +1726,7 @@ define([ if (!edPublic) { Feedback.send("TEAM_CORRUPTED_EDPUBLIC"); } else if (edPrivate && edPublic && !checkKeyPair(edPrivate, edPublic)) { - Feedback.send("TEAM_CORRUPTED_EDPUBLIC"); + Feedback.send("TEAM_CORRUPTED_EDPRIVATE"); delete teams[id].keys.drive.edPrivate; edPrivate = undefined; }