Add admin panel
This commit is contained in:
parent
5a629e8681
commit
bb5f03bd0f
@ -49,6 +49,20 @@ var baseCSP = [
|
|||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
/* =====================
|
||||||
|
* Admin
|
||||||
|
* ===================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CryptPad now contains an administration panel. Its access is restricted to specific
|
||||||
|
* users using the following list.
|
||||||
|
* To give access to the admin panel to a user account, just add their user id,
|
||||||
|
* which can be found on the settings page for registered users.
|
||||||
|
* Entries should be strings separated by a comma.
|
||||||
|
*/
|
||||||
|
adminKeys: [
|
||||||
|
//"https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=",
|
||||||
|
],
|
||||||
|
|
||||||
/* =====================
|
/* =====================
|
||||||
* Infra setup
|
* Infra setup
|
||||||
|
|||||||
@ -131,6 +131,10 @@
|
|||||||
@colortheme_kanban-color: #000;
|
@colortheme_kanban-color: #000;
|
||||||
@colortheme_kanban-warn: #e6385d;
|
@colortheme_kanban-warn: #e6385d;
|
||||||
|
|
||||||
|
@colortheme_admin-bg: #7c0404;
|
||||||
|
@colortheme_admin-color: #FFF;
|
||||||
|
@colortheme_admin-warn: #ffae00;
|
||||||
|
|
||||||
// Sidebar layout (profile / settings)
|
// Sidebar layout (profile / settings)
|
||||||
@colortheme_sidebar-active: #fff;
|
@colortheme_sidebar-active: #fff;
|
||||||
@colortheme_sidebar-left-bg: #eee;
|
@colortheme_sidebar-left-bg: #eee;
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
.cp-icon-color-ooslide { color: @colortheme_ooslide-bg; }
|
.cp-icon-color-ooslide { color: @colortheme_ooslide-bg; }
|
||||||
.cp-icon-color-sheet { color: @colortheme_oocell-bg; }
|
.cp-icon-color-sheet { color: @colortheme_oocell-bg; }
|
||||||
.cp-icon-color-kanban { color: @colortheme_kanban-bg; }
|
.cp-icon-color-kanban { color: @colortheme_kanban-bg; }
|
||||||
|
.cp-icon-color-admin { color: @colortheme_admin-bg; }
|
||||||
|
|
||||||
.cp-border-color-pad { border-color: @colortheme_pad-bg !important; }
|
.cp-border-color-pad { border-color: @colortheme_pad-bg !important; }
|
||||||
.cp-border-color-code { border-color: @colortheme_code-bg !important; }
|
.cp-border-color-code { border-color: @colortheme_code-bg !important; }
|
||||||
@ -37,5 +38,6 @@
|
|||||||
.cp-border-color-ooslide { border-color: @colortheme_ooslide-bg !important; }
|
.cp-border-color-ooslide { border-color: @colortheme_ooslide-bg !important; }
|
||||||
.cp-border-color-sheet { border-color: @colortheme_oocell-bg !important; }
|
.cp-border-color-sheet { border-color: @colortheme_oocell-bg !important; }
|
||||||
.cp-border-color-kanban { border-color: @colortheme_kanban-bg !important; }
|
.cp-border-color-kanban { border-color: @colortheme_kanban-bg !important; }
|
||||||
|
.cp-border-color-admin { border-color: @colortheme_admin-bg !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,8 @@
|
|||||||
"sortify": "^1.0.4",
|
"sortify": "^1.0.4",
|
||||||
"stream-to-pull-stream": "^1.7.2",
|
"stream-to-pull-stream": "^1.7.2",
|
||||||
"tweetnacl": "~0.12.2",
|
"tweetnacl": "~0.12.2",
|
||||||
"ws": "^1.0.1"
|
"ws": "^1.0.1",
|
||||||
|
"get-folder-size": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"flow-bin": "^0.59.0",
|
"flow-bin": "^0.59.0",
|
||||||
|
|||||||
106
rpc.js
106
rpc.js
@ -15,6 +15,7 @@ const Package = require('./package.json');
|
|||||||
const Pinned = require('./pinned');
|
const Pinned = require('./pinned');
|
||||||
const Saferphore = require("saferphore");
|
const Saferphore = require("saferphore");
|
||||||
const nThen = require("nthen");
|
const nThen = require("nthen");
|
||||||
|
const getFolderSize = require("get-folder-size");
|
||||||
|
|
||||||
var RPC = module.exports;
|
var RPC = module.exports;
|
||||||
|
|
||||||
@ -1484,6 +1485,101 @@ var isNewChannel = function (Env, channel, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var getDiskUsage = function (Env, cb) {
|
||||||
|
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 () {
|
||||||
|
cb (void 0, data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var getRegisteredUsers = function (Env, cb) {
|
||||||
|
var dir = Env.paths.pin;
|
||||||
|
var folders;
|
||||||
|
var users = 0;
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
Fs.readdir(dir, waitFor(function (err, list) {
|
||||||
|
if (err) {
|
||||||
|
waitFor.abort();
|
||||||
|
return void cb(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; }
|
||||||
|
users += list.length;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}).nThen(function () {
|
||||||
|
cb(void 0, users);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var getActiveSessions = function (Env, ctx, cb) {
|
||||||
|
var total = ctx.users ? Object.keys(ctx.users).length : '?';
|
||||||
|
|
||||||
|
var ips = [];
|
||||||
|
Object.keys(ctx.users).forEach(function (u) {
|
||||||
|
var user = ctx.users[u];
|
||||||
|
var socket = user.socket;
|
||||||
|
var conn = socket.upgradeReq.connection;
|
||||||
|
if (ips.indexOf(conn.remoteAddress) === -1) {
|
||||||
|
ips.push(conn.remoteAddress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cb (void 0, [total, ips.length]);
|
||||||
|
};
|
||||||
|
|
||||||
|
var adminCommand = function (Env, ctx, publicKey, config, data, cb) {
|
||||||
|
var admins = [];
|
||||||
|
try {
|
||||||
|
admins = (config.adminKeys || []).map(function (k) {
|
||||||
|
k = k.replace(/\/+$/, '');
|
||||||
|
var s = k.split('/');
|
||||||
|
return s[s.length-1];
|
||||||
|
});
|
||||||
|
} catch (e) { console.error("Can't parse admin keys. Please update or fix your config.js file!"); }
|
||||||
|
if (admins.indexOf(publicKey) === -1) {
|
||||||
|
return void cb("FORBIDDEN");
|
||||||
|
}
|
||||||
|
// Handle commands here
|
||||||
|
switch (data[0]) {
|
||||||
|
case 'ACTIVE_SESSIONS':
|
||||||
|
return getActiveSessions(Env, ctx, cb);
|
||||||
|
case 'ACTIVE_PADS':
|
||||||
|
return cb(void 0, ctx.channels ? Object.keys(ctx.channels).length : '?');
|
||||||
|
case 'REGISTERED_USERS':
|
||||||
|
return getRegisteredUsers(Env, cb);
|
||||||
|
case 'DISK_USAGE':
|
||||||
|
return getDiskUsage(Env, cb);
|
||||||
|
case 'FLUSH_CACHE':
|
||||||
|
config.flushCache();
|
||||||
|
return cb(void 0, true);
|
||||||
|
default:
|
||||||
|
return cb('UNHANDLED_ADMIN_COMMAND');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var isUnauthenticatedCall = function (call) {
|
var isUnauthenticatedCall = function (call) {
|
||||||
return [
|
return [
|
||||||
'GET_FILE_SIZE',
|
'GET_FILE_SIZE',
|
||||||
@ -1516,6 +1612,7 @@ var isAuthenticatedCall = function (call) {
|
|||||||
'REMOVE_PINS',
|
'REMOVE_PINS',
|
||||||
'WRITE_LOGIN_BLOCK',
|
'WRITE_LOGIN_BLOCK',
|
||||||
'REMOVE_LOGIN_BLOCK',
|
'REMOVE_LOGIN_BLOCK',
|
||||||
|
'ADMIN',
|
||||||
].indexOf(call) !== -1;
|
].indexOf(call) !== -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1587,6 +1684,7 @@ RPC.create = function (
|
|||||||
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
|
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||||
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||||
paths.block = keyOrDefaultString('blockPath', './block');
|
paths.block = keyOrDefaultString('blockPath', './block');
|
||||||
|
paths.data = keyOrDefaultString('filePath', './datastore');
|
||||||
|
|
||||||
var isUnauthenticateMessage = function (msg) {
|
var isUnauthenticateMessage = function (msg) {
|
||||||
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
||||||
@ -1872,6 +1970,14 @@ RPC.create = function (
|
|||||||
}
|
}
|
||||||
Respond(e);
|
Respond(e);
|
||||||
});
|
});
|
||||||
|
case 'ADMIN':
|
||||||
|
return void adminCommand(Env, ctx, safeKey, config, msg[1], function (e, result) {
|
||||||
|
if (e) {
|
||||||
|
WARN(e, result);
|
||||||
|
return void Respond(e);
|
||||||
|
}
|
||||||
|
Respond(void 0, result);
|
||||||
|
});
|
||||||
default:
|
default:
|
||||||
return void Respond('UNSUPPORTED_RPC_CALL', msg);
|
return void Respond('UNSUPPORTED_RPC_CALL', msg);
|
||||||
}
|
}
|
||||||
|
|||||||
13
server.js
13
server.js
@ -54,6 +54,10 @@ if (FRESH_MODE) {
|
|||||||
console.log("FRESH MODE ENABLED");
|
console.log("FRESH MODE ENABLED");
|
||||||
FRESH_KEY = +new Date();
|
FRESH_KEY = +new Date();
|
||||||
}
|
}
|
||||||
|
config.flushCache = function () {
|
||||||
|
FRESH_KEY = +new Date();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const clone = (x) => (JSON.parse(JSON.stringify(x)));
|
const clone = (x) => (JSON.parse(JSON.stringify(x)));
|
||||||
|
|
||||||
@ -167,6 +171,14 @@ if (config.privKeyAndCertFiles) {
|
|||||||
|
|
||||||
app.get('/api/config', function(req, res){
|
app.get('/api/config', function(req, res){
|
||||||
var host = req.headers.host.replace(/\:[0-9]+/, '');
|
var host = req.headers.host.replace(/\:[0-9]+/, '');
|
||||||
|
var admins = [];
|
||||||
|
try {
|
||||||
|
admins = (config.adminKeys || []).map(function (k) {
|
||||||
|
k = k.replace(/\/+$/, '');
|
||||||
|
var s = k.split('/');
|
||||||
|
return s[s.length-1];
|
||||||
|
});
|
||||||
|
} catch (e) { console.error("Can't parse admin keys"); }
|
||||||
res.setHeader('Content-Type', 'text/javascript');
|
res.setHeader('Content-Type', 'text/javascript');
|
||||||
res.send('define(function(){\n' + [
|
res.send('define(function(){\n' + [
|
||||||
'var obj = ' + JSON.stringify({
|
'var obj = ' + JSON.stringify({
|
||||||
@ -180,6 +192,7 @@ app.get('/api/config', function(req, res){
|
|||||||
websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' +
|
websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' +
|
||||||
websocketPort + '/cryptpad_websocket',
|
websocketPort + '/cryptpad_websocket',
|
||||||
httpUnsafeOrigin: config.httpUnsafeOrigin,
|
httpUnsafeOrigin: config.httpUnsafeOrigin,
|
||||||
|
adminKeys: admins,
|
||||||
}, null, '\t'),
|
}, null, '\t'),
|
||||||
'obj.httpSafeOrigin = ' + (function () {
|
'obj.httpSafeOrigin = ' + (function () {
|
||||||
if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; }
|
if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; }
|
||||||
|
|||||||
@ -17,6 +17,6 @@ define(function () {
|
|||||||
// Sub
|
// Sub
|
||||||
plan: 'CryptPad_plan',
|
plan: 'CryptPad_plan',
|
||||||
// Apps
|
// Apps
|
||||||
criticalApps: ['profile', 'settings', 'debug']
|
criticalApps: ['profile', 'settings', 'debug', 'admin']
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1634,6 +1634,14 @@ define([
|
|||||||
content: h('span', Messages.settingsButton)
|
content: h('span', Messages.settingsButton)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Add administration panel link if the user is an admin
|
||||||
|
if (priv.edPublic && Array.isArray(Config.adminKeys) && Config.adminKeys.indexOf(priv.edPublic) !== -1) {
|
||||||
|
options.push({
|
||||||
|
tag: 'a',
|
||||||
|
attributes: {'class': 'cp-toolbar-menu-admin fa fa-cogs'},
|
||||||
|
content: h('span', Messages.adminPage || 'Admin')
|
||||||
|
});
|
||||||
|
}
|
||||||
// Add login or logout button depending on the current status
|
// Add login or logout button depending on the current status
|
||||||
if (accountName) {
|
if (accountName) {
|
||||||
options.push({
|
options.push({
|
||||||
@ -1729,6 +1737,13 @@ define([
|
|||||||
window.parent.location = origin+'/settings/';
|
window.parent.location = origin+'/settings/';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$userAdmin.find('a.cp-toolbar-menu-admin').click(function () {
|
||||||
|
if (padType) {
|
||||||
|
window.open(origin+'/admin/');
|
||||||
|
} else {
|
||||||
|
window.parent.location = origin+'/admin/';
|
||||||
|
}
|
||||||
|
});
|
||||||
$userAdmin.find('a.cp-toolbar-menu-profile').click(function () {
|
$userAdmin.find('a.cp-toolbar-menu-profile').click(function () {
|
||||||
if (padType) {
|
if (padType) {
|
||||||
window.open(origin+'/profile/');
|
window.open(origin+'/profile/');
|
||||||
|
|||||||
@ -615,6 +615,11 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
common.adminRpc = function (data, cb) {
|
||||||
|
postMessage("ADMIN_RPC", data, cb);
|
||||||
|
};
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
common.onNetworkDisconnect = Util.mkEvent();
|
common.onNetworkDisconnect = Util.mkEvent();
|
||||||
common.onNetworkReconnect = Util.mkEvent();
|
common.onNetworkReconnect = Util.mkEvent();
|
||||||
|
|||||||
@ -947,6 +947,14 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
Store.adminRpc = function (clientId, data, cb) {
|
||||||
|
store.rpc.adminRpc(data, function (err, res) {
|
||||||
|
if (err) { return void cb({error: err}); }
|
||||||
|
cb(res);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
/////////////////////// PAD //////////////////////////////////////
|
/////////////////////// PAD //////////////////////////////////////
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -77,6 +77,8 @@ define([
|
|||||||
DRIVE_USEROBJECT: Store.userObjectCommand,
|
DRIVE_USEROBJECT: Store.userObjectCommand,
|
||||||
// Settings,
|
// Settings,
|
||||||
DELETE_ACCOUNT: Store.deleteAccount,
|
DELETE_ACCOUNT: Store.deleteAccount,
|
||||||
|
// Admin
|
||||||
|
ADMIN_RPC: Store.adminRpc,
|
||||||
};
|
};
|
||||||
|
|
||||||
Rpc.query = function (cmd, data, cb) {
|
Rpc.query = function (cmd, data, cb) {
|
||||||
|
|||||||
@ -58,6 +58,18 @@ define([
|
|||||||
rpc.send('UNPIN', channels, cb);
|
rpc.send('UNPIN', channels, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get data for the admin panel
|
||||||
|
exp.adminRpc = function (obj, cb) {
|
||||||
|
if (!obj.cmd) {
|
||||||
|
setTimeout(function () {
|
||||||
|
cb('[TypeError] admin rpc expects a command');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var params = [obj.cmd, obj.data];
|
||||||
|
rpc.send('ADMIN', params, cb);
|
||||||
|
};
|
||||||
|
|
||||||
// ask the server what it thinks your hash is
|
// ask the server what it thinks your hash is
|
||||||
exp.getServerHash = function (cb) {
|
exp.getServerHash = function (cb) {
|
||||||
rpc.send('GET_HASH', edPublic, function (e, hash) {
|
rpc.send('GET_HASH', edPublic, function (e, hash) {
|
||||||
|
|||||||
@ -409,6 +409,10 @@ define([
|
|||||||
setDocumentTitle();
|
setDocumentTitle();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sframeChan.on('EV_SET_HASH', function (hash) {
|
||||||
|
window.location.hash = hash;
|
||||||
|
});
|
||||||
|
|
||||||
Cryptpad.autoStore.onStoreRequest.reg(function (data) {
|
Cryptpad.autoStore.onStoreRequest.reg(function (data) {
|
||||||
sframeChan.event("EV_AUTOSTORE_DISPLAY_POPUP", data);
|
sframeChan.event("EV_AUTOSTORE_DISPLAY_POPUP", data);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -228,6 +228,10 @@ define([
|
|||||||
ctx.sframeChan.event('EV_SET_TAB_TITLE', newTitle);
|
ctx.sframeChan.event('EV_SET_TAB_TITLE', newTitle);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
funcs.setHash = function (hash) {
|
||||||
|
ctx.sframeChan.event('EV_SET_HASH', hash);
|
||||||
|
};
|
||||||
|
|
||||||
funcs.setLoginRedirect = function (cb) {
|
funcs.setLoginRedirect = function (cb) {
|
||||||
cb = cb || $.noop;
|
cb = cb || $.noop;
|
||||||
ctx.sframeChan.query('Q_SET_LOGIN_REDIRECT', null, cb);
|
ctx.sframeChan.query('Q_SET_LOGIN_REDIRECT', null, cb);
|
||||||
|
|||||||
@ -1511,6 +1511,7 @@ define([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
active = key;
|
active = key;
|
||||||
|
common.setHash(key);
|
||||||
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
|
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
|
||||||
$category.addClass('cp-leftside-active');
|
$category.addClass('cp-leftside-active');
|
||||||
showCategories(categories[key]);
|
showCategories(categories[key]);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user