From eafa141a2fafa56272caa5f26b7efb393fa72904 Mon Sep 17 00:00:00 2001 From: David Hoppenbrouwers Date: Sat, 15 Oct 2022 22:35:47 +0200 Subject: [PATCH] Autoregister on comment --- db/sqlite.py | 2 +- main.py | 73 +++++++++++++++++++++++++++--------------- static/theme.css | 9 ++++++ templates/base.html | 6 +++- templates/comment.html | 22 +++++++++++-- 5 files changed, 82 insertions(+), 30 deletions(-) diff --git a/db/sqlite.py b/db/sqlite.py index d8f6bca..c13fe36 100644 --- a/db/sqlite.py +++ b/db/sqlite.py @@ -432,7 +432,7 @@ class DB: return c.execute(''' select user_id from users - where name = ? + where name = lower(?) ''', (username,) ).fetchone() diff --git a/main.py b/main.py index a76cdca..4a6ec54 100644 --- a/main.py +++ b/main.py @@ -234,34 +234,39 @@ def delete_thread(thread_id): # TODO return 403, maybe? return redirect(url_for('index')) +def _add_comment_check_user(): + user_id = session.get('user_id') + if user_id is not None: + return user_id + if not config.registration_enabled: + flash('Registrations are not enabled. Please log in to comment', 'error') + if register_user(True): + return session['user_id'] + @app.route('/thread//comment/', methods = ['POST']) def add_comment(thread_id): - user_id = session.get('user_id') - if user_id is None: - return redirect(url_for('login')) - - text = trim_text(request.form['text']) - if text == '': - flash('Text may not be empty', 'error') - elif db.add_comment_to_thread(thread_id, user_id, text, time.time_ns()): - flash('Added comment', 'success') - else: - flash('Failed to add comment', 'error') + user_id = _add_comment_check_user() + if user_id is not None: + text = trim_text(request.form['text']) + if text == '': + flash('Text may not be empty', 'error') + elif db.add_comment_to_thread(thread_id, user_id, text, time.time_ns()): + flash('Added comment', 'success') + else: + flash('Failed to add comment', 'error') return redirect(url_for('thread', thread_id = thread_id)) @app.route('/comment//comment/', methods = ['POST']) def add_comment_parent(comment_id): - user_id = session.get('user_id') - if user_id is None: - return redirect(url_for('login')) - - text = trim_text(request.form['text']) - if text == '': - flash('Text may not be empty', 'error') - elif db.add_comment_to_comment(comment_id, user_id, text, time.time_ns()): - flash('Added comment', 'success') - else: - flash('Failed to add comment', 'error') + user_id = _add_comment_check_user() + if user_id is not None: + text = trim_text(request.form['text']) + if text == '': + flash('Text may not be empty', 'error') + elif db.add_comment_to_comment(comment_id, user_id, text, time.time_ns()): + flash('Added comment', 'success') + else: + flash('Failed to add comment', 'error') return redirect(url_for('comment', comment_id = comment_id)) @app.route('/comment//confirm_delete/') @@ -358,8 +363,7 @@ def edit_comment(comment_id): def register(): if request.method == 'POST': username, passwd = request.form['username'], request.form['password'] - if register_user(): - flash('Account has been created', 'success') + if register_user(False): return redirect(url_for('index')) capt, answer = captcha.generate(config.captcha_key) @@ -700,7 +704,7 @@ def get_user(): return User(id, name, role, banned_until) return None -def register_user(): +def register_user(show_password): username, passwd = request.form['username'], request.form['password'] if any(c in username for c in string.whitespace): # This error is more ergonomic in case someone tries to play tricks again :) @@ -720,6 +724,10 @@ def register_user(): if uid is None: flash('Failed to create account (username may already be taken)', 'error') else: + s = 'Account has been created.' + if show_password: + s += f' Your password is {passwd} (hover to reveal).' + flash(s, 'success') uid, = uid session['user_id'] = uid return True @@ -774,11 +782,26 @@ def utility_processor(): def format_time(t): return datetime.utcfromtimestamp(t / 10 ** 9).replace(microsecond=0) + def rand_password(): + ''' + Generate a random password. + + The current implementation returns 12 random lower- and uppercase alphabet characters. + This gives up to `log((26 * 2) ** 12) / log(2) = ~68` bits of entropy, which should be + enough for the foreseeable future. + ''' + return ''.join(string.ascii_letters[secrets.randbelow(52)] for _ in range(12)) + + def gen_captcha(): + return captcha.generate(config.captcha_key) + return { 'format_since': format_since, 'format_time': format_time, 'format_until': format_until, 'minimd': minimd.html, + 'rand_password': rand_password, + 'gen_captcha': gen_captcha, } diff --git a/static/theme.css b/static/theme.css index fd8628f..083195a 100644 --- a/static/theme.css +++ b/static/theme.css @@ -129,3 +129,12 @@ table.form > * > tr > td, th { .small { font-size: 85%; } + +.spoiler { + background-color: black; + color: black; +} +.spoiler:hover { + opacity: 1; + color: white; +} diff --git a/templates/base.html b/templates/base.html index ca786d6..df21e1a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -34,7 +34,11 @@

{{ title }}

{%- for category, msg in get_flashed_messages(True) -%} -

{{ msg }}

+ {#- + FIXME ensure all flash() messages are free of XSS vectors. + In particular, check places where we flash error messages. + -#} +

{{ msg | safe }}

{%- endfor -%} {%- block content %}{% endblock -%}
diff --git a/templates/comment.html b/templates/comment.html index d4b4024..f5c089f 100644 --- a/templates/comment.html +++ b/templates/comment.html @@ -57,10 +57,26 @@ {%- endmacro -%} {%- macro reply() -%} -{%- if user is not none and not user.is_banned() -%} +{%- if user is none -%} +{%- if config.registration_enabled -%}
-

-

+

+{#- + Using the password generator for usernames should be sufficient to ensure it is unique. + If not, it means the password generator is broken and *must* be fixed. +-#} + + +{% set q, a = gen_captcha() %} +

Captcha: {{ q }}

+ +

(I already have an account)

+
+{%- endif -%} +{%- elif not user.is_banned() -%} +
+

+

{%- endif -%} {%- endmacro -%}