diff --git a/backend/Dockerfile b/backend/Dockerfile index 2145c54..e6faad8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,7 +10,7 @@ RUN apt update && apt install -y \ WORKDIR /code COPY requirements.txt /code RUN target=/root/.cache/pip \ - pip3 install -r requirements.txt + pip3 install --root-user-action=ignore -q -r requirements.txt # Need to make this explicit as part of expansion, no migrations or venv COPY . . diff --git a/backend/README.md b/backend/README.md index e2b34d0..198de84 100644 --- a/backend/README.md +++ b/backend/README.md @@ -18,6 +18,8 @@ pip install flask-migrate pip install flask-login pip install email-validator pip install pydenticon +pip install flask-mail +pip install pyjwt Prod only, require sys packages: pip install mariadb pip install uwsgi diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 8d36e49..758e496 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -5,6 +5,7 @@ from flask_migrate import Migrate from flask_login import LoginManager import logging, sys from logging.handlers import SMTPHandler +from flask_mail import Mail app = Flask(__name__) app.config.from_object(Config) @@ -12,6 +13,7 @@ db = SQLAlchemy(app) migrate = Migrate(app, db) login = LoginManager(app) login.login_view = 'login' +mail=Mail(app) if not app.debug: if app.config['MAIL_SERVER']: diff --git a/backend/app/email.py b/backend/app/email.py new file mode 100644 index 0000000..2adc7a8 --- /dev/null +++ b/backend/app/email.py @@ -0,0 +1,9 @@ +from flask_mail import Message +from app import mail + +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 + mail.send(msg) + diff --git a/backend/app/forms.py b/backend/app/forms.py index 5d50a5c..25249ca 100644 --- a/backend/app/forms.py +++ b/backend/app/forms.py @@ -43,6 +43,10 @@ class EditProfileForm(FlaskForm): if user is not None: raise ValidationError('Please use a different username.') +class ResetPasswordRequestForm(FlaskForm): + email = StringField('Email', validators=[DataRequired(), Email()]) + submit = SubmitField('Request Password Reset') + class PostForm(FlaskForm): post = TextAreaField('Post:', validators=[DataRequired(), Length(min=1, max=140)]) submit = SubmitField('Submit') diff --git a/backend/app/routes.py b/backend/app/routes.py index d2ae9a9..099877a 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -5,8 +5,9 @@ from datetime import datetime, timezone import sqlalchemy as sa from app import app, db -from app.forms import LoginForm, RegistrationForm, EditProfileForm, EmptyForm, PostForm +from app.forms import LoginForm, RegistrationForm, EditProfileForm, EmptyForm, PostForm, ResetPasswordRequestForm from app.models import User, Post +#from app.email import send_password_reset_email @app.before_request def before_request(): @@ -149,4 +150,17 @@ def unfollow(username): else: return redirect(url_for('index')) +@app.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 = db.session.scalar(sa.select(User).where(User.email == form.email.data)) + if user: + send_password_reset_email(user) + flash('Password reset sent.') + return redirect(url_for('login')) + return render_template('reset_password_request.html', title='Reset Password', form=form) + diff --git a/backend/app/templates/login.html b/backend/app/templates/login.html index c604808..9bb8d2d 100644 --- a/backend/app/templates/login.html +++ b/backend/app/templates/login.html @@ -23,6 +23,7 @@
+ {% endblock %} diff --git a/backend/app/templates/reset_password_request.html b/backend/app/templates/reset_password_request.html new file mode 100644 index 0000000..da2b56e --- /dev/null +++ b/backend/app/templates/reset_password_request.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} +