Merge branch 'staging' into soon

pull/1/head
ansuz 4 years ago
commit 60b3728164

@ -15,6 +15,8 @@
* websockets
* sandbox CSP
* login block
* recommend against trailing slashes for configured domains
* remove slashes in server.js anyway
* admin page
* support responses to closed tickets
* collapse very long messages
@ -24,7 +26,9 @@
* display survey URL
* support 'KB' in Util.magnitudeOfBytes
* degraded mode
* decide on a number
* decide on a number: 8
* provide an easy way to change it (application_config.js)
* inform users what the limit is (when degraded mode "kicks in")
* sheets
* fix naming collisions between images in spreadsheets
* degraded mode not supported
@ -32,14 +36,29 @@
* pinning?
* oo rebuild
* OnlyOffice v6.2
* some buttons that we were hiding have new ids and needed to be hidden again
* translations
* updated catch-phrase (Collaboration suite\nend-to-end-encrypted and open-source
* CKEditor
* cursor jump when clicking on a comment bubble
* keybindings for common styles
* test if this affects scroll position (it shouldn't)
* check that CTRL-space doesn't mess with anything and that it is what Google uses
* test on Mac
* nodrive
* load anonymous accounts without creating a drive
* faster load time, less junk on the server
* `AppConfig.allowDrivelessMode`
* cursor color is randomly generated each time and doesn't persist after creating a drive
* only affects framework apps for now
* secure iframe now always knows the channel of the related document
* more consistent API with other APPs
* debug app doesn't create a drive
* implement/fix ability to destroy pads whether they exist in your drive or not
* Known issues
* change password for documents in your drive when you don't have the most recent password (multi-owner pads)
# 4.2.1

@ -54,7 +54,7 @@ module.exports = {
* and it may have unintended consequences in practice.
*
*/
httpUnsafeOrigin: 'http://localhost:3000/',
httpUnsafeOrigin: 'http://localhost:3000',
/* httpSafeOrigin is the URL that is used for the 'sandbox' described above.
* If you're testing or developing with CryptPad on your local machine then

@ -184,6 +184,7 @@
}
&.btn-register {
margin-top: 10px !important;
white-space: normal;
}

@ -661,6 +661,8 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
if (txid) { msg[0] = txid; }
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
}, (err) => {
// Any error but ENOENT: abort
// ENOENT is allowed in case we want to create a new pad
if (err && err.code !== 'ENOENT') {
if (err.message === "EUNKNOWN") {
Log.error("HK_GET_HISTORY", {
@ -675,11 +677,28 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
err: err && err.message || err,
stack: err && err.stack,
}); }
// FIXME err.message isn't useful for users
const parsedMsg = {error:err.message, channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return;
}
// If we're asking for a specific version (lastKnownHash) but we receive an
// ENOENT, this is not a pad creation so we need to abort.
if (err && err.code === 'ENOENT' && lastKnownHash) {
/*
This informs clients that the pad they're trying to load was deleted by its owner.
The user in question might be reconnecting or might have loaded the document from their cache.
The owner that deleted it could be another user or the same user from a different device.
Either way, the respectful thing to do is display an error screen informing them that the content
is no longer on the server so they don't abuse the data and so that they don't unintentionally continue
to edit it in a broken state.
*/
const parsedMsg2 = {error:'EDELETED', channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg2)]);
return;
}
if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
handleFirstMessage(Env, channelName, metadata);
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(metadata)]);

@ -16,15 +16,19 @@ var Env = require("./lib/env").create(config);
var app = Express();
var canonicalizeOrigin = function (s) {
return (s || '').trim().replace(/\/+$/, '');
};
(function () {
// you absolutely must provide an 'httpUnsafeOrigin'
if (typeof(config.httpUnsafeOrigin) !== 'string') {
throw new Error("No 'httpUnsafeOrigin' provided");
}
config.httpUnsafeOrigin = config.httpUnsafeOrigin.trim();
config.httpUnsafeOrigin = canonicalizeOrigin(config.httpUnsafeOrigin);
if (typeof(config.httpSafeOrigin) === 'string') {
config.httpSafeOrigin = config.httpSafeOrigin.trim().replace(/\/$/, '');
config.httpSafeOrigin = canonicalizeOrigin(config.httpSafeOrigin);
}
// fall back to listening on a local address

@ -42,12 +42,13 @@ define([
return void cb(trimmedSafe !== trimmedUnsafe);
}, _alert('Sandbox configuration: httpUnsafeOrigin !== httpSafeOrigin'));
assert(function (cb) {
cb(trimmedSafe === ApiConfig.httpSafeOrigin);
}, "httpSafeOrigin must not have a trailing slash");
assert(function (cb) {
var origin = window.location.origin;
return void cb([
origin,
origin + '/'
].indexOf(ApiConfig.httpUnsafeOrigin) !== -1);
return void cb(ApiConfig.httpUnsafeOrigin === origin);
}, _alert('Sandbox configuration: loading via httpUnsafeOrigin'));

@ -162,7 +162,7 @@ define(function() {
// making it much faster to open new tabs.
config.disableWorkers = false;
config.surveyURL = "https://survey.cryptpad.fr/index.php/672782";
//config.surveyURL = "";
// 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
@ -179,5 +179,23 @@ define(function() {
// You can change the value here.
// config.maxOwnedTeams = 5;
// 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
// name, avatar, color, etc.), they update the "userlist" part of the document. When too many
// editors are in the same document, all these changes increase the risks of conflicts which
// require CPU time to solve. A "degraded" mode can now be set when a certain number of editors
// are in a document at the same time. This mode disables the userlist, the chat and the
// 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,
// but this number depends on the network and CPU performances of each user's device.
config.degradedLimit = 8;
// 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
// 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
// the driveless mode by changing the following value to "false"
config.allowDrivelessMode = true;
return config;
});

@ -1552,6 +1552,8 @@ define([
faqLine,
]);
$(content).find('a').attr('target', '_blank');
var buttons = [
{
className: 'primary',
@ -2538,6 +2540,7 @@ define([
UIElements.onServerError = function (common, err, toolbar, cb) {
//if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
var priv = common.getMetadataMgr().getPrivateData();
var sframeChan = common.getSframeChannel();
var msg = err.type;
if (err.type === 'EEXPIRED') {
msg = Messages.expiredError;
@ -2547,10 +2550,29 @@ define([
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
} else if (err.type === 'EDELETED') {
if (priv.burnAfterReading) { return void cb(); }
if (autoStoreModal[priv.channel]) {
autoStoreModal[priv.channel].delete();
delete autoStoreModal[priv.channel];
}
// View users have the wrong seed, thay can't retireve access directly
// Version 1 hashes don't support passwords
if (!priv.readOnly && !priv.oldVersionHash) {
sframeChan.event('EV_SHARE_OPEN', {hidden: true}); // Close share modal
UIElements.displayPasswordPrompt(common, {
fromServerError: true,
loaded: err.loaded,
});
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
(cb || function () {})();
return;
}
msg = Messages.deletedError;
if (err.loaded) {
msg += Messages.errorCopy;
}
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
} else if (err.type === 'ERESTRICTED') {
msg = Messages.restrictedError;
@ -2559,7 +2581,6 @@ define([
msg = Messages.oo_deletedVersion;
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
}
var sframeChan = common.getSframeChannel();
sframeChan.event('EV_SHARE_OPEN', {hidden: true});
UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded));
(cb || function () {})();
@ -2568,7 +2589,10 @@ define([
UIElements.displayPasswordPrompt = function (common, cfg, isError) {
var error;
if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); }
var info = h('p.cp-password-info', Messages.password_info);
var info_loaded = setHTML(h('p.cp-password-info'), Messages.errorCopy);
var password = UI.passwordInput({placeholder: Messages.password_placeholder});
var $password = $(password);
var button = h('button.btn.btn-primary', Messages.password_submit);
@ -2580,6 +2604,21 @@ define([
var submit = function () {
var value = $password.find('.cp-password-input').val();
// Password-prompt called from UIElements.onServerError
if (cfg.fromServerError) {
common.getSframeChannel().query('Q_PASSWORD_CHECK', value, function (err, obj) {
if (obj && obj.error) {
console.error(obj.error);
return void UI.warn(Messages.error);
}
// On success, outer will reload the page: this is a wrong password
UIElements.displayPasswordPrompt(common, cfg, true);
});
return;
}
// Initial load
UI.addLoadingScreen({newProgress: true});
if (window.CryptPad_updateLoadingProgress) {
window.CryptPad_updateLoadingProgress({
@ -2593,6 +2632,8 @@ define([
}
});
};
$password.find('.cp-password-input').on('keydown', function (e) { if (e.which === 13) { submit(); } });
$(button).on('click', function () { submit(); });
@ -2600,12 +2641,13 @@ define([
var block = h('div#cp-loading-password-prompt', [
error,
info,
cfg.loaded ? info_loaded : undefined,
h('p.cp-password-form', [
password,
button
])
]),
]);
UI.errorLoadingScreen(block);
UI.errorLoadingScreen(block, Boolean(cfg.loaded), Boolean(cfg.loaded));
$password.find('.cp-password-input').focus();
};

@ -481,10 +481,20 @@ define([
});
};
common.isNewChannel = function (href, password, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var channel = Hash.hrefToHexChannelId(href, password);
postMessage('IS_NEW_CHANNEL', {channel: channel}, function (obj) {
var error = obj && obj.error;
if (error) { return void cb(error); }
if (!obj) { return void cb('ERROR'); }
cb (null, obj.isNew);
}, {timeout: -1});
};
// This function is used when we want to open a pad. We first need
// to check if it exists. With the cached drive, we need to wait for
// the network to be available before we can continue.
common.isNewChannel = function (href, password, _cb) {
common.hasChannelHistory = function (href, password, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var channel = Hash.hrefToHexChannelId(href, password);
var error;
@ -1095,6 +1105,7 @@ define([
common.changePadPassword = function (Crypt, Crypto, data, cb) {
var href = data.href;
var oldPassword = data.oldPassword;
var newPassword = data.password;
var teamId = data.teamId;
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
@ -1123,7 +1134,9 @@ define([
var isSharedFolder = parsed.type === 'drive';
var optsGet = {};
var optsGet = {
password: oldPassword
};
var optsPut = {
password: newPassword,
metadata: {},
@ -1133,7 +1146,7 @@ define([
var cryptgetVal;
Nthen(function (waitFor) {
if (parsed.hashData && parsed.hashData.password) {
if (parsed.hashData && parsed.hashData.password && !oldPassword) {
common.getPadAttribute('password', waitFor(function (err, password) {
optsGet.password = password;
}), href);
@ -1179,7 +1192,6 @@ define([
} else if (mailbox && typeof(mailbox) === "object") {
m = {};
Object.keys(mailbox).forEach(function (ed) {
console.log(mailbox[ed]);
try {
m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true));
} catch (e) {
@ -1214,6 +1226,7 @@ define([
cryptgetVal = JSON.stringify(parsed);
}
}), optsGet);
Cache.clearChannel(newSecret.channel, waitFor());
}).nThen(function (waitFor) {
optsPut.metadata.restricted = oldMetadata.restricted;
optsPut.metadata.allowed = oldMetadata.allowed;
@ -1418,6 +1431,7 @@ define([
common.changeOOPassword = function (data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var href = data.href;
var oldPassword = data.oldPassword;
var newPassword = data.password;
var teamId = data.teamId;
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
@ -1431,7 +1445,6 @@ define([
var oldMetadata;
var oldRtChannel;
var privateData;
var padData;
var newSecret;
if (parsed.hashData.version >= 2) {
@ -1452,19 +1465,22 @@ define([
validateKey: newSecret.keys.validateKey
},
};
var optsGet = {};
var optsGet = {
password: oldPassword
};
Nthen(function (waitFor) {
common.getPadAttribute('', waitFor(function (err, _data) {
padData = _data;
optsGet.password = padData.password;
if (!oldPassword && _data) {
optsGet.password = _data.password;
}
}), href);
common.getAccessKeys(waitFor(function (keys) {
optsGet.accessKeys = keys;
optsPut.accessKeys = keys;
}));
}).nThen(function (waitFor) {
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password);
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password);
require([
'/common/cryptget.js',
@ -1592,6 +1608,7 @@ define([
}
}));
}));
Cache.clearChannel(newSecret.channel, waitFor());
}).nThen(function (waitFor) {
// The new rt channel is ready
// The blob uses its own encryption and doesn't need to be reencrypted
@ -2500,7 +2517,25 @@ define([
}
if (parsedNew.hashData) { oldHref = newHref; }
};
// XXX if you're in noDrive mode, check if an FS_hash is added and reload if that's the case
// Listen for login/logout in other tabs
if (rdyCfg.noDrive && !localStorage[Constants.fileHashKey]) {
window.addEventListener('storage', function (e) {
if (e.key !== Constants.fileHashKey) { return; }
// New entry added to FS_hash: drive created in another tab, reload
var o = e.oldValue;
var n = e.newValue;
if (!o && n) {
postMessage('HAS_DRIVE', null, function(obj) {
// If we're still in noDrive mode, reload
if (!obj.state) {
LocalStore.loginReload();
}
// Otherwise this worker is connected, nothing to do
});
}
});
}
window.addEventListener('storage', function (e) {
if (e.key !== Constants.userHashKey) { return; }
var o = e.oldValue;

@ -25,10 +25,12 @@ define([
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var channel = data.channel;
var priv = metadataMgr.getPrivateData();
var channel = data.channel || priv.channel;
var owners = data.owners || [];
var pending_owners = data.pending_owners || [];
var teamOwner = data.teamId;
var title = opts.title;
opts = opts || {};
var redrawAll = function () {};
@ -115,7 +117,7 @@ define([
if (!friend) { return; }
common.mailbox.sendTo("RM_OWNER", {
channel: channel,
title: data.title,
title: data.title || title,
pending: pending
}, {
channel: friend.notifications,
@ -271,7 +273,7 @@ define([
href: data.href || data.rohref,
password: data.password,
path: isTemplate ? ['template'] : undefined,
title: data.title || '',
title: data.title || title || "",
teamId: obj.id
}, waitFor(function (err) {
if (err) { return void console.error(err); }
@ -320,6 +322,12 @@ define([
}));
}
}).nThen(function (waitFor) {
var href = data.href;
var hashes = priv.hashes || {};
var bestHash = hashes.editHash || hashes.viewHash || hashes.fileHash;
if (data.fakeHref) {
href = Hash.hashToHref(bestHash, priv.app);
}
sel.forEach(function (el) {
var curve = $(el).attr('data-curve');
if (curve === user.curvePublic) { return; }
@ -327,9 +335,9 @@ define([
if (!friend) { return; }
common.mailbox.sendTo("ADD_OWNER", {
channel: channel,
href: data.href,
password: data.password,
title: data.title
href: href,
password: data.password || priv.password,
title: data.title || title
}, {
channel: friend.notifications,
curvePublic: friend.curvePublic
@ -398,7 +406,8 @@ define([
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var channel = data.channel;
var priv = metadataMgr.getPrivateData();
var channel = data.channel || priv.channel;
var owners = data.owners || [];
var restricted = data.restricted || false;
var allowed = data.allowed || [];
@ -888,9 +897,17 @@ define([
});
}
var href = data.href;
var hashes = priv.hashes || {};
var bestHash = hashes.editHash || hashes.viewHash || hashes.fileHash;
if (data.fakeHref) {
href = Hash.hashToHref(bestHash, priv.app);
}
var isNotStored = Boolean(data.fakeHref);
sframeChan.query(q, {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
href: data.href,
href: href,
oldPassword: priv.password,
password: newPass
}, function (err, data) {
$(passwordOk).text(Messages.properties_changePasswordButton);
@ -924,22 +941,26 @@ define([
// Pad password changed: update the href
// Use hidden hash if needed (we're an owner of this pad so we know it is stored)
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
var href = (priv.readOnly && data.roHref) ? data.roHref : data.href;
if (isNotStored) { useUnsafe = true; }
var _href = (priv.readOnly && data.roHref) ? data.roHref : data.href;
if (useUnsafe !== true) {
var newParsed = Hash.parsePadUrl(href);
var newParsed = Hash.parsePadUrl(_href);
var newSecret = Hash.getSecrets(newParsed.type, newParsed.hash, newPass);
var newHash = Hash.getHiddenHashFromKeys(parsed.type, newSecret, {});
href = Hash.hashToHref(newHash, parsed.type);
_href = Hash.hashToHref(newHash, parsed.type);
}
// Trigger a page reload if the href didn't change
if (_href === href) { _href = undefined; }
if (data.warning) {
return void UI.alert(Messages.properties_passwordWarning, function () {
common.gotoURL(href);
common.gotoURL(_href);
}, {force: true});
}
return void UI.alert(Messages.properties_passwordSuccess, function () {
if (!isSharedFolder) {
common.gotoURL(href);
common.gotoURL(_href);
}
}, {force: true});
});
@ -956,7 +977,7 @@ define([
spinner.spin();
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: data.channel
channel: data.channel || priv.channel
}, function (err, obj) {
spinner.done();
UI.findCancelButton().click();

@ -1281,13 +1281,15 @@ define([
'#title-user-name { display: none !important; }' +
(supportsXLSX() ? '' : '#slot-btn-dt-print { display: none !important; }') +
// New OO:
'#asc-gen566 { display: none !important; }' + // Insert image from url
'#asc-gen257 { display: none !important; }' + // Insert image from url
'section[data-tab="ins"] .separator:nth-last-child(2) { display: none !important; }' + // separator
'#slot-btn-insequation { display: none !important; }' + // Insert equation
//'.toolbar .tabs .ribtab:not(.canedit) { display: none !important; }' + // Switch collaborative mode
'#fm-btn-info { display: none !important; }' + // Author name, doc title, etc. in "File" (menu entry)
'#panel-info { display: none !important; }' + // Same but content
'#image-button-from-url { display: none !important; }' + // Inline image settings: replace with url
'#asc-gen1839 { display: none !important; }' + // Image context menu: replace with url
'#asc-gen5883 { display: none !important; }' + // Rightside image menu: replace with url
'#file-menu-panel .devider { display: none !important; }' + // separator in the "File" menu
'#left-btn-spellcheck, #left-btn-about { display: none !important; }'+
'div.btn-users.dropdown-toggle { display: none; !important }';

@ -331,6 +331,8 @@ define([
teamId = data.teamId;
}
// XXX CLEAR CACHE
if (channel === store.driveChannel && !force) {
return void cb({error: 'User drive removal blocked!'});
}
@ -450,7 +452,7 @@ define([
account.note = obj.note;
cb(obj);
});
});
}, Cache);
};
//////////////////////////////////////////////////////////////////
@ -586,11 +588,14 @@ define([
var proxy = store.proxy || {};
var disableThumbnails = Util.find(proxy, ['settings', 'general', 'disableThumbnails']);
var teams = (store.modules['team'] && store.modules['team'].getTeamsData(app)) || {};
if (!proxy.uid) {
store.noDriveUid = store.noDriveUid || Hash.createChannelId();
}
var metadata = {
// "user" is shared with everybody via the userlist
user: {
name: proxy[Constants.displayNameKey] || store.noDriveName || "",
uid: proxy.uid || Hash.createChannelId(), // Random uid in nodrive mode
uid: proxy.uid || store.noDriveUid, // Random uid in nodrive mode
avatar: Util.find(proxy, ['profile', 'avatar']),
profile: Util.find(proxy, ['profile', 'view']),
color: getUserColor(),
@ -858,6 +863,7 @@ define([
Store.setDisplayName = function (clientId, value, cb) {
if (!store.proxy) {
store.noDriveName = value;
broadcast([clientId], "UPDATE_METADATA");
return void cb();
}
if (store.modules['profile']) {
@ -1704,6 +1710,10 @@ define([
var onError = function (err) {
channel.bcast("PAD_ERROR", err);
if (err && err.type === "EDELETED" && Cache && Cache.clearChannel) {
Cache.clearChannel(data.channel);
}
// If this is a DELETED, EXPIRED or RESTRICTED pad, leave the channel
if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
Store.leavePad(null, data, function () {});
@ -1714,11 +1724,13 @@ define([
postMessage(clientId, "PAD_CACHE");
},
onCacheReady: function () {
channel.hasCache = true;
postMessage(clientId, "PAD_CACHE_READY");
},
onReady: function (pad) {
var padData = pad.metadata || {};
channel.data = padData;
channel.ready = true;
if (padData && padData.validateKey && store.messenger) {
store.messenger.storeValidateKey(data.channel, padData.validateKey);
}
@ -2835,6 +2847,12 @@ define([
if (store.ready) { return; } // the store is already ready, it is a reconnection
store.driveMetadata = info.metadata;
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
if (!rt.proxy[Constants.displayNameKey] && store.noDriveName) {
rt.proxy[Constants.displayNameKey] = store.noDriveName;
}
if (!rt.proxy.uid && store.noDriveUid) {
rt.proxy.uid = store.noDriveUid;
}
/*
// deprecating localStorage migration as of 4.2.0
var drive = rt.proxy.drive;
@ -2929,6 +2947,13 @@ define([
*/
var initialized = false;
// Are we still in noDrive mode?
Store.hasDrive = function (clientId, data, cb) {
cb({
state: Boolean(store.proxy)
});
};
// If we load CryptPad for the first time from an existing pad, don't create a
// drive automatically.
var onNoDrive = function (clientId, cb) {
@ -2950,7 +2975,13 @@ define([
if (!store.network) {
var wsUrl = NetConfig.getWebsocketURL();
return void Netflux.connect(wsUrl).then(function (network) {
store.network = network;
// If we already haave a network (race condition), use the
// existing one and forget this one
if (!store.network) { store.network = network; }
else {
network.disconnect();
network = store.network;
}
// We need to know the HistoryKeeper ID to initialize the anon RPC
// Join a basic ephemeral channel, get the ID and leave it instantly
network.join('0000000000000000000000000000000000').then(function (wc) {

@ -2,11 +2,12 @@ define([
'/common/common-util.js',
'/common/common-constants.js',
'/customize/messages.js',
'/customize/application_config.js',
'/bower_components/chainpad-crypto/crypto.js',
], function (Util, Constants, Messages, Crypto) {
], function (Util, Constants, Messages, AppConfig, Crypto) {
var Cursor = {};
var DEGRADED = 3; // XXX Number of users before switching to degraded mode
var DEGRADED = AppConfig.degradedLimit || 8;
var convertToUint8 = function (obj) {
var l = Object.keys(obj).length;
@ -50,6 +51,12 @@ define([
});
};
var updateDegraded = function (ctx, wc, chan) {
var m = wc.members;
chan.degraded = (m.length-1) >= DEGRADED;
ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients);
};
var initCursor = function (ctx, obj, client, cb) {
var channel = obj.channel;
var secret = obj.secret;
@ -92,14 +99,10 @@ define([
// ==> And push the new tab to the list
chan.clients.push(client);
updateDegraded(ctx, chan.wc, chan);
return void cb();
}
var updateDegraded = function (ctx, wc, chan) {
var m = wc.members;
chan.degraded = (m.length-1) >= DEGRADED;
ctx.emit('DEGRADED', { degraded: chan.degraded }, chan.clients);
};
var onOpen = function (wc) {
ctx.channels[channel] = ctx.channels[channel] || {};

@ -537,10 +537,9 @@ define([
if (peer === hk) { return; }
if (channel.readOnly) { return; }
// XXX review
// Sending myData is used to build a "mapId" object which links
// netflux IDs to a curvePublic/uid. We use this map in friend chat
// to detect is the other user is online and we also use it in team chat
// to detect if the other user is online and we also use it in team chat
// to show if other team members are online (in the roster section).
// It is not needed in the pad chat for now and only causes useless
// network usage.

@ -15,6 +15,7 @@ define([
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
PING: function (cId, data, cb) { cb(); },
CACHE_DISABLE: Store.disableCache,
HAS_DRIVE: Store.hasDrive,
// RPC
UPDATE_PIN_LIMIT: Store.updatePinLimit,
GET_PIN_LIMIT: Store.getPinLimit,

@ -868,7 +868,6 @@ define([
if (fId && Env.folders[fId] && Env.folders[fId].deleting) {
delete Env.folders[fId].deleting;
}
console.error(obj.error, chan);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + chan + '|' + obj.error, true);
return void cb();
}
@ -881,6 +880,11 @@ define([
ids.push(fId);
}
if (!ids.length) {
toDelete = undefined;
return void cb();
}
ids.forEach(function (id) {
var paths = findFile(Env, id);
var _resolved = _resolvePaths(Env, paths);
@ -912,8 +916,13 @@ define([
});
});
}).nThen(function () {
// Remove deleted pads from the drive
_delete(Env, { resolved: toDelete }, cb);
if (!toDelete) {
// Nothing to delete
cb();
} else {
// Remove deleted pads from the drive
_delete(Env, { resolved: toDelete }, cb);
}
// If we were using the access modal, send a refresh command
if (data.channel) {
Env.Store.refreshDriveUI();

@ -78,7 +78,7 @@ define([
};
var AppConfig;
var Test;
var password, newPadPassword;
var password, newPadPassword, newPadPasswordForce;
var initialPathInDrive;
var burnAfterReading;
@ -221,8 +221,11 @@ define([
}
} catch (e) { console.error(e); }
// NOTE: Driveless mode should only work for existing pads, but we can't check that
// before creating the worker because we need the anon RPC to do so.
// We're only going to check if a hash exists in the URL or not.
Cryptpad.ready(waitFor(), {
noDrive: cfg.noDrive,
noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash,
driveEvents: cfg.driveEvents,
cache: Boolean(cfg.cache),
currentPad: currentPad
@ -309,6 +312,7 @@ define([
newPadPassword = Crypto.decrypt(newPad.pw, uKey);
} catch (e) { console.error(e); }
}
if (newPad.f) { newPadPasswordForce = 1; }
if (newPad.d) {
Cryptpad.fromFileData = newPad.d;
var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href);
@ -316,6 +320,7 @@ define([
delete Cryptpad.fromFileData;
}
}
} catch (e) {
console.error(e, parsed.hashData.newPadOpts);
}
@ -346,7 +351,7 @@ define([
}
// We now need to check if there is a password and if we know the correct password.
// We'll use getFileSize and isNewChannel to detect incorrect passwords.
// We'll use getFileSize and hasChannelHistory to detect incorrect passwords.
// First we'll get the password value from our drive (getPadAttribute), and we'll check
// if the channel is valid. If the pad is not stored in our drive, we'll test with an
@ -394,15 +399,15 @@ define([
}
};
if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel)
// `hasChannelHistory` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
next(e, size === 0);
});
return;
}
// Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(currentPad.href, password, next);
// Not a file, so we can use `hasChannelHistory`
Cryptpad.hasChannelHistory(currentPad.href, password, next);
});
sframeChan.event("EV_PAD_PASSWORD", cfg);
};
@ -464,19 +469,32 @@ define([
currentPad.href = parsed.getUrl(opts);
currentPad.hash = parsed.hashData && parsed.hashData.getHash(opts);
}
Cryptpad.getPadAttribute('title', w(function (err, data) {
Cryptpad.getPadAttribute('channel', w(function (err, data) {
stored = (!err && typeof (data) === "string");
}));
Cryptpad.getPadAttribute('password', w(function (err, val) {
password = val;
}), parsed.getUrl());
}).nThen(function (w) {
// If we've already tested this password and this is a redirect, force
if (typeof(newPadPassword) !== "undefined" && newPadPasswordForce) {
password = newPadPassword;
return void todo();
}
// If the pad is not stored and we have a newPadPassword, it probably
// comes from a notification: password prompt pre-filled
if (!password && !stored && newPadPassword) {
passwordCfg.value = newPadPassword;
}
// Pad not stored && password required: always ask for the password
if (!stored && parsed.hashData.password && !newPadPasswordForce) {
return void askPassword(true, passwordCfg);
}
if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel)
// `hasChannelHistory` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) {
if (size !== 0) { return void todo(); }
@ -485,8 +503,8 @@ define([
}));
return;
}
// Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) {
// Not a file, so we can use `hasChannelHistory`
Cryptpad.hasChannelHistory(currentPad.href, password, w(function(e, isNew) {
if (isNew && expire && expire < (+new Date())) {
sframeChan.event("EV_EXPIRED_ERROR");
waitFor.abort();
@ -500,10 +518,6 @@ define([
waitFor.abort();
return;
}
if (!stored && !parsed.hashData.password) {
// We've received a link without /p/ and it doesn't work without a password: abort
return void todo();
}
// Wrong password or deleted file?
askPassword(true, passwordCfg);
}));
@ -537,8 +551,7 @@ define([
if (realtime) {
// TODO we probably don't need to check again for password-protected pads
// (we use isNewChannel to test the password...)
Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) {
Cryptpad.hasChannelHistory(currentPad.href, password, waitFor(function (e, isNew) {
if (e) { return console.error(e); }
isNewFile = Boolean(isNew);
}));
@ -604,6 +617,7 @@ define([
feedbackAllowed: Utils.Feedback.state,
isPresent: parsed.hashData && parsed.hashData.present,
isEmbed: parsed.hashData && parsed.hashData.embed,
oldVersionHash: parsed.hashData && parsed.hashData.version < 2, // password
isHistoryVersion: parsed.hashData && parsed.hashData.versionHash,
notifications: notifs,
accounts: {
@ -1404,6 +1418,7 @@ define([
};
config.data = {
app: parsed.type,
channel: secret.channel,
hashes: hashes,
password: password,
isTemplate: isTemplate,
@ -1669,6 +1684,45 @@ define([
});
});
sframeChan.on('Q_PASSWORD_CHECK', function (pw, cb) {
Cryptpad.isNewChannel(currentPad.href, pw, function (e, isNew) {
if (isNew === false) {
var channel = Utils.Hash.hrefToHexChannelId(currentPad.href, pw);
nThen(function (w) {
// If the pad is stored, update its data
var _secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, pw);
var chan = _secret.channel;
var editH = Utils.Hash.getEditHashFromKeys(_secret);
var viewH = Utils.Hash.getViewHashFromKeys(_secret);
var href = Utils.Hash.hashToHref(editH, parsed.type);
var roHref = Utils.Hash.hashToHref(viewH, parsed.type);
Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl());
Cryptpad.setPadAttribute('channel', chan, w(), parsed.getUrl());
Cryptpad.setPadAttribute('href', href, w(), parsed.getUrl());
Cryptpad.setPadAttribute('roHref', roHref, w(), parsed.getUrl());
}).nThen(function () {
// Get redirect URL
var uHash = Utils.LocalStore.getUserHash();
var uSecret = Utils.Hash.getSecrets('drive', uHash);
var uKey = uSecret.keys.cryptKey;
var url = Utils.Hash.getNewPadURL(currentPad.href, {
pw: Crypto.encrypt(pw, uKey),
f: 1
});
// redirect
window.location.href = url;
document.location.reload();
});
return;
}
cb({
error: e
});
});
});
if (cfg.messaging) {
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
Cryptpad.universal.execCommand({

@ -212,6 +212,7 @@ MessengerUI, Messages) {
var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'})
.appendTo($editUsers);
var degradedLimit = Config.degradedLimit || 8;
if (!online) {
$('<em>').text(Messages.userlist_offline).appendTo($editUsersList);
numberOfEditUsers = '?';
@ -219,8 +220,7 @@ MessengerUI, Messages) {
} else if (metadataMgr.isDegraded() === true) {
numberOfEditUsers = Math.max(metadataMgr.getChannelMembers().length - 1, 0);
numberOfViewUsers = '';
Messages.toolbar_degraded = "Too many editors are present in the pad. The userlist has been disabled to improve performances"; // XXX
$('<em>').text(Messages.toolbar_degraded).appendTo($editUsersList);
$('<em>').text(Messages._getKey('toolbar_degraded', [degradedLimit])).appendTo($editUsersList);
}
// Update the buttons
@ -565,7 +565,12 @@ MessengerUI, Messages) {
h('span.cp-button-name', Messages.accessButton)
]));
$accessBlock.click(function () {
Common.getSframeChannel().event('EV_ACCESS_OPEN');
var title = (config.title && config.title.getTitle && config.title.getTitle())
|| (config.title && config.title.defaultName)
|| "";
Common.getSframeChannel().event('EV_ACCESS_OPEN', {
title: title
});
});
toolbar.$bottomM.append($accessBlock);

@ -44,6 +44,7 @@ define([
meta.debugDrive = drive;
};
SFCommonO.start({
noDrive: true,
addData:addData
});
});

@ -70,7 +70,8 @@ define([
if ($uname.val()) {
localStorage.login_user = $uname.val();
}
window.location.href = '/register/';
var hash = (window.location.hash || '').replace(/\/login\//, '/register/');
window.location.href = '/register/' + hash;
});
Test(function (t) {

@ -89,9 +89,10 @@ define([
};
// Access modal
create['access'] = function () {
create['access'] = function (data) {
require(['/common/inner/access.js'], function (Access) {
Access.getAccessModal(common, {
title: data.title,
onClose: function () {
hideIframe();
}

@ -101,6 +101,7 @@ define([
origin: window.location.origin,
pathname: window.location.pathname,
feedbackAllowed: Utils.Feedback.state,
channel: config.data.channel,
hashes: config.data.hashes,
password: config.data.password,
propChannels: config.data.getPropChannels(),

Loading…
Cancel
Save