diff --git a/.gitignore b/.gitignore
index 681947f7ff38ef3bb2c398d79c496ecc6172d9cb..a2dca2bee7e168df398b922f596e4e4a6e230a26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,5 @@ media/*
 venv
 celerybeat-*
 env.sh
-.cache
\ No newline at end of file
+.cache
+.idea/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index be482e4e44bdb9307de52e9541d65cef31c05445..6b93e8ab16e9d955e4a725263820f997c0ed30c5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,3 +12,6 @@ addons:
   postgresql: "9.3"
 before_script:
   - psql -c 'create database helios;' -U postgres
+before_install:
+  - export BOTO_CONFIG=/dev/null
+
diff --git a/helios/__init__.py b/helios/__init__.py
index 06f05140bb83798916f63786610efa87d916e26a..3a516d7ce25e875c70086e5fa368e1af73eb6cd4 100644
--- a/helios/__init__.py
+++ b/helios/__init__.py
@@ -1,7 +1,5 @@
-
 from django.conf import settings
 from django.core.urlresolvers import reverse
-from helios.views import election_shortcut
 
 TEMPLATE_BASE = settings.HELIOS_TEMPLATE_BASE or "helios/templates/base.html"
 
diff --git a/helios/crypto/utils.py b/helios/crypto/utils.py
index dd395a598fbbec1df4bce90f81e7d95a9228b32b..258f4130cb45bf509bc62710674363830ebe6a5d 100644
--- a/helios/crypto/utils.py
+++ b/helios/crypto/utils.py
@@ -1,7 +1,7 @@
 """
 Crypto Utils
 """
-
+import hashlib
 import hmac, base64, json
 
 from hashlib import sha256
@@ -21,3 +21,11 @@ def to_json(d):
 def from_json(json_str):
   if not json_str: return None
   return json.loads(json_str)
+
+
+def do_hmac(k,s):
+  """
+  HMAC a value with a key, hex output
+  """
+  mac = hmac.new(k, s, hashlib.sha1)
+  return mac.hexdigest()
\ No newline at end of file
diff --git a/helios/templates/election_keygenerator.html b/helios/templates/election_keygenerator.html
index 60a2c55c703964884b93c44739c301ed47541561..4521fc9393185cf177f5e4cba6d3f69b2193ef46 100644
--- a/helios/templates/election_keygenerator.html
+++ b/helios/templates/election_keygenerator.html
@@ -76,11 +76,23 @@ function show_sk() {
 }
 
 function download_sk() {
-    UTILS.open_window_with_content(jQuery.toJSON(SECRET_KEY), "application/json");
+    $('#pk_content').show();
+    $('#sk_content').html(jQuery.toJSON(SECRET_KEY));
+}
+
+function download_sk_to_file(filename) {
+    var element = document.createElement('a');
+    element.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(jQuery.toJSON(SECRET_KEY)));
+    element.setAttribute('download', filename);
+    element.style.display = 'none';
+    document.body.appendChild(element);
+    element.click();
+    document.body.removeChild(element);
 }
 
 function show_pk() {
     $('#sk_download').hide();
+    $('#pk_content').hide();
     $('#pk_hash').show();
     $('#pk_form').show();
 }
@@ -101,7 +113,7 @@ function show_pk() {
 
 <span id="buttons"><button onclick="generate_keypair(); return false;" id="generate_button">Generate Election Keys</button></span>
 
-<br /><br />
+<br />
 If you've already generated a keypair, you can <a href="javascript:show_key_reuse()">reuse it</a>.
 </p>
 
@@ -126,11 +138,24 @@ Your key has been generated, but you may choose to<br /><a href="javascript:clea
 </span>
 
 <p>
-    <button style="font-size:16pt;" onclick="download_sk(); $('#pk_link').show();">Save your secret key</button>
+    <button style="font-size:16pt;" onclick="download_sk(); $('#pk_link').show();">Show my secret key</button>
 </p>
+</div>
 
-<p style="display: none;" id="pk_link">
-  <a href="javascript:show_pk();">ok, I've saved the key, let's move on</a>.
+<div style="display:none;" id="pk_content">
+    <p>Bellow is your trustee secret key content. Please copy its content and save it securely. <br>
+       You can also click to dowload it to a file.
+       And please don't lose it! Otherwise it will not be possible to decrypt the election tally.<br>
+    </p>
+    <textarea id="sk_content" rows="5" wrap="soft" cols="50" style="height: 25em;"></textarea>
+</div>
+
+<div style="display:none;" id="pk_link">
+<p>
+<a id="download_to_file" href="javascript:download_sk_to_file('trustee_key_for_{{election.name}}.txt');">download private key to a file</a>
+</p>
+<p>
+  <a href="javascript:show_pk();">ok, I've saved the key, let's move on</a>
 </p>
 </div>
 
diff --git a/helios/templates/trustee_check_sk.html b/helios/templates/trustee_check_sk.html
index f6b615e43b9f1400b752a39df6dfa491e5ce8de9..81afe42d4945f3b9856dc31a1eab792d3647c6db 100644
--- a/helios/templates/trustee_check_sk.html
+++ b/helios/templates/trustee_check_sk.html
@@ -59,7 +59,7 @@ To verify that you have the right secret key, paste it here:
 <p>
 
 <form onsubmit="check_sk(this.secret_key.value); this.secret_key.value=''; return false;">
-<textarea name="secret_key" cols="60" rows ="5" wrap="soft">
+<textarea name="secret_key" cols="60" rows ="5" wrap="soft" style="height: 25em;">
 </textarea>
 <br />
 <input type="submit" value="check" />
diff --git a/helios/templates/trustee_decrypt_and_prove.html b/helios/templates/trustee_decrypt_and_prove.html
index d4cbe438493aa0c6c9a00778acccaece94bc5956..05153b8a349b2ae8b91b5de283f2bc850ab89f63 100644
--- a/helios/templates/trustee_decrypt_and_prove.html
+++ b/helios/templates/trustee_decrypt_and_prove.html
@@ -157,7 +157,7 @@ function reset() {
 
       <form onsubmit="return false;">
           <h3>FIRST STEP: enter your secret key</h3>
-          <textarea id="sk_textarea" cols="60" rows="5"></textarea>
+          <textarea id="sk_textarea" cols="60" rows="5" style="height: 25em;"></textarea>
       </form>
       <p id="tally_section">
           <button onclick="do_tally();">Generate partial decryption</button>
@@ -187,13 +187,11 @@ function reset() {
           When you're ready, you can submit this result to the server.
       </p>
       Your partial decryption:<br />
-      <form action="javascript:submit_result();">
-          <textarea id="result_textarea" cols="60" rows="5" wrap="soft"></textarea><br /><br />
-          <input type="submit" value="Upload decryption factors to server" />
-      </form>
-      <br />
-      <a href="javascript:reset()">reset and restart decryption process</a>
-      <br />
+      <p>
+          <textarea id="result_textarea" cols="60" rows="5" wrap="soft" style="height: 25em;"></textarea>
+          <button onclick="submit_result();">Upload decryption factors to server</button>
+      </p>
+      <p><a href="javascript:reset()">reset and restart decryption process</a></p>
   </div>
   
   <div id="done_div">
diff --git a/helios/templates/voters_list.html b/helios/templates/voters_list.html
index 1409770868a887eafd24135e982a0d81d5c2155d..fffe57fdbc717c6c97cb39f4527938bccdacf811 100644
--- a/helios/templates/voters_list.html
+++ b/helios/templates/voters_list.html
@@ -112,6 +112,11 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter
 <table class="pretty">
 <tr>
 {% if admin_p or not election.use_voter_aliases %}
+{% if admin_p %}
+<th style="width: 80px;">Actions</th>
+<th>Login</th>
+<th>Email Address</th>
+{% endif %}
 <th>Name</th>
 {% endif %}
 
@@ -123,19 +128,22 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter
 {% for voter in voters %}
 <tr>
 {% if admin_p or not election.use_voter_aliases %}
-<td>
 {% if admin_p %}
+<td style="white-space: nowrap;">
 {% if election.frozen_at %}
 [<a href="{% url "helios.views.voters_email" election.uuid %}?voter_id={{voter.voter_login_id}}">email</a>]
 {% endif %}
 [<a onclick="return confirm('are you sure you want to remove {{voter.name}} ?');" href="{% url "helios.views.voter_delete" election.uuid voter.uuid %}">x</a>]
+</td>
+<td>{{voter.voter_login_id}}</td>
+<td>{{voter.voter_email}}</td>
 {% endif %}
-<img class="small-logo" src="/static/auth/login-icons/{{voter.voter_type}}.png" alt="{{voter.voter_type}}" /> {{voter.name}}</td>
+<td><img class="small-logo" src="/static/auth/login-icons/{{voter.voter_type}}.png" alt="{{voter.voter_type}}" /> {{voter.name}}</td>
 {% endif %}
 {% if election.use_voter_aliases %}
 <td>{{voter.alias}}</td>
 {% endif %}
-<td><tt style="font-size: 1.4em;;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "helios.views.castvote_shortcut" vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}&mdash;{% endif %}</tt></td>
+<td><tt style="font-size: 1.4em;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "helios.views.castvote_shortcut" vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}&mdash;{% endif %}</tt></td>
 </tr>
 {% endfor %}
 </table>
diff --git a/helios/urls.py b/helios/urls.py
index 67c0077672cd476a705ca58d4bf9ab6ca4288e07..8effba1727a9bbd7623471c2c70f00ae43dff62e 100644
--- a/helios/urls.py
+++ b/helios/urls.py
@@ -1,12 +1,8 @@
 # -*- coding: utf-8 -*-
-from django.conf.urls import *
-
-from django.conf import settings
+from django.conf.urls import patterns, include
 
 from views import *
 
-urlpatterns = None
-
 urlpatterns = patterns('',
   (r'^autologin$', admin_autologin),
   (r'^testcookie$', test_cookie),
diff --git a/helios/utils.py b/helios/utils.py
index d053dc11fec961d94b6edeee4879826107622d5c..03095a75074b55123571991a8e35cf0924248431 100644
--- a/helios/utils.py
+++ b/helios/utils.py
@@ -5,9 +5,7 @@ Ben Adida - ben@adida.net
 2005-04-11
 """
 
-import urllib, re, sys, datetime, urlparse, string
-
-import boto.ses
+import urllib, re, datetime, string
 
 # utils from helios_auth, too
 from helios_auth.utils import *
@@ -15,14 +13,6 @@ from helios_auth.utils import *
 from django.conf import settings
   
 import random, logging
-import hashlib, hmac, base64
-
-def do_hmac(k,s):
-  """
-  HMAC a value with a key, hex output
-  """
-  mac = hmac.new(k, s, hashlib.sha1)
-  return mac.hexdigest()
 
 
 def split_by_length(str, length, rejoin_with=None):
@@ -167,7 +157,7 @@ def one_val_raw_sql(raw_sql, values=[]):
   """
   for a simple aggregate
   """
-  from django.db import connection, transaction
+  from django.db import connection
   cursor = connection.cursor()
 
   cursor.execute(raw_sql, values)
diff --git a/helios/views.py b/helios/views.py
index 2959463db664c989371b021338ace8a48abca19f..3337459fae6dd93048807f7982f194873a2cc552 100644
--- a/helios/views.py
+++ b/helios/views.py
@@ -6,41 +6,41 @@ Ben Adida (ben@adida.net)
 """
 
 from django.core.urlresolvers import reverse
-from django.core.mail import send_mail
 from django.core.paginator import Paginator
 from django.core.exceptions import PermissionDenied
-from django.http import *
+from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseForbidden
 from django.db import transaction, IntegrityError
 
-from mimetypes import guess_type
-
 from validate_email import validate_email
 
-import csv, urllib, os, base64
+import urllib, os, base64
 
 from crypto import algs, electionalgs, elgamal
 from crypto import utils as cryptoutils
 from workflows import homomorphic
-from helios import utils as helios_utils
-from view_utils import *
+from helios import utils, VOTERS_EMAIL, VOTERS_UPLOAD
+from view_utils import SUCCESS, FAILURE, return_json, render_template, render_template_raw
 
-from helios_auth.security import *
+from helios_auth.security import check_csrf, login_required, get_user, save_in_session_across_logouts
 from helios_auth.auth_systems import AUTH_SYSTEMS, can_list_categories
 from helios_auth.models import AuthenticationExpired
 
-from helios import security
 from helios_auth import views as auth_views
 
 import tasks
 
-from security import *
-from helios_auth.security import get_user, save_in_session_across_logouts
+from security import (election_view, election_admin,
+                      trustee_check, set_logged_in_trustee,
+                      can_create_election, user_can_see_election, get_voter,
+                      user_can_admin_election, user_can_feature_election)
 
 import uuid, datetime
+import logging
 
-from models import *
+from models import User, Election, CastVote, Voter, VoterFile, Trustee, AuditedBallot
+import datatypes
 
-import forms, signals
+import forms
 
 # Parameters for everything
 ELGAMAL_PARAMS = elgamal.Cryptosystem()
@@ -196,7 +196,7 @@ def election_new(request):
       election_params = dict(election_form.cleaned_data)
       
       # is the short name valid
-      if helios_utils.urlencode(election_params['short_name']) == election_params['short_name']:      
+      if utils.urlencode(election_params['short_name']) == election_params['short_name']:
         election_params['uuid'] = str(uuid.uuid1())
         election_params['cast_url'] = settings.SECURE_URL_HOST + reverse(one_election_cast, args=[election_params['uuid']])
       
@@ -293,8 +293,8 @@ def election_badge(request, election):
 @election_view()
 def one_election_view(request, election):
   user = get_user(request)
-  admin_p = security.user_can_admin_election(user, election)
-  can_feature_p = security.user_can_feature_election(user, election)
+  admin_p = user_can_admin_election(user, election)
+  can_feature_p = user_can_feature_election(user, election)
   
   notregistered = False
   eligible_p = True
@@ -383,7 +383,7 @@ def list_trustees(request, election):
 def list_trustees_view(request, election):
   trustees = Trustee.get_by_election(election)
   user = get_user(request)
-  admin_p = security.user_can_admin_election(user, election)
+  admin_p = user_can_admin_election(user, election)
   
   return render_template(request, 'list_trustees', {'election': election, 'trustees': trustees, 'admin_p':admin_p})
   
@@ -424,14 +424,9 @@ def trustee_login(request, election_short_name, trustee_email, trustee_secret):
       if trustee.secret == trustee_secret:
         set_logged_in_trustee(request, trustee)
         return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(trustee_home, args=[election.uuid, trustee.uuid]))
-      else:
-        # bad secret, we'll let that redirect to the front page
-        pass
-    else:
-      # no such trustee
-      raise Http404
-
-  return HttpResponseRedirect(settings.SECURE_URL_HOST + "/")
+    # bad secret or no such trustee
+    raise Http404("Trustee not recognized.")
+  raise Http404("No election {} found.".format(election_short_name))
 
 @election_admin()
 def trustee_send_url(request, election, trustee_uuid):
@@ -451,7 +446,7 @@ Your trustee dashboard is at
 Helios  
 """ % (election.name, url)
 
-  helios_utils.send_email(settings.SERVER_EMAIL, ["%s <%s>" % (trustee.name, trustee.email)], 'your trustee homepage for %s' % election.name, body)
+  utils.send_email(settings.SERVER_EMAIL, ["%s <%s>" % (trustee.name, trustee.email)], 'your trustee homepage for %s' % election.name, body)
 
   logging.info("URL %s " % url)
   return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(list_trustees_view, args = [election.uuid]))
@@ -476,7 +471,7 @@ def trustee_upload_pk(request, election, trustee):
     if not trustee.public_key.verify_sk_proof(trustee.pok, algs.DLog_challenge_generator):
       raise Exception("bad pok for this public key")
     
-    trustee.public_key_hash = utils.hash_b64(utils.to_json(trustee.public_key.toJSONDict()))
+    trustee.public_key_hash = cryptoutils.hash_b64(utils.to_json(trustee.public_key.toJSONDict()))
 
     trustee.save()
     
@@ -512,8 +507,7 @@ def encrypt_ballot(request, election):
   perform the ballot encryption given answers_json, a JSON'ified list of list of answers
   (list of list because each question could have a list of answers if more than one.)
   """
-  # FIXME: maybe make this just request.POST at some point?
-  answers = utils.from_json(request.REQUEST['answers_json'])
+  answers = utils.from_json(request.POST['answers_json'])
   ev = homomorphic.EncryptedVote.fromElectionAndAnswers(election, answers)
   return ev.ld_object.includeRandomness().toJSONDict()
     
@@ -552,7 +546,13 @@ def password_voter_login(request, election):
   """
   
   # the URL to send the user to after they've logged in
-  return_url = request.REQUEST.get('return_url', reverse(one_election_cast_confirm, args=[election.uuid]))
+  if request.method == "GET" and 'return_url' in request.GET:
+      return_url = request.GET['return_url']
+  elif request. method == "POST" and 'return_url' in request.POST:
+      return_url = request.POST['return_url']
+  else:
+      return_url = reverse(one_election_cast_confirm, args=[election.uuid])
+
   bad_voter_login = (request.GET.get('bad_voter_login', "0") == "1")
 
   if request.method == "GET":
@@ -568,7 +568,7 @@ def password_voter_login(request, election):
                             'password_login_form': password_login_form,
                             'bad_voter_login' : bad_voter_login})
   
-  login_url = request.REQUEST.get('login_url', None)
+  login_url = request.GET.get('login_url', None)
 
   if not login_url:
     # login depending on whether this is a private election
@@ -923,7 +923,7 @@ def one_election_set_featured(request, election):
   """
 
   user = get_user(request)
-  if not security.user_can_feature_election(user, election):
+  if not user_can_feature_election(user, election):
     raise PermissionDenied()
 
   featured_p = bool(int(request.GET['featured_p']))
@@ -987,7 +987,7 @@ def one_election_copy(request, election):
 def one_election_questions(request, election):
   questions_json = utils.to_json(election.questions)
   user = get_user(request)
-  admin_p = security.user_can_admin_election(user, election)
+  admin_p = user_can_admin_election(user, election)
 
   return render_template(request, 'election_questions', {'election': election, 'questions_json' : questions_json, 'admin_p': admin_p})
 
@@ -1191,7 +1191,7 @@ def voters_list_pretty(request, election):
     order_by = 'alias'
 
   user = get_user(request)
-  admin_p = security.user_can_admin_election(user, election)
+  admin_p = user_can_admin_election(user, election)
 
   categories = None
   eligibility_category_id = None
@@ -1204,7 +1204,7 @@ def voters_list_pretty(request, election):
     return user_reauth(request, user)
   
   # files being processed
-  voter_files = election.voterfile_set.all()
+  voter_files = election.voterfile_set.all().order_by('-uploaded_at')
 
   # load a bunch of voters
   # voters = Voter.get_by_election(election, order_by=order_by)
@@ -1224,9 +1224,9 @@ def voters_list_pretty(request, election):
   return render_template(request, 'voters_list', 
                          {'election': election, 'voters_page': voters_page,
                           'voters': voters_page.object_list, 'admin_p': admin_p, 
-                          'email_voters': helios.VOTERS_EMAIL,
+                          'email_voters': VOTERS_EMAIL,
                           'limit': limit, 'total_voters': total_voters,
-                          'upload_p': helios.VOTERS_UPLOAD, 'q' : q,
+                          'upload_p': VOTERS_UPLOAD, 'q' : q,
                           'voter_files': voter_files,
                           'categories': categories,
                           'eligibility_category_id' : eligibility_category_id})
@@ -1330,7 +1330,7 @@ def voters_upload_cancel(request, election):
 
 @election_admin(frozen=True)
 def voters_email(request, election):
-  if not helios.VOTERS_EMAIL:
+  if not VOTERS_EMAIL:
     return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid]))
   TEMPLATES = [
     ('vote', 'Time to Vote'),
@@ -1339,11 +1339,11 @@ def voters_email(request, election):
     ('result', 'Election Result')
     ]
 
-  template = request.REQUEST.get('template', 'vote')
+  template = request.GET.get('template', 'vote')
   if not template in [t[0] for t in TEMPLATES]:
     raise Exception("bad template")
 
-  voter_id = request.REQUEST.get('voter_id', None)
+  voter_id = request.GET.get('voter_id', None)
 
   if voter_id:
     voter = Voter.get_by_election_and_voter_id(election, voter_id)
diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py b/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py
index 850184440c5fc153df12bf71e792cdccfe9baa57..5e793a6ae4a68e8662f45546b6475e5e7eb7a92c 100644
--- a/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py
+++ b/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls.defaults import *
+from django.conf.urls import *
 
 urlpatterns = patterns('{{ project }}.{{ app }}.views',
     (r'^$', 'canvas'),
diff --git a/helios_auth/auth_systems/google.py b/helios_auth/auth_systems/google.py
index b6eb57c4390ab54bd747bc03971bc42ba5fbca1f..7caa32fc2f03e810f2f675b587d37ea01d41cf40 100644
--- a/helios_auth/auth_systems/google.py
+++ b/helios_auth/auth_systems/google.py
@@ -50,11 +50,11 @@ def get_user_info_after_auth(request):
   # get the nice name
   http = httplib2.Http(".cache")
   http = credentials.authorize(http)
-  (resp_headers, content) = http.request("https://www.googleapis.com/plus/v1/people/me", "GET")
+  (resp_headers, content) = http.request("https://people.googleapis.com/v1/people/me?personFields=names", "GET")
 
   response = json.loads(content)
 
-  name = response['displayName']
+  name = response['names'][0]['displayName']
   
   # watch out, response also contains email addresses, but not sure whether thsoe are verified or not
   # so for email address we will only look at the id_token
diff --git a/helios_auth/security/__init__.py b/helios_auth/security/__init__.py
index b036067d9b837e4156df46fd9a6efdc78516b98f..facc0c32d73b225997acb529de57b501de2f8aad 100644
--- a/helios_auth/security/__init__.py
+++ b/helios_auth/security/__init__.py
@@ -10,6 +10,7 @@ from functools import update_wrapper
 from django.http import HttpResponse, Http404, HttpResponseRedirect
 from django.core.exceptions import *
 from django.conf import settings
+from django.http import HttpResponseNotAllowed
 
 import oauth
 
diff --git a/helios_auth/urls.py b/helios_auth/urls.py
index e4dca398503d1e2f802fd4811330b66b9020a1a9..11d10139530db6f84becb2cf5f55326641fb2163 100644
--- a/helios_auth/urls.py
+++ b/helios_auth/urls.py
@@ -1,31 +1,33 @@
+
 """
 Authentication URLs
 
 Ben Adida (ben@adida.net)
 """
 
-from django.conf.urls import *
+from django.conf.urls import url
 
-from views import *
-from auth_systems.password import password_login_view, password_forgotten_view
-from auth_systems.twitter import follow_view
+import views
+from settings import AUTH_ENABLED_AUTH_SYSTEMS
 
-urlpatterns = patterns('',
+urlpatterns = [
     # basic static stuff
-    (r'^$', index),
-    (r'^logout$', logout),
-    (r'^start/(?P<system_name>.*)$', start),
+    url(r'^$', views.index),
+    url(r'^logout$', views.logout),
+    url(r'^start/(?P<system_name>.*)$', views.start),
     # weird facebook constraint for trailing slash
-    (r'^after/$', after),
-    (r'^why$', perms_why),
-    (r'^after_intervention$', after_intervention),
-    
-    ## should make the following modular
+    url(r'^after/$', views.after),
+    url(r'^why$', views.perms_why),
+    url(r'^after_intervention$', views.after_intervention),
+]
 
-    # password auth
-    (r'^password/login', password_login_view),
-    (r'^password/forgot', password_forgotten_view),
+# password auth
+if 'password' in AUTH_ENABLED_AUTH_SYSTEMS:
+    from auth_systems.password import password_login_view, password_forgotten_view
+    urlpatterns.append(url(r'^password/login', password_login_view))
+    urlpatterns.append(url(r'^password/forgot', password_forgotten_view))
 
-    # twitter
-    (r'^twitter/follow', follow_view),
-)
+# twitter
+if 'twitter' in AUTH_ENABLED_AUTH_SYSTEMS:
+    from auth_systems.twitter import follow_view
+    urlpatterns.append(url(r'^twitter/follow', follow_view))
diff --git a/heliosbooth/boothworker-single.js b/heliosbooth/boothworker-single.js
index edb1debb5132006a7efcd8af99706ebd19b9d1bb..1c87986cad03eef9b61fd5320a0ff8659d6e8af3 100644
--- a/heliosbooth/boothworker-single.js
+++ b/heliosbooth/boothworker-single.js
@@ -49,12 +49,16 @@ function do_encrypt(message) {
 // receive either
 // a) an election and an integer position of the question
 // that this worker will be used to encrypt
-// {'type': 'setup', 'question_num' : 2, 'election' : election_json}
+// {'type': 'setup', 'election': election_json}
 //
 // b) an answer that needs encrypting
-// {'type': 'encrypt', 'answer' : answer_json}
+// {'type': 'encrypt', 'q_num': 2, 'id': id, 'answer': answer_json}
 //
 self.onmessage = function(event) {
     // dispatch to method
-    self['do_' + event.data.type](event.data);
-}
+    if (event.data.type === "setup") {
+        do_setup(event.data);
+	} else if (event.data.type === "encrypt") {
+        do_encrypt(event.data);
+    }
+};
diff --git a/heliosbooth/css/forms.css b/heliosbooth/css/forms.css
index ee1a141a51660875e8ca2ee388e6d0baa1633b12..ae4cbb7fcbf9e2208ab08c6585b68c7a5144fbe2 100644
--- a/heliosbooth/css/forms.css
+++ b/heliosbooth/css/forms.css
@@ -7,7 +7,7 @@ form.prettyform label,input,textarea,select {
     line-height: 1.8;
 }
 
-form.prettyform label {
+form.prettyform label:not(.answer) {
     display: block;
     text-align: right;
     float: left;
diff --git a/heliosbooth/js/jscrypto/random.js b/heliosbooth/js/jscrypto/random.js
index dd69f9d4fcb14add53e641eef94072b377b93844..8e363a6d0d561d3bbfd21a515b472b717d7cefde 100644
--- a/heliosbooth/js/jscrypto/random.js
+++ b/heliosbooth/js/jscrypto/random.js
@@ -26,10 +26,9 @@ Random.getRandomInteger = function(max) {
   var bit_length = max.bitLength();
   Random.setupGenerator();
   var random;
-  random = sjcl.random.randomWords(Math.ceil(bit_length / 32)+2, 0);
+  random = sjcl.random.randomWords(Math.ceil(bit_length / 32) + 2, 6);
   // we get a bit array instead of a BigInteger in this case
   var rand_bi = new BigInt(sjcl.codec.hex.fromBits(random), 16);
   return rand_bi.mod(max);
-  return BigInt._from_java_object(random).mod(max);
 };
 
diff --git a/heliosbooth/templates/question.html b/heliosbooth/templates/question.html
index 776eefdf5be00e3515be452728a743e9496731e6..8945e456ce7703cad8ab925c04c490435b375a7c 100644
--- a/heliosbooth/templates/question.html
+++ b/heliosbooth/templates/question.html
@@ -25,14 +25,19 @@ as many as you approve of
 </p>
 
 {#foreach $T.question.answers as answer}
-<div id="answer_label_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}"><input type="checkbox" class="ballot_answer" id="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" name="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" value="yes" onclick="BOOTH.click_checkbox({$T.question_num}, {$T.answer_ordering[$T.answer$index]}, this.checked);" /> {$T.question.answers[$T.answer_ordering[$T.answer$index]]}
+<div id="answer_label_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}">
+  <input type="checkbox" class="ballot_answer" id="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" name="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" value="yes" onclick="BOOTH.click_checkbox({$T.question_num}, {$T.answer_ordering[$T.answer$index]}, this.checked);" />
 
-{#if $T.question.answer_urls && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] != ""}
-&nbsp;&nbsp;
-<span style="font-size: 12pt;">
-[<a target="_blank" href="{$T.question.answer_urls[$T.answer_ordering[$T.answer$index]]}" rel="noopener noreferrer">more info</a>]
-</span>
-{#/if}
+  <label class="answer" for="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}">
+    {$T.question.answers[$T.answer_ordering[$T.answer$index]]}
+
+    {#if $T.question.answer_urls && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] != ""}
+      &nbsp;&nbsp;
+      <span style="font-size: 12pt;">
+        [<a target="_blank" href="{$T.question.answer_urls[$T.answer_ordering[$T.answer$index]]}" rel="noopener noreferrer">more info</a>]
+      </span>
+    {#/if}
+  </label>
 </div>
 {#/for}
 
diff --git a/heliosverifier/css/forms.css b/heliosverifier/css/forms.css
index ee1a141a51660875e8ca2ee388e6d0baa1633b12..ae4cbb7fcbf9e2208ab08c6585b68c7a5144fbe2 100644
--- a/heliosverifier/css/forms.css
+++ b/heliosverifier/css/forms.css
@@ -7,7 +7,7 @@ form.prettyform label,input,textarea,select {
     line-height: 1.8;
 }
 
-form.prettyform label {
+form.prettyform label:not(.answer) {
     display: block;
     text-align: right;
     float: left;
diff --git a/heliosverifier/verify.html b/heliosverifier/verify.html
index 344a277648a0791a8f6fa134f274f978ce0549b4..0e06b17c2d172f50e0e91290d1e4439806502033 100644
--- a/heliosverifier/verify.html
+++ b/heliosverifier/verify.html
@@ -97,151 +97,158 @@ function load_election_and_ballots(election_url) {
     
     // the hash will be computed within the setup function call now
     $.get(election_url, function(raw_json) {
-        election = HELIOS.Election.fromJSONString(raw_json);
-        result_append("loaded election: " + election.name);
-        result_append("election fingerprint: " + election.get_hash());
-        
-        var tally = [];
+        try {
+            election = HELIOS.Election.fromJSONString(raw_json);
+            result_append("loaded election: " + election.name);
+            result_append("election fingerprint: " + election.get_hash());
+
+            var tally = [];
 
-        $(election.questions).each(function(qnum, q) {
-            if (q.tally_type != "homomorphic") {
-              result_append("PROBLEM: this election is not a straight-forward homomorphic-tally election. As a result, Helios cannot currently verify it.");
-              return;
-            }
+            $(election.questions).each(function(qnum, q) {
+                if (q.tally_type != "homomorphic") {
+                  result_append("PROBLEM: this election is not a straight-forward homomorphic-tally election. As a result, Helios cannot currently verify it.");
+                  return;
+                }
 
-            tally[qnum] = $(q.answers).map(function(anum, a) {
-                return 1;
+                tally[qnum] = $(q.answers).map(function(anum, a) {
+                    return 1;
+                });
             });
-        });
 
-        result_append("loading list of voters...");
-        
-        // load voter list
-        load_ballot_list(election_url, [], null, function(ballot_list) {
-            result_append("loaded voter list, now loading ballots for each..");
+            result_append("loading list of voters...");
             
-            // load all ballots
-            load_ballots(election_url, ballot_list, [], function(ballots) {
-                result_append("");
-                result_append("<h3>Ballots</h3>");
-                // now load each ballot
-                $(ballots).each(function(i, cast_vote){
-
-                    if (cast_vote.vote == null)
-                      return;
-                      
-                    var vote = HELIOS.EncryptedVote.fromJSONObject(cast_vote.vote, election);
-                    result_append("Voter #" + (i+1));
-                    result_append("-- UUID: " + cast_vote.voter_uuid);
-                    result_append("-- Ballot Tracking Number: " + vote.get_hash());
-
-                    vote.verifyProofs(election.public_key, function(answer_num, choice_num, result, choice) {
-                        overall_result = overall_result && result;
-                        if (choice_num != null) {
-                            // keep track of tally
-                            tally[answer_num][choice_num] = choice.multiply(tally[answer_num][choice_num]);
-
-                            result_append("Question #" + (answer_num+1) + ", Option #" + (choice_num+1) + " -- " + pretty_result(result));                            
-                        } else {
-                            result_append("Question #" + (answer_num+1) + " OVERALL -- " + pretty_result(result));            
-                        }
-                    });
+            // load voter list
+            load_ballot_list(election_url, [], null, function(ballot_list) {
+                result_append("loaded voter list, now loading ballots for each..");
 
+                // load all ballots
+                load_ballots(election_url, ballot_list, [], function(ballots) {
                     result_append("");
-                });
-
-                // get the election result
-                $.get(election_url + "/result", function(result) {
-                    var results = $.secureEvalJSON(result);
-
-                    // get the trustees and proofs
-                    $.get(election_url + "/trustees/", function(trustees_json) {
-                       trustees = $.secureEvalJSON(trustees_json);
-
-                       // create the Helios objects
-                       trustees = $(trustees).map(function(i, trustee) {return HELIOS.Trustee.fromJSONObject(trustee)});
-
-                       // the public key that we'll check
-                       var combined_key = 1;
-
-                       result_append("<h3>Trustees</h3>");
-                       // verify the keys
-                       $(trustees).each(function(i, trustee) {
-                          result_append("Trustee #" + (i+1) + ": " + trustee.email);
-                          if (trustee.public_key.verifyKnowledgeOfSecretKey(trustee.pok, ElGamal.fiatshamir_dlog_challenge_generator)) {
-                              result_append("-- PK " + trustee.public_key_hash + " -- VERIFIED.");
+                    result_append("<h3>Ballots</h3>");
+                    // now load each ballot
+                    $(ballots).each(function(i, cast_vote){
+
+                        if (cast_vote.vote == null)
+                          return;
+
+                        var vote = HELIOS.EncryptedVote.fromJSONObject(cast_vote.vote, election);
+                        result_append("Voter #" + (i+1));
+                        result_append("-- UUID: " + cast_vote.voter_uuid);
+                        result_append("-- Ballot Tracking Number: " + vote.get_hash());
+
+                        vote.verifyProofs(election.public_key, function(answer_num, choice_num, result, choice) {
+                            overall_result = overall_result && result;
+                            if (choice_num != null) {
+                                // keep track of tally
+                                tally[answer_num][choice_num] = choice.multiply(tally[answer_num][choice_num]);
+
+                                result_append("Question #" + (answer_num+1) + ", Option #" + (choice_num+1) + " -- " + pretty_result(result));
+                            } else {
+                                result_append("Question #" + (answer_num+1) + " OVERALL -- " + pretty_result(result));
+                            }
+                        });
+
+                        result_append("");
+                    });
 
-                              // FIXME check the public key hash
-                          } else {
-                              result_append("==== ERROR for PK of trustee " + trustee.email);
-                              overall_result = false;
-                          }
+                    // get the election result
+                    $.get(election_url + "/result", function(result) {
+                        var results = $.secureEvalJSON(result);
 
-                          combined_key = trustee.public_key.multiply(combined_key);
+                        // get the trustees and proofs
+                        $.get(election_url + "/trustees/", function(trustees_json) {
+                           trustees = $.secureEvalJSON(trustees_json);
 
-                          result_append("");
-                       });
+                           // create the Helios objects
+                           trustees = $(trustees).map(function(i, trustee) {return HELIOS.Trustee.fromJSONObject(trustee)});
 
-                       // verify the combination of the keys into the final public key
-                       if (combined_key.equals(election.public_key)) {
-                           result_append("election public key CORRECTLY FORMED");
-                       } else {
-                           result_append("==== ERROR, election public key doesn't match");
-                           overall_result = false;
-                       }
+                           // the public key that we'll check
+                           var combined_key = 1;
 
-                       result_append("<h3>Tally</h3>");
+                           result_append("<h3>Trustees</h3>");
+                           // verify the keys
+                           $(trustees).each(function(i, trustee) {
+                              result_append("Trustee #" + (i+1) + ": " + trustee.email);
+                              if (trustee.public_key.verifyKnowledgeOfSecretKey(trustee.pok, ElGamal.fiatshamir_dlog_challenge_generator)) {
+                                  result_append("-- PK " + trustee.public_key_hash + " -- VERIFIED.");
 
-                       $(tally).each(function(q_num, q) {
-                           result_append("Question #" + (q_num+1) + ": " + election.questions[q_num].short_name);
-                           $(q).each(function(a_num, a) {
-                               var plaintext = new ElGamal.Plaintext(election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p), election.public_key);
+                                  // FIXME check the public key hash
+                              } else {
+                                  result_append("==== ERROR for PK of trustee " + trustee.email);
+                                  overall_result = false;
+                              }
 
-                               var check = true;
-                               result_append("Answer #" + (a_num + 1) + ": " + election.questions[q_num].answers[a_num] + " - COUNT = " + results[q_num][a_num]);
+                              combined_key = trustee.public_key.multiply(combined_key);
 
-                               var decryption_factors = [];
+                              result_append("");
+                           });
 
-                               // go through the trustees' decryption factors and verify each one
-                               $(trustees).each(function(t_num, trustee) {
-                                   if (trustee.public_key.verifyDecryptionFactor(a, trustee.decryption_factors[q_num][a_num],
-                                                   trustee.decryption_proofs[q_num][a_num], ElGamal.fiatshamir_challenge_generator)) {
-                                       result_append("-- Trustee " + trustee.email + ": decryption factor verifies");
+                           // verify the combination of the keys into the final public key
+                           if (combined_key.equals(election.public_key)) {
+                               result_append("election public key CORRECTLY FORMED");
+                           } else {
+                               result_append("==== ERROR, election public key doesn't match");
+                               overall_result = false;
+                           }
+
+                           result_append("<h3>Tally</h3>");
+
+                           $(tally).each(function(q_num, q) {
+                               result_append("Question #" + (q_num+1) + ": " + election.questions[q_num].short_name);
+                               $(q).each(function(a_num, a) {
+                                   var plaintext = new ElGamal.Plaintext(election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p), election.public_key);
+
+                                   var check = true;
+                                   result_append("Answer #" + (a_num + 1) + ": " + election.questions[q_num].answers[a_num] + " - COUNT = " + results[q_num][a_num]);
+
+                                   var decryption_factors = [];
+
+                                   // go through the trustees' decryption factors and verify each one
+                                   $(trustees).each(function(t_num, trustee) {
+                                       if (trustee.public_key.verifyDecryptionFactor(a, trustee.decryption_factors[q_num][a_num],
+                                                       trustee.decryption_proofs[q_num][a_num], ElGamal.fiatshamir_challenge_generator)) {
+                                           result_append("-- Trustee " + trustee.email + ": decryption factor verifies");
+                                       } else {
+                                           result_append("==== ERROR with Trustee " + trustee.email + ": decryption factor does not verify");
+                                           check= false;
+                                           overall_result = false;
+                                       }
+
+                                       decryption_factors.push(trustee.decryption_factors[q_num][a_num]);
+                                   });
+
+                                   // recheck decryption factors
+                                   var expected_value = election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p);                        
+                                   var recomputed_value = a.decrypt(decryption_factors).getM();
+                                   if (expected_value.equals(recomputed_value)) {
                                    } else {
-                                       result_append("==== ERROR with Trustee " + trustee.email + ": decryption factor does not verify");
-                                       check= false;
+                                       check = false;
                                        overall_result = false;
                                    }
 
-                                   decryption_factors.push(trustee.decryption_factors[q_num][a_num]);
+                                   result_append("-" + pretty_result(check));
                                });
 
-                               // recheck decryption factors
-                               var expected_value = election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p);                        
-                               var recomputed_value = a.decrypt(decryption_factors).getM();
-                               if (expected_value.equals(recomputed_value)) {
-                               } else {
-                                   check = false;
-                                   overall_result = false;
-                               }
-
-                               result_append("-" + pretty_result(check));
                            });
 
-                       });
+                           result_append("<h3>FINAL RESULT</h3>");
 
-                       result_append("<h3>FINAL RESULT</h3>");
-        
-                       if (overall_result) {
-                         result_append("ELECTION FULLY VERIFIED -- SUCCESS!");
-                       } else {
-                         result_append("VERIFICATION FAILED");
-                       }
+                           if (overall_result) {
+                             result_append("ELECTION FULLY VERIFIED -- SUCCESS!");
+                           } else {
+                             result_append("VERIFICATION FAILED");
+                           }
+                        });
                     });
-                });                
+                });
+
             });
-            
-        });
+    } catch (error) {
+        result_append("<p>It appears that you are trying to verify a private election.</p>");
+        result_append('<p>You can log in as a valid voter or log in as the election admin.</p>');
+        result_append('<a class="btn" href="' + election_url + '">Log in as a valid voter </a>');
+        result_append('<a class="btn" href="/auth/?return_url=/verifier/verify.html?election_url=' + election_url + '">Log in as the election admin</a>');
+    }
     });
         
 }
diff --git a/requirements.txt b/requirements.txt
index 77a8496dd0eb5685e812314d06a41878a656212e..237e75c98c2c3ecb7659d3d44a4c079030515fbc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-Django==1.8.18
+Django==1.8.19
 anyjson==0.3.3
 celery==3.1.18
 django-celery==3.1.16
@@ -10,8 +10,8 @@ pyparsing==1.5.7
 python-dateutil>=1.5
 python-openid==2.2.5
 wsgiref==0.1.2
-gunicorn==19.3
-requests==2.7.0
+gunicorn==19.9
+requests==2.21.0
 unicodecsv==0.9.0
 dj_database_url==0.3.0
 django-sslify==0.2.7
diff --git a/reset.sh b/reset.sh
index f7ad853220fcc371459bc86beeff2efdbac7122d..52141e13601899518420a971142f26d30f772ab2 100755
--- a/reset.sh
+++ b/reset.sh
@@ -1,4 +1,5 @@
 #!/bin/bash
+set -e  # Exit immediately if a command exits with a non-zero status.
 dropdb helios
 createdb helios
 python manage.py syncdb
diff --git a/runtime.txt b/runtime.txt
index 2b55d15ca0c808349643460bb82abb17df496d02..f27f1cc5ca4d2e750411e5ee682f6eb6baa674c2 100644
--- a/runtime.txt
+++ b/runtime.txt
@@ -1 +1 @@
-python-2.7.10
+python-2.7.15
diff --git a/server_ui/glue.py b/server_ui/glue.py
index bcb0995e16e53fe7d95facd436dca11cbcb7b6ee..9b3b5b37ba54c7de399c820d274e131374029184 100644
--- a/server_ui/glue.py
+++ b/server_ui/glue.py
@@ -5,6 +5,7 @@ Glue some events together
 from django.conf import settings
 from django.core.urlresolvers import reverse
 from django.conf import settings
+from helios.view_utils import render_template_raw
 import helios.views, helios.signals
 
 import views
@@ -12,30 +13,18 @@ import views
 def vote_cast_send_message(user, voter, election, cast_vote, **kwargs):
   ## FIXME: this doesn't work for voters that are not also users
   # prepare the message
-  subject = "%s - vote cast" % election.name
+  subject_template = 'email/cast_vote_subject.txt'
+  body_template = 'email/cast_vote_body.txt'
   
-  body = """
-You have successfully cast a vote in
-
-  %s
-  
-Your ballot is archived at:
-
-  %s
-""" % (election.name, helios.views.get_castvote_url(cast_vote))
-  
-  if election.use_voter_aliases:
-    body += """
-
-This election uses voter aliases to protect your privacy.
-Your voter alias is : %s    
-""" % voter.alias
-
-  body += """
-
---
-%s
-""" % settings.SITE_TITLE  
+  extra_vars = {
+    'election' : election,
+    'voter': voter,
+    'cast_vote': cast_vote,
+    'cast_vote_url': helios.views.get_castvote_url(cast_vote),
+    'custom_subject' : "%s - vote cast" % election.name
+  }
+  subject = render_template_raw(None, subject_template, extra_vars)
+  body = render_template_raw(None, body_template, extra_vars)
   
   # send it via the notification system associated with the auth system
   user.send_message(subject, body)
diff --git a/server_ui/media/boothcss/forms.css b/server_ui/media/boothcss/forms.css
index ee1a141a51660875e8ca2ee388e6d0baa1633b12..ae4cbb7fcbf9e2208ab08c6585b68c7a5144fbe2 100644
--- a/server_ui/media/boothcss/forms.css
+++ b/server_ui/media/boothcss/forms.css
@@ -7,7 +7,7 @@ form.prettyform label,input,textarea,select {
     line-height: 1.8;
 }
 
-form.prettyform label {
+form.prettyform label:not(.answer) {
     display: block;
     text-align: right;
     float: left;
diff --git a/server_ui/templates/email/cast_vote_body.txt b/server_ui/templates/email/cast_vote_body.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e248c36c1e105f47610ba8ea6774865badce1d19
--- /dev/null
+++ b/server_ui/templates/email/cast_vote_body.txt
@@ -0,0 +1,14 @@
+Dear {{voter.name}},
+
+You have successfully cast a vote in {{election.name}}.
+
+Your ballot is archived at: {{cast_vote_url}}
+
+{% if election.use_voter_aliases %}
+This election uses voter aliases to protect your privacy.
+Your voter alias is: {{voter.alias}}.
+{% endif %}
+
+--
+
+Helios
diff --git a/server_ui/templates/email/cast_vote_subject.txt b/server_ui/templates/email/cast_vote_subject.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a7929f12b2c11c9525bb772d319c7dafb0cf98f8
--- /dev/null
+++ b/server_ui/templates/email/cast_vote_subject.txt
@@ -0,0 +1 @@
+{{custom_subject|safe}}
diff --git a/server_ui/templates/index.html b/server_ui/templates/index.html
index 2b0a0e2c44ebf541b8fbcdc1309b9d526148e852..e31ac19e9c3fa5bc425c623c5e5fe6d7b946cc38 100644
--- a/server_ui/templates/index.html
+++ b/server_ui/templates/index.html
@@ -24,7 +24,7 @@ Helios elections are:
 </ul>
 
 <p>
-More than <b>100,000 votes</b> have been cast using Helios.
+More than <b>2,000,000 votes</b> have been cast using Helios.
 </p>
 
 {% if create_p %}
diff --git a/server_ui/urls.py b/server_ui/urls.py
index d71e04dcac68008d2001ca99c7df7f055bd6404a..0711bd7c15047d880c8d3d18926aceea00e338b4 100644
--- a/server_ui/urls.py
+++ b/server_ui/urls.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
-from django.conf.urls import *
+from django.conf.urls import patterns
 
-from views import *
+from views import home, about, docs, faq, privacy
 
 urlpatterns = patterns('',
   (r'^$', home),