diff --git a/lib/commands/moderators.js b/lib/commands/moderators.js index 3a90ad2ee..60dbb9e33 100644 --- a/lib/commands/moderators.js +++ b/lib/commands/moderators.js @@ -25,7 +25,9 @@ Moderators.add = (Env, edPublic, data, adminKey, _cb) => { const safeKey = Util.escapeKeyCharacters(edPublic); Moderator.write(Env, safeKey, data, (err) => { if (err) { return void cb(err); } - Env.moderators.push(edPublic); + if (!Env.moderators.includes(edPublic)) { + Env.moderators.push(edPublic); + } Env.envUpdated.fire(); cb(); }); @@ -33,9 +35,14 @@ Moderators.add = (Env, edPublic, data, adminKey, _cb) => { Moderators.delete = (Env, id, _cb) => { const cb = Util.once(Util.mkAsync(_cb)); - Moderator.delete(Env, id, (err) => { + const safeKey = Util.escapeKeyCharacters(id); + Moderator.delete(Env, safeKey, (err) => { if (err && err !== 'ENOENT') { return void cb(err); } - // XXX update Env.moderators + let idx = Env.moderators.indexOf(id); + if (idx !== -1) { + Env.moderators.splice(idx, 1); + Env.envUpdated.fire(); + } cb(void 0, true); }); }; diff --git a/lib/decrees.js b/lib/decrees.js index b51053130..75d7740bb 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -40,7 +40,7 @@ SET_MAINTENANCE // EASIER CONFIG SET_ADMIN_EMAIL SET_SUPPORT_MAILBOX -SET_SUPPORT_MAILBOX2 +SET_SUPPORT_KEYS // COMMUNITY PARTICIPATION AND GOVERNANCE CONSENT_TO_CONTACT diff --git a/www/admin/inner.js b/www/admin/inner.js index 5b5bd5623..855e7575c 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -2926,12 +2926,13 @@ Example Messages.admin_supportNewConfirm = "Are you sure? This will remove access to all current moderators and delete all existing tickets."; Messages.admin_supportMembers = "Current support team"; Messages.admin_supportAdd = "Add a contact to the support team"; + Messages.admin_supportRotateNotify = "Warning: new keys have been generated but an unenexpected error prevented the system to send them to the moderators. You may have to remove and re-add all the other moderators"; create['support-new'] = function () { const $div = makeBlock('support-new'); // Msg.admin_supportNewHint, .admin_supportNewTitle let supportKey = ApiConfig.supportMailboxKey; let edPublic = common.getMetadataMgr().getPrivateData().edPublic; // My edPublic let refresh = function () {}; - const redraw = function (membersData, oldPrivKey) { + const redraw = function (moderatorsData, oldPrivKey) { $div.empty(); const state = h('div'); @@ -2986,7 +2987,7 @@ Example spinner.done(); UI.log(Messages.saved); supportKey = undefined; - setState(); + refresh(); }); }); }); @@ -3006,7 +3007,7 @@ Example }; console.log(oldPrivKey, getEncryptor(oldPrivKey)); */ - const getMemberData = (curve) => { + const getContactData = (curve) => { let friends = common.getFriends(true); let f = friends[curve || 'me']; return { @@ -3019,17 +3020,12 @@ Example }; }; const generateKey = function () { - if (supportKey && !membersData[edPublic]) { + if (supportKey && !moderatorsData[edPublic]) { UI.alert("A support key already exists. You must be a moderator to generate a new one or delete the existing support data."); return; } spinner.spin(); $button.attr('disabled', 'disabled'); - const keyPair = Nacl.box.keyPair(); - const pub = Nacl.util.encodeBase64(keyPair.publicKey); - const priv = Nacl.util.encodeBase64(keyPair.secretKey); - const ed = Nacl.sign.keyPair.fromSeed(keypair.secretKey); - const edPub = Nacl.util.encodeBase64(ed.publicKey); const onError = (waitFor, err, res) => { if (waitFor) { waitFor.abort(); } console.error(err, res); @@ -3041,46 +3037,36 @@ Example //const newCrypto = getEncryptor(priv); nThen((waitFor) => { - // Send new key to server - sFrameChan.query('Q_ADMIN_RPC', { - cmd: 'ADMIN_DECREE', - data: ['SET_SUPPORT_MAILBOX2', [pub, edPub]] - }, waitFor((e, response) => { - $button.removeAttr('disabled'); - if (e || response.error) { return void onError(waitFor, e, response); } - })); - }).nThen((waitFor) => { - // Add to own mailbox - sFrameChan.query("Q_ADMIN_MAILBOX", { - version: 2, - priv: priv - }, waitFor((err, obj) => { - if (err || (obj && obj.error)) { return void onError(waitFor, err, obj); } - - spinner.done(); - UI.log(Messages.saved); - supportKey = pub; - setState(); - })); - }).nThen(() => { // Add myself to moderator role if not already there + let me = getContactData(); + if (moderatorsData[me.edPublic]) { return; } sFrameChan.query('Q_ADMIN_RPC', { cmd: 'ADD_MODERATOR', - data: getMemberData() - }, (e, response) => { + data: me + }, waitFor((e, response) => { console.error(e, response); - }); + })); + }).nThen((waitFor) => { + // Copy chainpad doc and pin + APP.supportModule.execCommand('ROTATE_KEYS', {}, waitFor((obj) => { + $button.removeAttr('disabled'); + if (obj && obj.error) { return void onError(waitFor, obj.error); } + if (obj && obj.success && obj.noNotify) { + UI.alert(Messages.admin_supportRotateNotify); + onError(waitFor, 'NOTIFY_ERROR', obj); + return; + } + spinner.done(); + UI.log(Messages.saved); + })); }).nThen(() => { - // XXX migrate chainpad doc - // XXX delete old pin log - // XXX send new keys to members - console.error('XXX TODO send to members'); + refresh(); }); }; - const addSupportMember = (curve, _cb) => { + const addModerator = (curve, _cb) => { let cb = Util.mkAsync(_cb); - let userData = getMemberData(curve); + let userData = getContactData(curve); if (!userData) { return void cb('INVALID_USER'); } sFrameChan.query('Q_ADMIN_RPC', { cmd: 'ADD_MODERATOR', @@ -3097,6 +3083,18 @@ Example }); }); }; + const removeModerator = (ed) => { + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'REMOVE_MODERATOR', + data: ed + }, (e, response) => { + if (e || (response && response.error)) { + console.error(e || response.error); + return void UI.warn(Messages.error); + } + generateKey(); + }); + }; Util.onClickEnter($button, function () { /* @@ -3109,12 +3107,12 @@ Example generateKey(); }); - const drawMembers = () => { + const drawModerators = () => { if (!supportKey) { return; } const members = {}; const friends = Util.clone(common.getFriends(false)) - Object.keys(membersData).forEach((ed) => { - let m = membersData[ed]; + Object.keys(moderatorsData).forEach((ed) => { + let m = moderatorsData[ed]; members[m.curvePublic] = { displayName: m.name, edPublic: m.edPublic, @@ -3133,7 +3131,8 @@ Example noSelect: true, data: members, remove: (el) => { - console.error('REMOVE', el); + let ed = $(el).attr('data-ed'); + removeModerator(ed); } }); let contactsGrid = UIElements.getUserGrid(Messages.admin_supportAdd, { @@ -3153,14 +3152,14 @@ Example console.error('Missing data on selected user', el); return void UI.warn(Messages.error); } - addSupportMember(curve, waitFor()); + addModerator(curve, waitFor()); }); }).nThen(() => { refresh(); }); }); // Only moderators can add new moderators - if (!membersData[edPublic]) { + if (!moderatorsData[edPublic]) { contactsGrid.div = undefined; addBtn = undefined; } @@ -3172,13 +3171,14 @@ Example ]); $div.append(list); }; - drawMembers(); + drawModerators(); }; refresh = () => { - let oldKey, members; + let oldKey, moderators; nThen((waitFor) => { APP.supportModule.execCommand('GET_PRIVATE_KEY', {}, waitFor((obj) => { oldKey = obj && obj.curvePrivate; + supportKey = obj && obj.curvePublic; })); }).nThen((waitFor) => { sFrameChan.query('Q_ADMIN_RPC', { @@ -3190,10 +3190,10 @@ Example UI.warn(Messages.error); return; } - members = response[0]; + moderators = response[0]; })); }).nThen(() => { - redraw(members, oldKey); + redraw(moderators, oldKey); }); }; refresh(); diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 05aa23ebb..e9a5cc720 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -913,17 +913,18 @@ define([ var msg = data.msg; var content = msg.content; var newKey = content.supportKey; - // check if it matches the server key - var pub = Hash.getBoxPublicFromSecret(newKey); - if (pub !== ApiConfig.supportMailboxKey) { return void cb(true); } - // We have a correct key: add support mailbox - ctx.Store.addAdminMailbox(null, { - version: 2, - priv: newKey, - lastKnownHash: content.lastKnownHash - }, function (err) { - if (err) { return void cb(true); } - cb(false); + + var support = Util.find(ctx, ['store', 'modules', 'support']); + support.updateAdminKey(content, cb); + }; + handlers['MODERATOR_NEW_KEY'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + var newKey = content.supportKey; + + var support = Util.find(ctx, ['store', 'modules', 'support']); + support.updateAdminKey(content, function () { + cb(true); // Always dismiss, this should be invisible }); }; diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index c3eb249dc..ea5db15ef 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -276,6 +276,7 @@ proxy.mailboxes = { var leaveChannel = function (ctx, type, cb) { + cb = cb || function () {}; var box = ctx.boxes[type]; if (!box) { return void cb(); } if (!box.cpNf || typeof(box.cpNf.stop) !== "function") { return void cb('EINVAL'); } diff --git a/www/common/outer/support.js b/www/common/outer/support.js index e3e2cac1b..5b514e258 100644 --- a/www/common/outer/support.js +++ b/www/common/outer/support.js @@ -8,21 +8,26 @@ define([ '/common/common-hash.js', '/common/common-realtime.js', '/common/pinpad.js', + '/common/cryptget.js', '/components/nthen/index.js', '/components/chainpad-crypto/crypto.js', 'chainpad-listmap', '/components/chainpad/chainpad.dist.js', 'chainpad-netflux' -], function (ApiConfig, Util, Hash, Realtime, Pinpad, nThen, Crypto, Listmap, ChainPad, CpNetflux) { +], function (ApiConfig, Util, Hash, Realtime, Pinpad, Crypt, + nThen, Crypto, Listmap, ChainPad, CpNetflux) { var Support = {}; + var Nacl = Crypto.Nacl; + // UTILS var getKeys = function (ctx, isAdmin, data, _cb) { var cb = Util.mkAsync(_cb); if (isAdmin && !ctx.adminRdyEvt) { return void cb('EFORBIDDEN'); } require(['/api/config?' + (+new Date())], function (NewConfig) { - ctx.moderatorKeys = NewConfig.moderatorKeys; // Update admin keys // XXX MODERATOR + ctx.moderatorKeys = NewConfig.moderatorKeys; // Update moderator keys + ctx.adminKeys = NewConfig.adminKeys; // Update moderator keys var supportKey = NewConfig.supportMailboxKey; if (!supportKey) { return void cb('E_NOT_INIT'); } @@ -379,31 +384,44 @@ define([ if (!ctx.adminRdyEvt) { return void cb(false); } // XXX not an admin, delete mailbox? ctx.adminRdyEvt.reg(() => { - // random timeout to avoid duplication wiht multiple admins - var rdmTo = Math.floor(Math.random() * 2000); // Between 0 and 2000ms - setTimeout(() => { - var doc = ctx.adminDoc.proxy; - if (doc.tickets.active[data.channel] || doc.tickets.closed[data.channel] - || doc.tickets.pending[data.channel]) { - return void cb(false); } - doc.tickets.active[data.channel] = { - title: data.title, - premium: data.premium, - time: data.time, - author: data.user && data.user.displayName, - authorKey: data.user && data.user.curvePublic - }; - Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () { - cb(false); + let supportKey; + nThen((waitFor) => { + // Send ticket to the admins and call back + getKeys(ctx, false, data, waitFor((err, obj) => { + if (err) { + waitFor.abort(); + return void cb({error: err}); + } + supportKey = obj.theirPublic; + })); + }).nThen(() => { + // random timeout to avoid duplication wiht multiple admins + var rdmTo = Math.floor(Math.random() * 2000); // Between 0 and 2000ms + setTimeout(() => { + var doc = ctx.adminDoc.proxy; + if (doc.tickets.active[data.channel] || doc.tickets.closed[data.channel] + || doc.tickets.pending[data.channel]) { + return void cb(false); } + doc.tickets.active[data.channel] = { + title: data.title, + premium: data.premium, + time: data.time, + author: data.user && data.user.displayName, + supportKey: supportKey, // Store current support key + authorKey: data.user && data.user.curvePublic + }; + Realtime.whenRealtimeSyncs(ctx.adminDoc.realtime, function () { + cb(false); + }); + notifyClient(ctx, true, 'NEW_TICKET', data.channel); + if (ctx.supportRpc) { ctx.supportRpc.pin([data.channel], () => {}); } }); - notifyClient(ctx, true, 'NEW_TICKET', data.channel); - if (ctx.adminRpc) { ctx.adminRpc.pin([data.channel], () => {}); } }); }); }; var updateAdminTicket = function (ctx, data) { // Wait for the chainpad to be ready before adding the data - if (!ctx.adminRdyEvt) { return void cb(false); } // XXX not an admin, delete mailbox? + if (!ctx.adminRdyEvt) { return; } // XXX not an admin, delete mailbox? ctx.adminRdyEvt.reg(() => { // random timeout to avoid duplication wiht multiple admins @@ -442,10 +460,52 @@ define([ } }; + var updateAdminKey = (ctx, data, cb) => { + let newKey = data.supportKey; + let newKeyPub = Hash.getBoxPublicFromSecret(newKey); + + let proxy = ctx.store.proxy; + const oldKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']); + const oldKeyPub = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']); + + getKeys(ctx, false, {}, (err, obj) => { + if (err) { return void cb(true); } + // already deprecated? abort + if (newKeyPub !== obj.theirPublic) { return void cb(true); } + // already known? abort + if (oldKey === newKey || oldKeyPub === newKeyPub) { return void cb(true); } + + // Close old data + let mailbox = Util.find(ctx, [ 'store', 'mailbox' ]); + try { + if (ctx.adminDoc) { ctx.adminDoc.stop(); } + if (mailbox) { mailbox.close('supportteam'); } + if (ctx.supportRpc) { ctx.supportRpc.destroy(); } + ctx.adminRdyEvt = Util.mkEvent(true); + } catch (e) { console.error(e); } + + // Store new key + ctx.Store.addAdminMailbox(null, { + version: 2, + priv: newKey + }, (obj) => { + if (obj && obj.error) { return void cb(true); } + // Reload moderator data + nThen((waitFor) => { + initializeSupportAdmin(ctx, true, waitFor); + ctx.adminRdyEvt.reg(() => { + notifyClient(ctx, true, 'UPDATE_RIGHTS'); + cb(false); + }); + }); + }); + }); + }; + // INITIALIZE ADMIN var getPinList = function (ctx) { - if (!ctx.adminDoc || !ctx.adminRpc) { return; } + if (!ctx.adminDoc || !ctx.supportRpc) { return; } let adminChan = ctx.adminDoc.metadata && ctx.adminDoc.metadata.channel; let doc = ctx.adminDoc.proxy; let t = doc.tickets; @@ -459,7 +519,6 @@ define([ }; var initAdminRpc = function (ctx, _cb) { let cb = Util.mkAsync(_cb); - let Nacl = Crypto.Nacl; let proxy = ctx.store.proxy; let curvePrivate = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']); if (!curvePrivate) { return void cb('EFORBIDDEN'); } @@ -477,7 +536,7 @@ define([ }, (e, call) => { if (e) { return void cb(e); } console.log("Support RPC ready, public key is ", edPublic); - ctx.adminRpc = call; + ctx.supportRpc = call; cb(); }); }; @@ -507,14 +566,14 @@ define([ ctx.adminRdyEvt.fire(); cb(); - if (!ctx.adminRpc) { return; } + if (!ctx.supportRpc) { return; } // Check pin list let list = getPinList(ctx); let local = Hash.hashChannelList(list); - ctx.adminRpc.getServerHash(function (e, hash) { + ctx.supportRpc.getServerHash(function (e, hash) { if (e) { return void console.warn(e); } if (hash !== local) { - ctx.adminRpc.reset(list, function (e, hash) { + ctx.supportRpc.reset(list, function (e, hash) { if (e) { console.warn(e); } }); } @@ -523,19 +582,22 @@ define([ }; - var initializeSupportAdmin = function (ctx, waitFor) { + var initializeSupportAdmin = function (ctx, isReset, waitFor) { let unlock = waitFor(); let proxy = ctx.store.proxy; let supportKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']); let privateKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']); - ctx.adminRdyEvt = Util.mkEvent(true); + if (!isReset) { ctx.adminRdyEvt = Util.mkEvent(true); } nThen((waitFor) => { getKeys(ctx, false, {}, waitFor((err, obj) => { setTimeout(unlock); // Unlock loading process if (err) { return void waitFor.abort(); } if (obj.theirPublic !== supportKey) { - // Deprecated support key: no longer an admin! - // XXX delete the mailbox? + try { + delete proxy.mailboxes.supportteam; + ctx.store.mailbox.close('supportteam'); + } catch (e) {} + delete ctx.adminRdyEvt; return void waitFor.abort(); } })); @@ -559,6 +621,182 @@ define([ }; + let updateServerKey = (ctx, curvePublic, curvePrivate, cb) => { + let edPrivate, edPublic + try { + let pair = Nacl.sign.keyPair.fromSeed(Nacl.util.decodeBase64(curvePrivate)); + edPublic = Nacl.util.encodeBase64(pair.publicKey); + } catch (e) { + return void cb(e); + } + ctx.Store.adminRpc(null, { + cmd: 'ADMIN_DECREE', + data: ['SET_SUPPORT_KEYS', [curvePublic, edPublic]] + }, cb); + }; + let getModerators = (ctx, data, cId, cb) => { + ctx.Store.adminRpc(null, { + cmd: 'GET_MODERATORS', + data: {} + }, cb); + }; + let rotateKeys = function (ctx, data, cId, _cb) { + let cb = Util.once(Util.mkAsync(_cb)); + let oldSupportKey; + let proxy = ctx.store.proxy; + let edPublic = proxy.edPublic; + + const keyPair = Nacl.box.keyPair(); + const newKeyPub = Nacl.util.encodeBase64(keyPair.publicKey); + const newKey = Nacl.util.encodeBase64(keyPair.secretKey); + const newEd = Nacl.sign.keyPair.fromSeed(keyPair.secretKey); + const newEdPub = Nacl.util.encodeBase64(newEd.publicKey); + + const oldKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']); + const oldKeyPub = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']); + + if (!newKey || !newKeyPub) { return void cb({ error: 'INVALID_KEY' }); } + let oldAdminChan; + nThen((waitFor) => { + // Check if support was already enabled + getKeys(ctx, false, {}, waitFor((err, obj) => { + if (err) { + cb({error: err}); + return void waitFor.abort(); + } + oldSupportKey = obj.theirPublic; + })); + }).nThen((waitFor) => { + // Only admins can rotate the keys + if (!ctx.adminKeys.includes(edPublic)) { + waitFor.abort(); + return void cb({error: 'EFORBIDDEN'}); + } + }).nThen((waitFor) => { + // If support is enabled, only current moderators can rotate keys. + // Other admins can only delete the support + if (!oldSupportKey) { return; } // support disabled + if (!ctx.moderatorKeys.includes(edPublic)) { + waitFor.abort(); + return void cb({error: 'EINVAL'}); + } + if (oldKeyPub !== oldSupportKey) { + waitFor.abort(); + return void cb({error: 'EFORBIDDEN'}); + } + }).nThen((waitFor) => { + // If support was enabled, migrate old chainpad + if (!oldSupportKey) { return; } // No old doc to copy + ctx.adminRdyEvt.reg(() => { + let oldDoc = ctx.adminDoc.proxy; + oldAdminChan = ctx.adminDoc.metadata && ctx.adminDoc.metadata.channel; + + let seed = newKey.slice(0,24); + let hash = Hash.getEditHashFromKeys({ + version: 2, + type: 'support', + keys: { + editKeyStr: seed + } + }); + let cfg = { + network: ctx.store.network, + initialState: '{}' + }; + var oldKeys = oldDoc.oldKeys = oldDoc.oldKeys || {}; + oldKeys[oldKeyPub] = { + curvePrivate: oldKey, + rotatedOn: +new Date(), + rotatedBy: edPublic + }; + Crypt.put(hash, JSON.stringify(oldDoc), waitFor((err) => { + if (err) { + waitFor.abort(); + return void cb({error: err}); + } + }), cfg); + }); + }).nThen((waitFor) => { + // Send new key to server + updateServerKey(ctx, newKeyPub, newKey, waitFor((obj) => { + if (obj && obj.error) { + waitFor.abort(); + return void cb(obj); + } + })); + }).nThen(() => { + // From now on, each error may cause issue because + // the server has already stored the new key + + // Disconnect old doc and mailbox + if (!oldSupportKey) { return; } // support disabled + + if (ctx.adminDoc) { ctx.adminDoc.stop(); } + + let mailbox = Util.find(ctx, [ 'store', 'mailbox' ]); + if (mailbox) { mailbox.close('supportteam'); } + + if (ctx.supportRpc) { ctx.supportRpc.destroy(); } + + ctx.adminRdyEvt = Util.mkEvent(true); + }).nThen((waitFor) => { + // Add key to my proxy + ctx.Store.addAdminMailbox(null, { + version: 2, + priv: newKey + }, waitFor((obj) => { + if (obj && obj.error) { // Should never happen with the previous checks + waitFor.abort(); + if (oldSupportKey) { + // If we weren't able to store the new key, abort and restore old keys + return updateServerKey(ctx, oldKeyPub, oldKey, () => { + return void cb(obj); + }); + } + return void cb(obj); + } + })); + }).nThen((waitFor) => { + // Notify other moderators + if (!oldSupportKey) { return; } + let mailbox = Util.find(ctx, [ 'store', 'mailbox' ]); + getModerators(ctx, null, null, waitFor((obj) => { + if (obj && obj.error) { + return void cb({ + success: true, + noNotify: true + }); + } + let all = obj && obj[0]; + Object.keys(all || {}).forEach((modEdPub) => { + let modData = all[modEdPub]; + mailbox.sendTo('MODERATOR_NEW_KEY', { + supportKey: newKey + }, { + channel: modData.mailbox, + curvePublic: modData.curvePublic + }, () => {}); + }); + })); + }).nThen((waitFor) => { + // Initialize new chainpad + initializeSupportAdmin(ctx, true, waitFor); + }).nThen((waitFor) => { + // Clean old data + if (!oldAdminChan) { return; } + ctx.Store.adminRpc(null, { + cmd: 'ARCHIVE_DOCUMENT', + data: { + id: oldAdminChan, + reason: 'Deprecated support pad' + } + }, waitFor()); + }).nThen((waitFor) => { + // Call back + cb({success: true}); + }); + }; + var getAdminKey = function (ctx, data, cId, cb) { let proxy = ctx.store.proxy; let supportKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']); @@ -567,7 +805,8 @@ define([ if (err) { return void cb({error: err}); } if (obj.theirPublic !== supportKey) { return void cb({ error: 'EFORBIDDEN' }); } cb({ - curvePrivate: privateKey + curvePrivate: privateKey, + curvePublic: supportKey }); }); }; @@ -611,6 +850,7 @@ define([ var ctx = { moderatorKeys: ApiConfig.moderatorKeys, + adminKeys: ApiConfig.adminKeys, supportData: proxy, store: cfg.store, Store: cfg.Store, @@ -619,7 +859,7 @@ define([ }; if (Util.find(store, ['proxy', 'mailboxes', 'supportteam'])) { - initializeSupportAdmin(ctx, waitFor); + initializeSupportAdmin(ctx, false, waitFor); } support.ctx = ctx; @@ -635,6 +875,9 @@ define([ support.updateAdminTicket = function (content) { updateAdminTicket(ctx, content); }; + support.updateAdminKey = function (content, cb) { + updateAdminKey(ctx, content, cb); + }; support.checkAdminTicket = function (content, cb) { checkAdminTicket(ctx, content, cb); }; @@ -662,6 +905,9 @@ define([ if (cmd === 'GET_PRIVATE_KEY') { return void getAdminKey(ctx, data, clientId, cb); } + if (cmd === 'ROTATE_KEYS') { + return void rotateKeys(ctx, data, clientId, cb); + } if (cmd === 'ADD_MODERATOR') { return void addModerator(ctx, data, clientId, cb); } diff --git a/www/moderation/inner.js b/www/moderation/inner.js index 6514789bf..519c7f84c 100644 --- a/www/moderation/inner.js +++ b/www/moderation/inner.js @@ -43,7 +43,8 @@ define([ var sframeChan; var events = { 'NEW_TICKET': Util.mkEvent(), - 'UPDATE_TICKET': Util.mkEvent() + 'UPDATE_TICKET': Util.mkEvent(), + 'UPDATE_RIGHTS': Util.mkEvent() }; // XXX @@ -68,6 +69,14 @@ define([ APP.module.execCommand('LIST_TICKETS_ADMIN', { type: type }, (tickets) => { + if (tickets.error) { + if (tickets.error === 'EFORBIDDEN') { + return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden'); + } + return void UI.errorLoadingScreen(tickets.error); + } + UI.removeLoadingScreen(); + let activeForms = {}; $container.find('.cp-support-form-container').each((i, el) => { let id = $(el).attr('data-id'); @@ -203,6 +212,7 @@ define([ let _refresh = Util.throttle(refreshAll, 500); events.NEW_TICKET.reg(_refresh); events.UPDATE_TICKET.reg(_refresh); + events.UPDATE_RIGHTS.reg(_refresh); // Make sidebar layout const categories = { @@ -301,11 +311,6 @@ define([ var privateData = metadataMgr.getPrivateData(); common.setTabTitle(Messages.supportPage); - if (!Array.isArray(ApiConfig.moderatorKeys) || - !ApiConfig.moderatorKeys.includes(privateData.edPublic)) { - return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden'); - } - APP.privateKey = privateData.supportPrivateKey; APP.origin = privateData.origin; APP.readOnly = privateData.readOnly;