Correct functions for creating new users, confirming email, resetting password and forgot password.
This commit is contained in:
@@ -10,10 +10,11 @@ from flask_jwt_extended import JWTManager
|
|||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
from flask_wtf import CSRFProtect
|
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.simple_encryption import SimpleEncryption
|
||||||
from .utils.minio_utils import MinioClient
|
from .utils.minio_utils import MinioClient
|
||||||
|
|
||||||
|
|
||||||
# Create extensions
|
# Create extensions
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class User(db.Model, UserMixin):
|
|||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
user_name = db.Column(db.String(80), unique=True, nullable=False)
|
user_name = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
email = db.Column(db.String(255), 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)
|
first_name = db.Column(db.String(80), nullable=False)
|
||||||
last_name = db.Column(db.String(80), nullable=False)
|
last_name = db.Column(db.String(80), nullable=False)
|
||||||
active = db.Column(db.Boolean)
|
active = db.Column(db.Boolean)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from flask import current_app, render_template
|
from flask import current_app, render_template
|
||||||
from flask_mailman import EmailMessage
|
from flask_mailman import EmailMessage
|
||||||
from itsdangerous import URLSafeTimedSerializer
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
|
import socket
|
||||||
|
|
||||||
from common.utils.nginx_utils import prefixed_url_for
|
from common.utils.nginx_utils import prefixed_url_for
|
||||||
|
|
||||||
@@ -35,15 +36,66 @@ def generate_confirmation_token(email):
|
|||||||
|
|
||||||
def send_confirmation_email(user):
|
def send_confirmation_email(user):
|
||||||
current_app.logger.debug(f'Sending confirmation email to {user.email}')
|
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)
|
token = generate_confirmation_token(user.email)
|
||||||
confirm_url = prefixed_url_for('security_bp.confirm_email', token=token, _external=True)
|
confirm_url = prefixed_url_for('security_bp.confirm_email', token=token, _external=True)
|
||||||
current_app.logger.debug(f'Confirmation URL: {confirm_url}')
|
current_app.logger.debug(f'Confirmation URL: {confirm_url}')
|
||||||
|
|
||||||
html = render_template('email/activate.html', confirm_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):
|
def send_reset_email(user):
|
||||||
|
current_app.logger.debug(f'Sending reset email to {user.email}')
|
||||||
token = generate_reset_token(user.email)
|
token = generate_reset_token(user.email)
|
||||||
reset_url = prefixed_url_for('security_bp.reset_password', token=token, _external=True)
|
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)
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from datetime import timedelta
|
|||||||
import redis
|
import redis
|
||||||
|
|
||||||
from common.utils.prompt_loader import load_prompt_templates
|
from common.utils.prompt_loader import load_prompt_templates
|
||||||
|
from eveai_app.views.security_forms import ResetPasswordForm
|
||||||
|
|
||||||
basedir = path.abspath(path.dirname(__file__))
|
basedir = path.abspath(path.dirname(__file__))
|
||||||
|
|
||||||
@@ -41,6 +42,11 @@ class Config(object):
|
|||||||
SECURITY_POST_LOGIN_VIEW = '/user/tenant_overview'
|
SECURITY_POST_LOGIN_VIEW = '/user/tenant_overview'
|
||||||
SECURITY_RECOVERABLE = True
|
SECURITY_RECOVERABLE = True
|
||||||
SECURITY_EMAIL_SENDER = "eveai_super@flow-it.net"
|
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
|
# Ensure Flask-Security-Too is handling CSRF tokens when behind a proxy
|
||||||
SECURITY_CSRF_PROTECT_MECHANISMS = ['session']
|
SECURITY_CSRF_PROTECT_MECHANISMS = ['session']
|
||||||
@@ -123,6 +129,15 @@ class Config(object):
|
|||||||
"LLM": {"name": "LLM", "description": "Algorithm using information integrated in the used LLM"}
|
"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):
|
class DevConfig(Config):
|
||||||
DEVELOPMENT = True
|
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_DATABASE_URI = f'postgresql+pg8000://{DB_USER}:{DB_PASS}@{DB_HOST}:5432/{DB_NAME}'
|
||||||
SQLALCHEMY_BINDS = {'public': SQLALCHEMY_DATABASE_URI}
|
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
|
# Define the nginx prefix used for the specific apps
|
||||||
EVEAI_APP_LOCATION_PREFIX = '/admin'
|
EVEAI_APP_LOCATION_PREFIX = '/admin'
|
||||||
EVEAI_CHAT_LOCATION_PREFIX = '/chat'
|
EVEAI_CHAT_LOCATION_PREFIX = '/chat'
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ x-common-variables: &common-variables
|
|||||||
SECRET_KEY: '97867c1491bea5ee6a8e8436eb11bf2ba6a69ff53ab1b17ecba450d0f2e572e1'
|
SECRET_KEY: '97867c1491bea5ee6a8e8436eb11bf2ba6a69ff53ab1b17ecba450d0f2e572e1'
|
||||||
SECURITY_PASSWORD_SALT: '228614859439123264035565568761433607235'
|
SECURITY_PASSWORD_SALT: '228614859439123264035565568761433607235'
|
||||||
MAIL_USERNAME: eveai_super@flow-it.net
|
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'
|
OPENAI_API_KEY: 'sk-proj-8R0jWzwjL7PeoPyMhJTZT3BlbkFJLb6HfRB2Hr9cEVFWEhU7'
|
||||||
GROQ_API_KEY: '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'
|
GROQ_API_KEY: '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'
|
||||||
ANTHROPIC_API_KEY: 'sk-ant-api03-c2TmkzbReeGhXBO5JxNH6BJNylRDonc9GmZd0eRbrvyekec2'
|
ANTHROPIC_API_KEY: 'sk-ant-api03-c2TmkzbReeGhXBO5JxNH6BJNylRDonc9GmZd0eRbrvyekec2'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
x-common-variables: &common-variables
|
x-common-variables: &common-variables
|
||||||
DB_HOST: bswnz4.stackhero-network.com
|
DB_HOST: bswnz4.stackhero-network.com
|
||||||
DB_USER: luke_skywalker
|
DB_USER: luke_skywalker
|
||||||
DB_PASS: 2MK&1rHmWEydE2rFuJLq*ls%tdkPAk2
|
DB_PASS: '2MK&1rHmWEydE2rFuJLq*ls%tdkPAk2'
|
||||||
DB_NAME: eveai
|
DB_NAME: eveai
|
||||||
DB_PORT: '5945'
|
DB_PORT: '5945'
|
||||||
FLASK_ENV: production
|
FLASK_ENV: production
|
||||||
@@ -20,6 +20,8 @@ x-common-variables: &common-variables
|
|||||||
SECURITY_PASSWORD_SALT: '166448071751628781809462050022558634074'
|
SECURITY_PASSWORD_SALT: '166448071751628781809462050022558634074'
|
||||||
MAIL_USERNAME: 'evie_admin@askeveai.com'
|
MAIL_USERNAME: 'evie_admin@askeveai.com'
|
||||||
MAIL_PASSWORD: 's5D%R#y^v!s&6Z^i0k&'
|
MAIL_PASSWORD: 's5D%R#y^v!s&6Z^i0k&'
|
||||||
|
MAIL_SERVER: mail.askeveai.com
|
||||||
|
MAIL_PORT: 465
|
||||||
REDIS_USER: eveai
|
REDIS_USER: eveai
|
||||||
REDIS_PASS: 'jHliZwGD36sONgbm0fc6SOpzLbknqq4RNF8K'
|
REDIS_PASS: 'jHliZwGD36sONgbm0fc6SOpzLbknqq4RNF8K'
|
||||||
REDIS_URL: 8bciqc.stackhero-network.com
|
REDIS_URL: 8bciqc.stackhero-network.com
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from common.extensions import (db, migrate, bootstrap, security, mail, login_man
|
|||||||
minio_client, simple_encryption)
|
minio_client, simple_encryption)
|
||||||
from common.models.user import User, Role, Tenant, TenantDomain
|
from common.models.user import User, Role, Tenant, TenantDomain
|
||||||
import common.models.interaction
|
import common.models.interaction
|
||||||
|
from common.utils.nginx_utils import prefixed_url_for
|
||||||
from config.logging_config import LOGGING
|
from config.logging_config import LOGGING
|
||||||
from common.utils.security import set_tenant_session_data
|
from common.utils.security import set_tenant_session_data
|
||||||
from .errors import register_error_handlers
|
from .errors import register_error_handlers
|
||||||
|
|||||||
13
eveai_app/templates/email/forgot_password.html
Normal file
13
eveai_app/templates/email/forgot_password.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Reset Your Password</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hi,</p>
|
||||||
|
<p>You requested a password reset for your EveAI account. Click the link below to reset your password:</p>
|
||||||
|
<p><a href="{{ reset_url }}">Reset Password</a></p>
|
||||||
|
<p>If you did not request a password reset, please ignore this email.</p>
|
||||||
|
<p>Thanks,<br>The EveAI Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
eveai_app/templates/security/email/reset_instructions.html
Normal file
13
eveai_app/templates/security/email/reset_instructions.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Reset Your Password</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hi,</p>
|
||||||
|
<p>You requested a password reset for your EveAI account. Click the link below to reset your password:</p>
|
||||||
|
<p><a href="{{ reset_link }}">Reset Password</a></p>
|
||||||
|
<p>If you did not request a password reset, please ignore this email.</p>
|
||||||
|
<p>Thanks,<br>The EveAI Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -9,10 +9,16 @@
|
|||||||
{% include "security/_messages.html" %}
|
{% include "security/_messages.html" %}
|
||||||
<form action="{{ url_for_security('forgot_password') }}" method="post" name="forgot_password_form">
|
<form action="{{ url_for_security('forgot_password') }}" method="post" name="forgot_password_form">
|
||||||
{{ forgot_password_form.hidden_tag() }}
|
{{ forgot_password_form.hidden_tag() }}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ forgot_password_form.email.label }}<br>
|
||||||
|
{{ forgot_password_form.email(size=80) }}
|
||||||
|
</p>
|
||||||
|
<p>{{ forgot_password_form.submit() }}</p>
|
||||||
|
|
||||||
{{ render_form_errors(forgot_password_form) }}
|
{{ render_form_errors(forgot_password_form) }}
|
||||||
{{ render_field_with_errors(forgot_password_form.email) }}
|
|
||||||
{{ render_field_errors(forgot_password_form.csrf_token) }}
|
{{ render_field_errors(forgot_password_form.csrf_token) }}
|
||||||
{{ render_field(forgot_password_form.submit) }}
|
|
||||||
</form>
|
</form>
|
||||||
{% include "security/_menu.html" %}
|
<!-- {% include "security/_menu.html" %}-->
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -14,10 +14,7 @@
|
|||||||
{{ login_user_form.password.label }}<br>
|
{{ login_user_form.password.label }}<br>
|
||||||
{{ login_user_form.password(size=80) }}
|
{{ login_user_form.password(size=80) }}
|
||||||
</p>
|
</p>
|
||||||
{# <p>#}
|
|
||||||
{# {{ login_user_form.remember_me }}#}
|
|
||||||
{# {{ login_user_form.remember_me.label }}#}
|
|
||||||
{# </p>#}
|
|
||||||
<p>{{ login_user_form.submit() }}</p>
|
<p>{{ login_user_form.submit() }}</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{% extends "security/base.html" %}
|
{% extends "security/base.html" %}
|
||||||
{% from "macros.html" import render_field %}
|
{% from "macros.html" import render_field %}
|
||||||
|
|
||||||
{% block title %} {{ _fsdomain('Reset password') }} {% endblock %}
|
{% block title %} {{ _fsdomain('Reset AskEveAI password') }} {% endblock %}
|
||||||
{% block content_title %} {{ _fsdomain('Reset 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_description %}An email will be sent to you with instructions.{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# {% include "security/_messages.html" %}#}
|
{# {% include "security/_messages.html" %}#}
|
||||||
{# <form action="{{ url_for_security('reset_password', token=reset_password_token) }}" method="post" name="reset_password_form">#}
|
{# <form action="{{ prefixed_url_for('security.reset_password', token=reset_password_token) }}" method="post" name="reset_password_form">#}
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{{ reset_password_form.hidden_tag() }}
|
{{ reset_password_form.hidden_tag() }}
|
||||||
{# {{ render_form_errors(reset_password_form) }}#}
|
{# {{ render_form_errors(reset_password_form) }}#}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<div class="form-group mt-3">
|
<div class="form-group mt-3">
|
||||||
<button type="submit" name="action" value="edit_user" class="btn btn-primary">Edit Selected User</button>
|
<button type="submit" name="action" value="edit_user" class="btn btn-primary">Edit Selected User</button>
|
||||||
<button type="submit" name="action" value="resend_confirmation_email" class="btn btn-secondary">Resend Confirmation Email</button>
|
<button type="submit" name="action" value="resend_confirmation_email" class="btn btn-secondary">Resend Confirmation Email</button>
|
||||||
|
<button type="submit" name="action" value="send_password_reset_email" class="btn btn-secondary">Send Password Reset Email</button>
|
||||||
<button type="submit" name="action" value="reset_uniquifier" class="btn btn-secondary">Reset Uniquifier</button>
|
<button type="submit" name="action" value="reset_uniquifier" class="btn btn-secondary">Reset Uniquifier</button>
|
||||||
<!-- Additional buttons can be added here for other actions -->
|
<!-- Additional buttons can be added here for other actions -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ from flask import current_app
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import PasswordField, SubmitField, StringField
|
from wtforms import PasswordField, SubmitField, StringField
|
||||||
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, EqualTo
|
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, EqualTo
|
||||||
|
from flask_security.forms import ForgotPasswordForm
|
||||||
|
from flask_security.utils import send_mail, config_value
|
||||||
|
|
||||||
|
from common.utils.nginx_utils import prefixed_url_for
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordForm(FlaskForm):
|
class SetPasswordForm(FlaskForm):
|
||||||
@@ -18,4 +22,4 @@ class RequestResetForm(FlaskForm):
|
|||||||
class ResetPasswordForm(FlaskForm):
|
class ResetPasswordForm(FlaskForm):
|
||||||
password = PasswordField('Password', validators=[DataRequired()])
|
password = PasswordField('Password', validators=[DataRequired()])
|
||||||
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
|
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
|
||||||
submit = SubmitField('Reset Password')
|
submit = SubmitField('Reset Password')
|
||||||
|
|||||||
@@ -164,3 +164,5 @@ def reset_password(token):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -154,30 +154,20 @@ def user():
|
|||||||
form.tenant_id.data = session.get('tenant').get('id') # It is only possible to create users for the session tenant
|
form.tenant_id.data = session.get('tenant').get('id') # It is only possible to create users for the session tenant
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
current_app.logger.info(f"Adding User for tenant {session['tenant']['id']} ")
|
current_app.logger.info(f"Adding User for tenant {session['tenant']['id']} ")
|
||||||
if form.password.data != form.confirm_password.data:
|
|
||||||
flash('Passwords do not match.', 'danger')
|
|
||||||
return render_template('user/user.html', form=form)
|
|
||||||
|
|
||||||
# Handle the required attributes
|
|
||||||
hashed_password = hash_password(form.password.data)
|
|
||||||
new_user = User(user_name=form.user_name.data,
|
new_user = User(user_name=form.user_name.data,
|
||||||
email=form.email.data,
|
email=form.email.data,
|
||||||
password=hashed_password,
|
|
||||||
first_name=form.first_name.data,
|
first_name=form.first_name.data,
|
||||||
last_name=form.last_name.data,
|
last_name=form.last_name.data,
|
||||||
valid_to=form.valid_to.data,
|
valid_to=form.valid_to.data,
|
||||||
tenant_id=form.tenant_id.data
|
tenant_id=form.tenant_id.data,
|
||||||
|
fs_uniquifier=uuid.uuid4().hex,
|
||||||
)
|
)
|
||||||
|
|
||||||
timestamp = dt.now(tz.utc)
|
timestamp = dt.now(tz.utc)
|
||||||
new_user.created_at = timestamp
|
new_user.created_at = timestamp
|
||||||
new_user.updated_at = timestamp
|
new_user.updated_at = timestamp
|
||||||
|
|
||||||
# Handle the relations
|
|
||||||
tenant_id = request.form.get('tenant_id')
|
|
||||||
# the_tenant = Tenant.query.get(tenant_id)
|
|
||||||
# new_user.tenant = the_tenant
|
|
||||||
|
|
||||||
# Add roles
|
# Add roles
|
||||||
for role_id in form.roles.data:
|
for role_id in form.roles.data:
|
||||||
the_role = Role.query.get(role_id)
|
the_role = Role.query.get(role_id)
|
||||||
@@ -188,11 +178,17 @@ def user():
|
|||||||
try:
|
try:
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
security.datastore.set_uniquifier()
|
# security.datastore.set_uniquifier(new_user)
|
||||||
send_confirmation_email(new_user)
|
try:
|
||||||
current_app.logger.debug(f'User {new_user.id} with name {new_user.user_name} added to database'
|
send_confirmation_email(new_user)
|
||||||
f'Confirmation email sent to {new_user.email}')
|
current_app.logger.debug(f'User {new_user.id} with name {new_user.user_name} added to database'
|
||||||
flash('User added successfully and confirmation email sent.', 'success')
|
f'Confirmation email sent to {new_user.email}')
|
||||||
|
flash('User added successfully and confirmation email sent.', 'success')
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'Failed to send confirmation email to {new_user.email}. Error: {str(e)}')
|
||||||
|
flash('User added successfully, but failed to send confirmation email. '
|
||||||
|
'Please contact the administrator.', 'warning')
|
||||||
|
|
||||||
return redirect(prefixed_url_for('user_bp.view_users'))
|
return redirect(prefixed_url_for('user_bp.view_users'))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f'Failed to add user with name {new_user.user_name}. Error: {str(e)}')
|
current_app.logger.error(f'Failed to add user with name {new_user.user_name}. Error: {str(e)}')
|
||||||
@@ -315,6 +311,9 @@ def handle_user_action():
|
|||||||
elif action == 'resend_confirmation_email':
|
elif action == 'resend_confirmation_email':
|
||||||
send_confirmation_email(user)
|
send_confirmation_email(user)
|
||||||
flash(f'Confirmation email sent to {user.email}.', 'success')
|
flash(f'Confirmation email sent to {user.email}.', 'success')
|
||||||
|
elif action == 'send_password_reset_email':
|
||||||
|
send_reset_email(user)
|
||||||
|
flash(f'Password reset email sent to {user.email}.', 'success')
|
||||||
elif action == 'reset_uniquifier':
|
elif action == 'reset_uniquifier':
|
||||||
reset_uniquifier(user)
|
reset_uniquifier(user)
|
||||||
flash(f'Uniquifier reset for {user.user_name}.', 'success')
|
flash(f'Uniquifier reset for {user.user_name}.', 'success')
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,74 @@
|
|||||||
|
"""fs_uniquifier required / password not required for user
|
||||||
|
|
||||||
|
Revision ID: 229774547fed
|
||||||
|
Revises: a39d2e378ccf
|
||||||
|
Create Date: 2024-08-21 08:49:55.397338
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '229774547fed'
|
||||||
|
down_revision = 'a39d2e378ccf'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('roles_users', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint('roles_users_user_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.drop_constraint('roles_users_role_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key(None, 'user', ['user_id'], ['id'], referent_schema='public', ondelete='CASCADE')
|
||||||
|
batch_op.create_foreign_key(None, 'role', ['role_id'], ['id'], referent_schema='public', ondelete='CASCADE')
|
||||||
|
|
||||||
|
with op.batch_alter_table('tenant_domain', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint('tenant_domain_tenant_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.drop_constraint('tenant_domain_updated_by_fkey', type_='foreignkey')
|
||||||
|
batch_op.drop_constraint('tenant_domain_created_by_fkey', type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key(None, 'user', ['updated_by'], ['id'], referent_schema='public')
|
||||||
|
batch_op.create_foreign_key(None, 'user', ['created_by'], ['id'], referent_schema='public')
|
||||||
|
batch_op.create_foreign_key(None, 'tenant', ['tenant_id'], ['id'], referent_schema='public')
|
||||||
|
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('password',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=True)
|
||||||
|
batch_op.alter_column('fs_uniquifier',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=False)
|
||||||
|
batch_op.drop_constraint('user_tenant_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key(None, 'tenant', ['tenant_id'], ['id'], referent_schema='public')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key('user_tenant_id_fkey', 'tenant', ['tenant_id'], ['id'])
|
||||||
|
batch_op.alter_column('fs_uniquifier',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=True)
|
||||||
|
batch_op.alter_column('password',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
with op.batch_alter_table('tenant_domain', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key('tenant_domain_created_by_fkey', 'user', ['created_by'], ['id'])
|
||||||
|
batch_op.create_foreign_key('tenant_domain_updated_by_fkey', 'user', ['updated_by'], ['id'])
|
||||||
|
batch_op.create_foreign_key('tenant_domain_tenant_id_fkey', 'tenant', ['tenant_id'], ['id'])
|
||||||
|
|
||||||
|
with op.batch_alter_table('roles_users', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key('roles_users_role_id_fkey', 'role', ['role_id'], ['id'], ondelete='CASCADE')
|
||||||
|
batch_op.create_foreign_key('roles_users_user_id_fkey', 'user', ['user_id'], ['id'], ondelete='CASCADE')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""Uniquifier not required when creating user
|
||||||
|
|
||||||
|
Revision ID: a39d2e378ccf
|
||||||
|
Revises: 1716099b62f0
|
||||||
|
Create Date: 2024-08-20 15:53:08.692690
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'a39d2e378ccf'
|
||||||
|
down_revision = '1716099b62f0'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('roles_users', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint('roles_users_role_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.drop_constraint('roles_users_user_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key(None, 'role', ['role_id'], ['id'], referent_schema='public', ondelete='CASCADE')
|
||||||
|
batch_op.create_foreign_key(None, 'user', ['user_id'], ['id'], referent_schema='public', ondelete='CASCADE')
|
||||||
|
|
||||||
|
with op.batch_alter_table('tenant_domain', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint('tenant_domain_updated_by_fkey', type_='foreignkey')
|
||||||
|
batch_op.drop_constraint('tenant_domain_created_by_fkey', type_='foreignkey')
|
||||||
|
batch_op.drop_constraint('tenant_domain_tenant_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key(None, 'user', ['updated_by'], ['id'], referent_schema='public')
|
||||||
|
batch_op.create_foreign_key(None, 'user', ['created_by'], ['id'], referent_schema='public')
|
||||||
|
batch_op.create_foreign_key(None, 'tenant', ['tenant_id'], ['id'], referent_schema='public')
|
||||||
|
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('fs_uniquifier',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=True)
|
||||||
|
batch_op.drop_constraint('user_tenant_id_fkey', type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key(None, 'tenant', ['tenant_id'], ['id'], referent_schema='public')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key('user_tenant_id_fkey', 'tenant', ['tenant_id'], ['id'])
|
||||||
|
batch_op.alter_column('fs_uniquifier',
|
||||||
|
existing_type=sa.VARCHAR(length=255),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
with op.batch_alter_table('tenant_domain', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key('tenant_domain_tenant_id_fkey', 'tenant', ['tenant_id'], ['id'])
|
||||||
|
batch_op.create_foreign_key('tenant_domain_created_by_fkey', 'user', ['created_by'], ['id'])
|
||||||
|
batch_op.create_foreign_key('tenant_domain_updated_by_fkey', 'user', ['updated_by'], ['id'])
|
||||||
|
|
||||||
|
with op.batch_alter_table('roles_users', schema=None) as batch_op:
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.drop_constraint(None, type_='foreignkey')
|
||||||
|
batch_op.create_foreign_key('roles_users_user_id_fkey', 'user', ['user_id'], ['id'], ondelete='CASCADE')
|
||||||
|
batch_op.create_foreign_key('roles_users_role_id_fkey', 'role', ['role_id'], ['id'], ondelete='CASCADE')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -53,6 +53,10 @@ http {
|
|||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /reset {
|
||||||
|
rewrite ^/reset(.*)$ /admin/reset$1 permanent;
|
||||||
|
}
|
||||||
|
|
||||||
location /static/ {
|
location /static/ {
|
||||||
alias /etc/nginx/static/;
|
alias /etc/nginx/static/;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user