Merge branch 'modern-users' of github.com:xwiki-labs/cryptpad into modern-users

pull/1/head
yflory 7 years ago
commit 36d1ae0f6c

@ -10,7 +10,7 @@
.sidebar-layout_main() { .sidebar-layout_main() {
input[type="text"] { input[type="text"], input[type="password"] {
padding-left: 10px; padding-left: 10px;
} }
#cp-sidebarlayout-container { #cp-sidebarlayout-container {
@ -60,7 +60,7 @@
} }
margin-bottom: 20px; margin-bottom: 20px;
} }
[type="text"], button { [type="text"], [type="password"], button {
vertical-align: middle; vertical-align: middle;
height: 40px; height: 40px;
box-sizing: border-box; box-sizing: border-box;

@ -5,23 +5,47 @@
display: none; display: none;
text-align: center; text-align: center;
width: 100%; width: 100%;
padding: 10px 0;
align-items: center;
justify-content: center;
* { * {
font: @colortheme_app-font; font: @colortheme_app-font;
} }
.cp-toolbar-history-next { .cp-history-filler {
display: inline-block; flex: 1;
vertical-align: middle;
margin: 20px;
} }
.cp-toolbar-history-previous { .cp-toolbar-history-close,
display: inline-block; .cp-toolbar-history-revert {
vertical-align: middle; background: white;
margin: 20px; color: black;
//margin-top: 5px;
&:hover {
background-color: #e6e6e6;
}
}
.cp-toolbar-history-loadmore {
height: 100%;
color: black;
width: 25px;
position: absolute;
left: 0;
padding: 0;
}
.cp-toolbar-history-version {
position: absolute;
height: 25px;
line-height: 25px;
width: 100%;
text-align: center;
} }
.cp-toolbar-history-goto { .cp-toolbar-history-goto {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
flex: 1;
flex-basis: 80%;
min-width: 0;
max-width: 600px;
input { width: 75px; } input { width: 75px; }
} }
.cp-toolbar-history-goto-input { .cp-toolbar-history-goto-input {
@ -29,6 +53,30 @@
margin-left: 5px; margin-left: 5px;
vertical-align: middle; vertical-align: middle;
} }
.cp-toolbar-history-bar {
width: 100%;
background: white;
height: 25px;
margin: auto;
position: relative;
}
.cp-toolbar-history-pos-container {
width: ~"calc(100% - 2px)";
height: 25px;
position: relative;
}
@pos-color: #55FF55;
.cp-toolbar-history-pos {
width: 2px;
height: 25px;
background: @pos-color;
&:after {
content: '';
border: 6px solid transparent;
border-top-color: @pos-color;
margin-left: -5px;
}
}
button { button {
color: inherit; color: inherit;
background-color: rgba(0,0,0,0.2); background-color: rgba(0,0,0,0.2);
@ -36,14 +84,6 @@
background-color: rgba(0,0,0,0.4); background-color: rgba(0,0,0,0.4);
} }
} }
.cp-toolbar-history-close {
background: white;
color: black;
margin-top: 5px;
&:hover {
background-color: #e6e6e6;
}
}
.fa-spinner { .fa-spinner {
font-size: 66px; font-size: 66px;
} }

@ -232,12 +232,11 @@ define(function () {
out.historyText = "Historique"; out.historyText = "Historique";
out.historyButton = "Afficher l'historique du document"; out.historyButton = "Afficher l'historique du document";
out.history_next = "Voir la version suivante"; out.history_next = "Version plus récente";
out.history_prev = "Voir la version précédente"; out.history_prev = "Version plus ancienne";
out.history_goTo = "Voir la version sélectionnée"; out.history_loadMore = "Charger davantage d'historique";
out.history_close = "Retour"; out.history_close = "Retour";
out.history_closeTitle = "Fermer l'historique"; out.history_closeTitle = "Fermer l'historique";
out.history_restore = "Restaurer";
out.history_restoreTitle = "Restaurer la version du document sélectionnée"; out.history_restoreTitle = "Restaurer la version du document sélectionnée";
out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?"; out.history_restorePrompt = "Êtes-vous sûr de vouloir remplacer la version actuelle du document par la version affichée ?";
out.history_restoreDone = "Document restauré"; out.history_restoreDone = "Document restauré";

@ -234,12 +234,10 @@ define(function () {
out.historyText = "History"; out.historyText = "History";
out.historyButton = "Display the document history"; out.historyButton = "Display the document history";
out.history_next = "Go to the next version"; out.history_next = "Newer version";
out.history_prev = "Go to the previous version"; out.history_prev = "Older version";
out.history_goTo = "Go to the selected version"; out.history_loadMore = "Load more history";
out.history_close = "Back";
out.history_closeTitle = "Close the history"; out.history_closeTitle = "Close the history";
out.history_restore = "Restore";
out.history_restoreTitle = "Restore the selected version of the document"; out.history_restoreTitle = "Restore the selected version of the document";
out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?"; out.history_restorePrompt = "Are you sure you want to replace the current version of the document by the displayed one?";
out.history_restoreDone = "Document restored"; out.history_restoreDone = "Document restored";
@ -607,6 +605,15 @@ define(function () {
out.settings_ownDriveButton = "Migrate"; // XXX out.settings_ownDriveButton = "Migrate"; // XXX
out.settings_ownDriveConfirm = "Are you sure?"; // XXX out.settings_ownDriveConfirm = "Are you sure?"; // XXX
out.settings_changePasswordTitle = "Change your password"; // XXX
out.settings_changePasswordHint = "Change your account's password without losing its data. You have to enter your existing password once, and the new password you want twice.<br>" +
"<b>We can't reset your password if you forget it so be very careful!</b>"; // XXX
out.settings_changePasswordButton = "Change password"; // XXX
out.settings_changePasswordCurrent = "Existing password"; // XXX
out.settings_changePasswordNew = "New password"; // XXX
out.settings_changePasswordNewConfirm = "Confirm new password"; // XXX
out.settings_changePasswordConfirm = "Are you sure?"; // XXX
out.upload_title = "File upload"; out.upload_title = "File upload";
out.upload_modal_title = "File upload options"; out.upload_modal_title = "File upload options";
out.upload_modal_filename = "File name (extension <em>{0}</em> added automatically)"; out.upload_modal_filename = "File name (extension <em>{0}</em> added automatically)";

@ -1380,8 +1380,7 @@ var createLoginBlockPath = function (Env, publicKey) {
}; };
var writeLoginBlock = function (Env, msg, cb) { var writeLoginBlock = function (Env, msg, cb) {
console.log(msg); // XXX //console.log(msg);
var publicKey = msg[0]; var publicKey = msg[0];
var signature = msg[1]; var signature = msg[1];
var block = msg[2]; var block = msg[2];

@ -10,9 +10,12 @@ define([
'/common/wire.js', '/common/wire.js',
'/common/flat-dom.js', '/common/flat-dom.js',
'/common/media-tag.js', '/common/media-tag.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag) { ], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag) {
window.Hyperjson = Hyperjson; window.Hyperjson = Hyperjson;
window.Sortify = Sortify; window.Sortify = Sortify;
var Nacl = window.nacl;
var assertions = 0; var assertions = 0;
var failed = false; var failed = false;
@ -296,6 +299,18 @@ define([
!secret.hashData.present); !secret.hashData.present);
}, "test support for ugly tracking query paramaters in url"); }, "test support for ugly tracking query paramaters in url");
assert(function (cb) {
var href = 'https://cryptpad.fr/block/pe/pewpewpewpewpew';
var key = Nacl.randomBytes(32);
var hash = Hash.createBlockHash(href, key);
var parsed = Hash.parseBlockHash(hash);
cb(parsed && href === parsed.href &&
parsed.keys.symmetric.length === key.length);
}, 'parse a block hash');
assert(function (cb) { assert(function (cb) {
try { try {
MediaTag(void 0).on('progress').on('decryption'); MediaTag(void 0).on('progress').on('decryption');

@ -3,6 +3,7 @@ define(function () {
// localStorage // localStorage
userHashKey: 'User_hash', userHashKey: 'User_hash',
userNameKey: 'User_name', userNameKey: 'User_name',
blockHashKey: 'Block_hash',
fileHashKey: 'FS_hash', fileHashKey: 'FS_hash',
// sessionStorage // sessionStorage
newPadPathKey: "newPadPath", newPadPathKey: "newPadPath",

@ -474,6 +474,43 @@ Version 1
'/' + curvePublic.replace(/\//g, '-') + '/'; '/' + curvePublic.replace(/\//g, '-') + '/';
}; };
// XXX consider putting Block functions in /common/outer/login-block.js
Hash.createBlockHash = function (href, key) {
if (typeof(href) !== 'string') { return; }
if (!key instanceof Uint8Array) { return; }
// TODO verify inputs
try { return href + '#' + Nacl.util.encodeBase64(key); }
catch (e) { return; }
};
var decodeSafeB64 = function (b64) {
try {
return Nacl.util.decodeBase64(b64.replace(/\-/g, '/'));
} catch (e) {
console.error(e);
return;
}
};
Hash.parseBlockHash = function (hash) {
if (typeof(hash) !== 'string') { return; }
var parts = hash.split('#');
if (parts.length !== 2) { return; }
try {
return {
href: parts[0],
keys: {
symmetric: decodeSafeB64(parts[1]),
}
};
} catch (e) {
console.error(e);
return;
}
};
// Create untitled documents when no name is given // Create untitled documents when no name is given
var getLocaleDate = function () { var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) { if (window.Intl && window.Intl.DateTimeFormat) {

@ -137,17 +137,15 @@ define([], function () {
else if (bytes >= oneMegabyte) { return 'MB'; } else if (bytes >= oneMegabyte) { return 'MB'; }
}; };
// given a path, asynchronously return an arraybuffer
Util.fetch = function (src, cb) { Util.fetch = function (src, cb) {
var done = false; var done = false;
var CB = function (err, res) { var CB = Util.once(cb);
if (done) { return; }
done = true;
cb(err, res);
};
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("GET", src, true); xhr.open("GET", src, true);
xhr.responseType = "arraybuffer"; xhr.responseType = "arraybuffer";
xhr.onerror = function (err) { CB(err); };
xhr.onload = function () { xhr.onload = function () {
if (/^4/.test(''+this.status)) { if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR'); return CB('XHR_ERROR');

@ -8,11 +8,12 @@ define([
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/common/outer/worker-channel.js', '/common/outer/worker-channel.js',
'/common/outer/login-block.js',
'/customize/application_config.js', '/customize/application_config.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
], function (Config, Messages, Util, Hash, ], function (Config, Messages, Util, Hash,
Messaging, Constants, Feedback, LocalStore, Channel, Messaging, Constants, Feedback, LocalStore, Channel, Block,
AppConfig, Nthen) { AppConfig, Nthen) {
/* This file exposes functionality which is specific to Cryptpad, but not to /* This file exposes functionality which is specific to Cryptpad, but not to
@ -474,7 +475,7 @@ define([
if (typeof(meta) === "object") { if (typeof(meta) === "object") {
meta.defaultTitle = meta.title || meta.defaultTitle; meta.defaultTitle = meta.title || meta.defaultTitle;
delete meta.users; delete meta.users;
delete meta.title; meta.title = "";
} }
val = JSON.stringify(parsed); val = JSON.stringify(parsed);
} catch (e) { } catch (e) {
@ -503,6 +504,13 @@ define([
if (typeof (data.title) !== "string") { return cb('Missing title'); } if (typeof (data.title) !== "string") { return cb('Missing title'); }
if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); } if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
if (common.initialPath) {
if (!data.path) {
data.path = common.initialPath;
delete common.initialPath;
}
}
postMessage("SET_PAD_TITLE", data, function (obj) { postMessage("SET_PAD_TITLE", data, function (obj) {
if (obj && obj.error) { if (obj && obj.error) {
console.log("unable to set pad title"); console.log("unable to set pad title");
@ -764,6 +772,9 @@ define([
common.getFullHistory = function (data, cb) { common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb); postMessage("GET_FULL_HISTORY", data, cb);
}; };
common.getHistoryRange = function (data, cb) {
postMessage("GET_HISTORY_RANGE", data, cb);
};
common.getShareHashes = function (secret, cb) { common.getShareHashes = function (secret, cb) {
var hashes; var hashes;
@ -949,7 +960,46 @@ define([
if (AppConfig.beforeLogin) { if (AppConfig.beforeLogin) {
AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor()); AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor());
} }
}).nThen(function (waitFor) {
var blockHash = LocalStore.getBlockHash();
if (blockHash) {
console.log(blockHash);
var parsed = Hash.parseBlockHash(blockHash);
if (typeof(parsed) !== 'object') {
console.error("Failed to parse blockHash");
console.log(parsed);
return;
} else {
console.log(parsed);
}
Util.fetch(parsed.href, waitFor(function (err, arraybuffer) {
if (err) { return void console.log(err); }
// use the results to load your user hash and
// put your userhash into localStorage
try {
var block_info = Block.decrypt(arraybuffer, parsed.keys);
if (block_info[Constants.userHashKey]) { LocalStore.setUserHash(block_info[Constants.userHashKey]); }
} catch (e) {
console.error(e);
return void console.error("failed to decrypt or decode block content");
}
}));
} else {
// XXX debugging
console.error("NO BLOCK HASH");
}
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// XXX debugging
if (LocalStore.getUserHash()) {
console.log('User_hash detected');
} else {
console.log("User_hash not detected");
}
var cfg = { var cfg = {
init: true, init: true,
//query: onMessage, // TODO temporary, will be replaced by a webworker channel //query: onMessage, // TODO temporary, will be replaced by a webworker channel
@ -961,7 +1011,7 @@ define([
driveEvents: rdyCfg.driveEvents // Boolean driveEvents: rdyCfg.driveEvents // Boolean
}; };
if (sessionStorage[Constants.newPadPathKey]) { if (sessionStorage[Constants.newPadPathKey]) {
cfg.initialPath = sessionStorage[Constants.newPadPathKey]; common.initialPath = sessionStorage[Constants.newPadPathKey];
delete sessionStorage[Constants.newPadPathKey]; delete sessionStorage[Constants.newPadPathKey];
} }

@ -1110,10 +1110,13 @@ define([
} }
}; };
var msgs = []; var msgs = [];
var completed = false;
var onMsg = function (msg) { var onMsg = function (msg) {
if (completed) { return; }
var parsed = parse(msg); var parsed = parse(msg);
if (parsed[0] === 'FULL_HISTORY_END') { if (parsed[0] === 'FULL_HISTORY_END') {
cb(msgs); cb(msgs);
completed = true;
return; return;
} }
if (parsed[0] !== 'FULL_HISTORY') { return; } if (parsed[0] !== 'FULL_HISTORY') { return; }
@ -1132,6 +1135,63 @@ define([
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey])); network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
}; };
Store.getHistoryRange = function (clientId, data, cb) {
var network = store.network;
var hkn = network.historyKeeper;
var parse = function (msg) {
try {
return JSON.parse(msg);
} catch (e) {
return null;
}
};
var msgs = [];
var first = true;
var fullHistory = false;
var completed = false;
var lastKnownHash;
var txid = Util.uid();
var onMsg = function (msg) {
if (completed) { return; }
var parsed = parse(msg);
if (parsed[1] !== txid) { console.log('bad txid'); return; }
if (parsed[0] === 'HISTORY_RANGE_END') {
cb({
messages: msgs,
isFull: fullHistory,
lastKnownHash: lastKnownHash
});
completed = true;
return;
}
if (parsed[0] !== 'HISTORY_RANGE') { return; }
if (parsed[2] && parsed[1].validateKey) { // Metadata
return;
}
if (parsed[2][3] !== data.channel) { return; }
msg = parsed[2][4];
if (msg) {
if (first) {
// If the first message if not a checkpoint, it means it is the first
// message of the pad, so we have the full history!
if (!/^cp\|/.test(msg)) { fullHistory = true; }
lastKnownHash = msg.slice(0,64);
first = false;
}
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
msgs.push(msg);
}
};
network.on('message', onMsg);
network.sendto(hkn, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
from: data.lastKnownHash,
cpCount: 2,
txid: txid
}]));
};
// Drive // Drive
Store.userObjectCommand = function (clientId, cmdData, cb) { Store.userObjectCommand = function (clientId, cmdData, cb) {
if (!cmdData || !cmdData.cmd) { return; } if (!cmdData || !cmdData.cmd) { return; }

@ -58,6 +58,14 @@ define([
localStorage[Constants.userHashKey] = sHash; localStorage[Constants.userHashKey] = sHash;
}; };
LocalStore.getBlockHash = function () {
return localStorage[Constants.blockHashKey];
};
LocalStore.setBlockHash = function (hash) {
LocalStorage[Constants.blockHashKey] = hash;
};
LocalStore.getAccountName = function () { LocalStore.getAccountName = function () {
return localStorage[Constants.userNameKey]; return localStorage[Constants.userNameKey];
}; };
@ -96,6 +104,7 @@ define([
[ [
Constants.userNameKey, Constants.userNameKey,
Constants.userHashKey, Constants.userHashKey,
Constants.blockHashKey,
'loginToken', 'loginToken',
'plan', 'plan',
].forEach(function (k) { ].forEach(function (k) {

@ -1,7 +1,8 @@
define([ define([
'/common/common-util.js', '/common/common-util.js',
'/api/config',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function (Util) { ], function (Util, ApiConfig) {
var Nacl = window.nacl; var Nacl = window.nacl;
var Block = {}; var Block = {};
@ -30,9 +31,11 @@ define([
var symmetric = seed.subarray(Nacl.sign.seedLength, var symmetric = seed.subarray(Nacl.sign.seedLength,
Nacl.sign.seedLength + Nacl.secretbox.keyLength); Nacl.sign.seedLength + Nacl.secretbox.keyLength);
console.log("symmetric key: ", Nacl.util.encodeBase64(symmetric));
return { return {
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
symmetric: symmetric, symmetric: symmetric, // 32 bytes ...
}; };
}; };
@ -51,8 +54,15 @@ define([
Block.decrypt = function (u8_content, keys) { Block.decrypt = function (u8_content, keys) {
// version is currently ignored since there is only one // version is currently ignored since there is only one
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength); var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
var box = content.subarray(1 + Nacl.secretbox.nonceLength); var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
return Nacl.secretbox.open(box, nonce, keys.symmetric);
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
try {
return JSON.parse(Nacl.util.encodeUTF8(plaintext));
} catch (e) {
console.error(e);
return;
}
}; };
// (Uint8Array block) => signature // (Uint8Array block) => signature
@ -86,5 +96,18 @@ define([
}; };
}; };
// FIXME don't spread the functions below across this file and common-hash
// find a permanent home for these hacks
var urlSafeB64 = function (u8) {
return Nacl.util.encodeBase64(u8).replace(/\//g, '-');
};
Block.getBlockHash = function (keys) {
var publicKey = urlSafeB64(keys.sign.publicKey);
var relative = 'block/' + publicKey.slice(0, 2) + '/' + publicKey; // XXX FIXME use configurable path from /api/config
var symmetric = urlSafeB64(keys.symmetric);
return ApiConfig.httpUnsafeOrigin + relative + '#' + symmetric;
};
return Block; return Block;
}); });

@ -71,6 +71,7 @@ define([
JOIN_PAD: Store.joinPad, JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad, LEAVE_PAD: Store.leavePad,
GET_FULL_HISTORY: Store.getFullHistory, GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
IS_NEW_CHANNEL: Store.isNewChannel, IS_NEW_CHANNEL: Store.isNewChannel,
// Drive // Drive
DRIVE_USEROBJECT: Store.userObjectCommand, DRIVE_USEROBJECT: Store.userObjectCommand,

@ -1,13 +1,24 @@
define([ define([
'jquery', 'jquery',
'/common/common-interface.js', '/common/common-interface.js',
'/bower_components/nthen/index.js',
//'/bower_components/chainpad-json-validator/json-ot.js', //'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function ($, UI, ChainPad /* JsonOT */) { ], function ($, UI, nThen, ChainPad /* JsonOT */) {
//var ChainPad = window.ChainPad; //var ChainPad = window.ChainPad;
var History = {}; var History = {};
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
var getStates = function (rt) { var getStates = function (rt) {
var states = []; var states = [];
var b = rt.getAuthBlock(); var b = rt.getAuthBlock();
@ -19,8 +30,7 @@ define([
return states; return states;
}; };
var loadHistory = function (config, common, cb) { var createRealtime = function (config) {
var createRealtime = function () {
return ChainPad.create({ return ChainPad.create({
userName: 'history', userName: 'history',
validateContent: function (content) { validateContent: function (content) {
@ -33,36 +43,45 @@ define([
} }
}, },
initialState: '', initialState: '',
//patchTransformer: ChainPad.NaiveJSONTransformer,
//logLevel: 0,
//transformFunction: JsonOT.validate,
logLevel: config.debug ? 2 : 0, logLevel: config.debug ? 2 : 0,
noPrune: true noPrune: true
}); });
}; };
var realtime = createRealtime();
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
/*var to = window.setTimeout(function () {
cb('[GET_FULL_HISTORY_TIMEOUT]');
}, 30000);*/
var loadFullHistory = function (config, common, cb) {
var realtime = createRealtime(config);
common.getFullHistory(realtime, function () { common.getFullHistory(realtime, function () {
//window.clearTimeout(to);
cb(null, realtime); cb(null, realtime);
}); });
}; };
loadFullHistory = loadFullHistory;
History.create = function (common, config) { var fillChainPad = function (realtime, messages) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");} messages.forEach(function (m) {
if (History.loading) { return void console.error("History is already being loaded..."); } realtime.message(m);
History.loading = true; });
var $toolbar = config.$toolbar; };
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) { var allMessages = [];
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory"); var lastKnownHash;
} var isComplete = false;
var loadMoreHistory = function (config, common, cb) {
if (isComplete) { return void cb ('EFULL'); }
var realtime = createRealtime(config);
var sframeChan = common.getSframeChannel();
sframeChan.query('Q_GET_HISTORY_RANGE', {
lastKnownHash: lastKnownHash
}, function (err, data) {
if (err) { return void console.error(err); }
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
lastKnownHash = data.lastKnownHash;
isComplete = data.isFull;
Array.prototype.unshift.apply(allMessages, data.messages); // Destructive concat
fillChainPad(realtime, allMessages);
cb (null, realtime);
});
};
// config.setHistory(bool, bool) // config.setHistory(bool, bool)
// - bool1: history value // - bool1: history value
@ -84,21 +103,20 @@ define([
}; };
config.setHistory(true); config.setHistory(true);
var onReady = function () { };
var Messages = common.Messages; var Messages = common.Messages;
var realtime; var realtime;
var states = []; var states = [];
var c = states.length - 1; var c = 0;//states.length - 1;
var $hist = $toolbar.find('.cp-toolbar-history'); var $hist = $toolbar.find('.cp-toolbar-history');
var $left = $toolbar.find('.cp-toolbar-leftside'); var $left = $toolbar.find('.cp-toolbar-leftside');
var $right = $toolbar.find('.cp-toolbar-rightside'); var $right = $toolbar.find('.cp-toolbar-rightside');
var $cke = $toolbar.find('.cke_toolbox_main'); var $cke = $toolbar.find('.cke_toolbox_main');
$hist.html('').show(); $hist.html('').css('display', 'flex');
$left.hide(); $left.hide();
$right.hide(); $right.hide();
$cke.hide(); $cke.hide();
@ -107,29 +125,73 @@ define([
var onUpdate; var onUpdate;
var update = function () { var update = function (newRt) {
realtime = newRt;
if (!realtime) { return []; } if (!realtime) { return []; }
states = getStates(realtime); states = getStates(realtime);
if (typeof onUpdate === "function") { onUpdate(); } if (typeof onUpdate === "function") { onUpdate(); }
return states; return states;
}; };
var $loadMore, $version, get;
// Get the content of the selected version, and change the version number // Get the content of the selected version, and change the version number
var get = function (i) { var loading = false;
var loadMore = function (cb) {
if (loading) { return; }
loading = true;
$loadMore.removeClass('fa fa-ellipsis-h')
.append($('<span>', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'}));
loadMoreHistory(config, common, function (err, newRt) {
if (err === 'EFULL') {
$loadMore.off('click').hide();
get(c);
$version.show();
return;
}
loading = false;
if (err) { return void console.error(err); }
update(newRt);
$loadMore.addClass('fa fa-ellipsis-h').html('');
get(c);
if (cb) { cb(); }
});
};
get = function (i) {
i = parseInt(i); i = parseInt(i);
if (isNaN(i)) { return; } if (isNaN(i)) { return; }
if (i < 0) { i = 0; } if (i > 0) { i = 0; }
if (i > states.length - 1) { i = states.length - 1; } if (i < -(states.length - 2)) { i = -(states.length - 2); }
var val = states[i].getContent().doc; if (i <= -(states.length - 11)) {
loadMore();
}
var idx = states.length - 1 + i;
var val = states[idx].getContent().doc;
c = i; c = i;
if (typeof onUpdate === "function") { onUpdate(); } if (typeof onUpdate === "function") { onUpdate(); }
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous').css('visibility', ''); $hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' +
if (c === states.length - 1) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); } '.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous')
if (c === 0) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); } .css('visibility', '');
if (c === -(states.length-1)) {
$hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden');
$hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden');
}
if (c === 0) {
$hist.find('.cp-toolbar-history-next').css('visibility', 'hidden');
$hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden');
}
var $pos = $hist.find('.cp-toolbar-history-pos');
var p = 100 * (1 - (-c / (states.length-1)));
$pos.css('margin-left', p+'%');
// Display the version when the full history is loaded
// Note: the first version is always empty and probably can't be displayed, so
// we can consider we have only states.length - 1 versions
$version.text(idx + ' / ' + (states.length-1));
if (config.debug) { if (config.debug) {
console.log(states[i]); console.log(states[idx]);
var ops = states[i] && states[i].getPatch() && states[i].getPatch().operations; var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations;
if (Array.isArray(ops)) { if (Array.isArray(ops)) {
ops.forEach(function (op) { console.log(op); }); ops.forEach(function (op) { console.log(op); });
} }
@ -148,6 +210,17 @@ define([
// Create the history toolbar // Create the history toolbar
var display = function () { var display = function () {
$hist.html(''); $hist.html('');
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o',
title: Messages.history_restoreTitle
}).appendTo($hist);//.text(Messages.history_restore);
if (History.readOnly) { $rev.css('visibility', 'hidden'); }
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $fastPrev = $('<button>', {
'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary',
title: Messages.history_prev
}).appendTo($hist);
var $prev =$('<button>', { var $prev =$('<button>', {
'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary', 'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary',
title: Messages.history_prev title: Messages.history_prev
@ -157,58 +230,73 @@ define([
'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary', 'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary',
title: Messages.history_next title: Messages.history_next
}).appendTo($hist); }).appendTo($hist);
var $fastNext = $('<button>', {
'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $close = $('<button>', {
'class':'cp-toolbar-history-close fa fa-window-close',
title: Messages.history_closeTitle
}).appendTo($hist);
var $bar = $('<div>', {'class': 'cp-toolbar-history-bar'}).appendTo($nav);
var $container = $('<div>', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar);
$('<div>', {'class': 'cp-toolbar-history-pos'}).appendTo($container);
$version = $('<span>', {
'class': 'cp-toolbar-history-version'
}).prependTo($bar).hide();
$loadMore = $('<button>', {
'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h',
title: Messages.history_loadMore
}).click(function () {
loadMore(function () {
get(c);
});
}).prependTo($container);
$('<label>').text(Messages.history_version).appendTo($nav); // Load a version when clicking on the bar
var $cur = $('<input>', { $container.click(function (e) {
'class' : 'cp-toolbar-history-goto-input',
'type' : 'number',
'min' : '1',
'max' : states.length
}).val(c + 1).appendTo($nav).mousedown(function (e) {
// stopPropagation because the event would be cancelled by the dropdown menus
e.stopPropagation(); e.stopPropagation();
if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; }
var p = e.offsetX / $container.width();
var v = -Math.round((states.length - 1) * (1 - p));
render(get(v));
}); });
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
$('<br>').appendTo($nav);
var $close = $('<button>', {
'class':'cp-toolbar-history-close',
title: Messages.history_closeTitle
}).text(Messages.history_closeTitle).appendTo($nav);
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
if (History.readOnly) { $rev.hide(); }
onUpdate = function () { onUpdate = function () {
$cur.attr('max', states.length); // Called when a new version is loaded
$cur.val(c+1);
$label2.text(' / ' + states.length);
}; };
var onKeyDown, onKeyUp;
var close = function () { var close = function () {
$hist.hide(); $hist.hide();
$left.show(); $left.show();
$right.show(); $right.show();
$cke.show(); $cke.show();
$(window).trigger('resize'); $(window).trigger('resize');
$(window).off('keydown', onKeyDown);
$(window).off('keyup', onKeyUp);
}; };
// Buttons actions // Version buttons
$prev.click(function () { render(getPrevious()); }); $prev.click(function () { render(getPrevious()); });
$next.click(function () { render(getNext()); }); $next.click(function () { render(getNext()); });
$cur.keydown(function (e) { $fastPrev.click(function () { render(getPrevious(10)); });
$fastNext.click(function () { render(getNext(10)); });
onKeyDown = function (e) {
var p = function () { e.preventDefault(); }; var p = function () { e.preventDefault(); };
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
if (e.which === 27) { p(); $close.click(); } if (e.which === 27) { p(); $close.click(); }
}).keyup(function (e) { e.stopPropagation(); }).focus(); };
$cur.on('change', function () { onKeyUp = function (e) { e.stopPropagation(); };
render( get($cur.val() - 1) ); $(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
});
// Close & restore buttons
$close.click(function () { $close.click(function () {
states = []; states = [];
close(); close();
@ -229,14 +317,13 @@ define([
}; };
// Load all the history messages into a new chainpad object // Load all the history messages into a new chainpad object
loadHistory(config, common, function (err, newRt) { loadMoreHistory(config, common, function (err, newRt) {
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
History.loading = false; History.loading = false;
if (err) { throw new Error(err); } if (err) { throw new Error(err); }
realtime = newRt; update(newRt);
update();
c = states.length - 1; c = states.length - 1;
display(); display();
onReady();
}); });
}; };

@ -420,6 +420,24 @@ define([
})); }));
}); });
}); });
sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
var crypto = Crypto.createEncryptor(secret.keys);
Cryptpad.getHistoryRange({
channel: secret.channel,
validateKey: secret.keys.validateKey,
lastKnownHash: data.lastKnownHash
}, function (data) {
cb({
isFull: data.isFull,
messages: data.messages.map(function (msg) {
// The 3rd parameter "true" means we're going to skip signature validation.
// We don't need it since the message is already validated serverside by hk
return crypto.decrypt(msg, true, true);
}),
lastKnownHash: data.lastKnownHash
});
});
});
sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) { sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
var href; var href;

@ -90,6 +90,7 @@ define({
// Request the full history from the server when the users clicks on the history button. // Request the full history from the server when the users clicks on the history button.
// Callback is called when the FULL_HISTORY_END message is received in the outside. // Callback is called when the FULL_HISTORY_END message is received in the outside.
'Q_GET_FULL_HISTORY': true, 'Q_GET_FULL_HISTORY': true,
'Q_GET_HISTORY_RANGE': true,
// When a (full) history message is received from the server. // When a (full) history message is received from the server.
'EV_RT_HIST_MESSAGE': true, 'EV_RT_HIST_MESSAGE': true,

@ -55,6 +55,15 @@
width: @sidebar_button-width; width: @sidebar_button-width;
} }
} }
.cp-settings-change-password {
[type="password"], [type="text"] {
width: @sidebar_button-width;
flex: unset;
}
button {
margin-top: 5px;
}
}
.cp-settings-drive-backup { .cp-settings-drive-backup {
button { button {
span.fa { span.fa {

@ -9,8 +9,10 @@ define([
'/common/common-hash.js', '/common/common-hash.js',
'/customize/messages.js', '/customize/messages.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/credential.js',
'/customize/application_config.js', '/customize/application_config.js',
'/api/config', '/api/config',
'/common/outer/login-block.js', // XXX HACK
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -27,8 +29,10 @@ define([
Hash, Hash,
Messages, Messages,
h, h,
Cred,
AppConfig, AppConfig,
ApiConfig ApiConfig,
Block // XXX HACK
) )
{ {
var saveAs = window.saveAs; var saveAs = window.saveAs;
@ -376,13 +380,32 @@ define([
var $div = $('<div>', { 'class': 'cp-settings-change-password cp-sidebarlayout-element'}); var $div = $('<div>', { 'class': 'cp-settings-change-password cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text("TODO Change your password").appendTo($div); // XXX $('<span>', {'class': 'label'}).text(Messages.settings_changePasswordTitle).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'}) $('<span>', {'class': 'cp-sidebarlayout-description'})
.append("TODO").appendTo($div); // XXX .append(Messages.settings_changePasswordHint).appendTo($div);
// var publicKey = privateData.edPublic; // var publicKey = privateData.edPublic;
var form = h('div', [
UI.passwordInput({
id: 'cp-settings-change-password-current',
placeholder: Messages.settings_changePasswordCurrent
}, true),
h('br'),
UI.passwordInput({
id: 'cp-settings-change-password-new',
placeholder: Messages.settings_changePasswordNew
}, true),
UI.passwordInput({
id: 'cp-settings-change-password-new2',
placeholder: Messages.settings_changePasswordNewConfirm
}, true),
h('button.btn.btn-primary', Messages.settings_changePasswordButton)
]);
$(form).appendTo($div);
var updateBlock = function (data, cb) { var updateBlock = function (data, cb) {
sframeChan.query('Q_WRITE_LOGIN_BLOCK', data, function (err, obj) { sframeChan.query('Q_WRITE_LOGIN_BLOCK', data, function (err, obj) {
if (err || obj.error) { return void cb ({error: err || obj.error}); } if (err || obj.error) { return void cb ({error: err || obj.error}); }
@ -390,7 +413,83 @@ define([
}); });
}; };
updateBlock = updateBlock; // jshint.. var removeBlock = function (data, cb) {
sframeChan.query('Q_REMOVE_LOGIN_BLOCK', data, function (err, obj) {
if (err || obj.error) { return void cb ({error: err || obj.error}); }
cb (obj);
});
};
// XXX
if (false) { // STUBBED, just for development purposes
console.error("TRYING TO WRITE A BLOCK");
var keys = Block.genkeys(Block.seed());
var data = Block.serialize(JSON.stringify({
a: 5,
b: 6,
User_hash: "XXX", /// TODO encode newly derived User_hash here
}), keys);
updateBlock(data, function (err, thing) {
console.log(err, thing);
console.log(Block.getBlockHash(keys));
return;
removeBlock(Block.remove(keys), function (err, obj) {
console.log(err, obj);
});
});
}
var todo = function () {
var oldPassword = $(form).find('#cp-settings-change-password-current').val();
var newPassword = $(form).find('#cp-settings-change-password-new').val();
var newPasswordConfirm = $(form).find('#cp-settings-change-password-new2').val();
/* basic validation */
if (!Cred.isLongEnoughPassword(newPassword)) {
var warning = Messages._getKey('register_passwordTooShort', [
Cred.MINIMUM_PASSWORD_LENGTH
]);
return void UI.alert(warning);
}
if (newPassword !== newPasswordConfirm) {
UI.alert(Messages.register_passwordsDontMatch);
return;
}
UI.confirm(Messages.settings_changePasswordConfirm,
function (yes) {
if (!yes) { return; }
// TODO
console.log(oldPassword, newPassword, newPasswordConfirm);
}, {
ok: Messages.register_writtenPassword,
cancel: Messages.register_cancel,
cancelClass: 'safe',
okClass: 'danger',
reverseOrder: true,
done: function ($dialog) {
$dialog.find('> div').addClass('half');
},
}, true);
};
$(form).find('button').click(function () {
todo();
});
$(form).find('input').keydown(function (e) {
// Save on Enter
if (e.which === 13) {
e.preventDefault();
e.stopPropagation();
todo();
}
});
return $div; return $div;
}; };

Loading…
Cancel
Save