diff --git a/helios/election_urls.py b/helios/election_urls.py
index 4b7687623f61d1c72147217ec32ed668f2d6b4dd..1b9b6aae872ff15effe6a9151c47db669c441850 100644
--- a/helios/election_urls.py
+++ b/helios/election_urls.py
@@ -60,6 +60,7 @@ urlpatterns = patterns('',
     # casting a ballot before we know who the voter is
     (r'^/cast$', one_election_cast),
     (r'^/cast_confirm$', one_election_cast_confirm),
+    (r'^/password_voter_login$', password_voter_login),
     (r'^/cast_done$', one_election_cast_done),
     
     # post audited ballot
diff --git a/helios/forms.py b/helios/forms.py
index c46c9af4fe252d6dc54a8926cb04766b068f347f..83f05d8aa016ac0db2d633e7a077620dd1e9cad9 100644
--- a/helios/forms.py
+++ b/helios/forms.py
@@ -34,3 +34,8 @@ class TallyNotificationEmailForm(forms.Form):
   subject = forms.CharField(max_length=80)
   body = forms.CharField(max_length=2000, widget=forms.Textarea)
   send_to = forms.ChoiceField(label="Send To", choices= [('all', 'all voters'), ('voted', 'only voters who cast a ballot')])
+
+class VoterPasswordForm(forms.Form):
+  voter_id = forms.CharField(max_length=50)
+  password = forms.CharField(widget=forms.PasswordInput(), max_length=100)
+
diff --git a/helios/models.py b/helios/models.py
index d34bdf569d5c1f0bbcc9e15ac97ee5550449c845..673dae28a81ac3ac13d4f7a46aec1dc9aaaf667a 100644
--- a/helios/models.py
+++ b/helios/models.py
@@ -669,6 +669,14 @@ class Voter(models.Model, electionalgs.Voter):
     else:
       return 'password'
   
+  @property
+  def display_html_big(self):
+    if self.user:
+      return self.user.display_html_big
+    else:
+      return """<img border="0" height="25" src="/static/auth/login-icons/password.png" alt="password" /> %s""" % self.name
+      
+
   def store_vote(self, cast_vote):
     # only store the vote if it's cast later than the current one
     if self.cast_at and cast_vote.cast_at < self.cast_at:
diff --git a/helios/templates/_castconfirm_docast.html b/helios/templates/_castconfirm_docast.html
new file mode 100644
index 0000000000000000000000000000000000000000..18acbd0cc5d863e7c4a5f8754dca0b28f80e3d1c
--- /dev/null
+++ b/helios/templates/_castconfirm_docast.html
@@ -0,0 +1,30 @@
+<div id="cast_form">
+<form method="post" action="" onsubmit="show_waiting()">
+    <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+    
+{% if status_update_label %}
+<div class="round" style="background: #eee; padding: 10px; border: 1px dashed #888;">
+<input type="checkbox" name="status_update" value="1" checked /> {{status_update_label}}<br />
+<blockquote style="font-size: 1.3em;">
+"{{status_update_message}}"
+</blockquote>
+<input type="hidden" name="status_update_message" value="{{status_update_message}}" />
+</div>
+<br />
+{% endif %}
+
+    <button type="submit" style="font-size: 1.5em; height: 50px;">I am <u>{{voter.display_html_big|safe}}</u>, cast this ballot</button>
+    <span style="font-size:0.8em;"><br />You can cast as many ballots as you want.<br />Only the last one counts.</span>
+</form>
+
+<p>
+    <button style="font-size: 1.5em;" onclick="document.location='./view';">cancel</button><br />
+    <span style="font-size:0.8em;">If you cancel now, your ballot will <em>NOT</em> be recorded.<br />
+    You can start the voting process over again, of course.</span>
+</p>
+
+</div>
+<div id="waiting_div">
+    Verifying and Casting your ballot<br />
+    <img src="/static/helios/loading.gif" />
+</div>
diff --git a/helios/templates/_castconfirm_password.html b/helios/templates/_castconfirm_password.html
new file mode 100644
index 0000000000000000000000000000000000000000..9e17c26ccf2938b90871a3114a2cddd9c856da44
--- /dev/null
+++ b/helios/templates/_castconfirm_password.html
@@ -0,0 +1,8 @@
+Please provide the username and password you received by email.<br /><br />
+<form method="post" action="{% url helios.views.password_voter_login election.uuid %}">
+<input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+<table>
+    {{password_login_form.as_table}}
+</table>
+<input type="submit" value="check credentials" />
+</form>
diff --git a/helios/templates/election_cast_confirm.html b/helios/templates/election_cast_confirm.html
index fcadde7338608e7486a0358c6039936aeb6f7d44..a16a786b8f2267f7e129f83a84549d5f30e8e303 100644
--- a/helios/templates/election_cast_confirm.html
+++ b/helios/templates/election_cast_confirm.html
@@ -11,6 +11,28 @@ function show_waiting() {
   $('#cast_form').hide();
   $('#waiting_div').show();
 }
+
+// FIXME: set this to false once it's clear how to set it back to true
+// so that it's not easy to forget to cast a ballot
+var ready_to_unload = true;
+
+window.onbeforeunload = function(evt) {
+  if (ready_to_unload)
+    return;
+
+  if (typeof evt == 'undefined') {
+    evt = window.event;
+  }
+  
+  var message = "You have not yet cast your ballot! Make sure to complete the voting process if you want your vote to count.";
+
+  if (evt) {
+    evt.returnValue = message;
+  }
+  
+  return message;
+};
+
 </script>
 <h1>{{election.name}} &mdash; Submit your Vote</h1>
 
@@ -20,65 +42,33 @@ Your smart ballot tracker is:<br /><br />
     <tt style="font-size:1.8em; font-weight: bold; padding-left: 20px;">  {{vote_fingerprint}}</tt>
 </p>
 
-{% if user %}
-
+{% if password_only %}
 {% if voter %}
-{% if past_votes %}
-<!--
-<h3>Past Votes Cast for this Election</h3>
-<ul>
-{% for vote in past_votes %}
-<li> <tt>{{vote.vote_hash}}</tt></li>
-{% endfor %}
-</ul>
--->
+{% include "_castconfirm_docast.html" %}
 {% else %}
-<!--<em>no vote cast yet</em>-->
+{% include "_castconfirm_password.html" %}
 {% endif %}
+{% else %}
 
-{% if election.voting_has_started %}
-{% if not election.voting_has_stopped %}
-<br />
-<div id="cast_form">
-<form method="post" action="" onsubmit="show_waiting()">
-    <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
-    
-{% if status_update_label %}
-<div class="round" style="background: #eee; padding: 10px; border: 1px dashed #888;">
-<input type="checkbox" name="status_update" value="1" checked /> {{status_update_label}}<br />
-<blockquote style="font-size: 1.3em;">
-"{{status_update_message}}"
-</blockquote>
-<input type="hidden" name="status_update_message" value="{{status_update_message}}" />
-</div>
-<br />
-{% endif %}
 
-    <button type="submit" style="font-size: 1.5em; height: 50px;">I am <u>{{user.display_html_big|safe}}</u>, cast this ballot</button>
-    <span style="font-size:0.8em;"><br />You can cast as many ballots as you want.<br />Only the last one counts.</span>
-</form>
+{% if user %}
 
-<p>
-    <button style="font-size: 1.5em;" onclick="document.location='./view';">cancel</button><br />
-    <span style="font-size:0.8em;">If you cancel now, your ballot will <em>NOT</em> be recorded.<br />
-    You can start the voting process over again, of course.</span>
-</p>
+{% if voter %}
 
-</div>
-<div id="waiting_div">
-    Verifying and Casting your ballot<br />
-    <img src="/static/helios/loading.gif" />
-</div>
-{% else %}
+{% if election.voting_has_started %}
+  {% if not election.voting_has_stopped %}
+    {% include "_castconfirm_docast.html" %}
+  {% else %}
 <p style="font-size:1.4em;">
   voting has stopped, sorry.
 </p>
-{% endif %}
+  {% endif %}
 {% else %}
 <p style="font-size:1.4em;">
   voting has not yet begun, sorry.
 </p>
 {% endif %}
+
 {% else %}
 <p>
 {% if election.openreg %}
@@ -96,31 +86,24 @@ Your smart ballot tracker is:<br /><br />
 <p>
   Now, we need you to log in, so we can verify your eligibility.<br /><br />
 {% if election.openreg %}
+
 {% if election.eligibility %}
-{% if password_only %}
-This election is open only to designated participants who received credentials via email.
-{% else %}
-{% endif %}
 {% else %}
-This election is open to <em>anyone</em>, so log in with your preferred account.
+ This election is open to <em>anyone</em>, so log in with your preferred account.
 {% endif %}
-{% else %}
-{% if password_only %}
-Please log in with the username and password you received by email.<br />
+
 {% else %}
 This election is only open to <em>registered voters</em>, so log in with
 the same account you registered with.
 {% endif %}
-{% endif %}
 </p>
 
 {{login_box|safe}}
-<!--
-<form method="get" action="{% url auth.views.index %}">
-    <input type="hidden" name="return_url" value="{% url helios.views.one_election_cast_confirm election.uuid %}">
-    <input type="submit" class="pretty" style="font-size: 1.6em;" value="Log In" />
-</form>-->
 <br />
 Don't worry, we'll remember your ballot while you log in.
 {% endif %}
+
+{# this closes the IF ELSE of this being password_only #}
+{% endif %}
+
 {% endblock %}
diff --git a/helios/views.py b/helios/views.py
index b0b490f835fe4ad684fb359461c24cdd2adc66ee..10698612f994a26adff98fa1f4e9ef2a2ddd5e2e 100644
--- a/helios/views.py
+++ b/helios/views.py
@@ -449,7 +449,24 @@ def one_election_cast(request, election):
   save_in_session_across_logouts(request, 'encrypted_vote', encrypted_vote)
 
   return HttpResponseRedirect("%s%s" % (settings.SECURE_URL_HOST, reverse(one_election_cast_confirm, args=[election.uuid])))
+
+@election_view(frozen=True)
+def password_voter_login(request, election):
+  """
+  This is used to log in as a voter for a particular election
+  """
+  password_login_form = forms.VoterPasswordForm(request.POST)
+  if password_login_form.is_valid():
+    try:
+      voter = election.voter_set.get(voter_login_id = password_login_form.cleaned_data['voter_id'],
+                                     voter_password = password_login_form.cleaned_data['password'])
+
+      request.session['CURRENT_VOTER'] = voter
+    except Voter.DoesNotExist:
+      pass
   
+  return HttpResponseRedirect(reverse(one_election_cast_confirm, args = [election.uuid]))
+
 @election_view(frozen=True)
 def one_election_cast_confirm(request, election):
   user = get_user(request)    
@@ -458,10 +475,15 @@ def one_election_cast_confirm(request, election):
   if not request.session.has_key('encrypted_vote'):
     return HttpResponseRedirect(settings.URL_HOST)
 
-  if user:
-    voter = Voter.get_by_election_and_user(election, user)
-  else:
-    voter = None
+  voter = None
+  if request.session.has_key('CURRENT_VOTER'):
+    voter = request.session['CURRENT_VOTER']
+    if voter.election != election:
+      voter = None
+
+  if not voter:
+    if user:
+      voter = Voter.get_by_election_and_user(election, user)
   
   # auto-register this person if the election is openreg
   if user and not voter and election.openreg:
@@ -518,8 +540,10 @@ def one_election_cast_confirm(request, election):
 
     if auth_systems == ['password']:
       password_only = True
+      password_login_form = forms.VoterPasswordForm()
     else:
       password_only = False
+      password_login_form = None
 
     return_url = reverse(one_election_cast_confirm, args=[election.uuid])
     login_box = auth_views.login_box_raw(request, return_url=return_url, auth_systems = auth_systems)
@@ -528,7 +552,7 @@ def one_election_cast_confirm(request, election):
         'login_box': login_box, 'election' : election, 'vote_fingerprint': vote_fingerprint,
         'past_votes': past_votes, 'issues': issues, 'voter' : voter,
         'status_update_label': status_update_label, 'status_update_message': status_update_message,
-        'password_only': password_only})
+        'password_only': password_only, 'password_login_form': password_login_form})
       
   if request.method == "POST":
     check_csrf(request)
diff --git a/server_ui/glue.py b/server_ui/glue.py
index 376087a34e266ebaaf3386e875d6b18626ae1010..7d87d63fb5bffd588edec250dc67a2f04480e2fb 100644
--- a/server_ui/glue.py
+++ b/server_ui/glue.py
@@ -10,6 +10,7 @@ import helios.views, helios.signals
 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