Implement simple email authentication

This commit is contained in:
RunasSudo 2017-11-26 20:48:15 +11:00
parent f31f5347e2
commit 8485aadbe4
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
12 changed files with 151 additions and 21 deletions

View File

@ -33,10 +33,10 @@ for f in eos.js_tests; do
# Disable handling of special attributes # Disable handling of special attributes
perl -0777 -pi -e 's/var __specialattrib__ = function \(attrib\) \{/var __specialattrib__ = function (attrib) { return false;/g' eos/__javascript__/$f.js perl -0777 -pi -e 's/var __specialattrib__ = function \(attrib\) \{/var __specialattrib__ = function (attrib) { return false;/g' eos/__javascript__/$f.js
# Fix handling of properties # Fix handling of properties, Transcrypt bug #407
perl -077 -pi -e 's/var __get__ = function \(self, func, quotedFuncName\) \{/var __get__ = function (self, func, quotedFuncName) { if(typeof(func) != "function"){return func;}/g' eos/__javascript__/$f.js perl -077 -pi -e 's/var __get__ = function \(self, func, quotedFuncName\) \{/var __get__ = function (self, func, quotedFuncName) { if(typeof(func) != "function"){return func;}/g' eos/__javascript__/$f.js
# Transcrypt bug # Transcrypt bug #407
perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__impl__(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__impl__(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js
perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__implpy_(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js perl -0777 -pi -e 's/property.call \((.*?), \g1.\g1.__implpy_(.*?)\)/property.call ($1, $1.__impl__$2)/g' eos/__javascript__/$f.js
done done

View File

@ -46,6 +46,37 @@ class Voter(EmbeddedObject):
class User(EmbeddedObject): class User(EmbeddedObject):
pass pass
def generate_password():
if is_python:
#__pragma__('skip')
import random
return ''.join(random.SystemRandom().choices('23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', k=12))
#__pragma__('noskip')
else:
return None
class EmailUser(User):
name = StringField()
email = StringField(is_protected=True)
password = StringField(is_protected=True, default=generate_password)
def matched_by(self, other):
if not isinstance(other, EmailUser):
return False
return self.email == other.email and self.password == other.password
def send_email(self, host, port, username, password, from_email, content):
#__pragma__('skip')
import smtplib
#__pragma__('noskip')
with smtplib.SMTP(host, port) as smtp:
if username is not None:
smtp.login(username, password)
smtp.sendmail(from_email, [self.email], content)
def email_password(self, host, port, username, password, from_email):
self.send_email(host, port, username, password, from_email, 'Subject: Registered to vote in {1}\nFrom: {4}\nTo: {2}\n\nDear {0},\n\nYou are registered to vote in the election {1}. Your log in details are as follows:\n\nEmail: {2}\nPassword: {3}'.format(self.name, self.recurse_parents(Election).name, self.email, self.password, from_email))
class UserVoter(Voter): class UserVoter(Voter):
user = EmbeddedObjectField() user = EmbeddedObjectField()

View File

@ -93,8 +93,16 @@ def setup_test_election():
election.name = 'Test Election' election.name = 'Test Election'
from eos.redditauth.election import RedditUser from eos.redditauth.election import RedditUser
election.voters.append(UserVoter(user=EmailUser(name='Alice', email='alice@localhost')))
election.voters.append(UserVoter(user=EmailUser(name='Bob', email='bob@localhost')))
election.voters.append(UserVoter(user=EmailUser(name='Carol', email='carol@localhost')))
election.voters.append(UserVoter(user=RedditUser(username='RunasSudo'))) election.voters.append(UserVoter(user=RedditUser(username='RunasSudo')))
for voter in election.voters:
if isinstance(voter, UserVoter):
if isinstance(voter.user, EmailUser):
voter.user.email_password(app.config['SMTP_HOST'], app.config['SMTP_PORT'], app.config['SMTP_USER'], app.config['SMTP_PASS'], app.config['SMTP_FROM'])
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting')) election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting')) election.mixing_trustees.append(InternalMixingTrustee(name='Eos Voting'))
@ -247,6 +255,29 @@ def login_complete():
def login_cancelled(): def login_cancelled():
return flask.render_template('auth/login_cancelled.html') return flask.render_template('auth/login_cancelled.html')
@app.route('/auth/email/login')
def email_login():
return flask.render_template('auth/email/login.html')
@app.route('/auth/email/authenticate', methods=['POST'])
def email_authenticate():
user = None
for election in Election.get_all():
for voter in election.voters:
if isinstance(voter.user, EmailUser):
if voter.user.email == flask.request.form['email']:
if voter.user.password == flask.request.form['password']:
user = voter.user
break
if user is None:
return flask.render_template('auth/email/login.html', error='The email or password you entered was invalid. Please check your details and try again. If the issue persists, contact the election administrator.')
flask.session['user'] = user
return flask.redirect(flask.url_for('login_complete'))
# === Apps === # === Apps ===
for app_name in app.config['APPS']: for app_name in app.config['APPS']:

View File

@ -21,10 +21,16 @@ BASE_URI = 'http://localhost:5000'
MONGO_URI = 'mongodb://localhost:27017/' MONGO_URI = 'mongodb://localhost:27017/'
DB_NAME = 'eos' DB_NAME = 'eos'
SECRET_KEY = 'FIXME'
APPS = [ APPS = [
'eosweb.redditauth' 'eosweb.redditauth'
] ]
AUTH_METHODS = [] AUTH_METHODS = [
('email', 'Email')
]
SECRET_KEY = 'FIXME' SMTP_HOST, SMTP_PORT = 'localhost', 25
SMTP_USER, SMTP_PASS = None, None
SMTP_FROM = 'eos@localhost'

View File

@ -55,7 +55,7 @@
{% block buttons %} {% block buttons %}
<button class="ui left floated button" onclick="prevTemplate(2);">Back</a> <button class="ui left floated button" onclick="prevTemplate(2);">Back</a>
<button class="ui right floated primary{% if not username %} hidden{% endif %} button" id="cast_button" onclick="castBallot();">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>
{% endblock %} {% endblock %}
{% block after %} {% block after %}
@ -67,9 +67,9 @@
function login(el) { function login(el) {
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() { function callback_complete(username) {
$("#cast_button").removeClass("hidden"); $("#cast_button").show();
$("#booth_logged_in_as").text("You are currently logged in."); $("#booth_logged_in_as").text("You are currently logged in as " + username + ".");
castBallot(); castBallot();
} }

View File

@ -0,0 +1,54 @@
{% extends 'semantic_base.html' %}
{#
Eos - Verifiable elections
Copyright © 2017 RunasSudo (Yingtong Li)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% block title %}Log in{% endblock %}
{% block basecontent %}
<div class="ui middle aligned center aligned grid" style="height: 100%;">
<div class="column" style="max-width: 400px;">
<h2 class="ui teal image header">
<div class="content">
Log in to Eos
</div>
</h2>
<form class="ui large form" action="{{ url_for('email_authenticate') }}" method="post">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="email" placeholder="E-mail address">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" name="password" placeholder="Password">
</div>
</div>
<input type="submit" class="ui fluid large teal submit button" value="Log in">
</div>
{% if error %}
<div class="ui visible error message">{{ error }}</div>
{% endif %}
</form>
</div>
</div>
{% endblock %}

View File

@ -22,7 +22,7 @@
{% block content %} {% block content %}
{% if session.user %} {% if session.user %}
<p>You are currently logged in as {{ session.user.username }}. Please select an option from the list below to switch accounts.</p> <p>You are currently logged in as {{ session.user.name }}. Please select an option from the list below to switch accounts.</p>
{% else %} {% else %}
<p>You are not currently logged in. Please select an option from the list below to log in.</p> <p>You are not currently logged in. Please select an option from the list below to log in.</p>
{% endif %} {% endif %}
@ -40,7 +40,7 @@
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() { function callback_complete(name) {
window.location = "/"; window.location = "/";
} }
</script> </script>

View File

@ -34,7 +34,7 @@
<script> <script>
window.setTimeout(function() { window.setTimeout(function() {
window.close(); window.close();
window.opener.callback_complete(); window.opener.callback_complete("{{ session.user.name }}");
}, 1000); }, 1000);
</script> </script>
{% endblock %} {% endblock %}

View File

@ -29,7 +29,7 @@
<a href="https://github.com/RunasSudo/Eos" class="item">Source Code</a> <a href="https://github.com/RunasSudo/Eos" class="item">Source Code</a>
{% if session.user %} {% if session.user %}
<div class="ui simple dropdown item right"> <div class="ui simple dropdown item right">
{{ session.user.username }} <i class="dropdown icon"></i> {{ session.user.name }} <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
<a href="{{ url_for('logout') }}?next={{ request.full_path|urlencode }}" class="item">Log out</a> <a href="{{ url_for('logout') }}?next={{ request.full_path|urlencode }}" class="item">Log out</a>
</div> </div>

View File

@ -39,7 +39,7 @@
var currentBoothTask = 0; var currentBoothTask = 0;
var selection_model_view_map = {{ selection_model_view_map|safe }}; {# :rooWut: #} var selection_model_view_map = {{ selection_model_view_map|safe }}; {# :rooWut: #}
var username = {% if session.user %}"{{ session.user.username }}"{% else %}null{% endif %}; var username = {% if session.user %}"{{ session.user.name }}"{% else %}null{% endif %};
var auth_methods = {{ auth_methods|safe }}; var auth_methods = {{ auth_methods|safe }};
function resetBooth() { function resetBooth() {

View File

@ -1,12 +1,20 @@
ORG_NAME = 'Your Organisation Here' ORG_NAME = 'Your Organisation Here'
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxx' SECRET_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxx'
REDDIT_USER_AGENT = 'Application Title by /u/Your_Username'
AUTH_METHODS = [ AUTH_METHODS = [
('email', 'Email'),
('reddit', 'Reddit') ('reddit', 'Reddit')
] ]
# Email
SMTP_HOST, SMTP_PORT = 'localhost', 25
SMTP_USER, SMTP_PASS = None, None
SMTP_FROM = 'eos@localhost'
# Reddit
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
REDDIT_USER_AGENT = 'Application Title by /u/Your_Username'