From 69ec4472c3ecd3e02551be09de5b3faea3bd87d7 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 21 Jan 2021 14:08:06 +0100 Subject: [PATCH] [System][Plugin] users: Send users a link to set their own password and initially set random password --- flaschengeist.example.toml | 5 +++ flaschengeist/controller/userController.py | 52 ++++++++++++---------- flaschengeist/plugins/users/__init__.py | 9 +++- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/flaschengeist.example.toml b/flaschengeist.example.toml index efe1efe..3f4ff49 100644 --- a/flaschengeist.example.toml +++ b/flaschengeist.example.toml @@ -41,6 +41,11 @@ welcome_subject = "Welcome to Flaschengeist {name}" welcome_text = ''' Hello {name}! Welcome to Flaschengeist! + +Your username is {username}, please set your password: +{password_link} +(If the link expires, please just use the Forgot-Password-function). + Have fun :) ''' diff --git a/flaschengeist/controller/userController.py b/flaschengeist/controller/userController.py index ed930e5..3f0c9b0 100644 --- a/flaschengeist/controller/userController.py +++ b/flaschengeist/controller/userController.py @@ -11,6 +11,23 @@ from flaschengeist.models.user import User, Role, _PasswordReset from flaschengeist.controller import messageController, sessionController +def _generate_password_reset(user): + """Generate a password reset link for the user""" + reset = _PasswordReset.query.get(user._id) + if not reset: + reset = _PasswordReset(_user_id=user._id) + db.session.add(reset) + + expires = datetime.now(tz=timezone.utc) + if not reset.expires or reset.expires < expires: + expires = expires + timedelta(hours=12) + reset.expires = expires + reset.token = secrets.token_urlsafe(24) + db.session.commit() + + return reset + + def login_user(username, password): logger.info("login user {{ {} }}".format(username)) @@ -27,17 +44,7 @@ def login_user(username, password): def request_reset(user: User): logger.debug(f"New password reset request for {user.userid}") - reset = _PasswordReset.query.get(user._id) - if not reset: - reset = _PasswordReset(_user_id=user._id) - db.session.add(reset) - - expires = datetime.now(tz=timezone.utc) - if not reset.expires or reset.expires < expires: - expires = expires + timedelta(hours=12) - reset.expires = expires - reset.token = secrets.token_urlsafe(24) - db.session.commit() + reset = _generate_password_reset(user) subject = str(config["MESSAGES"]["password_subject"]).format(name=user.display_name, username=user.userid) text = str(config["MESSAGES"]["password_text"]).format( @@ -161,27 +168,26 @@ def delete(user): def register(data): - for required in ["firstname", "lastname", "mail"]: - if required not in data: - raise BadRequest("Missing required parameters") allowed_keys = User().serialize().keys() values = {key: value for key, value in data.items() if key in allowed_keys} roles = values.pop("roles", []) user = User(**values) set_roles(user, roles) - current_app.config["FG_AUTH_BACKEND"].create_user(user, data["password"]) - + password = secrets.token_bytes(16) + current_app.config["FG_AUTH_BACKEND"].create_user(user, password) db.session.add(user) db.session.commit() - if user.mail and len(user.mail) > 3: - subject = str(config["MESSAGES"]["welcome_subject"]).format(name=user.display_name, username=user.userid) - text = str(config["MESSAGES"]["welcome_text"]).format( - name=user.display_name, - username=user.userid, - ) - messageController.send_message(messageController.Message(user, text, subject)) + reset = _generate_password_reset(user) + + subject = str(config["MESSAGES"]["welcome_subject"]).format(name=user.display_name, username=user.userid) + text = str(config["MESSAGES"]["welcome_text"]).format( + name=user.display_name, + username=user.userid, + password_link=f'https://{config["FLASCHENGEIST"]["domain"]}/reset?token={reset.token}' + ) + messageController.send_message(messageController.Message(user, text, subject)) return user diff --git a/flaschengeist/plugins/users/__init__.py b/flaschengeist/plugins/users/__init__.py index 24e4968..f5ed33d 100644 --- a/flaschengeist/plugins/users/__init__.py +++ b/flaschengeist/plugins/users/__init__.py @@ -24,17 +24,19 @@ _permission_register = "users_register" class UsersPlugin(Plugin): - def __init__(self, config): + def __init__(self, cfg): super().__init__(blueprint=users_bp, permissions=[_permission_edit, _permission_delete, _permission_set_roles]) @users_bp.route("/users", methods=["POST"]) def register(): """Register a new user + The password will be set to a random string of at lease 16byte entropy. + The user will receive a mail containing a link to set their own password. Route: ``/users`` | Method: ``POST`` - POST-data: Same as `flaschengeist.models.user.User` + ``password?: string`` + POST-data: Same as `flaschengeist.models.user.User` Returns: JSON encoded `flaschengeist.models.user.User` or HTTP error @@ -49,6 +51,9 @@ def register(): data = request.get_json() if not data: raise BadRequest + for required in ["firstname", "lastname", "mail"]: + if required not in data: + raise BadRequest("Missing required parameters") logger.debug("Register new User...") return make_response(jsonify(userController.register(data)), CREATED)