Compare commits

..

No commits in common. "02b5a846485f21b9c48600aa58df3a9c8dbe7160" and "4a3ab80a3585f3bbec1837f76e03bf79c06c2bec" have entirely different histories.

7 changed files with 106 additions and 181 deletions

View File

@ -41,7 +41,7 @@ def auth_required(func):
"FROM user_info LEFT JOIN user_session " "FROM user_info LEFT JOIN user_session "
"ON (user_info.id = user_session.user_id) " "ON (user_info.id = user_session.user_id) "
"WHERE user_info.id = $1 AND user_session.id = $2 " "WHERE user_info.id = $1 AND user_session.id = $2 "
"AND user_info.active = TRUE AND user_session.expires > NOW()", "AND user_session.expires > NOW()",
user_id, sid) user_id, sid)
if session: if session:
request['session'] = dict(session) request['session'] = dict(session)

View File

@ -16,7 +16,6 @@ import asyncpg
import uvloop import uvloop
import auth import auth
import forms
import tools import tools
import config import config
@ -24,7 +23,6 @@ uvloop.install()
routes = web.RouteTableDef() routes = web.RouteTableDef()
@routes.get(config.url_prefix + '/', name='index') @routes.get(config.url_prefix + '/', name='index')
@routes.post(config.url_prefix + '/', name='index')
@auth.auth_required @auth.auth_required
async def index(request): async def index(request):
"""The index page.""" """The index page."""
@ -57,26 +55,38 @@ async def index(request):
if index != -1: if index != -1:
users[user_perm['username']][apps[index]['name']] = True users[user_perm['username']][apps[index]['name']] = True
users_json = json.dumps(users) users_json = json.dumps(users)
if request.method == 'POST':
data = await request.post()
form = data.get('form_name')
forms_ = {
'invite_user': forms.invite_user,
'change_password': forms.change_password,
'delete_key': forms.delete_key,
'delele_session': forms.delete_session,
}
if not forms_[form]:
errors = defaultdict['main'].append("Unknown form id: {form}")
else:
errors = await forms_[form](request)
return render_template("index.html", request, locals()) return render_template("index.html", request, locals())
@routes.post(config.url_prefix + '/change_password', name='change_password')
@auth.auth_required
async def change_password(request):
"""Allows a user to change their password."""
data = await request.post()
current_pw = data.get('current_password')
new_pw = data.get('new_password')
verify_pw = data.get('verify_password')
if not all((current_pw, new_pw, verify_pw)):
return
if not new_pw == verify_pw:
return
async with request.app['pool'].acquire() as conn:
pw_hash = conn.fetchrow(
"SELECT password_hash FROM user_info WHERE id = $1",
request['session']['id'])
if not argon2.verify(current_pw, pw_hash['password_hash']):
return
h = argon2.hash(new_pw)
conn.execute(
"UPDATE user_info SET password_hash = $1 WHERE id = $2",
h, request['session']['id'])
index_url = request.app.router['index'].url_for()
raise web.HTTPFound(location=index_url)
@routes.get(config.url_prefix + '/login', name='login') @routes.get(config.url_prefix + '/login', name='login')
@routes.post(config.url_prefix + '/login', name='login') @routes.post(config.url_prefix + '/login', name='login')
async def login(request): async def login(request):
@ -327,6 +337,50 @@ async def set_session(request):
return web.json_response(error) return web.json_response(error)
@routes.post(config.url_prefix + '/delete_key', name='delete_key')
@auth.auth_required
async def delete_key(request):
"""Allows a user to delete a security key."""
data = await request.post()
async with request.app['pool'].acquire() as conn:
for key, value in data.items():
key_id = key.replace('fido-', '')
if not key_id:
continue
try:
key_id = int(key_id)
except ValueError:
continue
if value != 'on':
continue
await conn.execute(
"DELETE FROM user_credential "
"WHERE id = $1 AND user_id = $2",
key_id, request['session']['id'])
index_url = request.app.router['index'].url_for()
raise web.HTTPFound(location=index_url)
@routes.post(config.url_prefix + '/delete_session', name='delete_session')
@auth.auth_required
async def delete_session(request):
"""Allows a user to delete a session ."""
data = await request.post()
async with request.app['pool'].acquire() as conn:
for key, value in data.items():
session_id = key.replace('session-', '', 1)
if not session_id:
continue
if value != 'on':
continue
await conn.execute(
"DELETE FROM user_session "
"WHERE id = $1 AND user_id = $2",
session_id, request['session']['id'])
index_url = request.app.router['index'].url_for()
raise web.HTTPFound(location=index_url)
async def init_app(): async def init_app():
"""Initializes the application.""" """Initializes the application."""
app = web.Application() app = web.Application()

View File

@ -1,88 +0,0 @@
#!/usr/bin/env python3
"""
Co-routines for handling various forms in Buckler.
"""
import tools
async def invite_user(request):
"""Allows an admin to invite a new user."""
if not request['session']['admin']:
return {'main': "You do not have permission to do that."}
data = await request.post()
email = data.get('email')
if not email:
return {'invite_user': "This field is required."}
# TODO: validate email address
await tools.send_invite(request, email)
return {}
async def change_password(request):
"""Allows a user to change their password."""
errors = {}
data = await request.post()
current_pw = data.get('current_password')
new_pw = data.get('new_password')
verify_pw = data.get('verify_password')
if not all((current_pw, new_pw, verify_pw)):
errors['change_password'] = "All fields are required."
return errors
if not new_pw == verify_pw:
errors['change_password'] = "Passwords do not match."
return errors
async with request.app['pool'].acquire() as conn:
pw_hash = conn.fetchrow(
"SELECT password_hash FROM user_info WHERE id = $1",
request['session']['id'])
if not argon2.verify(current_pw, pw_hash['password_hash']):
errors['change_password'] = "Invalid password."
return errors
h = argon2.hash(new_pw)
conn.execute(
"UPDATE user_info SET password_hash = $1 WHERE id = $2",
h, request['session']['id'])
return errors
async def delete_key(request):
"""Allows a user to delete a security key."""
data = await request.post()
async with request.app['pool'].acquire() as conn:
for key, value in data.items():
key_id = key.replace('fido-', '')
if not key_id:
continue
try:
key_id = int(key_id)
except ValueError:
continue
if value != 'on':
continue
await conn.execute(
"DELETE FROM user_credential "
"WHERE id = $1 AND user_id = $2",
key_id, request['session']['id'])
return {}
async def delete_session(request):
"""Allows a user to delete a session ."""
data = await request.post()
async with request.app['pool'].acquire() as conn:
for key, value in data.items():
session_id = key.replace('session-', '', 1)
if not session_id:
continue
if value != 'on':
continue
await conn.execute(
"DELETE FROM user_session "
"WHERE id = $1 AND user_id = $2",
session_id, request['session']['id'])
return {}

View File

@ -65,16 +65,16 @@ td {
text-align: center; text-align: center;
} }
.no_borders { #change_password {
border: none; border: none;
border-collapse: separate; border-collapse: separate;
width: auto; width: auto;
} }
.no_borders tr { #change_password tr {
border: none; border: none;
} }
.no_borders td { #change_password td {
text-align: left; text-align: left;
} }

View File

@ -60,18 +60,6 @@
</table> </table>
</article> </article>
</section> </section>
<section class="sub_section">
<h3>Invite New User</h3>
<article style="display: none;">
<hr>
<form method="post" enctype="application/x-www-form-urlencoded">
<input name="form_name" type="hidden" value="invite_user">
<label for="new_user_email">Email</label>
<input id="new_user_email" name="email" type="email">
<p><input type="submit" value="Submit">
</form>
</article>
</section>
</article> </article>
</section> </section>
{% endif %} {% endif %}
@ -79,9 +67,8 @@
<h2>Change Password</h2> <h2>Change Password</h2>
<article style="display: none;"> <article style="display: none;">
<hr> <hr>
<form method="post" enctype="application/x-www-form-urlencoded"> <form action="{{ request.app.router['change_password'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded">
<input name="form_name" type="hidden" value="change_password"> <table id="change_password">
<table id="change_password" class="no_borders">
<tr> <tr>
<td><label for="current_password">Current password</label></td> <td><label for="current_password">Current password</label></td>
<td><input id="current_password" name="current_password" type="password"></td> <td><input id="current_password" name="current_password" type="password"></td>
@ -104,8 +91,7 @@
<article style="display: none;"> <article style="display: none;">
<hr> <hr>
{% if fido2_keys %} {% if fido2_keys %}
<form method="POST" enctype="application/x-www-form-urlencoded"> <form action="{{ request.app.router['delete_key'].url_for() }}" method="POST" enctype="application/x-www-form-urlencoded">
<input name="form_name" type="hidden" value="delete_key">
<table id="security_keys"> <table id="security_keys">
<thead> <thead>
<tr> <tr>
@ -134,8 +120,7 @@
<h2>Active Sessions</h2> <h2>Active Sessions</h2>
<article style="display: none;"> <article style="display: none;">
<hr> <hr>
<form method="POST" enctype="application/x-www-form-urlencoded"> <form action="{{ request.app.router['delete_session'].url_for() }}" method="POST" enctype="application/x-www-form-urlencoded">
<input name="form_name" type="hidden" value="delete_session">
<table id="active_sessions"> <table id="active_sessions">
<thead> <thead>
<tr> <tr>

View File

@ -14,60 +14,35 @@
<main> <main>
<section> <section>
<form method="POST" enctype="application/x-www-form-urlencoded"> <form method="POST" enctype="application/x-www-form-urlencoded">
<table class="no_borders"> <label for="username">Username</label>
<tr> <input id="username" name="username" type="text" minlength="3" maxlength="20"><br>
<td><label for="username">Username</label></td>
<td><input id="username" name="username" type="text" minlength="3" maxlength="20"></td>
</tr>
{% if errors['username'] %} {% if errors['username'] %}
<tr> <ul>
<td></td> {% for error in errors['username'] %}
<td> <li class="error">{{ error }}</li>
<ul> {% endfor %}
{% for error in errors['username'] %} </ul>
<li class="error">{{ error }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endif %} {% endif %}
<tr> <label for="email">Email</label>
<td><label for="email">Email</label></td> <input id="email" name="email" type="email"><br>
<td><input id="email" name="email" type="email"></td>
</tr>
{% if errors['email'] %} {% if errors['email'] %}
<tr> <ul>
<td></td> {% for error in errors['email'] %}
<td> <li class="error">{{ error }}</li>
<ul> {% endfor %}
{% for error in errors['email'] %} </ul>
<li class="error">{{ error }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endif %} {% endif %}
<tr> <label for="password">Password</label>
<td><label for="password">Password</label></td> <input id="password" name="password" type="password" minlength="8" maxlength="10240"><br>
<td><input id="password" name="password" type="password" minlength="8" maxlength="10240"></td> <label for="password_verify">Verify Password</label>
</tr> <input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"><br>
<tr>
<td><label for="password_verify">Verify Password</label></td>
<td><input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"></td>
</tr>
{% if errors['password'] %} {% if errors['password'] %}
<tr> <ul>
<td></td> {% for error in errors['password'] %}
<td> <li class="error">{{ error }}</li>
<ul> {% endfor %}
{% for error in errors['password'] %} </ul>
<li class="error">{{ error }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endif %} {% endif %}
</table>
<input type="submit" value="Register"> <input type="submit" value="Register">
</form> </form>
</section> </section>

View File

@ -35,7 +35,7 @@ async def send_invite(request, to_addr):
to_addr, token) to_addr, token)
d = {'invite': token} d = {'invite': token}
invite_url = request.app.router['register'].url_for().with_query(d) invite_url = request.app.router['register'].url_for().with_query(d)
invite_url = 'https://' + config.server_domain + str(invite_url) invite_url = config.server_domain + config.url_prefix + invite_url
body = "Buckle up.\n" + invite_url body = "Buckle up.\n" + invite_url
send_mail(to_addr, "Buckler Invite", body) send_mail(to_addr, "Buckler Invite", body)
@ -52,9 +52,8 @@ async def send_confirmation(request, user_id, to_addr):
user_id, token) user_id, token)
d = {'confirm': token} d = {'confirm': token}
confirm_url = request.app.router['register'].url_for().with_query(d) confirm_url = request.app.router['register'].url_for().with_query(d)
confirm_url = 'https://' + config.server_domain + str(confirm_url) confirm_url = config.server_domain + str(confirm_url)
body = "Buckle up.\n" + confirm_url body = "Buckle up.\n" + confirm_url
send_mail(to_addr, "Confirm Email", body)
async def validate_register(request, form): async def validate_register(request, form):