about to be able to create user

master
Marc Wäckerlin 8 years ago
parent d2a6a0f518
commit d6beeefe74
  1. 433
      nodejs/public/javascripts/safechat.js
  2. 2
      nodejs/public/stylesheets/safechat.css
  3. 17
      nodejs/sockets/index.js
  4. 23
      nodejs/views/index.ejs

@ -41,9 +41,15 @@ function SafeChat() {
/// Create UID from a name by appending an E-Mail
function uid(name) {
return name+' <'+name+'@'+hostname+'>'
return name+' <'+mail(name)+'>'
}
function mail(name) {
var hostname = window.location.hostname!='localhost'?window.location.hostname:'safechat.ch'
return name+'@'+hostname
}
//==============================================================================
/// @class Crypto cryptographic functions
/** @param view is of class SafeChat.View */
function Crypto(controller) {
@ -51,42 +57,23 @@ function SafeChat() {
/// cache client's key from local strorage
var k = null
/// detect hosstname, default to safechat.ch
var hostname = window.location.hostname!='localhost'?window.location.hostname:'safechat.ch'
/// detect hostname, default to safechat.ch
/// 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)
this.key = function() {
if (k) return k // cached key
if (typeof localStorage.privkey === 'undefined') return null
return k = openpgp.key.readArmored(localStorage.privkey)
}
/// get own user name
/** get user name as user id of first public key */
function user() {
this.user = function() {
if (k || key()) return k.pub.keys[0].getUserIds()[0]
return null
}
/// create New User
function createuser(user, email, pwd) {
controller.notice("generating keys")
openpgp.generateKey({
numBits: 4096,
userIds: [{name: user, email: email}],
passphrase: pwd
}).then(function(keyPair) {
controller.success("keys generated")
localStorage.key = keyPair.privateKeyArmored
k = keyPair.key
}).catch(function(e) {
console.log(e)
controller.fatal("generating key pairs failed")
})
}
/// open private key with password
/** @return @c true if password matches */
function password(pwd) {
@ -116,8 +103,15 @@ function SafeChat() {
return true
}
//------------------------------------------------------------------------------
if (openpgp.initWorker("openpgp.worker.min.js"))
console.log("asynchronous openpgp enabled")
else
console.log("asynchronous openpgp failed")
}
//==============================================================================
/// database that stores in indexed db
function DataBase() {
@ -126,6 +120,7 @@ function SafeChat() {
}
//==============================================================================
/// manage local copy of users
function Users() {
@ -142,6 +137,7 @@ function SafeChat() {
}
//------------------------------------------------------------------------------
/// manage local copy of messages
function Messages() {
@ -152,6 +148,7 @@ function SafeChat() {
}
//==============================================================================
/// @class Communication client socket communication
/** @param view is of class SafeChat.View */
function Communication(controller) {
@ -163,9 +160,13 @@ function SafeChat() {
socket.broadcast.emit(signal, data)
}
function emit(signal, data) {
function emit(signal, data, next) {
console.log("<-snd "+signal)
socket.emit(signal, data)
socket.emit(signal, data, next)
}
this.lookup = function(usr, next) {
emit('user', usr, next)
}
socket
@ -183,6 +184,7 @@ function SafeChat() {
}
//==============================================================================
/// @class View provides the glue to the GUI in the index.ejs file
/** View provides the following callbacks:
- status updates:
@ -299,7 +301,7 @@ function SafeChat() {
@param msg (optional) the success message text */
function show(id, msg) {
console.log("state: "+id)
if (msg) success(msg) else $("#status").hide()
if (msg) success(msg); else $("#status").hide();
$("#main").children(":not(#"+id+")").hide()
$("#main #"+id).show()
$("#main #"+id+" form input:first-child").focus()
@ -327,21 +329,15 @@ function SafeChat() {
}
function checkFeature(id, query) {
if (query) $('#'+id+':before')
.css('color', 'green')
.css('content', '&#x2714;')
else $('#'+id+':before')
.css('color', 'red')
.css('content', '&#x2718;')
if (query) $('#'+id)
.css('color', 'green')
.css('text-decoration', 'line-through')
.prepend('<span>&#x2714;</span>')
else $('#'+id)
.css('color', 'red')
.css('text-decoration', 'none')
.prepend('<span>&#x2718;</span>')
}
function checkFeatures() {
this.checkFeatures = function() {
$('ul.features').css('list-style-type', 'none')
checkFeature("localstorage", Storage)
checkFeature("indexeddb", window.indexedDB)
@ -350,6 +346,54 @@ function SafeChat() {
checkFeature("filereader", window.FileReader)
}
/// @name create new user
/// @{
this.newuser = function() {
show('newuser')
}
var user = null
var pwd = false
function invalid(usr) {
return !user || !user.exists && user.name.length<3
}
this.available = function(usr) {
user = usr
console.log("props:", invalid(user) || !pwd)
$("#createuser").prop(":disabled", invalid(user) || !pwd)
if (user.length==0)
notice("please chose a user name")
else if (user.length<3)
notice("please chose a longer user name")
else if (user.exists)
notice("user name is already in use")
else if (!pwd)
notice("please chose a password")
else
success("user is ready to be created")
}
this.passwords = function(pwd1, pwd2) {
pwd = pwd1==pwd2 && pwd1.length>5
console.log("props:", invalid(user) || !pwd)
$("#createuser").prop(":disabled", invalid(user) || !pwd)
if (pwd1.length==0)
notice('please chose a password')
else if (pwd1.length<6)
notice('please chose a longer password')
else if (pwd1 != pwd2)
notice("passwords don't match")
else if (invalid(user))
notice("please chose a user name")
else
success("user is ready to be created")
}
/// @}
function DataTransfer() {
var reboottimer = null
@ -405,10 +449,11 @@ function SafeChat() {
}
//==============================================================================
/// @class Controller defines the programm flow
function Controller(view) {
var db = new Database()
var db = new DataBase()
var crypto = new Crypto(this)
var communication = new Communication(this)
var users = new Users()
@ -461,6 +506,33 @@ function SafeChat() {
// @}
/// @name signals from view
/// @{
/// @name new user registration
/// @{
this.lookup = function(usr) {
if (usr.length > 2) communication.lookup(uid(usr), function(res) {
view.available(res)
})
}
this.checkpasswords = view.passwords
this.createuser = function(name, pwd) {
crypto.createuser(name, name+'@'+hostname, pwd).then(function() {
if (!crypto.password(pwd))
fatal("private key decryption failed")
else
chat()
})
}
/// @}
/// @}
function initBrowser() {
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction
@ -469,52 +541,57 @@ function SafeChat() {
return window.indexedDB && window.crypto.getRandomValues && Storage
}
function register() {
var newuser = view.newuser
function chat() {
}
function password() {
}
function login() {
if (!crypto.key()) register()
else password()
if (!crypto.key()) newuser(); else password();
}
function run() {
this.run = function() {
login()
}
function start() {
view.reboot = run
this.start = function() {
view.reboot = this.run
var compatible = initBrowser()
view.checkFeatures()
if (!compatible)
view.fatal("your browser is not supported")
else
run()
this.run()
}
}
//==============================================================================
//------------------------------------------------------------------------------
return new Controller(new View())
}
var filecontent = new Array() ///< temporary storage for attachments
var reboottimer = null
//==============================================================================
//------------------------------------------------------------------------------
function connectionstatus() {
if (socket.connected) connected() else disconnected()
}
var filecontent = new Array() ///< temporary storage for attachments
var reboottimer = null
/// Configure local groups
/** … */
function groups() {
}
function connectionstatus() {
if (socket.connected) connected(); else disconnected();
}
/// Configure local groups
/** … */
function groups() {
}
/// Check if password is set and matches the repeated password
/** Checks if both passwords are identical and valid and gives
/// Check if password is set and matches the repeated password
/** Checks if both passwords are identical and valid and gives
feedback to the user.
Called when user edits the password fields.
@ -524,7 +601,7 @@ function SafeChat() {
@param pwd The password.
@param pwd2 The repeated password. */
function checkpwd(pwd, pwd2) {
function checkpwd(pwd, pwd2) {
$("#register").submit(function(event) {
return false
})
@ -541,21 +618,21 @@ function SafeChat() {
else notice("please chose a user name")
}
}
}
/// Checks if the receiver of a message exists on server.
/** Calls checknewuser.php on server and enables the message submit
/// Checks if the receiver of a message exists on server.
/** Calls checknewuser.php on server and enables the message submit
button if the receiver of the message exists on the server. */
function checkpartner(user) {
function checkpartner(user) {
$("#chat").submit(function(event) {
return false
})
emit("user", uid(user))
}
}
/// Create Local Public-/Private-Key Pair
/** Called if user has not yet his keys, just generates a new key pair. */
function createkeypair(user, pwd) {
/// Create Local Public-/Private-Key Pair
/** Called if user has not yet his keys, just generates a new key pair. */
function createkeypair(user, pwd) {
notice("generating keys")
openpgp.generateKey({
numBits: 4096,
@ -570,11 +647,11 @@ function SafeChat() {
console.log(e)
error("generating key pairs failed")
})
}
}
/// Get Own Public Key
/** @return public key object */
function publicKey() {
/// Get Own Public Key
/** @return public key object */
function publicKey() {
if (typeof localStorage.pubkey == 'undefined') {
if (typeof localStorage.pubKey == 'undefined') {
return null
@ -584,11 +661,11 @@ function SafeChat() {
}
}
return openpgp.key.readArmored(localStorage.pubkey)
}
}
/// Get Own Private Key
/** @return private key object */
function privateKey() {
/// Get Own Private Key
/** @return private key object */
function privateKey() {
if (typeof localStorage.privkey == 'undefined') {
if (typeof localStorage.privKey == 'undefined') {
return null
@ -598,37 +675,37 @@ function SafeChat() {
}
}
return openpgp.key.readArmored(localStorage.privkey)
}
}
/// Get Own User Name
/** Get user name as user id of first public key */
function userid() {
/// Get Own User Name
/** Get user name as user id of first public key */
function userid() {
if (!publicKey() ||
publicKey().keys.length < 1 ||
publicKey().keys[0].getUserIds().length < 1) return null
return publicKey().keys[0].getUserIds()[0]
}
}
/// Clear Message Text And Attachments
/** Does not remove the receiver's name */
function clearmessage() {
/// Clear Message Text And Attachments
/** Does not remove the receiver's name */
function clearmessage() {
$("#message").prop(":disabled", true)
filecontent = new Array()
$('#preview').empty()
$("#msg").val("")
$("#message").prop(":disabled", false)
}
}
function guessfilename(mimetype, user, date) {
function guessfilename(mimetype, user, date) {
if (!user) user = userid()
if (!date) date = new Date()
var ext = mimetype.replace(/.*\/(x-)?/i, "")
return pad(date.getFullYear())+pad(date.getMonth()+1)+pad(date.getDate())
+"-"+ext+"-"+user+"@"+hostname+'.'+ext
}
}
/// Display Image Attachments
function attachments(files, id, from, date) {
/// Display Image Attachments
function attachments(files, id, from, date) {
if (files) files.forEach(function(file) {
console.log(file)
if (!file.name) file.name = guessfilename(file.type, from, date)
@ -655,11 +732,11 @@ function SafeChat() {
}
$(id).append(a)
})
}
}
var recorder
var recorder
function done() {
function done() {
if (recorder) {
recorder.stop()
recorder.recording(function(data) {
@ -667,18 +744,19 @@ function SafeChat() {
abort()
})
}
}
}
function abort() {
function abort() {
if (recorder) {
$("#videorecorder").hide()
recorder.release()
delete recorder recorder = null
}
delete recorder
recorder = null
}
}
/// Record Video from builtin camera
function recordvideo() {
/// Record Video from builtin camera
function recordvideo() {
try {
abort()
$("#videorecorder").show()
@ -701,9 +779,9 @@ function SafeChat() {
console.log(e)
error("cannot access camera", true)
}
}
}
function previewfile(content, type, name) {
function previewfile(content, type, name) {
if (!name) name = guessfilename(type)
if (type.match('^image/')) {
var img = document.createElement("img")
@ -748,16 +826,16 @@ function SafeChat() {
img.title = name+"\n"+size(content.length)
$("#preview").append(img)
}
}
}
/// Upload Attachment
/** Prepares attachment to be sent in a message. If the attachment is
/// Upload Attachment
/** Prepares attachment to be sent in a message. If the attachment is
an image, it resizes the image to 400px on the lager side.
By now, only images are supported.
Stores data in global variable @ref filecontent. */
function fileupload(evt) {
function fileupload(evt) {
if (!window.FileReader)
return error("your browser does not support file upload", true)
for (var i=0, f; f=evt.target.files[i]; ++i) {
@ -771,21 +849,21 @@ function SafeChat() {
}
reader.readAsDataURL(file)
}
}
}
/// Sets Receiver's Name
/** Called when clicked on a receiver's name. Sets focus to the
/// Sets Receiver's Name
/** Called when clicked on a receiver's name. Sets focus to the
message text field.
@param name The receiver's name. */
function setreceiver(name) {
function setreceiver(name) {
$("#recv").val(name)
checkpartner(name)
$("#msg").focus()
}
}
var userMap = null
function users(userlist) {
var userMap = null
function users(userlist) {
console.log("rcv-> users")
userMap = new Array()
$("#allusers").empty()
@ -796,9 +874,9 @@ function SafeChat() {
console.log(" user: "+usr.name)
})
localStorage.userMap = JSON.stringify(userMap)
}
}
function user(usr) {
function user(usr) {
if (usr.exists) console.log("rcv-> user("+usr.name+")")
else console.log("rcv-> user("+usr.name+"): name is available")
if ($("#newuser").is(":visible") && usr.name==uid($('#user').val())) {
@ -831,35 +909,35 @@ function SafeChat() {
$("#allusers").append('option value="'+htmlenc(usr.name)+'"')
localStorage.userMap = JSON.stringify(userMap)
}
}
}
function queryuser(usr) {
function queryuser(usr) {
console.log("query user: "+uid(usr))
socket.emit("user", uid(usr))
}
}
/// Get a user's public key.
/** The first time, gets it from the server, later from the cache. */
function getPublicKey(user) {
/// Get a user's public key.
/** The first time, gets it from the server, later from the cache. */
function getPublicKey(user) {
var deferredObject = $.Deferred()
if (userMap && userMap[user]) deferredObject.resolve(userMap[user])
else deferredObject.reject("unknown user")
return deferredObject.promise()
}
}
/// Received a list of messages from server
function messages(msgs) {
/// Received a list of messages from server
function messages(msgs) {
console.log("rcv-> messages("+msgs.length+")")
if (!password || !privateKey())
return setTimeout(function() {emit("messages")}, 1000) // try again later
status("allmessages")
show("allmessages")
notice("load messages, please wait …")
msgs.forEach(function(msg) {message(msg, true)})
status("chat")
}
show("chat")
}
/// Received a message from server
function message(m, internal) {
/// Received a message from server
function message(m, internal) {
if (!internal) console.log("rcv-> message("+m.user+")")
if (!password || !privateKey()) return
var key=openpgp.key.readArmored(m.pubkey)
@ -905,12 +983,13 @@ function SafeChat() {
// not for me
success()
})
}
})
}
/// Send Message To Server
/** User wants to send a message. Encrypt message with own private and
/// Send Message To Server
/** User wants to send a message. Encrypt message with own private and
the receiver's public key, then send it to the server. */
function sendmessage(recv, txt) {
function sendmessage(recv, txt) {
notice("1/3 preparing message …")
$("#message").prop(":disabled", true)
getPublicKey(recv) // get receiver's public key
@ -943,15 +1022,15 @@ function SafeChat() {
$("#message").prop(":disabled", false)
error("user not found", true)
})
}
}
/// Check And Set Password
/** Check if given password matches to decrypt the private key. If so,
/// Check And Set Password
/** Check if given password matches to decrypt the private key. If so,
store it in global temporary variable @ref password and start the
chat. The password matches, when the private key can be decrypted.
@param pwd The password to check. */
function setpw(pwd) {
function setpw(pwd) {
if (privateKey().keys[0].decrypt(pwd)) {
success("password matches")
$("#removeKey").hide()
@ -960,48 +1039,48 @@ function SafeChat() {
} else {
notice("password does not match")
}
}
}
/// Create Password Entry Field
/** Asks user for password. When user starts to enter it, it is
/// Create Password Entry Field
/** Asks user for password. When user starts to enter it, it is
permanentely checked in setpw(). As soon as the password matches,
setpw() continues automatically. No submit is required by the
user. */
function getpwd() {
function getpwd() {
if (password) return
$("#removeKey").show()
status("getpwd")
}
show("getpwd")
}
function deleteUser() {
function deleteUser() {
var uid = userid()
localStorage.removeItem(pubkey)
localStorage.removeItem(privkey)
error("user "+uid+" permanentely lost")
}
}
function removeKey() {
function removeKey() {
togglemenu()
$("#removeKey").hide()
status('forgotpassword')
}
show('forgotpassword')
}
/// Main Chat Window
/** Gets chat widgets from server and displays them. Starts timer for
/// Main Chat Window
/** Gets chat widgets from server and displays them. Starts timer for
get() which polls for new messages. */
var firsttime = true
function chat() {
var firsttime = true
function chat() {
if (!password) return getpwd()
status("chat")
show("chat")
if (firsttime && $('#msgs').is(':empty')) {
firsttime = false
notice("getting previous messages, please wait …")
emit("messages")
}
}
}
/// Login User
/** This is not really a login, it is just some kind of validation.
/// Login User
/** 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
interesting to the client to make sure, everything is fine. User
is logged in the following way: User name and public key are sent
@ -1011,39 +1090,39 @@ function SafeChat() {
server, it is created now. If user exists, but public key is
different, then this is a complete failure, something went
terribly wrong. */
function login() {
function login() {
$("#username").html(userid()+"@"+hostname)
emit("login", {name: userid(),
pubkey: localStorage.pubkey})
success("login sent to server")
}
}
/// Get And Display Form To Create New User
/** Shows user creation form. On submit, a private key is generated in
/// Get And Display Form To Create New User
/** Shows user creation form. On submit, a private key is generated in
createkeypair(), then login() creates the user. */
function newuser() {
status("newuser")
}
function newuser() {
show("newuser")
}
/// Check if local storage is available
function checkLocalStorage() {
/// Check if local storage is available
function checkLocalStorage() {
var test = 'test'
try {
localStorage.setItem(test, test)
localStorage.removeItem(test)
return true
} catch(e) {
status("nolocalstorage")
show("nolocalstorage")
error("local storage not available")
}
return false
}
}
/// Initial Function: Startup
/** Decide whether to login or to create a new user */
function start() {
/// Initial Function: Startup
/** Decide whether to login or to create a new user */
function start() {
$("#menu").hide()
//status("startup")
//show("startup")
if (checkLocalStorage())
try {
if (!userid()) {
@ -1055,9 +1134,15 @@ function SafeChat() {
console.log(m.stack)
error(m)
}
}
}
var safechat = new SafeChat()
function init() {
function init() {
safechat.start()
}
function old() {
/// On Load, Call @ref start
$(window.onbeforeunload = function() {
return "Are you sure you want to navigate away?"
@ -1075,7 +1160,7 @@ function SafeChat() {
console.log("asynchronous openpgp failed")
emit('users')
start()
}
}
/// Start Main Loop
$(init)
/// Start Main Loop
$(init)

@ -58,7 +58,7 @@ form input#msg {
height: 1.5em;
}
.toolbutton input {
display:none;
display: none;
}
.toolbutton.bad:first-line,
.toolbutton.good:first-line {

@ -79,7 +79,7 @@ module.exports = function(sql) {
});
});
socket.on("user", function(name) {
socket.on("user", function(name, fn) {
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) {
@ -87,7 +87,20 @@ module.exports = function(sql) {
result.exists = true;
result.pubkey = res[0].pubkey;
}
emit('user', result);
fn(result);
});
});
socket.on("lookup", function(name, fn) {
console.log("-> signal: lookup("+name+")")
var result = false
sql.query("select pubkey from user where name = ?",
[name],
function(err, res, flds) {
if (!err && res && res.length) {
result = true;
}
fn(result)
});
});

@ -51,21 +51,12 @@
<div id="newuser" style="display: none">
<h2>Register User</h2>
<p>All you need to start is a username and a password:</p>
<form id="register" autocomplete="off">
<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="repeat password" autocomplete="off" type="password" id="pwd2" oninput="checkpwd(document.getElementById('pwd').value, this.value)"/>
<button id="createuser" disabled>register</button>
<form id="register" autocomplete="off" onsubmit="safechat.createuser(document.getElementById('user').value, document.getElementById('pwd').value)">
<input placeholder="username" autocomplete="off" type="text" id="user" oninput="safechat.lookup(this.value)" />
<input placeholder="password" autocomplete="off" type="password" id="pwd" oninput="safechat.checkpasswords(this.value, document.getElementById('pwd2').value)"/>
<input placeholder="repeat password" autocomplete="off" type="password" id="pwd2" oninput="safechat.checkpasswords(document.getElementById('pwd').value, this.value)"/>
<input id="createuser" type="submit" value="register" disabled />
</form>
<script>
$("#user").on("input", function() {
queryuser($('#user').val());
});
$("#createuser").on("click", function(event) {
createkeypair($('#user').val(), $('#pwd').val());
return false;
});
</script>
<p>Please chose any username, e.g. a pseudonym, your e-mail,
your phone number, your real name, and chose a safe
password.</p>
@ -184,9 +175,9 @@
<!-- Fatal: Abort -->
<div id="fatal">
<div id="fatal" style="display: none">
<h2 id="fatal-msg">Failure</h2>
<p>The SafeChat has been aborted due to a fatal error.</p>
<p>SafeChat has been aborted due to a fatal error.</p>
<p>There is a problem in your browser. Please try to reload. If the problem persists, please update your web browser or try SafeChat in another browser.</p>
<p>The following java script features are required:</p>
<ul class="features">

Loading…
Cancel
Save