diff --git a/webinterface/minibase/__init__.py b/webinterface/minibase/__init__.py index 578b180c..c9ac9c9a 100644 --- a/webinterface/minibase/__init__.py +++ b/webinterface/minibase/__init__.py @@ -2,9 +2,11 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_bcrypt import Bcrypt from flask_login import LoginManager +from flask_mail import Mail +import os app = Flask(__name__) -app.config['SECRET_KEY'] = '57636aef33666affr6' +app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' db = SQLAlchemy(app) bcrypt = Bcrypt(app) @@ -12,4 +14,12 @@ login_manager = LoginManager(app) login_manager.login_view = 'login' login_manager.login_message_category = 'info' +app.config['MAIL_SERVER']= 'smtp.googlemail.com' +app.config['MAIL_PORT']= 465 +app.config['MAIL_USE_TLS']= False +app.config['MAIL_USE_SSL']= True +app.config['MAIL_USERNAME']= 'kerem.yollu@gmail.com' +app.config['MAIL_PASSWORD']= 'eoilsjxjwmnfjbbj' +mail = Mail(app) + from minibase import route diff --git a/webinterface/minibase/__pycache__/__init__.cpython-311.pyc b/webinterface/minibase/__pycache__/__init__.cpython-311.pyc index 3aef9a32..c6ad3d3a 100644 Binary files a/webinterface/minibase/__pycache__/__init__.cpython-311.pyc and b/webinterface/minibase/__pycache__/__init__.cpython-311.pyc differ diff --git a/webinterface/minibase/__pycache__/forms.cpython-311.pyc b/webinterface/minibase/__pycache__/forms.cpython-311.pyc index 9812146c..1a341918 100644 Binary files a/webinterface/minibase/__pycache__/forms.cpython-311.pyc and b/webinterface/minibase/__pycache__/forms.cpython-311.pyc differ diff --git a/webinterface/minibase/__pycache__/models.cpython-311.pyc b/webinterface/minibase/__pycache__/models.cpython-311.pyc index 843a8d23..6f89b6b8 100644 Binary files a/webinterface/minibase/__pycache__/models.cpython-311.pyc and b/webinterface/minibase/__pycache__/models.cpython-311.pyc differ diff --git a/webinterface/minibase/__pycache__/route.cpython-311.pyc b/webinterface/minibase/__pycache__/route.cpython-311.pyc index 410e1dae..d3c9bcad 100644 Binary files a/webinterface/minibase/__pycache__/route.cpython-311.pyc and b/webinterface/minibase/__pycache__/route.cpython-311.pyc differ diff --git a/webinterface/minibase/forms.py b/webinterface/minibase/forms.py index 7a0b3fc4..4b4e1760 100644 --- a/webinterface/minibase/forms.py +++ b/webinterface/minibase/forms.py @@ -6,10 +6,14 @@ from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationE from minibase.models import User class registrationForm(FlaskForm): - username = StringField('User Name', validators=[DataRequired(),Length(min = 3, max = 20)]) - email = StringField('Email', validators=[DataRequired(),Email()]) - password = PasswordField('Password', validators=[DataRequired()]) - password_confirm = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) + username = StringField('User Name', + validators=[DataRequired(),Length(min = 3, max = 20)]) + email = StringField('Email', + validators=[DataRequired(),Email()]) + password = PasswordField('Password', + validators=[DataRequired()]) + password_confirm = PasswordField('Confirm Password', + validators=[DataRequired(), EqualTo('password')]) submit = SubmitField('Sing Up') def validate_username(self, username): @@ -23,14 +27,16 @@ class registrationForm(FlaskForm): raise ValidationError('That email is taken do you have an acocunt ?') class loginForm(FlaskForm): - email = StringField('Email',validators=[DataRequired(),Email()]) - password = PasswordField('Password',validators=[DataRequired()]) + email = StringField('Email', + validators=[DataRequired(),Email()]) + password = PasswordField('Password', + validators=[DataRequired()]) remember = BooleanField('Remember Me') submit = SubmitField('Log In') class updateAccountForm(FlaskForm): username = StringField('User Name', - validators=[DataRequired(),Length(min = 3, max = 20)]) + validators=[DataRequired(),Length(min = 3, max = 20)]) email = StringField('Email', validators=[DataRequired(),Email()]) picture = FileField('Update Profile Picture', @@ -50,6 +56,26 @@ class updateAccountForm(FlaskForm): raise ValidationError('That email is taken do you have an acocunt ?') class postForm(FlaskForm): - title = StringField('Title', validators=[DataRequired(),Length(min = 3, max = 100)]) - content = TextAreaField('Content', validators=[DataRequired()]) + title = StringField('Title', + validators=[DataRequired(),Length(min = 3, max = 100)]) + content = TextAreaField('Content', + validators=[DataRequired()]) submit = SubmitField('Post') + +class requestResetForm(FlaskForm): + email = StringField('Email', + validators=[DataRequired(),Email()]) + submit = SubmitField('Request Password Reset') + + def validate_email(self, email): + email = User.query.filter_by(email=email.data).first() + if email is None: + raise ValidationError('There is no Account with this email your must register first.') + +class resetPasswordForm(FlaskForm): + password = PasswordField('Password', + validators=[DataRequired()]) + password_confirm = PasswordField('Confirm Password', + validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Reset Password') + diff --git a/webinterface/minibase/models.py b/webinterface/minibase/models.py index ea76f3b1..f1027200 100644 --- a/webinterface/minibase/models.py +++ b/webinterface/minibase/models.py @@ -1,7 +1,9 @@ +from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from datetime import datetime -from minibase import db, login_manager +from minibase import db, login_manager, app from flask_login import UserMixin + @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id)) @@ -14,6 +16,19 @@ class User(db.Model, UserMixin): password = db.Column(db.String(60), nullable=False) posts = db.relationship('Post', backref='author', lazy=True) + def get_reset_token(self, expires_sec=1800): + s = Serializer(app.config['SECRET_KEY'], expires_sec) + return s.dumps({'user_id': self.id}).decode('utf-8') + + @staticmethod + def verify_reset_token(token): + s = Serializer(app.config['SECRET_KEY']) + try: + user_id = s.loads(token)['user_id'] + except: + return none + return User.query.get(user_id) + def __repr__(self): return f"User('{self.username}', '{self.email}', '{self.image_file}')" @@ -26,3 +41,4 @@ class Post(db.Model): def __repr__(self): return f"User('{self.title}', '{self.date_posted}')" + diff --git a/webinterface/minibase/route.py b/webinterface/minibase/route.py index f7303167..4d241c02 100644 --- a/webinterface/minibase/route.py +++ b/webinterface/minibase/route.py @@ -2,11 +2,11 @@ import os import secrets from PIL import Image from flask import render_template, url_for, flash, redirect, request, abort -from minibase import app, db, bcrypt -from minibase.forms import registrationForm, loginForm, updateAccountForm, postForm +from minibase import app, db, bcrypt, mail +from minibase.forms import registrationForm, loginForm, updateAccountForm, postForm, requestResetForm, resetPasswordForm from minibase.models import User, Post from flask_login import login_user, current_user, logout_user, login_required - +from flask_mail import Message #Redirect from / and also /home routes to the / @app.route("/") @@ -33,7 +33,7 @@ def register(): db.session.add(user) db.session.commit() flash(f'Your account has benn created you can now log in!','success') - return redirect(url_for('home')) + return redirect(url_for('login')) return render_template('register.html', title='Register', form=form) @@ -141,3 +141,43 @@ def user_posts(username): .order_by(Post.date_posted.asc())\ .paginate(page=page, per_page=2) return render_template('user_posts.html', posts=posts, user=user) + + +def send_reset_email(user): + token = user.get_reset_token() + msg = Message('Password Reset Request', + sender='noreply@demo.com', + recipients=[user.email]) + msg.body = f'''To reset your password, visit the following link: +{url_for('reset_token', token=token, _external=True)} +If you didn't make this request, then simply ingnore this email and no chancges will be made. +''' + mail.send(msg) + +@app.route("/reset_password", methods=['GET', 'POST']) +def reset_request(): + if current_user.is_authenticated: + return redirect(url_for('home')) + form = requestResetForm() + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data).first() + send_reset_email(user) + flash('An Email has benn sent with instruction to reset your password', 'warning') + return render_template('reset_request.html', title='Reset Password', form=form) + +@app.route("/reset_password/", methods=['GET', 'POST']) +def reset_token(token): + if current_user.is_authenticated: + return redirect(url_for('home')) + user = User.verify_reset_token(token) + if user is None: + flash('That is an invalid or expired token', 'warning') + return redirect(url_for('reset_request')) + form = resetPasswordForm() + if form.validate_on_submit(): + hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8') + user.password = hashed_pw + db.session.commit() + flash(f'Your password has benn udated','success') + return redirect(url_for('login')) + return render_template('reset_token.html', title='Reset Password', form=form) diff --git a/webinterface/minibase/site.db b/webinterface/minibase/site.db index 5fa8ce3a..2cf6104d 100644 Binary files a/webinterface/minibase/site.db and b/webinterface/minibase/site.db differ diff --git a/webinterface/minibase/templates/login.html b/webinterface/minibase/templates/login.html index b6556b87..207c7009 100644 --- a/webinterface/minibase/templates/login.html +++ b/webinterface/minibase/templates/login.html @@ -40,7 +40,7 @@ {{ form.submit(class="btn btn-outline-info") }} - Forgot Password? + Forgot Password? diff --git a/webinterface/minibase/templates/reset_request.html b/webinterface/minibase/templates/reset_request.html new file mode 100644 index 00000000..bc3863c1 --- /dev/null +++ b/webinterface/minibase/templates/reset_request.html @@ -0,0 +1,27 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ Reset Password +
+ {{ form.email.label(class="form-control-label") }} + {% if form.email.errors %} + {{ form.email(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.email.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.email(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+{% endblock content %} diff --git a/webinterface/minibase/templates/reset_token.html b/webinterface/minibase/templates/reset_token.html new file mode 100644 index 00000000..f0ed2b78 --- /dev/null +++ b/webinterface/minibase/templates/reset_token.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ Reset Password +
+ {{ form.password.label(class="form-control-label") }} + {% if form.password.errors %} + {{ form.password(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.password_confirm.label(class="form-control-label") }} + {% if form.password_confirm.errors %} + {{ form.password_confirm(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password_confirm.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password_confirm(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+{% endblock content %}