diff --git a/flask-backend/api/__init__.py b/flask-backend/api/__init__.py index 9ac0507c..fc2d843d 100644 --- a/flask-backend/api/__init__.py +++ b/flask-backend/api/__init__.py @@ -3,6 +3,8 @@ from flask_login import LoginManager from flask_marshmallow import Marshmallow from flask_cors import CORS, cross_origin +from dotenv import load_dotenv +from flask_jwt_extended import JWTManager db = SQLAlchemy() ma = Marshmallow() @@ -15,8 +17,14 @@ def create_app(): app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config["JWT_SECRET_KEY"] = "super-secret" # Change this! + jwt = JWTManager(app) + db.init_app(app) + # Load environment variables + load_dotenv() + login_manager = LoginManager() login_manager.login_view = 'auth.login' login_manager.init_app(app) @@ -26,7 +34,10 @@ def create_app(): @login_manager.user_loader def load_user(user_id): - return User.query.get(int(user_id)) + user = User.query.get(int(user_id)) + if user and user.is_verified: + return user + return None @login_manager.unauthorized_handler def unauthorized_handler(): diff --git a/flask-backend/api/helpers/mail.py b/flask-backend/api/helpers/mail.py new file mode 100644 index 00000000..6c6844af --- /dev/null +++ b/flask-backend/api/helpers/mail.py @@ -0,0 +1,25 @@ +import os +from flask_mail import Message, Mail + +from .. import create_app + +def send_confirmation_email(recipient, url): + app = create_app() + app.config.update(dict( + DEBUG = True, + MAIL_SERVER = os.getenv('MAIL_SERVER'), + MAIL_PORT = os.getenv('MAIL_PORT'), + MAIL_USE_TLS = True, + MAIL_USE_SSL = False, + MAIL_USERNAME = os.getenv('MAIL_USERNAME'), + MAIL_PASSWORD = os.getenv('MAIL_PASSWORD'), + )) + mail = Mail(app) + msg = Message('Confirm OpenMF account', sender='test@openmf.com', recipients=[recipient]) + msg.body = f'Please use this url to confirm your account: {url}' + try: + mail.send(msg) + except ConnectionRefusedError as err: + print("Mail Server not working") + print(err) + raise ConnectionRefusedError \ No newline at end of file diff --git a/flask-backend/api/models/models.py b/flask-backend/api/models/models.py index 172fa3b8..d2aa47d4 100644 --- a/flask-backend/api/models/models.py +++ b/flask-backend/api/models/models.py @@ -12,6 +12,7 @@ class User(UserMixin, db.Model): role = db.Column(db.String(20)) timestamp = db.Column(db.Float) has_admin = db.column_property(role != "adimn") + is_verified = db.Column(db.Boolean) _admin = db.Column(db.String(100)) def __init__(self, email, password, name, role, timestamp): @@ -21,6 +22,7 @@ def __init__(self, email, password, name, role, timestamp): self.role = role self.timestamp = timestamp self._admin = "Admin not assinged." + self.is_verified = False @hybrid_property def admin(self): @@ -50,7 +52,7 @@ def __init__(self, case_name, data_size, timestamp, extractor_id, data_path): class UserSchema(ma.Schema): class Meta: - fields = ('name', 'email', 'role', 'timestamp', 'admin') + fields = ('name', 'email', 'role', 'timestamp', 'admin', 'is_verified') class CaseSchema(ma.Schema): class Meta: diff --git a/flask-backend/api/routes/user.py b/flask-backend/api/routes/user.py index ffd952af..4db97d25 100644 --- a/flask-backend/api/routes/user.py +++ b/flask-backend/api/routes/user.py @@ -6,6 +6,8 @@ from werkzeug.security import generate_password_hash, check_password_hash from .. import db from sqlalchemy import update +from ..helpers.mail import send_confirmation_email +from flask_jwt_extended import create_access_token, decode_token user_schema = UserSchema() users_schema = UserSchema(many=True) @@ -104,7 +106,11 @@ def create_user(): # Add only admin can create functionality, once deployed on a db.session.add(new_user) db.session.commit() - return 'user created', 202 + token = create_access_token(email) + url = f'http://localhost:5000/confirm-account/{token}' + send_confirmation_email(email, url) + + return 'user created. Please confirm account via email', 202 # Route for admin to add user @@ -133,7 +139,11 @@ def add_users(): db.session.add(new_user) db.session.commit() - return 'user created', 202 + token = create_access_token(email) + url = f'http://localhost:5000/confirm-account/{token}' + send_confirmation_email(email, url) + + return 'user created. Please confirm account via email', 202 return "You can't add users, you are not an admin", 409 diff --git a/flask-backend/api/userAuthentication/auth.py b/flask-backend/api/userAuthentication/auth.py index f4f45ef3..b9427839 100644 --- a/flask-backend/api/userAuthentication/auth.py +++ b/flask-backend/api/userAuthentication/auth.py @@ -1,6 +1,7 @@ from flask import Blueprint, render_template, redirect, url_for, request, flash, request from werkzeug.security import generate_password_hash, check_password_hash from flask_login import login_user, logout_user, login_required +from flask_jwt_extended import decode_token from ..models.models import User from .. import db @@ -39,4 +40,16 @@ def login_post(): @login_required def logout(): logout_user() - return 'logged out successfully', 200 \ No newline at end of file + return 'logged out successfully', 200 + +@auth.route('/confirm-account/') +def confirm_account(token): + email = decode_token(token)['sub'] + user = User.query.filter_by(email=email).first() + + if not user: + return 'User does not exist', 404 + + user.is_verified = True + db.session.commit() + return 'Account confirmed', 200 \ No newline at end of file diff --git a/flask-backend/requirements.txt b/flask-backend/requirements.txt index ae143a4e..30a5e6f0 100644 --- a/flask-backend/requirements.txt +++ b/flask-backend/requirements.txt @@ -1,8 +1,12 @@ astroid==2.4.2 +blinker==1.4 click==7.1.2 +colorama==0.4.4 Flask==1.1.1 Flask-Cors==3.0.10 +Flask-JWT-Extended==4.0.2 Flask-Login==0.5.0 +Flask-Mail==0.9.0 flask-marshmallow==0.13.0 Flask-SQLAlchemy==2.4.4 isort==4.3.21 @@ -14,10 +18,13 @@ marshmallow==3.7.1 marshmallow-sqlalchemy==0.23.1 mccabe==0.6.1 pdfkit==0.6.1 +PyJWT==2.0.1 pylint==2.5.3 +python-dotenv==0.15.0 six==1.15.0 SQLAlchemy==1.3.18 toml==0.10.1 +typed-ast==1.4.2 typing==3.7.4.3 Werkzeug==1.0.1 wrapt==1.12.1