Compare commits
3 Commits
4a3ab80a35
...
02b5a84648
Author | SHA1 | Date | |
---|---|---|---|
02b5a84648 | |||
d8d6d6d5ec | |||
52f3808318 |
2
auth.py
2
auth.py
|
@ -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_session.expires > NOW()",
|
"AND user_info.active = TRUE AND user_session.expires > NOW()",
|
||||||
user_id, sid)
|
user_id, sid)
|
||||||
if session:
|
if session:
|
||||||
request['session'] = dict(session)
|
request['session'] = dict(session)
|
||||||
|
|
92
buckler.py
92
buckler.py
|
@ -16,6 +16,7 @@ import asyncpg
|
||||||
import uvloop
|
import uvloop
|
||||||
|
|
||||||
import auth
|
import auth
|
||||||
|
import forms
|
||||||
import tools
|
import tools
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ 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."""
|
||||||
|
@ -55,38 +57,26 @@ 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):
|
||||||
|
@ -337,50 +327,6 @@ 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()
|
||||||
|
|
88
forms.py
Normal file
88
forms.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#!/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 {}
|
|
@ -65,16 +65,16 @@ td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#change_password {
|
.no_borders {
|
||||||
border: none;
|
border: none;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#change_password tr {
|
.no_borders tr {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#change_password td {
|
.no_borders td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,18 @@
|
||||||
</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 %}
|
||||||
|
@ -67,8 +79,9 @@
|
||||||
<h2>Change Password</h2>
|
<h2>Change Password</h2>
|
||||||
<article style="display: none;">
|
<article style="display: none;">
|
||||||
<hr>
|
<hr>
|
||||||
<form action="{{ request.app.router['change_password'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded">
|
<form method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<table id="change_password">
|
<input name="form_name" type="hidden" value="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>
|
||||||
|
@ -91,7 +104,8 @@
|
||||||
<article style="display: none;">
|
<article style="display: none;">
|
||||||
<hr>
|
<hr>
|
||||||
{% if fido2_keys %}
|
{% if fido2_keys %}
|
||||||
<form action="{{ request.app.router['delete_key'].url_for() }}" method="POST" enctype="application/x-www-form-urlencoded">
|
<form 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>
|
||||||
|
@ -120,7 +134,8 @@
|
||||||
<h2>Active Sessions</h2>
|
<h2>Active Sessions</h2>
|
||||||
<article style="display: none;">
|
<article style="display: none;">
|
||||||
<hr>
|
<hr>
|
||||||
<form action="{{ request.app.router['delete_session'].url_for() }}" method="POST" enctype="application/x-www-form-urlencoded">
|
<form 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>
|
||||||
|
|
|
@ -14,35 +14,60 @@
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<form method="POST" enctype="application/x-www-form-urlencoded">
|
<form method="POST" enctype="application/x-www-form-urlencoded">
|
||||||
<label for="username">Username</label>
|
<table class="no_borders">
|
||||||
<input id="username" name="username" type="text" minlength="3" maxlength="20"><br>
|
<tr>
|
||||||
|
<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'] %}
|
||||||
<ul>
|
<tr>
|
||||||
{% for error in errors['username'] %}
|
<td></td>
|
||||||
<li class="error">{{ error }}</li>
|
<td>
|
||||||
{% endfor %}
|
<ul>
|
||||||
</ul>
|
{% for error in errors['username'] %}
|
||||||
|
<li class="error">{{ error }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<label for="email">Email</label>
|
<tr>
|
||||||
<input id="email" name="email" type="email"><br>
|
<td><label for="email">Email</label></td>
|
||||||
|
<td><input id="email" name="email" type="email"></td>
|
||||||
|
</tr>
|
||||||
{% if errors['email'] %}
|
{% if errors['email'] %}
|
||||||
<ul>
|
<tr>
|
||||||
{% for error in errors['email'] %}
|
<td></td>
|
||||||
<li class="error">{{ error }}</li>
|
<td>
|
||||||
{% endfor %}
|
<ul>
|
||||||
</ul>
|
{% for error in errors['email'] %}
|
||||||
|
<li class="error">{{ error }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<label for="password">Password</label>
|
<tr>
|
||||||
<input id="password" name="password" type="password" minlength="8" maxlength="10240"><br>
|
<td><label for="password">Password</label></td>
|
||||||
<label for="password_verify">Verify Password</label>
|
<td><input id="password" name="password" type="password" minlength="8" maxlength="10240"></td>
|
||||||
<input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"><br>
|
</tr>
|
||||||
|
<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'] %}
|
||||||
<ul>
|
<tr>
|
||||||
{% for error in errors['password'] %}
|
<td></td>
|
||||||
<li class="error">{{ error }}</li>
|
<td>
|
||||||
{% endfor %}
|
<ul>
|
||||||
</ul>
|
{% for error in errors['password'] %}
|
||||||
|
<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>
|
||||||
|
|
5
tools.py
5
tools.py
|
@ -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 = config.server_domain + config.url_prefix + invite_url
|
invite_url = 'https://' + config.server_domain + str(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,8 +52,9 @@ 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 = config.server_domain + str(confirm_url)
|
confirm_url = 'https://' + 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):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user