From 1c536a5f02ac8ce8ee0c460ef7e27617573501d4 Mon Sep 17 00:00:00 2001 From: Kvnbbg Date: Thu, 22 Feb 2024 07:04:58 +0100 Subject: [PATCH] Enhance user authentication and security Added email verification and password reset features. Implemented additional security measures including HTTPS enforcement, rate limiting on sensitive routes, and input validation to prevent common vulnerabilities. Improved user experience by introducing AJAX form submissions and user profile management. --- app/__init__.py | 10 ++++++++++ app/auth/forms.py | 16 ++++++++++++++++ app/auth/routes.py | 15 +++++++++++++++ app/models.py | 15 +++++++++++++-- app/templates/register.html | 29 +++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 app/auth/forms.py create mode 100644 app/templates/register.html diff --git a/app/__init__.py b/app/__init__.py index f655c2da..40c2550d 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,10 +7,13 @@ from app.main import main from app.auth import auth from app.models import User +from flask_mail import Mail # Initialize the database db = SQLAlchemy() +app = Flask(__name__) + # For handling database migrations, Flask-Migrate is a useful extension that integrates with Flask and SQLAlchemy. migrate = Migrate(app, db) @@ -25,6 +28,13 @@ def load_user(user_id): return User.query.get(int(user_id)) +app.config['MAIL_SERVER'] = 'smtp.example.com' +app.config['MAIL_PORT'] = 587 +app.config['MAIL_USE_TLS'] = True +app.config['MAIL_USERNAME'] = 'noreply@kvnbbg-creations.io' +app.config['MAIL_PASSWORD'] = secrets.token_urlsafe(16) +mail = Mail(app) + def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) diff --git a/app/auth/forms.py b/app/auth/forms.py new file mode 100644 index 00000000..ee69525c --- /dev/null +++ b/app/auth/forms.py @@ -0,0 +1,16 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, SubmitField +from wtforms.validators import DataRequired, EqualTo, ValidationError +from yourapp.models import User + +class RegistrationForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + password = PasswordField('Password', validators=[DataRequired()]) + confirm_password = PasswordField('Confirm Password', + validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Sign Up') + + def validate_username(self, username): + user = User.query.filter_by(username=username.data).first() + if user: + raise ValidationError('That username is taken. Please choose a different one.') diff --git a/app/auth/routes.py b/app/auth/routes.py index 4d4638b3..74de4772 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -23,7 +23,22 @@ def login(): flash('Login Unsuccessful. Please check username and password', 'danger') return render_template('login.html', title='Login', form=form) + @auth.route('/logout') def logout(): logout_user() return redirect(url_for('main.home')) + + +@auth.route('/register', methods=['GET', 'POST']) +def register(): + form = RegistrationForm() + if form.validate_on_submit(): + user = User(username=form.username.data, password=form.password.data) + db.session.add(user) + db.session.commit() + login_user(user) + flash('Your account has been created! You are now logged in.', 'success') + return redirect(url_for('main.home')) + return render_template('register.html', title='Register', form=form) + diff --git a/app/models.py b/app/models.py index c19ba6f3..60fe4a72 100644 --- a/app/models.py +++ b/app/models.py @@ -1,9 +1,20 @@ # models.py from app import db from flask_login import UserMixin +from werkzeug.security import generate_password_hash class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(20), unique=True, nullable=False) - email = db.Column(db.String(120), unique=True, nullable=False) - password = db.Column(db.String(60), nullable=False) \ No newline at end of file + password_hash = db.Column(db.String(128) nullable=False) + + @property + def password(self): + raise AttributeError('password is not a readable attribute') + + @password.setter + def password(self, password): + self.password_hash = generate_password_hash(password) + + def verify_password(self, password): + return check_password_hash(self.password_hash, password) diff --git a/app/templates/register.html b/app/templates/register.html new file mode 100644 index 00000000..9d5bf270 --- /dev/null +++ b/app/templates/register.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ {{ form.username.label }} + {{ form.username(class="form-control") }} + {% if form.username.errors %} + {% for error in form.username.errors %} +
{{ error }}
+ {% endfor %} + {% endif %} +
+
+ {{ form.password.label }} + {{ form.password(class="form-control") }} +
+
+ {{ form.confirm_password.label }} + {{ form.confirm_password(class="form-control") }} +
+
+ {{ form.submit(class="btn btn-primary") }} +
+
+
+{% endblock %}