Implement login fix for browsers without window.opener support #11
This commit is contained in:
parent
baeba08366
commit
ebe6530557
@ -16,6 +16,7 @@
|
||||
|
||||
import click
|
||||
import flask
|
||||
import flask_session
|
||||
import timeago
|
||||
|
||||
from eos.core.objects import *
|
||||
@ -62,6 +63,16 @@ if 'EOSWEB_SETTINGS' in os.environ:
|
||||
# Connect to database
|
||||
db_connect(app.config['DB_NAME'], app.config['DB_URI'], app.config['DB_TYPE'])
|
||||
|
||||
# Configure sessions
|
||||
if app.config['DB_TYPE'] == 'mongodb':
|
||||
app.config['SESSION_TYPE'] = 'mongodb'
|
||||
app.config['SESSION_MONGODB'] = dbinfo.provider.client
|
||||
app.config['SESSION_MONGODB_DB'] = dbinfo.provider.db_name
|
||||
elif app.config['DB_TYPE'] == 'postgresql':
|
||||
app.config['SESSION_TYPE'] = 'sqlalchemy'
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = dbinfo.provider.conn.dsn
|
||||
flask_session.Session(app)
|
||||
|
||||
# Set configs
|
||||
User.admins = app.config['ADMINS']
|
||||
|
||||
@ -298,6 +309,12 @@ def election_admin_schedule_task(election):
|
||||
|
||||
return flask.redirect(flask.url_for('election_admin_summary', election_id=election._id))
|
||||
|
||||
@app.route('/election/<election_id>/stage_ballot', methods=['POST'])
|
||||
@using_election
|
||||
def election_api_stage_ballot(election):
|
||||
flask.session['staged_ballot'] = json.loads(flask.request.data)
|
||||
return 'OK'
|
||||
|
||||
@app.route('/election/<election_id>/cast_ballot', methods=['POST'])
|
||||
@using_election
|
||||
def election_api_cast_vote(election):
|
||||
@ -305,7 +322,7 @@ def election_api_cast_vote(election):
|
||||
# Voting is not yet open or has closed
|
||||
return flask.Response('Voting is not yet open or has closed', 409)
|
||||
|
||||
data = json.loads(flask.request.data)
|
||||
data = flask.session['staged_ballot']
|
||||
|
||||
if 'user' not in flask.session:
|
||||
# User is not authenticated
|
||||
@ -336,6 +353,8 @@ def election_api_cast_vote(election):
|
||||
|
||||
vote.save()
|
||||
|
||||
del flask.session['staged_ballot']
|
||||
|
||||
return flask.Response(json.dumps({
|
||||
'voter': EosObject.serialise_and_wrap(voter, None, SerialiseOptions(should_protect=True)),
|
||||
'vote': EosObject.serialise_and_wrap(vote, None, SerialiseOptions(should_protect=True))
|
||||
@ -365,14 +384,29 @@ def debug():
|
||||
|
||||
@app.route('/auth/login')
|
||||
def login():
|
||||
flask.session['login_next'] = flask.request.referrer
|
||||
return flask.render_template('auth/login.html')
|
||||
|
||||
@app.route('/auth/stage_next', methods=['POST'])
|
||||
def auth_stage_next():
|
||||
flask.session['login_next'] = flask.request.data
|
||||
return 'OK'
|
||||
|
||||
@app.route('/auth/logout')
|
||||
def logout():
|
||||
flask.session['user'] = None
|
||||
#return flask.redirect(flask.request.args['next'] if 'next' in flask.request.args else '/')
|
||||
# I feel like there's some kind of exploit here, so we'll leave this for now
|
||||
return flask.redirect('/')
|
||||
if flask.request.referrer:
|
||||
return flask.redirect(flask.request.referrer)
|
||||
else:
|
||||
return flask.redirect('/')
|
||||
|
||||
@app.route('/auth/login_callback')
|
||||
def login_callback():
|
||||
print(flask.session)
|
||||
if 'login_next' in flask.session and flask.session['login_next']:
|
||||
return flask.redirect(flask.session['login_next'])
|
||||
else:
|
||||
return flask.redirect('/')
|
||||
|
||||
@app.route('/auth/login_complete')
|
||||
def login_complete():
|
||||
|
@ -59,8 +59,13 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block buttons %}
|
||||
<button class="ui left floated button" onclick="prevTemplate(2);">Back</button>
|
||||
<button class="ui right floated primary button" id="cast_button" onclick="castBallot();"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
||||
{% if is_cast %}
|
||||
<a class="ui left floated button" href="{{ election_base_url }}booth">Reset</a>
|
||||
<button class="ui right floated primary button" id="cast_button" onclick="castBallot();"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
||||
{% else %}
|
||||
<button class="ui left floated button" onclick="prevTemplate(2);">Back</button>
|
||||
<button class="ui right floated primary button" id="cast_button" onclick="stageBallot(castBallot);"{% if not username %} style="display: none;"{% endif %}>Cast ballot</button>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block after %}
|
||||
@ -75,30 +80,90 @@
|
||||
});
|
||||
|
||||
function login(el) {
|
||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||
// Stage the vote for casting in case we change page
|
||||
stageBallot(function(data) {
|
||||
// Stage the next page in case we change page
|
||||
stageNext("{{ election_base_url }}booth?cast", function(data) {
|
||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function callback_complete(username) {
|
||||
$("#cast_button").show();
|
||||
$("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
|
||||
// Ballot was staged when we clicked the login button
|
||||
castBallot();
|
||||
return true;
|
||||
}
|
||||
|
||||
function stageBallot(callback) {
|
||||
// Prepare ballot
|
||||
var deauditedBallot = booth.ballot.deaudit();
|
||||
|
||||
$.ajax({
|
||||
url: "{{ election_base_url }}stage_ballot",
|
||||
type: "POST",
|
||||
data: eosjs.eos.core.objects.__all__.EosObject.to_json({
|
||||
"ballot": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(deauditedBallot, null),
|
||||
"fingerprint": booth.fingerprint || null
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "text",
|
||||
async: false // so window.open happens in main thread
|
||||
})
|
||||
.done(function(data) {
|
||||
callback(data);
|
||||
})
|
||||
.fail(function(xhr, status, err) {
|
||||
if (xhr.responseText && xhr.responseText.length < 100) {
|
||||
$("#error_unknown_tech").text("Technical details: " + err + " – " + xhr.responseText);
|
||||
} else {
|
||||
$("#error_unknown_tech").text("Technical details: " + err);
|
||||
}
|
||||
$("#error_unknown").removeClass("hidden");
|
||||
|
||||
$("#error_invalid_id").addClass("hidden");
|
||||
|
||||
console.error(xhr);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function stageNext(url, callback) {
|
||||
$.ajax({
|
||||
url: "/auth/stage_next",
|
||||
type: "POST",
|
||||
data: url,
|
||||
contentType: "text/plain",
|
||||
dataType: "text",
|
||||
async: false // so window.open happens in main thread
|
||||
})
|
||||
.done(function(data) {
|
||||
callback(data);
|
||||
})
|
||||
.fail(function(xhr, status, err) {
|
||||
if (xhr.responseText && xhr.responseText.length < 100) {
|
||||
$("#error_unknown_tech").text("Technical details: " + err + " – " + xhr.responseText);
|
||||
} else {
|
||||
$("#error_unknown_tech").text("Technical details: " + err);
|
||||
}
|
||||
$("#error_unknown").removeClass("hidden");
|
||||
|
||||
$("#error_invalid_id").addClass("hidden");
|
||||
|
||||
console.error(xhr);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function castBallot() {
|
||||
$("#cast_prompt").hide();
|
||||
$("#casting").show();
|
||||
|
||||
// Prepare ballot
|
||||
booth.ballot = booth.ballot.deaudit();
|
||||
|
||||
$.ajax({
|
||||
url: "{{ election_base_url }}cast_ballot",
|
||||
type: "POST",
|
||||
data: eosjs.eos.core.objects.__all__.EosObject.to_json({
|
||||
"ballot": eosjs.eos.core.objects.__all__.EosObject.serialise_and_wrap(booth.ballot, null),
|
||||
"fingerprint": booth.fingerprint || null
|
||||
}),
|
||||
contentType: "application/json",
|
||||
dataType: "text"
|
||||
})
|
||||
.done(function(data) {
|
||||
@ -134,6 +199,10 @@
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
{% if is_cast %}
|
||||
castBallot();
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -31,6 +31,12 @@
|
||||
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="ui hidden error message">
|
||||
<div class="header">Error logging in</div>
|
||||
<p>Your log in details appear to be correct, however there was an unknown error while logging you in.</p>
|
||||
<p>Please try again. If the problem persists, contact your election administrator.</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block basecontent %}
|
||||
@ -40,8 +46,8 @@
|
||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||
}
|
||||
|
||||
function callback_complete(name) {
|
||||
window.location = "/";
|
||||
function callback_complete() {
|
||||
window.location = "{{ url_for('login_callback') }}";
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
@ -23,33 +23,46 @@
|
||||
{% block basecontent %}
|
||||
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
||||
<div class="column" style="max-width: 400px;">
|
||||
<div class="ui hidden success message">
|
||||
<div class="ui hidden success message" id="success-popup">
|
||||
<div class="header">Log in successful</div>
|
||||
<p>You have successfully logged in to your account.</p>
|
||||
<p>You may now close this window and return to your previous page.</p>
|
||||
</div>
|
||||
<div class="ui hidden success message" id="success-redirect">
|
||||
<div class="header">Log in successful</div>
|
||||
<p>You have successfully logged in to your account.</p>
|
||||
<p>Please wait, you should be returned to your previous page momentarily.</p>
|
||||
</div>
|
||||
<div class="ui hidden error message">
|
||||
<div class="header">Error logging in</div>
|
||||
<p>Your log in details appear to be correct, however there was an unknown error while logging you in.</p>
|
||||
<p>Please close this window try again. If the problem persists, contact your election administrator.</p>
|
||||
<p>Please close this window and try again. If the problem persists, contact your election administrator.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
try {
|
||||
var result = window.opener.callback_complete("{{ session.user.name }}");
|
||||
if (result) {
|
||||
$(".success.message").removeClass("hidden");
|
||||
|
||||
window.setTimeout(function() {
|
||||
window.close();
|
||||
}, 1000);
|
||||
} else {
|
||||
if (window.opener && window.opener.callback_complete) {
|
||||
// Normal popup window
|
||||
// Redirect happens in the opener
|
||||
try {
|
||||
var result = window.opener.callback_complete("{{ session.user.name }}");
|
||||
if (result) {
|
||||
$("#success-popup").removeClass("hidden");
|
||||
setTimeout(window.close, 1000);
|
||||
} else {
|
||||
$(".error.message").removeClass("hidden");
|
||||
}
|
||||
} catch (ex) {
|
||||
$(".error.message").removeClass("hidden");
|
||||
console.error(ex);
|
||||
throw ex;
|
||||
}
|
||||
} catch (ex) {
|
||||
$(".error.message").removeClass("hidden");
|
||||
} else {
|
||||
// This browser does not support popups
|
||||
// Manually redirect in this window
|
||||
$("#success-redirect").removeClass("hidden");
|
||||
window.location = "{{ url_for('login_callback') }}";
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -192,67 +192,88 @@
|
||||
// === BOOTH TASKS ===
|
||||
// TODO: Make modular
|
||||
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/welcome.html');
|
||||
}
|
||||
});
|
||||
templates['booth/base.html'] = null;
|
||||
templates['booth/welcome.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/selections.html');
|
||||
}
|
||||
});
|
||||
templates['booth/selections.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
if (fromLeft) {
|
||||
showTemplate('booth/encrypt.html');
|
||||
} else {
|
||||
prevTemplate();
|
||||
}
|
||||
}
|
||||
});
|
||||
templates['booth/encrypt.html'] = null;
|
||||
|
||||
if (location.search.indexOf('?prepoll') >= 0) {
|
||||
// Pre-poll
|
||||
if (location.search.indexOf('?cast') < 0) {
|
||||
// Normal booth
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/review_prepoll.html', {ballot: booth.ballot});
|
||||
showTemplate('booth/welcome.html');
|
||||
}
|
||||
});
|
||||
templates['booth/review_prepoll.html'] = null;
|
||||
templates['booth/welcome.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||
showTemplate('booth/selections.html');
|
||||
}
|
||||
});
|
||||
templates['booth/audit.html'] = null;
|
||||
templates['booth/selections.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
|
||||
if (fromLeft) {
|
||||
showTemplate('booth/encrypt.html');
|
||||
} else {
|
||||
prevTemplate();
|
||||
}
|
||||
}
|
||||
});
|
||||
templates['booth/cast_prepoll.html'] = null;
|
||||
templates['booth/encrypt.html'] = null;
|
||||
|
||||
if (location.search.indexOf('?prepoll') >= 0) {
|
||||
// Pre-poll
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/review_prepoll.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/review_prepoll.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/audit.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/cast_prepoll.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/cast_prepoll.html'] = null;
|
||||
} else {
|
||||
// Real voting booth
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/review.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/review.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/audit.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: false});
|
||||
}
|
||||
});
|
||||
templates['booth/cast.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/complete.html', {voter: booth.voter, vote: booth.vote});
|
||||
}
|
||||
});
|
||||
templates['booth/complete.html'] = null;
|
||||
}
|
||||
} else {
|
||||
// Real voting booth
|
||||
// Cast immediately
|
||||
{% if session.staged_ballot %}
|
||||
booth.ballot = eosjs.eos.core.objects.__all__.EosObject.deserialise_and_unwrap(eosjs.eos.core.objects.__all__.EosObject.from_json('{{ eos.core.objects.EosObject.to_json(session.staged_ballot.ballot)|safe }}'), null);
|
||||
{% endif %}
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/review.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/review.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/audit.html', {ballot: booth.ballot});
|
||||
}
|
||||
});
|
||||
templates['booth/audit.html'] = null;
|
||||
boothTasks.append({
|
||||
activate: function(fromLeft) {
|
||||
showTemplate('booth/cast.html', {ballot: booth.ballot});
|
||||
showTemplate('booth/cast.html', {ballot: booth.ballot, is_cast: true});
|
||||
}
|
||||
});
|
||||
templates['booth/cast.html'] = null;
|
||||
|
@ -2,6 +2,7 @@ coverage==4.4.1
|
||||
Flask==0.12.2
|
||||
Flask-Mail==0.9.1
|
||||
Flask-OAuthlib==0.9.4
|
||||
Flask-Session==0.3.1
|
||||
gunicorn==19.7.1
|
||||
libsass==0.13.4
|
||||
premailer==3.1.1
|
||||
|
Loading…
Reference in New Issue
Block a user