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
Python 3.7+
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
```

View File

@ -4,6 +4,7 @@ A security shield for protecting a number of small web applications.
"""
import secrets
import functools
from datetime import datetime
from aiohttp import web
import jinja2
@ -11,9 +12,11 @@ import aiohttp_jinja2
from aiohttp_jinja2 import render_template
from passlib.hash import argon2
import asyncpg
import uvloop
import config
uvloop.install()
routes = web.RouteTableDef()
def auth_required(func):
@ -26,17 +29,42 @@ def auth_required(func):
sid = request.cookies.get('session')
try:
userid = int(request.cookies.get('userid'))
except ValueError:
except (ValueError, TypeError):
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",
"WHERE user_id = $1 AND expiry > NOW() ",
userid)
if sid in [s.get('id') for s in sessions]:
return await func(request, *args, **kwargs)
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",
userid)
resp = 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:
raise web.HTTPFound(location=login_url)
return wrapper
@ -71,7 +99,12 @@ async def login(request):
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'])
resp.set_cookie(
'userid',
user_info['id'],
max_age=30*24*60*60,
secure=True,
httponly=True)
sid = secrets.token_urlsafe(64)
ip_address = request.headers['X-Real-IP']
async with request.app['pool'].acquire() as conn:
@ -81,7 +114,11 @@ async def login(request):
user_info['id'],
sid,
ip_address)
resp.set_cookie('session', sid)
resp.set_cookie(
'session',sid,
max_age=30*24*60*60,
secure=True,
httponly=True)
raise resp
else:
return render_template("login.html", request, locals())
@ -91,28 +128,26 @@ async def login(request):
@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())
if request.method == 'POST':
data = await request.post()
username = data.get('username')
email = data.get('email')
password = data.get('password')
password_verify = data.get('password_verify')
data = await request.post()
username = data.get('username')
email = data.get('email')
password = data.get('password')
password_verify = data.get('password_verify')
errors = {}
if password != password_verify:
errors[''] = ''
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)
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)
return render_template("register.html", request, locals())
async def init_app():

View File

@ -6,7 +6,10 @@ CREATE TABLE IF NOT EXISTS user_info (
);
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,
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>
<form action="{{ request.app.router['register'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded">
<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>
<input id="email" name="email" type="email"><br>
<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>
<input id="password_verify" name="password_verify" type="password"><br>
<input type="submit" value="Login">
<input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"><br>
<input type="submit" value="Register">
</form>
</body>
</html>