From 684390f3c0120611e058e3c6fb3ba677748fd313 Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Thu, 6 Jul 2017 18:04:42 -0400 Subject: [PATCH 1/6] Implement display of visual information (title and description) as well as checking session and participation code with the server on load in the client side --- client/index.html | 12 +++- client/script/client.js | 42 ++++++++++++++ server/index.js | 92 ++++++++++++++++++++++++++----- trusted/index.html | 16 ++++-- trusted/script/generateSession.js | 7 ++- 5 files changed, 146 insertions(+), 23 deletions(-) diff --git a/client/index.html b/client/index.html index 0758b0ef..acec0b21 100755 --- a/client/index.html +++ b/client/index.html @@ -47,8 +47,8 @@

Workforce Survey
-

Title

-

Some explanation about what to do.

+

+

     
@@ -63,6 +63,8 @@

Title

aria-hidden="true"> +
@@ -76,6 +78,8 @@

Title

aria-hidden="true"> +
@@ -241,12 +245,15 @@

$parent.find('.success-icon').removeClass('hidden').addClass('show'); $parent.find('.fail-icon').removeClass('show').addClass('hidden'); $parent.find('.fail-help').removeClass('show').addClass('hidden'); + $parent.find('.fail-custom').removeClass('show').addClass('hidden'); + verify_keys_and_fetch_description(); } else { $parent.removeClass('has-success').addClass('has-error has-feedback'); $parent.find('.success-icon').removeClass('show').addClass('hidden'); $parent.find('.fail-icon').removeClass('hidden').addClass('show'); $parent.find('.fail-help').removeClass('hidden').addClass('show'); + $parent.find('.fail-custom').removeClass('show').addClass('hidden'); } }; @@ -262,6 +269,7 @@

$participationCode.val(getParameterByName('participationCode')); $session.val(getParameterByName('session')); + verify_keys_and_fetch_description(); var req = $.ajax({ type: "GET", diff --git a/client/script/client.js b/client/script/client.js index 08e55869..a525869e 100644 --- a/client/script/client.js +++ b/client/script/client.js @@ -16,6 +16,48 @@ function success(msg) { alertify.alert("SuccessSuccess!", msg); } +// When the session and/or participation code is modified, fetch session info from server. +function verify_keys_and_fetch_description() { + var session = $("#session").val().trim(); + var participationCode = $("#participation-code").val().trim(); + + // Do not fetch things from server that do not match the format (Why waste internet!?) + if (!participationCode.match(/^[a-z0-9]{32}$/) || !session.match(/^[a-z0-9]{32}$/)) + return; + + $.ajax({ + type: "POST", + url: "/sessioninfo", + contentType: "application/json", + data: JSON.stringify({session: session, userkey: participationCode}), + dataType: "text" + }).then(function(response) { + var title = response.title; + var description = response.description; + + $("#session-title").html(title); + $("#session-description").html(description); + + var $parent = $('#session, #participation-code').parent; + $parent.removeClass('has-error').addClass('has-success has-feedback'); + $parent.find('.success-icon').removeClass('hidden').addClass('show'); + $parent.find('.fail-icon').removeClass('show').addClass('hidden'); + $parent.find('.fail-help').removeClass('show').addClass('hidden'); + $parent.find('.fail-custom').removeClass('show').addClass('hidden'); + }).catch(function(err) { + var errorMsg = SERVER_ERR; + if (err && err.hasOwnProperty('responseText') && err.responseText !== undefined) + errorMsg = err.responseText; + + var $parent = $('#session, #participation-code').parent; + $parent.removeClass('has-success').addClass('has-error has-feedback'); + $parent.find('.success-icon').removeClass('show').addClass('hidden'); + $parent.find('.fail-icon').removeClass('hidden').addClass('show'); + $parent.find('.fail-help').removeClass('show').addClass('hidden'); + $parent.find('.fail-custom').removeClass('hidden').addClass('show').html(errorMsg); + }); +} + /** * Called when the submit button is pressed. */ diff --git a/server/index.js b/server/index.js index 73405abb..760ab2e1 100644 --- a/server/index.js +++ b/server/index.js @@ -126,7 +126,9 @@ var SessionInfo = mongoose.model('SessionInfo', { _id: String, session: String, pub_key: String, - password: String + password: String, + title: String, + description: String }); var FinalAggregate = mongoose.model('FinalAggregate', { _id: String, @@ -150,7 +152,7 @@ var SessionStatus = mongoose.model('SessionStatus', { function verify_password(session, password, success, fail) { SessionInfo.findOne({session: session, password: password}, function (err, data) { if (err) - fail('Error while verifying user key.'); + fail('Error while verifying password.'); else if (data == null) fail('Invalid session/password'); else @@ -162,7 +164,7 @@ function verify_password(session, password, success, fail) { function verify_status(session, status, success, fail) { SessionStatus.findOne({_id: session}, function (err, data) { if (err) - fail('Error while verifying user key.'); + fail('Error while verifying participation code.'); var db_status = "START"; if (data != null) @@ -201,7 +203,7 @@ app.post('/', function (req, res) { joi.validate(body, bodySchema, function (err, body) { if (err) { console.log(err); - res.status(500).send('Missing or invalid fields'); + res.status(500).send('Missing or invalid fields.'); return; } @@ -222,12 +224,12 @@ app.post('/', function (req, res) { UserKey.findOne({_id: ID}, function (err, data) { if (err) { console.log(err); - res.status(500).send('Error while verifying user key.'); + res.status(500).send('Error while verifying participation code.'); return; } if (data == null) { - res.status(500).send('Invalid user key'); + res.status(500).send('Invalid participation code.'); } else { // User Key Found. // save the mask and individual aggregate var aggToSave = new Aggregate({ @@ -264,7 +266,7 @@ app.post('/', function (req, res) { }) .catch(function (err) { console.log(err); - res.status(500).send('Unable to save aggregate, please try again'); + res.status(500).send('Unable to save aggregate, please try again.'); return; }); } @@ -284,17 +286,23 @@ app.post("/publickey", function (req, res) { joi.validate(req.body, schema, function (valErr, body) { if (valErr) { console.log(valErr); - res.status(500).send('Error while fetching key.'); + res.status(500).send('Invalid request.'); return; } + + var mask = body.mask, + req_data = body.data, + session = body.session, + user = body.user; + SessionInfo.findOne({session: body.session}, function (err, data) { if (err) { console.log(err); - res.status(500).send('Error while fetching key.'); + res.status(500).send('Error while fetching session.'); return; } if (data == null) { - res.status(500).send('No key found with the specified session ID'); + res.status(500).send('Session is not found.'); } else { res.send(data.pub_key); } @@ -302,13 +310,64 @@ app.post("/publickey", function (req, res) { }); }); +// endpoint for verifying user and session key and getting the session info. +app.post("/sessioninfo", function (req, res) { + console.log('POST /sessioninfo'); + console.log(req.body); + var schema = { + session: joi.string().alphanum().required(), + userkey: joi.string().alphanum().required() + }; + + joi.validate(req.body, schema, function (valErr, body) { + if (valErr) { + console.log(valErr); + res.status(500).send('Invalid request.'); + return; + } + + var session = body.session; + var userkey = body.userkey; + var ID = session + userkey; + + UserKey.findOne({_id: ID}, function (err, data) { + if (err) { + console.log(err); + res.status(500).send('Error while fetching data.'); + return; + } + + if (data == null) { + res.status(500).send('Invalid session key or participation code key'); + } else { + SessionInfo.findOne({session: session}, function (err, data) { + if (err) { + console.log(err); + res.status(500).send('Error while fetching data.'); + return; + } + if (data == null) { + res.status(500).send('Invalid session key.'); + } else { + res.send( { title: data.title, description: data.description } ); + } + }); + } + }); + }); +}); + // endpoint for generating and saving the public key app.post('/create_session', function (req, res) { console.log('POST /create_session'); console.log(req.body); // TODO: should be more restrictive here - var schema = {publickey: joi.string().required()}; + var schema = { + publickey: joi.string().required(), + title: joi.string().required(), + description: joi.string().required() + }; joi.validate(req.body, schema, function (valErr, body) { if (valErr) { @@ -321,11 +380,16 @@ app.post('/create_session', function (req, res) { var sessionID = crypto.randomBytes(16).toString('hex'); var password = crypto.randomBytes(16).toString('hex'); + var title = body.title.replace("<", "<").replace(">", ">"); + var description = body.description.replace("<", "<").replace(">", ">"); + var sessInfo = new SessionInfo({ _id: sessionID, session: sessionID, pub_key: publickey, - password: password + password: password, + title: title, + description: description }); sessInfo.save(function (err) { @@ -372,7 +436,7 @@ app.post('/generate_client_urls', function (req, res) { UserKey.where({session: body.session}).find(function (err, data) { if (err) { console.log(err); - res.status(500).send('Error getting user keys.'); + res.status(500).send('Error getting participation codes.'); return; } @@ -416,7 +480,7 @@ app.post('/generate_client_urls', function (req, res) { UserKey.insertMany(models, function (error, docs) { if (err) { console.log(err); - res.status(500).send("Error during storing keys."); + res.status(500).send("Error during storing participation codes."); return; } else { diff --git a/trusted/index.html b/trusted/index.html index 56d22438..8b0a955c 100644 --- a/trusted/index.html +++ b/trusted/index.html @@ -11,6 +11,9 @@ #infoDiv { visibility: hidden; } + textarea { + width: 100%; + } @@ -30,15 +33,18 @@

Trusted Party
Secure Session Creator

Instructions

- After generating a secure session, please keep the private key file named Session_#######_private_key.pem. - All secured data will be lost if the private key is lost. Also, do not share your private key. After clicking the - "Generate Session" button, email the Session Key to all participants. Once the data is collected from the - participants, with your private key, continue to the next step. + After generating a secure session, please keep the private key files named Session_#######_private_key.pem and Session_#######_password.txt. + All secured data will be lost if the private key is lost. Also, you will not be able to manage the session if you loose the password or session key. + Do not share your private key. Click on "Go To Live Data Page", enter the password, then generate and email links to the participants. + You can generate more links later. Once the data is collected from the participants, with your private key, continue to the next step.

+ +
+

diff --git a/trusted/script/generateSession.js b/trusted/script/generateSession.js index 2444a1ae..4ac043e7 100644 --- a/trusted/script/generateSession.js +++ b/trusted/script/generateSession.js @@ -94,7 +94,10 @@ function fetchOldLinks(session, password, oldUrlsID, section) { }); } -function generateSession(hiddenDiv, sessionID, passwordID, pubID, privID, linkID) { +function generateSession(hiddenDiv, sessionID, passwordID, pubID, privID, linkID, titleID, descriptionID) { + var title = document.getElementById(titleID).value; + var description = document.getElementById(descriptionID).value; + document.getElementById(hiddenDiv).style.visibility = "visible"; document.getElementById(sessionID).innerHTML = "Loading..."; document.getElementById(passwordID).innerHTML = "Loading..."; @@ -131,7 +134,7 @@ function generateSession(hiddenDiv, sessionID, passwordID, pubID, privID, linkID type: "POST", url: "/create_session", contentType: "application/json", - data: JSON.stringify({publickey: publicKey}), + data: JSON.stringify({publickey: publicKey, title: title, description: description}), success: function (resp) { console.log(resp); var rndSess = resp.sessionID; From f8aac7c2a79010e089fbaa33fae16ce9a1f26444 Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Thu, 6 Jul 2017 18:20:38 -0400 Subject: [PATCH 2/6] Finalize and test visual information and onload checks for participation code and session key --- client/index.html | 2 +- client/script/client.js | 7 +++++-- server/index.js | 4 ++-- trusted/session_data.html | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/client/index.html b/client/index.html index acec0b21..926dcc15 100755 --- a/client/index.html +++ b/client/index.html @@ -269,7 +269,7 @@

$participationCode.val(getParameterByName('participationCode')); $session.val(getParameterByName('session')); - verify_keys_and_fetch_description(); + $('#session, #participation-code').blur(); // Verify var req = $.ajax({ type: "GET", diff --git a/client/script/client.js b/client/script/client.js index a525869e..8c340d83 100644 --- a/client/script/client.js +++ b/client/script/client.js @@ -32,13 +32,14 @@ function verify_keys_and_fetch_description() { data: JSON.stringify({session: session, userkey: participationCode}), dataType: "text" }).then(function(response) { + response = JSON.parse(response); var title = response.title; var description = response.description; $("#session-title").html(title); $("#session-description").html(description); - var $parent = $('#session, #participation-code').parent; + var $parent = $('#session, #participation-code').parent(); $parent.removeClass('has-error').addClass('has-success has-feedback'); $parent.find('.success-icon').removeClass('hidden').addClass('show'); $parent.find('.fail-icon').removeClass('show').addClass('hidden'); @@ -49,12 +50,14 @@ function verify_keys_and_fetch_description() { if (err && err.hasOwnProperty('responseText') && err.responseText !== undefined) errorMsg = err.responseText; - var $parent = $('#session, #participation-code').parent; + var $parent = $('#session, #participation-code').parent(); $parent.removeClass('has-success').addClass('has-error has-feedback'); $parent.find('.success-icon').removeClass('show').addClass('hidden'); $parent.find('.fail-icon').removeClass('hidden').addClass('show'); $parent.find('.fail-help').removeClass('show').addClass('hidden'); $parent.find('.fail-custom').removeClass('hidden').addClass('show').html(errorMsg); + + console.log("ERROR " + errorMsg); }); } diff --git a/server/index.js b/server/index.js index 760ab2e1..7f33d839 100644 --- a/server/index.js +++ b/server/index.js @@ -380,8 +380,8 @@ app.post('/create_session', function (req, res) { var sessionID = crypto.randomBytes(16).toString('hex'); var password = crypto.randomBytes(16).toString('hex'); - var title = body.title.replace("<", "<").replace(">", ">"); - var description = body.description.replace("<", "<").replace(">", ">"); + var title = body.title.split("<").join("<").split(">").join(">"); + var description = body.description.split("<").join("<").split(">").join(">"); var sessInfo = new SessionInfo({ _id: sessionID, diff --git a/trusted/session_data.html b/trusted/session_data.html index ccc29623..24c88e6e 100644 --- a/trusted/session_data.html +++ b/trusted/session_data.html @@ -146,14 +146,14 @@

Previously Generated Links

fetchOldLinks(session, password, "oldUrlsID", "oldUrlsSection"); document.getElementById("gen_button").onclick = function () { - var count = document.getElementById(countID).value; + var count = document.getElementById("countID").value; count = parseInt(count); - + if(isNaN(count)) { // fail gracefully alert("Please enter the number of desired links"); return; } - + generateUrls(session, password, 'urlsID', count); }; From 7921c465f24fb32e0692c86f8d8cb591bbef25fa Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Fri, 7 Jul 2017 12:59:00 -0400 Subject: [PATCH 3/6] UI Fixes * Session and participation code boxes are validated on load but not when empty. * Title and description rolled back to what they were previously. --- client/index.html | 7 ++++--- client/script/client.js | 10 ++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/client/index.html b/client/index.html index 926dcc15..bdbb0ecc 100755 --- a/client/index.html +++ b/client/index.html @@ -47,8 +47,8 @@

Workforce Survey
-

-

+    

Title

+

Some explanation about what to do.


@@ -269,7 +269,8 @@

$participationCode.val(getParameterByName('participationCode')); $session.val(getParameterByName('session')); - $('#session, #participation-code').blur(); // Verify + if($session.val().trim() !== "") $session.blur(); + if($participationCode.val().trim() !== "") $participationCode.blur(); var req = $.ajax({ type: "GET", diff --git a/client/script/client.js b/client/script/client.js index 8c340d83..5a9cd700 100644 --- a/client/script/client.js +++ b/client/script/client.js @@ -21,10 +21,6 @@ function verify_keys_and_fetch_description() { var session = $("#session").val().trim(); var participationCode = $("#participation-code").val().trim(); - // Do not fetch things from server that do not match the format (Why waste internet!?) - if (!participationCode.match(/^[a-z0-9]{32}$/) || !session.match(/^[a-z0-9]{32}$/)) - return; - $.ajax({ type: "POST", url: "/sessioninfo", @@ -36,8 +32,8 @@ function verify_keys_and_fetch_description() { var title = response.title; var description = response.description; - $("#session-title").html(title); - $("#session-description").html(description); + //$("#session-title").html(title); + //$("#session-description").html(description); var $parent = $('#session, #participation-code').parent(); $parent.removeClass('has-error').addClass('has-success has-feedback'); @@ -56,8 +52,6 @@ function verify_keys_and_fetch_description() { $parent.find('.fail-icon').removeClass('hidden').addClass('show'); $parent.find('.fail-help').removeClass('show').addClass('hidden'); $parent.find('.fail-custom').removeClass('hidden').addClass('show').html(errorMsg); - - console.log("ERROR " + errorMsg); }); } From bebd5d254e1610e6b7f95d4c5d60f16e4a864ec6 Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Fri, 7 Jul 2017 13:04:23 -0400 Subject: [PATCH 4/6] Mirror .toLowerCase() on session and participation codes from master --- client/script/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/script/client.js b/client/script/client.js index ece36868..4defa0f1 100644 --- a/client/script/client.js +++ b/client/script/client.js @@ -18,8 +18,8 @@ function success(msg) { // When the session and/or participation code is modified, fetch session info from server. function verify_keys_and_fetch_description() { - var session = $("#session").val().trim(); - var participationCode = $("#participation-code").val().trim(); + var session = $("#session").val().trim().toLowerCase(); + var participationCode = $("#participation-code").val().trim().toLowerCase(); $.ajax({ type: "POST", From 98d8a635f2e481567cda23a5cc5ba02dab418688 Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Fri, 7 Jul 2017 13:24:46 -0400 Subject: [PATCH 5/6] UI Fixes: Temporary red/invalid cells during spreadsheet upload, error tooltip lingers on --- client/script/drop_sheet.js | 2 +- shared/sail_hot.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/script/drop_sheet.js b/client/script/drop_sheet.js index 75ad9d27..33c9f5cf 100644 --- a/client/script/drop_sheet.js +++ b/client/script/drop_sheet.js @@ -187,7 +187,7 @@ var DropSheet = function DropSheet(opts) { function process_ws(ws, table_def, table) { // Clear existing values in case user is submitting updated sheet after error. - table.clear(); + //table.clear(); // Default range for input section of spreadsheet, obtained from tables.json. var sheet_start = table_def.excel[0].start; diff --git a/shared/sail_hot.js b/shared/sail_hot.js index 8ac7a637..72be1d8c 100644 --- a/shared/sail_hot.js +++ b/shared/sail_hot.js @@ -183,7 +183,12 @@ var renderer = function (instance, TD, row, col, prop, value, cellProperties) { } - } //else { + } else { // Remove tooltip if it was already initialized + if (element !== null && element.qtip('api') !== null) { + element.qtip('api').destroy(); + } + } + //else { // // Prompt message with light-colored cell and tooltip. // // Shows on initial table load and // TD.style.background = '#ffffff'; From d78c35e1cb720190f1e3d2df1308feb6815904bf Mon Sep 17 00:00:00 2001 From: Kinan Dak Albab Date: Fri, 7 Jul 2017 13:34:28 -0400 Subject: [PATCH 6/6] Fixes --- client/script/client.js | 2 ++ shared/sail_hot.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/script/client.js b/client/script/client.js index 4defa0f1..67a2077c 100644 --- a/client/script/client.js +++ b/client/script/client.js @@ -21,6 +21,8 @@ function verify_keys_and_fetch_description() { var session = $("#session").val().trim().toLowerCase(); var participationCode = $("#participation-code").val().trim().toLowerCase(); + if(session == "" || participationCode == "") return; + $.ajax({ type: "POST", url: "/sessioninfo", diff --git a/shared/sail_hot.js b/shared/sail_hot.js index 72be1d8c..2f350ff9 100644 --- a/shared/sail_hot.js +++ b/shared/sail_hot.js @@ -184,7 +184,7 @@ var renderer = function (instance, TD, row, col, prop, value, cellProperties) { } } else { // Remove tooltip if it was already initialized - if (element !== null && element.qtip('api') !== null) { + if (element !== null && element.qtip('api') != null) { element.qtip('api').destroy(); } } @@ -254,6 +254,7 @@ var renderer = function (instance, TD, row, col, prop, value, cellProperties) { // Fallback if no jQuery - use comments. if (tooltip !== undefined && tooltip !== null && (typeof jQuery === 'undefined' || typeof jQuery().qtip === 'undefined')) { if (cellProperties.valid === false) cellProperties.comment = {"value": tooltip.errorTitle.toUpperCase() + ' - ' + tooltip.error}; + else cellProperties.comment = null; //else cellProperties.comment = { "value": tooltip.promptTitle.toUpperCase() + ' - ' + tooltip.prompt }; }