New support: rotate keys

This commit is contained in:
yflory 2024-02-26 19:07:39 +01:00
parent 757f30d4a1
commit 20dd15b99d
7 changed files with 364 additions and 104 deletions

View File

@ -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);
});
};

View File

@ -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

View File

@ -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();

View File

@ -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
});
};

View File

@ -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'); }

View File

@ -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);
}

View File

@ -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;