mirror of
https://github.com/cryptpad/cryptpad.git
synced 2026-09-14 11:05:41 +05:00
New support: add moderator role
This commit is contained in:
parent
a67b1ea7cc
commit
757f30d4a1
@ -14,6 +14,7 @@ const Core = require("./core");
|
||||
const Channel = require("./channel");
|
||||
const Invitation = require("./invitation");
|
||||
const Users = require("./users");
|
||||
const Moderators = require("./moderators");
|
||||
const BlockStore = require("../storage/block");
|
||||
const MFA = require("../storage/mfa");
|
||||
const ArchiveAccount = require('../archive-account');
|
||||
@ -918,6 +919,30 @@ var deleteInvitation = (Env, Server, cb, data) => {
|
||||
Invitation.delete(Env, id, cb);
|
||||
};
|
||||
|
||||
var getModerators = (Env, Server, cb) => {
|
||||
Moderators.getAll(Env, cb);
|
||||
};
|
||||
var addModerator = (Env, Server, cb, data, unsafeKey) => {
|
||||
const obj = Array.isArray(data) && data[1];
|
||||
const name = obj.name;
|
||||
const edPublic = obj.edPublic;
|
||||
const curvePublic = obj.curvePublic;
|
||||
const mailbox = obj.mailbox;
|
||||
const profile = obj.profile;
|
||||
const userData = {
|
||||
name,
|
||||
edPublic,
|
||||
curvePublic,
|
||||
mailbox,
|
||||
profile
|
||||
};
|
||||
Moderators.add(Env, edPublic, userData, unsafeKey, cb);
|
||||
};
|
||||
var removeModerator = (Env, Server, cb, data) => {
|
||||
const id = Array.isArray(data) && data[1];
|
||||
Moderators.delete(Env, id, cb);
|
||||
};
|
||||
|
||||
var commands = {
|
||||
ACTIVE_SESSIONS: getActiveSessions,
|
||||
ACTIVE_PADS: getActiveChannelCount,
|
||||
@ -985,6 +1010,10 @@ var commands = {
|
||||
ADD_KNOWN_USER: addKnownUser,
|
||||
DELETE_KNOWN_USER: deleteKnownUser,
|
||||
UPDATE_KNOWN_USER: updateKnownUser,
|
||||
|
||||
GET_MODERATORS: getModerators,
|
||||
ADD_MODERATOR: addModerator,
|
||||
REMOVE_MODERATOR: removeModerator,
|
||||
};
|
||||
|
||||
// addFirstAdmin is an anon_rpc command
|
||||
|
||||
42
lib/commands/moderators.js
Normal file
42
lib/commands/moderators.js
Normal file
@ -0,0 +1,42 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
/*jshint esversion: 6 */
|
||||
const Moderators = module.exports;
|
||||
|
||||
const Moderator = require('../storage/moderator');
|
||||
const Util = require("../common-util");
|
||||
|
||||
Moderators.getAll = (Env, cb) => {
|
||||
Moderator.getAll(Env, (err, data) => {
|
||||
if (err) { return void cb(err); }
|
||||
cb(null, data);
|
||||
});
|
||||
};
|
||||
Moderators.getKeysSync = (Env) => {
|
||||
return Moderator.getAllKeys(Env);
|
||||
};
|
||||
|
||||
Moderators.add = (Env, edPublic, data, adminKey, _cb) => {
|
||||
const cb = Util.once(Util.mkAsync(_cb));
|
||||
data.createdBy = adminKey;
|
||||
data.time = +new Date();
|
||||
const safeKey = Util.escapeKeyCharacters(edPublic);
|
||||
Moderator.write(Env, safeKey, data, (err) => {
|
||||
if (err) { return void cb(err); }
|
||||
Env.moderators.push(edPublic);
|
||||
Env.envUpdated.fire();
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
Moderators.delete = (Env, id, _cb) => {
|
||||
const cb = Util.once(Util.mkAsync(_cb));
|
||||
Moderator.delete(Env, id, (err) => {
|
||||
if (err && err !== 'ENOENT') { return void cb(err); }
|
||||
// XXX update Env.moderators
|
||||
cb(void 0, true);
|
||||
});
|
||||
};
|
||||
|
||||
@ -371,6 +371,14 @@ module.exports.create = function (config) {
|
||||
console.error("Can't parse admin keys. Please update or fix your config.js file!");
|
||||
}
|
||||
|
||||
try {
|
||||
let moderators = require('./commands/moderators').getKeysSync(Env);
|
||||
Env.moderators = moderators || [];
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error("Can't parse support keys.");
|
||||
}
|
||||
|
||||
Env.envUpdated = Util.mkEvent();
|
||||
Env.cacheFlushed = Util.mkEvent();
|
||||
|
||||
|
||||
@ -569,6 +569,7 @@ var serveConfig = makeRouteCache(function () {
|
||||
httpUnsafeOrigin: Env.httpUnsafeOrigin,
|
||||
adminEmail: Env.adminEmail,
|
||||
adminKeys: Env.admins,
|
||||
moderatorKeys: Env.moderators,
|
||||
inactiveTime: Env.inactiveTime,
|
||||
supportMailbox: Env.supportMailbox,
|
||||
supportMailboxKey: Env.supportMailboxKey,
|
||||
|
||||
@ -45,6 +45,10 @@ Basic.readDir = function (Env, path, cb) {
|
||||
if (!path) { return void pathError(cb); }
|
||||
Fs.readdir(path, cb);
|
||||
};
|
||||
Basic.readDirSync = function (Env, path, cb) {
|
||||
if (!path) { return []; }
|
||||
return Fs.readdirSync(path);
|
||||
};
|
||||
|
||||
Basic.write = function (Env, path, data, cb) {
|
||||
if (!path) { return void pathError(cb); }
|
||||
|
||||
87
lib/storage/moderator.js
Normal file
87
lib/storage/moderator.js
Normal file
@ -0,0 +1,87 @@
|
||||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const Basic = require("./basic.js");
|
||||
const Path = require("node:path");
|
||||
const nThen = require('nthen');
|
||||
const Util = require('../common-util');
|
||||
|
||||
const Moderator = module.exports;
|
||||
/* This module manages storage used to implement user management. "Known users" can
|
||||
be added here in order to store their public key, their block ID and an alias
|
||||
used to recognize them.
|
||||
*/
|
||||
|
||||
const pathFromId = function (Env, id) {
|
||||
if (!id || typeof(id) !== 'string') { return void console.error('KNWONUSER_BAD_ID', id); }
|
||||
return Path.join(Env.paths.base, "support", id.slice(0, 2), id);
|
||||
};
|
||||
|
||||
Moderator.read = function (Env, id, cb) {
|
||||
var path = pathFromId(Env, id);
|
||||
Basic.read(Env, path, (err, data) => {
|
||||
if (err) { return void cb(err.code); }
|
||||
cb(void 0, Util.tryParse(data));
|
||||
});
|
||||
};
|
||||
|
||||
Moderator.getAllKeys = function (Env) {
|
||||
let keys = [];
|
||||
let dirPath = Path.join(Env.paths.base, "support");
|
||||
try {
|
||||
let prefixes = Basic.readDirSync(Env, dirPath);
|
||||
prefixes.forEach((prefix) => {
|
||||
let dirPath2 = Path.join(Env.paths.base, "support", prefix);
|
||||
try {
|
||||
let newKeys = Basic.readDirSync(Env, dirPath2);
|
||||
keys.push(...newKeys);
|
||||
} catch (e) {}
|
||||
});
|
||||
return keys;
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
Moderator.getAll = function (Env, cb) {
|
||||
let users = {};
|
||||
nThen((waitFor) => {
|
||||
let dirPath = Path.join(Env.paths.base, "support");
|
||||
Basic.readDir(Env, dirPath, waitFor((err, prefixes) => {
|
||||
if (err && err.code === 'ENOENT') { return void cb(void 0, {}); }
|
||||
if (err) { waitFor.abort(); return void cb(err.code); }
|
||||
prefixes.forEach((prefix) => {
|
||||
var dirPath2 = Path.join(Env.paths.base, "support", prefix);
|
||||
Basic.readDir(Env, dirPath2, waitFor((err, files) => {
|
||||
if (err) { waitFor.abort(); return void cb(err.code); }
|
||||
files.forEach((id) => {
|
||||
Moderator.read(Env, id, waitFor((err, data) => {
|
||||
users[id] = data || { error: err };
|
||||
}));
|
||||
});
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}).nThen(() => {
|
||||
cb(null, users);
|
||||
});
|
||||
};
|
||||
|
||||
Moderator.write = function (Env, id, data, cb) {
|
||||
var path = pathFromId(Env, id);
|
||||
Basic.write(Env, path, JSON.stringify(data), (err) => {
|
||||
if (err) { return void cb(err.code); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
Moderator.delete = function (Env, id, cb) {
|
||||
var path = pathFromId(Env, id);
|
||||
Basic.delete(Env, path, (err) => {
|
||||
if (err) { return void cb(err.code); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -2923,22 +2923,28 @@ Example
|
||||
Messages.admin_supportNewDisabled = "Modern support system is disabled.";
|
||||
Messages.admin_supportNewInit = "Initialize support page on this instance";
|
||||
Messages.admin_supportNewDelete = "Disable support";
|
||||
Messages.admin_supportNewConfirm = "Are you sure? This will remove access to all current moderators.";
|
||||
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";
|
||||
create['support-new'] = function () {
|
||||
var $div = makeBlock('support-new'); // Msg.admin_supportNewHint, .admin_supportNewTitle
|
||||
var newSupportKey = ApiConfig.supportMailboxKey;
|
||||
(function () {
|
||||
var state = h('div');
|
||||
var $state = $(state).appendTo($div);
|
||||
var button = h('button.btn.btn-primary', Messages.admin_supportNewInit);
|
||||
var $button = $(button).appendTo($div);
|
||||
var delButton = h('button.btn.btn-danger', Messages.admin_supportNewDelete);
|
||||
var $delButton = $(delButton).appendTo($div).hide();
|
||||
var spinner = UI.makeSpinner($div);
|
||||
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) {
|
||||
$div.empty();
|
||||
|
||||
var setState = function () {
|
||||
const state = h('div');
|
||||
const $state = $(state).appendTo($div);
|
||||
const button = h('button.btn.btn-primary', Messages.admin_supportNewInit);
|
||||
const $button = $(button).appendTo($div);
|
||||
const delButton = h('button.btn.btn-danger', Messages.admin_supportNewDelete);
|
||||
const $delButton = $(delButton).appendTo($div).hide();
|
||||
const spinner = UI.makeSpinner($div);
|
||||
|
||||
const setState = function () {
|
||||
$state.html('');
|
||||
if (newSupportKey) {
|
||||
if (supportKey) {
|
||||
$button.hide();
|
||||
$delButton.show();
|
||||
return $state.append([
|
||||
@ -2954,6 +2960,7 @@ Example
|
||||
};
|
||||
setState();
|
||||
|
||||
|
||||
// XXX TODO add/remove access
|
||||
// XXX when removing access, send new private keys to everybody who should keep access AND move chainpad doc to the new one
|
||||
// ==> we'll need to delete the old chainpad doc using admin commands
|
||||
@ -2967,7 +2974,7 @@ Example
|
||||
$delButton.attr('disabled', 'disabled');
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['SET_SUPPORT_MAILBOX2', ['']]
|
||||
data: ['SET_SUPPORT_MAILBOX2', ['', '']]
|
||||
}, function (e, response) {
|
||||
$delButton.removeAttr('disabled');
|
||||
if (e || response.error) {
|
||||
@ -2978,61 +2985,218 @@ Example
|
||||
}
|
||||
spinner.done();
|
||||
UI.log(Messages.saved);
|
||||
newSupportKey = undefined;
|
||||
supportKey = undefined;
|
||||
setState();
|
||||
});
|
||||
});
|
||||
});
|
||||
var next = function () {
|
||||
|
||||
/*
|
||||
const getEncryptor = (curvePrivate) => {
|
||||
const seed = curvePrivate.slice(0,24);
|
||||
const hash = Hash.getEditHashFromKeys({
|
||||
version: 2,
|
||||
type: 'support',
|
||||
keys: {
|
||||
editKeyStr: seed
|
||||
}
|
||||
});
|
||||
const secret = Hash.getSecrets('support', hash);
|
||||
return Crypto.createEncryptor(secret.keys);
|
||||
};
|
||||
console.log(oldPrivKey, getEncryptor(oldPrivKey));
|
||||
*/
|
||||
const getMemberData = (curve) => {
|
||||
let friends = common.getFriends(true);
|
||||
let f = friends[curve || 'me'];
|
||||
return {
|
||||
name: f.displayName,
|
||||
edPublic: f.edPublic,
|
||||
curvePublic: f.curvePublic,
|
||||
mailbox: f.notifications,
|
||||
profile: f.profile
|
||||
// XXX add avatar?
|
||||
};
|
||||
};
|
||||
const generateKey = function () {
|
||||
if (supportKey && !membersData[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');
|
||||
var keyPair = Nacl.box.keyPair();
|
||||
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
|
||||
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
|
||||
var ed = Nacl.sign.keyPair.fromSeed(keypair.secretKey);
|
||||
var edPub = Nacl.util.encodeBase64(ed.publicKey);
|
||||
// Store the private key first. It won't be used until the decree is accepted.
|
||||
sFrameChan.query("Q_ADMIN_MAILBOX", {
|
||||
version: 2,
|
||||
priv: priv
|
||||
}, function (err, obj) {
|
||||
if (err || (obj && obj.error)) {
|
||||
console.error(err || obj.error);
|
||||
UI.warn(Messages.error);
|
||||
spinner.hide();
|
||||
return;
|
||||
}
|
||||
// Then send the decree
|
||||
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);
|
||||
spinner.hide();
|
||||
UI.warn(Messages.error);
|
||||
};
|
||||
|
||||
//const oldCrypto = supportKey ? getEncryptor(oldPrivKey) : undefined;
|
||||
//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]]
|
||||
}, function (e, response) {
|
||||
}, waitFor((e, response) => {
|
||||
$button.removeAttr('disabled');
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
console.error(e, response);
|
||||
spinner.hide();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
newSupportKey = pub;
|
||||
supportKey = pub;
|
||||
setState();
|
||||
//$('.cp-admin-support-init').hide();
|
||||
//APP.$rightside.append(create['support-list']());
|
||||
//APP.$rightside.append(create['support-priv']());
|
||||
}));
|
||||
}).nThen(() => {
|
||||
// Add myself to moderator role if not already there
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADD_MODERATOR',
|
||||
data: getMemberData()
|
||||
}, (e, response) => {
|
||||
console.error(e, response);
|
||||
});
|
||||
}).nThen(() => {
|
||||
// XXX migrate chainpad doc
|
||||
// XXX delete old pin log
|
||||
// XXX send new keys to members
|
||||
console.error('XXX TODO send to members');
|
||||
});
|
||||
};
|
||||
|
||||
const addSupportMember = (curve, _cb) => {
|
||||
let cb = Util.mkAsync(_cb);
|
||||
let userData = getMemberData(curve);
|
||||
if (!userData) { return void cb('INVALID_USER'); }
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADD_MODERATOR',
|
||||
data: userData
|
||||
}, (e, response) => {
|
||||
if (e || (response && response.error)) {
|
||||
return void cb(e || response.error);
|
||||
}
|
||||
|
||||
// User added to support team in database, send them the keys
|
||||
APP.supportModule.execCommand('ADD_MODERATOR', userData, (obj) => {
|
||||
// XXX IF ERROR, (can't notify) remove from DB?
|
||||
cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Util.onClickEnter($button, function () {
|
||||
if (newSupportKey) {
|
||||
/*
|
||||
if (supportKey) {
|
||||
return void UI.confirm(Messages.admin_supportNewConfirm, function (yes) {
|
||||
if (yes) { next(); }
|
||||
if (yes) { generateKey(); }
|
||||
});
|
||||
}
|
||||
next();
|
||||
*/
|
||||
generateKey();
|
||||
});
|
||||
})();
|
||||
|
||||
const drawMembers = () => {
|
||||
if (!supportKey) { return; }
|
||||
const members = {};
|
||||
const friends = Util.clone(common.getFriends(false))
|
||||
Object.keys(membersData).forEach((ed) => {
|
||||
let m = membersData[ed];
|
||||
members[m.curvePublic] = {
|
||||
displayName: m.name,
|
||||
edPublic: m.edPublic,
|
||||
profile: m.profile,
|
||||
curvePublic: m.curvePublic,
|
||||
notificatons: m.mailbox
|
||||
};
|
||||
});
|
||||
Object.keys(friends).forEach((curve) => {
|
||||
if (members[curve]) { delete friends[curve]; }
|
||||
});
|
||||
let currentList = UIElements.getUserGrid(Messages.admin_supportMembers, {
|
||||
common: common,
|
||||
list: true,
|
||||
large: true,
|
||||
noSelect: true,
|
||||
data: members,
|
||||
remove: (el) => {
|
||||
console.error('REMOVE', el);
|
||||
}
|
||||
});
|
||||
let contactsGrid = UIElements.getUserGrid(Messages.admin_supportAdd, {
|
||||
common: common,
|
||||
list: true,
|
||||
large: true,
|
||||
data: friends
|
||||
}, function () {});
|
||||
|
||||
let addBtn = h('button.btn.btn-primary', 'ADD');
|
||||
Util.onClickEnter($(addBtn), () => {
|
||||
var $sel = $(contactsGrid.div).find('.cp-usergrid-user.cp-selected');
|
||||
nThen((waitFor) => {
|
||||
$sel.each((i, el) => {
|
||||
let curve = $(el).attr('data-curve');
|
||||
if (!curve) {
|
||||
console.error('Missing data on selected user', el);
|
||||
return void UI.warn(Messages.error);
|
||||
}
|
||||
addSupportMember(curve, waitFor());
|
||||
});
|
||||
}).nThen(() => {
|
||||
refresh();
|
||||
});
|
||||
});
|
||||
// Only moderators can add new moderators
|
||||
if (!membersData[edPublic]) {
|
||||
contactsGrid.div = undefined;
|
||||
addBtn = undefined;
|
||||
}
|
||||
|
||||
const list = h('div', [
|
||||
currentList.div,
|
||||
contactsGrid.div,
|
||||
h('nev', addBtn)
|
||||
]);
|
||||
$div.append(list);
|
||||
};
|
||||
drawMembers();
|
||||
};
|
||||
refresh = () => {
|
||||
let oldKey, members;
|
||||
nThen((waitFor) => {
|
||||
APP.supportModule.execCommand('GET_PRIVATE_KEY', {}, waitFor((obj) => {
|
||||
oldKey = obj && obj.curvePrivate;
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'GET_MODERATORS',
|
||||
data: {}
|
||||
}, waitFor((e, response) => {
|
||||
if (e || response.error) {
|
||||
console.error(e || response.error);
|
||||
UI.warn(Messages.error);
|
||||
return;
|
||||
}
|
||||
members = response[0];
|
||||
}));
|
||||
}).nThen(() => {
|
||||
redraw(members, oldKey);
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
return $div;
|
||||
};
|
||||
create['support-init'] = function () {
|
||||
@ -4096,6 +4260,7 @@ Example
|
||||
APP.origin = privateData.origin;
|
||||
APP.readOnly = privateData.readOnly;
|
||||
APP.support = Support.create(common, true);
|
||||
APP.supportModule = common.makeUniversal('support');
|
||||
|
||||
|
||||
// Content
|
||||
|
||||
@ -486,6 +486,7 @@ define([
|
||||
|
||||
// XXX
|
||||
Messages.support_userNotification = "New support ticket or response: {0}";
|
||||
Messages.support_moderatorNotification = "You have been added to the moderators list";
|
||||
handlers['NOTIF_TICKET'] = function (common, data) {
|
||||
var content = data.content;
|
||||
var msg = content.msg.content;
|
||||
@ -504,6 +505,19 @@ define([
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
handlers['ADD_MODERATOR'] = function (common, data) {
|
||||
var content = data.content;
|
||||
content.getFormatText = function () {
|
||||
return Messages.support_moderatorNotification;
|
||||
};
|
||||
content.handler = function () {
|
||||
common.openURL('/moderation/');
|
||||
defaultDismiss(common, data)();
|
||||
};
|
||||
if (!content.archived) {
|
||||
content.dismissHandler = defaultDismiss(common, data);
|
||||
}
|
||||
};
|
||||
|
||||
handlers['BROADCAST_CUSTOM'] = function (common, data) {
|
||||
var content = data.content;
|
||||
|
||||
@ -1627,7 +1627,7 @@ define([
|
||||
var box = mailboxes[key] = {
|
||||
channel: channel,
|
||||
viewed: [],
|
||||
lastKnownHash: '',
|
||||
lastKnownHash: data.lastKnownHash || '',
|
||||
keys: {
|
||||
curvePublic: pub,
|
||||
curvePrivate: priv
|
||||
|
||||
@ -3,11 +3,12 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
define([
|
||||
'/api/config',
|
||||
'/common/common-messaging.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-util.js',
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
], function (Messaging, Hash, Util, Crypto) {
|
||||
], function (ApiConfig, Messaging, Hash, Util, Crypto) {
|
||||
|
||||
// Random timeout between 10 and 30 times your sync time (lag + chainpad sync)
|
||||
var getRandomTimeout = function (ctx) {
|
||||
@ -908,6 +909,24 @@ define([
|
||||
if (adminSupportNotif && adminSupportNotif.channel === id) { adminSupportNotif = undefined; }
|
||||
};
|
||||
|
||||
handlers['ADD_MODERATOR'] = function (ctx, box, data, cb) {
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
add: function (ctx, box, data, cb) {
|
||||
/**
|
||||
|
||||
@ -22,7 +22,7 @@ define([
|
||||
var cb = Util.mkAsync(_cb);
|
||||
if (isAdmin && !ctx.adminRdyEvt) { return void cb('EFORBIDDEN'); }
|
||||
require(['/api/config?' + (+new Date())], function (NewConfig) {
|
||||
ctx.adminKeys = NewConfig.adminKeys; // Update admin keys // XXX MODERATOR
|
||||
ctx.moderatorKeys = NewConfig.moderatorKeys; // Update admin keys // XXX MODERATOR
|
||||
|
||||
var supportKey = NewConfig.supportMailboxKey;
|
||||
if (!supportKey) { return void cb('E_NOT_INIT'); }
|
||||
@ -324,7 +324,7 @@ define([
|
||||
entry.premium = premium;
|
||||
|
||||
if (senderKey) {
|
||||
entry.lastAdmin = ctx.adminKeys.indexOf(senderKey) !== -1
|
||||
entry.lastAdmin = ctx.moderatorKeys.indexOf(senderKey) !== -1
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -544,7 +544,7 @@ define([
|
||||
if (err) { console.error('Support RPC not ready', err); }
|
||||
}));
|
||||
}).nThen((waitFor) => {
|
||||
let seed = privateKey.slice(0,24); // XXX better way to get seed?
|
||||
let seed = privateKey.slice(0,24); // XXX better way to get seed? also in admin/inner.js
|
||||
let hash = Hash.getEditHashFromKeys({
|
||||
version: 2,
|
||||
type: 'support',
|
||||
@ -559,6 +559,44 @@ define([
|
||||
|
||||
};
|
||||
|
||||
var getAdminKey = function (ctx, data, cId, cb) {
|
||||
let proxy = ctx.store.proxy;
|
||||
let supportKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']);
|
||||
let privateKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']);
|
||||
getKeys(ctx, false, {}, (err, obj) => {
|
||||
if (err) { return void cb({error: err}); }
|
||||
if (obj.theirPublic !== supportKey) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
cb({
|
||||
curvePrivate: privateKey
|
||||
});
|
||||
});
|
||||
};
|
||||
var addModerator = function (ctx, data, cId, cb) {
|
||||
let proxy = ctx.store.proxy;
|
||||
var mailbox = Util.find(ctx, [ 'store', 'mailbox' ]);
|
||||
|
||||
let supportKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePublic']);
|
||||
let privateKey = Util.find(proxy, ['mailboxes', 'supportteam', 'keys', 'curvePrivate']);
|
||||
let lastKnownHash = Util.find(proxy, ['mailboxes', 'supportteam', 'lastKnownHash']);
|
||||
let edPublic = proxy.edPublic;
|
||||
|
||||
// Confirm that I know the latest private key
|
||||
getKeys(ctx, false, {}, (err, obj) => {
|
||||
if (err) { return void cb({error: err}); }
|
||||
if (obj.theirPublic !== supportKey) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
if (!ctx.moderatorKeys.includes(edPublic)) { return void cb({ error: 'EFORBIDDEN' }); }
|
||||
// Send this private key to the selected user
|
||||
mailbox.sendTo('ADD_MODERATOR', {
|
||||
supportKey: privateKey,
|
||||
lastKnownHash
|
||||
}, {
|
||||
channel: data.mailbox,
|
||||
curvePublic: data.curvePublic
|
||||
}, () => {
|
||||
cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Support.init = function (cfg, waitFor, emit) {
|
||||
var support = {};
|
||||
@ -572,7 +610,7 @@ define([
|
||||
var proxy = store.proxy.support = store.proxy.support || {};
|
||||
|
||||
var ctx = {
|
||||
adminKeys: ApiConfig.adminKeys,
|
||||
moderatorKeys: ApiConfig.moderatorKeys,
|
||||
supportData: proxy,
|
||||
store: cfg.store,
|
||||
Store: cfg.Store,
|
||||
@ -621,6 +659,12 @@ define([
|
||||
if (cmd === 'CLOSE_TICKET_ADMIN') {
|
||||
return void closeTicketAdmin(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'GET_PRIVATE_KEY') {
|
||||
return void getAdminKey(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'ADD_MODERATOR') {
|
||||
return void addModerator(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'GET_MY_TICKETS') {
|
||||
return void getMyTickets(ctx, data, clientId, cb);
|
||||
}
|
||||
|
||||
@ -301,7 +301,8 @@ define([
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
common.setTabTitle(Messages.supportPage);
|
||||
|
||||
if (!common.isAdmin()) {
|
||||
if (!Array.isArray(ApiConfig.moderatorKeys) ||
|
||||
!ApiConfig.moderatorKeys.includes(privateData.edPublic)) {
|
||||
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
|
||||
}
|
||||
|
||||
|
||||
@ -423,7 +423,7 @@ define([
|
||||
// Check content.sender to see if it comes from us or from an admin
|
||||
var senderKey = content.sender && content.sender.edPublic;
|
||||
var fromMe = senderKey === privateData.edPublic;
|
||||
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1
|
||||
var fromAdmin = ctx.moderatorKeys.indexOf(senderKey) !== -1
|
||||
|| (!senderKey && content.sender.accountName === 'support'); // XXX anon key?
|
||||
var fromPremium = Boolean(content.sender.plan || Util.find(content, ['sender', 'quota', 'plan']));
|
||||
|
||||
@ -498,7 +498,7 @@ define([
|
||||
|
||||
var senderKey = content.sender && content.sender.edPublic;
|
||||
var fromMe = senderKey === privateData.edPublic;
|
||||
var fromAdmin = ctx.adminKeys.indexOf(senderKey) !== -1;
|
||||
var fromAdmin = ctx.moderatorKeys.indexOf(senderKey) !== -1;
|
||||
var adminClass = (fromAdmin? '.cp-support-fromadmin': '');
|
||||
|
||||
var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
|
||||
@ -518,7 +518,7 @@ define([
|
||||
isAdmin: isAdmin,
|
||||
pinUsage: pinUsage || false,
|
||||
teamsUsage: teamsUsage || false,
|
||||
adminKeys: Array.isArray(ApiConfig.adminKeys)? ApiConfig.adminKeys.slice(): [],
|
||||
moderatorKeys: Array.isArray(ApiConfig.moderatorKeys)? ApiConfig.moderatorKeys.slice(): [],
|
||||
};
|
||||
|
||||
ctx.supportModule = common.makeUniversal('support');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user