diff --git a/app/__init__.py b/app/__init__.py
index cc38a61..c68fb02 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -3,6 +3,7 @@ from flask import Flask
from flask_bootstrap import Bootstrap
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
+from flask_mail import Mail
from flask_migrate import Migrate
from logging import getLogger
from logging.config import fileConfig
@@ -28,6 +29,7 @@ bootstrap = Bootstrap()
db = SQLAlchemy()
login = LoginManager()
login.login_view = 'auth.web_login'
+mail = Mail()
migrate = Migrate()
@@ -37,6 +39,7 @@ def create_app(config_class=Config):
bootstrap.init_app(app)
db.init_app(app)
login.init_app(app)
+ mail.init_app(app)
migrate.init_app(app, db, render_as_batch=True)
from app.auth import bp as auth_bp
diff --git a/app/auth/email.py b/app/auth/email.py
new file mode 100644
index 0000000..3bed0ee
--- /dev/null
+++ b/app/auth/email.py
@@ -0,0 +1,14 @@
+from flask import current_app
+from app.utils.email import send_email
+from app.utils.routes_utils import render_custom_template as render_template
+
+
+def send_password_reset_email(user):
+ token = user.get_reset_password_token()
+ send_email('[Scan2Kasse] Reset Your Password',
+ sender=current_app.config['ADMINS'][0],
+ recipients=[user.email],
+ text_body=render_template('email/reset_password.txt',
+ user=user, token=token),
+ html_body=render_template('email/reset_password.html',
+ user=user, token=token))
\ No newline at end of file
diff --git a/app/auth/forms.py b/app/auth/forms.py
index 7594222..8dfb21b 100644
--- a/app/auth/forms.py
+++ b/app/auth/forms.py
@@ -24,4 +24,14 @@ class RegistrationForm(FlaskForm):
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
- raise ValidationError('Please use a different email address.')
\ No newline at end of file
+ raise ValidationError('Please use a different email address.')
+
+class ResetPasswordRequestForm(FlaskForm):
+ email = StringField('Email', validators=[DataRequired(), Email()])
+ submit = SubmitField('Request Password Reset', render_kw={"class": "btn btn-primary mt-3"})
+
+class ResetPasswordForm(FlaskForm):
+ password = PasswordField('Password', validators=[DataRequired()])
+ password2 = PasswordField(
+ 'Repeat Password', validators=[DataRequired(), EqualTo('password')])
+ submit = SubmitField('Request Password Reset', render_kw={"class": "btn btn-primary mt-3"})
\ No newline at end of file
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 2f50377..f2c810e 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -1,6 +1,7 @@
from app import db
from app.auth import bp
-from app.auth.forms import LoginForm, RegistrationForm
+from app.auth.email import send_password_reset_email
+from app.auth.forms import LoginForm, RegistrationForm, ResetPasswordForm, ResetPasswordRequestForm
from app.models import User
from app.utils.routes_utils import render_custom_template as render_template
from flask import flash, redirect, request, url_for
@@ -41,4 +42,33 @@ def web_login():
@bp.route(f'/logout')
def web_logout():
logout_user()
- return redirect(url_for('main.index'))
\ No newline at end of file
+ return redirect(url_for('main.index'))
+
+@bp.route('/reset_password_request', methods=['GET', 'POST'])
+def reset_password_request():
+ if current_user.is_authenticated:
+ return redirect(url_for('index'))
+ form = ResetPasswordRequestForm()
+ if form.validate_on_submit():
+ user = User.query.filter_by(email=form.email.data).first()
+ if user:
+ send_password_reset_email(user)
+ flash('Check your email for the instructions to reset your password')
+ return redirect(url_for('auth.web_login'))
+ return render_template('auth/reset_password_request.html',
+ title='Reset Password', form=form)
+
+@bp.route('/reset_password/
+ Forgot Your Password? + Click to Reset It +
{% endblock %} \ No newline at end of file diff --git a/app/templates/auth/reset_password.html b/app/templates/auth/reset_password.html new file mode 100644 index 0000000..61153f5 --- /dev/null +++ b/app/templates/auth/reset_password.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block app_content %} +Dear User,
++ To reset your password + + click here + . +
+Alternatively, you can paste the following link in your browser's address bar:
+{{ url_for('auth.reset_password', token=token, _external=True) }}
+If you have not requested a password reset simply ignore this message.
+Sincerely,
+The Admins
\ No newline at end of file diff --git a/app/templates/email/reset_password.txt b/app/templates/email/reset_password.txt new file mode 100644 index 0000000..e35e60f --- /dev/null +++ b/app/templates/email/reset_password.txt @@ -0,0 +1,11 @@ +Dear User, + +To reset your password click on the following link: + +{{ url_for('auth.reset_password', token=token, _external=True) }} + +If you have not requested a password reset simply ignore this message. + +Sincerely, + +The Admins \ No newline at end of file diff --git a/app/utils/email.py b/app/utils/email.py new file mode 100644 index 0000000..2d50a80 --- /dev/null +++ b/app/utils/email.py @@ -0,0 +1,15 @@ +from app import mail +from flask import current_app +from flask_mail import Message +from threading import Thread + +def send_async_email(app, msg): + with app.app_context(): + mail.send(msg) + +def send_email(subject, sender, recipients, text_body, html_body): + msg = Message(subject, sender=sender, recipients=recipients) + msg.body = text_body + msg.html = html_body + Thread(target=send_async_email, + args=(current_app._get_current_object(), msg)).start() \ No newline at end of file diff --git a/configs/config.py b/configs/config.py index 958b259..e47ad8e 100644 --- a/configs/config.py +++ b/configs/config.py @@ -13,3 +13,9 @@ class Config(object): f"@{os.environ.get('DATABASE_HOST', 'localhost')}:{os.environ.get('DATABASE_PORT', '5432')}" f"/{os.environ.get('DATABASE_DB', '') or os.environ.get('DATABASE_USER', 'scan2kasse')}") SQLALCHEMY_TRACK_MODIFICATIONS = False + MAIL_SERVER = os.environ.get('MAIL_SERVER') + MAIL_PORT = int(os.environ.get('MAIL_PORT')) + MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None + MAIL_USERNAME = os.environ.get('MAIL_USERNAME') + MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') + ADMINS = ['postmaster@wpgcommunity.net'] \ No newline at end of file