Basic Reddit authentication flow
This commit is contained in:
parent
bf1b8cee09
commit
c70dabcbe1
@ -50,6 +50,28 @@ if 'EOSWEB_SETTINGS' in os.environ:
|
|||||||
# Connect to database
|
# Connect to database
|
||||||
db_connect(app.config['DB_NAME'], app.config['MONGO_URI'])
|
db_connect(app.config['DB_NAME'], app.config['MONGO_URI'])
|
||||||
|
|
||||||
|
# Make Flask's serialisation, e.g. for sessions, EosObject aware
|
||||||
|
class EosObjectJSONEncoder(flask.json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
if isinstance(obj, EosObject):
|
||||||
|
return EosObject.serialise_and_wrap(obj)
|
||||||
|
return super().default(obj)
|
||||||
|
class EosObjectJSONDecoder(flask.json.JSONDecoder):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.super_object_hook = kwargs.get('object_hook', None)
|
||||||
|
kwargs['object_hook'] = self.my_object_hook
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def my_object_hook(self, val):
|
||||||
|
if 'type' in val:
|
||||||
|
if val['type'] in EosObject.objects:
|
||||||
|
return EosObject.deserialise_and_unwrap(val)
|
||||||
|
if self.super_object_hook:
|
||||||
|
return self.super_object_hook(val)
|
||||||
|
return val
|
||||||
|
app.json_encoder = EosObjectJSONEncoder
|
||||||
|
app.json_decoder = EosObjectJSONDecoder
|
||||||
|
|
||||||
@app.cli.command('test')
|
@app.cli.command('test')
|
||||||
@click.option('--prefix', default=None)
|
@click.option('--prefix', default=None)
|
||||||
@click.option('--lang', default=None)
|
@click.option('--lang', default=None)
|
||||||
@ -123,6 +145,8 @@ def count_test_election():
|
|||||||
def inject_globals():
|
def inject_globals():
|
||||||
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
|
return {'eos': eos, 'eosweb': eosweb, 'SHA256': eos.core.hashing.SHA256}
|
||||||
|
|
||||||
|
# === Views ===
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return flask.render_template('index.html')
|
return flask.render_template('index.html')
|
||||||
@ -197,6 +221,35 @@ def election_api_cast_vote(election):
|
|||||||
'vote': EosObject.serialise_and_wrap(vote)
|
'vote': EosObject.serialise_and_wrap(vote)
|
||||||
}), mimetype='application/json')
|
}), mimetype='application/json')
|
||||||
|
|
||||||
|
@app.route('/debug')
|
||||||
|
def debug():
|
||||||
|
assert False
|
||||||
|
|
||||||
|
@app.route('/auth/login')
|
||||||
|
def login():
|
||||||
|
return flask.render_template('auth/login.html')
|
||||||
|
|
||||||
|
@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('/')
|
||||||
|
|
||||||
|
@app.route('/auth/login_complete')
|
||||||
|
def login_complete():
|
||||||
|
return flask.render_template('auth/login_complete.html')
|
||||||
|
|
||||||
|
@app.route('/auth/login_cancelled')
|
||||||
|
def login_cancelled():
|
||||||
|
return flask.render_template('auth/login_cancelled.html')
|
||||||
|
|
||||||
|
# === Apps ===
|
||||||
|
|
||||||
|
for app_name in app.config['APPS']:
|
||||||
|
app_main = importlib.import_module(app_name + '.main')
|
||||||
|
app_main.main(app)
|
||||||
|
|
||||||
# === Model-Views ===
|
# === Model-Views ===
|
||||||
|
|
||||||
model_view_map = {}
|
model_view_map = {}
|
||||||
|
47
eosweb/core/templates/auth/login.html
Normal file
47
eosweb/core/templates/auth/login.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% extends '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 %}Login{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% 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>
|
||||||
|
{% else %}
|
||||||
|
<p>You are not currently logged in. Please select an option from the list below to log in.</p>
|
||||||
|
{% endif %}
|
||||||
|
<ul>
|
||||||
|
{% for auth_method in eosweb.app.config['AUTH_METHODS'] %}
|
||||||
|
<li><a href="/auth/{{ auth_method[0] }}/login" target="_blank" onclick="login(this);return false;">{{ auth_method[1] }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block basecontent %}
|
||||||
|
{{ super() }}
|
||||||
|
<script>
|
||||||
|
function login(el) {
|
||||||
|
window.open(el.getAttribute("href"), "eos_login_window", "width=400,height=600");
|
||||||
|
}
|
||||||
|
|
||||||
|
function callback_complete() {
|
||||||
|
window.location = "/";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
40
eosweb/core/templates/auth/login_complete.html
Normal file
40
eosweb/core/templates/auth/login_complete.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% 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 %}Login{% endblock %}
|
||||||
|
|
||||||
|
{% block basecontent %}
|
||||||
|
<div class="ui middle aligned center aligned grid" style="height: 100%;">
|
||||||
|
<div class="column" style="max-width: 400px;">
|
||||||
|
<div class="ui success message">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.setTimeout(function() {
|
||||||
|
window.close();
|
||||||
|
window.opener.callback_complete();
|
||||||
|
}, 1000);
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -26,7 +26,16 @@
|
|||||||
<div class="ui fixed inverted menu" style="margin-right: 1.5em;">
|
<div class="ui fixed inverted menu" style="margin-right: 1.5em;">
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<a href="/" class="header item">Eos Voting</a>
|
<a href="/" class="header item">Eos Voting</a>
|
||||||
<a href="#" class="item right">Log in</a>
|
{% if session.user %}
|
||||||
|
<div class="ui simple dropdown item right">
|
||||||
|
{{ session.user.username }} <i class="dropdown icon"></i>
|
||||||
|
<div class="menu">
|
||||||
|
<a href="{{ url_for('logout') }}?next={{ request.full_path|urlencode }}" class="item">Log out</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('login') }}" class="item right">Log in</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui main text container" id="main_container">
|
<div class="ui main text container" id="main_container">
|
||||||
|
0
eosweb/redditauth/__init__.py
Normal file
0
eosweb/redditauth/__init__.py
Normal file
68
eosweb/redditauth/main.py
Normal file
68
eosweb/redditauth/main.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
from flask_oauthlib.client import OAuth
|
||||||
|
|
||||||
|
import flask
|
||||||
|
|
||||||
|
from eos.core.objects import *
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class RedditUser(DocumentObject):
|
||||||
|
oauth_token = StringField(is_protected=True)
|
||||||
|
username = StringField()
|
||||||
|
|
||||||
|
def main(app):
|
||||||
|
oauth = OAuth()
|
||||||
|
reddit = oauth.remote_app('Reddit',
|
||||||
|
request_token_url=None,
|
||||||
|
authorize_url='https://www.reddit.com/api/v1/authorize.compact',
|
||||||
|
request_token_params={'duration': 'temporary', 'scope': 'identity'},
|
||||||
|
access_token_url='https://www.reddit.com/api/v1/access_token',
|
||||||
|
access_token_method='POST',
|
||||||
|
access_token_headers={
|
||||||
|
'Authorization': 'Basic ' + base64.b64encode('{}:{}'.format(app.config['REDDIT_OAUTH_CLIENT_ID'], app.config['REDDIT_OAUTH_CLIENT_SECRET']).encode('ascii')).decode('ascii'),
|
||||||
|
'User-Agent': app.config['REDDIT_USER_AGENT']
|
||||||
|
},
|
||||||
|
consumer_key=app.config['REDDIT_OAUTH_CLIENT_ID'],
|
||||||
|
consumer_secret=app.config['REDDIT_OAUTH_CLIENT_SECRET']
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/auth/reddit/login')
|
||||||
|
def reddit_login():
|
||||||
|
return reddit.authorize(callback=app.config['BASE_URI'] + flask.url_for('reddit_oauth_authorized'), state=uuid.uuid4())
|
||||||
|
|
||||||
|
@reddit.tokengetter
|
||||||
|
def get_reddit_oauth_token():
|
||||||
|
return (flask.session.get('user').oauth_token, '')
|
||||||
|
|
||||||
|
@app.route('/auth/reddit/oauth_callback')
|
||||||
|
def reddit_oauth_authorized():
|
||||||
|
resp = reddit.authorized_response()
|
||||||
|
if resp is None:
|
||||||
|
# Request denied
|
||||||
|
return flask.redirect(flask.url_for('login_cancelled'))
|
||||||
|
|
||||||
|
user = RedditUser()
|
||||||
|
user.oauth_token = resp['access_token']
|
||||||
|
flask.session['user'] = user
|
||||||
|
|
||||||
|
me = reddit.get('https://oauth.reddit.com/api/v1/me')
|
||||||
|
user.username = me.data['name']
|
||||||
|
|
||||||
|
return flask.redirect(flask.url_for('login_complete'))
|
20
eosweb/redditauth/settings.py
Normal file
20
eosweb/redditauth/settings.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
REDDIT_OAUTH_CLIENT_ID = 'xxxxxxxxxxxxxx'
|
||||||
|
REDDIT_OAUTH_CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
|
|
||||||
|
REDDIT_USER_AGENT = 'FIXME'
|
Loading…
Reference in New Issue
Block a user