Added cryptsheet to cryptpad suite!

This commit is contained in:
Caleb James DeLisle 2015-01-29 17:55:18 +01:00
parent 4a63ba7df3
commit 50c10f818e
18 changed files with 525 additions and 54 deletions

View File

@ -1,3 +1,3 @@
{ {
"directory" : "www/bower" "directory" : "www/bower_components"
} }

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
www/bower/* www/bower_components/*
node_modules node_modules
/config.js /config.js

View File

@ -18,7 +18,8 @@
"tests" "tests"
], ],
"dependencies": { "dependencies": {
"jquery": "~2.1.1", "jquery.sheet": "master",
"jquery": "~2.1.3",
"tweetnacl": "~0.12.2", "tweetnacl": "~0.12.2",
"ckeditor": "~4.4.5", "ckeditor": "~4.4.5",
"requirejs": "~2.1.15", "requirejs": "~2.1.15",

View File

@ -12,6 +12,12 @@ config.websocketPort = config.websocketPort || config.httpPort;
var app = Express(); var app = Express();
app.use(Express.static(__dirname + '/www')); app.use(Express.static(__dirname + '/www'));
// Bower is broken and does not allow components nested within components...
// And jquery.sheet expects it!
// *Workaround*
app.use("/bower_components/jquery.sheet/bower_components",
Express.static(__dirname + '/www/bower_components'));
var httpsOpts; var httpsOpts;
if (config.privKeyAndCertFiles) { if (config.privKeyAndCertFiles) {
var privKeyAndCerts = ''; var privKeyAndCerts = '';

View File

@ -589,6 +589,18 @@ var unschedule = function (realtime, schedule) {
clearTimeout(schedule); clearTimeout(schedule);
}; };
var onMessage = function (realtime, message, callback) {
if (!realtime.messageHandlers.length) {
callback("no onMessage() handler registered");
}
for (var i = 0; i < realtime.messageHandlers.length; i++) {
realtime.messageHandlers[i](message, function () {
callback.apply(null, arguments);
callback = function () { };
});
}
};
var sync = function (realtime) { var sync = function (realtime) {
if (Common.PARANOIA) { check(realtime); } if (Common.PARANOIA) { check(realtime); }
if (realtime.syncSchedule) { if (realtime.syncSchedule) {
@ -622,7 +634,7 @@ var sync = function (realtime) {
var strMsg = Message.toString(msg); var strMsg = Message.toString(msg);
realtime.onMessage(strMsg, function (err) { onMessage(realtime, strMsg, function (err) {
if (err) { if (err) {
debug(realtime, "Posting to server failed [" + err + "]"); debug(realtime, "Posting to server failed [" + err + "]");
} }
@ -657,7 +669,7 @@ var getMessages = function (realtime) {
realtime.authToken, realtime.authToken,
realtime.channelId, realtime.channelId,
Message.REGISTER); Message.REGISTER);
realtime.onMessage(Message.toString(msg), function (err) { onMessage(realtime, Message.toString(msg), function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
}; };
@ -670,7 +682,7 @@ var sendPing = function (realtime) {
realtime.channelId, realtime.channelId,
Message.PING, Message.PING,
realtime.lastPingTime); realtime.lastPingTime);
realtime.onMessage(Message.toString(msg), function (err) { onMessage(realtime, Message.toString(msg), function (err) {
if (err) { throw err; } if (err) { throw err; }
}); });
}; };
@ -706,9 +718,7 @@ var create = ChainPad.create = function (userName, authToken, channelId, initial
patchHandlers: [], patchHandlers: [],
opHandlers: [], opHandlers: [],
onMessage: function (message, callback) { messageHandlers: [],
callback("no onMessage() handler registered");
},
schedules: [], schedules: [],
@ -1127,7 +1137,8 @@ module.exports.create = function (userName, authToken, channelId, initialState,
doOperation(realtime, Operation.create(offset, 0, str)); doOperation(realtime, Operation.create(offset, 0, str));
}), }),
onMessage: enterChainPad(realtime, function (handler) { onMessage: enterChainPad(realtime, function (handler) {
realtime.onMessage = handler; Common.assert(typeof(handler) === 'function');
realtime.messageHandlers.push(handler);
}), }),
message: enterChainPad(realtime, function (message) { message: enterChainPad(realtime, function (message) {
handleMessage(realtime, message); handleMessage(realtime, message);

172
www/common/toolbar.js Normal file
View File

@ -0,0 +1,172 @@
var Toolbar = function ($, container, Messages, myUserName, realtime) {
/** Id of the element for getting debug info. */
var DEBUG_LINK_CLS = 'rtwysiwyg-debug-link';
/** Id of the div containing the user list. */
var USER_LIST_CLS = 'rtwysiwyg-user-list';
/** Id of the div containing the lag info. */
var LAG_ELEM_CLS = 'rtwysiwyg-lag';
/** The toolbar class which contains the user list, debug link and lag. */
var TOOLBAR_CLS = 'rtwysiwyg-toolbar';
/** Key in the localStore which indicates realtime activity should be disallowed. */
var LOCALSTORAGE_DISALLOW = 'rtwysiwyg-disallow';
var SPINNER_DISAPPEAR_TIME = 3000;
var SPINNER = [ '-', '\\', '|', '/' ];
var uid = function () {
return 'rtwysiwyg-uid-' + String(Math.random()).substring(2);
};
var createRealtimeToolbar = function (container) {
var id = uid();
$(container).prepend(
'<div class="' + TOOLBAR_CLS + '" id="' + id + '">' +
'<div class="rtwysiwyg-toolbar-leftside"></div>' +
'<div class="rtwysiwyg-toolbar-rightside"></div>' +
'</div>'
);
var toolbar = $('#'+id);
toolbar.append([
'<style>',
'.' + TOOLBAR_CLS + ' {',
' color: #666;',
' font-weight: bold;',
// ' background-color: #f0f0ee;',
// ' border-bottom: 1px solid #DDD;',
// ' border-top: 3px solid #CCC;',
// ' border-right: 2px solid #CCC;',
// ' border-left: 2px solid #CCC;',
' height: 26px;',
' margin-bottom: -3px;',
' display: inline-block;',
' width: 100%;',
'}',
'.' + TOOLBAR_CLS + ' div {',
' padding: 0 10px;',
' height: 1.5em;',
// ' background: #f0f0ee;',
' line-height: 25px;',
' height: 22px;',
'}',
'.rtwysiwyg-toolbar-leftside {',
' float: left;',
'}',
'.rtwysiwyg-toolbar-rightside {',
' float: right;',
'}',
'.rtwysiwyg-lag {',
' float: right;',
'}',
'.rtwysiwyg-spinner {',
' float: left;',
'}',
'.gwt-TabBar {',
' display:none;',
'}',
'.' + DEBUG_LINK_CLS + ':link { color:transparent; }',
'.' + DEBUG_LINK_CLS + ':link:hover { color:blue; }',
'.gwt-TabPanelBottom { border-top: 0 none; }',
'</style>'
].join('\n'));
return toolbar;
};
var createSpinner = function (container) {
var id = uid();
$(container).append('<div class="rtwysiwyg-spinner" id="'+id+'"></div>');
return $('#'+id)[0];
};
var kickSpinner = function (spinnerElement, reversed) {
var txt = spinnerElement.textContent || '-';
var inc = (reversed) ? -1 : 1;
spinnerElement.textContent = SPINNER[(SPINNER.indexOf(txt) + inc) % SPINNER.length];
spinnerElement.timeout && clearTimeout(spinnerElement.timeout);
spinnerElement.timeout = setTimeout(function () {
spinnerElement.textContent = '';
}, SPINNER_DISAPPEAR_TIME);
};
var createUserList = function (container) {
var id = uid();
$(container).prepend('<div class="' + USER_LIST_CLS + '" id="'+id+'"></div>');
return $('#'+id)[0];
};
var updateUserList = function (myUserName, listElement, userList) {
var meIdx = userList.indexOf(myUserName);
if (meIdx === -1) {
listElement.textContent = Messages.synchronizing;
return;
}
if (userList.length === 1) {
listElement.textContent = Messages.editingAlone;
} else if (userList.length === 2) {
listElement.textContent = Messages.editingWithOneOtherPerson;
} else {
listElement.textContent = Messages.editingWith + ' ' + (userList.length - 1) + ' '
Messages.otherPeople;
}
};
var createLagElement = function (container) {
var id = uid();
$(container).append('<div class="' + LAG_ELEM_CLS + '" id="'+id+'"></div>');
return $('#'+id)[0];
};
var checkLag = function (realtime, lagElement) {
var lag = realtime.getLag();
var lagSec = lag.lag/1000;
var lagMsg = Messages.lag + ' ';
if (lag.waiting && lagSec > 1) {
lagMsg += "?? " + Math.floor(lagSec);
} else {
lagMsg += lagSec;
}
lagElement.textContent = lagMsg;
};
var toolbar = createRealtimeToolbar(container);
var userListElement = createUserList(toolbar.find('.rtwysiwyg-toolbar-leftside'));
var spinner = createSpinner(toolbar.find('.rtwysiwyg-toolbar-rightside'));
var lagElement = createLagElement(toolbar.find('.rtwysiwyg-toolbar-rightside'));
var connected = false;
realtime.onUserListChange(function (userList) {
if (userList.indexOf(myUserName) !== -1) { connected = true; }
if (!connected) { return; }
updateUserList(myUserName, userListElement, userList);
});
var ks = function () {
if (connected) { kickSpinner(spinner, false); }
};
realtime.onPatch(ks);
realtime.onMessage(ks);
setInterval(function () {
if (!connected) { return; }
checkLag(realtime, lagElement);
}, 3000);
return {
reconnecting: function () {
connected = false;
userListElement.textContent = Messages.reconnecting;
lagElement.textContent = '';
},
connected: function () {
connected = true;
}
};
};

View File

@ -3,25 +3,32 @@
<head> <head>
<!--<title>Sample - CKEditor</title>--> <!--<title>Sample - CKEditor</title>-->
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script data-main="main" src="bower/requirejs/require.js"></script> <link rel="stylesheet" type="text/css" href="index.css" />
<script>
if (window.location.href.indexOf('#') === -1) {
document.head.innerHTML +=
'<link rel="stylesheet" type="text/css" href="index.css" />';
}
</script>
<style> <style>
#whatis { #whatis {
padding-left: 15%; padding-left: 15%;
padding-right: 15%; padding-right: 15%;
font-size: medium; font-size: medium;
} }
#create-pad { .create {
background-color: #009afd; background-color: rgb(77, 146, 68);
background-image: linear-gradient(rgb(39, 100, 0) 0%, rgb(77, 146, 68) 100%);
border-bottom-color: rgb(77, 146, 68);
color: rgb(243, 243, 243);
font-weight:bold; font-weight:bold;
font-size:large; font-size:large;
width:150px; margin-right: 5px;
height:35px; margin-left: 5px;
}
.buttons {
margin-top: 10px;
}
.button {
padding: 2px 6px 2px 6px;
border-top: 1px solid #CCCCCC;
border-right: 1px solid #333333;
border-bottom: 1px solid #333333;
border-left: 1px solid #CCCCCC;
} }
</style> </style>
</head> </head>
@ -48,16 +55,14 @@
get caught and laughed at and humiliated in front of the whole world (again). If you&#39;re making get caught and laughed at and humiliated in front of the whole world (again). If you&#39;re making
the NSA mad enough for them to use an active attack against you, Great Success Highfive, now take the NSA mad enough for them to use an active attack against you, Great Success Highfive, now take
the battery out of your computer before it spawns Agent Smith.</p> the battery out of your computer before it spawns Agent Smith.</p>
<center>
<h5>Try it out!</h5>
<div class="buttons">
<a class="button create" href="pad">CREATE PAD</a>
<a class="button create" href="sheet">CREATE SPREADSHEET</a>
</div>
</center>
</div> </div>
<form id="go" action="#" method="post">
<center>
<h5>Try it out!</h5>
<br/>
<input id="create-pad" type="submit" name="x" value="Create Pad!" />
</center>
<textarea style="display:none" cols="80" id="editor1" name="editor1" rows="10">
</textarea>
</form>
</body> </body>
</html> </html>

View File

@ -16,12 +16,14 @@
*/ */
require.config({ require.config({
'shim': { 'shim': {
'bower/modalBox/modalBox-min': ['bower/jquery/dist/jquery.min'], '/bower_components/modalBox/modalBox-min.js': [
'/bower_components/jquery/dist/jquery.min.js'
],
} }
}); });
define([ define([
'messages', '/common/messages.js',
'bower/modalBox/modalBox-min' '/bower_components/modalBox/modalBox-min.js'
], function (Messages) { ], function (Messages) {
var STYLE = [ var STYLE = [

View File

@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
define([ define([
'bower/jquery/dist/jquery.min', '/bower_components/jquery/dist/jquery.min.js',
'otaml' '/common/otaml.js'
], function () { ], function () {
var $ = jQuery; var $ = jQuery;

11
www/pad/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
</head>
<body>
<textarea style="display:none" cols="80" id="editor1" name="editor1" rows="10"></textarea>
</body>
</html>

View File

@ -1,10 +1,10 @@
define([ define([
'api/config?cb=' + Math.random().toString(16).substring(2), '/api/config?cb=' + Math.random().toString(16).substring(2),
'realtime-wysiwyg', '/pad/realtime-wysiwyg.js',
'messages', '/common/messages.js',
'bower/jquery/dist/jquery.min', '/bower_components/jquery/dist/jquery.min.js',
'bower/ckeditor/ckeditor', '/bower_components/ckeditor/ckeditor.js',
'bower/tweetnacl/nacl-fast.min', '/bower_components/tweetnacl/nacl-fast.min.js',
], function (Config, RTWysiwyg, Messages) { ], function (Config, RTWysiwyg, Messages) {
var Ckeditor = window.CKEDITOR; var Ckeditor = window.CKEDITOR;
var Nacl = window.nacl; var Nacl = window.nacl;
@ -31,10 +31,7 @@ define([
window.location.reload(); window.location.reload();
}); });
if (window.location.href.indexOf('#') === -1) { if (window.location.href.indexOf('#') === -1) {
$('#create-pad').click(function (ev) { window.location.href = window.location.href + '#' + genKey();
ev.preventDefault();
window.location.href = window.location.href + '#' + genKey();
});
return; return;
} }
var key = parseKey(window.location.hash.substring(1)); var key = parseKey(window.location.hash.substring(1));

View File

@ -15,15 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
define([ define([
'html-patcher', '/pad/html-patcher.js',
'errorbox', '/pad/errorbox.js',
'messages', '/common/messages.js',
'bower/reconnectingWebsocket/reconnecting-websocket', '/bower_components/reconnectingWebsocket/reconnecting-websocket.js',
'rangy', '/pad/rangy.js',
'chainpad', '/common/chainpad.js',
'otaml', '/common/otaml.js',
'bower/jquery/dist/jquery.min', '/bower_components/jquery/dist/jquery.min.js',
'bower/tweetnacl/nacl-fast.min' '/bower_components/tweetnacl/nacl-fast.min.js'
], function (HTMLPatcher, ErrorBox, Messages, ReconnectingWebSocket) { ], function (HTMLPatcher, ErrorBox, Messages, ReconnectingWebSocket) {
window.ErrorBox = ErrorBox; window.ErrorBox = ErrorBox;

29
www/sheet/index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
<style>
iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
textarea {
display:none;
}
</style>
</head>
<body>
<iframe src="inner.html"></iframe>
<textarea id="sheet-json"></textarea>
</body>
</html>

36
www/sheet/inner.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="-1">
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>jQuery.sheet - The Ajax Spreadsheet DTS Lazy Loading</title>
<script src="/bower_components/jquery-legacy/dist/jquery.js"></script>
<script src="/bower_components/jquery.sheet/parser/formula/formula.js"></script>
<script src="/bower_components/jquery.sheet/jquery.sheet.js"></script>
<script>
var numColumns = 10;
var numRows = 20;
var spreadsheetJson = [ { "title": "", "rows": [ ] } ];
for (var i = 0; i < numRows; i++) {
var columns = [];
spreadsheetJson[0].rows.push({ columns: columns });
for (var j = 0; j < numColumns; j++) { columns[j] = { "value": "" }; }
}
$.sheet.preLoad('/bower_components/jquery.sheet/');
$(function () {
window.sheetLdr = new Sheet.JSONLoader( spreadsheetJson );
window.sh = $('#sheet-json').height($(window).height()).width($(window).width()).sheet({
loader: window.sheetLdr,
minSize: { rows:numRows, cols:numColumns }
});
});
</script>
</head>
<body>
<div id="sheet-json" title="CryptSheet"></div>
</body>
</html>

201
www/sheet/main.js Normal file
View File

@ -0,0 +1,201 @@
define([
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/messages.js',
'/common/toolbar.js',
'/common/chainpad.js',
'/bower_components/jquery/dist/jquery.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/common/otaml.js'
], function (Config, Messages) {
var Nacl = window.nacl;
var $ = jQuery;
var ChainPad = window.ChainPad;
var Otaml = window.Otaml;
var Toolbar = window.Toolbar;
var module = { exports: {} };
var parseKey = function (str) {
var array = Nacl.util.decodeBase64(str);
var hash = Nacl.hash(array);
return { lookupKey: hash.subarray(32), cryptKey: hash.subarray(0,32) };
};
var genKey = function () {
return Nacl.util.encodeBase64(Nacl.randomBytes(18));
};
var userName = function () {
return 'Other-' + Nacl.util.encodeBase64(Nacl.randomBytes(8));
};
var sheetToJson = function (ifrWindow) {
var xx = ifrWindow.sh[0].jS
var m = [];
for (var i = 0; i < xx.spreadsheets.length; i++) {
m[i]=[];
var sheet = xx.spreadsheets[i];
for (var j = 1; j < sheet.length; j++) {
m[i][j]=[];
var row = sheet[j];
for (var k = 1; k < row.length; k++) {
var col = row[k];
m[i][j][k] = { value: col.value, formula: col.formula };
}
}
}
return m;
};
var jsonToSheet = function (ifrWindow, json) {
var xx = ifrWindow.sh[0].jS;
for (var i = 0; i < xx.spreadsheets.length; i++) {
var sheet = xx.spreadsheets[i];
for (var j = 1; j < sheet.length; j++) {
var row = sheet[j];
for (var k = 1; k < row.length; k++) {
var col = row[k];
var jcol = json[i][j][k];
if (jcol.value === col.value && jcol.formula === col.formula) { continue; }
col.value = jcol.value;
col.formula = jcol.formula;
col.displayValue();
}
}
}
};
var encryptStr = function (str, key) {
var array = Nacl.util.decodeUTF8(str);
var nonce = Nacl.randomBytes(24);
var packed = Nacl.secretbox(array, nonce, key);
if (!packed) { throw new Error(); }
return Nacl.util.encodeBase64(nonce) + "|" + Nacl.util.encodeBase64(packed);
};
var decryptStr = function (str, key) {
var arr = str.split('|');
if (arr.length !== 2) { throw new Error(); }
var nonce = Nacl.util.decodeBase64(arr[0]);
var packed = Nacl.util.decodeBase64(arr[1]);
var unpacked = Nacl.secretbox.open(packed, nonce, key);
if (!unpacked) { throw new Error(); }
return Nacl.util.encodeUTF8(unpacked);
};
// this is crap because of bencoding messages... it should go away....
var splitMessage = function (msg, sending) {
var idx = 0;
var nl;
for (var i = ((sending) ? 0 : 1); i < 3; i++) {
nl = msg.indexOf(':',idx);
idx = nl + Number(msg.substring(idx,nl)) + 1;
}
return [ msg.substring(0,idx), msg.substring(msg.indexOf(':',idx) + 1) ];
};
var encrypt = function (msg, key) {
var spl = splitMessage(msg, true);
var json = JSON.parse(spl[1]);
// non-patches are not encrypted.
if (json[0] !== 2) { return msg; }
json[1] = encryptStr(JSON.stringify(json[1]), key);
var res = JSON.stringify(json);
return spl[0] + res.length + ':' + res;
};
var decrypt = function (msg, key) {
var spl = splitMessage(msg, false);
var json = JSON.parse(spl[1]);
// non-patches are not encrypted.
if (json[0] !== 2) { return msg; }
if (typeof(json[1]) !== 'string') { throw new Error(); }
json[1] = JSON.parse(decryptStr(json[1], key));
var res = JSON.stringify(json);
return spl[0] + res.length + ':' + res;
};
var applyChange = function(ctx, oldval, newval) {
if (oldval === newval) return;
var commonStart = 0;
while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
commonStart++;
}
var commonEnd = 0;
while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) &&
commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
commonEnd++;
}
if (oldval.length !== commonStart + commonEnd) {
ctx.remove(commonStart, oldval.length - commonStart - commonEnd);
}
if (newval.length !== commonStart + commonEnd) {
ctx.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd));
}
};
$(function () {
if (window.location.href.indexOf('#') === -1) {
window.location.href = window.location.href + '#' + genKey();
}
$(window).on('hashchange', function() {
window.location.reload();
});
var $sheetJson = $('#sheet-json');
var ifrw = $('iframe')[0].contentWindow;
var sheetEvent = function (realtime) {
var sheetJson = JSON.stringify(sheetToJson(ifrw));
applyChange(realtime, realtime.getUserDoc(), sheetJson);
$sheetJson.text(sheetJson);
};
var eventPending = false;
var realtimeEvent = function (realtime) {
if (eventPending) { return; }
eventPending = true;
setTimeout(function () {
eventPending = false;
try{
var data = window.data = realtime.getUserDoc();
$sheetJson.text(data);
var json = JSON.parse(data);
jsonToSheet(ifrw, json);
}catch(e) { console.log(e.stack); }
}, 0);
};
var key = parseKey(window.location.hash.substring(1));
var channel = Nacl.util.encodeBase64(key.lookupKey).substring(0,10);
var myUserName = userName();
var socket = new WebSocket(Config.websocketURL);
socket.onopen = function () {
var realtime = ChainPad.create(
myUserName, 'x', channel, '', { transformFunction: Otaml.transform });
socket.onmessage = function (evt) {
var message = decrypt(evt.data, key.cryptKey);
realtime.message(message);
};
realtime.onMessage(function (message) {
message = encrypt(message, key.cryptKey);
try {
socket.send(message);
} catch (e) {
console.log(e.stack);
}
});
ifrw.sh.on('sheetCellEdited', function () { sheetEvent(realtime); });
sheetEvent(realtime);
realtime.onPatch(function () { realtimeEvent(realtime); });
ifrw.$('.jSTitle').html('');
Toolbar(ifrw.$, ifrw.$('.jSTitle'), Messages, myUserName, realtime);
realtime.start();
};
});
});