Add admin panel

pull/1/head
yflory 6 years ago
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

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

@ -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…
Cancel
Save