third commit

This commit is contained in:
iou1name 2019-09-16 20:18:50 -04:00
parent 46b5a65990
commit 04156e39b3
4 changed files with 71 additions and 33 deletions

View File

@ -4,7 +4,7 @@ A security shield for protecting a number of small web applications.
## Requirements ## Requirements
Python 3.7+ Python 3.7+
PostgreSQL 11.5+ PostgreSQL 11.5+
Python packages: `gunicorn aiohttp aiohttp_jinja2 asyncpg passlib argon2_cffi` Python packages: `wheel gunicorn aiohttp aiohttp_jinja2 asyncpg passlib argon2_cffi uvloop`
## Install ## Install
``` ```

View File

@ -4,6 +4,7 @@ A security shield for protecting a number of small web applications.
""" """
import secrets import secrets
import functools import functools
from datetime import datetime
from aiohttp import web from aiohttp import web
import jinja2 import jinja2
@ -11,9 +12,11 @@ import aiohttp_jinja2
from aiohttp_jinja2 import render_template from aiohttp_jinja2 import render_template
from passlib.hash import argon2 from passlib.hash import argon2
import asyncpg import asyncpg
import uvloop
import config import config
uvloop.install()
routes = web.RouteTableDef() routes = web.RouteTableDef()
def auth_required(func): def auth_required(func):
@ -26,17 +29,42 @@ def auth_required(func):
sid = request.cookies.get('session') sid = request.cookies.get('session')
try: try:
userid = int(request.cookies.get('userid')) userid = int(request.cookies.get('userid'))
except ValueError: except (ValueError, TypeError):
userid = None userid = None
if not sid or not userid: if not sid or not userid:
raise web.HTTPFound(location=login_url) raise web.HTTPFound(location=login_url)
async with request.app['pool'].acquire() as conn: async with request.app['pool'].acquire() as conn:
sessions = await conn.fetch( sessions = await conn.fetch(
"SELECT * FROM user_session " "SELECT * FROM user_session "
"WHERE user_id = $1 AND expiry > NOW() ",
userid)
session = [s for s in sessions if s.get('id') == sid]
if session:
session = session[0]
tz = session['last_used'].tzinfo
delta = datetime.now(tz) - session['last_used']
if delta.seconds > 600:
async with request.app['pool'].acquire() as conn:
await conn.execute(
"UPDATE user_session SET last_used = NOW(),"
"expiry = NOW() + INTERVAL '30 DAYS'"
"WHERE user_id = $1", "WHERE user_id = $1",
userid) userid)
if sid in [s.get('id') for s in sessions]: resp = await func(request, *args, **kwargs)
return await func(request, *args, **kwargs) if delta.seconds > 600:
resp.set_cookie(
'userid',
userid,
max_age=30*24*60*60,
secure=True,
httponly=True)
resp.set_cookie(
'session',
sid,
max_age=30*24*60*60,
secure=True,
httponly=True)
return resp
else: else:
raise web.HTTPFound(location=login_url) raise web.HTTPFound(location=login_url)
return wrapper return wrapper
@ -71,7 +99,12 @@ async def login(request):
if argon2.verify(password, user_info['password_hash']): if argon2.verify(password, user_info['password_hash']):
index_url = request.app.router['index'].url_for() index_url = request.app.router['index'].url_for()
resp = web.HTTPFound(location=index_url) resp = web.HTTPFound(location=index_url)
resp.set_cookie('userid', user_info['id']) resp.set_cookie(
'userid',
user_info['id'],
max_age=30*24*60*60,
secure=True,
httponly=True)
sid = secrets.token_urlsafe(64) sid = secrets.token_urlsafe(64)
ip_address = request.headers['X-Real-IP'] ip_address = request.headers['X-Real-IP']
async with request.app['pool'].acquire() as conn: async with request.app['pool'].acquire() as conn:
@ -81,7 +114,11 @@ async def login(request):
user_info['id'], user_info['id'],
sid, sid,
ip_address) ip_address)
resp.set_cookie('session', sid) resp.set_cookie(
'session',sid,
max_age=30*24*60*60,
secure=True,
httponly=True)
raise resp raise resp
else: else:
return render_template("login.html", request, locals()) return render_template("login.html", request, locals())
@ -91,28 +128,26 @@ async def login(request):
@routes.post(config.url_prefix + '/register', name='register') @routes.post(config.url_prefix + '/register', name='register')
async def register(request): async def register(request):
"""Register new accounts.""" """Register new accounts."""
if request.method == 'GET': if request.method == 'POST':
return render_template("register.html", request, locals())
data = await request.post() data = await request.post()
username = data.get('username') username = data.get('username')
email = data.get('email') email = data.get('email')
password = data.get('password') password = data.get('password')
password_verify = data.get('password_verify') password_verify = data.get('password_verify')
errors = {}
if password != password_verify: if password != password_verify:
raise web.HTTPNotAcceptable errors[''] = ''
pw_hash = argon2.hash(password) pw_hash = argon2.hash(password)
async with request.app['pool'].acquire() as conn: async with request.app['pool'].acquire() as conn:
await conn.execute( await conn.execute(
"INSERT INTO user_info (username, email, password_hash) " "INSERT INTO user_info (username, email, password_hash) "
"VALUES ($1, $2, $3)", "VALUES ($1, $2, $3)",
username, username, email, pw_hash)
email,
pw_hash)
login_url = request.app.router['login'].url_for() login_url = request.app.router['login'].url_for()
raise web.HTTPFound(location=login_url) raise web.HTTPFound(location=login_url)
return render_template("register.html", request, locals())
async def init_app(): async def init_app():

View File

@ -6,7 +6,10 @@ CREATE TABLE IF NOT EXISTS user_info (
); );
CREATE TABLE IF NOT EXISTS user_session ( CREATE TABLE IF NOT EXISTS user_session (
user_id INTEGER references user_info(id), user_id INTEGER references user_info(id) ON DELETE CASCADE,
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
ip_address TEXT NOT NULL ip_address INET NOT NULL,
date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expiry TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '30 DAYS',
last_used TIMESTAMP WITH TIME ZONE DEFAULT NOW()
); );

View File

@ -7,14 +7,14 @@
<h1>Buckler Register</h1> <h1>Buckler Register</h1>
<form action="{{ request.app.router['register'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded"> <form action="{{ request.app.router['register'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded">
<label for="username">Username</label> <label for="username">Username</label>
<input id="username" name="username" type="text"><br> <input id="username" name="username" type="text" minlength="3" maxlength="20"><br>
<label for="email">Email</label> <label for="email">Email</label>
<input id="email" name="email" type="email"><br> <input id="email" name="email" type="email"><br>
<label for="password">Password</label> <label for="password">Password</label>
<input id="password" name="password" type="password"><br> <input id="password" name="password" type="password" minlength="8" maxlength="10240"><br>
<label for="password_verify">Verify Password</label> <label for="password_verify">Verify Password</label>
<input id="password_verify" name="password_verify" type="password"><br> <input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"><br>
<input type="submit" value="Login"> <input type="submit" value="Register">
</form> </form>
</body> </body>
</html> </html>