[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
from sqlalchemy.orm.exc import NoResultFound
from werkzeug.exceptions import NotFound, BadRequest
import secrets
from flask import current_app
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.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):
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
if mail:
query |= User.mail == username
user = User.query.filter(query).one_or_none()
user = find_user(username)
if not user:
logger.debug("User not found in Database.")
user = User(userid=username)
@ -27,6 +24,49 @@ def login_user(username, password):
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
def 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)
if new_password:
# TODO: Password changed mail
logger.error(f"Password changed for user {user.userid}")
def get_users():
return User.query.all()
@ -76,6 +120,16 @@ def get_user(uid):
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):
current_app.config["FG_AUTH_BACKEND"].delete_user(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 typing import Optional
from datetime import date, datetime
from sqlalchemy.orm.collections import attribute_mapped_collection
from . import ModelSerializeMixin
from flaschengeist.database import db
from ..database import db
from . import ModelSerializeMixin, UtcDateTime
association_table = db.Table(
"user_x_role",
@ -103,6 +102,15 @@ class _UserAttribute(db.Model, ModelSerializeMixin):
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:
"""Wrapper class for avatar binaries"""