diff --git a/common/extensions.py b/common/extensions.py index f36134b..deeafdd 100644 --- a/common/extensions.py +++ b/common/extensions.py @@ -10,10 +10,11 @@ from flask_jwt_extended import JWTManager from flask_session import Session from flask_wtf import CSRFProtect -# from .utils.key_encryption import JosKMSClient +from .utils.nginx_utils import prefixed_url_for from .utils.simple_encryption import SimpleEncryption from .utils.minio_utils import MinioClient + # Create extensions db = SQLAlchemy() migrate = Migrate() diff --git a/common/models/user.py b/common/models/user.py index 69e17f2..c700613 100644 --- a/common/models/user.py +++ b/common/models/user.py @@ -126,7 +126,7 @@ class User(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) user_name = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(255), unique=True, nullable=False) - password = db.Column(db.String(255), nullable=False) + password = db.Column(db.String(255), nullable=True) first_name = db.Column(db.String(80), nullable=False) last_name = db.Column(db.String(80), nullable=False) active = db.Column(db.Boolean) diff --git a/common/utils/security_utils.py b/common/utils/security_utils.py index 4a3a897..f86bad1 100644 --- a/common/utils/security_utils.py +++ b/common/utils/security_utils.py @@ -1,6 +1,7 @@ from flask import current_app, render_template from flask_mailman import EmailMessage from itsdangerous import URLSafeTimedSerializer +import socket from common.utils.nginx_utils import prefixed_url_for @@ -35,15 +36,66 @@ def generate_confirmation_token(email): def send_confirmation_email(user): current_app.logger.debug(f'Sending confirmation email to {user.email}') + + if not test_smtp_connection(): + raise Exception("Failed to connect to SMTP server") + token = generate_confirmation_token(user.email) confirm_url = prefixed_url_for('security_bp.confirm_email', token=token, _external=True) current_app.logger.debug(f'Confirmation URL: {confirm_url}') + html = render_template('email/activate.html', confirm_url=confirm_url) - send_email(user.email, "Confirm your email", html) + subject = "Please confirm your email" + + try: + send_email(user.email, "Confirm your email", html) + current_app.logger.info(f'Confirmation email sent to {user.email}') + except Exception as e: + current_app.logger.error(f'Failed to send confirmation email to {user.email}. Error: {str(e)}') + raise def send_reset_email(user): + current_app.logger.debug(f'Sending reset email to {user.email}') token = generate_reset_token(user.email) reset_url = prefixed_url_for('security_bp.reset_password', token=token, _external=True) + current_app.logger.debug(f'Reset URL: {reset_url}') + html = render_template('email/reset_password.html', reset_url=reset_url) - send_email(user.email, "Reset Your Password", html) + subject = "Reset Your Password" + + try: + send_email(user.email, "Reset Your Password", html) + current_app.logger.info(f'Reset email sent to {user.email}') + except Exception as e: + current_app.logger.error(f'Failed to send reset email to {user.email}. Error: {str(e)}') + raise + + +def test_smtp_connection(): + try: + current_app.logger.info(f"Attempting to resolve google.com...") + google_ip = socket.gethostbyname('google.com') + current_app.logger.info(f"Successfully resolved google.com to {google_ip}") + except Exception as e: + current_app.logger.error(f"Failed to resolve google.com: {str(e)}") + + try: + smtp_server = current_app.config['MAIL_SERVER'] + current_app.logger.info(f"Attempting to resolve {smtp_server}...") + smtp_ip = socket.gethostbyname(smtp_server) + current_app.logger.info(f"Successfully resolved {smtp_server} to {smtp_ip}") + except Exception as e: + current_app.logger.error(f"Failed to resolve {smtp_server}: {str(e)}") + + try: + smtp_server = current_app.config['MAIL_SERVER'] + smtp_port = current_app.config['MAIL_PORT'] + sock = socket.create_connection((smtp_server, smtp_port), timeout=10) + sock.close() + current_app.logger.info(f"Successfully connected to SMTP server {smtp_server}:{smtp_port}") + return True + except Exception as e: + current_app.logger.error(f"Failed to connect to SMTP server: {str(e)}") + return False + diff --git a/config/config.py b/config/config.py index d37db88..50a90a3 100644 --- a/config/config.py +++ b/config/config.py @@ -3,6 +3,7 @@ from datetime import timedelta import redis from common.utils.prompt_loader import load_prompt_templates +from eveai_app.views.security_forms import ResetPasswordForm basedir = path.abspath(path.dirname(__file__)) @@ -41,6 +42,11 @@ class Config(object): SECURITY_POST_LOGIN_VIEW = '/user/tenant_overview' SECURITY_RECOVERABLE = True SECURITY_EMAIL_SENDER = "eveai_super@flow-it.net" + SECURITY_EMAIL_SUBJECT_PASSWORD_RESET = 'Reset Your Password' + SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE = 'Your Password Has Been Reset' + SECURITY_EMAIL_PLAINTEXT = False + SECURITY_EMAIL_HTML = True + SECURITY_RESET_PASSWORD_FORM = ResetPasswordForm # Ensure Flask-Security-Too is handling CSRF tokens when behind a proxy SECURITY_CSRF_PROTECT_MECHANISMS = ['session'] @@ -123,6 +129,15 @@ class Config(object): "LLM": {"name": "LLM", "description": "Algorithm using information integrated in the used LLM"} } + # flask-mailman settings + MAIL_SERVER = environ.get('MAIL_SERVER') + MAIL_PORT = int(environ.get('MAIL_PORT', 465)) + MAIL_USE_TLS = False + MAIL_USE_SSL = True + MAIL_USERNAME = environ.get('MAIL_USERNAME') + MAIL_PASSWORD = environ.get('MAIL_PASSWORD') + MAIL_DEFAULT_SENDER = ('eveAI Admin', MAIL_USERNAME) + class DevConfig(Config): DEVELOPMENT = True @@ -138,15 +153,6 @@ class DevConfig(Config): SQLALCHEMY_DATABASE_URI = f'postgresql+pg8000://{DB_USER}:{DB_PASS}@{DB_HOST}:5432/{DB_NAME}' SQLALCHEMY_BINDS = {'public': SQLALCHEMY_DATABASE_URI} - # flask-mailman settings - MAIL_SERVER = 'mail.flow-it.net' - MAIL_PORT = 587 - MAIL_USE_TLS = True - MAIL_USE_SSL = False - MAIL_DEFAULT_SENDER = ('eveAI Admin', 'eveai_admin@flow-it.net') - MAIL_USERNAME = environ.get('MAIL_USERNAME') - MAIL_PASSWORD = environ.get('MAIL_PASSWORD') - # Define the nginx prefix used for the specific apps EVEAI_APP_LOCATION_PREFIX = '/admin' EVEAI_CHAT_LOCATION_PREFIX = '/chat' diff --git a/docker/compose_dev.yaml b/docker/compose_dev.yaml index 3e2b28f..e2ef793 100644 --- a/docker/compose_dev.yaml +++ b/docker/compose_dev.yaml @@ -19,7 +19,9 @@ x-common-variables: &common-variables SECRET_KEY: '97867c1491bea5ee6a8e8436eb11bf2ba6a69ff53ab1b17ecba450d0f2e572e1' SECURITY_PASSWORD_SALT: '228614859439123264035565568761433607235' MAIL_USERNAME: eveai_super@flow-it.net - MAIL_PASSWORD: '$6xsWGbNtx$CFMQZqc*' + MAIL_PASSWORD: '$$6xsWGbNtx$$CFMQZqc*' + MAIL_SERVER: mail.flow-it.net + MAIL_PORT: 465 OPENAI_API_KEY: 'sk-proj-8R0jWzwjL7PeoPyMhJTZT3BlbkFJLb6HfRB2Hr9cEVFWEhU7' GROQ_API_KEY: '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' ANTHROPIC_API_KEY: 'sk-ant-api03-c2TmkzbReeGhXBO5JxNH6BJNylRDonc9GmZd0eRbrvyekec2' diff --git a/docker/compose_stackhero.yaml b/docker/compose_stackhero.yaml index 0f4ac01..009407b 100644 --- a/docker/compose_stackhero.yaml +++ b/docker/compose_stackhero.yaml @@ -11,7 +11,7 @@ x-common-variables: &common-variables DB_HOST: bswnz4.stackhero-network.com DB_USER: luke_skywalker - DB_PASS: 2MK&1rHmWEydE2rFuJLq*ls%tdkPAk2 + DB_PASS: '2MK&1rHmWEydE2rFuJLq*ls%tdkPAk2' DB_NAME: eveai DB_PORT: '5945' FLASK_ENV: production @@ -20,6 +20,8 @@ x-common-variables: &common-variables SECURITY_PASSWORD_SALT: '166448071751628781809462050022558634074' MAIL_USERNAME: 'evie_admin@askeveai.com' MAIL_PASSWORD: 's5D%R#y^v!s&6Z^i0k&' + MAIL_SERVER: mail.askeveai.com + MAIL_PORT: 465 REDIS_USER: eveai REDIS_PASS: 'jHliZwGD36sONgbm0fc6SOpzLbknqq4RNF8K' REDIS_URL: 8bciqc.stackhero-network.com diff --git a/eveai_app/__init__.py b/eveai_app/__init__.py index 630f8d9..e43ba8f 100644 --- a/eveai_app/__init__.py +++ b/eveai_app/__init__.py @@ -10,6 +10,7 @@ from common.extensions import (db, migrate, bootstrap, security, mail, login_man minio_client, simple_encryption) from common.models.user import User, Role, Tenant, TenantDomain import common.models.interaction +from common.utils.nginx_utils import prefixed_url_for from config.logging_config import LOGGING from common.utils.security import set_tenant_session_data from .errors import register_error_handlers diff --git a/eveai_app/templates/email/forgot_password.html b/eveai_app/templates/email/forgot_password.html new file mode 100644 index 0000000..7838c41 --- /dev/null +++ b/eveai_app/templates/email/forgot_password.html @@ -0,0 +1,13 @@ + + +
+Hi,
+You requested a password reset for your EveAI account. Click the link below to reset your password:
+ +If you did not request a password reset, please ignore this email.
+Thanks,
The EveAI Team
Hi,
+You requested a password reset for your EveAI account. Click the link below to reset your password:
+ +If you did not request a password reset, please ignore this email.
+Thanks,
The EveAI Team
#} -{# {{ login_user_form.remember_me }}#} -{# {{ login_user_form.remember_me.label }}#} -{#
#} +{{ login_user_form.submit() }}
diff --git a/eveai_app/templates/security/reset_password.html b/eveai_app/templates/security/reset_password.html index e8c59f2..5d89d04 100644 --- a/eveai_app/templates/security/reset_password.html +++ b/eveai_app/templates/security/reset_password.html @@ -1,12 +1,12 @@ {% extends "security/base.html" %} {% from "macros.html" import render_field %} -{% block title %} {{ _fsdomain('Reset password') }} {% endblock %} -{% block content_title %} {{ _fsdomain('Reset password') }} {% endblock %} +{% block title %} {{ _fsdomain('Reset AskEveAI password') }} {% endblock %} +{% block content_title %} {{ _fsdomain('Reset AskEveAI password') }} {% endblock %} {% block content_description %}An email will be sent to you with instructions.{% endblock %} {% block content %} {# {% include "security/_messages.html" %}#} -{#