diff --git a/app/auth.py b/app/auth.py index 259d7b1..6308aa8 100644 --- a/app/auth.py +++ b/app/auth.py @@ -4,6 +4,7 @@ from argon2 import PasswordHasher from functools import wraps import secrets import time +import re ph = PasswordHasher() @@ -44,6 +45,15 @@ def create_session(user_id, temporary=False): 'expires_at': int(time.time()) + (expires_days * 24 * 60 * 60), }) +def parse_username(username: str) -> Tuple[str, str]: + if len(username) < 3: + raise ValueError + invalid_regex = r'[^a-zA-Z0-9_-]' + return username, re.sub(invalid_regex, '_', username.lower())[:20] + +def is_password_valid(password: str) -> bool: + return re.match(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,255}$', password) is not None + # annotations def login_required(view_func): @wraps(view_func) diff --git a/app/routes/users.py b/app/routes/users.py index 3085334..5a62cd2 100644 --- a/app/routes/users.py +++ b/app/routes/users.py @@ -1,8 +1,10 @@ from flask import Blueprint, redirect, url_for, render_template, request, session from functools import wraps +import time -from ..auth import digest, verify, create_session, is_logged_in +from ..auth import digest, verify, create_session, is_logged_in, parse_username, is_password_valid from ..models import Users +from ..constants import PermissionLevel bp = Blueprint('users', __name__, url_prefix='/users/') @@ -39,10 +41,53 @@ def log_in_post(): session.permanent = True return redirect(request.form.get('return_to', default=url_for('topics.all_topics'))) -@bp.get('/sign-up') +@bp.get('/sign-up/') @redirect_if_logged_in() def sign_up(): - return 'stub' + return render_template('users/sign_up.html') + +@bp.post('/sign-up/') +@redirect_if_logged_in() +def sign_up_post(): + generic_error_page = redirect(url_for('.sign_up', error='The username or password you entered is invalid.')) + user_exists_error_page = redirect(url_for('.sign_up', error='This username is already taken. Please pick another.')) + passwords_error_page = redirect(url_for('.sign_up', error='The passwords do not match.')) + username = request.form.get('username', default='') + if not username: + return generic_error_page + if request.form.get('password', default=None) is None: + return generic_error_page + if len(request.form.getlist('password')) != 2: + return passwords_error_page + username_pair = parse_username(username) + potential_user = Users.find({'username': username}) + if potential_user: + return user_exists_error_page + + if request.form.getlist('password')[0] != request.form.getlist('password')[1]: + return passwords_error_page + + password_hash = digest(request.form.get('password')) + + user = Users.create({ + 'username': username_pair[0], + 'password_hash': password_hash, + 'permission': PermissionLevel.GUEST.value, + 'created_at': int(time.time()), + }) + + if username_pair[0] != username_pair[1]: + user.update({ + 'display_name': username_pair[1] + }) + + session['remember'] = request.form.get('remember') == 'on' + sess = create_session(user.id, not session['remember']) + session['pyrom_session_key'] = sess.key + if session['remember']: + session.permanent = True + + return redirect(url_for('topics.all_topics')) @bp.get('/') def user_page(username): diff --git a/app/templates/common/topnav.html b/app/templates/common/topnav.html index 19b27ed..f80a4fa 100644 --- a/app/templates/common/topnav.html +++ b/app/templates/common/topnav.html @@ -13,7 +13,7 @@ {%- endif %} {%- endwith -%} - {%- else -%} + {%- elif request.path != url_for('users.sign_up') and request.path != url_for('users.log_in') -%}
diff --git a/app/templates/users/sign_up.html b/app/templates/users/sign_up.html new file mode 100644 index 0000000..4b9a73c --- /dev/null +++ b/app/templates/users/sign_up.html @@ -0,0 +1,24 @@ +{% from 'common/macros.html' import subheader %} +{%- extends 'base.html' -%} +{%- block title -%}sign up{%- endblock -%} +{%- block content -%} +{%- set welcome -%} +Please read the rules etc. stub +{%- endset -%} +{{ subheader('Sign up', welcome)}} +{%- if request.args.get('error') -%} +
+ {{request.args.get('error')}} +
+{%- endif -%} + + + + + + + + + +
+{%- endblock -%}