Optimizing admin interface for user domain, completing security views
This commit is contained in:
@@ -104,7 +104,6 @@ class User(db.Model, UserMixin):
|
||||
password = db.Column(db.String(255), nullable=False)
|
||||
first_name = db.Column(db.String(80), nullable=False)
|
||||
last_name = db.Column(db.String(80), nullable=False)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
active = db.Column(db.Boolean)
|
||||
fs_uniquifier = db.Column(db.String(255), unique=True, nullable=False)
|
||||
confirmed_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
@@ -1,34 +1,62 @@
|
||||
from flask import request
|
||||
from flask import request, session
|
||||
import time
|
||||
from flask_security import current_user
|
||||
|
||||
|
||||
def log_request_middleware(app):
|
||||
@app.before_request
|
||||
def log_request_info():
|
||||
start_time = time.time()
|
||||
app.logger.debug(f"Request URL: {request.url}")
|
||||
app.logger.debug(f"Request Method: {request.method}")
|
||||
app.logger.debug(f"Request Headers: {request.headers}")
|
||||
app.logger.debug(f"Time taken for logging request info: {time.time() - start_time} seconds")
|
||||
try:
|
||||
app.logger.debug(f"Request Body: {request.get_data()}")
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error reading request body: {e}")
|
||||
app.logger.debug(f"Time taken for logging request body: {time.time() - start_time} seconds")
|
||||
# @app.before_request
|
||||
# def log_request_info():
|
||||
# start_time = time.time()
|
||||
# app.logger.debug(f"Request URL: {request.url}")
|
||||
# app.logger.debug(f"Request Method: {request.method}")
|
||||
# app.logger.debug(f"Request Headers: {request.headers}")
|
||||
# app.logger.debug(f"Time taken for logging request info: {time.time() - start_time} seconds")
|
||||
# try:
|
||||
# app.logger.debug(f"Request Body: {request.get_data()}")
|
||||
# except Exception as e:
|
||||
# app.logger.error(f"Error reading request body: {e}")
|
||||
# app.logger.debug(f"Time taken for logging request body: {time.time() - start_time} seconds")
|
||||
|
||||
# @app.before_request
|
||||
# def check_csrf_token():
|
||||
# start_time = time.time()
|
||||
# if request.method == "POST":
|
||||
# csrf_token = request.form.get("csrf_token")
|
||||
# app.logger.debug(f"CSRF Token: {csrf_token}")
|
||||
# app.logger.debug(f"Time taken for logging CSRF token: {time.time() - start_time} seconds")
|
||||
|
||||
# @app.before_request
|
||||
# def log_user_info():
|
||||
# if current_user and current_user.is_authenticated:
|
||||
# app.logger.debug(f"Before: User ID: {current_user.id}")
|
||||
# app.logger.debug(f"Before: User Email: {current_user.email}")
|
||||
# app.logger.debug(f"Before: User Roles: {current_user.roles}")
|
||||
# else:
|
||||
# app.logger.debug("After: No user logged in")
|
||||
|
||||
@app.before_request
|
||||
def check_csrf_token():
|
||||
start_time = time.time()
|
||||
if request.method == "POST":
|
||||
csrf_token = request.form.get("csrf_token")
|
||||
app.logger.debug(f"CSRF Token: {csrf_token}")
|
||||
app.logger.debug(f"Time taken for logging CSRF token: {time.time() - start_time} seconds")
|
||||
def log_session_state_before():
|
||||
app.logger.debug(f'Session state before request: {session.items()}')
|
||||
|
||||
# @app.after_request
|
||||
# def log_response_info(response):
|
||||
# start_time = time.time()
|
||||
# app.logger.debug(f"Response Status: {response.status}")
|
||||
# app.logger.debug(f"Response Headers: {response.headers}")
|
||||
#
|
||||
# app.logger.debug(f"Time taken for logging response info: {time.time() - start_time} seconds")
|
||||
# return response
|
||||
|
||||
# @app.after_request
|
||||
# def log_user_after_request(response):
|
||||
# if current_user and current_user.is_authenticated:
|
||||
# app.logger.debug(f"After: User ID: {current_user.id}")
|
||||
# app.logger.debug(f"after: User Email: {current_user.email}")
|
||||
# app.logger.debug(f"After: User Roles: {current_user.roles}")
|
||||
# else:
|
||||
# app.logger.debug("After: No user logged in")
|
||||
|
||||
@app.after_request
|
||||
def log_response_info(response):
|
||||
start_time = time.time()
|
||||
app.logger.debug(f"Response Status: {response.status}")
|
||||
app.logger.debug(f"Response Headers: {response.headers}")
|
||||
|
||||
app.logger.debug(f"Time taken for logging response info: {time.time() - start_time} seconds")
|
||||
def log_session_state_after(response):
|
||||
app.logger.debug(f'Session state after request: {session.items()}')
|
||||
return response
|
||||
0
common/utils/model_utils.py
Normal file
0
common/utils/model_utils.py
Normal file
@@ -1,7 +1,19 @@
|
||||
from flask import current_app, request, url_for
|
||||
from flask import request, current_app, url_for
|
||||
from urllib.parse import urlsplit, urlunsplit
|
||||
|
||||
|
||||
def prefixed_url_for(endpoint, **values):
|
||||
prefix = request.headers.get('X-Forwarded-Prefix', '')
|
||||
current_app.logger.debug(f'prefix: {prefix}')
|
||||
return prefix + url_for(endpoint, **values)
|
||||
scheme = request.headers.get('X-Forwarded-Proto', request.scheme)
|
||||
host = request.headers.get('Host', request.host)
|
||||
current_app.logger.debug(f'prefix: {prefix}, scheme: {scheme}, host: {host}')
|
||||
|
||||
external = values.pop('_external', False)
|
||||
generated_url = url_for(endpoint, **values)
|
||||
|
||||
if external:
|
||||
path, query, fragment = urlsplit(generated_url)[2:5]
|
||||
new_path = prefix + path
|
||||
return urlunsplit((scheme, host, new_path, query, fragment))
|
||||
else:
|
||||
return prefix + generated_url
|
||||
@@ -1,9 +1,10 @@
|
||||
from flask import session
|
||||
from flask import session, current_app
|
||||
from common.models.user import Tenant
|
||||
|
||||
|
||||
# Definition of Trigger Handlers
|
||||
def set_tenant_session_data(sender, user, **kwargs):
|
||||
current_app.logger.debug(f"Setting tenant session data for user {user.id}")
|
||||
tenant = Tenant.query.filter_by(id=user.tenant_id).first()
|
||||
session['tenant'] = tenant.to_dict()
|
||||
session['default_language'] = tenant.default_language
|
||||
|
||||
53
common/utils/security_utils.py
Normal file
53
common/utils/security_utils.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from flask import current_app, render_template
|
||||
from flask_mailman import EmailMessage
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def confirm_token(token, expiration=3600):
|
||||
serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
|
||||
try:
|
||||
email = serializer.loads(token, salt=current_app.config['SECURITY_PASSWORD_SALT'], max_age=expiration)
|
||||
except Exception as e:
|
||||
current_app.logger.debug(f'Error confirming token: {e}')
|
||||
raise
|
||||
return email
|
||||
|
||||
|
||||
def send_email(to, subject, template):
|
||||
msg = EmailMessage(subject=subject,
|
||||
body=template,
|
||||
to=[to])
|
||||
msg.content_subtype = "html"
|
||||
msg.send()
|
||||
|
||||
|
||||
def generate_reset_token(email):
|
||||
serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
|
||||
return serializer.dumps(email, salt=current_app.config['SECURITY_PASSWORD_SALT'])
|
||||
|
||||
|
||||
def generate_confirmation_token(email):
|
||||
serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
|
||||
return serializer.dumps(email, salt=current_app.config['SECURITY_PASSWORD_SALT'])
|
||||
|
||||
|
||||
def send_confirmation_email(user):
|
||||
current_app.logger.debug(f'Sending confirmation email to {user.email}')
|
||||
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)
|
||||
|
||||
|
||||
def send_reset_email(user):
|
||||
token = generate_reset_token(user.email)
|
||||
reset_url = prefixed_url_for('security_bp.reset_password', token=token, _external=True)
|
||||
html = render_template('email/reset_password.html', reset_url=reset_url)
|
||||
send_email(user.email, "Reset Your Password", html)
|
||||
|
||||
@@ -36,8 +36,6 @@ class Config(object):
|
||||
SECURITY_POST_LOGIN_VIEW = '/user/tenant_overview'
|
||||
SECURITY_RECOVERABLE = True
|
||||
SECURITY_EMAIL_SENDER = "eveai_super@flow-it.net"
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(minutes=60)
|
||||
SESSION_REFRESH_EACH_REQUEST = True
|
||||
|
||||
# Ensure Flask-Security-Too is handling CSRF tokens when behind a proxy
|
||||
SECURITY_CSRF_PROTECT_MECHANISMS = ['session']
|
||||
@@ -70,9 +68,9 @@ class Config(object):
|
||||
CELERY_TIMEZONE = 'UTC'
|
||||
CELERY_ENABLE_UTC = True
|
||||
|
||||
# Chunk Definition
|
||||
MIN_CHUNK_SIZE = 2000
|
||||
MAX_CHUNK_SIZE = 3000
|
||||
# Chunk Definition, Embedding dependent
|
||||
O_TE3SMALL_MIN_CHUNK_SIZE = 2000
|
||||
O_TE3SMALL_MAX_CHUNK_SIZE = 3000
|
||||
|
||||
# LLM TEMPLATES
|
||||
GPT4_SUMMARY_TEMPLATE = """Write a concise summary of the text in the same language as the provided text.
|
||||
@@ -104,7 +102,8 @@ class Config(object):
|
||||
SESSION_TYPE = 'redis'
|
||||
SESSION_PERMANENT = False
|
||||
SESSION_USE_SIGNER = True
|
||||
SESSION_KEY_PREFIX = 'eveai_chat_'
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(minutes=60)
|
||||
SESSION_REFRESH_EACH_REQUEST = True
|
||||
|
||||
|
||||
class DevConfig(Config):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
import os
|
||||
from flask import Flask, render_template, jsonify
|
||||
from flask import Flask, render_template, jsonify, flash, redirect, request
|
||||
from flask_security import SQLAlchemyUserDatastore, LoginForm
|
||||
from flask_security.signals import user_authenticated
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
@@ -14,6 +14,7 @@ from common.utils.security import set_tenant_session_data
|
||||
from .errors import register_error_handlers
|
||||
from common.utils.celery_utils import make_celery, init_celery
|
||||
from common.utils.debug_utils import log_request_middleware
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
|
||||
|
||||
def create_app(config_file=None):
|
||||
@@ -27,6 +28,8 @@ def create_app(config_file=None):
|
||||
else:
|
||||
app.config.from_object(config_file)
|
||||
|
||||
app.config['SESSION_KEY_PREFIX'] = 'eveai_app_'
|
||||
|
||||
try:
|
||||
os.makedirs(app.instance_path)
|
||||
except OSError:
|
||||
@@ -67,8 +70,9 @@ def create_app(config_file=None):
|
||||
security_logger.setLevel(logging.DEBUG)
|
||||
sqlalchemy_logger = logging.getLogger('sqlalchemy.engine')
|
||||
sqlalchemy_logger.setLevel(logging.DEBUG)
|
||||
# log_request_middleware(app) # Add this when debugging nginx or another proxy
|
||||
log_request_middleware(app) # Add this when debugging nginx or another proxy
|
||||
|
||||
# Some generic Error Handling Routines
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
app.logger.error(f"Unhandled Exception: {e}", exc_info=True)
|
||||
|
||||
16
eveai_app/templates/basic/confirm_email_fail.html
Normal file
16
eveai_app/templates/basic/confirm_email_fail.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_field %}
|
||||
|
||||
{% block title %}Email Confirmation OK{% endblock %}
|
||||
|
||||
{% block content_title %}Email Confirmation OK{% endblock %}
|
||||
{% block content_description %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
Your email cannot be confirmed. The link has expired or is invalid. Please contact your administrator to send you a new confirmation link ;-)
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content_footer %}
|
||||
|
||||
{% endblock %}
|
||||
16
eveai_app/templates/basic/confirm_email_ok.html
Normal file
16
eveai_app/templates/basic/confirm_email_ok.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_field %}
|
||||
|
||||
{% block title %}Email Confirmation OK{% endblock %}
|
||||
|
||||
{% block content_title %}Email Confirmation OK{% endblock %}
|
||||
{% block content_description %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
Your email has been confirmed. You will shortly receive an email to set your password. Then we'll be able to communicate on a deeper level ;-)
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content_footer %}
|
||||
|
||||
{% endblock %}
|
||||
13
eveai_app/templates/email/activate.html
Normal file
13
eveai_app/templates/email/activate.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Confirm Your Email</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hi,</p>
|
||||
<p>You have been registered with EveAI by your administrator. Please confirm your email address by clicking the link below:</p>
|
||||
<p><a href="{{ confirm_url }}">Confirm Email</a></p>
|
||||
<p>If you were not informed to register, please ignore this email.</p>
|
||||
<p>Thanks,<br>The EveAI Team</p>
|
||||
</body>
|
||||
</html>
|
||||
13
eveai_app/templates/email/reset_password.html
Normal file
13
eveai_app/templates/email/reset_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>
|
||||
@@ -68,14 +68,13 @@
|
||||
<div class="collapse navbar-collapse w-100 pt-3 pb-2 py-lg-0" id="navigation">
|
||||
<ul class="navbar-nav navbar-nav-hover mx-auto">
|
||||
{% if current_user.is_authenticated %}
|
||||
{{ dropdown('User Mgmt', 'contacts', [
|
||||
{{ dropdown('Tenant Configuration', 'contacts', [
|
||||
{'name': 'Tenant List', 'url': '/user/select_tenant', 'roles': ['Super User']},
|
||||
{'name': 'Tenant Registration', 'url': '/user/tenant', 'roles': ['Super User']},
|
||||
{'name': 'Generate Chat API Key', 'url': '/user/generate_chat_api_key', 'roles': ['Super User']},
|
||||
{'name': 'Tenant Overview', 'url': '/user/tenant_overview', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Tenant Domains', 'url': '/user/view_tenant_domains/' + session['tenant']['id']|string, 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Tenant Domains', 'url': '/user/view_tenant_domains', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'Tenant Domain Registration', 'url': '/user/tenant_domain', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'User List', 'url': '/user/view_users/' + session['tenant']['id']|string, 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'User List', 'url': '/user/view_users', 'roles': ['Super User', 'Tenant Admin']},
|
||||
{'name': 'User Registration', 'url': '/user/user', 'roles': ['Super User', 'Tenant Admin']},
|
||||
]) }}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "security/base.html" %}
|
||||
{% from "security/_macros.html" import render_field_with_errors, render_field, render_field_errors, render_form_errors %}
|
||||
{% from "macros.html" import render_field %}
|
||||
|
||||
{% block title %} {{ _fsdomain('Reset password') }} {% endblock %}
|
||||
{% block content_title %} {{ _fsdomain('Reset password') }} {% endblock %}
|
||||
{% block content_description %}An email will be sent to you with instructions.{% endblock %}
|
||||
{% 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="" method="post">
|
||||
{{ reset_password_form.hidden_tag() }}
|
||||
{{ render_form_errors(reset_password_form) }}
|
||||
{{ render_field_with_errors(reset_password_form.password) }}
|
||||
{{ render_field_with_errors(reset_password_form.password_confirm) }}
|
||||
{{ render_field_errors(reset_password_form.csrf_token) }}
|
||||
{{ render_field(reset_password_form.submit) }}
|
||||
{# {{ render_form_errors(reset_password_form) }}#}
|
||||
{{ render_field(reset_password_form.password) }}
|
||||
{{ render_field(reset_password_form.confirm_password) }}
|
||||
{# {{ render_field_errors(reset_password_form.csrf_token) }}#}
|
||||
<button type="submit" class="btn btn-primary">Reset Password</button>
|
||||
</form>
|
||||
{# {% include "security/_menu.html" %}#}
|
||||
{% endblock content %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_selectable_table %}
|
||||
{% from "macros.html" import render_selectable_table, render_pagination %}
|
||||
|
||||
{% block title %}Tenant Selection{% endblock %}
|
||||
|
||||
@@ -8,37 +8,44 @@
|
||||
|
||||
{% block content %}
|
||||
<form method="POST" action="{{ url_for('user_bp.handle_tenant_selection') }}">
|
||||
{{ render_selectable_table(headers=["Tenant ID", "Tenant Name", "Website"], rows=tenants, selectable=True, id="tenantsTable") }}
|
||||
{{ render_selectable_table(headers=["Tenant ID", "Tenant Name", "Website"], rows=rows, selectable=True, id="tenantsTable") }}
|
||||
<div class="form-group mt-3">
|
||||
<button type="submit" name="action" value="select_tenant" class="btn btn-primary">Set Session Tenant</button>
|
||||
<button type="submit" name="action" value="view_users" class="btn btn-secondary">View Users</button>
|
||||
<button type="submit" name="action" value="edit_tenant" class="btn btn-secondary">Edit Tenant</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#tenantsTable').DataTable({
|
||||
'columnDefs': [
|
||||
{
|
||||
'targets': 0,
|
||||
'searchable': false,
|
||||
'orderable': false,
|
||||
'className': 'dt-body-center',
|
||||
},
|
||||
{
|
||||
'targets': 1,
|
||||
'orderable': true
|
||||
},
|
||||
{
|
||||
'targets': 2,
|
||||
'orderable': true
|
||||
}
|
||||
],
|
||||
'order': [[1, 'asc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% block content_footer %}
|
||||
{{ render_pagination(pagination, 'user_bp.select_tenant') }}
|
||||
{% endblock %}
|
||||
|
||||
{#{% block scripts %}#}
|
||||
{#<script>#}
|
||||
{#$(document).ready(function() {#}
|
||||
{# $('#tenantsTable').DataTable({#}
|
||||
{# 'columnDefs': [#}
|
||||
{# {#}
|
||||
{# 'targets': 0,#}
|
||||
{# 'searchable': false,#}
|
||||
{# 'orderable': false,#}
|
||||
{# 'className': 'dt-body-center',#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 1,#}
|
||||
{# 'orderable': true#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 2,#}
|
||||
{# 'orderable': true#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 2,#}
|
||||
{# 'orderable': true#}
|
||||
{# },#}
|
||||
{# ],#}
|
||||
{# 'order': [[1, 'asc']]#}
|
||||
{# });#}
|
||||
{#});#}
|
||||
{#</script>#}
|
||||
{#{% endblock %}#}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_selectable_table %}
|
||||
{% from "macros.html" import render_selectable_table, render_pagination %}
|
||||
|
||||
{% block title %}View Tenant Domains{% endblock %}
|
||||
|
||||
@@ -8,38 +8,43 @@
|
||||
|
||||
{% block content %}
|
||||
<form action="{{ url_for('user_bp.handle_tenant_domain_action') }}" method="POST">
|
||||
{{ render_selectable_table(headers=["ID", "Domain Name", "Valid To"], rows=tenant_domains, selectable=True, id="tenantDomainsTable") }}
|
||||
{{ render_selectable_table(headers=["ID", "Domain Name", "Valid To"], rows=rows, selectable=True, id="tenantDomainsTable") }}
|
||||
<div class="form-group mt-3">
|
||||
<button type="submit" name="action" value="edit_tenant_domain" class="btn btn-primary">Edit Selected Domain</button>
|
||||
<!-- Additional buttons can be added here for other actions -->
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#tenantDomainsTable').DataTable({
|
||||
'columnDefs': [
|
||||
{
|
||||
'targets': 0,
|
||||
'searchable': false,
|
||||
'orderable': false,
|
||||
'className': 'dt-body-center',
|
||||
'render': function (data, type, full, meta) {
|
||||
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';
|
||||
}
|
||||
},
|
||||
{
|
||||
'targets': 1,
|
||||
'orderable': true,
|
||||
},
|
||||
{
|
||||
'targets': 2,
|
||||
'orderable': true,
|
||||
},
|
||||
],
|
||||
'order': [[1, 'asc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block content_footer %}
|
||||
{{ render_pagination(pagination, 'user_bp.view_tenant_domains') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{#<script>#}
|
||||
{#$(document).ready(function() {#}
|
||||
{# $('#tenantDomainsTable').DataTable({#}
|
||||
{# 'columnDefs': [#}
|
||||
{# {#}
|
||||
{# 'targets': 0,#}
|
||||
{# 'searchable': false,#}
|
||||
{# 'orderable': false,#}
|
||||
{# 'className': 'dt-body-center',#}
|
||||
{# 'render': function (data, type, full, meta) {#}
|
||||
{# return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';#}
|
||||
{# }#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 1,#}
|
||||
{# 'orderable': true,#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 2,#}
|
||||
{# 'orderable': true,#}
|
||||
{# },#}
|
||||
{# ],#}
|
||||
{# 'order': [[1, 'asc']]#}
|
||||
{# });#}
|
||||
{#});#}
|
||||
{#</script>#}
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% from "macros.html" import render_selectable_table %}
|
||||
{% from "macros.html" import render_selectable_table, render_pagination %}
|
||||
|
||||
{% block title %}View Users{% endblock %}
|
||||
|
||||
@@ -8,42 +8,49 @@
|
||||
|
||||
{% block content %}
|
||||
<form action="{{ url_for('user_bp.handle_user_action') }}" method="POST">
|
||||
{{ render_selectable_table(headers=["User ID", "User Name", "Email"], rows=users, selectable=True, id="usersTable") }}
|
||||
{{ render_selectable_table(headers=["User ID", "User Name", "Email"], rows=rows, selectable=True, id="usersTable") }}
|
||||
<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="resend_confirmation_email" class="btn btn-secondary">Resend Confirmation Email</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 -->
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#usersTable').DataTable({
|
||||
'columnDefs': [
|
||||
{
|
||||
'targets': 0,
|
||||
'searchable': false,
|
||||
'orderable': false,
|
||||
'className': 'dt-body-center',
|
||||
'render': function (data, type, full, meta) {
|
||||
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';
|
||||
}
|
||||
},
|
||||
{
|
||||
'targets': 1,
|
||||
'orderable': true,
|
||||
},
|
||||
{
|
||||
'targets': 2,
|
||||
'orderable': true,
|
||||
},
|
||||
{
|
||||
'targets': 3,
|
||||
'orderable': true,
|
||||
}
|
||||
],
|
||||
'order': [[1, 'asc']]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block content_footer %}
|
||||
{{ render_pagination(pagination, 'user_bp.select_tenant') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{#<script>#}
|
||||
{#$(document).ready(function() {#}
|
||||
{# $('#usersTable').DataTable({#}
|
||||
{# 'columnDefs': [#}
|
||||
{# {#}
|
||||
{# 'targets': 0,#}
|
||||
{# 'searchable': false,#}
|
||||
{# 'orderable': false,#}
|
||||
{# 'className': 'dt-body-center',#}
|
||||
{# 'render': function (data, type, full, meta) {#}
|
||||
{# return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';#}
|
||||
{# }#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 1,#}
|
||||
{# 'orderable': true,#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 2,#}
|
||||
{# 'orderable': true,#}
|
||||
{# },#}
|
||||
{# {#}
|
||||
{# 'targets': 3,#}
|
||||
{# 'orderable': true,#}
|
||||
{# }#}
|
||||
{# ],#}
|
||||
{# 'order': [[1, 'asc']]#}
|
||||
{# });#}
|
||||
{#});#}
|
||||
{#</script>#}
|
||||
{% endblock %}
|
||||
@@ -22,6 +22,16 @@ def index():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@basic_bp.route('/confirm_email_ok', methods=['GET', ])
|
||||
def confirm_email_ok():
|
||||
return render_template('basic/confirm_email_ok.html')
|
||||
|
||||
|
||||
@basic_bp.route('/confirm_email_fail', methods=['GET', ])
|
||||
def confirm_email_fail():
|
||||
return render_template('basic/confirm_email_fail.html')
|
||||
|
||||
|
||||
@basic_bp.route('/session_defaults', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def session_defaults():
|
||||
|
||||
21
eveai_app/views/security_forms.py
Normal file
21
eveai_app/views/security_forms.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from flask import current_app
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, SubmitField, StringField
|
||||
from wtforms.validators import DataRequired, Length, Email, NumberRange, Optional, EqualTo
|
||||
|
||||
|
||||
class SetPasswordForm(FlaskForm):
|
||||
password = PasswordField('Password', validators=[DataRequired()])
|
||||
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
|
||||
submit = SubmitField('Set Password')
|
||||
|
||||
|
||||
class RequestResetForm(FlaskForm):
|
||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||
submit = SubmitField('Request Password Reset')
|
||||
|
||||
|
||||
class ResetPasswordForm(FlaskForm):
|
||||
password = PasswordField('Password', validators=[DataRequired()])
|
||||
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
|
||||
submit = SubmitField('Reset Password')
|
||||
@@ -1,13 +1,20 @@
|
||||
# views/security_views.py
|
||||
from flask import Blueprint, render_template, redirect, request, flash, current_app
|
||||
from flask import Blueprint, render_template, redirect, request, flash, current_app, abort, session
|
||||
from flask_security import current_user, login_required, login_user, logout_user
|
||||
from flask_security.utils import verify_and_update_password, get_message, do_flash, config_value
|
||||
from flask_security.utils import verify_and_update_password, get_message, do_flash, config_value, hash_password
|
||||
from flask_security.forms import LoginForm
|
||||
from urllib.parse import urlparse
|
||||
import datetime as dt
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from common.models.user import User
|
||||
from common.utils.nginx_utils import prefixed_url_for
|
||||
from eveai_app.views.security_forms import SetPasswordForm, ResetPasswordForm, RequestResetForm
|
||||
from common.extensions import db
|
||||
from common.utils.security_utils import confirm_token, send_confirmation_email, send_reset_email
|
||||
from common.utils.security import set_tenant_session_data
|
||||
|
||||
security_bp = Blueprint('security_bp', __name__)
|
||||
|
||||
@@ -15,11 +22,19 @@ security_bp = Blueprint('security_bp', __name__)
|
||||
@security_bp.before_request
|
||||
def log_before_request():
|
||||
current_app.logger.debug(f"Before request (security_bp): {request.method} {request.url}")
|
||||
if current_user and current_user.is_authenticated:
|
||||
current_app.logger.debug(f"After request (security_bp): Current User: {current_user.email}")
|
||||
else:
|
||||
current_app.logger.debug(f"After request (security_bp): No user logged in")
|
||||
|
||||
|
||||
@security_bp.after_request
|
||||
def log_after_request(response):
|
||||
current_app.logger.debug(f"After request (security_bp): {request.method} {request.url} - Status: {response.status}")
|
||||
if current_user and current_user.is_authenticated:
|
||||
current_app.logger.debug(f"After request (security_bp): Current User: {current_user.email}")
|
||||
else:
|
||||
current_app.logger.debug(f"After request (security_bp): No user logged in")
|
||||
return response
|
||||
|
||||
|
||||
@@ -34,11 +49,19 @@ def login():
|
||||
current_app.logger.debug(f'Validating login form: {form.email.data}')
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user is None or not verify_and_update_password(form.password.data, user):
|
||||
flash('Invalid username or password')
|
||||
flash('Invalid username or password', 'danger')
|
||||
return redirect(prefixed_url_for('security_bp.login'))
|
||||
login_user(user, remember=form.remember.data)
|
||||
|
||||
if login_user(user):
|
||||
current_app.logger.info(f'Login successful! Current User is {current_user.email}')
|
||||
db.session.commit()
|
||||
return redirect(prefixed_url_for('user_bp.tenant_overview'))
|
||||
else:
|
||||
flash('Invalid username or password', 'danger')
|
||||
current_app.logger.debug(f'Failed to login user {user.email}')
|
||||
abort(401)
|
||||
else:
|
||||
current_app.logger.debug(f'Invalid login form: {form.errors}')
|
||||
|
||||
return render_template('security/login_user.html', login_user_form=form)
|
||||
|
||||
@@ -50,3 +73,76 @@ def logout():
|
||||
logout_user()
|
||||
current_app.logger.debug('After Logout')
|
||||
return redirect(prefixed_url_for('basic_bp.index'))
|
||||
|
||||
|
||||
@security_bp.route('/confirm_email/<token>', methods=['GET', 'POST'])
|
||||
def confirm_email(token):
|
||||
try:
|
||||
email = confirm_token(token)
|
||||
except Exception as e:
|
||||
flash('The confirmation link is invalid or has expired.', 'danger')
|
||||
current_app.logger.debug(f'Invalid confirmation link detected: {token} - error: {e}')
|
||||
return redirect(prefixed_url_for('basic_bp.confirm_email_fail'))
|
||||
|
||||
user = User.query.filter_by(email=email).first_or_404()
|
||||
current_app.logger.debug(f'Trying to confirm email for user {user.email}')
|
||||
if user.active:
|
||||
flash('Account already confirmed. Please login.', 'success')
|
||||
current_app.logger.debug(f'Account for user {user.email} was already activated')
|
||||
return redirect(prefixed_url_for('security_bp.login'))
|
||||
else:
|
||||
current_app.logger.debug(f'Trying to confirm email for user {user.email}')
|
||||
user.active = True
|
||||
user.updated_at = dt.now(tz.utc)
|
||||
user.confirmed_at = dt.now(tz.utc)
|
||||
|
||||
try:
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
except SQLAlchemyError as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.debug(f'Failed to confirm email for user {user.email}: {e}')
|
||||
return redirect(prefixed_url_for('basic_bp.confirm_email_fail'))
|
||||
|
||||
current_app.logger.debug(f'Account for user {user.email} was confirmed.')
|
||||
send_reset_email(user)
|
||||
return redirect(prefixed_url_for('basic_bp.confirm_email_ok'))
|
||||
|
||||
|
||||
@security_bp.route('/reset_password_request', methods=['GET', 'POST'])
|
||||
def reset_password_request():
|
||||
form = RequestResetForm()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user:
|
||||
send_reset_email(user)
|
||||
flash('An email with instructions to reset your password has been sent.', 'info')
|
||||
return redirect(prefixed_url_for('security_bp.login'))
|
||||
return render_template('security/reset_password_request.html', form=form)
|
||||
|
||||
|
||||
@security_bp.route('/reset_password/<token>', methods=['GET', 'POST'])
|
||||
def reset_password(token):
|
||||
try:
|
||||
email = confirm_token(token)
|
||||
except Exception as e:
|
||||
flash('The reset link is invalid or has expired.', 'danger')
|
||||
current_app.logger.debug(f'Invalid reset link detected: {token} - error: {e}')
|
||||
return redirect(prefixed_url_for('security_bp.reset_password_request'))
|
||||
|
||||
user = User.query.filter_by(email=email).first_or_404()
|
||||
form = ResetPasswordForm()
|
||||
if form.validate_on_submit():
|
||||
user.password = hash_password(form.password.data)
|
||||
user.updated_at = dt.now(tz.utc)
|
||||
db.session.commit()
|
||||
flash('Your password has been updated.', 'success')
|
||||
return redirect(prefixed_url_for('security_bp.login'))
|
||||
return render_template('security/reset_password.html', reset_password_form=form)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ class BaseUserForm(FlaskForm):
|
||||
email = EmailField('Email', validators=[DataRequired(), Email()])
|
||||
first_name = StringField('First Name', validators=[DataRequired(), Length(max=80)])
|
||||
last_name = StringField('Last Name', validators=[DataRequired(), Length(max=80)])
|
||||
is_active = BooleanField('Is Active', id='flexSwitchCheckDefault', default=True)
|
||||
valid_to = DateField('Valid to', id='form-control datepicker', validators=[Optional()])
|
||||
tenant_id = IntegerField('Tenant ID', validators=[NumberRange(min=0)])
|
||||
roles = SelectMultipleField('Roles', coerce=int)
|
||||
@@ -59,8 +58,6 @@ class BaseUserForm(FlaskForm):
|
||||
|
||||
|
||||
class CreateUserForm(BaseUserForm):
|
||||
password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
|
||||
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), Length(min=8)])
|
||||
submit = SubmitField('Create User')
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ import uuid
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
from flask import request, redirect, flash, render_template, Blueprint, session, current_app, jsonify
|
||||
from flask_security import hash_password, roles_required, roles_accepted, current_user
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import ast
|
||||
|
||||
from common.models.user import User, Tenant, Role, TenantDomain
|
||||
from common.extensions import db, kms_client
|
||||
from common.extensions import db, kms_client, security
|
||||
from common.utils.security_utils import send_confirmation_email, send_reset_email
|
||||
from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm
|
||||
from common.utils.database import Database
|
||||
from common.utils.view_assistants import prepare_table_for_macro
|
||||
@@ -20,11 +22,19 @@ user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
|
||||
@user_bp.before_request
|
||||
def log_before_request():
|
||||
current_app.logger.debug(f"Before request (user_bp): {request.method} {request.url}")
|
||||
if current_user and current_user.is_authenticated:
|
||||
current_app.logger.debug(f"After request (user_bp): Current User: {current_user.email}")
|
||||
else:
|
||||
current_app.logger.debug(f"After request (user_bp): No user logged in")
|
||||
|
||||
|
||||
@user_bp.after_request
|
||||
def log_after_request(response):
|
||||
current_app.logger.debug(f"After request (user_bp): {request.method} {request.url} - Status: {response.status}")
|
||||
if current_user and current_user.is_authenticated:
|
||||
current_app.logger.debug(f"After request (user_bp): Current User: {current_user.email}")
|
||||
else:
|
||||
current_app.logger.debug(f"After request (user_bp): No user logged in")
|
||||
return response
|
||||
|
||||
|
||||
@@ -133,12 +143,10 @@ def user():
|
||||
password=hashed_password,
|
||||
first_name=form.first_name.data,
|
||||
last_name=form.last_name.data,
|
||||
is_active=form.is_active.data,
|
||||
valid_to=form.valid_to.data,
|
||||
tenant_id=form.tenant_id.data
|
||||
)
|
||||
|
||||
new_user.fs_uniquifier = str(uuid.uuid4())
|
||||
timestamp = dt.now(tz.utc)
|
||||
new_user.created_at = timestamp
|
||||
new_user.updated_at = timestamp
|
||||
@@ -158,8 +166,11 @@ def user():
|
||||
try:
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
current_app.logger.debug(f'User {new_user.id} with name {new_user.user_name} added to database')
|
||||
flash('User added successfully.', 'success')
|
||||
security.datastore.set_uniquifier()
|
||||
send_confirmation_email(new_user)
|
||||
current_app.logger.debug(f'User {new_user.id} with name {new_user.user_name} added to database'
|
||||
f'Confirmation email sent to {new_user.email}')
|
||||
flash('User added successfully and confirmation email sent.', 'success')
|
||||
return redirect(prefixed_url_for('user_bp.view_users'))
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'Failed to add user with name {new_user.user_name}. Error: {str(e)}')
|
||||
@@ -179,7 +190,6 @@ def edit_user(user_id):
|
||||
# Populate the user with form data
|
||||
user.first_name = form.first_name.data
|
||||
user.last_name = form.last_name.data
|
||||
user.is_active = form.is_active.data
|
||||
user.valid_to = form.valid_to.data
|
||||
user.updated_at = dt.now(tz.utc)
|
||||
|
||||
@@ -200,7 +210,8 @@ def edit_user(user_id):
|
||||
db.session.commit()
|
||||
flash('User updated successfully.', 'success')
|
||||
return redirect(
|
||||
prefixed_url_for('user_bp.edit_user', user_id=user.id)) # Assuming there's a user profile view to redirect to
|
||||
prefixed_url_for('user_bp.edit_user',
|
||||
user_id=user.id)) # Assuming there's a user profile view to redirect to
|
||||
|
||||
form.roles.data = [role.id for role in user.roles]
|
||||
return render_template('user/edit_user.html', form=form, user_id=user_id)
|
||||
@@ -209,11 +220,17 @@ def edit_user(user_id):
|
||||
@user_bp.route('/select_tenant')
|
||||
@roles_required('Super User')
|
||||
def select_tenant():
|
||||
tenants = Tenant.query.all() # Fetch all tenants from the database
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
prepared_data = prepare_table_for_macro(tenants, [('id', ''), ('name', ''), ('website', '')])
|
||||
query = Tenant.query.order_by(Tenant.name) # Fetch all tenants from the database
|
||||
|
||||
return render_template('user/select_tenant.html', tenants=prepared_data)
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
tenants = pagination.items
|
||||
|
||||
rows = prepare_table_for_macro(tenants, [('id', ''), ('name', ''), ('website', '')])
|
||||
|
||||
return render_template('user/select_tenant.html', rows=rows, pagination=pagination)
|
||||
|
||||
|
||||
@user_bp.route('/handle_tenant_selection', methods=['POST'])
|
||||
@@ -239,17 +256,24 @@ def handle_tenant_selection():
|
||||
return redirect(prefixed_url_for('select_tenant'))
|
||||
|
||||
|
||||
@user_bp.route('/view_users/<int:tenant_id>')
|
||||
@user_bp.route('/view_users')
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def view_users(tenant_id):
|
||||
tenant_id = int(tenant_id)
|
||||
users = User.query.filter_by(tenant_id=tenant_id).all()
|
||||
def view_users():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
prepared_data = prepare_table_for_macro(users, [('id', ''), ('user_name', ''), ('email', '')])
|
||||
current_app.logger.debug(f'prepared_data: {prepared_data}')
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
query = User.query.filter_by(tenant_id=tenant_id).order_by(User.user_name)
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
users = pagination.items
|
||||
|
||||
# prepare table data
|
||||
|
||||
rows = prepare_table_for_macro(users, [('id', ''), ('user_name', ''), ('email', '')])
|
||||
|
||||
# Render the users in a template
|
||||
return render_template('user/view_users.html', users=prepared_data)
|
||||
return render_template('user/view_users.html', rows=rows, pagination=pagination)
|
||||
|
||||
|
||||
@user_bp.route('/handle_user_action', methods=['POST'])
|
||||
@@ -257,25 +281,38 @@ def view_users(tenant_id):
|
||||
def handle_user_action():
|
||||
user_identification = request.form['selected_row']
|
||||
user_id = ast.literal_eval(user_identification).get('value')
|
||||
user = User.query.get_or_404(user_id)
|
||||
action = request.form['action']
|
||||
|
||||
if action == 'edit_user':
|
||||
return redirect(prefixed_url_for('user_bp.edit_user', user_id=user_id))
|
||||
# Add more conditions for other actions
|
||||
return redirect(prefixed_url_for('view_users'))
|
||||
elif action == 'resend_confirmation_email':
|
||||
send_confirmation_email(user)
|
||||
flash(f'Confirmation email sent to {user.email}.', 'success')
|
||||
elif action == 'reset_uniquifier':
|
||||
reset_uniquifier(user)
|
||||
flash(f'Uniquifier reset for {user.user_name}.', 'success')
|
||||
|
||||
return redirect(prefixed_url_for('user_bp.view_users'))
|
||||
|
||||
|
||||
@user_bp.route('/view_tenant_domains/<int:tenant_id>')
|
||||
@user_bp.route('/view_tenant_domains')
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def view_tenant_domains(tenant_id):
|
||||
tenant_id = int(tenant_id)
|
||||
tenant_domains = TenantDomain.query.filter_by(tenant_id=tenant_id).all()
|
||||
def view_tenant_domains():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
tenant_id = session.get('tenant').get('id')
|
||||
query = TenantDomain.query.filter_by(tenant_id=tenant_id).order_by(TenantDomain.domain)
|
||||
|
||||
pagination = query.paginate(page=page, per_page=per_page)
|
||||
tenant_domains = pagination.items
|
||||
|
||||
# prepare table data
|
||||
prepared_data = prepare_table_for_macro(tenant_domains, [('id', ''), ('domain', ''), ('valid_to', '')])
|
||||
rows = prepare_table_for_macro(tenant_domains, [('id', ''), ('domain', ''), ('valid_to', '')])
|
||||
|
||||
# Render the users in a template
|
||||
return render_template('user/view_tenant_domains.html', tenant_domains=prepared_data)
|
||||
return render_template('user/view_tenant_domains.html', rows=rows, pagination=pagination)
|
||||
|
||||
|
||||
@user_bp.route('/handle_tenant_domain_action', methods=['POST'])
|
||||
@@ -336,7 +373,8 @@ def edit_tenant_domain(tenant_domain_id):
|
||||
f'for tenant {session["tenant"]["id"]}'
|
||||
f'Error: {str(e)}')
|
||||
return redirect(
|
||||
prefixed_url_for('user_bp.view_tenant_domains', tenant_id=session['tenant']['id'])) # Assuming there's a user profile view to redirect to
|
||||
prefixed_url_for('user_bp.view_tenant_domains',
|
||||
tenant_id=session['tenant']['id'])) # Assuming there's a user profile view to redirect to
|
||||
|
||||
return render_template('user/edit_tenant_domain.html', form=form, tenant_domain_id=tenant_domain_id)
|
||||
|
||||
@@ -374,12 +412,23 @@ def generate_chat_api_key():
|
||||
@user_bp.route('/tenant_overview', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Tenant Admin')
|
||||
def tenant_overview():
|
||||
current_app.logger.debug('Rendering tenant overview')
|
||||
current_app.logger.debug(f'current_user: {current_user}')
|
||||
current_app.logger.debug(f'Current user roles: {current_user.roles}')
|
||||
tenant_id = session['tenant']['id']
|
||||
current_app.logger.debug(f'Generating overview for tenant {tenant_id}')
|
||||
tenant = Tenant.query.get_or_404(tenant_id)
|
||||
form = TenantForm(obj=tenant)
|
||||
return render_template('user/tenant_overview.html', form=form)
|
||||
|
||||
|
||||
def reset_uniquifier(user):
|
||||
security.datastore.set_uniquifier(user)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
send_reset_email(user)
|
||||
|
||||
|
||||
def set_logging_information(obj, timestamp):
|
||||
obj.created_at = timestamp
|
||||
obj.updated_at = timestamp
|
||||
|
||||
@@ -20,6 +20,8 @@ def create_app(config_file=None):
|
||||
else:
|
||||
app.config.from_object(config_file)
|
||||
|
||||
app.config['SESSION_KEY_PREFIX'] = 'eveai_chat_'
|
||||
|
||||
logging.config.dictConfig(LOGGING)
|
||||
register_extensions(app)
|
||||
|
||||
|
||||
@@ -11,3 +11,5 @@ langchain~=0.1.17
|
||||
requests~=2.31.0
|
||||
beautifulsoup4~=4.12.3
|
||||
google~=3.0.0
|
||||
redis~=5.0.4
|
||||
itsdangerous~=2.2.0
|
||||
Reference in New Issue
Block a user