diff --git a/CHANGELOG.md b/CHANGELOG.md index 892b56b..4cf2c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Matemat Changelog + +## Version 0.2.11 + +Feature release + +### Changes + + +- Feature: Permit user signup + + + + ## Version 0.2.10 diff --git a/matemat/__init__.py b/matemat/__init__.py index a0f6762..b890880 100644 --- a/matemat/__init__.py +++ b/matemat/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.2.10' +__version__ = '0.2.11' diff --git a/matemat/webserver/pagelets/__init__.py b/matemat/webserver/pagelets/__init__.py index d363652..b17da36 100644 --- a/matemat/webserver/pagelets/__init__.py +++ b/matemat/webserver/pagelets/__init__.py @@ -7,6 +7,7 @@ A new pagelet function must be imported here to be automatically loaded when the from .main import main_page from .login import login_page from .logout import logout +from .signup import signup from .touchkey import touchkey_page from .buy import buy from .deposit import deposit diff --git a/matemat/webserver/pagelets/admin.py b/matemat/webserver/pagelets/admin.py index 6ae506a..18126e3 100644 --- a/matemat/webserver/pagelets/admin.py +++ b/matemat/webserver/pagelets/admin.py @@ -163,14 +163,14 @@ def handle_admin_change(args: FormsDict, files: FormsDict, db: MatematDatabase): # The user requested to create a new user if change == 'newuser': # Only create a new user if all required properties of the user are present in the request arguments - if 'username' not in args or 'email' not in args or 'password' not in args: + if 'username' not in args or 'password' not in args: return # Read the properties from the request arguments username = str(args.username) - email = str(args.email) - # An empty e-mail field should be interpreted as NULL - if len(email) == 0: - email = None + email = None + if 'email' in args and len(args.email) > 0: + # An empty e-mail field should be interpreted as NULL + email = str(args.email) password = str(args.password) is_member = 'ismember' in args is_admin = 'isadmin' in args diff --git a/matemat/webserver/pagelets/main.py b/matemat/webserver/pagelets/main.py index 17ae4a0..8a521a6 100644 --- a/matemat/webserver/pagelets/main.py +++ b/matemat/webserver/pagelets/main.py @@ -34,4 +34,5 @@ def main_page(): # If no user is logged in, fetch the list of users and render the userlist template users = db.list_users(with_touchkey=True) return template.render('userlist.html', - users=users, setupname=config['InstanceName']) + users=users, setupname=config['InstanceName'], + signup=(config.get('SignupEnabled', '0') == '1')) diff --git a/matemat/webserver/pagelets/signup.py b/matemat/webserver/pagelets/signup.py new file mode 100644 index 0000000..bc68611 --- /dev/null +++ b/matemat/webserver/pagelets/signup.py @@ -0,0 +1,93 @@ + +import os +from shutil import copyfile +from io import BytesIO + +import magic +from bottle import get, post, redirect, abort, request, FormsDict +from PIL import Image + +from matemat.db import MatematDatabase +from matemat.db.primitives import User +from matemat.webserver import template, session +from matemat.webserver.config import get_app_config + + +def signup_user(args: FormsDict, files: FormsDict, db: MatematDatabase) -> User: + config = get_app_config() + if 'username' not in args or 'password' not in args or 'password2' not in args: + raise ValueError('Required fields missing') + username = str(args.username) + password = str(args.password) + password2 = str(args.password2) + if password != password2: + raise ValueError('Passwords do not match') + email = None + if 'email' in args and len(args.email) > 0: + email = str(args.email) + + new_user = db.create_user(username, password, email=email, admin=False, member=False) + # If a touchkey was provided, set it + if 'touchkey' in args and len(args.touchkey) > 0: + touchkey = str(args.touchkey) + db.change_touchkey(new_user, password, touchkey, verify_password=False) + # Finally, set the avatar, if provided + if 'avatar' in files: + avatar = files.avatar.file.read() + if len(avatar) > 0: + filemagic: magic.FileMagic = magic.detect_from_content(avatar) + if filemagic.mime_type.startswith('image/'): + abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/users/') + os.makedirs(abspath, exist_ok=True) + # Parse the image data + image: Image = Image.open(BytesIO(avatar)) + # Resize the image to 150x150 + image.thumbnail((150, 150), Image.LANCZOS) + # Write the image to the file + image.save(os.path.join(abspath, f'{new_user.id}.png'), 'PNG') + else: + # If a default avatar is set, copy it to the user's avatar path + # Create the absolute path of the upload directory + abspath: str = os.path.join(os.path.abspath(config['UploadDir']), 'thumbnails/users/') + # Derive the individual paths + default: str = os.path.join(abspath, 'default.png') + userimg: str = os.path.join(abspath, f'{new_user.id}.png') + # Copy the default image, if it exists + if os.path.exists(default): + copyfile(default, userimg, follow_symlinks=True) + return new_user + + +@get('/signup') +@post('/signup') +def signup(): + """ + The password login mechanism. If called via GET, render the UI template; if called via POST, attempt to log in with + the provided credentials (username and passsword). + """ + config = get_app_config() + session_id: str = session.start() + # If signup is not enabled, redirect to the main page + if config.get('SignupEnabled', '0') != '1': + redirect('/') + # If a user is already logged in, simply redirect to the main page, showing the product list + if session.has(session_id, 'authenticated_user'): + redirect('/') + # If requested via HTTP POST, read the request arguments and attempt to create a new user + if request.method == 'POST': + # Connect to the database and create the user + with MatematDatabase(config['DatabaseFile']) as db: + try: + user = signup_user(request.params, request.files, db) + except ValueError as e: + redirect('/signup') + # Set the user ID session variable + session.put(session_id, 'authenticated_user', user.id) + # Set the authlevel session variable (0 = none, 1 = touchkey, 2 = password login) + session.put(session_id, 'authentication_level', 2) + # Redirect to the main page, showing the product list + redirect('/') + elif request.method != 'GET': + abort(405, 'Method not allowed') + return template.render('signup.html', + setupname=config['InstanceName']) diff --git a/package/debian/matemat/DEBIAN/control b/package/debian/matemat/DEBIAN/control index 10edf70..94ff7e6 100644 --- a/package/debian/matemat/DEBIAN/control +++ b/package/debian/matemat/DEBIAN/control @@ -1,5 +1,5 @@ Package: matemat -Version: 0.2.10 +Version: 0.2.11 Maintainer: s3lph Section: web Priority: optional diff --git a/templates/signup.html b/templates/signup.html new file mode 100644 index 0000000..f829769 --- /dev/null +++ b/templates/signup.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block header %} +

Signup

+ {{ super() }} +{% endblock %} + +{% block main %} + + {# Show a username/password signup form #} +
+ +
+ + +
+ + +
+ + +
+ + +
+ +
+ + + +
+ + + + {{ super() }} + +{% endblock %} diff --git a/templates/userlist.html b/templates/userlist.html index 16c271a..40b44bc 100644 --- a/templates/userlist.html +++ b/templates/userlist.html @@ -23,6 +23,9 @@
{# Link to the password login #} Password login + {% if signup %} + Create account + {% endif %} {{ super() }}