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 click
|
||||||
import flask
|
import flask
|
||||||
|
import flask_session
|
||||||
import timeago
|
import timeago
|
||||||
|
|
||||||
from eos.core.objects import *
|
from eos.core.objects import *
|
||||||
@ -62,6 +63,16 @@ if 'EOSWEB_SETTINGS' in os.environ:
|
|||||||
# Connect to database
|
# Connect to database
|
||||||
db_connect(app.config['DB_NAME'], app.config['DB_URI'], app.config['DB_TYPE'])
|
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
|
# Set configs
|
||||||
User.admins = app.config['ADMINS']
|
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))
|
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'])
|
@app.route('/election/<election_id>/cast_ballot', methods=['POST'])
|
||||||
@using_election
|
@using_election
|
||||||
def election_api_cast_vote(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
|
# Voting is not yet open or has closed
|
||||||
return flask.Response('Voting is not yet open or has closed', 409)
|
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:
|
if 'user' not in flask.session:
|
||||||
# User is not authenticated
|
# User is not authenticated
|
||||||
@ -336,6 +353,8 @@ def election_api_cast_vote(election):
|
|||||||
|
|
||||||
vote.save()
|
vote.save()
|
||||||
|
|
||||||
|
del flask.session['staged_ballot']
|
||||||
|
|
||||||
return flask.Response(json.dumps({
|
return flask.Response(json.dumps({
|
||||||
'voter': EosObject.serialise_and_wrap(voter, None, SerialiseOptions(should_protect=True)),
|
'voter': EosObject.serialise_and_wrap(voter, None, SerialiseOptions(should_protect=True)),
|
||||||
'vote': EosObject.serialise_and_wrap(vote, None, SerialiseOptions(should_protect=True))
|
'vote': EosObject.serialise_and_wrap(vote, None, SerialiseOptions(should_protect=True))
|
||||||
@ -365,13 +384,28 @@ def debug():
|
|||||||
|
|
||||||
@app.route('/auth/login')
|
@app.route('/auth/login')
|
||||||
def login():
|
def login():
|
||||||
|
flask.session['login_next'] = flask.request.referrer
|
||||||
return flask.render_template('auth/login.html')
|
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')
|
@app.route('/auth/logout')
|
||||||
def logout():
|
def logout():
|
||||||
flask.session['user'] = None
|
flask.session['user'] = None
|
||||||
#return flask.redirect(flask.request.args['next'] if 'next' in flask.request.args else '/')
|
if flask.request.referrer:
|
||||||
# I feel like there's some kind of exploit here, so we'll leave this for now
|
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('/')
|
return flask.redirect('/')
|
||||||
|
|
||||||
@app.route('/auth/login_complete')
|
@app.route('/auth/login_complete')
|
||||||
|
@ -59,8 +59,13 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttons %}
|
{% block buttons %}
|
||||||
<button class="ui left floated button" onclick="prevTemplate(2);">Back</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>
|
<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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after %}
|
{% block after %}
|
||||||
@ -75,30 +80,90 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function login(el) {
|
function login(el) {
|
||||||
|
// 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");
|
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function callback_complete(username) {
|
function callback_complete(username) {
|
||||||
$("#cast_button").show();
|
$("#cast_button").show();
|
||||||
$("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
|
$("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
|
||||||
|
// Ballot was staged when we clicked the login button
|
||||||
castBallot();
|
castBallot();
|
||||||
return true;
|
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() {
|
function castBallot() {
|
||||||
$("#cast_prompt").hide();
|
$("#cast_prompt").hide();
|
||||||
$("#casting").show();
|
$("#casting").show();
|
||||||
|
|
||||||
// Prepare ballot
|
|
||||||
booth.ballot = booth.ballot.deaudit();
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "{{ election_base_url }}cast_ballot",
|
url: "{{ election_base_url }}cast_ballot",
|
||||||
type: "POST",
|
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"
|
dataType: "text"
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
@ -134,6 +199,10 @@
|
|||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% if is_cast %}
|
||||||
|
castBallot();
|
||||||
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% 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>
|
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block basecontent %}
|
{% block basecontent %}
|
||||||
@ -40,8 +46,8 @@
|
|||||||
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||||
}
|
}
|
||||||
|
|
||||||
function callback_complete(name) {
|
function callback_complete() {
|
||||||
window.location = "/";
|
window.location = "{{ url_for('login_callback') }}";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -23,33 +23,46 @@
|
|||||||
{% block basecontent %}
|
{% block basecontent %}
|
||||||
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
||||||
<div class="column" style="max-width: 400px;">
|
<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>
|
<div class="header">Log in successful</div>
|
||||||
<p>You have successfully logged in to your account.</p>
|
<p>You have successfully logged in to your account.</p>
|
||||||
<p>You may now close this window and return to your previous page.</p>
|
<p>You may now close this window and return to your previous page.</p>
|
||||||
</div>
|
</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="ui hidden error message">
|
||||||
<div class="header">Error logging in</div>
|
<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>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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
if (window.opener && window.opener.callback_complete) {
|
||||||
|
// Normal popup window
|
||||||
|
// Redirect happens in the opener
|
||||||
try {
|
try {
|
||||||
var result = window.opener.callback_complete("{{ session.user.name }}");
|
var result = window.opener.callback_complete("{{ session.user.name }}");
|
||||||
if (result) {
|
if (result) {
|
||||||
$(".success.message").removeClass("hidden");
|
$("#success-popup").removeClass("hidden");
|
||||||
|
setTimeout(window.close, 1000);
|
||||||
window.setTimeout(function() {
|
|
||||||
window.close();
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
} else {
|
||||||
$(".error.message").removeClass("hidden");
|
$(".error.message").removeClass("hidden");
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
$(".error.message").removeClass("hidden");
|
$(".error.message").removeClass("hidden");
|
||||||
|
console.error(ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This browser does not support popups
|
||||||
|
// Manually redirect in this window
|
||||||
|
$("#success-redirect").removeClass("hidden");
|
||||||
|
window.location = "{{ url_for('login_callback') }}";
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -192,12 +192,15 @@
|
|||||||
// === BOOTH TASKS ===
|
// === BOOTH TASKS ===
|
||||||
// TODO: Make modular
|
// TODO: Make modular
|
||||||
|
|
||||||
|
templates['booth/base.html'] = null;
|
||||||
|
|
||||||
|
if (location.search.indexOf('?cast') < 0) {
|
||||||
|
// Normal booth
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/welcome.html');
|
showTemplate('booth/welcome.html');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/base.html'] = null;
|
|
||||||
templates['booth/welcome.html'] = null;
|
templates['booth/welcome.html'] = null;
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
@ -252,7 +255,25 @@
|
|||||||
templates['booth/audit.html'] = null;
|
templates['booth/audit.html'] = null;
|
||||||
boothTasks.append({
|
boothTasks.append({
|
||||||
activate: function(fromLeft) {
|
activate: function(fromLeft) {
|
||||||
showTemplate('booth/cast.html', {ballot: booth.ballot});
|
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 {
|
||||||
|
// 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/cast.html', {ballot: booth.ballot, is_cast: true});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templates['booth/cast.html'] = null;
|
templates['booth/cast.html'] = null;
|
||||||
|
@ -2,6 +2,7 @@ coverage==4.4.1
|
|||||||
Flask==0.12.2
|
Flask==0.12.2
|
||||||
Flask-Mail==0.9.1
|
Flask-Mail==0.9.1
|
||||||
Flask-OAuthlib==0.9.4
|
Flask-OAuthlib==0.9.4
|
||||||
|
Flask-Session==0.3.1
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
libsass==0.13.4
|
libsass==0.13.4
|
||||||
premailer==3.1.1
|
premailer==3.1.1
|
||||||
|
Loading…
Reference in New Issue
Block a user