Fixed a number of bugs that arose from merging the code from Bens fork. Added an extra step when voting which asks the user to confirm their voting choices. The voter now gets an email when they've successfully submitted thier vote.
This commit is contained in:
parent
fc0d0ea21c
commit
a168ba9e28
6 changed files with 281 additions and 75 deletions
|
@ -211,7 +211,7 @@ class Ballot(models.Model):
|
||||||
|
|
||||||
class EncBallot(models.Model):
|
class EncBallot(models.Model):
|
||||||
handle = models.CharField(primary_key=True, default=uuid.uuid4, editable=False, max_length=255)
|
handle = models.CharField(primary_key=True, default=uuid.uuid4, editable=False, max_length=255)
|
||||||
ballot = models.CharField(max_length=4096)
|
ballot = models.CharField(max_length=10240)
|
||||||
|
|
||||||
|
|
||||||
# Implements the new binary encoding scheme
|
# Implements the new binary encoding scheme
|
||||||
|
|
|
@ -212,6 +212,22 @@ def email_voters_vote_url(voters, event):
|
||||||
voter.send_email(email_subject, email_body)
|
voter.send_email(email_subject, email_body)
|
||||||
|
|
||||||
|
|
||||||
|
@task()
|
||||||
|
def email_voting_success(voter, ballotHandle, eventTitle):
|
||||||
|
email_subject = "Vote(s) received for Event '" + eventTitle + "'"
|
||||||
|
|
||||||
|
# Plain text email - this could be replaced for a HTML-based email in the future
|
||||||
|
email_body_base = str("")
|
||||||
|
email_body_base += "Dear Voter,\n\n"
|
||||||
|
email_body_base += "Thank you for your vote(s) for the event: " + eventTitle + ". This has been securely encrypted "
|
||||||
|
email_body_base += "and anonymously stored in our system.\n\n"
|
||||||
|
email_body_base += "For your reference, the identifier for your selected ballot is:\n"
|
||||||
|
email_body_base += ballotHandle
|
||||||
|
email_body_base += get_email_sign_off()
|
||||||
|
|
||||||
|
voter.send_email(email_subject, email_body_base)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -18,6 +18,7 @@ from allauthdemo.auth.models import DemoUser
|
||||||
|
|
||||||
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots
|
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots
|
||||||
from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally, combine_encrypted_votes
|
from .tasks import create_ballots_for_poll, email_voters_vote_url, combine_decryptions_and_tally, combine_encrypted_votes
|
||||||
|
from .tasks import email_voting_success
|
||||||
|
|
||||||
from .utils.EventModelAdaptor import EventModelAdaptor
|
from .utils.EventModelAdaptor import EventModelAdaptor
|
||||||
|
|
||||||
|
@ -220,7 +221,9 @@ def event_vote(request, event_id, poll_id):
|
||||||
ballot.selection = selection
|
ballot.selection = selection
|
||||||
ballot.save()
|
ballot.save()
|
||||||
|
|
||||||
combine_encrypted_votes.delay(email_key[0].user, poll)
|
voter = email_key[0].user
|
||||||
|
combine_encrypted_votes.delay(voter, poll)
|
||||||
|
email_voting_success.delay(voter, handle_json, event.title)
|
||||||
|
|
||||||
if next_poll_uuid:
|
if next_poll_uuid:
|
||||||
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid,
|
return HttpResponseRedirect(reverse('polls:event-vote', kwargs={'event_id': event.uuid,
|
||||||
|
|
|
@ -34,8 +34,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<br/>
|
<br/>
|
||||||
<span><strong>Number of polls for this event:</strong> {{ poll_count }}</span>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
<br/>
|
||||||
<span><strong>Instructions:</strong>
|
<span><strong>Instructions:</strong>
|
||||||
You will be shown each poll for this event one by one where you will need to make a selection for the current
|
You will be shown each poll for this event one by one where you will need to make a selection for the current
|
||||||
|
@ -61,26 +59,21 @@
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading"><strong>Options</strong></div>
|
<div class="panel-heading"><strong>Options</strong></div>
|
||||||
<div class="panel panel-body">
|
<div class="panel panel-body">
|
||||||
{% comment %}<select class="radio-inline select form-control" id="poll-options" name="options">
|
|
||||||
{% load custom_filters_tags %}
|
|
||||||
<option value="{{ -1|get_ballot_value:object.options.all.count }}">Please Select...</option>
|
|
||||||
{% for option in object.options.all %}
|
|
||||||
<option value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>{% endcomment %}
|
|
||||||
{% for option in object.options.all %}
|
{% for option in object.options.all %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
{% load custom_filters_tags %}
|
{% load custom_filters_tags %}
|
||||||
<label><input type="checkbox" value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}</label>
|
<label id="{{forloop.counter|get_ballot_value:object.options.all.count}}">
|
||||||
|
<input type="checkbox" value="{{forloop.counter|get_ballot_value:object.options.all.count}}">{{ option.choice_text }}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr/>
|
<hr/>
|
||||||
<div id="ballot-gen-progress-area">
|
<div id="ballot-gen-progress-area">
|
||||||
<button id="gen-ballots-btn" class="btn btn-primary">Generate Ballots</button>
|
<button id="gen-ballots-btn" class="btn btn-primary">Begin Voting</button>
|
||||||
<!-- Progress bar which is used during encryption -->
|
<!-- Progress bar which is used during encryption -->
|
||||||
<h4 id="progress-bar-description" class="hidden">Generating Ballots...</h4>
|
<h4 id="progress-bar-description" class="hidden">Generating 2 Digital Ballots. Please wait...</h4>
|
||||||
<div id="progress-bar-container" class="progress hidden">
|
<div id="progress-bar-container" class="progress hidden">
|
||||||
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
<div id="progress-bar" class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
|
||||||
<span class="sr-only">70% Complete</span>
|
<span class="sr-only">70% Complete</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -124,8 +117,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="cancelVoteBtn" type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
|
<button id="nextDialogBtn" type="button" class="btn btn-primary">Next</button>
|
||||||
|
<button id="cancelDialogBtn" type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
|
||||||
<button id="closeDialogBtn" type="button" class="btn btn-primary hidden" data-dismiss="modal">Close</button>
|
<button id="closeDialogBtn" type="button" class="btn btn-primary hidden" data-dismiss="modal">Close</button>
|
||||||
|
<button id="startOverDialogBtn" type="button" class="btn btn-danger hidden" data-dismiss="modal">Start Over</button>
|
||||||
|
<button id="submitDialogBtn" type="button" class="btn btn-success hidden">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -209,3 +209,29 @@ input[type="file"] {
|
||||||
width: 54%;
|
width: 54%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img[src^="data"] {
|
||||||
|
width: 45%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.containerMarginTop {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skDIV {
|
||||||
|
width: 75%;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: 3em;
|
||||||
|
background-color: darkslategrey;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skDIV > p {
|
||||||
|
text-align: center;
|
||||||
|
height: 3em;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 2.9em;
|
||||||
|
}
|
|
@ -1,4 +1,11 @@
|
||||||
var dialogOpen = false;
|
var dialogOpen = false;
|
||||||
|
var DIALOG_BTN_STATES = {
|
||||||
|
STEP_1: 1,
|
||||||
|
STEP_2: 2,
|
||||||
|
STEP_3: 3,
|
||||||
|
VOTE_SUCCESS: 4,
|
||||||
|
VOTE_ERROR: 5
|
||||||
|
};
|
||||||
|
|
||||||
function showDialogWithText(titleTxt, bodyTxt) {
|
function showDialogWithText(titleTxt, bodyTxt) {
|
||||||
var modalDialog = $('#modalDialog');
|
var modalDialog = $('#modalDialog');
|
||||||
|
@ -10,14 +17,55 @@ function showDialogWithText(titleTxt, bodyTxt) {
|
||||||
var p = document.createElement("p");
|
var p = document.createElement("p");
|
||||||
p.innerHTML = bodyTxt;
|
p.innerHTML = bodyTxt;
|
||||||
body.empty();
|
body.empty();
|
||||||
body.append( p );
|
body.append(p);
|
||||||
|
|
||||||
if(!dialogOpen) {
|
if (!dialogOpen) {
|
||||||
modalDialog.modal('toggle');
|
modalDialog.modal('toggle');
|
||||||
dialogOpen = true;
|
dialogOpen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDialogButtons(state) {
|
||||||
|
// Trigger the btn selectors once here
|
||||||
|
let nextDialogBtn = $('#nextDialogBtn');
|
||||||
|
let cancelDialogBtn = $('#cancelDialogBtn');
|
||||||
|
let closeDialogBtn = $('#closeDialogBtn');
|
||||||
|
let startOverDialogBtn = $('#startOverDialogBtn');
|
||||||
|
let submitDialogBtn = $('#submitDialogBtn');
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case DIALOG_BTN_STATES.STEP_1:
|
||||||
|
nextDialogBtn.removeClass("hidden");
|
||||||
|
cancelDialogBtn.removeClass("hidden");
|
||||||
|
closeDialogBtn.addClass("hidden");
|
||||||
|
startOverDialogBtn.addClass("hidden");
|
||||||
|
submitDialogBtn.addClass("hidden");
|
||||||
|
break;
|
||||||
|
case DIALOG_BTN_STATES.STEP_2:
|
||||||
|
nextDialogBtn.addClass("hidden");
|
||||||
|
cancelDialogBtn.removeClass("hidden");
|
||||||
|
closeDialogBtn.addClass("hidden");
|
||||||
|
startOverDialogBtn.addClass("hidden");
|
||||||
|
submitDialogBtn.addClass("hidden");
|
||||||
|
break;
|
||||||
|
case DIALOG_BTN_STATES.STEP_3:
|
||||||
|
nextDialogBtn.addClass("hidden");
|
||||||
|
cancelDialogBtn.addClass("hidden");
|
||||||
|
closeDialogBtn.addClass("hidden");
|
||||||
|
startOverDialogBtn.removeClass("hidden");
|
||||||
|
submitDialogBtn.removeClass("hidden");
|
||||||
|
break;
|
||||||
|
case DIALOG_BTN_STATES.VOTE_SUCCESS:
|
||||||
|
case DIALOG_BTN_STATES.VOTE_ERROR:
|
||||||
|
nextDialogBtn.addClass("hidden");
|
||||||
|
cancelDialogBtn.addClass("hidden");
|
||||||
|
closeDialogBtn.removeClass("hidden");
|
||||||
|
startOverDialogBtn.addClass("hidden");
|
||||||
|
submitDialogBtn.addClass("hidden");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This should stop people ticking more than the maximum permitted
|
// This should stop people ticking more than the maximum permitted
|
||||||
function updateCheckboxInteractivity() {
|
function updateCheckboxInteractivity() {
|
||||||
var inputs = $("label input[type=checkbox]");
|
var inputs = $("label input[type=checkbox]");
|
||||||
|
@ -65,10 +113,6 @@ function isVotingInputValid() {
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(selectedCount < MAX_SELECTIONS) {
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will highlight when people haven't selected enough options
|
// This will highlight when people haven't selected enough options
|
||||||
|
|
||||||
if(!valid) {
|
if(!valid) {
|
||||||
|
@ -86,6 +130,7 @@ function isVotingInputValid() {
|
||||||
let titleTxt = 'Voting Error';
|
let titleTxt = 'Voting Error';
|
||||||
|
|
||||||
showDialogWithText(titleTxt, errText);
|
showDialogWithText(titleTxt, errText);
|
||||||
|
updateDialogButtons(DIALOG_BTN_STATES.VOTE_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +304,18 @@ function SHA256Hash(bytes, toStr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateBallots() {
|
function generateBallots() {
|
||||||
|
// Get the user's selected option
|
||||||
|
let inputs = $("label input[type=checkbox]");
|
||||||
|
let selectedOption = "";
|
||||||
|
inputs.each(function() {
|
||||||
|
let input = $(this);
|
||||||
|
|
||||||
|
if(input.prop('checked')) {
|
||||||
|
selectedOption = input.val();
|
||||||
|
selectedOption = document.getElementById(selectedOption).innerText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Generate Ballot A and Ballot B to be displayed to the user
|
// Generate Ballot A and Ballot B to be displayed to the user
|
||||||
// This fn starts the process
|
// This fn starts the process
|
||||||
var ballotA = generateBallot();
|
var ballotA = generateBallot();
|
||||||
|
@ -271,11 +328,12 @@ function generateBallots() {
|
||||||
var ballotB = generateBallot();
|
var ballotB = generateBallot();
|
||||||
progressBar.setAttribute("style", "width: 100%;");
|
progressBar.setAttribute("style", "width: 100%;");
|
||||||
|
|
||||||
showFirstQRCode(ballotA, ballotB);
|
showFirstQRCode(ballotA, ballotB, selectedOption);
|
||||||
}, 150);
|
}, 150);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showFirstQRCode(ballotA, ballotB) {
|
// Called in stage 1 of 3 in the voting process
|
||||||
|
function showFirstQRCode(ballotA, ballotB, selectedOption) {
|
||||||
var ballots = new Array(ballotA, ballotB);
|
var ballots = new Array(ballotA, ballotB);
|
||||||
var ballotHashes = new Array(2);
|
var ballotHashes = new Array(2);
|
||||||
|
|
||||||
|
@ -287,14 +345,19 @@ function showFirstQRCode(ballotA, ballotB) {
|
||||||
var modalDialog = $('#modalDialog');
|
var modalDialog = $('#modalDialog');
|
||||||
var title = modalDialog.find('.modal-title');
|
var title = modalDialog.find('.modal-title');
|
||||||
var body = modalDialog.find('.modal-body');
|
var body = modalDialog.find('.modal-body');
|
||||||
var footer = modalDialog.find('.modal-footer');
|
|
||||||
|
|
||||||
body.empty();
|
body.empty();
|
||||||
title.text('Please Scan this QR Code');
|
title.text('Step 1 of 3: Link Your Vote');
|
||||||
|
|
||||||
|
let pleaseScanP = document.createElement('p');
|
||||||
|
pleaseScanP.innerHTML = "Please scan the following QR code from your DEMOS 2 mobile application:";
|
||||||
|
|
||||||
|
let QRDiv = document.createElement('div');
|
||||||
var QRCodeImg = document.createElement('img');
|
var QRCodeImg = document.createElement('img');
|
||||||
QRCodeImg.setAttribute('class', 'QR-code');
|
QRCodeImg.setAttribute('class', 'QR-code');
|
||||||
|
QRCodeImg.setAttribute('id', "qr-img");
|
||||||
new QRCode(QRCodeImg, ballotHashes[0] + ';' + ballotHashes[1]);
|
new QRCode(QRCodeImg, ballotHashes[0] + ';' + ballotHashes[1]);
|
||||||
|
QRDiv.append(QRCodeImg);
|
||||||
|
|
||||||
// ----------------------------------------------
|
// ----------------------------------------------
|
||||||
|
|
||||||
|
@ -315,37 +378,31 @@ function showFirstQRCode(ballotA, ballotB) {
|
||||||
|
|
||||||
// -----------------------------------------------
|
// -----------------------------------------------
|
||||||
|
|
||||||
body.append(QRCodeImg);
|
body.append(pleaseScanP);
|
||||||
|
body.append(QRDiv);
|
||||||
body.append(hashGroupDiv);
|
body.append(hashGroupDiv);
|
||||||
|
|
||||||
var closeButton = $('close-button');
|
// Prepare the appropriate dialog buttons
|
||||||
closeButton.removeClass('btn-success');
|
updateDialogButtons(DIALOG_BTN_STATES.STEP_1);
|
||||||
closeButton.addClass('btn-danger');
|
|
||||||
closeButton.text("Close without submitting vote");
|
|
||||||
|
|
||||||
var nextButton = document.createElement('button');
|
if(!dialogOpen) {
|
||||||
nextButton.setAttribute('type', 'button');
|
modalDialog.modal('toggle');
|
||||||
nextButton.setAttribute('id', 'next-button');
|
dialogOpen = true;
|
||||||
nextButton.setAttribute('class', 'btn btn-default');
|
}
|
||||||
nextButton.innerHTML = "Next";
|
|
||||||
|
|
||||||
footer.prepend(nextButton);
|
$('#nextDialogBtn').click(function(e) {
|
||||||
|
showBallotChoiceDialog(ballots, ballotHashes, selectedOption, modalDialog);
|
||||||
modalDialog.modal('show');
|
|
||||||
|
|
||||||
$('#next-button').click(function(e) {
|
|
||||||
showBallotChoiceDialog(ballots);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showBallotChoiceDialog(ballots) {
|
// Called in stage 2 of 3 in the voting process
|
||||||
|
function showBallotChoiceDialog(ballots, ballotHashes, selectedOption, dialog) {
|
||||||
// Display the ballot choice dialog
|
// Display the ballot choice dialog
|
||||||
var modalDialog = $('#modalDialog');
|
var title = dialog.find('.modal-title');
|
||||||
var title = modalDialog.find('.modal-title');
|
var body = dialog.find('.modal-body');
|
||||||
var body = modalDialog.find('.modal-body');
|
|
||||||
|
|
||||||
body.empty();
|
body.empty();
|
||||||
title.text('Please Select a Ballot');
|
title.text('Step 2 of 3: Select a Ballot');
|
||||||
|
|
||||||
// Generate the body of the dialog which consists of a button for A and for B
|
// Generate the body of the dialog which consists of a button for A and for B
|
||||||
var choiceGroupDiv = document.createElement('div');
|
var choiceGroupDiv = document.createElement('div');
|
||||||
|
@ -363,21 +420,104 @@ function showBallotChoiceDialog(ballots) {
|
||||||
btnChoiceB.innerHTML = 'B';
|
btnChoiceB.innerHTML = 'B';
|
||||||
choiceGroupDiv.append(btnChoiceB);
|
choiceGroupDiv.append(btnChoiceB);
|
||||||
|
|
||||||
body.append(choiceGroupDiv);
|
// ----------------------------------------------
|
||||||
|
|
||||||
modalDialog.modal('show');
|
var hashGroupDiv = document.createElement('div');
|
||||||
|
var br = document.createElement('br');
|
||||||
|
hashGroupDiv.append( br );
|
||||||
|
|
||||||
|
var hashA = document.createElement("span");
|
||||||
|
hashA.innerHTML = "Hash A: " + ballotHashes[0];
|
||||||
|
hashGroupDiv.append( hashA );
|
||||||
|
|
||||||
|
var br2 = document.createElement('br');
|
||||||
|
hashGroupDiv.append( br2 );
|
||||||
|
|
||||||
|
var hashB = document.createElement("span");
|
||||||
|
hashB.innerHTML = "Hash B: " + ballotHashes[1];
|
||||||
|
hashGroupDiv.append( hashB );
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
body.append(choiceGroupDiv);
|
||||||
|
body.append(hashGroupDiv);
|
||||||
|
|
||||||
// Register callback functions for the selection of either A or B
|
// Register callback functions for the selection of either A or B
|
||||||
$('#choice-A').click(function(e) {
|
$('#choice-A').click(function(e) {
|
||||||
sendBallotsToServer(ballots[0], ballots[1]);
|
showSelectionConfirmationDialog("A", ballots[0], ballotHashes[0], ballots[1], selectedOption, dialog);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#choice-B').click(function(e) {
|
$('#choice-B').click(function(e) {
|
||||||
sendBallotsToServer(ballots[1], ballots[0]);
|
showSelectionConfirmationDialog("B", ballots[1], ballotHashes[1], ballots[0], selectedOption, dialog);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateDialogButtons(DIALOG_BTN_STATES.STEP_2);
|
||||||
|
|
||||||
|
if(!dialogOpen) {
|
||||||
|
modalDialog.modal('toggle');
|
||||||
|
dialogOpen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendBallotsToServer(selection, alt) {
|
// Called in stage 3 of 3 in the voting process
|
||||||
|
function showSelectionConfirmationDialog(selection, selectedBallot, selectedBallotHash,
|
||||||
|
otherBallot, selectedOption, dialog) {
|
||||||
|
let title = dialog.find('.modal-title');
|
||||||
|
let body = dialog.find('.modal-body');
|
||||||
|
body.empty();
|
||||||
|
|
||||||
|
title.text("Step 3 of 3: Confirm Ballot Selection");
|
||||||
|
|
||||||
|
// Ballot detail section
|
||||||
|
let selectedInfoSecDiv = document.createElement('div');
|
||||||
|
|
||||||
|
let detailsP = document.createElement('p');
|
||||||
|
detailsP.innerHTML = "Please check the following details are correct: ";
|
||||||
|
selectedInfoSecDiv.append(detailsP);
|
||||||
|
|
||||||
|
let ul = document.createElement('ul');
|
||||||
|
|
||||||
|
let selectedOptionLi = document.createElement('li');
|
||||||
|
selectedOptionLi.innerHTML = "Selected Option: " + selectedOption;
|
||||||
|
|
||||||
|
let ballotSelectionLi = document.createElement('li');
|
||||||
|
ballotSelectionLi.innerHTML = "Selected Ballot: " + selection;
|
||||||
|
|
||||||
|
let ballotHashLi = document.createElement('li');
|
||||||
|
ballotHashLi.innerHTML = "SHA256 Ballot Fingerprint: " + selectedBallotHash;
|
||||||
|
|
||||||
|
ul.append(selectedOptionLi);
|
||||||
|
ul.append(ballotSelectionLi);
|
||||||
|
ul.append(ballotHashLi);
|
||||||
|
selectedInfoSecDiv.append(ul);
|
||||||
|
|
||||||
|
// Instruction section
|
||||||
|
let instructionsP = document.createElement('p');
|
||||||
|
instructionsP.innerHTML = "If you are happy with your selection you can click on the 'Submit' button below to store"
|
||||||
|
+ " your vote. Otherwise you can select 'Start Over' to go through the voting process again.";
|
||||||
|
selectedInfoSecDiv.append(instructionsP);
|
||||||
|
|
||||||
|
let additionalInstructionsP = document.createElement('p');
|
||||||
|
additionalInstructionsP.innerHTML = "You can overwrite your vote later by re-visiting this page and voting again.";
|
||||||
|
selectedInfoSecDiv.append(additionalInstructionsP);
|
||||||
|
|
||||||
|
body.append(selectedInfoSecDiv);
|
||||||
|
|
||||||
|
// Update the dialog buttons accordingly
|
||||||
|
updateDialogButtons(DIALOG_BTN_STATES.STEP_3);
|
||||||
|
|
||||||
|
$('#submitDialogBtn').click(function() {
|
||||||
|
// Dispatch the ballot to the server
|
||||||
|
sendBallotsToServer(selection, selectedBallot, otherBallot);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!dialogOpen) {
|
||||||
|
modalDialog.modal('toggle');
|
||||||
|
dialogOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendBallotsToServer(selection, selectedBallot, otherBallot) {
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
beforeSend: function(xhr, settings) {
|
beforeSend: function(xhr, settings) {
|
||||||
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||||
|
@ -416,14 +556,15 @@ function sendBallotsToServer(selection, alt) {
|
||||||
var pollNum = $('#poll-num').text();
|
var pollNum = $('#poll-num').text();
|
||||||
var ballotID = encodeURIComponent(btoa(JSON.stringify({voterID: voterID, eventID: eventID, pollNum: pollNum})));
|
var ballotID = encodeURIComponent(btoa(JSON.stringify({voterID: voterID, eventID: eventID, pollNum: pollNum})));
|
||||||
|
|
||||||
|
// TODO: Generate a SK rather than using a static one. UUID generated server side and then injected JS side?
|
||||||
var SK = "temporary";
|
var SK = "temporary";
|
||||||
var encAlt = sjcl.encrypt(SK, JSON.stringify(alt));
|
var encAlt = sjcl.encrypt(SK, JSON.stringify(otherBallot));
|
||||||
selection = JSON.stringify(selection);
|
let selectedBallotAsStr = JSON.stringify(selectedBallot);
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : "POST",
|
type : "POST",
|
||||||
url : window.location,
|
url : window.location,
|
||||||
data : { handle: ballotID, encBallot: encAlt, ballot: selection },
|
data : { handle: ballotID, encBallot: encAlt, ballot: selectedBallotAsStr, selection: selection },
|
||||||
success : function(){
|
success : function(){
|
||||||
onAfterBallotSend(ballotID, SK);
|
onAfterBallotSend(ballotID, SK);
|
||||||
}
|
}
|
||||||
|
@ -432,40 +573,64 @@ function sendBallotsToServer(selection, alt) {
|
||||||
|
|
||||||
// Called once the ballot has been sent to the back-end and dialog has closed
|
// Called once the ballot has been sent to the back-end and dialog has closed
|
||||||
function onAfterBallotSend(ballotID, SK) {
|
function onAfterBallotSend(ballotID, SK) {
|
||||||
let titleText = 'Vote Successfully Received';
|
|
||||||
let bodyText = "Thank you for voting! Your secret key is '"+SK+"'. Make sure to scan this QR code with your phone before closing this window.";
|
|
||||||
|
|
||||||
if(POLL_NUM !== POLL_COUNT) {
|
|
||||||
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// With one ballot selected, we can display a QR code of the ballot ID
|
// With one ballot selected, we can display a QR code of the ballot ID
|
||||||
var modalDialog = $('#modalDialog');
|
var modalDialog = $('#modalDialog');
|
||||||
var title = modalDialog.find('.modal-title');
|
var title = modalDialog.find('.modal-title');
|
||||||
var body = modalDialog.find('.modal-body');
|
var body = modalDialog.find('.modal-body');
|
||||||
title.text(titleText);
|
|
||||||
body.empty();
|
body.empty();
|
||||||
|
|
||||||
var p = document.createElement("p");
|
let titleText = 'Vote Successfully Received';
|
||||||
p.innerHTML = bodyText;
|
title.text(titleText);
|
||||||
body.append(p);
|
|
||||||
|
|
||||||
// Generate the body of the dialog which displays the unselected ballot QR code
|
// Add the first section: Instructions on next steps
|
||||||
|
let instructions1Txt = "Thank you for voting! Please note down the ballot identifier by scanning " +
|
||||||
|
"this QR code using the DEMOS2 mobile application: ";
|
||||||
|
|
||||||
|
var instructions1P = document.createElement("p");
|
||||||
|
instructions1P.innerHTML = instructions1Txt;
|
||||||
|
body.append(instructions1P);
|
||||||
|
|
||||||
|
// Add the second section: QR code that contains the ballot identifier
|
||||||
var QRCodeImg = document.createElement('img');
|
var QRCodeImg = document.createElement('img');
|
||||||
QRCodeImg.setAttribute('class', 'QR-code');
|
QRCodeImg.setAttribute('class', 'QR-code');
|
||||||
new QRCode(QRCodeImg, ballotID);
|
new QRCode(QRCodeImg, ballotID);
|
||||||
|
|
||||||
body.append(QRCodeImg);
|
body.append(QRCodeImg);
|
||||||
|
|
||||||
var closeButton = $('#close-button');
|
// Add the third section: instructions on Ballot ID and SK
|
||||||
closeButton.removeClass('btn-danger');
|
let instructions2Div = document.createElement('div');
|
||||||
closeButton.addClass('btn-success');
|
instructions2Div.setAttribute('class', 'containerMarginTop');
|
||||||
closeButton.text("Close");
|
|
||||||
if(POLL_NUM == POLL_COUNT) {
|
let instructions2Txt = "You will also be emailed the ballot identifier. However, you will need to note down the following " +
|
||||||
$('#next-button').hide();
|
"secret in order to later verify your ballot was recorded as cast: ";
|
||||||
|
let instructions2P = document.createElement('p');
|
||||||
|
instructions2P.innerHTML = instructions2Txt;
|
||||||
|
instructions2Div.append(instructions2P);
|
||||||
|
body.append(instructions2Div);
|
||||||
|
|
||||||
|
// Add the fourth section: SK plain text
|
||||||
|
let SKContainerDiv = document.createElement('div');
|
||||||
|
SKContainerDiv.setAttribute("class", "containerMarginTop");
|
||||||
|
|
||||||
|
let SKDiv = document.createElement('div');
|
||||||
|
SKDiv.setAttribute("class", "skDIV");
|
||||||
|
|
||||||
|
let SKP = document.createElement('p');
|
||||||
|
SKP.innerHTML = SK;
|
||||||
|
SKDiv.append(SKP);
|
||||||
|
|
||||||
|
SKContainerDiv.append(SKDiv);
|
||||||
|
body.append(SKContainerDiv);
|
||||||
|
|
||||||
|
// Conditional fifth section: Instructions on how to vote on the next poll for the event
|
||||||
|
if(POLL_NUM !== POLL_COUNT) {
|
||||||
|
let instructions3Txt = "You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
|
||||||
|
let instructions3P = document.createElement('p');
|
||||||
|
instructions3P.innerHTML = instructions3Txt;
|
||||||
|
body.append(instructions3P);
|
||||||
}
|
}
|
||||||
|
|
||||||
modalDialog.modal('show');
|
updateDialogButtons(DIALOG_BTN_STATES.VOTE_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#modalDialog').on('hide.bs.modal', function (e) {
|
$('#modalDialog').on('hide.bs.modal', function (e) {
|
||||||
|
@ -474,7 +639,7 @@ $('#modalDialog').on('hide.bs.modal', function (e) {
|
||||||
if(titleText.indexOf("Received") > -1) {
|
if(titleText.indexOf("Received") > -1) {
|
||||||
// Update page to reflect the fact that a vote has taken place
|
// Update page to reflect the fact that a vote has taken place
|
||||||
location.reload();
|
location.reload();
|
||||||
} else {
|
} else if (titleText.indexOf("Error") === -1) {
|
||||||
// Reset poll voting to allow user to vote again
|
// Reset poll voting to allow user to vote again
|
||||||
progressBar.setAttribute("style", "width: 0%;");
|
progressBar.setAttribute("style", "width: 0%;");
|
||||||
$('#gen-ballots-btn').toggleClass("hidden");
|
$('#gen-ballots-btn').toggleClass("hidden");
|
||||||
|
|
Reference in a new issue