in the middle of rewriting it

master
Marc Wäckerlin 9 years ago
parent d0e32dfcae
commit ded3085d90
  1. 1
      nodejs/database/index.js
  2. 9
      nodejs/etc/systemd/system/safechat.service
  3. 1235
      nodejs/public/javascripts/safechat.js
  4. 30
      nodejs/routes/index.js
  5. 23
      nodejs/safechat.js
  6. 185
      nodejs/sockets/index.js
  7. 21
      nodejs/views/index.ejs

@ -3,7 +3,6 @@ module.exports = function(config) {
var fs = require('fs'); var fs = require('fs');
config.multipleStatements = true; config.multipleStatements = true;
var pool = mysql.createPool(config); var pool = mysql.createPool(config);
console.log(__dirname+'/schema.sql')
pool.query(fs.readFileSync(__dirname+'/schema.sql').toString()); pool.query(fs.readFileSync(__dirname+'/schema.sql').toString());
if (config.max_allowed_packet) if (config.max_allowed_packet)
pool.query("set global max_allowed_packet=?", [config.max_allowed_packet]); pool.query("set global max_allowed_packet=?", [config.max_allowed_packet]);

@ -0,0 +1,9 @@
[Unit]
Description=Secure and Encrypted Chat Server
[Service]
ExecStart=/usr/bin/nodejs /usr/share/safechat/nodejs/safechat > /var/log/safechat.log
Restart=on-abort
[Install]
WantedBy=multi-user.target

@ -1,245 +1,313 @@
/*! @file /*! @file
@id $Id$ @id $Id$
This is the main application as it is fully run in the user's browser. This is the main application as it is fully run in the user's browser.
@dot @dot
digraph X { digraph X {
start [URL="\ref start()"]; start [URL="\ref start()"];
newuser [URL="\ref newuser()"]; newuser [URL="\ref newuser()"];
login [URL="\ref login()"]; login [URL="\ref login()"];
createkeypair [URL="\ref createkeypair()"]; createkeypair [URL="\ref createkeypair()"];
chat [URL="\ref chat()"]; chat [URL="\ref chat()"];
getpwd [URL="\ref getpwd()"]; getpwd [URL="\ref getpwd()"];
setpw [URL="\ref setpw()"]; setpw [URL="\ref setpw()"];
get [URL="\ref get()"]; get [URL="\ref get()"];
sendmessage [URL="\ref sendmessage()"]; sendmessage [URL="\ref sendmessage()"];
start -> newuser [label="if no keys exist"]; start -> newuser [label="if no keys exist"];
start -> login [label="if keys exist"]; start -> login [label="if keys exist"];
newuser -> createkeypair [label="on submit"]; newuser -> createkeypair [label="on submit"];
createkeypair -> "openpgp.generateKeyPair"; createkeypair -> "openpgp.generateKey";
"openpgp.generateKeyPair" -> login [label="keys generated in local store"]; "openpgp.generateKey" -> login [label="keys generated in local store"];
login -> chat [label="user is valid on server"]; login -> chat [label="user is valid on server"];
chat -> getpwd [label="password not yet entered"]; chat -> getpwd [label="password not yet entered"];
getpwd -> setpw [label="on input"]; getpwd -> setpw [label="on input"];
setpw -> chat [label="password is valid"]; setpw -> chat [label="password is valid"];
chat -> chat [label="remain in chat"]; chat -> chat [label="remain in chat"];
chat -> get [label="start timer"]; chat -> get [label="start timer"];
get -> get [label="restart timer"]; get -> get [label="restart timer"];
chat -> sendmessage [label="on submit"]; chat -> sendmessage [label="on submit"];
sendmessage -> chat [label="remain in chat"]; sendmessage -> chat [label="remain in chat"];
} }
@enddot @enddot
*/ */
// 1 2 3 4 5 6 7 8 // 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890 // 45678901234567890123456789012345678901234567890123456789012345678901234567890
function SafeChatClient(success, notice, error) {
/// Cache Client's Key from local Strorage
var k = null;
/// Get User Key
/** @internal key ist cached in k
@return key */
function key() {
if (k) return k
if (typeof localStorage.key == 'undefined') return null
return k = openpgp.key.readArmored(localStorage.key)
}
/// Get Own User Name
/** Get user name as user id of first public key */
function uid() {
if (k || key()) return k.pub.keys[0].getUserIds()[0]
return null
}
/// Create New User
function createuser(user, email, pwd) {
notice("generating keys")
openpgp.generateKey({
numBits: 4096,
userIds: [{name: user, email: email}],
passphrase: pwd
}).then(function(keyPair) {
success("keys generated")
localStorage.key = keyPair.privateKeyArmored
k = keyPair.key
}).catch(function(e) {
console.log(e)
error("generating key pairs failed")
})
}
function password(pwd) {
return (k || keys()) && k.keys[0].decrypt(pwd)
}
/// Encrypt Message
function encrypt(targetkeys, message, done, failed) {
if (!k) return false
openpgp.encrypt({
publicKeys: targets.keys.concat(k.keys),
privateKeys: k,
data: message,
armor: false})
.then(done)
.else(failed)
return true
}
/// Decrypt Message
function decrypt(message) {
if (!k) return false
return true
}
}
var password = null; ///< password, only stored temporary, until reload var password = null; ///< password, only stored temporary, until reload
var username = null; ///< username, only used during registration var username = null; ///< username, only used during registration
var filecontent = new Array(); ///< temporary storage for attachments var filecontent = new Array(); ///< temporary storage for attachments
var socket = io.connect(); var socket = io.connect();
var hostname = window.location.hostname!='localhost'?window.location.hostname:'safechat.ch';
/// Padding for numbers in dates /// Padding for numbers in dates
function pad(n) { function pad(n) {
return n<10 ? '0'+n : n return n<10 ? '0'+n : n
}
function uid(name) {
return name+' <'+name+'@'+hostname+'>';
} }
/// Convert number of bytes to readable text /// Convert number of bytes to readable text
function size(num) { function size(num) {
if (num>0.6*1024) { if (num>0.6*1024) {
if (num>0.6*1024*1024) { if (num>0.6*1024*1024) {
if (num>0.6*1024*1024*1024) { if (num>0.6*1024*1024*1024) {
if (num>0.6*1024*1024*1024*1024) { if (num>0.6*1024*1024*1024*1024) {
return Math.round(num/1024/1024/1024/1024)+"TB"; return Math.round(num/1024/1024/1024/1024)+"TB";
} else {
return Math.round(num/1024/1024/1024)+"GB";
}
} else {
return Math.round(num/1024/1024)+"MB";
}
} else { } else {
return Math.round(num/1024)+"kB"; return Math.round(num/1024/1024/1024)+"GB";
} }
} else {
return Math.round(num/1024/1024)+"MB";
}
} else { } else {
return num+"B"; return Math.round(num/1024)+"kB";
} }
} else {
return num+"B";
}
} }
var reboottimer = null; var reboottimer = null;
/// Show error messsage /// Show error messsage
/** Fades in an error message and logs to console. /** Fades in an error message and logs to console.
@param data (optional) The error can be a string or any structure. @param data (optional) The error can be a string or any structure.
Strings are shown to the user, structures are logged only. Strings are shown to the user, structures are logged only.
@param stay (optional) If not given as @c true, reloads page after 5s. */ @param stay (optional) If not given as @c true, reloads page after 5s. */
function error(data, stay) { function error(data, stay) {
$("#status").hide(); $("#status").hide();
$("#status").addClass("error") $("#status").addClass("error")
$("#status").removeClass("notice") $("#status").removeClass("notice")
$("#status").removeClass("success") $("#status").removeClass("success")
if (data) { if (data) {
if (typeof data == 'string') { if (typeof data == 'string') {
$("#status").html(data); $("#status").html(data);
console.log("error: "+data); console.log("error: "+data);
} else {
$("#status").html('unknown error: '+JSON.stringify(data));
console.log("error: "+JSON.stringify(data));
}
} else { } else {
$("#status").html('error'); $("#status").html('unknown error: '+JSON.stringify(data));
console.log("error"); console.log("error: "+JSON.stringify(data));
}
$("#status").show();
if (!stay) {
console.log("reboot in 5s");
console.log((new Error('stacktrace')).stack);
if (!reboottimer) reboottimer = setTimeout(function() {
reboottimer = null;
start();
}, 5000);
} }
} else {
$("#status").html('error');
console.log("error");
}
$("#status").show();
if (!stay) {
console.log("reboot in 5s");
console.log((new Error('stacktrace')).stack);
if (!reboottimer) reboottimer = setTimeout(function() {
reboottimer = null;
start();
}, 5000);
}
} }
/// Show notice messsage /// Show notice messsage
/** Fades in an notice message and logs to console. /** Fades in an notice message and logs to console.
@param text (optional) The data is a string. */ @param text (optional) The data is a string. */
function notice(text) { function notice(text) {
$("#status").hide() $("#status").hide()
$("#status").addClass("notice") $("#status").addClass("notice")
$("#status").removeClass("error") $("#status").removeClass("error")
$("#status").removeClass("success") $("#status").removeClass("success")
if (text) { if (text) {
$("#status").html(text); $("#status").html(text);
console.log("notice: "+text); console.log("notice: "+text);
} else { } else {
$("#status").html(''); $("#status").html('');
console.log("notice"); console.log("notice");
} }
$("#status").show(); $("#status").show();
} }
/// Show notice messsage /// Show notice messsage
/** Fades in an success message and logs to console. /** Fades in an success message and logs to console.
@param text (optional) The data is a string. */ @param text (optional) The data is a string. */
function success(text) { function success(text) {
$("#status").hide(); $("#status").hide();
$("#status").addClass("success") $("#status").addClass("success")
$("#status").removeClass("error") $("#status").removeClass("error")
$("#status").removeClass("notice") $("#status").removeClass("notice")
if (text) { if (text) {
$("#status").html(text); $("#status").html(text);
console.log("success: "+text); console.log("success: "+text);
} else { } else {
$("#status").html(''); $("#status").html('');
console.log("success"); console.log("success");
} }
$("#status").show(); $("#status").show();
} }
/// Show status message in the main screen area /// Show status message in the main screen area
/** @param id HTML id to be shown. /** @param id HTML id to be shown.
@param msg The success message text */ @param msg The success message text */
function status(id, msg) { function status(id, msg) {
console.log("state: "+id); console.log("state: "+id);
if (msg) success(msg); else $("#status").hide(); if (msg) success(msg); else $("#status").hide();
$("#main").children(":not(#"+id+")").hide(); $("#main").children(":not(#"+id+")").hide();
$("#main #"+id).show(); $("#main #"+id).show();
$("#main #"+id+" form input:first-child").focus(); $("#main #"+id+" form input:first-child").focus();
} }
function emit(signal, data) { function emit(signal, data) {
console.log("<-snd "+signal); console.log("<-snd "+signal);
socket.emit(signal, data); socket.emit(signal, data);
} }
function connected() { function connected() {
console.log("server connected"); console.log("server connected");
$("#connectionstatus #bad").hide(); $("#connectionstatus #bad").hide();
$("#connectionstatus #good").show(); $("#connectionstatus #good").show();
success("server connected"); success("server connected");
} }
function disconnected() { function disconnected() {
console.log("server disconnected"); console.log("server disconnected");
$("#connectionstatus #good").hide(); $("#connectionstatus #good").hide();
$("#connectionstatus #bad").show(); $("#connectionstatus #bad").show();
error("server disconnected", true); error("server disconnected", true);
} }
function connectionstatus() { function connectionstatus() {
if (socket.connected) connected(); else disconnected(); if (socket.connected) connected(); else disconnected();
} }
function htmlenc(html) { function htmlenc(html) {
return $('<div/>').text(html).html(); return $('<div/>').text(html).html();
} }
function htmldec(data) { function htmldec(data) {
return $('<div/>').html(data).text(); return $('<div/>').html(data).text();
} }
/// Alert user /// Alert user
/** Alert user, e.g. that a new message has arrived. */ /** Alert user, e.g. that a new message has arrived. */
function beep(user) { function beep(user) {
if (user) success("message from "+htmlenc(user)+" received"); if (user) success("message from "+htmlenc(user)+" received");
navigator.vibrate = navigator.vibrate =
navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate;
if (navigator.vibrate) { if (navigator.vibrate) {
// vibration API supported // vibration API supported
navigator.vibrate(1000); navigator.vibrate(1000);
} }
(new Audio("sounds/A-Tone-His_Self-1266414414.mp3")).play(); (new Audio("sounds/A-Tone-His_Self-1266414414.mp3")).play();
} }
/// Toggle Menu Display /// Toggle Menu Display
function togglemenu() { function togglemenu() {
$("#menu").toggle(); $("#menu").toggle();
} }
/// Download Profile Backup /// Download Profile Backup
function backup() { function backup() {
var download = document.createElement('a'); var download = document.createElement('a');
download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage)); download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage));
download.target = '_blank'; download.target = '_blank';
var now = new Date(); var now = new Date();
download.download = download.download =
pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+ pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+
"-"+userid()+"@"+window.location.hostname+".bak"; "-"+userid()+"@"+hostname+".bak";
var clickEvent = new MouseEvent("click", { var clickEvent = new MouseEvent("click", {
"view": window, "view": window,
"bubbles": true, "bubbles": true,
"cancelable": false "cancelable": false
}); });
download.dispatchEvent(clickEvent); download.dispatchEvent(clickEvent);
togglemenu(); togglemenu();
} }
/// Upload Profile Backup /// Upload Profile Backup
function restore(evt) { function restore(evt) {
if (!window.FileReader) if (!window.FileReader)
return error("your browser does not support file upload", true); return error("your browser does not support file upload", true);
for (var i=0, f; f=evt.target.files[i]; ++i) { for (var i=0, f; f=evt.target.files[i]; ++i) {
var file = f; var file = f;
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(evt) { reader.onload = function(evt) {
if (evt.target.error) return error("error reading file", true); if (evt.target.error) return error("error reading file", true);
if (evt.target.readyState==0) return notice("waiting for data …"); if (evt.target.readyState==0) return notice("waiting for data …");
if (evt.target.readyState==1) return notice("loading data …"); if (evt.target.readyState==1) return notice("loading data …");
var parsed=JSON.parse(evt.target.result); var parsed=JSON.parse(evt.target.result);
togglemenu(); togglemenu();
localStorage.pubkey = parsed.pubkey; localStorage.pubkey = parsed.pubkey;
localStorage.privkey = parsed.privkey; localStorage.privkey = parsed.privkey;
success("backup is restored"); success("backup is restored");
console.log("reboot after restore in 2s"); console.log("reboot after restore in 2s");
if (!reboottimer) reboottimer = setTimeout(function() { if (!reboottimer) reboottimer = setTimeout(function() {
reboottimer = null; reboottimer = null;
start(); start();
}, 2000); }, 2000);
}
reader.readAsText(file);
} }
reader.readAsText(file);
}
} }
/// Configure local groups /// Configure local groups
@ -249,573 +317,582 @@ function groups() {
/// Check if password is set and matches the repeated password /// Check if password is set and matches the repeated password
/** Checks if both passwords are identical and valid and gives /** Checks if both passwords are identical and valid and gives
feedback to the user. feedback to the user.
Called when user edits the password fields. Called when user edits the password fields.
Sets @ref username and checks @ref password - if both are well Sets @ref username and checks @ref password - if both are well
defined, enables the submit button. defined, enables the submit button.
@param pwd The password. @param pwd The password.
@param pwd2 The repeated password. */ @param pwd2 The repeated password. */
function checkpwd(pwd, pwd2) { function checkpwd(pwd, pwd2) {
$("#register").submit(function(event) { $("#register").submit(function(event) {
return false; return false;
}); });
if (pwd==pwd2) password=pwd; if (pwd==pwd2) password=pwd;
else password=null; else password=null;
if (!password||password.length<1) password=null; if (!password||password.length<1) password=null;
$("#createuser").prop("disabled", !(username && password)); $("#createuser").prop("disabled", !(username && password));
if (password) { if (password) {
if (username) success("user is ready to be created"); if (username) success("user is ready to be created");
else notice("password matches, please chose a valid user name"); else notice("password matches, please chose a valid user name");
} else { } else {
if (username) notice("passwords don't match"); if (username) notice("passwords don't match");
else if ($('#user').val()) notice("user name is already in use"); else if ($('#user').val()) notice("user name is already in use");
else notice("please chose a user name"); else notice("please chose a user name");
} }
} }
/// Checks if the receiver of a message exists on server. /// Checks if the receiver of a message exists on server.
/** Calls checknewuser.php on server and enables the message submit /** Calls checknewuser.php on server and enables the message submit
button if the receiver of the message exists on the server. */ button if the receiver of the message exists on the server. */
function checkpartner(user) { function checkpartner(user) {
$("#chat").submit(function(event) { $("#chat").submit(function(event) {
return false; return false;
}); });
emit("user", user); emit("user", uid(user));
} }
/// Create Local Public-/Private-Key Pair /// Create Local Public-/Private-Key Pair
/** Called if user has not yet his keys, just generates a new key pair. */ /** Called if user has not yet his keys, just generates a new key pair. */
function createkeypair(user, pwd) { function createkeypair(user, pwd) {
notice("generating keys"); notice("generating keys");
openpgp.generateKeyPair({ openpgp.generateKey({
numBits: 4096, numBits: 4096,
userId: user, userIds: [{name: user, email: user+'@'+hostname}],
passphrase: pwd passphrase: pwd
}).then(function(keyPair) { }).then(function(keyPair) {
success("keys generated"); success("keys generated");
localStorage.pubkey = keyPair.publicKeyArmored; localStorage.pubkey = keyPair.publicKeyArmored;
localStorage.privkey = keyPair.privateKeyArmored; localStorage.privkey = keyPair.privateKeyArmored;
login(); login();
}).catch(function(e) { }).catch(function(e) {
error("generating key pairs failed"); console.log(e)
}); error("generating key pairs failed")
});
} }
/// Get Own Public Key /// Get Own Public Key
/** @return public key object */ /** @return public key object */
function publicKey() { function publicKey() {
if (typeof localStorage.pubkey == 'undefined') { if (typeof localStorage.pubkey == 'undefined') {
if (typeof localStorage.pubKey == 'undefined') { if (typeof localStorage.pubKey == 'undefined') {
return null; return null;
} else { } else {
localStorage.pubkey = localStorage.pubKey; localStorage.pubkey = localStorage.pubKey;
localStorage.removeItem(pubKey); localStorage.removeItem(pubKey);
}
} }
return openpgp.key.readArmored(localStorage.pubkey); }
return openpgp.key.readArmored(localStorage.pubkey);
} }
/// Get Own Private Key /// Get Own Private Key
/** @return private key object */ /** @return private key object */
function privateKey() { function privateKey() {
if (typeof localStorage.privkey == 'undefined') { if (typeof localStorage.privkey == 'undefined') {
if (typeof localStorage.privKey == 'undefined') { if (typeof localStorage.privKey == 'undefined') {
return null; return null;
} else { } else {
localStorage.privkey = localStorage.privKey; localStorage.privkey = localStorage.privKey;
localStorage.removeItem(privKey); localStorage.removeItem(privKey);
}
} }
return openpgp.key.readArmored(localStorage.privkey); }
return openpgp.key.readArmored(localStorage.privkey);
} }
/// Get Own User Name /// Get Own User Name
/** Get user name as user id of first public key */ /** Get user name as user id of first public key */
function userid() { function userid() {
if (!publicKey() || if (!publicKey() ||
publicKey().keys.length < 1 || publicKey().keys.length < 1 ||
publicKey().keys[0].getUserIds().length < 1) return null publicKey().keys[0].getUserIds().length < 1) return null
return publicKey().keys[0].getUserIds()[0]; return publicKey().keys[0].getUserIds()[0];
} }
/// Clear Message Text And Attachments /// Clear Message Text And Attachments
/** Does not remove the receiver's name */ /** Does not remove the receiver's name */
function clearmessage() { function clearmessage() {
$("#message").prop(":disabled", true); $("#message").prop(":disabled", true);
filecontent = new Array(); filecontent = new Array();
$('#preview').empty(); $('#preview').empty();
$("#msg").val(""); $("#msg").val("");
$("#message").prop(":disabled", false); $("#message").prop(":disabled", false);
} }
function guessfilename(mimetype, user, date) { function guessfilename(mimetype, user, date) {
if (!user) user = userid(); if (!user) user = userid();
if (!date) date = new Date(); if (!date) date = new Date();
var ext = mimetype.replace(/.*\/(x-)?/i, ""); var ext = mimetype.replace(/.*\/(x-)?/i, "");
return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate()) return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate())
+"-"+ext+"-"+user+"@"+window.location.hostname+'.'+ext; +"-"+ext+"-"+user+"@"+hostname+'.'+ext;
} }
/// Display Image Attachments /// Display Image Attachments
function attachments(files, id, from, date) { function attachments(files, id, from, date) {
if (files) files.forEach(function(file) { if (files) files.forEach(function(file) {
console.log(file); console.log(file);
if (!file.name) file.name = guessfilename(file.type, from, date); if (!file.name) file.name = guessfilename(file.type, from, date);
var a = document.createElement('a'); var a = document.createElement('a');
a.href = file.content; a.href = file.content;
a.download = file.name; a.download = file.name;
a.target = '_blank'; a.target = '_blank';
if (file.type.match('^image/')) { if (file.type.match('^image/')) {
var img = document.createElement('img'); var img = document.createElement('img');
img.title = file.name; img.title = file.name;
img.src = file.content; img.src = file.content;
a.appendChild(img); a.appendChild(img);
} else if (file.type.match('^video/')) { } else if (file.type.match('^video/')) {
var video = document.createElement('video'); var video = document.createElement('video');
video.controls = true; video.controls = true;
video.title = file.name; video.title = file.name;
video.src = file.content; video.src = file.content;
a.appendChild(video); a.appendChild(video);
} else { } else {
var img = document.createElement('img'); var img = document.createElement('img');
img.title = file.name; img.title = file.name;
img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg"; img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg";
a.appendChild(img); a.appendChild(img);
} }
$(id).append(a); $(id).append(a);
}); });
} }
var recorder; var recorder;
function done() { function done() {
if (recorder) { if (recorder) {
recorder.stop(); recorder.stop();
recorder.recording(function(data) { recorder.recording(function(data) {
previewfile(data, "video/webm"); previewfile(data, "video/webm");
abort(); abort();
}); });
} }
} }
function abort() { function abort() {
if (recorder) { if (recorder) {
$("#videorecorder").hide(); $("#videorecorder").hide();
recorder.release(); recorder.release();
delete recorder; recorder = null; delete recorder; recorder = null;
} }
} }
/// Record Video from builtin camera /// Record Video from builtin camera
function recordvideo() { function recordvideo() {
try { try {
abort(); abort();
$("#videorecorder").show(); $("#videorecorder").show();
recorder = new MediaStreamRecorder({ recorder = new MediaStreamRecorder({
video: { video: {
width: {ideal: 180}, width: {ideal: 180},
height: {ideal: 160} height: {ideal: 160}
}, },
audio: true audio: true
}); });
recorder.on("ready", function() { recorder.on("ready", function() {
$("#videorecorder video").attr("src", recorder.preview()); $("#videorecorder video").attr("src", recorder.preview());
$("#videorecorder video").css("width", 180); $("#videorecorder video").css("width", 180);
$("#videorecorder video").css("height", 160); $("#videorecorder video").css("height", 160);
$("#videorecorder video").attr("width", 180); $("#videorecorder video").attr("width", 180);
$("#videorecorder video").attr("height", 160); $("#videorecorder video").attr("height", 160);
recorder.start(); recorder.start();
}); });
} catch (e) { } catch (e) {
console.log(e); console.log(e);
error("cannot access camera", true); error("cannot access camera", true);
} }
} }
function previewfile(content, type, name) { function previewfile(content, type, name) {
if (!name) name = guessfilename(type); if (!name) name = guessfilename(type);
if (type.match('^image/')) { if (type.match('^image/')) {
var img = document.createElement("img"); var img = document.createElement("img");
img.onload = function() { // resize image to maximum 400px img.onload = function() { // resize image to maximum 400px
var MAX = 400; var MAX = 400;
var width = img.width; var width = img.width;
var height = img.height; var height = img.height;
if (width > MAX) { if (width > MAX) {
height *= MAX / width; height *= MAX / width;
width = MAX; width = MAX;
} }
if (height > MAX) { if (height > MAX) {
width *= MAX / height; width *= MAX / height;
height = MAX; height = MAX;
} }
var canvas = document.createElement("canvas"); var canvas = document.createElement("canvas");
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
var ctx = canvas.getContext("2d"); var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height); ctx.drawImage(img, 0, 0, width, height);
img.onload = function() { img.onload = function() {
filecontent.push({name: name, type: type, content: img.src}); filecontent.push({name: name, type: type, content: img.src});
$("#preview").append(img);
success('image is ready to be sent');
}
img.src = canvas.toDataURL(file.type);
img.title = name+"\n"+size(img.src.length);
}
img.src=content;
} else if (type.match('^video/')) {
filecontent.push({name: name, type: type, content: content});
var video = document.createElement("video");
video.setAttribute("controls", "controls");
video.setAttribute("loop", "loop");
video.setAttribute("src", content);
video.setAttribute("title", name+"\n"+size(content.length));
$("#preview").append(video);
} else {
filecontent.push({name: name, type: type, content: content});
var img = document.createElement("img");
img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg";
img.title = name+"\n"+size(content.length);
$("#preview").append(img); $("#preview").append(img);
success('image is ready to be sent');
}
img.src = canvas.toDataURL(file.type);
img.title = name+"\n"+size(img.src.length);
} }
img.src=content;
} else if (type.match('^video/')) {
filecontent.push({name: name, type: type, content: content});
var video = document.createElement("video");
video.setAttribute("controls", "controls");
video.setAttribute("loop", "loop");
video.setAttribute("src", content);
video.setAttribute("title", name+"\n"+size(content.length));
$("#preview").append(video);
} else {
filecontent.push({name: name, type: type, content: content});
var img = document.createElement("img");
img.src = "images/Document_sans_PICOL-PIctorial-COmmunication-Language.svg";
img.title = name+"\n"+size(content.length);
$("#preview").append(img);
}
} }
/// Upload Attachment /// Upload Attachment
/** Prepares attachment to be sent in a message. If the attachment is /** Prepares attachment to be sent in a message. If the attachment is
an image, it resizes the image to 400px on the lager side. an image, it resizes the image to 400px on the lager side.
By now, only images are supported. By now, only images are supported.
Stores data in global variable @ref filecontent. */ Stores data in global variable @ref filecontent. */
function fileupload(evt) { function fileupload(evt) {
if (!window.FileReader) if (!window.FileReader)
return error("your browser does not support file upload", true); return error("your browser does not support file upload", true);
for (var i=0, f; f=evt.target.files[i]; ++i) { for (var i=0, f; f=evt.target.files[i]; ++i) {
var file = f; var file = f;
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(evt) { reader.onload = function(evt) {
if (evt.target.error) return error("error reading file", true); if (evt.target.error) return error("error reading file", true);
if (evt.target.readyState==0) return notice("waiting for data …"); if (evt.target.readyState==0) return notice("waiting for data …");
if (evt.target.readyState==1) return notice("loading data …"); if (evt.target.readyState==1) return notice("loading data …");
previewfile(evt.target.result, file.type, file.name); previewfile(evt.target.result, file.type, file.name);
}
reader.readAsDataURL(file);
} }
reader.readAsDataURL(file);
}
} }
/// Sets Receiver's Name /// Sets Receiver's Name
/** Called when clicked on a receiver's name. Sets focus to the /** Called when clicked on a receiver's name. Sets focus to the
message text field. message text field.
@param name The receiver's name. */ @param name The receiver's name. */
function setreceiver(name) { function setreceiver(name) {
$("#recv").val(name); $("#recv").val(name);
checkpartner(name); checkpartner(name);
$("#msg").focus(); $("#msg").focus();
} }
var userMap = null; var userMap = null;
function users(userlist) { function users(userlist) {
console.log("rcv-> users"); console.log("rcv-> users");
userMap = new Array(); userMap = new Array();
$("#allusers").empty(); $("#allusers").empty();
userlist.forEach(function(usr) { userlist.forEach(function(usr) {
userMap[usr.name] = usr.pubkey; userMap[usr.name] = usr.pubkey;
$("#allusers").append('<option value="'+htmlenc(usr.name)+'">') $("#allusers").append('<option value="'+htmlenc(usr.name)+'">')
$("#allusers").hide(); $("#allusers").hide();
console.log(" user: "+usr.name); console.log(" user: "+usr.name);
}); });
localStorage.userMap = JSON.stringify(userMap); localStorage.userMap = JSON.stringify(userMap);
} }
function fail(msg) { function fail(msg) {
console.log("rcv-> fail"); console.log("rcv-> fail");
error(msg); error(msg);
} }
function loggedin() { function loggedin() {
console.log("rcv-> login"); console.log("rcv-> login");
success("login successful"); success("login successful");
chat(); chat();
} }
function user(usr) { function user(usr) {
if (usr.exists) console.log("rcv-> user("+usr.name+")"); if (usr.exists) console.log("rcv-> user("+usr.name+")");
else console.log("rcv-> user("+usr.name+"): name is available"); else console.log("rcv-> user("+usr.name+"): name is available");
if ($("#newuser").is(":visible") && usr.name==$('#user').val()) { if ($("#newuser").is(":visible") && usr.name==uid($('#user').val())) {
// same username as in the create user form // same username as in the create user form
$("#createuser").prop("disabled", usr.exists); // todo: check password $("#createuser").prop("disabled", usr.exists); // todo: check password
if (!usr.exists) { if (!usr.exists) {
username = usr.name; username = usr.name;
success("user name "+usr.name+" is available"); success("user name "+usr.name+" is available");
} else { } else {
username = null; username = null;
error("user name "+usr.name+" is in use", true); error("user name "+usr.name+" is in use", true);
}
}
if ($("#chat").is(":visible") && usr.name==$("#recv").val()) { // same username as in receiver
$('#send').prop("disabled", !usr.exists);
$("label[for=send] img").css("opacity", usr.exists?"1.0":"0.4");
$("label[for=send] img").css("filter", usr.exists?"alpha(opacity=100)":"alpha(opacity=40)");
if (usr.exists) success("recipient exists");
else error("unknown recipient", true);
}
if (userMap == null) {
if (localStorage.userMap) {
userMap = JSON.parse(localStorage.userMap);
} else {
userMap = new Array();
}
} }
if (usr.exists && usr.pubkey && userMap[usr.name] != usr.pubkey) { }
userMap[usr.name] = usr.pubkey; if ($("#chat").is(":visible") && usr.name==uid($("#recv").val())) { // same username as in receiver
$("#allusers").append('option value="'+htmlenc(usr.name)+'"') $('#send').prop("disabled", !usr.exists);
localStorage.userMap = JSON.stringify(userMap); $("label[for=send] img").css("opacity", usr.exists?"1.0":"0.4");
$("label[for=send] img").css("filter", usr.exists?"alpha(opacity=100)":"alpha(opacity=40)");
if (usr.exists) success("recipient exists");
else error("unknown recipient", true);
}
if (userMap == null) {
if (localStorage.userMap) {
userMap = JSON.parse(localStorage.userMap);
} else {
userMap = new Array();
} }
}
if (usr.exists && usr.pubkey && userMap[usr.name] != usr.pubkey) {
userMap[usr.name] = usr.pubkey;
$("#allusers").append('option value="'+htmlenc(usr.name)+'"')
localStorage.userMap = JSON.stringify(userMap);
}
}
function queryuser(usr) {
console.log("query user: "+uid(usr));
socket.emit("user", uid(usr));
} }
/// Get a user's public key. /// Get a user's public key.
/** The first time, gets it from the server, later from the cache. */ /** The first time, gets it from the server, later from the cache. */
function getPublicKey(user) { function getPublicKey(user) {
var deferredObject = $.Deferred(); var deferredObject = $.Deferred();
if (userMap && userMap[user]) deferredObject.resolve(userMap[user]); if (userMap && userMap[user]) deferredObject.resolve(userMap[user]);
else deferredObject.reject("unknown user"); else deferredObject.reject("unknown user");
return deferredObject.promise(); return deferredObject.promise();
} }
/// Received a list of messages from server /// Received a list of messages from server
function messages(msgs) { function messages(msgs) {
console.log("rcv-> messages("+msgs.length+")"); console.log("rcv-> messages("+msgs.length+")");
if (!password || !privateKey()) if (!password || !privateKey())
return setTimeout(function() {emit("messages");}, 1000); // try again later return setTimeout(function() {emit("messages");}, 1000); // try again later
status("allmessages"); status("allmessages");
notice("load messages, please wait …"); notice("load messages, please wait …");
msgs.forEach(function(msg) {message(msg, true);}); msgs.forEach(function(msg) {message(msg, true);});
status("chat"); status("chat");
} }
/// Received a message from server /// Received a message from server
function message(m, internal) { function message(m, internal) {
if (!internal) console.log("rcv-> message("+m.user+")"); if (!internal) console.log("rcv-> message("+m.user+")");
if (!password || !privateKey()) return; if (!password || !privateKey()) return;
var key=openpgp.key.readArmored(m.pubkey); var key=openpgp.key.readArmored(m.pubkey);
if (key.err) return error("key of sender unreadable", true); if (key.err) return error("key of sender unreadable", true);
var message = openpgp.message.readArmored(m.msg); var message = openpgp.message.readArmored(m.msg);
var privkey = privateKey().keys[0]; var privkey = privateKey().keys[0];
if (privkey.decrypt(password)) // prepare own key if (privkey.decrypt(password)) // prepare own key
openpgp.decryptAndVerifyMessage(privkey, key.keys, message) openpgp.decrypt({
.then(function(msg) { // decryption succeded privateKeys: privkey,
// prepend message to list of messages publicKeys: key.keys,
var message = JSON.parse(msg.text); message: message
$("#msgs") // todo: check msg.signatures[0].valid }).then(function(msg) { // decryption succeded
.prepend('<div id="id'+(m.id)+'" class="msg '+ // prepend message to list of messages
(m.user==userid()?"me":"other")+ var message = JSON.parse(msg.text);
'"><div class="header">'+ $("#msgs") // todo: check msg.signatures[0].valid
'<span class="date">'+ .prepend('<div id="id'+(m.id)+'" class="msg '+
(new Date(m.time)).toLocaleString()+ (m.user==userid()?"me":"other")+
'</span><span class="sender">'+ '"><div class="header">'+
'<a href="javascript:void(0)" '+ '<span class="date">'+
'onclick="setreceiver(this.innerHTML)">'+ (new Date(m.time)).toLocaleString()+
htmlenc(m.user)+ '</span><span class="sender">'+
'</a>'+(message.receiver?' → <a href="javascript:void(0)" '+ '<a href="javascript:void(0)" '+
'onclick="setreceiver(this.innerHTML)">' 'onclick="setreceiver(this.innerHTML)">'+
+htmlenc(message.receiver)+'</a>':"")+ htmlenc(m.user)+
'</span></div>'+ '</a>'+(message.receiver?' → <a href="javascript:void(0)" '+
'<div class="text">'+ 'onclick="setreceiver(this.innerHTML)">'
htmlenc(message.text)+ +htmlenc(message.receiver)+'</a>':"")+
'</div></div><div class="clear"/>'); '</span></div>'+
// show attachments '<div class="text">'+
attachments(message.files, '#id'+m.id+' .text', m.user, new Date(m.time)); htmlenc(message.text)+
// calculate and show emoticons '</div></div><div class="clear"/>');
$('#id'+m.id).emoticonize(); // show attachments
if (!internal) beep(m.user); attachments(message.files, '#id'+m.id+' .text', m.user, new Date(m.time));
}) // calculate and show emoticons
.catch(function(e) { $('#id'+m.id).emoticonize();
// not for me if (!internal) beep(m.user);
success(); }).catch(function(e) {
}); // not for me
success();
});
} }
/// Send Message To Server /// Send Message To Server
/** User wants to send a message. Encrypt message with own private and /** User wants to send a message. Encrypt message with own private and
the receiver's public key, then send it to the server. */ the receiver's public key, then send it to the server. */
function sendmessage(recv, txt) { function sendmessage(recv, txt) {
notice("1/3 preparing message …"); notice("1/3 preparing message …");
$("#message").prop(":disabled", true); $("#message").prop(":disabled", true);
getPublicKey(recv) // get receiver's public key getPublicKey(recv) // get receiver's public key
.done(function(pk) { .done(function(pk) {
var key=openpgp.key.readArmored(pk); var key=openpgp.key.readArmored(pk);
if (!pk||key.err) { if (!pk||key.err) {
$("#message").prop(":disabled", false); $("#message").prop(":disabled", false);
error("receiver's key not found", true); error("receiver's key not found", true);
return; return;
} }
var privkey = privateKey().keys[0]; var privkey = privateKey().keys[0];
privkey.decrypt(password); // get own private key ready privkey.decrypt(password); // get own private key ready
var message = JSON.stringify({receiver: recv, text: txt, files: filecontent}); var message = JSON.stringify({receiver: recv, text: txt, files: filecontent});
notice("2/3 encrypting message …"); notice("2/3 encrypting message …");
openpgp.signAndEncryptMessage(key.keys.concat(publicKey().keys), openpgp.encrypt({publicKeys: key.keys.concat(publicKey().keys),
privkey, privateKeys: privkey,
message) data: message,
.then(function(msg) { // message is encrypted armor: false})
notice("3/3 sending message …"); .then(function(msg) { // message is encrypted
emit("message", {user: userid(), content: msg}); notice("3/3 sending message …");
clearmessage(); emit("message", {user: userid(), content: msg});
}) clearmessage();
.catch(function(e) { })
$("#message").prop(":disabled", false); .catch(function(e) {
error("encryption of message failed", true); $("#message").prop(":disabled", false);
}); error("encryption of message failed", true);
}) });
.fail(function(e) { })
$("#message").prop(":disabled", false); .fail(function(e) {
error("user not found", true); $("#message").prop(":disabled", false);
}); error("user not found", true);
});
} }
/// Check And Set Password /// Check And Set Password
/** Check if given password matches to decrypt the private key. If so, /** Check if given password matches to decrypt the private key. If so,
store it in global temporary variable @ref password and start the store it in global temporary variable @ref password and start the
chat. The password matches, when the private key can be decrypted. chat. The password matches, when the private key can be decrypted.
@param pwd The password to check. */ @param pwd The password to check. */
function setpw(pwd) { function setpw(pwd) {
if (privateKey().keys[0].decrypt(pwd)) { if (privateKey().keys[0].decrypt(pwd)) {
success("password matches"); success("password matches");
$("#removeKey").hide(); $("#removeKey").hide();
password = pwd; password = pwd;
chat(); chat();
} else { } else {
notice("password does not match"); notice("password does not match");
} }
} }
/// Create Password Entry Field /// Create Password Entry Field
/** Asks user for password. When user starts to enter it, it is /** Asks user for password. When user starts to enter it, it is
permanentely checked in setpw(). As soon as the password matches, permanentely checked in setpw(). As soon as the password matches,
setpw() continues automatically. No submit is required by the setpw() continues automatically. No submit is required by the
user. */ user. */
function getpwd() { function getpwd() {
if (password) return; if (password) return;
$("#removeKey").show(); $("#removeKey").show();
status("getpwd"); status("getpwd");
} }
function deleteUser() { function deleteUser() {
var uid = userid(); var uid = userid();
localStorage.removeItem(pubkey); localStorage.removeItem(pubkey);
localStorage.removeItem(privkey); localStorage.removeItem(privkey);
error("user "+uid+" permanentely lost"); error("user "+uid+" permanentely lost");
} }
function removeKey() { function removeKey() {
togglemenu(); togglemenu();
$("#removeKey").hide(); $("#removeKey").hide();
status('forgotpassword'); status('forgotpassword');
} }
/// Main Chat Window /// Main Chat Window
/** Gets chat widgets from server and displays them. Starts timer for /** Gets chat widgets from server and displays them. Starts timer for
get() which polls for new messages. */ get() which polls for new messages. */
var firsttime = true; var firsttime = true;
function chat() { function chat() {
if (!password) return getpwd(); if (!password) return getpwd();
status("chat"); status("chat");
if (firsttime && $('#msgs').is(':empty')) { if (firsttime && $('#msgs').is(':empty')) {
firsttime = false; firsttime = false;
notice("getting previous messages, please wait …"); notice("getting previous messages, please wait …");
emit("messages"); emit("messages");
} }
} }
/// Login User /// Login User
/** This is not really a login, it is just some kind of validation. /** This is not really a login, it is just some kind of validation.
The server does not care if a user is online or not, it is only The server does not care if a user is online or not, it is only
interesting to the client to make sure, everything is fine. User interesting to the client to make sure, everything is fine. User
is logged in the following way: User name and public key are sent is logged in the following way: User name and public key are sent
to the server. If the user name exists on the server and the to the server. If the user name exists on the server and the
public key is the same, the user is considered logged in, his public key is the same, the user is considered logged in, his
credentials seem to be valid. If user does not yet exits on credentials seem to be valid. If user does not yet exits on
server, it is created now. If user exists, but public key is server, it is created now. If user exists, but public key is
different, then this is a complete failure, something went different, then this is a complete failure, something went
terribly wrong. */ terribly wrong. */
function login() { function login() {
$("#username").html(userid()+"@"+window.location.hostname); $("#username").html(userid()+"@"+hostname);
emit("login", {name: userid(), emit("login", {name: userid(),
pubkey: localStorage.pubkey}); pubkey: localStorage.pubkey});
success("login sent to server"); success("login sent to server");
} }
/// Get And Display Form To Create New User /// Get And Display Form To Create New User
/** Shows user creation form. On submit, a private key is generated in /** Shows user creation form. On submit, a private key is generated in
createkeypair(), then login() creates the user. */ createkeypair(), then login() creates the user. */
function newuser() { function newuser() {
status("newuser"); status("newuser");
} }
/// Check if local storage is available /// Check if local storage is available
function checkLocalStorage() { function checkLocalStorage() {
var test = 'test'; var test = 'test';
try { try {
localStorage.setItem(test, test); localStorage.setItem(test, test);
localStorage.removeItem(test); localStorage.removeItem(test);
return true; return true;
} catch(e) { } catch(e) {
status("nolocalstorage"); status("nolocalstorage");
error("local storage not available"); error("local storage not available");
} }
return false; return false;
} }
/// Initial Function: Startup /// Initial Function: Startup
/** Decide whether to login or to create a new user */ /** Decide whether to login or to create a new user */
function start() { function start() {
$("#menu").hide(); $("#menu").hide();
//status("startup"); //status("startup");
if (checkLocalStorage()) if (checkLocalStorage())
try { try {
if (!userid()) { if (!userid()) {
newuser(); newuser();
} else { } else {
login(); login();
} }
} catch (m) { } catch (m) {
console.log(m.stack); console.log(m.stack);
error(m); error(m);
} }
} }
function init() { function init() {
/// On Load, Call @ref start /// On Load, Call @ref start
$(window.onbeforeunload = function() { $(window.onbeforeunload = function() {
return "Are you sure you want to navigate away?"; return "Are you sure you want to navigate away?";
}); });
/// Allow Running in Background on Android /// Allow Running in Background on Android
document.addEventListener('deviceready', function () { document.addEventListener('deviceready', function () {
if (cordova && cordova.plugins.backgroundMode) { if (cordova && cordova.plugins.backgroundMode) {
cordova.plugins.backgroundMode.enable(); cordova.plugins.backgroundMode.enable();
} }
}, false); }, false);
socket.io.on("connect", connected); socket.io.on("connect", connected);
socket.io.on("reconnect", connected); socket.io.on("reconnect", connected);
socket.io.on("disconnect", disconnected); socket.io.on("disconnect", disconnected);
socket.io.on("error", disconnected); socket.io.on("error", disconnected);
socket.on("login", loggedin); socket.on("login", loggedin);
socket.on("fail", fail); socket.on("fail", fail);
socket.on("user", user); socket.on("user", user);
socket.on("users", users); socket.on("users", users);
socket.on("message", message); socket.on("message", message);
socket.on("messages", messages); socket.on("messages", messages);
connectionstatus(); connectionstatus();
if (openpgp.initWorker("javascripts/openpgp.worker.js")) if (openpgp.initWorker("openpgp.worker.min.js"))
console.log("asynchronous openpgp enabled"); console.log("asynchronous openpgp enabled");
else else
console.log("asynchronous openpgp failed"); console.log("asynchronous openpgp failed");
emit('users'); emit('users');
start(); start();
} }
/// Start Main Loop /// Start Main Loop

@ -1,22 +1,14 @@
module.exports = function(app, package) {
/*
* GET home page. [
*/ '',
'webrtc'
var package = require(__dirname+"/../package.json"); ].forEach(function(p) {
app.get('/'+p, function(req, res) {
exports.index = function(req, res) { res.render(p?p:'index', {
res.render(path, { package: package
projecturl: package.documentation, })
packagename: package.name, })
packageversion: package.version
}) })
}
exports.webrtc = function(req, res) {
res.render('webrtc', {
projecturl: package.documentation,
packagename: package.name,
packageversion: package.version
});
} }

@ -5,9 +5,9 @@
var package = require(__dirname+'/package.json'); var package = require(__dirname+'/package.json');
var config = require(package.path.config); var config = require(package.path.config);
var express = require('express'); var express = require('express');
var routes = require(__dirname+'/routes');
var app = module.exports = express.createServer(); var app = module.exports = express.createServer();
var io = require('socket.io').listen(app); var routes = require(__dirname+'/routes')(app, package);
var io = require('socket.io').listen(app);
var sql = require(__dirname+'/database')(config.mysql); var sql = require(__dirname+'/database')(config.mysql);
var sockets = require(__dirname+'/sockets')(sql); var sockets = require(__dirname+'/sockets')(sql);
@ -34,11 +34,17 @@ app.configure(function() {
app.use(app.router); app.use(app.router);
app.use(express.static(__dirname + '/public')); app.use(express.static(__dirname + '/public'));
[ [
'jquery/dist', 'jquery/dist/jquery.min.js',
'openpgp/dist' 'jquery/dist/jquery.js',
'openpgp/dist/openpgp.min.js',
'openpgp/dist/openpgp.js',
'openpgp/dist/openpgp.worker.min.js',
'openpgp/dist/openpgp.worker.js'
].forEach(function(file) { ].forEach(function(file) {
app.use('/'+file.replace(/\/.*/g, ''), app.get('/'+file.replace(/.*\//g, ''),
express.static(__dirname + '/node_modules/'+file)); function(req, res) {
res.sendfile('/node_modules/'+file, {root: __dirname})
})
}) })
}); });
@ -54,11 +60,6 @@ app.configure('production', function(){
io.sockets.on('connection', sockets.connection); io.sockets.on('connection', sockets.connection);
// Routes
app.get('/', routes.index);
app.get('/webrtc', routes.webrtc);
app.listen(config.port, function(){ app.listen(config.port, function(){
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
}); });

@ -1,103 +1,104 @@
module.exports = function(sql) { module.exports = function(sql) {
var module={}; var module={};
module.connection = function(socket) { module.connection = function(socket) {
console.log("new client"); console.log("new client");
function emit(signal, data, info) { function emit(signal, data, info) {
if (typeof data == 'string') { if (typeof data == 'string') {
console.log("<- signal: "+signal+"("+data+")"); console.log("<- signal: "+signal+"("+data+")");
} else { } else {
console.log("<- signal: "+signal); console.log("<- signal: "+signal);
} }
if (info) console.log(info); if (info) console.log(info);
socket.emit(signal, data); socket.emit(signal, data);
} }
function broadcast(signal, data) {
console.log("<= signal: "+signal);
socket.broadcast.emit(signal, data);
}
socket.on("message", function(msg) {
console.log("-> signal: message");
if (!msg || !msg.user || !msg.content) return emit("fail", "wrong message format");
sql.query("select pubkey from user where name = ?", [msg.user],
function(err, res, flds) {
if (err || !res || !res.length) return emit("fail", "unknown sender");
sql.query("insert into message set ?", {user: msg.user, msg: msg.content},
function(err, result) {
if (err) {
if (err.code=='ER_NET_PACKET_TOO_LARGE')
return emit('fail', "message too large", err);
else
return emit("fail", "cannot store message", err);
}
sql.query("select * from message, user"+
" where message.id = ? and"+
" message.user = user.name",
[result.insertId],
function(err, res, flds) {
broadcast('message', res[0]);
emit('message', res[0]);
});
});
});
});
socket.on("messages", function(msg) { function broadcast(signal, data) {
console.log("-> signal: messages"); console.log("<= signal: "+signal);
sql.query("select * from message, user where message.user = user.name order by message.id", [], socket.broadcast.emit(signal, data);
function(err, res, flds) { }
emit('messages', res);
});
});
socket.on("login", function(user) { socket.on("message", function(msg) {
if (!user.name || !user.pubkey) return emit("fail", "wrong login format"); console.log("-> signal: message");
console.log("-> signal: login("+user.name+")"); if (!msg || !msg.user || !msg.content) return emit("fail", "wrong message format");
if (user.name=="safechat") return emit("fail", "user name safechat is reserved"); sql.query("select pubkey from user where name = ?", [msg.user],
sql.query("select name, pubkey from user where name = ?", [user.name], function(err, res, flds) {
function(err, res, flds) { if (err || !res || !res.length) return emit("fail", "unknown sender");
if (err) return emit('fail', "login failed (db access)", err); sql.query("insert into message set ?", {user: msg.user, msg: msg.content},
if (!res || res.length==0) { function(err, result) {
sql.query("insert into user (name, pubkey) values (?,?)", if (err) {
[user.name, user.pubkey], function(err, res, flds) { if (err.code=='ER_NET_PACKET_TOO_LARGE')
if (err) return emit('fail', "create user failed", err); return emit('fail', "message too large", err);
broadcast("user", { else
name: user.name, exists: false, pubkey: user.pubkey return emit("fail", "cannot store message", err);
}); }
emit('login'); sql.query("select * from message, user"+
" where message.id = ? and"+
" message.user = user.name",
[result.insertId],
function(err, res, flds) {
broadcast('message', res[0]);
emit('message', res[0]);
}); });
} else { });
if (res[0].pubkey==user.pubkey) return emit('login'); });
emit('fail', "login failed - wrong credentials"); });
}
});
});
socket.on("user", function(name) { socket.on("messages", function(msg) {
console.log("-> signal: user("+name+")"); console.log("-> signal: messages");
var result = {name: name, exists: false, pubkey: null}; sql.query("select * from message, user where message.user = user.name order by message.id", [],
sql.query("select pubkey from user where name = ?", [name], function(err, res, flds) { function(err, res, flds) {
if (!err && res && res.length) { emit('messages', res);
result.exists = true; });
result.pubkey = res[0].pubkey; });
}
emit('user', result); socket.on("login", function(user) {
}); console.log('-> signal: login('+user.name+')');
}); if (!user.name || !user.pubkey) return emit("fail", "wrong login format");
console.log("-> signal: login("+user.name+")");
socket.on("users", function(name) { if (user.name=="safechat") return emit("fail", "user name safechat is reserved");
console.log("-> signal: users"); sql.query("select name, pubkey from user where name = ?", [user.name],
sql.query("select name, pubkey from user", [name], function(err, res, flds) { function(err, res, flds) {
if (!err && res && res.length) emit('users', res); if (err) return emit('fail', "login failed (db access)", err);
}); if (!res || res.length==0) {
}); sql.query("insert into user (name, pubkey) values (?,?)",
[user.name, user.pubkey], function(err, res, flds) {
}; if (err) return emit('fail', "create user failed", err);
broadcast("user", {
name: user.name, exists: false, pubkey: user.pubkey
});
emit('login');
});
} else {
if (res[0].pubkey==user.pubkey) return emit('login');
emit('fail', "login failed - wrong credentials");
}
});
});
socket.on("user", function(name) {
console.log("-> signal: user("+name+")");
var result = {name: name, exists: false, pubkey: null};
sql.query("select pubkey from user where name = ?", [name], function(err, res, flds) {
if (!err && res && res.length) {
result.exists = true;
result.pubkey = res[0].pubkey;
}
emit('user', result);
});
});
socket.on("users", function(name) {
console.log("-> signal: users");
sql.query("select name, pubkey from user", [name], function(err, res, flds) {
if (!err && res && res.length) emit('users', res);
});
});
return module; };
return module;
}; };

@ -4,9 +4,8 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width initial-scale=1" /> <meta name="viewport" content="width=device-width initial-scale=1" />
<link href="stylesheets/safechat.css" rel="stylesheet" type="text/css" /> <link href="stylesheets/safechat.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="jquery/jquery.min.js"></script> <script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="openpgp/openpgp.min.js"></script> <script type="text/javascript" src="openpgp.min.js"></script>
<script type="text/javascript" src="openpgp/openpgp.worker.min.js"></script>
<script type="text/javascript" src="socket.io/socket.io.js"></script> <script type="text/javascript" src="socket.io/socket.io.js"></script>
<script type="text/javascript" src="javascripts/mediarecorder.js"></script> <script type="text/javascript" src="javascripts/mediarecorder.js"></script>
<script type="text/javascript" src="javascripts/safechat.js"></script> <script type="text/javascript" src="javascripts/safechat.js"></script>
@ -18,7 +17,7 @@
<body> <body>
<div id="header" class="header"> <div id="header" class="header">
<h1>Safe Chat <%= packageversion %></h1> <h1>Safe Chat <%= package.version %></h1>
<div id="togglemenu"> <div id="togglemenu">
<span id="username">[unknown]</span> <span id="username">[unknown]</span>
<span id="connectionstatus"> <span id="connectionstatus">
@ -35,7 +34,7 @@
<li id="groups" onclick="groups()">Edit Groups</li> <li id="groups" onclick="groups()">Edit Groups</li>
<li id="removeKey" style="display: none" onclick="removeKey()">Password Forgotten</li> <li id="removeKey" style="display: none" onclick="removeKey()">Password Forgotten</li>
<li id="android-download" href="safechat.apk"><a href="safechat.apk">Download Android-App</a></li> <li id="android-download" href="safechat.apk"><a href="safechat.apk">Download Android-App</a></li>
<li href="<%= projecturl %>" target="_blank"><a href="<%= projecturl %>" target="_blank">About Safe Chat</a></li> <li href="<%= package.documentation %>" target="_blank"><a href="<%= package.documentation %>" target="_blank">About Safe Chat</a></li>
</ul> </ul>
<script type="text/javascript"> <script type="text/javascript">
$(function() { // on load: without cordova, remove andoid-download $(function() { // on load: without cordova, remove andoid-download
@ -56,16 +55,14 @@
<input placeholder="username" autocomplete="off" type="text" id="user"/> <input placeholder="username" autocomplete="off" type="text" id="user"/>
<input placeholder="password" autocomplete="off" type="password" id="pwd" oninput="checkpwd(this.value, document.getElementById('pwd2').value)"/> <input placeholder="password" autocomplete="off" type="password" id="pwd" oninput="checkpwd(this.value, document.getElementById('pwd2').value)"/>
<input placeholder="repeat password" autocomplete="off" type="password" id="pwd2" oninput="checkpwd(document.getElementById('pwd').value, this.value)"/> <input placeholder="repeat password" autocomplete="off" type="password" id="pwd2" oninput="checkpwd(document.getElementById('pwd').value, this.value)"/>
<input id="createuser" type="submit" disabled/> <button id="createuser" disabled>register</button>
</form> </form>
<script> <script>
$("#user").on("input", function() { $("#user").on("input", function() {
console.log("query user: "+$('#user').val()); queryuser($('#user').val());
socket.emit("user", $('#user').val());
}); });
$("#register").submit(function(event) { $("#createuser").on("click", function(event) {
createkeypair(event.target.elements['user'].value, createkeypair($('#user').val(), $('#pwd').val());
event.target.elements['pwd'].value);
return false; return false;
}); });
</script> </script>
@ -193,7 +190,7 @@
server. Your password and your secret key are fully under server. Your password and your secret key are fully under
your control. That's why you must enable javascript and your control. That's why you must enable javascript and
local storage for this application.</p> local storage for this application.</p>
<p><a href="<%= projecturl %>" target="_blank">more information</a></p> <p><a href="<%= package.documentation %>" target="_blank">more information</a></p>
</noscript> </noscript>
<!-- Error: Missing LocalStorage --> <!-- Error: Missing LocalStorage -->

Loading…
Cancel
Save