[System] Implemented password reset function in user controller

This commit is contained in:
Ferdinand Thiessen 2021-01-18 16:17:40 +01:00
parent 559c8c5c9c
commit 1f93bc6d80
2 changed files with 79 additions and 17 deletions

View File

@ -1,22 +1,19 @@
from flask import current_app, url_for import secrets
from sqlalchemy.orm.exc import NoResultFound from flask import current_app
from werkzeug.exceptions import NotFound, BadRequest from datetime import datetime, timedelta, timezone
from werkzeug.exceptions import NotFound, BadRequest, Forbidden
from flaschengeist.utils.hook import Hook
from flaschengeist.models.user import User, Role
from flaschengeist.database import db
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.database import db
from flaschengeist.utils.hook import Hook
from flaschengeist.models.user import User, Role, _PasswordReset
from flaschengeist.controller import messageController, sessionController
def login_user(username, password): def login_user(username, password):
logger.info("login user {{ {} }}".format(username)) logger.info("login user {{ {} }}".format(username))
mail = username.split("@")
mail = len(mail) == 2 and len(mail[0]) > 0 and len(mail[1]) > 0
query = User.userid == username user = find_user(username)
if mail:
query |= User.mail == username
user = User.query.filter(query).one_or_none()
if not user: if not user:
logger.debug("User not found in Database.") logger.debug("User not found in Database.")
user = User(userid=username) user = User(userid=username)
@ -27,6 +24,49 @@ def login_user(username, password):
return None return None
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(16)
subject = "Flaschengeist - Passwort zurücksetzten"
domain = "flaschengeist.local"
text = f"""Hallo {user.display_name},
Jemand hat das Zurücksetzen des Passworts für dein Flaschengeist Benutzerkonto angefordert.
Benutzername: {user.userid}
Falls das nicht beabsichtigt war, ignoriere diese E-Mail einfach. Es wird dann nichts passieren.
Um dein Passwort zurückzusetzen, besuche folgende Adresse, der Link ist 12 Stunden gültig:
<https://{domain}/reset?token={reset.token}>
"""
db.session.commit()
messageController.send_message(messageController.Message(user, text, subject))
def reset_password(token: str, password: str):
reset = _PasswordReset.query.filter(_PasswordReset.token == token).one_or_none()
logger.debug(f"Token is {'valid' if reset else 'invalid'}")
if not reset or reset.expires < datetime.now(tz=timezone.utc):
raise Forbidden
modify_user(reset.user, None, password)
sessionController.delete_sessions(reset.user)
db.session.delete(reset)
db.session.commit()
@Hook @Hook
def update_user(user): def update_user(user):
current_app.config["FG_AUTH_BACKEND"].update_user(user) current_app.config["FG_AUTH_BACKEND"].update_user(user)
@ -60,6 +100,10 @@ def modify_user(user, password, new_password=None):
""" """
current_app.config["FG_AUTH_BACKEND"].modify_user(user, password, new_password) current_app.config["FG_AUTH_BACKEND"].modify_user(user, password, new_password)
if new_password:
# TODO: Password changed mail
logger.error(f"Password changed for user {user.userid}")
def get_users(): def get_users():
return User.query.all() return User.query.all()
@ -76,6 +120,16 @@ def get_user(uid):
return user return user
def find_user(uid_mail):
mail = uid_mail.split("@")
mail = len(mail) == 2 and len(mail[0]) > 0 and len(mail[1]) > 0
query = User.userid == uid_mail
if mail:
query |= User.mail == uid_mail
return User.query.filter(query).one_or_none()
def delete(user): def delete(user):
current_app.config["FG_AUTH_BACKEND"].delete_user(user) current_app.config["FG_AUTH_BACKEND"].delete_user(user)
db.session.delete(user) db.session.delete(user)

View File

@ -1,11 +1,10 @@
from datetime import date
from typing import Optional
from flask import url_for from flask import url_for
from typing import Optional
from datetime import date, datetime
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
from . import ModelSerializeMixin from ..database import db
from flaschengeist.database import db from . import ModelSerializeMixin, UtcDateTime
association_table = db.Table( association_table = db.Table(
"user_x_role", "user_x_role",
@ -103,6 +102,15 @@ class _UserAttribute(db.Model, ModelSerializeMixin):
value: any = db.Column(db.PickleType(protocol=4)) value: any = db.Column(db.PickleType(protocol=4))
class _PasswordReset(db.Model):
"""Table containing password reset requests"""
__tablename__ = "password_reset"
_user_id: User = db.Column("user", db.Integer, db.ForeignKey("user.id"), primary_key=True)
user: User = db.relationship("User", foreign_keys=[_user_id])
token: str = db.Column(db.String(30))
expires: datetime = db.Column(UtcDateTime)
class _Avatar: class _Avatar:
"""Wrapper class for avatar binaries""" """Wrapper class for avatar binaries"""