From 41db88a360aa27f082401c11bbf61463dfd0e69c Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 8 Jun 2021 20:24:30 +0530 Subject: [PATCH 1/6] WIP admin 'network' panel --- config/config.example.js | 22 ---- lib/api.js | 32 ++++++ lib/commands/admin-rpc.js | 7 ++ lib/decrees.js | 22 ++++ lib/env.js | 14 ++- server.js | 43 +++---- www/admin/inner.js | 229 +++++++++++++++++++++++++++++++++++++- 7 files changed, 316 insertions(+), 53 deletions(-) diff --git a/config/config.example.js b/config/config.example.js index d05b985af..69f0b1e91 100644 --- a/config/config.example.js +++ b/config/config.example.js @@ -122,28 +122,6 @@ module.exports = { ], */ - /* We're very proud that CryptPad is available to the public as free software! - * We do, however, still need to pay our bills as we develop the platform. - * - * By default CryptPad will prompt users to consider donating to - * our OpenCollective campaign. We publish the state of our finances periodically - * so you can decide for yourself whether our expenses are reasonable. - * - * You can disable any solicitations for donations by setting 'removeDonateButton' to true, - * but we'd appreciate it if you didn't! - */ - //removeDonateButton: false, - - /* - * By default, CryptPad contacts one of our servers once a day. - * This check-in will also send some very basic information about your instance including its - * version and the adminEmail so we can reach you if we are aware of a serious problem. - * We will never sell it or send you marketing mail. - * - * If you want to block this check-in and remain set 'blockDailyCheck' to true. - */ - //blockDailyCheck: false, - /* ===================== * STORAGE * ===================== */ diff --git a/lib/api.js b/lib/api.js index 7152f75b9..26aaa3527 100644 --- a/lib/api.js +++ b/lib/api.js @@ -10,6 +10,8 @@ module.exports.create = function (Env) { nThen(function (w) { Decrees.load(Env, w(function (err) { + Env.flushCache(); + if (err) { log.error('DECREES_LOADING', { error: err.code || err, @@ -17,6 +19,36 @@ nThen(function (w) { }); console.error(err); } + + var stats = { + restrictRegistration: Env.restrictRegistration, + supportMailbox: Env.supportMailbox, + + defaultStorageLimit: Env.defaultStorageLimit, + maxUploadSize: Env.maxUploadSize, + premiumUploadSize: Env.premiumUploadSize, + + adminEmail: Env.adminEmail, + inactiveTime: Env.inactiveTime, + + accountRetentionTime: Env.accountRetentionTime, + archiveRetentionTime: Env.archiveRetentionTime, + + httpUnsafeOrigin: Env.httpUnsafeOrigin, + httpSafeOrigin: Env.httpSafeOrigin, + + adminKeys: Env.admins, + + consentToContact: Env.consentToContact, + listMyInstance: Env.listMyInstance, + provideAggregateStatistics: Env.provideAggregateStatistics, + + removeDonateButton: Env.removeDonateButton, + blockDailyCheck: Env.blockDailyCheck, + }; + + console.log(stats); + })); }).nThen(function () { // asynchronously create a historyKeeper and RPC together diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 9ab86bdf2..532f85382 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -317,6 +317,13 @@ var instanceStatus = function (Env, Server, cb) { maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, + + consentToContact: Env.consentToContact, + listMyInstance: Env.listMyInstance, + provideAggregateStatistics: Env.provideAggregateStatistics, + + removeDonateButton: Env.removeDonateButton, + blockDailyCheck: Env.blockDailyCheck, }); }; diff --git a/lib/decrees.js b/lib/decrees.js index eed840057..960ece1ef 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -34,6 +34,13 @@ SET_MAINTENANCE SET_ADMIN_EMAIL SET_SUPPORT_MAILBOX +// COMMUNITY PARTICIPATION AND GOVERNANCE +CONSENT_TO_CONTACT +LIST_MY_INSTANCE +PROVIDE_AGGREGATE_STATISTICS +REMOVE_DONATE_BUTTON +BLOCK_DAILY_CHECK + NOT IMPLEMENTED: // RESTRICTED REGISTRATION @@ -89,6 +96,21 @@ commands.DISABLE_INTEGRATED_EVICTION = makeBooleanSetter('disableIntegratedEvict // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['DISABLE_INTEGRATED_TASKS', [true]]], console.log) commands.DISABLE_INTEGRATED_TASKS = makeBooleanSetter('disableIntegratedTasks'); +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['CONSENT_TO_CONTACT', [true]]], console.log) +commands.CONSENT_TO_CONTACT = makeBooleanSetter('consentToContact'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['LIST_MY_INSTANCE', [true]]], console.log) +commands.LIST_MY_INSTANCE = makeBooleanSetter('listMyInstance'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['PROVIDE_AGGREGATE_STATISTICS', [true]]], console.log) +commands.PROVIDE_AGGREGATE_STATISTICS = makeBooleanSetter('provideAggregateStatistics'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['REMOVE_DONATE_BUTTON', [true]]], console.log) +commands.REMOVE_DONATE_BUTTON = makeBooleanSetter('removeDonateButton'); + +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['BLOCK_DAILY_CHECK', [true]]], console.log) +commands.BLOCK_DAILY_CHECK = makeBooleanSetter('blockDailyCheck'); + /* var isNonNegativeNumber = function (n) { return !(typeof(n) !== 'number' || isNaN(n) || n < 0); diff --git a/lib/env.js b/lib/env.js index 389f059f6..06169297e 100644 --- a/lib/env.js +++ b/lib/env.js @@ -11,9 +11,17 @@ const Core = require("./commands/core"); const Quota = require("./commands/quota"); const Util = require("./common-util"); -module.exports.create = function (config) { +var canonicalizeOrigin = function (s) { + if (typeof(s) === 'undefined') { return; } + return (s || '').trim().replace(/\/+$/, ''); +}; +module.exports.create = function (config) { const Env = { + httpUnsafeOrigin: canonicalizeOrigin(config.httpUnsafeOrigin), + httpSafeOrigin: canonicalizeOrigin(config.httpSafeOrigin), + removeDonateButton: config.removeDonateButton, + OFFLINE_MODE: false, FRESH_KEY: '', FRESH_MODE: true, @@ -97,6 +105,10 @@ module.exports.create = function (config) { allowSubscriptions: config.allowSubscriptions === true, blockDailyCheck: config.blockDailyCheck === true, + consentToContact: false, + listMyInstance: false, + provideAggregateStatistics: false, + myDomain: config.myDomain, mySubdomain: config.mySubdomain, // only exists for the accounts integration customLimits: {}, diff --git a/server.js b/server.js index d92540399..c84c9a78c 100644 --- a/server.js +++ b/server.js @@ -16,10 +16,6 @@ var Env = require("./lib/env").create(config); var app = Express(); -var canonicalizeOrigin = function (s) { - return (s || '').trim().replace(/\/+$/, ''); -}; - var fancyURL = function (domain, path) { try { if (domain && path) { return new URL(path, domain).href; } @@ -30,15 +26,10 @@ var fancyURL = function (domain, path) { (function () { // you absolutely must provide an 'httpUnsafeOrigin' - if (typeof(config.httpUnsafeOrigin) !== 'string') { + if (typeof(Env.httpUnsafeOrigin) !== 'string') { throw new Error("No 'httpUnsafeOrigin' provided"); } - config.httpUnsafeOrigin = canonicalizeOrigin(config.httpUnsafeOrigin); - if (typeof(config.httpSafeOrigin) === 'string') { - config.httpSafeOrigin = canonicalizeOrigin(config.httpSafeOrigin); - } - // fall back to listening on a local address // if httpAddress is not a string if (typeof(config.httpAddress) !== 'string') { @@ -50,7 +41,7 @@ var fancyURL = function (domain, path) { config.httpPort = 3000; } - if (typeof(config.httpSafeOrigin) !== 'string') { + if (typeof(Env.httpSafeOrigin) !== 'string') { Env.NO_SANDBOX = true; if (typeof(config.httpSafePort) !== 'number') { config.httpSafePort = config.httpPort + 1; @@ -76,7 +67,7 @@ var setHeaders = (function () { } // next define the base Content Security Policy (CSP) headers - if (typeof(config.contentSecurity) === 'string') { + if (typeof(config.contentSecurity) === 'string') { // XXX deprecate this headers['Content-Security-Policy'] = config.contentSecurity; if (!/;$/.test(headers['Content-Security-Policy'])) { headers['Content-Security-Policy'] += ';' } if (headers['Content-Security-Policy'].indexOf('frame-ancestors') === -1) { @@ -87,14 +78,14 @@ var setHeaders = (function () { } } else { // use the default CSP headers constructed with your domain - headers['Content-Security-Policy'] = Default.contentSecurity(config.httpUnsafeOrigin); + headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin); } const padHeaders = Util.clone(headers); if (typeof(config.padContentSecurity) === 'string') { padHeaders['Content-Security-Policy'] = config.padContentSecurity; } else { - padHeaders['Content-Security-Policy'] = Default.padContentSecurity(config.httpUnsafeOrigin); + padHeaders['Content-Security-Policy'] = Default.padContentSecurity(Env.httpUnsafeOrigin); } if (Object.keys(headers).length) { return function (req, res) { @@ -151,7 +142,7 @@ app.head(/^\/common\/feedback\.html/, function (req, res, next) { app.use('/blob', function (req, res, next) { if (req.method === 'HEAD') { - Express.static(Path.join(__dirname, (config.blobPath || './blob')), { + Express.static(Path.join(__dirname, Env.paths.blob), setHeaders: function (res, path, stat) { res.set('Access-Control-Allow-Origin', '*'); res.set('Access-Control-Allow-Headers', 'Content-Length'); @@ -191,13 +182,13 @@ var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); app.get(mainPagePattern, Express.static(__dirname + '/customize')); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); -app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { +app.use("/blob", Express.static(Path.join(__dirname, Env.paths.blob), { maxAge: Env.DEV_MODE? "0d": "365d" })); -app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), { +app.use("/datastore", Express.static(Path.join(__dirname, Env.paths.data), { maxAge: "0d" })); -app.use("/block", Express.static(Path.join(__dirname, (config.blockPath || '/block')), { +app.use("/block", Express.static(Path.join(__dirname, Env.paths.block), { maxAge: "0d", })); @@ -254,10 +245,10 @@ var serveConfig = makeRouteCache(function (host) { waitSeconds: 600, urlArgs: 'ver=' + Package.version + cacheString(), }, - removeDonateButton: (config.removeDonateButton === true), - allowSubscriptions: (config.allowSubscriptions === true), + removeDonateButton: (Env.removeDonateButton === true), + allowSubscriptions: (Env.allowSubscriptions === true), websocketPath: config.externalWebsocketURL, - httpUnsafeOrigin: config.httpUnsafeOrigin, + httpUnsafeOrigin: Env.httpUnsafeOrigin, adminEmail: Env.adminEmail, adminKeys: Env.admins, inactiveTime: Env.inactiveTime, @@ -265,10 +256,10 @@ var serveConfig = makeRouteCache(function (host) { defaultStorageLimit: Env.defaultStorageLimit, maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, - restrictRegistration: Env.restrictRegistration, // FIXME see the race condition in env.js + restrictRegistration: Env.restrictRegistration, }, null, '\t'), 'obj.httpSafeOrigin = ' + (function () { - if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; } + if (Env.httpSafeOrigin) { return '"' + Env.httpSafeOrigin + '"'; } if (config.httpSafePort) { return "(function () { return window.location.origin.replace(/\:[0-9]+$/, ':" + config.httpSafePort + "'); }())"; @@ -332,16 +323,16 @@ nThen(function (w) { var ps = port === 80? '': ':' + port; var roughAddress = 'http://' + hostName + ps; - var betterAddress = fancyURL(config.httpUnsafeOrigin); + var betterAddress = fancyURL(Env.httpUnsafeOrigin); if (betterAddress) { console.log('Serving content for %s via %s.\n', betterAddress, roughAddress); } else { console.log('Serving content via %s.\n', roughAddress); } - if (!Array.isArray(config.adminKeys)) { + if (!Env.admins.length) { console.log("Your instance is not correctly configured for safe use in production.\nSee %s for more information.\n", - fancyURL(config.httpUnsafeOrigin, '/checkup/') || 'https://your-domain.com/checkup/' + fancyURL(Env.httpUnsafeOrigin, '/checkup/') || 'https://your-domain.com/checkup/' ); } }); diff --git a/www/admin/inner.js b/www/admin/inner.js index 3956d7e50..8567c8852 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -85,15 +85,26 @@ define([ 'performance': [ // Msg.admin_cat_performance 'cp-admin-refresh-performance', 'cp-admin-performance-profiling', - ] + ], + 'network': [ // Msg.admin_cat_network + 'cp-admin-checkup', + 'cp-admin-block-daily-check', + 'cp-admin-consent-to-contact', + 'cp-admin-list-my-instance', + 'cp-admin-provide-aggregate-statistics', + 'cp-admin-remove-donate-button', + ], }; var create = {}; + var keyToCamlCase = function (key) { + return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); + }; + var makeBlock = function (key, addButton) { // Title, Hint, maybeButton // Convert to camlCase for translation keys - var safeKey = key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); - + var safeKey = keyToCamlCase(key); var $div = $('
', {'class': 'cp-admin-' + key + ' cp-sidebarlayout-element'}); $('