2023-10-20 14:35:26 +00:00
|
|
|
|
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
|
2020-01-24 16:25:48 +00:00
|
|
|
|
const nThen = require("nthen");
|
|
|
|
|
const getFolderSize = require("get-folder-size");
|
2020-02-27 20:00:31 +00:00
|
|
|
|
const Util = require("../common-util");
|
2020-03-12 16:09:44 +00:00
|
|
|
|
const Ulimit = require("ulimit");
|
2020-10-02 07:24:17 +00:00
|
|
|
|
const Decrees = require("../decrees");
|
2021-01-31 09:58:45 +00:00
|
|
|
|
const Pinning = require("./pin-rpc");
|
2022-08-11 06:23:03 +00:00
|
|
|
|
const Core = require("./core");
|
|
|
|
|
const Channel = require("./channel");
|
2023-12-01 14:44:20 +00:00
|
|
|
|
const Invitation = require("./invitation");
|
|
|
|
|
const Users = require("./users");
|
2024-02-22 17:46:26 +00:00
|
|
|
|
const Moderators = require("./moderators");
|
2022-08-11 06:23:03 +00:00
|
|
|
|
const BlockStore = require("../storage/block");
|
2023-05-16 13:11:43 +00:00
|
|
|
|
const MFA = require("../storage/mfa");
|
2023-09-14 15:49:16 +00:00
|
|
|
|
const ArchiveAccount = require('../archive-account');
|
2023-09-05 14:31:04 +00:00
|
|
|
|
const { Worker } = require('node:worker_threads');
|
2024-03-15 15:09:23 +00:00
|
|
|
|
const Fse = require("fs-extra");
|
2020-02-05 22:31:44 +00:00
|
|
|
|
|
2024-03-15 15:09:23 +00:00
|
|
|
|
const Fs = require("fs");
|
2020-01-24 16:25:48 +00:00
|
|
|
|
|
|
|
|
|
var Admin = module.exports;
|
|
|
|
|
|
2020-03-12 15:35:16 +00:00
|
|
|
|
var getFileDescriptorCount = function (Env, server, cb) {
|
|
|
|
|
Fs.readdir('/proc/self/fd', function(err, list) {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
cb(void 0, list.length);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-12 16:09:44 +00:00
|
|
|
|
var getFileDescriptorLimit = function (env, server, cb) {
|
|
|
|
|
Ulimit(cb);
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-27 23:59:26 +00:00
|
|
|
|
var getCacheStats = function (env, server, cb) {
|
2020-03-30 22:26:04 +00:00
|
|
|
|
var metaSize = 0;
|
|
|
|
|
var channelSize = 0;
|
2020-03-27 23:59:26 +00:00
|
|
|
|
var metaCount = 0;
|
|
|
|
|
var channelCount = 0;
|
|
|
|
|
|
2020-03-30 22:26:04 +00:00
|
|
|
|
try {
|
|
|
|
|
var meta = env.metadata_cache;
|
|
|
|
|
for (var x in meta) {
|
|
|
|
|
if (meta.hasOwnProperty(x)) {
|
|
|
|
|
metaCount++;
|
|
|
|
|
metaSize += JSON.stringify(meta[x]).length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var channels = env.channel_cache;
|
|
|
|
|
for (var y in channels) {
|
|
|
|
|
if (channels.hasOwnProperty(y)) {
|
|
|
|
|
channelCount++;
|
|
|
|
|
channelSize += JSON.stringify(channels[y]).length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return void cb(err && err.message);
|
2020-03-27 23:59:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cb(void 0, {
|
|
|
|
|
metadata: metaCount,
|
2020-03-30 22:26:04 +00:00
|
|
|
|
metaSize: metaSize,
|
2020-03-27 23:59:26 +00:00
|
|
|
|
channel: channelCount,
|
2020-03-30 22:26:04 +00:00
|
|
|
|
channelSize: channelSize,
|
2020-04-14 19:53:58 +00:00
|
|
|
|
memoryUsage: process.memoryUsage(),
|
2020-03-27 23:59:26 +00:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-08 05:14:46 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['GET_WORKER_PROFILES'], console.log)
|
|
|
|
|
var getWorkerProfiles = function (Env, Server, cb) {
|
|
|
|
|
cb(void 0, Env.commandTimers);
|
|
|
|
|
};
|
|
|
|
|
|
2020-02-03 19:20:05 +00:00
|
|
|
|
var getActiveSessions = function (Env, Server, cb) {
|
|
|
|
|
var stats = Server.getSessionStats();
|
|
|
|
|
cb(void 0, [
|
|
|
|
|
stats.total,
|
|
|
|
|
stats.unique
|
|
|
|
|
]);
|
2020-01-24 16:25:48 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-02-03 19:20:05 +00:00
|
|
|
|
var shutdown = function (Env, Server, cb) {
|
2024-02-29 09:38:59 +00:00
|
|
|
|
if (true) { // eslint-disable-line no-constant-condition
|
2020-02-03 22:14:23 +00:00
|
|
|
|
return void cb('E_NOT_IMPLEMENTED');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// disconnect all users and reject new connections
|
|
|
|
|
Server.shutdown();
|
|
|
|
|
|
|
|
|
|
// stop all intervals that may be running
|
|
|
|
|
Object.keys(Env.intervals).forEach(function (name) {
|
|
|
|
|
clearInterval(Env.intervals[name]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// set a flag to prevent incoming database writes
|
|
|
|
|
// wait until all pending writes are complete
|
2020-01-24 16:25:48 +00:00
|
|
|
|
// then process.exit(0);
|
|
|
|
|
// and allow system functionality to restart the server
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-11 16:14:45 +00:00
|
|
|
|
var getRegisteredUsers = Admin.getRegisteredUsers = function (Env, Server, cb) {
|
2020-02-27 20:00:31 +00:00
|
|
|
|
Env.batchRegisteredUsers('', cb, function (done) {
|
2020-01-24 16:25:48 +00:00
|
|
|
|
var dir = Env.paths.pin;
|
2024-06-11 16:14:45 +00:00
|
|
|
|
var dirB = Env.paths.block;
|
|
|
|
|
var folders, foldersB;
|
2020-01-24 16:25:48 +00:00
|
|
|
|
var users = 0;
|
2024-06-11 16:14:45 +00:00
|
|
|
|
var blocks = 0;
|
2020-01-24 16:25:48 +00:00
|
|
|
|
nThen(function (waitFor) {
|
|
|
|
|
Fs.readdir(dir, waitFor(function (err, list) {
|
|
|
|
|
if (err) {
|
|
|
|
|
waitFor.abort();
|
|
|
|
|
return void done(err);
|
|
|
|
|
}
|
|
|
|
|
folders = list;
|
|
|
|
|
}));
|
|
|
|
|
}).nThen(function (waitFor) {
|
|
|
|
|
folders.forEach(function (f) {
|
|
|
|
|
var dir = Env.paths.pin + '/' + f;
|
|
|
|
|
Fs.readdir(dir, waitFor(function (err, list) {
|
|
|
|
|
if (err) { return; }
|
2024-06-11 16:14:45 +00:00
|
|
|
|
// Don't count placeholders
|
|
|
|
|
list = list.filter(name => {
|
|
|
|
|
return !/\.placeholder$/.test(name);
|
|
|
|
|
});
|
2020-01-24 16:25:48 +00:00
|
|
|
|
users += list.length;
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
}).nThen(function () {
|
2024-07-23 08:36:46 +00:00
|
|
|
|
done(void 0, {users});
|
2020-01-24 16:25:48 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-02-27 20:00:31 +00:00
|
|
|
|
var getDiskUsage = function (Env, Server, cb) {
|
|
|
|
|
Env.batchDiskUsage('', cb, function (done) {
|
2020-01-24 16:25:48 +00:00
|
|
|
|
var data = {};
|
|
|
|
|
nThen(function (waitFor) {
|
|
|
|
|
getFolderSize('./', waitFor(function(err, info) {
|
|
|
|
|
data.total = info;
|
|
|
|
|
}));
|
|
|
|
|
getFolderSize(Env.paths.pin, waitFor(function(err, info) {
|
|
|
|
|
data.pin = info;
|
|
|
|
|
}));
|
|
|
|
|
getFolderSize(Env.paths.blob, waitFor(function(err, info) {
|
|
|
|
|
data.blob = info;
|
|
|
|
|
}));
|
|
|
|
|
getFolderSize(Env.paths.staging, waitFor(function(err, info) {
|
|
|
|
|
data.blobstage = info;
|
|
|
|
|
}));
|
|
|
|
|
getFolderSize(Env.paths.block, waitFor(function(err, info) {
|
|
|
|
|
data.block = info;
|
|
|
|
|
}));
|
|
|
|
|
getFolderSize(Env.paths.data, waitFor(function(err, info) {
|
|
|
|
|
data.datastore = info;
|
|
|
|
|
}));
|
|
|
|
|
}).nThen(function () {
|
|
|
|
|
done(void 0, data);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-02-27 20:00:31 +00:00
|
|
|
|
var getActiveChannelCount = function (Env, Server, cb) {
|
|
|
|
|
cb(void 0, Server.getActiveChannelCount());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var flushCache = function (Env, Server, cb) {
|
|
|
|
|
Env.flushCache();
|
|
|
|
|
cb(void 0, true);
|
|
|
|
|
};
|
|
|
|
|
|
2020-09-25 08:54:26 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['ARCHIVE_DOCUMENT', documentID], console.log)
|
|
|
|
|
var archiveDocument = function (Env, Server, cb, data) {
|
2022-02-18 13:15:52 +00:00
|
|
|
|
if (!Array.isArray(data)) { return void cb("EINVAL"); }
|
|
|
|
|
var args = data[1];
|
|
|
|
|
|
|
|
|
|
var id, reason;
|
|
|
|
|
if (typeof(args) === 'string') {
|
|
|
|
|
id = args;
|
|
|
|
|
} else if (args && typeof(args) === 'object') {
|
|
|
|
|
id = args.id;
|
|
|
|
|
reason = args.reason;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-25 08:54:26 +00:00
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
|
|
|
|
|
2023-09-07 15:03:20 +00:00
|
|
|
|
const archiveReason = {
|
|
|
|
|
code: 'MODERATION_PAD',
|
|
|
|
|
txt: reason
|
|
|
|
|
};
|
|
|
|
|
const reasonStr = `MODERATION_PAD:${reason}`;
|
|
|
|
|
|
2020-09-25 08:54:26 +00:00
|
|
|
|
switch (id.length) {
|
|
|
|
|
case 32:
|
2023-09-07 15:03:20 +00:00
|
|
|
|
return void Env.msgStore.archiveChannel(id, archiveReason, Util.both(cb, function (err) {
|
2020-12-11 11:33:48 +00:00
|
|
|
|
Env.Log.info("ARCHIVAL_CHANNEL_BY_ADMIN_RPC", {
|
|
|
|
|
channelId: id,
|
2022-02-18 13:15:52 +00:00
|
|
|
|
reason: reason,
|
2020-12-11 11:33:48 +00:00
|
|
|
|
status: err? String(err): "SUCCESS",
|
|
|
|
|
});
|
2023-09-07 15:03:20 +00:00
|
|
|
|
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reasonStr, err => {
|
2022-09-07 12:45:19 +00:00
|
|
|
|
if (err) { } // TODO
|
2022-08-11 06:23:03 +00:00
|
|
|
|
});
|
2020-12-11 11:33:48 +00:00
|
|
|
|
}));
|
2020-09-25 08:54:26 +00:00
|
|
|
|
case 48:
|
2023-09-07 15:03:20 +00:00
|
|
|
|
return void Env.blobStore.archive.blob(id, archiveReason, Util.both(cb, function (err) {
|
2020-12-11 11:33:48 +00:00
|
|
|
|
Env.Log.info("ARCHIVAL_BLOB_BY_ADMIN_RPC", {
|
|
|
|
|
id: id,
|
2022-02-18 13:15:52 +00:00
|
|
|
|
reason: reason,
|
2020-12-11 11:33:48 +00:00
|
|
|
|
status: err? String(err): "SUCCESS",
|
|
|
|
|
});
|
|
|
|
|
}));
|
2020-09-25 08:54:26 +00:00
|
|
|
|
default:
|
|
|
|
|
return void cb("INVALID_ID_LENGTH");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// archival for blob proofs isn't automated, but evict-inactive.js will
|
|
|
|
|
// clean up orpaned blob proofs
|
|
|
|
|
// Env.blobStore.archive.proof(userSafeKey, blobId, cb)
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-13 13:02:50 +00:00
|
|
|
|
var removeDocument = function (Env, Server, cb, data) {
|
|
|
|
|
if (!Array.isArray(data)) { return void cb("EINVAL"); }
|
|
|
|
|
var args = data[1];
|
|
|
|
|
|
|
|
|
|
var id, reason;
|
|
|
|
|
if (typeof(args) === 'string') {
|
|
|
|
|
id = args;
|
|
|
|
|
} else if (args && typeof(args) === 'object') {
|
|
|
|
|
id = args.id;
|
2023-09-07 15:03:20 +00:00
|
|
|
|
reason = `MODERATION_DESTROY:${args.reason}`;
|
2022-09-13 13:02:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
|
|
|
|
|
|
|
|
|
switch (id.length) {
|
|
|
|
|
case 32:
|
|
|
|
|
return void Env.msgStore.removeChannel(id, Util.both(cb, function (err) {
|
|
|
|
|
Env.Log.info("REMOVAL_CHANNEL_BY_ADMIN_RPC", {
|
|
|
|
|
channelId: id,
|
|
|
|
|
reason: reason,
|
|
|
|
|
status: err? String(err): "SUCCESS",
|
|
|
|
|
});
|
2023-09-07 15:03:20 +00:00
|
|
|
|
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reason, err => {
|
2022-09-13 13:02:50 +00:00
|
|
|
|
if (err) { } // TODO
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
case 48:
|
|
|
|
|
return void Env.blobStore.remove.blob(id, Util.both(cb, function (err) {
|
|
|
|
|
Env.Log.info("REMOVAL_BLOB_BY_ADMIN_RPC", {
|
|
|
|
|
id: id,
|
|
|
|
|
reason: reason,
|
|
|
|
|
status: err? String(err): "SUCCESS",
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
default:
|
|
|
|
|
return void cb("INVALID_ID_LENGTH");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2020-11-18 15:49:40 +00:00
|
|
|
|
var restoreArchivedDocument = function (Env, Server, cb, data) {
|
2022-02-18 13:15:52 +00:00
|
|
|
|
if (!Array.isArray(data)) { return void cb("EINVAL"); }
|
|
|
|
|
var args = data[1];
|
|
|
|
|
|
|
|
|
|
var id, reason;
|
|
|
|
|
if (typeof(args) === 'string') {
|
|
|
|
|
id = args;
|
|
|
|
|
} else if (args && typeof(args) === 'object') {
|
|
|
|
|
id = args.id;
|
|
|
|
|
reason = args.reason;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-18 15:49:40 +00:00
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
2020-09-25 08:54:26 +00:00
|
|
|
|
|
2020-11-18 15:49:40 +00:00
|
|
|
|
switch (id.length) {
|
|
|
|
|
case 32:
|
2020-12-11 12:27:42 +00:00
|
|
|
|
return void Env.msgStore.restoreArchivedChannel(id, Util.both(cb, function (err) {
|
|
|
|
|
Env.Log.info("RESTORATION_CHANNEL_BY_ADMIN_RPC", {
|
|
|
|
|
id: id,
|
2022-02-18 13:15:52 +00:00
|
|
|
|
reason: reason,
|
2020-12-11 12:27:42 +00:00
|
|
|
|
status: err? String(err): 'SUCCESS',
|
|
|
|
|
});
|
|
|
|
|
}));
|
2020-11-18 15:49:40 +00:00
|
|
|
|
case 48:
|
2020-12-11 12:27:42 +00:00
|
|
|
|
// FIXME this does not yet restore blob ownership
|
|
|
|
|
// Env.blobStore.restore.proof(userSafekey, id, cb)
|
|
|
|
|
return void Env.blobStore.restore.blob(id, Util.both(cb, function (err) {
|
|
|
|
|
Env.Log.info("RESTORATION_BLOB_BY_ADMIN_RPC", {
|
|
|
|
|
id: id,
|
2022-02-18 13:15:52 +00:00
|
|
|
|
reason: reason,
|
2020-12-11 12:27:42 +00:00
|
|
|
|
status: err? String(err): 'SUCCESS',
|
|
|
|
|
});
|
|
|
|
|
}));
|
2020-11-18 15:49:40 +00:00
|
|
|
|
default:
|
|
|
|
|
return void cb("INVALID_ID_LENGTH");
|
|
|
|
|
}
|
2020-09-25 08:54:26 +00:00
|
|
|
|
};
|
2020-03-17 14:11:52 +00:00
|
|
|
|
|
2023-09-14 15:49:16 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['ARCHIVE_ACCOUNT', {key, block, reason}], console.log)
|
2023-09-05 14:31:04 +00:00
|
|
|
|
var archiveAccount = function (Env, Server, _cb, data) {
|
|
|
|
|
const cb = Util.once(_cb);
|
|
|
|
|
const worker = new Worker('./lib/archive-account.js');
|
2023-09-14 15:49:16 +00:00
|
|
|
|
const args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
2023-09-05 14:31:04 +00:00
|
|
|
|
worker.on('message', message => {
|
|
|
|
|
if (message === 'READY') {
|
|
|
|
|
return worker.postMessage({
|
|
|
|
|
command: 'start',
|
2023-09-14 15:49:16 +00:00
|
|
|
|
content: args.key,
|
|
|
|
|
block: args.block, // optional, may be including in pin log
|
|
|
|
|
reason: args.reason
|
2023-09-05 14:31:04 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
2023-09-07 15:03:20 +00:00
|
|
|
|
|
2023-09-05 14:31:04 +00:00
|
|
|
|
// DONE: disconnect all users from these channels
|
2023-09-14 15:49:16 +00:00
|
|
|
|
Env.Log.info('ARCHIVE_ACCOUNT_BY_ADMIN', {
|
|
|
|
|
safeKey: args.key,
|
|
|
|
|
reason: args.reason,
|
|
|
|
|
});
|
|
|
|
|
const reason = `MODERATION_ACCOUNT:${args.reason}`;
|
2023-09-05 14:31:04 +00:00
|
|
|
|
var deletedChannels = Util.tryParse(message);
|
|
|
|
|
if (Array.isArray(deletedChannels)) {
|
|
|
|
|
let n = nThen;
|
|
|
|
|
deletedChannels.forEach((chanId) => {
|
|
|
|
|
n = n((w) => {
|
|
|
|
|
setTimeout(w(() => {
|
2023-09-07 15:03:20 +00:00
|
|
|
|
Channel.disconnectChannelMembers(Env, Server, chanId, 'EDELETED', reason, () => {});
|
2023-09-05 14:31:04 +00:00
|
|
|
|
}), 10);
|
|
|
|
|
}).nThen;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
cb(void 0, { state: true });
|
|
|
|
|
});
|
|
|
|
|
worker.on('error', (err) => {
|
|
|
|
|
console.error(err);
|
|
|
|
|
cb(err);
|
|
|
|
|
});
|
|
|
|
|
worker.on('exit', () => { worker.unref(); });
|
|
|
|
|
};
|
|
|
|
|
var restoreAccount = function (Env, Server, _cb, data) {
|
|
|
|
|
const cb = Util.once(_cb);
|
|
|
|
|
const worker = new Worker('./lib/archive-account.js');
|
2023-09-14 15:49:16 +00:00
|
|
|
|
const args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
2023-09-05 14:31:04 +00:00
|
|
|
|
worker.on('message', message => {
|
|
|
|
|
if (message === 'READY') {
|
|
|
|
|
return worker.postMessage({
|
|
|
|
|
command: 'restore',
|
2023-09-14 15:49:16 +00:00
|
|
|
|
content: args.key
|
2023-09-05 14:31:04 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Response
|
2023-09-14 15:49:16 +00:00
|
|
|
|
Env.Log.info('RESTORE_ACCOUNT_BY_ADMIN', {
|
|
|
|
|
safeKey: args.key,
|
|
|
|
|
reason: args.reason,
|
|
|
|
|
});
|
2023-09-05 14:31:04 +00:00
|
|
|
|
cb(void 0, {
|
|
|
|
|
state: true,
|
|
|
|
|
errors: Util.tryParse(message)
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
worker.on('error', (err) => {
|
|
|
|
|
console.error(err);
|
|
|
|
|
cb(err);
|
|
|
|
|
});
|
|
|
|
|
worker.on('exit', () => { worker.unref(); });
|
2023-09-14 15:49:16 +00:00
|
|
|
|
};
|
|
|
|
|
var getAccountArchiveStatus = function (Env, Server, cb, data) {
|
|
|
|
|
const args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
|
|
|
|
ArchiveAccount.getStatus(Env, args.key, cb);
|
2023-09-05 14:31:04 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-10-20 11:42:26 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_INDEX', documentID], console.log)
|
2020-10-20 11:37:55 +00:00
|
|
|
|
var clearChannelIndex = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
|
|
|
|
delete Env.channel_cache[id];
|
|
|
|
|
cb();
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-20 11:42:26 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['GET_CACHED_CHANNEL_INDEX', documentID], console.log)
|
2020-10-20 11:37:55 +00:00
|
|
|
|
var getChannelIndex = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
|
|
|
|
|
|
|
|
|
var index = Util.find(Env, ['channel_cache', id]);
|
|
|
|
|
if (!index) { return void cb("ENOENT"); }
|
|
|
|
|
cb(void 0, index);
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-20 11:42:26 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_METADATA', documentID], console.log)
|
|
|
|
|
var clearChannelMetadata = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
|
|
|
|
delete Env.metadata_cache[id];
|
|
|
|
|
cb();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['GET_CACHED_CHANNEL_METADATA', documentID], console.log)
|
|
|
|
|
var getChannelMetadata = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
|
|
|
|
|
|
|
|
|
|
var index = Util.find(Env, ['metadata_cache', id]);
|
|
|
|
|
if (!index) { return void cb("ENOENT"); }
|
|
|
|
|
cb(void 0, index);
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-02 07:24:17 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log)
|
2020-10-09 07:58:13 +00:00
|
|
|
|
var adminDecree = function (Env, Server, cb, data, unsafeKey) {
|
2020-10-02 07:24:17 +00:00
|
|
|
|
var value = data[1];
|
|
|
|
|
if (!Array.isArray(value)) { return void cb('INVALID_DECREE'); }
|
|
|
|
|
|
|
|
|
|
var command = value[0];
|
|
|
|
|
var args = value[1];
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
The admin should have sent a command to be run:
|
|
|
|
|
|
|
|
|
|
the server adds two pieces of information to the supplied decree:
|
|
|
|
|
|
|
|
|
|
* the unsafeKey of the admin who uploaded it
|
|
|
|
|
* the current time
|
|
|
|
|
|
|
|
|
|
1. test the command to see if it's valid and will result in a change
|
|
|
|
|
2. if so, apply it and write it to the log for persistence
|
|
|
|
|
3. respond to the admin with an error or nothing
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
var decree = [command, args, unsafeKey, +new Date()];
|
|
|
|
|
var changed;
|
|
|
|
|
try {
|
|
|
|
|
changed = Decrees.handleCommand(Env, decree) || false;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return void cb(err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!changed) { return void cb(); }
|
2020-10-13 05:26:40 +00:00
|
|
|
|
Env.Log.info('ADMIN_DECREE', decree);
|
2024-02-07 11:25:20 +00:00
|
|
|
|
let _err;
|
|
|
|
|
nThen((waitFor) => {
|
|
|
|
|
Decrees.write(Env, decree, waitFor((err) => {
|
|
|
|
|
_err = err;
|
|
|
|
|
}));
|
|
|
|
|
setTimeout(waitFor(), 300); // NOTE: 300 because cache update may take up to 250ms
|
|
|
|
|
}).nThen(function () {
|
|
|
|
|
cb(_err);
|
|
|
|
|
});
|
2020-10-02 07:24:17 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-10-15 07:41:09 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['SET_LAST_EVICTION', 0], console.log)
|
|
|
|
|
var setLastEviction = function (Env, Server, cb, data, unsafeKey) {
|
|
|
|
|
var time = data && data[1];
|
|
|
|
|
if (typeof(time) !== 'number') {
|
|
|
|
|
return void cb('INVALID_ARGS');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Env.lastEviction = time;
|
|
|
|
|
cb();
|
|
|
|
|
Env.Log.info('LAST_EVICTION_TIME_SET', {
|
|
|
|
|
author: unsafeKey,
|
|
|
|
|
time: time,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-02 07:24:17 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log)
|
|
|
|
|
var instanceStatus = function (Env, Server, cb) {
|
|
|
|
|
cb(void 0, {
|
2024-05-07 13:14:06 +00:00
|
|
|
|
|
2024-06-19 16:11:55 +00:00
|
|
|
|
appsToDisable: Env.appsToDisable,
|
2020-10-13 05:26:40 +00:00
|
|
|
|
restrictRegistration: Env.restrictRegistration,
|
2023-12-19 16:59:56 +00:00
|
|
|
|
restrictSsoRegistration: Env.restrictSsoRegistration,
|
2024-01-18 14:46:35 +00:00
|
|
|
|
dontStoreSSOUsers: Env.dontStoreSSOUsers,
|
|
|
|
|
dontStoreInvitedUsers: Env.dontStoreInvitedUsers,
|
2023-12-19 16:59:56 +00:00
|
|
|
|
|
2022-03-24 07:13:16 +00:00
|
|
|
|
enableEmbedding: Env.enableEmbedding,
|
2020-10-02 07:24:17 +00:00
|
|
|
|
launchTime: Env.launchTime,
|
|
|
|
|
currentTime: +new Date(),
|
|
|
|
|
|
2020-10-13 06:06:30 +00:00
|
|
|
|
inactiveTime: Env.inactiveTime,
|
2020-10-02 07:24:17 +00:00
|
|
|
|
accountRetentionTime: Env.accountRetentionTime,
|
|
|
|
|
archiveRetentionTime: Env.archiveRetentionTime,
|
|
|
|
|
|
|
|
|
|
defaultStorageLimit: Env.defaultStorageLimit,
|
2020-10-12 12:22:15 +00:00
|
|
|
|
|
|
|
|
|
lastEviction: Env.lastEviction,
|
2020-10-15 07:41:09 +00:00
|
|
|
|
evictionReport: Env.evictionReport,
|
|
|
|
|
|
2020-10-13 05:26:40 +00:00
|
|
|
|
disableIntegratedEviction: Env.disableIntegratedEviction,
|
|
|
|
|
disableIntegratedTasks: Env.disableIntegratedTasks,
|
|
|
|
|
|
2022-03-07 13:12:00 +00:00
|
|
|
|
enableProfiling: Env.enableProfiling,
|
|
|
|
|
profilingWindow: Env.profilingWindow,
|
|
|
|
|
|
2020-10-13 05:26:40 +00:00
|
|
|
|
maxUploadSize: Env.maxUploadSize,
|
|
|
|
|
premiumUploadSize: Env.premiumUploadSize,
|
2021-06-08 14:54:30 +00:00
|
|
|
|
|
|
|
|
|
consentToContact: Env.consentToContact,
|
|
|
|
|
listMyInstance: Env.listMyInstance,
|
|
|
|
|
provideAggregateStatistics: Env.provideAggregateStatistics,
|
|
|
|
|
|
|
|
|
|
removeDonateButton: Env.removeDonateButton,
|
|
|
|
|
blockDailyCheck: Env.blockDailyCheck,
|
2021-06-09 13:15:02 +00:00
|
|
|
|
|
|
|
|
|
updateAvailable: Env.updateAvailable,
|
2021-06-25 07:34:29 +00:00
|
|
|
|
instancePurpose: Env.instancePurpose,
|
2022-02-24 09:58:24 +00:00
|
|
|
|
|
2022-05-10 07:41:53 +00:00
|
|
|
|
instanceDescription: Env.instanceDescription,
|
|
|
|
|
instanceJurisdiction: Env.instanceJurisdiction,
|
|
|
|
|
instanceName: Env.instanceName,
|
|
|
|
|
instanceNotice: Env.instanceNotice,
|
2023-12-11 15:40:05 +00:00
|
|
|
|
enforceMFA: Env.enforceMFA,
|
2020-10-02 07:24:17 +00:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-07 09:47:29 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['GET_LIMITS'], console.log)
|
2020-10-07 09:38:33 +00:00
|
|
|
|
var getLimits = function (Env, Server, cb) {
|
|
|
|
|
cb(void 0, Env.limits);
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-11 06:23:03 +00:00
|
|
|
|
var isValidKey = key => {
|
|
|
|
|
return typeof(key) === 'string' && key.length === 44;
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-31 09:58:45 +00:00
|
|
|
|
// CryptPad_AsyncStore.rpc.send('ADMIN', ['GET_USER_TOTAL_SIZE', "CrufexqXcY/z+eKJlEbNELVy5Sb7E/EAAEFI8GnEtZ0="], console.log)
|
|
|
|
|
var getUserTotalSize = function (Env, Server, cb, data) {
|
|
|
|
|
var signingKey = Array.isArray(data) && data[1];
|
2022-08-11 06:23:03 +00:00
|
|
|
|
if (!isValidKey(signingKey)) { return void cb("EINVAL"); }
|
2022-08-23 10:38:57 +00:00
|
|
|
|
var safeKey = Util.escapeKeyCharacters(signingKey);
|
|
|
|
|
Pinning.getTotalSize(Env, safeKey, cb);
|
2022-08-11 06:23:03 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getPinActivity = function (Env, Server, cb, data) {
|
|
|
|
|
var signingKey = Array.isArray(data) && data[1];
|
|
|
|
|
if (!isValidKey(signingKey)) { return void cb("EINVAL"); }
|
2022-08-23 10:38:57 +00:00
|
|
|
|
// the db-worker ensures the signing key is of the appropriate form
|
2022-08-11 13:00:19 +00:00
|
|
|
|
Env.getPinActivity(signingKey, function (err, response) {
|
|
|
|
|
if (err) { return void cb(err && err.code); }
|
|
|
|
|
cb(void 0, response);
|
|
|
|
|
});
|
2022-08-11 06:23:03 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-09-07 12:45:19 +00:00
|
|
|
|
var isUserOnline = function (Env, Server, cb, data) {
|
2022-08-11 06:23:03 +00:00
|
|
|
|
var key = Array.isArray(data) && data[1];
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
key = Util.unescapeKeyCharacters(key);
|
|
|
|
|
var online = false;
|
|
|
|
|
try {
|
|
|
|
|
Object.keys(Env.netfluxUsers).some(function (netfluxId) {
|
|
|
|
|
if (!Env.netfluxUsers[netfluxId][key]) { return; }
|
|
|
|
|
online = true;
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
Env.Log.error('ADMIN_USER_ONLINE_CHECK', {
|
|
|
|
|
error: err,
|
|
|
|
|
key: key,
|
|
|
|
|
});
|
|
|
|
|
return void cb("SERVER_ERROR");
|
|
|
|
|
}
|
|
|
|
|
cb(void 0, online);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getPinLogStatus = function (Env, Server, cb, data) {
|
|
|
|
|
var key = Array.isArray(data) && data[1];
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
var safeKey = Util.escapeKeyCharacters(key);
|
|
|
|
|
|
|
|
|
|
var response = {};
|
|
|
|
|
nThen(function (w) {
|
|
|
|
|
Env.pinStore.isChannelAvailable(safeKey, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('PIN_LOG_STATUS_AVAILABLE', err);
|
|
|
|
|
}
|
|
|
|
|
response.live = result;
|
|
|
|
|
}));
|
|
|
|
|
Env.pinStore.isChannelArchived(safeKey, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('PIN_LOG_STATUS_ARCHIVED', err);
|
|
|
|
|
}
|
|
|
|
|
response.archived = result;
|
|
|
|
|
}));
|
|
|
|
|
}).nThen(function () {
|
|
|
|
|
cb(void 0, response);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getDocumentStatus = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (typeof(id) !== 'string') { return void cb("EINVAL"); }
|
|
|
|
|
var response = {};
|
|
|
|
|
if (id.length === 44) {
|
|
|
|
|
return void nThen(function (w) {
|
|
|
|
|
BlockStore.isAvailable(Env, id, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('BLOCK_STATUS_AVAILABLE', err);
|
|
|
|
|
}
|
|
|
|
|
response.live = result;
|
|
|
|
|
}));
|
|
|
|
|
BlockStore.isArchived(Env, id, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('BLOCK_STATUS_ARCHIVED', err);
|
|
|
|
|
}
|
|
|
|
|
response.archived = result;
|
|
|
|
|
}));
|
2023-09-14 15:49:16 +00:00
|
|
|
|
BlockStore.readPlaceholder(Env, id, w((result) => {
|
|
|
|
|
if (!result) { return; }
|
|
|
|
|
response.placeholder = result;
|
|
|
|
|
}));
|
2023-05-16 13:11:43 +00:00
|
|
|
|
MFA.read(Env, id, w(function (err, v) {
|
|
|
|
|
if (err === 'ENOENT') {
|
|
|
|
|
response.totp = 'DISABLED';
|
|
|
|
|
} else if (v) {
|
|
|
|
|
var parsed = Util.tryParse(v);
|
|
|
|
|
response.totp = {
|
|
|
|
|
enabled: true,
|
|
|
|
|
recovery: parsed.contact && parsed.contact.split(':')[0]
|
|
|
|
|
};
|
|
|
|
|
} else {
|
2023-06-06 14:09:17 +00:00
|
|
|
|
response.totp = err;
|
2023-05-16 13:11:43 +00:00
|
|
|
|
}
|
|
|
|
|
}));
|
2022-08-11 06:23:03 +00:00
|
|
|
|
}).nThen(function () {
|
|
|
|
|
cb(void 0, response);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (id.length === 48) {
|
|
|
|
|
return void nThen(function (w) {
|
|
|
|
|
Env.blobStore.isBlobAvailable(id, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('BLOB_STATUS_AVAILABLE', err);
|
|
|
|
|
}
|
|
|
|
|
response.live = result;
|
|
|
|
|
}));
|
|
|
|
|
Env.blobStore.isBlobArchived(id, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('BLOB_STATUS_ARCHIVED', err);
|
|
|
|
|
}
|
|
|
|
|
response.archived = result;
|
|
|
|
|
}));
|
2023-09-14 15:49:16 +00:00
|
|
|
|
Env.blobStore.getPlaceholder(id, w((result) => {
|
|
|
|
|
if (!result) { return; }
|
|
|
|
|
response.placeholder = result;
|
|
|
|
|
}));
|
2022-08-11 06:23:03 +00:00
|
|
|
|
}).nThen(function () {
|
|
|
|
|
cb(void 0, response);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (id.length !== 32) { return void cb("EINVAL"); }
|
|
|
|
|
nThen(function (w) {
|
|
|
|
|
Env.store.isChannelAvailable(id, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('CHANNEL_STATUS_AVAILABLE', err);
|
|
|
|
|
}
|
|
|
|
|
response.live = result;
|
|
|
|
|
}));
|
|
|
|
|
Env.store.isChannelArchived(id, w(function (err, result) {
|
|
|
|
|
if (err) {
|
|
|
|
|
return void Env.Log.error('CHANNEL_STATUS_ARCHIVED', err);
|
|
|
|
|
}
|
|
|
|
|
response.archived = result;
|
|
|
|
|
}));
|
2023-09-14 15:49:16 +00:00
|
|
|
|
Env.store.getPlaceholder(id, w((result) => {
|
|
|
|
|
if (!result) { return; }
|
|
|
|
|
response.placeholder = result;
|
|
|
|
|
}));
|
2022-08-11 06:23:03 +00:00
|
|
|
|
}).nThen(function () {
|
|
|
|
|
cb(void 0, response);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-16 13:11:43 +00:00
|
|
|
|
var disableMFA = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (typeof(id) !== 'string' || id.length !== 44) { return void cb("EINVAL"); }
|
|
|
|
|
MFA.revoke(Env, id, cb);
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-11 06:23:03 +00:00
|
|
|
|
var getPinList = function (Env, Server, cb, data) {
|
|
|
|
|
var key = Array.isArray(data) && data[1];
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
var safeKey = Util.escapeKeyCharacters(key);
|
|
|
|
|
|
|
|
|
|
Env.getPinState(safeKey, function (err, value) {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
try {
|
|
|
|
|
return void cb(void 0, Object.keys(value).filter(k => value[k]));
|
|
|
|
|
} catch (err2) { }
|
|
|
|
|
cb("UNEXPECTED_SERVER_ERROR");
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getPinHistory = function (Env, Server, cb, data) {
|
|
|
|
|
Env.Log.debug('GET_PIN_HISTORY', data);
|
|
|
|
|
cb("NOT_IMPLEMENTED");
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-14 15:49:16 +00:00
|
|
|
|
/*
|
|
|
|
|
// NOTE: Deprecated, archive whole account now
|
2022-08-11 06:23:03 +00:00
|
|
|
|
var archivePinLog = function (Env, Server, cb, data) {
|
2022-08-24 04:40:32 +00:00
|
|
|
|
var args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
|
|
|
|
var key = args.key;
|
|
|
|
|
var reason = args.reason || '';
|
2022-08-11 06:23:03 +00:00
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
var safeKey = Util.escapeKeyCharacters(key);
|
2022-08-11 13:00:19 +00:00
|
|
|
|
|
2023-09-07 15:03:20 +00:00
|
|
|
|
Env.pinStore.archiveChannel(safeKey, undefined, function (err) {
|
2022-08-11 13:00:19 +00:00
|
|
|
|
Core.expireSession(Env.Sessions, safeKey);
|
2022-08-11 06:23:03 +00:00
|
|
|
|
if (err) {
|
|
|
|
|
Env.Log.error('ARCHIVE_PIN_LOG_BY_ADMIN', {
|
|
|
|
|
error: err,
|
|
|
|
|
safeKey: safeKey,
|
2022-08-24 04:40:32 +00:00
|
|
|
|
reason: reason,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
Env.Log.info('ARCHIVE_PIN_LOG_BY_ADMIN', {
|
|
|
|
|
safeKey: safeKey,
|
2022-08-24 04:40:32 +00:00
|
|
|
|
reason: reason,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
cb(err);
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-09-14 15:49:16 +00:00
|
|
|
|
*/
|
2022-08-11 06:23:03 +00:00
|
|
|
|
|
|
|
|
|
var archiveBlock = function (Env, Server, cb, data) {
|
|
|
|
|
var args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args) { return void cb("INVALID_ARGS"); }
|
|
|
|
|
var key = args.key;
|
|
|
|
|
var reason = args.reason;
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
2023-09-08 16:10:02 +00:00
|
|
|
|
const archiveReason = {
|
|
|
|
|
code: 'MODERATION_BLOCK',
|
|
|
|
|
txt: reason
|
|
|
|
|
};
|
|
|
|
|
BlockStore.archive(Env, key, archiveReason, err => {
|
2022-09-07 12:45:19 +00:00
|
|
|
|
Env.Log.info("ARCHIVE_BLOCK_BY_ADMIN", {
|
2022-08-11 06:23:03 +00:00
|
|
|
|
error: err,
|
|
|
|
|
key: key,
|
|
|
|
|
reason: reason || '',
|
|
|
|
|
});
|
|
|
|
|
cb(err);
|
|
|
|
|
});
|
2023-11-17 16:19:04 +00:00
|
|
|
|
let SSOUtils = Env.plugins && Env.plugins.SSO && Env.plugins.SSO.utils;
|
2023-11-06 15:27:52 +00:00
|
|
|
|
if (SSOUtils) { SSOUtils.deleteAccount(Env, key, () => {}); }
|
2022-08-11 06:23:03 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var restoreArchivedBlock = function (Env, Server, cb, data) {
|
|
|
|
|
var args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args) { return void cb("INVALID_ARGS"); }
|
|
|
|
|
var key = args.key;
|
|
|
|
|
var reason = args.reason;
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
BlockStore.restore(Env, key, err => {
|
2022-09-07 12:45:19 +00:00
|
|
|
|
Env.Log.info("RESTORE_ARCHIVED_BLOCK_BY_ADMIN", {
|
2022-08-11 06:23:03 +00:00
|
|
|
|
error: err,
|
|
|
|
|
key: key,
|
|
|
|
|
reason: reason || '',
|
|
|
|
|
});
|
2023-10-26 15:55:54 +00:00
|
|
|
|
|
|
|
|
|
// Also restore SSO data
|
2023-11-17 16:19:04 +00:00
|
|
|
|
let SSOUtils = Env.plugins && Env.plugins.SSO && Env.plugins.SSO.utils;
|
2023-11-06 15:27:52 +00:00
|
|
|
|
if (SSOUtils) { SSOUtils.restoreAccount(Env, key, () => {}); }
|
2023-10-26 15:55:54 +00:00
|
|
|
|
|
2022-08-11 06:23:03 +00:00
|
|
|
|
cb(err);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-14 15:49:16 +00:00
|
|
|
|
/*
|
|
|
|
|
// NOTE: Deprecated, archive whole account now
|
2022-08-11 06:23:03 +00:00
|
|
|
|
var restoreArchivedPinLog = function (Env, Server, cb, data) {
|
2022-08-24 04:40:32 +00:00
|
|
|
|
var args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
|
|
|
|
var key = args.key;
|
|
|
|
|
var reason = args.reason || '';
|
2022-08-11 06:23:03 +00:00
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
var safeKey = Util.escapeKeyCharacters(key);
|
|
|
|
|
Env.pinStore.restoreArchivedChannel(safeKey, function (err) {
|
2022-08-11 13:00:19 +00:00
|
|
|
|
Core.expireSession(Env.Sessions, safeKey);
|
2022-08-11 06:23:03 +00:00
|
|
|
|
if (err) {
|
|
|
|
|
Env.Log.error("RESTORE_ARCHIVED_PIN_LOG_BY_ADMIN", {
|
|
|
|
|
error: err,
|
|
|
|
|
safeKey: safeKey,
|
2022-08-24 04:40:32 +00:00
|
|
|
|
reason: reason,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
Env.Log.info('RESTORE_ARCHIVED_PIN_LOG_BY_ADMIN', {
|
|
|
|
|
safeKey: safeKey,
|
2022-08-24 04:40:32 +00:00
|
|
|
|
reason: reason,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
cb(err);
|
|
|
|
|
});
|
|
|
|
|
};
|
2023-09-14 15:49:16 +00:00
|
|
|
|
*/
|
2022-08-11 06:23:03 +00:00
|
|
|
|
|
|
|
|
|
var archiveOwnedDocuments = function (Env, Server, cb, data) {
|
|
|
|
|
Env.Log.debug('ARCHIVE_OWNED_DOCUMENTS', data);
|
|
|
|
|
cb("NOT_IMPLEMENTED");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// quotas...
|
|
|
|
|
var getUserQuota = function (Env, Server, cb, data) {
|
|
|
|
|
var key = Array.isArray(data) && data[1];
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
Pinning.getLimit(Env, key, cb);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getUserStorageStats = function (Env, Server, cb, data) {
|
|
|
|
|
var key = Array.isArray(data) && data[1];
|
|
|
|
|
if (!isValidKey(key)) { return void cb("EINVAL"); }
|
|
|
|
|
var safeKey = Util.escapeKeyCharacters(key);
|
|
|
|
|
|
|
|
|
|
Env.getPinState(safeKey, function (err, value) {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
try {
|
|
|
|
|
var res = {
|
|
|
|
|
channels: 0,
|
|
|
|
|
files: 0,
|
|
|
|
|
};
|
|
|
|
|
Object.keys(value).forEach(k => {
|
|
|
|
|
switch (k.length) {
|
|
|
|
|
case 32: return void ((res.channels++));
|
|
|
|
|
case 48: return void ((res.files++));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return void cb(void 0, res);
|
|
|
|
|
} catch (err2) { }
|
|
|
|
|
cb("UNEXPECTED_SERVER_ERROR");
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getStoredMetadata = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (!Core.isValidId(id)) { return void cb('INVALID_CHAN'); }
|
|
|
|
|
Env.computeMetadata(id, function (err, data) {
|
|
|
|
|
cb(err, data);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getDocumentSize = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (!Core.isValidId(id)) { return void cb('INVALID_CHAN'); }
|
|
|
|
|
Env.getFileSize(id, (err, size) => {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
cb(err, size);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getLastChannelTime = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (!Core.isValidId(id)) { return void cb('INVALID_CHAN'); }
|
|
|
|
|
Env.getLastChannelTime(id, function (err, time) {
|
2022-08-24 04:40:32 +00:00
|
|
|
|
if (err) { return void cb(err && err.code); }
|
2022-08-11 06:23:03 +00:00
|
|
|
|
cb(err, time);
|
|
|
|
|
});
|
2021-01-31 09:58:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-08-26 12:41:59 +00:00
|
|
|
|
var getMetadataHistory = function (Env, Server, cb, data) {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
if (!Core.isValidId(id)) { return void cb('INVALID_CHAN'); }
|
|
|
|
|
|
|
|
|
|
var lines = [];
|
|
|
|
|
Env.msgStore.readChannelMetadata(id, (err, line) => {
|
|
|
|
|
if (err) { return; }
|
|
|
|
|
lines.push(line);
|
|
|
|
|
}, err => {
|
|
|
|
|
if (err) {
|
|
|
|
|
Env.Log.error('ADMIN_GET_METADATA_HISTORY', {
|
|
|
|
|
error: err,
|
|
|
|
|
id: id,
|
|
|
|
|
});
|
|
|
|
|
return void cb(err);
|
|
|
|
|
}
|
|
|
|
|
cb(void 0, lines);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-12-01 14:44:20 +00:00
|
|
|
|
var getKnownUsers = (Env, Server, cb) => {
|
|
|
|
|
Users.getAll(Env, cb);
|
|
|
|
|
};
|
|
|
|
|
var addKnownUser = (Env, Server, cb, data, unsafeKey) => {
|
|
|
|
|
var obj = Array.isArray(data) && data[1];
|
|
|
|
|
var edPublic = obj.edPublic;
|
|
|
|
|
var block = obj.block;
|
|
|
|
|
var alias = obj.alias;
|
|
|
|
|
var userData = {
|
|
|
|
|
edPublic,
|
|
|
|
|
block,
|
|
|
|
|
alias,
|
2024-01-18 14:46:35 +00:00
|
|
|
|
email: obj.email,
|
2024-01-30 11:28:29 +00:00
|
|
|
|
name: obj.name,
|
2023-12-01 14:44:20 +00:00
|
|
|
|
type: 'manual'
|
|
|
|
|
};
|
|
|
|
|
Users.add(Env, edPublic, userData, unsafeKey, cb);
|
|
|
|
|
};
|
2024-01-18 15:32:06 +00:00
|
|
|
|
var deleteKnownUser = (Env, Server, cb, data) => {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
Users.delete(Env, id, cb);
|
|
|
|
|
};
|
|
|
|
|
var updateKnownUser = (Env, Server, cb, data) => {
|
|
|
|
|
var args = Array.isArray(data) && data[1];
|
|
|
|
|
var edPublic = args.edPublic;
|
|
|
|
|
var changes = args.changes;
|
|
|
|
|
Users.update(Env, edPublic, changes, cb);
|
|
|
|
|
};
|
2023-12-01 14:44:20 +00:00
|
|
|
|
|
|
|
|
|
var getInvitations = (Env, Server, cb) => {
|
|
|
|
|
Invitation.getAll(Env, cb);
|
|
|
|
|
};
|
|
|
|
|
var createInvitation = (Env, Server, cb, data, unsafeKey) => {
|
2024-01-18 14:46:35 +00:00
|
|
|
|
const args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
|
|
|
|
Invitation.create(Env, args.alias, args.email, cb, unsafeKey);
|
2023-12-01 14:44:20 +00:00
|
|
|
|
};
|
|
|
|
|
var deleteInvitation = (Env, Server, cb, data) => {
|
|
|
|
|
var id = Array.isArray(data) && data[1];
|
|
|
|
|
Invitation.delete(Env, id, cb);
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-22 17:46:26 +00:00
|
|
|
|
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);
|
|
|
|
|
};
|
2024-02-27 16:17:45 +00:00
|
|
|
|
var archiveSupport = (Env, Server, cb) => {
|
2024-02-27 15:52:30 +00:00
|
|
|
|
let supportPinKey = Env.supportPinKey;
|
|
|
|
|
getPinList(Env, Server, (err, list) => {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
let n = nThen;
|
|
|
|
|
list.forEach(id => {
|
|
|
|
|
n = n(waitFor => {
|
|
|
|
|
archiveDocument(Env, Server, waitFor(), [null, {id, reason:'DISABLE_SUPPORT'}]);
|
|
|
|
|
}).nThen;
|
|
|
|
|
});
|
|
|
|
|
n(() => {
|
|
|
|
|
cb();
|
|
|
|
|
});
|
|
|
|
|
}, [null, supportPinKey]);
|
|
|
|
|
};
|
2024-02-22 17:46:26 +00:00
|
|
|
|
|
2024-03-15 15:09:23 +00:00
|
|
|
|
const MAX_LOGO_SIZE = 200*1024; // 200KB
|
2024-03-15 15:38:06 +00:00
|
|
|
|
var removeLogo = (Env, Server, cb) => {
|
|
|
|
|
Fse.unlink('./customize/CryptPad_logo_hero.svg', (err) => {
|
|
|
|
|
cb(err);
|
|
|
|
|
});
|
|
|
|
|
};
|
2024-03-15 15:09:23 +00:00
|
|
|
|
var uploadLogo = (Env, Server, cb, data, unsafeKey) => {
|
|
|
|
|
const args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
|
|
|
|
let dataURL = args.dataURL;
|
|
|
|
|
|
|
|
|
|
// (size*4/3) + 24 ==> base64 and dataURL overhead
|
|
|
|
|
if (!dataURL || dataURL.length > ((MAX_LOGO_SIZE*4/3)+24)) {
|
|
|
|
|
return void cb('E_TOO_LARGE');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let s = dataURL.split(',');
|
|
|
|
|
let base64 = s[1];
|
|
|
|
|
let mime = s[0].slice(s[0].indexOf(":")+1, s[0].indexOf(";"));
|
|
|
|
|
if (!base64 || !mime) { return void cb('EINVAL'); }
|
|
|
|
|
let buf;
|
|
|
|
|
try {
|
|
|
|
|
buf = Buffer.from(base64, 'base64');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return void cb(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nThen(waitFor => {
|
|
|
|
|
Fse.mkdirp('customize', {}, waitFor((err) => {
|
|
|
|
|
if (!err) { return; }
|
|
|
|
|
waitFor.abort();
|
|
|
|
|
return void cb(err);
|
|
|
|
|
}));
|
|
|
|
|
}).nThen(waitFor => {
|
|
|
|
|
Fse.writeFile('./customize/CryptPad_logo_hero.svg', buf, waitFor((err) => {
|
|
|
|
|
if (!err) { return; }
|
|
|
|
|
waitFor.abort();
|
|
|
|
|
return void cb(err);
|
|
|
|
|
}));
|
|
|
|
|
}).nThen(() => {
|
|
|
|
|
adminDecree(Env, null, function (err) {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
Env.flushCache();
|
|
|
|
|
cb(void 0, true);
|
|
|
|
|
}, ['UPLOAD_LOGO', [
|
|
|
|
|
'SET_LOGO_MIME',
|
|
|
|
|
[mime]
|
|
|
|
|
]], unsafeKey);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-19 17:16:01 +00:00
|
|
|
|
var changeColor = (Env, Server, cb, data, unsafeKey) => {
|
|
|
|
|
const args = Array.isArray(data) && data[1];
|
|
|
|
|
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
|
|
|
|
|
let color = args.color;
|
|
|
|
|
adminDecree(Env, null, function (err) {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
Env.flushCache();
|
|
|
|
|
cb(void 0, true);
|
|
|
|
|
}, ['CHANGE_COLOR', [
|
|
|
|
|
'SET_ACCENT_COLOR',
|
|
|
|
|
[color]
|
|
|
|
|
]], unsafeKey);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2020-02-27 20:00:31 +00:00
|
|
|
|
var commands = {
|
|
|
|
|
ACTIVE_SESSIONS: getActiveSessions,
|
|
|
|
|
ACTIVE_PADS: getActiveChannelCount,
|
|
|
|
|
REGISTERED_USERS: getRegisteredUsers,
|
|
|
|
|
DISK_USAGE: getDiskUsage,
|
|
|
|
|
FLUSH_CACHE: flushCache,
|
|
|
|
|
SHUTDOWN: shutdown,
|
2020-03-12 15:35:16 +00:00
|
|
|
|
GET_FILE_DESCRIPTOR_COUNT: getFileDescriptorCount,
|
2020-03-12 16:09:44 +00:00
|
|
|
|
GET_FILE_DESCRIPTOR_LIMIT: getFileDescriptorLimit,
|
2020-03-27 23:59:26 +00:00
|
|
|
|
GET_CACHE_STATS: getCacheStats,
|
2020-09-25 08:54:26 +00:00
|
|
|
|
|
2022-08-11 06:23:03 +00:00
|
|
|
|
GET_PIN_ACTIVITY: getPinActivity,
|
|
|
|
|
IS_USER_ONLINE: isUserOnline,
|
|
|
|
|
GET_USER_QUOTA: getUserQuota,
|
|
|
|
|
GET_USER_STORAGE_STATS: getUserStorageStats,
|
|
|
|
|
GET_PIN_LOG_STATUS: getPinLogStatus,
|
|
|
|
|
|
2022-08-26 12:41:59 +00:00
|
|
|
|
GET_METADATA_HISTORY: getMetadataHistory,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
GET_STORED_METADATA: getStoredMetadata,
|
|
|
|
|
GET_DOCUMENT_SIZE: getDocumentSize,
|
|
|
|
|
GET_LAST_CHANNEL_TIME: getLastChannelTime,
|
|
|
|
|
GET_DOCUMENT_STATUS: getDocumentStatus,
|
|
|
|
|
|
2023-05-16 13:11:43 +00:00
|
|
|
|
DISABLE_MFA: disableMFA,
|
|
|
|
|
|
2022-08-11 06:23:03 +00:00
|
|
|
|
GET_PIN_LIST: getPinList,
|
|
|
|
|
GET_PIN_HISTORY: getPinHistory,
|
2023-09-14 15:49:16 +00:00
|
|
|
|
//ARCHIVE_PIN_LOG: archivePinLog,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
ARCHIVE_OWNED_DOCUMENTS: archiveOwnedDocuments,
|
2023-09-14 15:49:16 +00:00
|
|
|
|
//RESTORE_ARCHIVED_PIN_LOG: restoreArchivedPinLog,
|
2022-08-11 06:23:03 +00:00
|
|
|
|
|
|
|
|
|
ARCHIVE_BLOCK: archiveBlock,
|
|
|
|
|
RESTORE_ARCHIVED_BLOCK: restoreArchivedBlock,
|
|
|
|
|
|
2020-09-25 08:54:26 +00:00
|
|
|
|
ARCHIVE_DOCUMENT: archiveDocument,
|
|
|
|
|
RESTORE_ARCHIVED_DOCUMENT: restoreArchivedDocument,
|
2020-10-02 07:24:17 +00:00
|
|
|
|
|
2023-09-05 14:31:04 +00:00
|
|
|
|
ARCHIVE_ACCOUNT: archiveAccount,
|
|
|
|
|
RESTORE_ACCOUNT: restoreAccount,
|
2023-09-14 15:49:16 +00:00
|
|
|
|
GET_ACCOUNT_ARCHIVE_STATUS: getAccountArchiveStatus,
|
2023-09-05 14:31:04 +00:00
|
|
|
|
|
2020-10-20 11:42:26 +00:00
|
|
|
|
CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex,
|
|
|
|
|
GET_CACHED_CHANNEL_INDEX: getChannelIndex,
|
2021-05-07 12:37:13 +00:00
|
|
|
|
// TODO implement admin historyTrim
|
|
|
|
|
// TODO implement kick from channel
|
|
|
|
|
// TODO implement force-disconnect user(s)?
|
2020-10-20 11:42:26 +00:00
|
|
|
|
|
|
|
|
|
CLEAR_CACHED_CHANNEL_METADATA: clearChannelMetadata,
|
|
|
|
|
GET_CACHED_CHANNEL_METADATA: getChannelMetadata,
|
2020-10-20 11:37:55 +00:00
|
|
|
|
|
2020-10-02 07:24:17 +00:00
|
|
|
|
ADMIN_DECREE: adminDecree,
|
|
|
|
|
INSTANCE_STATUS: instanceStatus,
|
2020-10-07 09:38:33 +00:00
|
|
|
|
GET_LIMITS: getLimits,
|
2020-10-15 07:41:09 +00:00
|
|
|
|
SET_LAST_EVICTION: setLastEviction,
|
2020-12-08 05:14:46 +00:00
|
|
|
|
GET_WORKER_PROFILES: getWorkerProfiles,
|
2021-01-31 09:58:45 +00:00
|
|
|
|
GET_USER_TOTAL_SIZE: getUserTotalSize,
|
2022-09-13 13:02:50 +00:00
|
|
|
|
|
|
|
|
|
REMOVE_DOCUMENT: removeDocument,
|
2023-12-01 14:44:20 +00:00
|
|
|
|
|
|
|
|
|
GET_ALL_INVITATIONS: getInvitations,
|
|
|
|
|
CREATE_INVITATION: createInvitation,
|
|
|
|
|
DELETE_INVITATION: deleteInvitation,
|
|
|
|
|
|
|
|
|
|
GET_ALL_USERS: getKnownUsers,
|
|
|
|
|
ADD_KNOWN_USER: addKnownUser,
|
2024-01-18 15:32:06 +00:00
|
|
|
|
DELETE_KNOWN_USER: deleteKnownUser,
|
|
|
|
|
UPDATE_KNOWN_USER: updateKnownUser,
|
2024-02-22 17:46:26 +00:00
|
|
|
|
|
2024-02-27 15:52:30 +00:00
|
|
|
|
ARCHIVE_SUPPORT: archiveSupport,
|
2024-02-22 17:46:26 +00:00
|
|
|
|
GET_MODERATORS: getModerators,
|
|
|
|
|
ADD_MODERATOR: addModerator,
|
|
|
|
|
REMOVE_MODERATOR: removeModerator,
|
2024-03-20 11:36:34 +00:00
|
|
|
|
|
2024-03-15 15:09:23 +00:00
|
|
|
|
UPLOAD_LOGO: uploadLogo,
|
2024-03-15 15:38:06 +00:00
|
|
|
|
REMOVE_LOGO: removeLogo,
|
2024-03-19 17:16:01 +00:00
|
|
|
|
CHANGE_COLOR: changeColor,
|
2020-02-27 20:00:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-12-20 15:56:41 +00:00
|
|
|
|
// addFirstAdmin is an anon_rpc command
|
|
|
|
|
Admin.addFirstAdmin = function (Env, data, cb) {
|
2023-01-12 14:54:16 +00:00
|
|
|
|
if (!Env.installToken) { return void cb('EINVAL'); }
|
2022-12-20 15:56:41 +00:00
|
|
|
|
var token = data.token;
|
2023-01-12 14:54:16 +00:00
|
|
|
|
if (!token || !data.edPublic) { return void cb('MISSING_ARGS'); }
|
|
|
|
|
if (token.length !== 64 || data.edPublic.length !== 44) { return void cb('INVALID_ARGS'); }
|
2022-12-20 15:56:41 +00:00
|
|
|
|
if (token !== Env.installToken) { return void cb('FORBIDDEN'); }
|
|
|
|
|
if (Array.isArray(Env.admins) && Env.admins.length) { return void cb('EEXISTS'); }
|
|
|
|
|
|
|
|
|
|
var key = data.edPublic;
|
|
|
|
|
|
2022-12-20 16:25:54 +00:00
|
|
|
|
adminDecree(Env, null, function (err) {
|
|
|
|
|
if (err) { return void cb(err); }
|
|
|
|
|
Env.flushCache();
|
|
|
|
|
cb();
|
|
|
|
|
}, ['ADD_FIRST_ADMIN', [
|
2022-12-20 15:56:41 +00:00
|
|
|
|
'ADD_ADMIN_KEY',
|
|
|
|
|
[key]
|
|
|
|
|
]], "");
|
|
|
|
|
};
|
|
|
|
|
|
2020-02-27 20:00:31 +00:00
|
|
|
|
Admin.command = function (Env, safeKey, data, _cb, Server) {
|
|
|
|
|
var cb = Util.once(Util.mkAsync(_cb));
|
|
|
|
|
|
2020-01-24 16:25:48 +00:00
|
|
|
|
var admins = Env.admins;
|
2020-10-09 07:58:13 +00:00
|
|
|
|
|
|
|
|
|
var unsafeKey = Util.unescapeKeyCharacters(safeKey);
|
|
|
|
|
if (admins.indexOf(unsafeKey) === -1) {
|
2020-01-24 16:25:48 +00:00
|
|
|
|
return void cb("FORBIDDEN");
|
|
|
|
|
}
|
2020-02-03 19:20:05 +00:00
|
|
|
|
|
2020-02-27 20:00:31 +00:00
|
|
|
|
var command = commands[data[0]];
|
|
|
|
|
|
|
|
|
|
if (typeof(command) === 'function') {
|
2020-10-09 07:58:13 +00:00
|
|
|
|
return void command(Env, Server, cb, data, unsafeKey);
|
2020-01-24 16:25:48 +00:00
|
|
|
|
}
|
2020-02-27 20:00:31 +00:00
|
|
|
|
|
|
|
|
|
return void cb('UNHANDLED_ADMIN_COMMAND');
|
2020-01-24 16:25:48 +00:00
|
|
|
|
};
|
|
|
|
|
|