Merge branch 'staging' into soon

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

@ -15,6 +15,8 @@
* websockets * websockets
* sandbox CSP * sandbox CSP
* login block * login block
* recommend against trailing slashes for configured domains
* remove slashes in server.js anyway
* admin page * admin page
* support responses to closed tickets * support responses to closed tickets
* collapse very long messages * collapse very long messages
@ -24,7 +26,9 @@
* display survey URL * display survey URL
* support 'KB' in Util.magnitudeOfBytes * support 'KB' in Util.magnitudeOfBytes
* degraded mode * 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 * sheets
* fix naming collisions between images in spreadsheets * fix naming collisions between images in spreadsheets
* degraded mode not supported * degraded mode not supported
@ -32,14 +36,29 @@
* pinning? * pinning?
* oo rebuild * oo rebuild
* OnlyOffice v6.2 * OnlyOffice v6.2
* some buttons that we were hiding have new ids and needed to be hidden again
* translations * translations
* updated catch-phrase (Collaboration suite\nend-to-end-encrypted and open-source * updated catch-phrase (Collaboration suite\nend-to-end-encrypted and open-source
* CKEditor * CKEditor
* cursor jump when clicking on a comment bubble * cursor jump when clicking on a comment bubble
* keybindings for common styles * 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 * nodrive
* load anonymous accounts without creating a drive * load anonymous accounts without creating a drive
* faster load time, less junk on the server * 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 # 4.2.1

@ -54,7 +54,7 @@ module.exports = {
* and it may have unintended consequences in practice. * 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. /* 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 * If you're testing or developing with CryptPad on your local machine then

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

@ -661,6 +661,8 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
if (txid) { msg[0] = txid; } if (txid) { msg[0] = txid; }
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
}, (err) => { }, (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 && err.code !== 'ENOENT') {
if (err.message === "EUNKNOWN") { if (err.message === "EUNKNOWN") {
Log.error("HK_GET_HISTORY", { Log.error("HK_GET_HISTORY", {
@ -675,11 +677,28 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
err: err && err.message || err, err: err && err.message || err,
stack: err && err.stack, stack: err && err.stack,
}); } }); }
// FIXME err.message isn't useful for users
const parsedMsg = {error:err.message, channel: channelName, txid: txid}; const parsedMsg = {error:err.message, channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return; 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)) { if (msgCount === 0 && !metadata_cache[channelName] && Server.channelContainsUser(channelName, userId)) {
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)]);

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

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

@ -162,7 +162,7 @@ define(function() {
// making it much faster to open new tabs. // making it much faster to open new tabs.
config.disableWorkers = false; 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 // 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
@ -179,5 +179,23 @@ define(function() {
// You can change the value here. // You can change the value here.
// config.maxOwnedTeams = 5; // 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; return config;
}); });

@ -1552,6 +1552,8 @@ define([
faqLine, faqLine,
]); ]);
$(content).find('a').attr('target', '_blank');
var buttons = [ var buttons = [
{ {
className: 'primary', className: 'primary',
@ -2538,6 +2540,7 @@ define([
UIElements.onServerError = function (common, err, toolbar, cb) { UIElements.onServerError = function (common, err, toolbar, cb) {
//if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; } //if (["EDELETED", "EEXPIRED", "ERESTRICTED"].indexOf(err.type) === -1) { return; }
var priv = common.getMetadataMgr().getPrivateData(); var priv = common.getMetadataMgr().getPrivateData();
var sframeChan = common.getSframeChannel();
var msg = err.type; var msg = err.type;
if (err.type === 'EEXPIRED') { if (err.type === 'EEXPIRED') {
msg = Messages.expiredError; msg = Messages.expiredError;
@ -2547,10 +2550,29 @@ define([
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
} else if (err.type === 'EDELETED') { } else if (err.type === 'EDELETED') {
if (priv.burnAfterReading) { return void cb(); } 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; msg = Messages.deletedError;
if (err.loaded) { if (err.loaded) {
msg += Messages.errorCopy; msg += Messages.errorCopy;
} }
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
} else if (err.type === 'ERESTRICTED') { } else if (err.type === 'ERESTRICTED') {
msg = Messages.restrictedError; msg = Messages.restrictedError;
@ -2559,7 +2581,6 @@ define([
msg = Messages.oo_deletedVersion; msg = Messages.oo_deletedVersion;
if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); } if (toolbar && typeof toolbar.failed === "function") { toolbar.failed(true); }
} }
var sframeChan = common.getSframeChannel();
sframeChan.event('EV_SHARE_OPEN', {hidden: true}); sframeChan.event('EV_SHARE_OPEN', {hidden: true});
UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded)); UI.errorLoadingScreen(msg, Boolean(err.loaded), Boolean(err.loaded));
(cb || function () {})(); (cb || function () {})();
@ -2568,7 +2589,10 @@ define([
UIElements.displayPasswordPrompt = function (common, cfg, isError) { UIElements.displayPasswordPrompt = function (common, cfg, isError) {
var error; var error;
if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_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 = 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 = UI.passwordInput({placeholder: Messages.password_placeholder});
var $password = $(password); var $password = $(password);
var button = h('button.btn.btn-primary', Messages.password_submit); var button = h('button.btn.btn-primary', Messages.password_submit);
@ -2580,6 +2604,21 @@ define([
var submit = function () { var submit = function () {
var value = $password.find('.cp-password-input').val(); 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}); UI.addLoadingScreen({newProgress: true});
if (window.CryptPad_updateLoadingProgress) { if (window.CryptPad_updateLoadingProgress) {
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(); } }); $password.find('.cp-password-input').on('keydown', function (e) { if (e.which === 13) { submit(); } });
$(button).on('click', function () { submit(); }); $(button).on('click', function () { submit(); });
@ -2600,12 +2641,13 @@ define([
var block = h('div#cp-loading-password-prompt', [ var block = h('div#cp-loading-password-prompt', [
error, error,
info, info,
cfg.loaded ? info_loaded : undefined,
h('p.cp-password-form', [ h('p.cp-password-form', [
password, password,
button button
]) ]),
]); ]);
UI.errorLoadingScreen(block); UI.errorLoadingScreen(block, Boolean(cfg.loaded), Boolean(cfg.loaded));
$password.find('.cp-password-input').focus(); $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 // 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 // to check if it exists. With the cached drive, we need to wait for
// the network to be available before we can continue. // 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 cb = Util.once(Util.mkAsync(_cb));
var channel = Hash.hrefToHexChannelId(href, password); var channel = Hash.hrefToHexChannelId(href, password);
var error; var error;
@ -1095,6 +1105,7 @@ define([
common.changePadPassword = function (Crypt, Crypto, data, cb) { common.changePadPassword = function (Crypt, Crypto, data, cb) {
var href = data.href; var href = data.href;
var oldPassword = data.oldPassword;
var newPassword = data.password; var newPassword = data.password;
var teamId = data.teamId; var teamId = data.teamId;
if (!href) { return void cb({ error: 'EINVAL_HREF' }); } if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
@ -1123,7 +1134,9 @@ define([
var isSharedFolder = parsed.type === 'drive'; var isSharedFolder = parsed.type === 'drive';
var optsGet = {}; var optsGet = {
password: oldPassword
};
var optsPut = { var optsPut = {
password: newPassword, password: newPassword,
metadata: {}, metadata: {},
@ -1133,7 +1146,7 @@ define([
var cryptgetVal; var cryptgetVal;
Nthen(function (waitFor) { Nthen(function (waitFor) {
if (parsed.hashData && parsed.hashData.password) { if (parsed.hashData && parsed.hashData.password && !oldPassword) {
common.getPadAttribute('password', waitFor(function (err, password) { common.getPadAttribute('password', waitFor(function (err, password) {
optsGet.password = password; optsGet.password = password;
}), href); }), href);
@ -1179,7 +1192,6 @@ define([
} else if (mailbox && typeof(mailbox) === "object") { } else if (mailbox && typeof(mailbox) === "object") {
m = {}; m = {};
Object.keys(mailbox).forEach(function (ed) { Object.keys(mailbox).forEach(function (ed) {
console.log(mailbox[ed]);
try { try {
m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true)); m[ed] = newCrypto.encrypt(oldCrypto.decrypt(mailbox[ed], true, true));
} catch (e) { } catch (e) {
@ -1214,6 +1226,7 @@ define([
cryptgetVal = JSON.stringify(parsed); cryptgetVal = JSON.stringify(parsed);
} }
}), optsGet); }), optsGet);
Cache.clearChannel(newSecret.channel, waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
optsPut.metadata.restricted = oldMetadata.restricted; optsPut.metadata.restricted = oldMetadata.restricted;
optsPut.metadata.allowed = oldMetadata.allowed; optsPut.metadata.allowed = oldMetadata.allowed;
@ -1418,6 +1431,7 @@ define([
common.changeOOPassword = function (data, _cb) { common.changeOOPassword = function (data, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
var href = data.href; var href = data.href;
var oldPassword = data.oldPassword;
var newPassword = data.password; var newPassword = data.password;
var teamId = data.teamId; var teamId = data.teamId;
if (!href) { return void cb({ error: 'EINVAL_HREF' }); } if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
@ -1431,7 +1445,6 @@ define([
var oldMetadata; var oldMetadata;
var oldRtChannel; var oldRtChannel;
var privateData; var privateData;
var padData;
var newSecret; var newSecret;
if (parsed.hashData.version >= 2) { if (parsed.hashData.version >= 2) {
@ -1452,19 +1465,22 @@ define([
validateKey: newSecret.keys.validateKey validateKey: newSecret.keys.validateKey
}, },
}; };
var optsGet = {}; var optsGet = {
password: oldPassword
};
Nthen(function (waitFor) { Nthen(function (waitFor) {
common.getPadAttribute('', waitFor(function (err, _data) { common.getPadAttribute('', waitFor(function (err, _data) {
padData = _data; if (!oldPassword && _data) {
optsGet.password = padData.password; optsGet.password = _data.password;
}
}), href); }), href);
common.getAccessKeys(waitFor(function (keys) { common.getAccessKeys(waitFor(function (keys) {
optsGet.accessKeys = keys; optsGet.accessKeys = keys;
optsPut.accessKeys = keys; optsPut.accessKeys = keys;
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password); oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password);
require([ require([
'/common/cryptget.js', '/common/cryptget.js',
@ -1592,6 +1608,7 @@ define([
} }
})); }));
})); }));
Cache.clearChannel(newSecret.channel, waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// The new rt channel is ready // The new rt channel is ready
// The blob uses its own encryption and doesn't need to be reencrypted // The blob uses its own encryption and doesn't need to be reencrypted
@ -2500,7 +2517,25 @@ define([
} }
if (parsedNew.hashData) { oldHref = newHref; } 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 // 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) { window.addEventListener('storage', function (e) {
if (e.key !== Constants.userHashKey) { return; } if (e.key !== Constants.userHashKey) { return; }
var o = e.oldValue; var o = e.oldValue;

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

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

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

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

@ -537,10 +537,9 @@ define([
if (peer === hk) { return; } if (peer === hk) { return; }
if (channel.readOnly) { return; } if (channel.readOnly) { return; }
// XXX review
// Sending myData is used to build a "mapId" object which links // Sending myData is used to build a "mapId" object which links
// netflux IDs to a curvePublic/uid. We use this map in friend chat // 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). // 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 // It is not needed in the pad chat for now and only causes useless
// network usage. // network usage.

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

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

@ -78,7 +78,7 @@ define([
}; };
var AppConfig; var AppConfig;
var Test; var Test;
var password, newPadPassword; var password, newPadPassword, newPadPasswordForce;
var initialPathInDrive; var initialPathInDrive;
var burnAfterReading; var burnAfterReading;
@ -221,8 +221,11 @@ define([
} }
} catch (e) { console.error(e); } } 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(), { Cryptpad.ready(waitFor(), {
noDrive: cfg.noDrive, noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash,
driveEvents: cfg.driveEvents, driveEvents: cfg.driveEvents,
cache: Boolean(cfg.cache), cache: Boolean(cfg.cache),
currentPad: currentPad currentPad: currentPad
@ -309,6 +312,7 @@ define([
newPadPassword = Crypto.decrypt(newPad.pw, uKey); newPadPassword = Crypto.decrypt(newPad.pw, uKey);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
} }
if (newPad.f) { newPadPasswordForce = 1; }
if (newPad.d) { if (newPad.d) {
Cryptpad.fromFileData = newPad.d; Cryptpad.fromFileData = newPad.d;
var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href); var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href);
@ -316,6 +320,7 @@ define([
delete Cryptpad.fromFileData; delete Cryptpad.fromFileData;
} }
} }
} catch (e) { } catch (e) {
console.error(e, parsed.hashData.newPadOpts); 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 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 // 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 // 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") { 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 // `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, function (e, size) { Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
next(e, size === 0); next(e, size === 0);
}); });
return; return;
} }
// Not a file, so we can use `isNewChannel` // Not a file, so we can use `hasChannelHistory`
Cryptpad.isNewChannel(currentPad.href, password, next); Cryptpad.hasChannelHistory(currentPad.href, password, next);
}); });
sframeChan.event("EV_PAD_PASSWORD", cfg); sframeChan.event("EV_PAD_PASSWORD", cfg);
}; };
@ -464,19 +469,32 @@ define([
currentPad.href = parsed.getUrl(opts); currentPad.href = parsed.getUrl(opts);
currentPad.hash = parsed.hashData && parsed.hashData.getHash(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"); stored = (!err && typeof (data) === "string");
})); }));
Cryptpad.getPadAttribute('password', w(function (err, val) { Cryptpad.getPadAttribute('password', w(function (err, val) {
password = val; password = val;
}), parsed.getUrl()); }), parsed.getUrl());
}).nThen(function (w) { }).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) { if (!password && !stored && newPadPassword) {
passwordCfg.value = 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") { 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 // `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) { Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) {
if (size !== 0) { return void todo(); } if (size !== 0) { return void todo(); }
@ -485,8 +503,8 @@ define([
})); }));
return; return;
} }
// Not a file, so we can use `isNewChannel` // Not a file, so we can use `hasChannelHistory`
Cryptpad.isNewChannel(currentPad.href, password, w(function(e, isNew) { Cryptpad.hasChannelHistory(currentPad.href, password, w(function(e, isNew) {
if (isNew && expire && expire < (+new Date())) { if (isNew && expire && expire < (+new Date())) {
sframeChan.event("EV_EXPIRED_ERROR"); sframeChan.event("EV_EXPIRED_ERROR");
waitFor.abort(); waitFor.abort();
@ -500,10 +518,6 @@ define([
waitFor.abort(); waitFor.abort();
return; 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? // Wrong password or deleted file?
askPassword(true, passwordCfg); askPassword(true, passwordCfg);
})); }));
@ -537,8 +551,7 @@ define([
if (realtime) { if (realtime) {
// TODO we probably don't need to check again for password-protected pads // TODO we probably don't need to check again for password-protected pads
// (we use isNewChannel to test the password...) Cryptpad.hasChannelHistory(currentPad.href, password, waitFor(function (e, isNew) {
Cryptpad.isNewChannel(currentPad.href, password, waitFor(function (e, isNew) {
if (e) { return console.error(e); } if (e) { return console.error(e); }
isNewFile = Boolean(isNew); isNewFile = Boolean(isNew);
})); }));
@ -604,6 +617,7 @@ define([
feedbackAllowed: Utils.Feedback.state, feedbackAllowed: Utils.Feedback.state,
isPresent: parsed.hashData && parsed.hashData.present, isPresent: parsed.hashData && parsed.hashData.present,
isEmbed: parsed.hashData && parsed.hashData.embed, isEmbed: parsed.hashData && parsed.hashData.embed,
oldVersionHash: parsed.hashData && parsed.hashData.version < 2, // password
isHistoryVersion: parsed.hashData && parsed.hashData.versionHash, isHistoryVersion: parsed.hashData && parsed.hashData.versionHash,
notifications: notifs, notifications: notifs,
accounts: { accounts: {
@ -1404,6 +1418,7 @@ define([
}; };
config.data = { config.data = {
app: parsed.type, app: parsed.type,
channel: secret.channel,
hashes: hashes, hashes: hashes,
password: password, password: password,
isTemplate: isTemplate, 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) { if (cfg.messaging) {
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) { sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
Cryptpad.universal.execCommand({ Cryptpad.universal.execCommand({

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

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

@ -70,7 +70,8 @@ define([
if ($uname.val()) { if ($uname.val()) {
localStorage.login_user = $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) { Test(function (t) {

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

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

Loading…
Cancel
Save