diff --git a/customize.dist/messages.js b/customize.dist/messages.js index be65e98a9..3c9055f29 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -135,6 +135,24 @@ define(req, function(AppConfig, Default, Language) { return text; } }; + + // XXX + Messages.admin_cat_admins = "Administrators"; + Messages.admin_admin = "Admin"; + Messages.admin_listAdminsTitle = "Current administrators"; + Messages.admin_listAdminsHint = "View and remove administrators"; + Messages.admin_addAdminsTitle = "Add administrators"; + Messages.admin_addAdminsHint = "Add administrators from their public key or from your contacts list"; + Messages.admin_addAdminsAdd = "Promote a contact to admin"; + Messages.admin_addKeyLabel = "Add an admin using their public key"; + + Messages.admin_listName = "Admin name"; + Messages.admin_listKey = "Admin key"; + Messages.admin_listAction = "Remove admin rights"; + + Messages.admin_listHardcoded = "Admin added into config.js. Can only be removed by editing the config file."; + Messages.admin_listConfirm = "Are you sure you want to remove the admin rights of this user?"; + return Messages; }); diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 90eb873ef..33950434c 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -7,6 +7,7 @@ @import (reference) "/customize/src/less2/include/colortheme-all.less"; @import (reference) "/customize/src/less2/include/leftside-menu.less"; @import (reference) "/customize/src/less2/include/browser.less"; +@import (reference) "/customize/src/less2/include/variables.less"; @sidebar_block-width: 25rem;; @sidebar_base-margin: 0.5rem; @@ -121,6 +122,12 @@ flex-flow: column; align-items: baseline; } + .cp-usergrid-container { + margin-bottom: 0px !important; + .cp-usergrid-grid { + margin-bottom: -3px; + } + } label { margin-bottom: 0; } @@ -128,6 +135,9 @@ font-family: inherit; max-width: @sidebar_block-width; } + input:invalid { + border: 1px solid red; + } [type="color"] { width: @sidebar_block-width/5; padding: 3px; @@ -214,7 +224,7 @@ border-top-left-radius: 0px; border-bottom-left-radius: 0px; border-left: 0px; - height: 40px; + height: @variables_input-height; margin: 0 !important; } } @@ -236,6 +246,12 @@ margin-bottom: 0; } } + .cp-sidebarlayout-description-item { + display: block; + color: @cp_sidebar-hint; + margin-top: @sidebar_base-margin; + margin-bottom: 0; + } label.noTitle { display: inline-flex; .fa { diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index d029fc079..6bc35f5be 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -18,9 +18,11 @@ const MFA = require("../storage/mfa"); const ArchiveAccount = require('../archive-account'); const { Worker } = require('node:worker_threads'); const Fse = require("fs-extra"); - const Fs = require("fs"); +const config = require("../load-config"); +const Keys = require("../keys"); + var Admin = module.exports; var getFileDescriptorCount = function (Env, server, cb) { @@ -468,7 +470,29 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) { }; // CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log) +const getAdminsData = (Env) => { + return Env.adminsData.map(str => { + // str is either a full public key or just the ed part + const edPublic = Keys.canonicalize(str); + const hardcoded = Array.isArray(config?.adminKeys) && + config.adminKeys.some(key => { + return Keys.canonicalize(key) === edPublic; + }); + if (str.length === 44) { + return { edPublic, first: true, hardcoded }; + } + let name; + try { + const parsed = Keys.parseUser(str); + name = parsed.user; + } catch (e) {} + return { + edPublic, hardcoded, name + }; + }); +}; var instanceStatus = function (Env, Server, cb) { + cb(void 0, { appsToDisable: Env.appsToDisable, @@ -514,6 +538,8 @@ var instanceStatus = function (Env, Server, cb) { instanceName: Env.instanceName, instanceNotice: Env.instanceNotice, enforceMFA: Env.enforceMFA, + + admins: getAdminsData(Env) }); }; @@ -1138,6 +1164,19 @@ Admin.command = function (Env, safeKey, data, _cb, Server) { var command = commands[data[0]]; + Object.keys(Env.plugins || {}).forEach(name => { + let plugin = Env.plugins[name]; + if (!plugin.addAdminCommands) { return; } + try { + let c = plugin.addAdminCommands(Env); + Object.keys(c || {}).forEach(cmd => { + if (typeof(c[cmd]) !== "function") { return; } + if (commands[cmd]) { return; } + commands[cmd] = c[cmd]; + }); + } catch (e) {} + }); + if (typeof(command) === 'function') { return void command(Env, Server, cb, data, unsafeKey); } diff --git a/lib/decrees-core.js b/lib/decrees-core.js new file mode 100644 index 000000000..3a40e5d6a --- /dev/null +++ b/lib/decrees-core.js @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +var Decrees = module.exports; +var Util = require("./common-util"); +var Fs = require("fs"); +var Path = require("path"); +var readFileBin = require("./stream-file").readFileBin; +var Schedule = require("./schedule"); +var Fse = require("fs-extra"); +var nThen = require("nthen"); + + +const Utils = Decrees.Utils = {}; +var isString = (str) => { + return typeof(str) === "string"; +}; +var isInteger = function (n) { + return !(typeof(n) !== 'number' || isNaN(n) || (n % 1) !== 0); +}; +Utils.args_isBoolean = function (args) { + return !(!Array.isArray(args) || typeof(args[0]) !== 'boolean'); +}; +Utils.args_isString = function (args) { + return !(!Array.isArray(args) || !isString(args[0])); +}; +Utils.args_isInteger = function (args) { + return !(!Array.isArray(args) || !isInteger(args[0])); +}; +Utils.args_isPositiveInteger = function (args) { + return Array.isArray(args) && isInteger(args[0]) && args[0] > 0; +}; + + +Decrees.create = (name, commands) => { + // [, , ,