From e9ef9140f05d10c8d1cace1c1f3b50ab3e8340fc Mon Sep 17 00:00:00 2001 From: David Hoppenbrouwers Date: Sat, 8 Oct 2022 15:25:28 +0200 Subject: [PATCH] Add registration --- captcha.py | 20 ++++++++++++++++++++ db/sqlite.py | 14 ++++++++++++++ main.py | 39 ++++++++++++++++++++++++++++++++++++++- schema.txt | 2 +- static/theme.css | 8 ++++++++ templates/base.html | 2 ++ templates/register.html | 13 +++++++++++++ test/init_db.txt | 15 +++++++++------ 8 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 captcha.py create mode 100644 templates/register.html diff --git a/captcha.py b/captcha.py new file mode 100644 index 0000000..eea5287 --- /dev/null +++ b/captcha.py @@ -0,0 +1,20 @@ +from random import randint +import hashlib, base64 + +# FIXME hash can be reused +def generate(key): + ''' + Generate a simple CAPTCHA. + It is based on a simple math expression which stops the simplest of bots. + ''' + # The parameters are chosen such that they are simple to solve on paper. + a = randint(1, 10) + b = randint(1, 10) + c = randint(10, 20) + return f'{a} * {b} + {c} = ', _hash_answer(key, str(a * b + c)) + +def verify(key, answer, hash): + return _hash_answer(key, answer) == hash + +def _hash_answer(key, answer): + return base64.b64encode(hashlib.sha256((key + answer).encode('utf-8')).digest()).decode('ascii') diff --git a/db/sqlite.py b/db/sqlite.py index 407d885..9740440 100644 --- a/db/sqlite.py +++ b/db/sqlite.py @@ -283,5 +283,19 @@ class DB: return True return False + def add_user(self, username, password, time): + db = self._db() + c = db.cursor() + c.execute(''' + insert into users(name, password, join_time) + values (?, ?, ?) + ''', + (username, password, time) + ) + if c.rowcount > 0: + db.commit() + return True + return False + def _db(self): return sqlite3.connect(self.conn) diff --git a/main.py b/main.py index cfc029a..7ed2a30 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import os import passlib.hash import time from datetime import datetime +import captcha app = Flask(__name__) db = DB(os.getenv('DB')) @@ -11,6 +12,7 @@ NAME = 'Agrepy' # TODO config file app.config['SECRET_KEY'] = 'totally random' +captcha_key = 'piss off bots' app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True @@ -70,7 +72,7 @@ def login(): v = db.get_user_password(request.form['username']) if v is not None: id, hash = v - if passlib.hash.argon2.verify(request.form['password'], hash): + if verify_password(request.form['password'], hash): flash('Logged in', 'success') session['user_id'] = id session['username'] = request.form['username'] @@ -256,6 +258,34 @@ def edit_comment(comment_id): text = text, ) +@app.route('/register/', methods = ['GET', 'POST']) +def register(): + if request.method == 'POST': + username, password = request.form['username'], request.form['password'] + if len(username) < 3: + flash('Username must be at least 3 characters long', 'error') + elif len(password) < 8: + flash('Password must be at least 8 characters long', 'error') + elif not captcha.verify( + captcha_key, + request.form['captcha'], + request.form['answer'], + ): + flash('CAPTCHA answer is incorrect', 'error') + elif not db.add_user(username, hash_password(password), time.time_ns()): + flash('Failed to create user (username may already be taken)') + else: + flash('Account has been created. You can login now.', 'success') + return redirect(url_for('index')) + + capt, answer = captcha.generate(captcha_key) + return render_template( + 'register.html', + title = 'Register', + captcha = capt, + answer = answer, + ) + class Comment: def __init__(self, id, author_id, author, text, create_time, modify_time, parent_id): @@ -348,3 +378,10 @@ def utility_processor(): 'format_since': format_since, 'minimd': minimd, } + + +def hash_password(password): + return passlib.hash.argon2.hash(password) + +def verify_password(password, hash): + return passlib.hash.argon2.verify(password, hash) diff --git a/schema.txt b/schema.txt index f9275cf..596bfca 100644 --- a/schema.txt +++ b/schema.txt @@ -4,7 +4,7 @@ create table users ( password varchar(128) not null, email varchar(254), about text not null default '', - join_time integer, + join_time integer not null, role integer not null default 0 ); diff --git a/static/theme.css b/static/theme.css index 4fdfeef..01308fc 100644 --- a/static/theme.css +++ b/static/theme.css @@ -95,3 +95,11 @@ table.form > * > tr > td, th { border-radius: 5px; padding: 8px; } + +.login { + width: 50%; +} + +.login input[type=text], .login input[type=password] { + width: 90%; +} diff --git a/templates/base.html b/templates/base.html index f444d8b..8bff4a4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -14,6 +14,8 @@ | Logout {% else %} + Register + | Login {% endif %} diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..5106b63 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{%- block content %} +
+ + + + +
Username
Password
{{ captcha }}
+ + +
+{% endblock %} diff --git a/test/init_db.txt b/test/init_db.txt index 8f9c71f..a7e8162 100644 --- a/test/init_db.txt +++ b/test/init_db.txt @@ -1,19 +1,22 @@ -insert into users (name, password, email) values ( +insert into users (name, password, email, join_time) values ( "Foo", -- supasecret "$argon2id$v=19$m=65536,t=3,p=4$qBWCEAKgdA4BYOy915qzlg$KhGy3UF0QMlplt2eB7r7QNL2kDcggXUimRWUrWql8sI", - "foo@bar.baz" + "foo@bar.baz", + 0 ); -insert into users (name, password, email) values ( +insert into users (name, password, email, join_time) values ( "Bar", -- abraca "$argon2id$v=19$m=65536,t=3,p=4$klJKCUFoDaF07j3nPCeEUA$lCphd5n1YIs8MaVop2vGNirwknkh91qJIZHMuBOlgWA", - "bar@foo.baz" + "bar@foo.baz", + 0 ); -insert into users (name, password) values ( +insert into users (name, password, join_time) values ( "bazzers", -- e - "$argon2id$v=19$m=65536,t=3,p=4$9v5fS2ktxTinNEbIGUOoFQ$LMdEuAuuTCJ7utOE88+nXn7o6R/DEKY8ZA6wV+YkVGQ" + "$argon2id$v=19$m=65536,t=3,p=4$9v5fS2ktxTinNEbIGUOoFQ$LMdEuAuuTCJ7utOE88+nXn7o6R/DEKY8ZA6wV+YkVGQ", + 0 ); insert into forums (name, description, allowed_roles_mask)