2019-09-14 18:36:23 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
|
|
|
A security shield for protecting a number of small web applications.
|
|
|
|
"""
|
2019-09-15 19:40:58 -04:00
|
|
|
import secrets
|
|
|
|
import functools
|
|
|
|
|
2019-09-14 18:36:23 -04:00
|
|
|
from aiohttp import web
|
|
|
|
import jinja2
|
|
|
|
import aiohttp_jinja2
|
|
|
|
from aiohttp_jinja2 import render_template
|
2019-09-15 19:40:58 -04:00
|
|
|
from passlib.hash import argon2
|
|
|
|
import asyncpg
|
2019-09-14 18:36:23 -04:00
|
|
|
|
|
|
|
import config
|
|
|
|
|
|
|
|
routes = web.RouteTableDef()
|
|
|
|
|
2019-09-15 19:40:58 -04:00
|
|
|
def auth_required(func):
|
|
|
|
"""
|
|
|
|
Wrapper for views with should be protected by authtication.
|
|
|
|
"""
|
|
|
|
@functools.wraps(func)
|
|
|
|
async def wrapper(request, *args, **kwargs):
|
|
|
|
login_url = request.app.router['login'].url_for()
|
|
|
|
sid = request.cookies.get('session')
|
|
|
|
try:
|
|
|
|
userid = int(request.cookies.get('userid'))
|
|
|
|
except ValueError:
|
|
|
|
userid = None
|
|
|
|
if not sid or not userid:
|
|
|
|
raise web.HTTPFound(location=login_url)
|
|
|
|
async with request.app['pool'].acquire() as conn:
|
|
|
|
sessions = await conn.fetch(
|
|
|
|
"SELECT * FROM user_session "
|
|
|
|
"WHERE user_id = $1",
|
|
|
|
userid)
|
|
|
|
if sid in [s.get('id') for s in sessions]:
|
|
|
|
return await func(request, *args, **kwargs)
|
|
|
|
else:
|
|
|
|
raise web.HTTPFound(location=login_url)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
@routes.get(config.url_prefix + '/', name='index')
|
|
|
|
@auth_required
|
2019-09-14 18:36:23 -04:00
|
|
|
async def index(request):
|
|
|
|
"""The index page."""
|
|
|
|
return render_template("index.html", request, locals())
|
|
|
|
|
2019-09-15 19:40:58 -04:00
|
|
|
|
|
|
|
@routes.get(config.url_prefix + '/login', name='login')
|
|
|
|
@routes.post(config.url_prefix + '/login', name='login')
|
|
|
|
async def login(request):
|
|
|
|
"""Handle login."""
|
|
|
|
if request.method == 'GET':
|
|
|
|
return render_template("login.html", request, locals())
|
|
|
|
|
|
|
|
data = await request.post()
|
|
|
|
username = data.get('username')
|
|
|
|
password = data.get('password')
|
|
|
|
|
|
|
|
async with request.app['pool'].acquire() as conn:
|
|
|
|
user_info = await conn.fetchrow(
|
|
|
|
"SELECT * FROM user_info WHERE username = $1",
|
|
|
|
username)
|
|
|
|
|
|
|
|
if not user_info:
|
|
|
|
return render_template("login.html", request, locals())
|
|
|
|
|
|
|
|
if argon2.verify(password, user_info['password_hash']):
|
|
|
|
index_url = request.app.router['index'].url_for()
|
|
|
|
resp = web.HTTPFound(location=index_url)
|
|
|
|
resp.set_cookie('userid', user_info['id'])
|
|
|
|
sid = secrets.token_urlsafe(64)
|
|
|
|
ip_address = request.headers['X-Real-IP']
|
|
|
|
async with request.app['pool'].acquire() as conn:
|
|
|
|
await conn.execute(
|
|
|
|
"INSERT INTO user_session (user_id, id, ip_address)"
|
|
|
|
"VALUES ($1, $2, $3)",
|
|
|
|
user_info['id'],
|
|
|
|
sid,
|
|
|
|
ip_address)
|
|
|
|
resp.set_cookie('session', sid)
|
|
|
|
raise resp
|
|
|
|
else:
|
|
|
|
return render_template("login.html", request, locals())
|
|
|
|
|
|
|
|
|
|
|
|
@routes.get(config.url_prefix + '/register', name='register')
|
|
|
|
@routes.post(config.url_prefix + '/register', name='register')
|
|
|
|
async def register(request):
|
|
|
|
"""Register new accounts."""
|
|
|
|
if request.method == 'GET':
|
|
|
|
return render_template("register.html", request, locals())
|
|
|
|
|
|
|
|
data = await request.post()
|
|
|
|
username = data.get('username')
|
|
|
|
email = data.get('email')
|
|
|
|
password = data.get('password')
|
|
|
|
password_verify = data.get('password_verify')
|
|
|
|
|
|
|
|
if password != password_verify:
|
|
|
|
raise web.HTTPNotAcceptable
|
|
|
|
|
|
|
|
pw_hash = argon2.hash(password)
|
|
|
|
async with request.app['pool'].acquire() as conn:
|
|
|
|
await conn.execute(
|
|
|
|
"INSERT INTO user_info (username, email, password_hash) "
|
|
|
|
"VALUES ($1, $2, $3)",
|
|
|
|
username,
|
|
|
|
email,
|
|
|
|
pw_hash)
|
|
|
|
login_url = request.app.router['login'].url_for()
|
|
|
|
raise web.HTTPFound(location=login_url)
|
|
|
|
|
|
|
|
|
|
|
|
async def init_app():
|
|
|
|
"""Initializes the application."""
|
|
|
|
app = web.Application()
|
|
|
|
aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('templates'))
|
|
|
|
app['pool'] = await asyncpg.create_pool(**config.db)
|
|
|
|
|
|
|
|
async with app['pool'].acquire() as conn:
|
|
|
|
with open('buckler.sql', 'r') as file:
|
|
|
|
await conn.execute(file.read())
|
|
|
|
|
|
|
|
app.router.add_routes(routes)
|
|
|
|
return app
|
2019-09-14 18:36:23 -04:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2019-09-15 19:40:58 -04:00
|
|
|
app = init_app()
|
2019-09-14 18:36:23 -04:00
|
|
|
web.run_app(app, host='0.0.0.0', port=5400)
|