diff --git a/buckler.py b/buckler.py
index 96c1bdf..1766703 100644
--- a/buckler.py
+++ b/buckler.py
@@ -16,6 +16,7 @@ import asyncpg
import uvloop
import auth
+import forms
import tools
import config
@@ -23,6 +24,7 @@ uvloop.install()
routes = web.RouteTableDef()
@routes.get(config.url_prefix + '/', name='index')
+@routes.post(config.url_prefix + '/', name='index')
@auth.auth_required
async def index(request):
"""The index page."""
@@ -55,38 +57,26 @@ async def index(request):
if index != -1:
users[user_perm['username']][apps[index]['name']] = True
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())
-@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.post(config.url_prefix + '/login', name='login')
async def login(request):
@@ -337,50 +327,6 @@ async def set_session(request):
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():
"""Initializes the application."""
app = web.Application()
diff --git a/forms.py b/forms.py
new file mode 100644
index 0000000..728b192
--- /dev/null
+++ b/forms.py
@@ -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 {}
diff --git a/static/buckler.css b/static/buckler.css
index b5e5c02..7228ac4 100644
--- a/static/buckler.css
+++ b/static/buckler.css
@@ -65,16 +65,16 @@ td {
text-align: center;
}
-#change_password {
+.no_borders {
border: none;
border-collapse: separate;
width: auto;
}
-#change_password tr {
+.no_borders tr {
border: none;
}
-#change_password td {
+.no_borders td {
text-align: left;
}
diff --git a/templates/index.html b/templates/index.html
index 4451747..614948d 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -60,6 +60,18 @@
+ Invite New User
+
+