Merge branch 'staging' into soon

pull/1/head
ansuz 4 years ago
commit ef604422ab

@ -1,3 +1,38 @@
# 4.6.0
## Goals
* work on polls/surveys
* stabilize and implement tests
## Update notes
* checkup/server/config
* test for anti-FLoC header
* add anti-FloC header to server so the default dev server passes all tests
* update NGINX example to avoid duplicated headers
* simplify dev server headers
* adjust table borders for dark/light mode
* say what headers are wrong
* rename exported object in `application_config_internal.js` to avoid copypasta errors
* `AppConfig.disableAnonymousPadCreation = false;`
* note that it's only enforced client-side
* update lodash devDependency
## Features
* generate `supportMailbox` keys via the admin panel
* document this
* markdown preview
* code blocks are full width
## Bug fixes
* fix opening links from temporary shared folders on iphone or other contexts that do not support shared workers
* show "features" instead of "pricing" in static pages' footer when premium subscriptions are not available
* use preferred 12/24h time format in date picker
* add error handling to admin panel decree RPCs
# 4.5.0 # 4.5.0
## Goals ## Goals

@ -122,19 +122,6 @@ module.exports = {
], ],
*/ */
/* CryptPad's administration panel includes a "support" tab
* wherein administrators with a secret key can view messages
* sent from users via the encrypted forms on the /support/ page
*
* To enable this functionality:
* run `node ./scripts/generate-admin-keys.js`
* save the public key in your config in the value below
* add the private key via the admin panel
* and back it up in a secure manner
*
*/
// supportMailboxPublicKey: "",
/* We're very proud that CryptPad is available to the public as free software! /* 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. * We do, however, still need to pay our bills as we develop the platform.
* *
@ -147,11 +134,6 @@ module.exports = {
*/ */
//removeDonateButton: false, //removeDonateButton: false,
/* CryptPad will display a point of contact for your instance on its contact page
* (/contact.html) if you provide it below.
*/
adminEmail: 'i.did.not.read.my.config@cryptpad.fr',
/* /*
* By default, CryptPad contacts one of our servers once a day. * 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 * This check-in will also send some very basic information about your instance including its

@ -43,6 +43,17 @@ define([
return Pages.externalLink(el, Pages.localizeDocsLink(href)); return Pages.externalLink(el, Pages.localizeDocsLink(href));
}; };
var accounts = Pages.accounts = {
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
Pages.areSubscriptionsAllowed = function () {
try {
return ApiConfig.allowSubscriptions && accounts.upgradeURL && !ApiConfig.restrictRegistration;
} catch (err) { return void console.error(err); }
};
var languageSelector = function () { var languageSelector = function () {
var options = []; var options = [];
var languages = Msg._languages; var languages = Msg._languages;
@ -94,7 +105,7 @@ define([
var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ? var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ?
'/imprint.html' : AppConfig.imprint); '/imprint.html' : AppConfig.imprint);
Pages.versionString = "v4.5.0"; Pages.versionString = "v4.6.0";
// used for the about menu // used for the about menu
@ -133,7 +144,7 @@ define([
footerCol('footer_product', [ footerCol('footer_product', [
footLink('/what-is-cryptpad.html', 'topbar_whatIsCryptpad'), footLink('/what-is-cryptpad.html', 'topbar_whatIsCryptpad'),
Pages.docsLink, Pages.docsLink,
footLink('/features.html', 'pricing'), footLink('/features.html', Pages.areSubscriptionsAllowed()? 'pricing': 'features'), // Messages.pricing, Messages.features
Pages.githubLink, Pages.githubLink,
footLink('https://opencollective.com/cryptpad/contribute/', 'footer_donate'), footLink('https://opencollective.com/cryptpad/contribute/', 'footer_donate'),
]), ]),

@ -8,10 +8,7 @@ define([
'/api/config', '/api/config',
'/common/common-ui-elements.js', '/common/common-ui-elements.js',
], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements) { ], function ($, h, Msg, AppConfig, LocalStore, Pages, Config, UIElements) {
var accounts = { var accounts = Pages.accounts;
donateURL: AppConfig.donateURL || "https://opencollective.com/cryptpad/",
upgradeURL: AppConfig.upgradeURL
};
return function () { return function () {
Msg.features_f_apps_note = AppConfig.availablePadTypes.map(function (app) { Msg.features_f_apps_note = AppConfig.availablePadTypes.map(function (app) {
@ -145,10 +142,11 @@ define([
]), ]),
]), ]),
]); ]);
var availableFeatures = var availableFeatures = [
(Config.allowSubscriptions && accounts.upgradeURL && !Config.restrictRegistration) ? anonymousFeatures,
[anonymousFeatures, registeredFeatures, premiumFeatures] : registeredFeatures,
[anonymousFeatures, registeredFeatures]; Pages.areSubscriptionsAllowed() ? premiumFeatures: undefined,
];
return h('div#cp-main', [ return h('div#cp-main', [
Pages.infopageTopbar(), Pages.infopageTopbar(),

@ -194,7 +194,7 @@
pre > code { pre > code {
display: block; display: block;
position: relative; position: relative;
width: 90%; width: 100%;
margin: auto; margin: auto;
padding: 5px; padding: 5px;
overflow-x: auto; overflow-x: auto;

@ -167,6 +167,13 @@ server {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# These settings prevent both NGINX and the API server
# from setting the same headers and creating duplicates
proxy_hide_header Cross-Origin-Resource-Policy;
add_header Cross-Origin-Resource-Policy cross-origin;
proxy_hide_header Cross-Origin-Embedder-Policy;
add_header Cross-Origin-Embedder-Policy require-corp;
} }
# encrypted blobs are immutable and are thus cached for a year # encrypted blobs are immutable and are thus cached for a year

@ -348,6 +348,9 @@ var commands = {
CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex, CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex,
GET_CACHED_CHANNEL_INDEX: getChannelIndex, GET_CACHED_CHANNEL_INDEX: getChannelIndex,
// TODO implement admin historyTrim
// TODO implement kick from channel
// TODO implement force-disconnect user(s)?
CLEAR_CACHED_CHANNEL_METADATA: clearChannelMetadata, CLEAR_CACHED_CHANNEL_METADATA: clearChannelMetadata,
GET_CACHED_CHANNEL_METADATA: getChannelMetadata, GET_CACHED_CHANNEL_METADATA: getChannelMetadata,

@ -48,6 +48,7 @@ Default.httpHeaders = function () {
"X-XSS-Protection": "1; mode=block", "X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff", "X-Content-Type-Options": "nosniff",
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
"Permissions-policy":"interest-cohort=()"
}; };
}; };
Default.mainPages = function () { Default.mainPages = function () {

@ -701,6 +701,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
} }
if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) { if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
// TODO this might be a good place to reject channel creation by anonymous users
handleFirstMessage(Env, channelName, metadata); handleFirstMessage(Env, channelName, metadata);
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]);
} }

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"version": "4.5.0", "version": "4.6.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "4.5.0", "version": "4.6.0",
"license": "AGPL-3.0+", "license": "AGPL-3.0+",
"repository": { "repository": {
"type": "git", "type": "git",

@ -108,28 +108,21 @@ var setHeaders = (function () {
// apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere // apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere
applyHeaderMap(res, { applyHeaderMap(res, {
"Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '', "Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '',
"Cross-Origin-Embedder-Policy": 'require-corp',
}); });
if (Env.NO_SANDBOX) { // handles correct configuration for local development if (Env.NO_SANDBOX) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs // https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
applyHeaderMap(res, { applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin', "Cross-Origin-Resource-Policy": 'cross-origin',
"Cross-Origin-Embedder-Policy": 'require-corp',
}); });
} }
// Don't set CSP headers on /api/config because they aren't necessary and they cause problems // Don't set CSP headers on /api/ endpoints
// because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments // when duplicated by NGINX in production environments
if (/^\/api\/(broadcast|config)/.test(req.url)) { if (/^\/api\/(broadcast|config)/.test(req.url)) { return; }
/*
if (Env.NO_SANDBOX) {
applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin',
});
}
*/
return;
}
applyHeaderMap(res, { applyHeaderMap(res, {
"Cross-Origin-Resource-Policy": 'cross-origin', "Cross-Origin-Resource-Policy": 'cross-origin',
}); });

@ -16,6 +16,7 @@ define([
'/support/ui.js', '/support/ui.js',
'/lib/datepicker/flatpickr.js', '/lib/datepicker/flatpickr.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/lib/datepicker/flatpickr.min.css', 'css!/lib/datepicker/flatpickr.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -44,6 +45,7 @@ define([
'instanceStatus': {} 'instanceStatus': {}
}; };
var Nacl = window.nacl;
var common; var common;
var sFrameChan; var sFrameChan;
@ -54,6 +56,7 @@ define([
'cp-admin-archive', 'cp-admin-archive',
'cp-admin-unarchive', 'cp-admin-unarchive',
'cp-admin-registration', 'cp-admin-registration',
'cp-admin-email'
], ],
'quota': [ // Msg.admin_cat_quota 'quota': [ // Msg.admin_cat_quota
'cp-admin-defaultlimit', 'cp-admin-defaultlimit',
@ -71,7 +74,8 @@ define([
], ],
'support': [ // Msg.admin_cat_support 'support': [ // Msg.admin_cat_support
'cp-admin-support-list', 'cp-admin-support-list',
'cp-admin-support-init' 'cp-admin-support-init',
'cp-admin-support-priv',
], ],
'broadcast': [ // Msg.admin_cat_broadcast 'broadcast': [ // Msg.admin_cat_broadcast
'cp-admin-maintenance', 'cp-admin-maintenance',
@ -267,8 +271,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]] data: ['RESTRICT_REGISTRATION', [val]]
}, function (e) { }, function (e, response) {
if (e) { UI.warn(Messages.error); console.error(e); } if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () { APP.updateStatus(function () {
spinner.done(); spinner.done();
state = APP.instanceStatus.restrictRegistration; state = APP.instanceStatus.restrictRegistration;
@ -282,6 +289,45 @@ define([
return $div; return $div;
}; };
create['email'] = function () {
var key = 'email';
var $div = makeBlock(key, true); // Msg.admin_emailHint, Msg.admin_emailTitle, Msg.admin_emailButton
var $button = $div.find('button');
var input = h('input', {
type: 'email',
value: ApiConfig.adminEmail || ''
});
var $input = $(input);
var innerDiv = h('div.cp-admin-setlimit-form', input);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
if (!$input.val()) { return; }
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_ADMIN_EMAIL', [$input.val()]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
$input.val('');
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
});
});
$button.before(innerDiv);
return $div;
};
var getPrettySize = UIElements.prettySize; var getPrettySize = UIElements.prettySize;
create['defaultlimit'] = function () { create['defaultlimit'] = function () {
@ -316,8 +362,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['UPDATE_DEFAULT_STORAGE', data] data: ['UPDATE_DEFAULT_STORAGE', data]
}, function (e) { }, function (e, response) {
if (e) { UI.warn(Messages.error); return void console.error(e); } if (e || response.error) {
UI.warn(Messages.error);
return void console.error(e, response);
}
var limit = getPrettySize(l); var limit = getPrettySize(l);
$div.find('.cp-admin-defaultlimit-value').text(Messages._getKey('admin_limit', [limit])); $div.find('.cp-admin-defaultlimit-value').text(Messages._getKey('admin_limit', [limit]));
}); });
@ -448,8 +497,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['RM_QUOTA', data] data: ['RM_QUOTA', data]
}, function (e) { }, function (e, response) {
if (e) { UI.warn(Messages.error); console.error(e); } if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits(); APP.refreshLimits();
$key.val(''); $key.val('');
}); });
@ -462,8 +515,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['SET_QUOTA', data] data: ['SET_QUOTA', data]
}, function (e) { }, function (e, response) {
if (e) { UI.warn(Messages.error); console.error(e); } if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits(); APP.refreshLimits();
$key.val(''); $key.val('');
}); });
@ -637,8 +694,13 @@ define([
}; };
var supportKey = ApiConfig.supportMailbox; var supportKey = ApiConfig.supportMailbox;
var checkAdminKey = function (priv) {
if (!supportKey) { return; }
return Hash.checkBoxKeyPair(priv, supportKey);
};
create['support-list'] = function () { create['support-list'] = function () {
if (!supportKey || !APP.privateKey) { return; } if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var $container = makeBlock('support-list'); // Msg.admin_supportListHint, .admin_supportListTitle var $container = makeBlock('support-list'); // Msg.admin_supportListHint, .admin_supportListTitle
var $div = $(h('div.cp-support-container')).appendTo($container); var $div = $(h('div.cp-support-container')).appendTo($container);
@ -898,16 +960,63 @@ define([
return $container; return $container;
}; };
create['support-priv'] = function () {
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var checkAdminKey = function (priv) { var $div = makeBlock('support-priv', true); // Msg.admin_supportPrivHint, .admin_supportPrivTitle, .admin_supportPrivButton
if (!supportKey) { return; } var $button = $div.find('button').click(function () {
return Hash.checkBoxKeyPair(priv, supportKey); $button.remove();
var $selectable = $(UI.dialog.selectable(APP.privateKey)).css({ 'max-width': '28em' });
$div.append($selectable);
});
return $div;
}; };
create['support-init'] = function () { create['support-init'] = function () {
var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle
if (!supportKey) { if (!supportKey) {
$div.append(h('p', Messages.admin_supportInitHelp)); (function () {
$div.append(h('p', Messages.admin_supportInitHelp));
var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate);
var $button = $(button).appendTo($div);
$div.append($button);
var spinner = UI.makeSpinner($div);
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
var keyPair = Nacl.box.keyPair();
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
UI.warn(Messages.error);
spinner.hide();
return;
}
// Then send the decree
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX', [pub]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
supportKey = pub;
APP.privateKey = priv;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
});
})();
return $div; return $div;
} }
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) { if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
@ -937,6 +1046,7 @@ define([
APP.privateKey = key; APP.privateKey = key;
$('.cp-admin-support-init').hide(); $('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']()); APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
}); });
}); });
return $div; return $div;
@ -1030,9 +1140,10 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['SET_LAST_BROADCAST_HASH', [lastHash]] data: ['SET_LAST_BROADCAST_HASH', [lastHash]]
}, function (e) { }, function (e, response) {
if (e) { if (e || response.error) {
console.error(e); UI.warn(Messages.error);
console.error(e, response);
return; return;
} }
console.log('lastBroadcastHash updated'); console.log('lastBroadcastHash updated');
@ -1299,19 +1410,23 @@ define([
var $start = $(start); var $start = $(start);
var $end = $(end); var $end = $(end);
var is24h = false; var is24h = false;
var dateFormat = "Y-m-d H:i";
try { try {
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/); is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {} } catch (e) {}
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
var endPickr = Flatpickr(end, { var endPickr = Flatpickr(end, {
enableTime: true, enableTime: true,
time_24hr: is24h, time_24hr: is24h,
dateFormat: dateFormat,
minDate: new Date() minDate: new Date()
}); });
Flatpickr(start, { Flatpickr(start, {
enableTime: true, enableTime: true,
time_24hr: is24h, time_24hr: is24h,
minDate: new Date(), minDate: new Date(),
dateFormat: dateFormat,
onChange: function () { onChange: function () {
endPickr.set('minDate', new Date($start.val())); endPickr.set('minDate', new Date($start.val()));
} }
@ -1336,9 +1451,10 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['SET_MAINTENANCE', [data]] data: ['SET_MAINTENANCE', [data]]
}, function (e) { }, function (e, response) {
if (e) { if (e || response.error) {
UI.warn(Messages.error); console.error(e); UI.warn(Messages.error);
console.error(e, response);
$button.prop('disabled', ''); $button.prop('disabled', '');
return; return;
} }
@ -1430,10 +1546,11 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['SET_SURVEY_URL', [data]] data: ['SET_SURVEY_URL', [data]]
}, function (e) { }, function (e, response) {
if (e) { if (e || response.error) {
$button.prop('disabled', ''); $button.prop('disabled', '');
UI.warn(Messages.error); console.error(e); UI.warn(Messages.error);
console.error(e, response);
return; return;
} }
// Maintenance applied, send notification // Maintenance applied, send notification
@ -1529,11 +1646,12 @@ define([
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_WORKER_PROFILES', cmd: 'GET_WORKER_PROFILES',
}, function (e, data) { }, function (e, data) {
if (e) { return void console.error(e); } if (e || data.error) {
UI.warn(Messages.error);
return void console.error(e, data);
}
//console.info(data); //console.info(data);
$div.find("table").remove(); $div.find("table").remove();
process(data); process(data);
$div.append(table); $div.append(table);
}); });

@ -20,7 +20,7 @@ html, body {
} }
.pending { .pending {
border: 1px solid white; border: 1px solid @cryptpad_text_col;
.fa { .fa {
margin-right: 20px; margin-right: 20px;
} }
@ -45,7 +45,7 @@ html, body {
table { table {
td { td {
padding: 5px; padding: 5px;
border: 1px solid white; border: 1px solid @cryptpad_text_col;
} }
} }

@ -50,6 +50,10 @@ define([
]); ]);
}; };
var cacheBuster = function (url) {
return url + '?test=' + (+new Date());
};
var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin); var trimmedSafe = trimSlashes(ApiConfig.httpSafeOrigin);
var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin); var trimmedUnsafe = trimSlashes(ApiConfig.httpUnsafeOrigin);
@ -117,7 +121,7 @@ define([
var checkAvailability = function (url, cb) { var checkAvailability = function (url, cb) {
$.ajax({ $.ajax({
url: url, url: cacheBuster(url),
data: {}, data: {},
complete: function (xhr) { complete: function (xhr) {
cb(xhr.status === 200); cb(xhr.status === 200);
@ -169,10 +173,13 @@ define([
}).nThen(function () { }).nThen(function () {
// Iframe is loaded // Iframe is loaded
clearTimeout(to); clearTimeout(to);
console.log("removing sandbox iframe");
$('iframe#sbox-iframe').remove();
cb(true); cb(true);
}); });
}); });
var shared_websocket;
// Test Websocket // Test Websocket
var evWSError = Util.mkEvent(true); var evWSError = Util.mkEvent(true);
assert(function (_cb, msg) { assert(function (_cb, msg) {
@ -185,6 +192,7 @@ define([
})); }));
var ws = new WebSocket(NetConfig.getWebsocketURL()); var ws = new WebSocket(NetConfig.getWebsocketURL());
shared_websocket = ws;
var to = setTimeout(function () { var to = setTimeout(function () {
console.error('Websocket TIMEOUT'); console.error('Websocket TIMEOUT');
evWSError.fire(); evWSError.fire();
@ -203,6 +211,7 @@ define([
}); });
// Test login block // Test login block
var shared_realtime;
assert(function (_cb, msg) { assert(function (_cb, msg) {
var websocketErr = "No WebSocket available"; var websocketErr = "No WebSocket available";
var cb = Util.once(Util.both(_cb, function (status) { var cb = Util.once(Util.both(_cb, function (status) {
@ -237,7 +246,7 @@ define([
var blockUrl = Login.Block.getBlockUrl(opt.blockKeys); var blockUrl = Login.Block.getBlockUrl(opt.blockKeys);
var blockRequest = Login.Block.serialize("{}", opt.blockKeys); var blockRequest = Login.Block.serialize("{}", opt.blockKeys);
var removeRequest = Login.Block.remove(opt.blockKeys); var removeRequest = Login.Block.remove(opt.blockKeys);
console.log('Test block URL:', blockUrl); console.warn('Testing block URL (%s). One 404 is normal.', blockUrl);
var userHash = '/2/drive/edit/000000000000000000000000'; var userHash = '/2/drive/edit/000000000000000000000000';
var secret = Hash.getSecrets('drive', userHash); var secret = Hash.getSecrets('drive', userHash);
@ -264,7 +273,7 @@ define([
console.error("Can't create new channel. This may also be a websocket issue."); console.error("Can't create new channel. This may also be a websocket issue.");
return void cb(false); return void cb(false);
} }
RT = rt; shared_realtime = RT = rt;
var proxy = rt.proxy; var proxy = rt.proxy;
proxy.edPublic = opt.edPublic; proxy.edPublic = opt.edPublic;
proxy.edPrivate = opt.edPrivate; proxy.edPrivate = opt.edPrivate;
@ -330,14 +339,13 @@ define([
}).nThen(function () { }).nThen(function () {
cb(true); cb(true);
}); });
}); });
var sheetURL = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html'; var sheetURL = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
assert(function (cb, msg) { assert(function (cb, msg) {
msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. "; msg.innerText = "Missing HTTP headers required for .xlsx export from sheets. ";
var url = sheetURL; var url = cacheBuster(sheetURL);
var expect = { var expect = {
'cross-origin-resource-policy': 'cross-origin', 'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp', 'cross-origin-embedder-policy': 'require-corp',
@ -366,37 +374,12 @@ define([
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
msg = msg; msg.innerText = "Missing HTTP header required to disable Google's Floc.";
return void cb(true); $.ajax('/?'+ (+new Date()), {
/*
msg.appendChild(h('span', [
"The spreadsheet editor's code was not served with the required Content-Security Policy headers. ",
"This is most often caused by incorrectly configured sandbox parameters (",
h('code', 'httpUnsafeOrigin'),
' and ',
h('code', 'httpSafeOrigin'),
' in ',
CONFIG_PATH,
"), or settings in your reverse proxy's configuration which don't match your application server's config. ",
RESTART_WARNING(),
]));
$.ajax(sheetURL, {
complete: function (xhr) { complete: function (xhr) {
var csp = xhr.getResponseHeader('Content-Security-Policy'); cb(xhr.getResponseHeader('permissions-policy') === 'interest-cohort=()');
if (!/unsafe\-eval/.test(csp)) {
// OnlyOffice requires unsafe-eval
console.error('CSP', csp);
return cb("expected 'unsafe-eval'");
}
if (!/unsafe\-inline/.test(csp)) {
// OnlyOffice also requires unsafe-inline
console.error('CSP', csp);
return cb("expected 'unsafe-inline'");
}
cb(true);
}, },
}); */ });
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
@ -407,17 +390,20 @@ define([
"Your browser console may provide more details as to why this resource could not be loaded. ", "Your browser console may provide more details as to why this resource could not be loaded. ",
])); ]));
$.ajax('/api/broadcast', { $.ajax(cacheBuster('/api/broadcast'), {
dataType: 'text', dataType: 'text',
complete: function (xhr) { complete: function (xhr) {
console.log(xhr);
cb(xhr.status === 200); cb(xhr.status === 200);
}, },
}); });
}); });
var checkAPIHeaders = function (url, cb) { var code = function (content) {
$.ajax(url, { return h('code', content);
};
var checkAPIHeaders = function (url, msg, cb) {
$.ajax(cacheBuster(url), {
dataType: 'text', dataType: 'text',
complete: function (xhr) { complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders(); var allHeaders = xhr.getAllResponseHeaders();
@ -436,15 +422,31 @@ define([
var expect = { var expect = {
'cross-origin-resource-policy': 'cross-origin', 'cross-origin-resource-policy': 'cross-origin',
'cross-origin-embedder-policy': 'require-corp',
}; };
var incorrect = Object.keys(expect).some(function (k) { var incorrect = false;
Object.keys(expect).forEach(function (k) {
var response = xhr.getResponseHeader(k); var response = xhr.getResponseHeader(k);
if (response !== expect[k]) { var expected = expect[k];
return true; if (response !== expected) {
incorrect = true;
msg.appendChild(h('p', [
'The ',
code(k),
' header for ',
code(url),
" is '",
code(response),
"' instead of '",
code(expected),
"' as expected.",
]));
} }
}); });
if (duplicated || incorrect) { console.error(allHeaders); } if (duplicated || incorrect) { console.debug(allHeaders); }
cb(!duplicated && !incorrect); cb(!duplicated && !incorrect);
}, },
}); });
@ -455,13 +457,13 @@ define([
assert(function (cb, msg) { assert(function (cb, msg) {
var url = '/api/config'; var url = '/api/config';
msg.innerText = url + INCORRECT_HEADER_TEXT; msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, cb); checkAPIHeaders(url, msg, cb);
}); });
assert(function (cb, msg) { assert(function (cb, msg) {
var url = '/api/broadcast'; var url = '/api/broadcast';
msg.innerText = url + INCORRECT_HEADER_TEXT; msg.innerText = url + INCORRECT_HEADER_TEXT;
checkAPIHeaders(url, cb); checkAPIHeaders(url, msg, cb);
}); });
var setWarningClass = function (msg) { var setWarningClass = function (msg) {
@ -479,8 +481,9 @@ define([
'This instance does not provide a valid ', 'This instance does not provide a valid ',
h('code', 'adminEmail'), h('code', 'adminEmail'),
' which can make it difficult to contact its adminstrator to report vulnerabilities or abusive content.', ' which can make it difficult to contact its adminstrator to report vulnerabilities or abusive content.',
' This can be configured in ', CONFIG_PATH(), '. ', " This can be configured on your instance's admin panel. Use the provided ",
RESTART_WARNING(), code("Flush cache'"),
" button for this change to take effect for all users.",
])); ]));
cb(email); cb(email);
}); });
@ -517,6 +520,156 @@ define([
cb(false); cb(false);
}); });
var response = Util.response(function (err) {
console.error('SANDBOX_ERROR', err);
});
var sandboxIframe = h('iframe', {
class: 'sandbox-test',
src: cacheBuster(trimmedSafe + '/checkup/sandbox/index.html'),
});
document.body.appendChild(sandboxIframe);
var sandboxIframeReady = Util.mkEvent(true);
setTimeout(function () {
sandboxIframeReady.fire("TIMEOUT");
}, 10 * 1000);
var postMessage = function (content, cb) {
try {
var txid = Util.uid();
content.txid = txid;
response.expect(txid, cb, 15000);
sandboxIframe.contentWindow.postMessage(JSON.stringify(content), '*');
} catch (err) {
console.error(err);
}
};
var deferredPostMessage = function (content, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
nThen(function (w) {
sandboxIframeReady.reg(w(function (err) {
if (!err) { return; }
w.abort();
cb(err);
}));
}).nThen(function () {
postMessage(content, cb);
});
};
window.addEventListener('message', function (event) {
try {
var msg = JSON.parse(event.data);
if (msg.command === 'READY') { return void sandboxIframeReady.fire(); }
if (msg.q === "READY") { return; } // ignore messages from the usual sandboxed iframe
var txid = msg.txid;
if (!txid) { return console.log("no handler for ", txid); }
response.handle(txid, msg.content);
} catch (err) {
console.error(event);
console.error(err);
}
});
var parseCSP = function (CSP) {
//console.error(CSP);
var CSP_headers = {};
CSP.split(";")
.forEach(function (rule) {
rule = (rule || "").trim();
if (!rule) { return; }
var parts = rule.split(/\s/);
var first = parts[0];
var rest = rule.slice(first.length + 1);
CSP_headers[first] = rest;
//console.error(rule.trim());
//console.info("[%s] '%s'", first, rest);
});
return CSP_headers;
};
var hasUnsafeEval = function (CSP_headers) {
return /unsafe\-eval/.test(CSP_headers['script-src']);
};
var hasUnsafeInline = function (CSP_headers) {
return /unsafe\-inline/.test(CSP_headers['script-src']);
};
var hasOnlyOfficeHeaders = function (CSP_headers) {
if (!hasUnsafeEval(CSP_headers)) {
console.error("NO_UNSAFE_EVAL");
console.log(CSP_headers);
return false;
}
if (!hasUnsafeInline(CSP_headers)) {
console.error("NO_UNSAFE_INLINE");
return void false;
}
return true;
};
var CSP_WARNING = function (url) {
return h('span', [
code(url),
' does not have the required ',
code("'content-security-policy'"),
' headers set. This is most often related to incorrectly configured sandbox domains or reverse proxies.',
]);
};
assert(function (_cb, msg) {
var url = '/sheet/inner.html';
var cb = Util.once(Util.mkAsync(_cb));
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
assert(function (cb, msg) {
var url = '/common/onlyoffice/v4/web-apps/apps/spreadsheeteditor/main/index.html';
msg.appendChild(CSP_WARNING(url));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'content-security-policy',
},
}, function (content) {
var CSP_headers = parseCSP(content);
cb(hasOnlyOfficeHeaders(CSP_headers));
});
});
assert(function (cb, msg) {
var url = '/sheet/inner.html';
msg.appendChild(h('span', [
code(url),
' does not have the required ',
code("'cross-origin-opener-policy'"),
' headers set.',
]));
deferredPostMessage({
command: 'GET_HEADER',
content: {
url: url,
header: 'cross-origin-opener-policy',
},
}, function (content) {
cb(content === 'same-origin');
});
});
if (false) { if (false) {
assert(function (cb, msg) { assert(function (cb, msg) {
msg.innerText = 'fake test to simulate failure'; msg.innerText = 'fake test to simulate failure';
@ -583,6 +736,14 @@ define([
$progress.remove(); $progress.remove();
$('body').prepend(report); $('body').prepend(report);
try {
console.log('closing shared websocket');
shared_websocket.close();
} catch (err) { console.error(err); }
try {
console.log('closing shared realtime');
shared_realtime.network.disconnect();
} catch (err) { console.error(err); }
}, function (i, total) { }, function (i, total) {
console.log('test '+ i +' completed'); console.log('test '+ i +' completed');
completed++; completed++;

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
</head>
<body>
<div id="cp-progress"></div>
<iframe-placeholder>

@ -0,0 +1,50 @@
define([
'jquery',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/checkup/app-checkup.less',
], function ($) {
var postMessage = function (content) {
window.parent.postMessage(JSON.stringify(content), '*');
};
postMessage({ command: "READY", });
var getHeaders = function (url, cb) {
$.ajax(url + "?test=" + (+new Date()), {
dataType: 'text',
complete: function (xhr) {
var allHeaders = xhr.getAllResponseHeaders();
return void cb(void 0, allHeaders, xhr);
},
});
};
var COMMANDS = {};
COMMANDS.GET_HEADER = function (content, cb) {
var url = content.url;
getHeaders(url, function (err, headers, xhr) {
cb(xhr.getResponseHeader(content.header));
});
};
window.addEventListener("message", function (event) {
if (event && event.data) {
try {
//console.log(JSON.parse(event.data));
var msg = JSON.parse(event.data);
var command = msg.command;
var txid = msg.txid;
COMMANDS[command](msg.content, function (response) {
// postMessage with same txid
postMessage({
txid: txid,
content: response,
});
});
} catch (err) {
console.error(err);
}
} else {
console.error(event);
}
});
});

@ -4,14 +4,14 @@
* file (make a copy from /customize.dist/application_config.js) * file (make a copy from /customize.dist/application_config.js)
*/ */
define(function() { define(function() {
var config = {}; var AppConfig = {};
/* Select the buttons displayed on the main page to create new collaborative sessions. /* Select the buttons displayed on the main page to create new collaborative sessions.
* Removing apps from the list will prevent users from accessing them. They will instead be * Removing apps from the list will prevent users from accessing them. They will instead be
* redirected to the drive. * redirected to the drive.
* You should never remove the drive from this list. * You should never remove the drive from this list.
*/ */
config.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard', AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
/*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts' /*, 'calendar' */]; /*'doc', 'presentation',*/ 'file', /*'todo',*/ 'contacts' /*, 'calendar' */];
/* The registered only types are apps restricted to registered users. /* The registered only types are apps restricted to registered users.
* You should never remove apps from this list unless you know what you're doing. The apps * You should never remove apps from this list unless you know what you're doing. The apps
@ -20,7 +20,7 @@ define(function() {
* users and these users will be redirected to the login page if they still try to access * users and these users will be redirected to the login page if they still try to access
* the app * the app
*/ */
config.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support']; AppConfig.registeredOnlyTypes = ['file', 'contacts', 'notifications', 'support'];
/* CryptPad is available is multiple languages, but only English and French are maintained /* CryptPad is available is multiple languages, but only English and French are maintained
* by the developers. The other languages may be outdated, and any missing string for a langauge * by the developers. The other languages may be outdated, and any missing string for a langauge
@ -30,37 +30,37 @@ define(function() {
* can be found at the top of the file `/customize.dist/messages.js`. The list should only * can be found at the top of the file `/customize.dist/messages.js`. The list should only
* contain languages code ('en', 'fr', 'de', 'pt-br', etc.), not their full name. * contain languages code ('en', 'fr', 'de', 'pt-br', etc.), not their full name.
*/ */
//config.availableLanguages = ['en', 'fr', 'de']; //AppConfig.availableLanguages = ['en', 'fr', 'de'];
/* You can display a link to the imprint (legal notice) of your website in the static pages /* You can display a link to the imprint (legal notice) of your website in the static pages
* footer. To do so, you can either set the following value to `true` and create an imprint.html page * footer. To do so, you can either set the following value to `true` and create an imprint.html page
* in the `customize` directory. You can also set it to an absolute URL if your imprint page already exists. * in the `customize` directory. You can also set it to an absolute URL if your imprint page already exists.
*/ */
config.imprint = false; AppConfig.imprint = false;
// config.imprint = true; // AppConfig.imprint = true;
// config.imprint = 'https://xwiki.com/en/company/legal-notice'; // AppConfig.imprint = 'https://xwiki.com/en/company/legal-notice';
/* You can display a link to your own privacy policy in the static pages footer. /* You can display a link to your own privacy policy in the static pages footer.
* To do so, set the following value to the absolute URL of your privacy policy. * To do so, set the following value to the absolute URL of your privacy policy.
*/ */
// config.privacy = 'https://xwiki.com/en/company/PrivacyPolicy'; // AppConfig.privacy = 'https://xwiki.com/en/company/PrivacyPolicy';
/* We (the project's developers) include the ability to display a 'Roadmap' in static pages footer. /* We (the project's developers) include the ability to display a 'Roadmap' in static pages footer.
* This is disabled by default. * This is disabled by default.
* We use this to publish the project's development roadmap, but you can use it however you like. * We use this to publish the project's development roadmap, but you can use it however you like.
* To do so, set the following value to an absolute URL. * To do so, set the following value to an absolute URL.
*/ */
//config.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/'; //AppConfig.roadmap = 'https://cryptpad.fr/kanban/#/2/kanban/view/PLM0C3tFWvYhd+EPzXrbT+NxB76Z5DtZhAA5W5hG9wo/';
/* Cryptpad apps use a common API to display notifications to users /* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds * by default, notifications are hidden after 5 seconds
* You can change their duration here (measured in milliseconds) * You can change their duration here (measured in milliseconds)
*/ */
config.notificationTimeout = 5000; AppConfig.notificationTimeout = 5000;
config.disableUserlistNotifications = false; AppConfig.disableUserlistNotifications = false;
// Update the default colors available in the whiteboard application // Update the default colors available in the whiteboard application
config.whiteboardPalette = [ AppConfig.whiteboardPalette = [
'#000000', // black '#000000', // black
'#FFFFFF', // white '#FFFFFF', // white
'#848484', // grey '#848484', // grey
@ -82,14 +82,14 @@ define(function() {
// Background color in the apps with centered content: // Background color in the apps with centered content:
// - file app in view mode // - file app in view mode
// - rich text app when editor's width reduced in settings // - rich text app when editor's width reduced in settings
config.appBackgroundColor = '#666'; AppConfig.appBackgroundColor = '#666';
// Set enableTemplates to false to remove the button allowing users to save a pad as a template // Set enableTemplates to false to remove the button allowing users to save a pad as a template
// and remove the template category in CryptDrive // and remove the template category in CryptDrive
config.enableTemplates = true; AppConfig.enableTemplates = true;
// Set enableHistory to false to remove the "History" button in all the apps. // Set enableHistory to false to remove the "History" button in all the apps.
config.enableHistory = true; AppConfig.enableHistory = true;
/* user passwords are hashed with scrypt, and salted with their username. /* user passwords are hashed with scrypt, and salted with their username.
this value will be appended to the username, causing the resulting hash this value will be appended to the username, causing the resulting hash
@ -101,15 +101,15 @@ define(function() {
created. Changing it at a later time will break logins for all existing created. Changing it at a later time will break logins for all existing
users. users.
*/ */
config.loginSalt = ''; AppConfig.loginSalt = '';
config.minimumPasswordLength = 8; AppConfig.minimumPasswordLength = 8;
// Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad // Amount of time (ms) before aborting the session when the algorithm cannot synchronize the pad
config.badStateTimeout = 30000; AppConfig.badStateTimeout = 30000;
// Customize the icon used for each application. // Customize the icon used for each application.
// You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less // You can update the colors by making a copy of /customize.dist/src/less2/include/colortheme.less
config.applicationsIcon = { AppConfig.applicationsIcon = {
file: 'cptools-file', file: 'cptools-file',
fileupload: 'cptools-file-upload', fileupload: 'cptools-file-upload',
folderupload: 'cptools-folder-upload', folderupload: 'cptools-folder-upload',
@ -130,50 +130,49 @@ define(function() {
// Ability to create owned pads and expiring pads through a new pad creation screen. // Ability to create owned pads and expiring pads through a new pad creation screen.
// The new screen can be disabled by the users in their settings page // The new screen can be disabled by the users in their settings page
config.displayCreationScreen = true; AppConfig.displayCreationScreen = true;
// Prevent anonymous users from storing pads in their drive // Prevent anonymous users from storing pads in their drive
config.disableAnonymousStore = false; // NOTE: this is only enforced client-side as the server does not distinguish between users drives and pads
AppConfig.disableAnonymousStore = false;
// Prevent anonymous users from creating new pads (they can still access and edit existing ones)
// NOTE: this is only enforced client-side and will not prevent malicious clients from storing data
AppConfig.disableAnonymousPadCreation = false;
// Hide the usage bar in settings and drive // Hide the usage bar in settings and drive
//config.hideUsageBar = true; //AppConfig.hideUsageBar = true;
// Disable feedback for all the users and hide the settings part about feedback // Disable feedback for all the users and hide the settings part about feedback
//config.disableFeedback = true; //AppConfig.disableFeedback = true;
// Add new options in the share modal (extend an existing tab or add a new tab).
// More info about how to use it on the wiki:
// https://github.com/xwiki-labs/cryptpad/wiki/Application-config#configcustomizeshareoptions
//config.customizeShareOptions = function (hashes, tabs, config) {};
// Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is // Add code to be executed on every page before loading the user object. `isLoggedIn` (bool) is
// indicating if the user is registered or anonymous. Here you can change the way anonymous users // indicating if the user is registered or anonymous. Here you can change the way anonymous users
// work in CryptPad, use an external SSO or even force registration // work in CryptPad, use an external SSO or even force registration
// *NOTE*: You have to call the `callback` function to continue the loading process // *NOTE*: You have to call the `callback` function to continue the loading process
//config.beforeLogin = function(isLoggedIn, callback) {}; //AppConfig.beforeLogin = function(isLoggedIn, callback) {};
// Add code to be executed on every page after the user object is loaded (also work for // Add code to be executed on every page after the user object is loaded (also work for
// unregistered users). This allows you to interact with your users' drive // unregistered users). This allows you to interact with your users' drive
// *NOTE*: You have to call the `callback` function to continue the loading process // *NOTE*: You have to call the `callback` function to continue the loading process
//config.afterLogin = function(api, callback) {}; //AppConfig.afterLogin = function(api, callback) {};
// Disabling the profile app allows you to import the profile informations (display name, avatar) // Disabling the profile app allows you to import the profile informations (display name, avatar)
// from an external source and make sure the users can't change them from CryptPad. // from an external source and make sure the users can't change them from CryptPad.
// You can use config.afterLogin to import these values in the users' drive. // You can use AppConfig.afterLogin to import these values in the users' drive.
//config.disableProfile = true; //AppConfig.disableProfile = true;
// Disable the use of webworkers and sharedworkers in CryptPad. // Disable the use of webworkers and sharedworkers in CryptPad.
// Workers allow us to run the websockets connection and open the user drive in a separate thread. // Workers allow us to run the websockets connection and open the user drive in a separate thread.
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs, // SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
// making it much faster to open new tabs. // making it much faster to open new tabs.
config.disableWorkers = false; AppConfig.disableWorkers = false;
// Teams are always loaded during the initial loading screen (for the first tab only if // Teams are always loaded during the initial loading screen (for the first tab only if
// SharedWorkers are available). Allowing users to be members of multiple teams can // SharedWorkers are available). Allowing users to be members of multiple teams can
// make them have a very slow loading time. To avoid impacting the user experience // make them have a very slow loading time. To avoid impacting the user experience
// significantly, we're limiting the number of teams per user to 3 by default. // significantly, we're limiting the number of teams per user to 3 by default.
// You can change this value here. // You can change this value here.
//config.maxTeamsSlots = 5; //AppConfig.maxTeamsSlots = 5;
// Each team is considered as a registered user by the server. Users and teams are indistinguishable // Each team is considered as a registered user by the server. Users and teams are indistinguishable
// in the database so teams will offer the same storage limits as users by default. // in the database so teams will offer the same storage limits as users by default.
@ -181,7 +180,7 @@ define(function() {
// We're limiting the number of teams each user is able to own to 1 in order to make sure // We're limiting the number of teams each user is able to own to 1 in order to make sure
// users don't use "fake" teams (1 member) just to increase their storage limit. // users don't use "fake" teams (1 member) just to increase their storage limit.
// You can change the value here. // You can change the value here.
// config.maxOwnedTeams = 5; // AppConfig.maxOwnedTeams = 5;
// The userlist displayed in collaborative documents is stored alongside the document data. // The userlist displayed in collaborative documents is stored alongside the document data.
// Everytime someone with edit rights joins a document or modify their user data (display // Everytime someone with edit rights joins a document or modify their user data (display
@ -192,14 +191,14 @@ define(function() {
// position of other users' cursor. You can configure the number of user from which the session // position of other users' cursor. You can configure the number of user from which the session
// will enter into degraded mode. A big number may result in collaborative edition being broken, // will enter into degraded mode. A big number may result in collaborative edition being broken,
// but this number depends on the network and CPU performances of each user's device. // but this number depends on the network and CPU performances of each user's device.
config.degradedLimit = 8; AppConfig.degradedLimit = 8;
// In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad // In "legacy" mode, one-time users were always creating an "anonymous" drive when visiting CryptPad
// in which they could store their pads. The new "driveless" mode allow users to open an existing // in which they could store their pads. The new "driveless" mode allow users to open an existing
// pad without creating a drive in the background. The drive will only be created if they visit // pad without creating a drive in the background. The drive will only be created if they visit
// a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable // a different page (Drive, Settings, etc.) or try to create a new pad themselves. You can disable
// the driveless mode by changing the following value to "false" // the driveless mode by changing the following value to "false"
config.allowDrivelessMode = true; AppConfig.allowDrivelessMode = true;
return config; return AppConfig;
}); });

@ -5355,10 +5355,12 @@ var PDFFunction = function PDFFunctionClosure() {
var domain = IR[1]; var domain = IR[1];
var range = IR[2]; var range = IR[2];
var code = IR[3]; var code = IR[3];
/*
var compiled = new PostScriptCompiler().compile(code, domain, range); var compiled = new PostScriptCompiler().compile(code, domain, range);
if (compiled) { if (compiled) {
return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled); return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
} }
*/
(0, _util.info)('Unable to compile PS function'); (0, _util.info)('Unable to compile PS function');
var numOutputs = range.length >> 1; var numOutputs = range.length >> 1;
var numInputs = domain.length >> 1; var numInputs = domain.length >> 1;

@ -448,6 +448,9 @@ define([
} }
}; };
funcs.createPad = function (cfg, cb) { funcs.createPad = function (cfg, cb) {
if (AppConfig.disableAnonymousPadCreation && !funcs.isLoggedIn()) {
return void UI.errorLoadingScreen(Messages.mustLogin);
}
ctx.sframeChan.query("Q_CREATE_PAD", { ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned, owned: cfg.owned,
expire: cfg.expire, expire: cfg.expire,

@ -9,14 +9,17 @@ define([
var end = cfg.endpicker; var end = cfg.endpicker;
var is24h = false var is24h = false
var dateFormat = "Y-m-d H:i";
try { try {
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/); is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {} } catch (e) {}
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
var e = $(end.input)[0]; var e = $(end.input)[0];
var endPickr = Flatpickr(e, { var endPickr = Flatpickr(e, {
enableTime: true, enableTime: true,
time_24hr: is24h, time_24hr: is24h,
dateFormat: dateFormat,
minDate: start.date minDate: start.date
}); });
endPickr.setDate(end.date); endPickr.setDate(end.date);
@ -25,6 +28,7 @@ define([
var startPickr = Flatpickr(s, { var startPickr = Flatpickr(s, {
enableTime: true, enableTime: true,
time_24hr: is24h, time_24hr: is24h,
dateFormat: dateFormat,
onChange: function () { onChange: function () {
endPickr.set('minDate', startPickr.parseDate(s.value)); endPickr.set('minDate', startPickr.parseDate(s.value));
} }

Loading…
Cancel
Save