From c77661e9855eaf01efc99d9e9e271da8ee4200d8 Mon Sep 17 00:00:00 2001 From: crunk Date: Sun, 3 Dec 2023 11:33:07 +0100 Subject: [PATCH] user login scaffolding --- library/application/user/forgotpassword.py | 58 ++++++++++++++++ library/application/user/loginuser.py | 36 ++++++++++ library/application/user/registeruser.py | 70 +++++++++++++++++++ library/application/user/resetpassword.py | 81 ++++++++++++++++++++++ library/forms/forgotpasswordform.py | 19 +++++ library/forms/loginform.py | 23 ++++++ library/forms/registerform.py | 43 ++++++++++++ library/forms/resetpasswordform.py | 27 ++++++++ library/templates/user/forgotpassword.html | 29 ++++++++ library/templates/user/login.html | 26 +++++++ library/templates/user/register.html | 39 +++++++++++ library/templates/user/resetpassword.html | 29 ++++++++ 12 files changed, 480 insertions(+) create mode 100644 library/application/user/forgotpassword.py create mode 100644 library/application/user/loginuser.py create mode 100644 library/application/user/registeruser.py create mode 100644 library/application/user/resetpassword.py create mode 100644 library/forms/forgotpasswordform.py create mode 100644 library/forms/loginform.py create mode 100644 library/forms/registerform.py create mode 100644 library/forms/resetpasswordform.py create mode 100644 library/templates/user/forgotpassword.html create mode 100644 library/templates/user/login.html create mode 100644 library/templates/user/register.html create mode 100644 library/templates/user/resetpassword.html diff --git a/library/application/user/forgotpassword.py b/library/application/user/forgotpassword.py new file mode 100644 index 0000000..ad4250e --- /dev/null +++ b/library/application/user/forgotpassword.py @@ -0,0 +1,58 @@ +from uuid import uuid1 +from datetime import datetime +from sqlalchemy.exc import ( + DataError, + DatabaseError, + InterfaceError, + InvalidRequestError, +) +from flask import render_template +from flask_mail import Message + +from usermodel import User +from forms.forgotpasswordform import ForgotPasswordForm +from app import db + + +def ForgotPassword(mail): + forgotpasswordform = ForgotPasswordForm() + if forgotpasswordform.validate_on_submit(): + user = User.query.filter_by( + email=forgotpasswordform.email.data + ).first() + if user is not None: + resethash = AddResetPasswordHash(user, forgotpasswordform) + ResetPassWordMessage(user, resethash, mail) + forgotpasswordform.email.errors.append( + f"""If {forgotpasswordform.email.data} exists, an email is send with + a password reset link. (If your inbox doesn't + contain any new mail, please check your spam folder.)""" + ) + return render_template( + "forgotpassword.html", forgotpasswordform=forgotpasswordform + ) + + +def AddResetPasswordHash(user, forgotpasswordform): + resethash = uuid1().hex + try: + user.resettime = datetime.now() + user.resethash = resethash + db.session.commit() + except (InvalidRequestError, DataError, InterfaceError, DatabaseError): + forgotpasswordform.email.errors.append("Something went wrong!") + db.session.rollback() + return resethash + + +def ResetPassWordMessage(user, resethash, mail): + msg = Message( + "Forgotten Password ", + sender=("mailer", "test@this.com"), + recipients=[user.email], + ) + msg.html = f"""{user.username} has requested a password reset for +libary website.

+Click here to +reset your password.""" + mail.send(msg) diff --git a/library/application/user/loginuser.py b/library/application/user/loginuser.py new file mode 100644 index 0000000..2d5e349 --- /dev/null +++ b/library/application/user/loginuser.py @@ -0,0 +1,36 @@ +from flask import ( + render_template, + redirect, + request, + flash, + url_for, + abort, +) +from usermodel import User +from forms.loginform import LoginForm +from flask_login import login_user +from flask_bcrypt import check_password_hash + + +def LoginUser(): + loginform = LoginForm() + if loginform.validate_on_submit(): + try: + user = User.query.filter_by(email=loginform.email.data).first() + if user is None: + loginform.password.errors.append("Invalid email or password!") + return render_template("login.html", loginform=loginform) + if check_password_hash(user.password, loginform.password.data): + login_user(user) + flash("Logged in successfully.", "success") + next = request.args.get("next") + if next is not None and not is_safe_url(next): # noqa: F821 + return abort(400) + return redirect(next or url_for("index")) + else: + flash("Invalid email or password!", "danger") + loginform.password.errors.append("Invalid email or password!") + return render_template("login.html", loginform=loginform) + except Exception as e: + flash(e, "danger") + return render_template("login.html", loginform=loginform) diff --git a/library/application/user/registeruser.py b/library/application/user/registeruser.py new file mode 100644 index 0000000..722f3c8 --- /dev/null +++ b/library/application/user/registeruser.py @@ -0,0 +1,70 @@ +from flask import ( + render_template, + redirect, + flash, + url_for, +) +from sqlalchemy.exc import ( + IntegrityError, + DataError, + DatabaseError, + InterfaceError, + InvalidRequestError, +) +from werkzeug.routing import BuildError +from usermodel import User +from forms.registerform import RegisterForm +from flask_login import login_user +from flask_bcrypt import generate_password_hash +from app import db + + +def RegisterUser(): + registerform = RegisterForm() + if registerform.validate_on_submit(): + try: + username = registerform.username.data + email = registerform.email.data + password = registerform.confirmpassword.data + + newuser = User( + username=username, + email=email, + password=generate_password_hash(password), + ) + + db.session.add(newuser) + db.session.commit() + flash("Account Succesfully created", "success") + login_user(newuser) + return redirect(url_for("index")) + + except InvalidRequestError: + db.session.rollback() + registerform.email.errors.append("Something went wrong!") + flash("Something went wrong!", "danger") + except IntegrityError: + db.session.rollback() + registerform.email.errors.append("User already exists!") + flash("User already exists!", "warning") + except DataError: + db.session.rollback() + registerform.email.errors.append("Invalid Entry") + flash("Invalid Entry", "warning") + except InterfaceError: + db.session.rollback() + registerform.email.errors.append( + "Error connecting to the database" + ) + flash("Error connecting to the database", "danger") + except DatabaseError: + db.session.rollback() + registerform.email.errors.append( + "Error connecting to the database" + ) + flash("Error connecting to the database", "danger") + except BuildError: + db.session.rollback() + registerform.email.errors.append("Unknown error occured!") + flash("An error occured !", "danger") + return render_template("register.html", registerform=registerform) diff --git a/library/application/user/resetpassword.py b/library/application/user/resetpassword.py new file mode 100644 index 0000000..b802dab --- /dev/null +++ b/library/application/user/resetpassword.py @@ -0,0 +1,81 @@ +from datetime import datetime +from flask import ( + render_template, + redirect, + flash, + url_for, +) +from sqlalchemy.exc import ( + IntegrityError, + DataError, + DatabaseError, + InterfaceError, + InvalidRequestError, +) +from werkzeug.routing import BuildError +from usermodel import User +from forms.resetpasswordform import ResetPasswordForm +from flask_login import login_user +from flask_bcrypt import generate_password_hash +from app import db + + +def ResetPassword(path): + linkvalid = False + user = User.query.filter_by(resethash=path).first() + if user is None: + return redirect(url_for("index")) + timepassed = datetime.now() - user.resettime + if timepassed.days < 1: + linkvalid = True + + resetpasswordform = ResetPasswordForm() + if resetpasswordform.validate_on_submit(): + return ResetUserPasswordInDB(user, resetpasswordform) + return render_template( + "resetpassword.html", + resetpasswordform=resetpasswordform, + path=path, + linkvalid=linkvalid, + ) + + +def ResetUserPasswordInDB(user, resetpasswordform): + try: + newpassword = resetpasswordform.confirmpassword.data + user.password = generate_password_hash(newpassword) + user.resethash = None + user.resettime = None + db.session.commit() + flash("Password Succesfully updated", "success") + login_user(user) + return redirect(url_for("index")) + + except InvalidRequestError: + db.session.rollback() + resetpasswordform.email.errors.append("Something went wrong!") + flash("Something went wrong!", "danger") + except IntegrityError: + db.session.rollback() + resetpasswordform.email.errors.append("User already exists!") + flash("User already exists!", "warning") + except DataError: + db.session.rollback() + resetpasswordform.email.errors.append("Invalid Entry") + flash("Invalid Entry", "warning") + except InterfaceError: + db.session.rollback() + resetpasswordform.email.errors.append( + "Error connecting to the database" + ) + flash("Error connecting to the database", "danger") + except DatabaseError: + db.session.rollback() + resetpasswordform.email.errors.append( + "Error connecting to the database" + ) + flash("Error connecting to the database", "danger") + except BuildError: + db.session.rollback() + resetpasswordform.email.errors.append("Unknown error occured!") + flash("An error occured !", "danger") diff --git a/library/forms/forgotpasswordform.py b/library/forms/forgotpasswordform.py new file mode 100644 index 0000000..0ddc0d6 --- /dev/null +++ b/library/forms/forgotpasswordform.py @@ -0,0 +1,19 @@ +"""Forgotten password form to help user.""" +from wtforms import ( + StringField, + SubmitField, +) + +from wtforms import validators +from wtforms.validators import Length, Email +from flask_wtf import FlaskForm + + +class ForgotPasswordForm(FlaskForm): + """Forgotten password csv-library form""" + + email = StringField( + "Email address:", + validators=[validators.InputRequired(), Email(), Length(6, 64)], + ) + submit = SubmitField("Send email") diff --git a/library/forms/loginform.py b/library/forms/loginform.py new file mode 100644 index 0000000..262bb5d --- /dev/null +++ b/library/forms/loginform.py @@ -0,0 +1,23 @@ +"""Login form to validate user.""" +from wtforms import ( + StringField, + SubmitField, + PasswordField, +) + +from wtforms import validators +from wtforms.validators import Length, Email +from flask_wtf import FlaskForm + + +class LoginForm(FlaskForm): + """Login csv-library form""" + + email = StringField( + "Email address:", + validators=[validators.InputRequired(), Email(), Length(6, 64)], + ) + password = PasswordField( + "Password:", validators=[validators.InputRequired()] + ) + submit = SubmitField("Sign In") diff --git a/library/forms/registerform.py b/library/forms/registerform.py new file mode 100644 index 0000000..d1d6795 --- /dev/null +++ b/library/forms/registerform.py @@ -0,0 +1,43 @@ +"""Register form to make a new user.""" +from wtforms import ( + StringField, + SubmitField, + PasswordField, +) + +from wtforms import validators +from wtforms.validators import Length, Email, EqualTo, ValidationError +from flask_wtf import FlaskForm + + +class RegisterForm(FlaskForm): + """Register for csv-library form""" + + username = StringField( + "Username:", + validators=[validators.InputRequired(), Length(3, 150)], + ) + + email = StringField( + "Email address:", + validators=[ + validators.InputRequired(), + Email(), + Length(6, 64), + ] + ) + + password = PasswordField( + "New password:", + validators=[validators.InputRequired(), Length(12, 72)], + ) + + confirmpassword = PasswordField( + "Confirm your password:", + validators=[ + validators.InputRequired(), + Length(12, 72), + EqualTo("password", message="Passwords must match !"), + ], + ) + submit = SubmitField("Register to the library") diff --git a/library/forms/resetpasswordform.py b/library/forms/resetpasswordform.py new file mode 100644 index 0000000..d24da84 --- /dev/null +++ b/library/forms/resetpasswordform.py @@ -0,0 +1,27 @@ +"""Reset Password Form form to reset a users PasswordField.""" +from wtforms import ( + SubmitField, + PasswordField, +) + +from wtforms import validators +from wtforms.validators import Length, EqualTo +from flask_wtf import FlaskForm + + +class ResetPasswordForm(FlaskForm): + """ResetPassword for csv-library form""" + + password = PasswordField( + "New password:", + validators=[validators.InputRequired(), Length(12, 72)], + ) + confirmpassword = PasswordField( + "Confirm your password:", + validators=[ + validators.InputRequired(), + Length(12, 72), + EqualTo("password", message="Passwords must match !"), + ], + ) + submit = SubmitField("Reset your password") diff --git a/library/templates/user/forgotpassword.html b/library/templates/user/forgotpassword.html new file mode 100644 index 0000000..0656f61 --- /dev/null +++ b/library/templates/user/forgotpassword.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} +{% block main %} +
+
+

Forgot your password?

+

+ Enter the email address that was used to register with the library. +

+
+ {{ forgotpasswordform.csrf_token }} +
+ {{ forgotpasswordform.email.label }} + {{ forgotpasswordform.email }} + {% for message in forgotpasswordform.email.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ forgotpasswordform.submit }} +
+ + + +
+
+
+
+
+
+ {{ loginform.csrf_token }} +
+ {{ loginform.email.label }} + {{ loginform.email }} + {% for message in loginform.email.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ loginform.password.label }} + {{ loginform.password }} + {% for message in loginform.password.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ loginform.submit }} + Forgot Password? +
+
+ +{% endblock main %} diff --git a/library/templates/user/register.html b/library/templates/user/register.html new file mode 100644 index 0000000..729ba7d --- /dev/null +++ b/library/templates/user/register.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% block main %} +
+
+ {{ registerform.csrf_token }} +
+ {{ registerform.username.label }} + {{ registerform.username }} + {% for message in registerform.username.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ registerform.email.label }} + {{ registerform.email }} + {% for message in registerform.email.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ registerform.password.label }} + {{ registerform.password }} + {% for message in registerform.password.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ registerform.confirmpassword.label }} + {{ registerform.confirmpassword }} + {% for message in registerform.confirmpassword.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ registerform.submit }} +
+
+
+{% endblock main %} diff --git a/library/templates/user/resetpassword.html b/library/templates/user/resetpassword.html new file mode 100644 index 0000000..cb7cf54 --- /dev/null +++ b/library/templates/user/resetpassword.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} +{% block main %} +
+ {% if linkvalid%} +
+ {{ resetpasswordform.csrf_token }} +
+ {{ resetpasswordform.password.label }} + {{ resetpasswordform.password }} + {% for message in resetpasswordform.password.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ resetpasswordform.confirmpassword.label }} + {{ resetpasswordform.confirmpassword }} + {% for message in resetpasswordform.confirmpassword.errors %} +
{{ message }}
+ {% endfor %} +
+
+ {{ resetpasswordform.submit }} +
+
+ {% else %} +

Password reset link no longer valid.

+ {% endif %} +
+{% endblock main %}