add user signup flow
This commit is contained in:
10
app/auth.py
10
app/auth.py
@@ -4,6 +4,7 @@ from argon2 import PasswordHasher
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
ph = PasswordHasher()
|
ph = PasswordHasher()
|
||||||
|
|
||||||
@@ -44,6 +45,15 @@ def create_session(user_id, temporary=False):
|
|||||||
'expires_at': int(time.time()) + (expires_days * 24 * 60 * 60),
|
'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
|
# annotations
|
||||||
def login_required(view_func):
|
def login_required(view_func):
|
||||||
@wraps(view_func)
|
@wraps(view_func)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from flask import Blueprint, redirect, url_for, render_template, request, session
|
from flask import Blueprint, redirect, url_for, render_template, request, session
|
||||||
from functools import wraps
|
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 ..models import Users
|
||||||
|
from ..constants import PermissionLevel
|
||||||
|
|
||||||
bp = Blueprint('users', __name__, url_prefix='/users/')
|
bp = Blueprint('users', __name__, url_prefix='/users/')
|
||||||
|
|
||||||
@@ -39,10 +41,53 @@ def log_in_post():
|
|||||||
session.permanent = True
|
session.permanent = True
|
||||||
return redirect(request.form.get('return_to', default=url_for('topics.all_topics')))
|
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()
|
@redirect_if_logged_in()
|
||||||
def sign_up():
|
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('/<username>')
|
@bp.get('/<username>')
|
||||||
def user_page(username):
|
def user_page(username):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{%- endwith -%}
|
{%- endwith -%}
|
||||||
{%- else -%}
|
{%- elif request.path != url_for('users.sign_up') and request.path != url_for('users.log_in') -%}
|
||||||
<form class="horizontal wrap" method="POST" action="{{url_for('users.log_in_post')}}">
|
<form class="horizontal wrap" method="POST" action="{{url_for('users.log_in_post')}}">
|
||||||
<input type="hidden" name="return_to" value="{{request.path}}">
|
<input type="hidden" name="return_to" value="{{request.path}}">
|
||||||
<input type="text" placeholder="Username" name="username" autocomplete="username" required>
|
<input type="text" placeholder="Username" name="username" autocomplete="username" required>
|
||||||
|
|||||||
24
app/templates/users/sign_up.html
Normal file
24
app/templates/users/sign_up.html
Normal file
@@ -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') -%}
|
||||||
|
<div class="infobox plank critical">
|
||||||
|
{{request.args.get('error')}}
|
||||||
|
</div>
|
||||||
|
{%- endif -%}
|
||||||
|
<form class="plank primary-bg full-width" method="POST">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" pattern="[a-zA-Z0-9_\-]{3,20}" title="3-20 characters. Only upper and lowercase letters, digits, hyphens, and underscores" autocomplete="username" required>
|
||||||
|
<label for="password">Create password</label>
|
||||||
|
<input type="password" id="password" name="password" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,255}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" autocomplete="new-password" required>
|
||||||
|
<label for="password2">Confirm password</label>
|
||||||
|
<input type="password" id="password2" name="password" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])(?!.*\s).{10,255}" title="10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces" autocomplete="new-password" required>
|
||||||
|
<span><input type="checkbox" name="remember" id="remember"> <label for="remember">Remember me</label></span>
|
||||||
|
<input type="submit" value="Sign up">
|
||||||
|
</form>
|
||||||
|
{%- endblock -%}
|
||||||
Reference in New Issue
Block a user