second commit
This commit is contained in:
parent
f709f78bcf
commit
46b5a65990
12
README.md
12
README.md
|
@ -4,9 +4,19 @@ A security shield for protecting a number of small web applications.
|
|||
## Requirements
|
||||
Python 3.7+
|
||||
PostgreSQL 11.5+
|
||||
Python packages: `gunicorn aiohttp aiohttp_jinja2 psycopg2 passlib argon2_cffi`
|
||||
Python packages: `gunicorn aiohttp aiohttp_jinja2 asyncpg passlib argon2_cffi`
|
||||
|
||||
## Install
|
||||
```
|
||||
$ psql
|
||||
postgres=# CREATE DATABASE "buckler";
|
||||
postgres=# CREATE USER "buckler" WITH PASSWORD 'password';
|
||||
postgres=# ALTER ROLE "buckler" SET client_encoding TO 'utf8';
|
||||
postgres=# ALTER ROLE "buckler" SET default_transaction_isolation TO 'read committed';
|
||||
postgres=# ALTER ROLE "buckler" SET timezone TO 'UTC';
|
||||
postgres=# GRANT ALL PRIVILEGES ON DATABASE "buckler" TO "buckler";
|
||||
postgres=# \q
|
||||
```
|
||||
1. Get on the floor
|
||||
2. Walk the dinosaur
|
||||
|
||||
|
|
118
buckler.py
118
buckler.py
|
@ -2,24 +2,132 @@
|
|||
"""
|
||||
A security shield for protecting a number of small web applications.
|
||||
"""
|
||||
import secrets
|
||||
import functools
|
||||
|
||||
from aiohttp import web
|
||||
import jinja2
|
||||
import aiohttp_jinja2
|
||||
from aiohttp_jinja2 import render_template
|
||||
from passlib.hash import argon2
|
||||
import asyncpg
|
||||
|
||||
import config
|
||||
|
||||
app = web.Application()
|
||||
aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('templates'))
|
||||
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
@routes.get(config.url_prefix + "/", name='index')
|
||||
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
|
||||
async def index(request):
|
||||
"""The index page."""
|
||||
return render_template("index.html", request, locals())
|
||||
|
||||
app.router.add_routes(routes)
|
||||
|
||||
@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
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = init_app()
|
||||
web.run_app(app, host='0.0.0.0', port=5400)
|
||||
|
|
12
buckler.sql
Normal file
12
buckler.sql
Normal file
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE IF NOT EXISTS user_info (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password_hash TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_session (
|
||||
user_id INTEGER references user_info(id),
|
||||
id TEXT PRIMARY KEY,
|
||||
ip_address TEXT NOT NULL
|
||||
);
|
|
@ -1,7 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Configuration settings for Buckler.
|
||||
url_prefix` is the root path you wish app to reside at
|
||||
`url_prefix` is the root path you wish app to reside at
|
||||
eg. https://example.com/buckler
|
||||
`db` specifies parameters for connecting to the PostgreSQL database.
|
||||
"""
|
||||
url_prefix = '/buckler'
|
||||
|
||||
db = {
|
||||
'database': 'buckler',
|
||||
'user': 'buckler',
|
||||
'password': 'password',
|
||||
'host': 'localhost',
|
||||
'port': 5432,
|
||||
}
|
||||
|
|
|
@ -5,13 +5,12 @@
|
|||
</head>
|
||||
<body>
|
||||
<h1>Buckler Login</h1>
|
||||
<form action="/login" method="post" enctype="application/x-www-form-urlencoded">
|
||||
<form action="{{ request.app.router['login'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="password" type="text">
|
||||
<input id="username" name="username" type="text"><br>
|
||||
<label for="password">Password</label>
|
||||
<input id="password" name="password" type="password">
|
||||
<input id="password" name="password" type="password"><br>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
20
templates/register.html
Normal file
20
templates/register.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Buckler - Register</title>
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<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>
|
||||
<label for="password_verify">Verify Password</label>
|
||||
<input id="password_verify" name="password_verify" type="password"><br>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user