refactor security to Flask-Security - Part 2
This commit is contained in:
13
config.py
13
config.py
@@ -8,6 +8,8 @@ class Config(object):
|
|||||||
DEVELOPMENT = False
|
DEVELOPMENT = False
|
||||||
SECRET_KEY = '97867c1491bea5ee6a8e8436eb11bf2ba6a69ff53ab1b17ecba450d0f2e572e1'
|
SECRET_KEY = '97867c1491bea5ee6a8e8436eb11bf2ba6a69ff53ab1b17ecba450d0f2e572e1'
|
||||||
|
|
||||||
|
# WTF_CSRF_ENABLED = True
|
||||||
|
|
||||||
# flask-security-too settings
|
# flask-security-too settings
|
||||||
SECURITY_PASSWORD_SALT = '228614859439123264035565568761433607235'
|
SECURITY_PASSWORD_SALT = '228614859439123264035565568761433607235'
|
||||||
REMEMBER_COOKIE_SAMESITE = 'strict'
|
REMEMBER_COOKIE_SAMESITE = 'strict'
|
||||||
@@ -16,9 +18,7 @@ class Config(object):
|
|||||||
SECURITY_TRACKABLE = True
|
SECURITY_TRACKABLE = True
|
||||||
SECURITY_PASSWORD_COMPLEXITY_CHECKER = 'zxcvbn'
|
SECURITY_PASSWORD_COMPLEXITY_CHECKER = 'zxcvbn'
|
||||||
SECURITY_POST_LOGIN_VIEW = '/user/tenant'
|
SECURITY_POST_LOGIN_VIEW = '/user/tenant'
|
||||||
SECURITY_REGISTERABLE = False
|
SECURITY_RECOVERABLE = True
|
||||||
SECURITY_LOGINABLE = False
|
|
||||||
SECURITY_LOGOUTABLE = False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -26,6 +26,9 @@ class Config(object):
|
|||||||
MAIL_SERVER = 'mail.flow-it.net'
|
MAIL_SERVER = 'mail.flow-it.net'
|
||||||
MAIL_PORT = 465
|
MAIL_PORT = 465
|
||||||
MAIL_USE_TLS = True
|
MAIL_USE_TLS = True
|
||||||
|
MAIL_USE_SSL = False
|
||||||
|
MAIL_DEFAULT_SENDER = 'eveAI Admin <eveai_admin@flow-it.net>'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DevConfig(Config):
|
class DevConfig(Config):
|
||||||
@@ -36,8 +39,8 @@ class DevConfig(Config):
|
|||||||
EXPLAIN_TEMPLATE_LOADING = True
|
EXPLAIN_TEMPLATE_LOADING = True
|
||||||
|
|
||||||
# flask-mailman settings
|
# flask-mailman settings
|
||||||
MAIL_USERNAME = 'eveai_admin@flow-it.net'
|
MAIL_USERNAME = 'eveai_super@flow-it.net'
|
||||||
MAIL_PASSWORD = 'FgV650K3ow#5FeBcZc5'
|
MAIL_PASSWORD = '$6xsWGbNtx$CFMQZqc*'
|
||||||
|
|
||||||
|
|
||||||
class ProdConfig(Config):
|
class ProdConfig(Config):
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_security import SQLAlchemyUserDatastore
|
from flask_security import SQLAlchemyUserDatastore
|
||||||
@@ -29,7 +30,9 @@ def create_app(config_file=None):
|
|||||||
|
|
||||||
register_blueprints(app)
|
register_blueprints(app)
|
||||||
|
|
||||||
print(app.config.get('SQLALCHEMY_DATABASE_URI'))
|
if app.config['DEBUG'] is True:
|
||||||
|
app.logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
@@ -45,8 +48,6 @@ def register_extensions(app):
|
|||||||
def register_blueprints(app):
|
def register_blueprints(app):
|
||||||
from .views.user_views import user_bp
|
from .views.user_views import user_bp
|
||||||
app.register_blueprint(user_bp)
|
app.register_blueprint(user_bp)
|
||||||
from .views.auth_views import auth_bp
|
|
||||||
app.register_blueprint(auth_bp)
|
|
||||||
|
|
||||||
|
|
||||||
def register_api(app):
|
def register_api(app):
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class User(db.Model, UserMixin):
|
|||||||
login_count = db.Column(db.Integer, nullable=False, default=0)
|
login_count = db.Column(db.Integer, nullable=False, default=0)
|
||||||
|
|
||||||
# Relations
|
# Relations
|
||||||
roles = db.relationship('Role', secondary='public.roles_users', backref=db.backref('users', lazy='dynamic'))
|
roles = db.relationship('Role', secondary=RolesUsers.__table__, backref=db.backref('users', lazy='dynamic'))
|
||||||
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
0
eveai_app/static/assets/css/eveai.css
Normal file
0
eveai_app/static/assets/css/eveai.css
Normal file
@@ -35,17 +35,36 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<span>
|
<span>
|
||||||
{% block content %}{% endblock %}
|
<div class="container mb-4">
|
||||||
|
<div class="row mt-lg-n12 mt-md-n12 mt-n12 justify-content-center">
|
||||||
|
<div class="col-xl-8 col-lg-5 col-md-7 mx-auto">
|
||||||
|
<div class="card mt-8">
|
||||||
|
<div class="card-header p-0 position-relative mt-n4 mx-3 z-index-2">
|
||||||
|
<div class="bg-gradient-success shadow-success border-radius-lg py-3 pe-1 text-center py-4">
|
||||||
|
<h4 class="font-weight-bolder text-white mt-1">
|
||||||
|
{% block content_title %}{% endblock %}
|
||||||
|
</h4>
|
||||||
|
<p class="mb-1 text-sm text-white">
|
||||||
|
{% block content_description %}{% endblock %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center pt-0 px-lg-2 px-1">
|
||||||
|
<p class="mb-4 text-sm mx-auto">
|
||||||
|
{% block content_footer %}{% endblock %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
@@ -56,6 +75,7 @@
|
|||||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||||
|
{% block scripts %}
|
||||||
</body>
|
{%- endblock scripts %}
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<header class="header-2">
|
<header class="header-2">
|
||||||
<div class="page-header min-vh-75" style="background-image: url({{url_for('static', filename='/assets/img/EveAI_bg1.jpg')}})" loading="lazy">
|
<div class="page-header min-vh-75" style="background-image: url({{url_for('static', filename='/assets/img/EveAI_bg2.jpg')}})" loading="lazy">
|
||||||
<span class="mask bg-gradient-primary opacity-4"></span>
|
<span class="mask bg-gradient-primary opacity-4"></span>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}Login{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form action="" method="post" novalidate>
|
|
||||||
<p>
|
|
||||||
{{ form.email.label }}<br>
|
|
||||||
{{ form.email(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password.label }}<br>
|
|
||||||
{{ form.password(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}Login{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form action="" method="post" novalidate>
|
|
||||||
<p>
|
|
||||||
{{ form.email.label }}<br>
|
|
||||||
{{ form.email(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password.label }}<br>
|
|
||||||
{{ form.password(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
3
eveai_app/templates/security/base.html
Normal file
3
eveai_app/templates/security/base.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{# djlint:off H030,H031 #}
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
18
eveai_app/templates/security/forgot_password.html
Normal file
18
eveai_app/templates/security/forgot_password.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{% extends "security/base.html" %}
|
||||||
|
{% from "security/_macros.html" import render_field_with_errors, render_field, render_field_errors, render_form_errors %}
|
||||||
|
|
||||||
|
{% block content_title %}
|
||||||
|
{{ _fsdomain('Send password reset instructions') }}
|
||||||
|
{% endblock content_title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include "security/_messages.html" %}
|
||||||
|
<form action="{{ url_for_security('forgot_password') }}" method="post" name="forgot_password_form">
|
||||||
|
{{ forgot_password_form.hidden_tag() }}
|
||||||
|
{{ render_form_errors(forgot_password_form) }}
|
||||||
|
{{ render_field_with_errors(forgot_password_form.email) }}
|
||||||
|
{{ render_field_errors(forgot_password_form.csrf_token) }}
|
||||||
|
{{ render_field(forgot_password_form.submit) }}
|
||||||
|
</form>
|
||||||
|
{% include "security/_menu.html" %}
|
||||||
|
{% endblock content %}
|
||||||
29
eveai_app/templates/security/login_user.html
Normal file
29
eveai_app/templates/security/login_user.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'security/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Login{% endblock %}
|
||||||
|
{% block content_title %}Sign In{% endblock %}
|
||||||
|
{% block content_description %}Enter your email and password to Sign In{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post" novalidate>
|
||||||
|
{{ login_user_form.hidden_tag() }}
|
||||||
|
<p>
|
||||||
|
{{ login_user_form.email.label }}<br>
|
||||||
|
{{ login_user_form.email(size=80) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ login_user_form.password.label }}<br>
|
||||||
|
{{ login_user_form.password(size=80) }}
|
||||||
|
</p>
|
||||||
|
{# <p>#}
|
||||||
|
{# {{ login_user_form.remember_me }}#}
|
||||||
|
{# {{ login_user_form.remember_me.label }}#}
|
||||||
|
{# </p>#}
|
||||||
|
<p>{{ login_user_form.submit() }}</p>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content_footer %}
|
||||||
|
First time here? Forgot your password?
|
||||||
|
<a href="/reset" class="text-success text-gradient font-weight-bold">Request new password</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{#{{ url_for_security('reset_password', token=reset_password_token) }}#}
|
||||||
19
eveai_app/templates/security/reset_password.html
Normal file
19
eveai_app/templates/security/reset_password.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "security/base.html" %}
|
||||||
|
{% from "security/_macros.html" import render_field_with_errors, render_field, render_field_errors, render_form_errors %}
|
||||||
|
|
||||||
|
{% 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" %}
|
||||||
|
{# <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) }}
|
||||||
|
</form>
|
||||||
|
{# {% include "security/_menu.html" %}#}
|
||||||
|
{% endblock content %}
|
||||||
25
eveai_app/templates/user/edit_user.html
Normal file
25
eveai_app/templates/user/edit_user.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Update User{% endblock %}
|
||||||
|
|
||||||
|
{% block content_title %}Update User{% endblock %}
|
||||||
|
{% block content_description %}Update given user account{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ field.label }}
|
||||||
|
{{ field(class="form-control", disabled=true) if field.name in ['email', 'user_name'] else field(class="form-control") }}
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary">Update User</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}User Details{% endblock %}
|
{% block title %}User Registration{% endblock %}
|
||||||
|
|
||||||
|
{% block content_title %}Register User{% endblock %}
|
||||||
|
{% block content_description %}Make a new user account{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="" method="post" novalidate>
|
<form action="" method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
<p>
|
<p>
|
||||||
{{ form.user_name.label }}<br>
|
{{ form.user_name.label }}<br>
|
||||||
{{ form.user_name(size=80) }}
|
{{ form.user_name(size=80) }}
|
||||||
@@ -16,6 +20,10 @@
|
|||||||
{{ form.password.label }}<br>
|
{{ form.password.label }}<br>
|
||||||
{{ form.password(size=80) }}
|
{{ form.password(size=80) }}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.confirm_password.label }}<br>
|
||||||
|
{{ form.confirm_password(size=80) }}
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ form.first_name.label }}<br>
|
{{ form.first_name.label }}<br>
|
||||||
{{ form.first_name(size=80) }}
|
{{ form.first_name(size=80) }}
|
||||||
@@ -28,18 +36,6 @@
|
|||||||
{{ form.is_active.label }}<br>
|
{{ form.is_active.label }}<br>
|
||||||
{{ form.is_active() }}
|
{{ form.is_active() }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
{{ form.is_tester.label }}<br>
|
|
||||||
{{ form.is_tester() }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.is_admin.label }}<br>
|
|
||||||
{{ form.is_admin() }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.is_super.label }}<br>
|
|
||||||
{{ form.is_super() }}
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
{{ form.valid_to.label }}<br>
|
{{ form.valid_to.label }}<br>
|
||||||
{{ form.valid_to() }}
|
{{ form.valid_to() }}
|
||||||
@@ -51,3 +47,8 @@
|
|||||||
<p>{{ form.submit() }}</p>
|
<p>{{ form.submit() }}</p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content_footer %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from flask_wtf import FlaskForm
|
|
||||||
from wtforms import PasswordField, SubmitField, EmailField, BooleanField
|
|
||||||
from wtforms.validators import DataRequired, Length, Email
|
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(FlaskForm):
|
|
||||||
email = EmailField('Email', validators=[DataRequired(), Email()])
|
|
||||||
password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
|
|
||||||
remember_me = BooleanField('Remember me')
|
|
||||||
submit = SubmitField('Login')
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
from datetime import datetime as dt, timezone as tz
|
|
||||||
from flask import request, redirect, url_for, flash, render_template, Blueprint, jsonify, session
|
|
||||||
from flask_security import login_user, logout_user
|
|
||||||
|
|
||||||
from ..models.user import User, Tenant
|
|
||||||
from .auth_forms import LoginForm
|
|
||||||
|
|
||||||
auth_bp = Blueprint('auth_bp', __name__, template_folder='templates')
|
|
||||||
|
|
||||||
|
|
||||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
form = LoginForm()
|
|
||||||
if form.validate_on_submit():
|
|
||||||
email = form.email.data
|
|
||||||
password = form.password.data
|
|
||||||
remember_me = True if form.remember_me.data else False
|
|
||||||
|
|
||||||
user = User.query.filter_by(email=email).first()
|
|
||||||
tenant = Tenant.query.filter_by(id=user.tenant_id).first()
|
|
||||||
if user and user.verify_and_update_password(password):
|
|
||||||
if user.is_active:
|
|
||||||
login_user(user, remember=remember_me)
|
|
||||||
next_page = request.args.get('next')
|
|
||||||
|
|
||||||
session['tenant_id'] = user.tenant_id
|
|
||||||
session['tenant_name'] = tenant.name
|
|
||||||
|
|
||||||
return redirect(next_page)
|
|
||||||
else:
|
|
||||||
flash('Account disabled. Please contact your administrator.', category='error')
|
|
||||||
else:
|
|
||||||
flash('Invalid email or password.', category='error')
|
|
||||||
|
|
||||||
return render_template('login.html', form=form)
|
|
||||||
|
|
||||||
|
|
||||||
@auth_bp.route('/logout', methods=['POST'])
|
|
||||||
def logout():
|
|
||||||
logout_user()
|
|
||||||
|
|
||||||
# Clear session data
|
|
||||||
session.pop('tenant_id', None)
|
|
||||||
session.pop('tenant_name', None)
|
|
||||||
|
|
||||||
return redirect(url_for('/'))
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# from . import user_bp
|
# from . import user_bp
|
||||||
|
import uuid
|
||||||
from datetime import datetime as dt, timezone as tz
|
from datetime import datetime as dt, timezone as tz
|
||||||
from flask import request, redirect, url_for, flash, render_template, Blueprint, session
|
from flask import request, redirect, url_for, flash, render_template, Blueprint, session
|
||||||
from flask_security import hash_password
|
from flask_security import hash_password
|
||||||
@@ -67,6 +68,10 @@ def tenant():
|
|||||||
def user():
|
def user():
|
||||||
form = UserForm()
|
form = UserForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
|
if form.password.data != form.confirm_password.data:
|
||||||
|
flash('Passwords do not match.')
|
||||||
|
|
||||||
|
# Handle the required attributes
|
||||||
hashed_password = hash_password(form.password.data)
|
hashed_password = hash_password(form.password.data)
|
||||||
new_user = User(
|
new_user = User(
|
||||||
user_name=form.user_name.data,
|
user_name=form.user_name.data,
|
||||||
@@ -79,6 +84,7 @@ def user():
|
|||||||
tenant_id=form.tenant_id.data
|
tenant_id=form.tenant_id.data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
new_user.fs_uniquifier = str(uuid.uuid4())
|
||||||
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
|
||||||
@@ -100,3 +106,18 @@ def user():
|
|||||||
flash(f'Failed to add user. Error: {str(e)}')
|
flash(f'Failed to add user. Error: {str(e)}')
|
||||||
|
|
||||||
return render_template('user/user.html', form=form)
|
return render_template('user/user.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
|
@user_bp.route('/user/<int:user_id>', methods=['GET', 'POST'])
|
||||||
|
def edit_user(user_id):
|
||||||
|
user = User.query.get_or_404(user_id) # This will return a 404 if no user is found
|
||||||
|
form = UserForm(obj=user)
|
||||||
|
|
||||||
|
if request.method == 'POST' and form.validate_on_submit():
|
||||||
|
# Populate the user with form data
|
||||||
|
form.populate_obj(user)
|
||||||
|
db.session.commit()
|
||||||
|
flash('User updated successfully.', 'success')
|
||||||
|
return redirect(url_for('user_bp.user_profile', user_id=user.id)) # Assuming there's a user profile view to redirect to
|
||||||
|
|
||||||
|
return render_template('user/edit_user.html', form=form, user_id=user_id)
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" itemscope itemtype="http://schema.org/WebPage">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href="{{url_for('static', filename='assets/img/apple-icon.png')}}">
|
|
||||||
<link rel="icon" type="image/png" href="{{url_for('static', filename='./assets/img/favicon.png')}}">
|
|
||||||
<title>
|
|
||||||
{% block title %}{% endblock %}
|
|
||||||
</title>
|
|
||||||
<!-- Fonts and icons -->
|
|
||||||
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900|Roboto+Slab:400,700" />
|
|
||||||
<!-- Nucleo Icons -->
|
|
||||||
<link href="{{url_for('static', filename='/assets/css/nucleo-icons.css" rel="stylesheet')}}" />
|
|
||||||
<link href="{{url_for('static', filename='/assets/css/nucleo-svg.css" rel="stylesheet')}}" />
|
|
||||||
<!-- Font Awesome Icons -->
|
|
||||||
<script src="https://kit.fontawesome.com/42d5adcbca.js" crossorigin="anonymous"></script>
|
|
||||||
<!-- Material Icons -->
|
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
|
|
||||||
<!-- CSS Files -->
|
|
||||||
<link id="pagestyle" href="{{url_for('static', filename='/assets/css/material-kit-pro.css')}}" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="presentation-page bg-gray-200">
|
|
||||||
{% include 'navbar.html' %}
|
|
||||||
{% include 'header.html' %}
|
|
||||||
{% with messages = get_flashed_messages() %}
|
|
||||||
{% if messages%}
|
|
||||||
{% for message in messages%}
|
|
||||||
<p>{{message}}</p>
|
|
||||||
{%endfor%}
|
|
||||||
{%endif%}
|
|
||||||
{%endwith%}
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<span>
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
{# {% include 'footer.html' %}#}
|
|
||||||
|
|
||||||
<!-- Optional JavaScript -->
|
|
||||||
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
|
||||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<header class="header-2">
|
|
||||||
<div class="page-header min-vh-75" style="background-image: url({{url_for('static', filename='/assets/img/EveAI_bg1.jpg')}})" loading="lazy">
|
|
||||||
<span class="mask bg-gradient-primary opacity-4"></span>
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-7 text-center mx-auto">
|
|
||||||
<h1 class="text-white pt-3 mt-n5">EveAI Virtual Assistant</h1>
|
|
||||||
<p class="lead text-white mt-3 px-5">Enhance Customer Interaction with AI</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}Login{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form action="" method="post" novalidate>
|
|
||||||
<p>
|
|
||||||
{{ form.email.label }}<br>
|
|
||||||
{{ form.email(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password.label }}<br>
|
|
||||||
{{ form.password(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}Login{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form action="" method="post" novalidate>
|
|
||||||
<p>
|
|
||||||
{{ form.email.label }}<br>
|
|
||||||
{{ form.email(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ form.password.label }}<br>
|
|
||||||
{{ form.password(size=80) }}
|
|
||||||
</p>
|
|
||||||
<p>{{ form.submit() }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<!-- Navbar Light -->
|
|
||||||
<div class="navbar navbar-expand-lg navbar-light bg-white z-index-3 py-3">
|
|
||||||
<div class="container position-sticky z-index-sticky top-0">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<nav class="navbar navbar-expand-lg blur border-radius-xl top-0 z-index-fixed shadow position-absolute my-3 py-2 start-0 end-0 mx-4">
|
|
||||||
<div class="container-fluid px-0">
|
|
||||||
<a class="navbar-brand font-weight-bolder ms-sm-3 d-none d-md-block" href="https://www.flow-it.net" rel="tooltip" title="Idee Generated & Implemented by Flow IT" data-placement="bottom">
|
|
||||||
EveAI
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<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">
|
|
||||||
<li class="nav-item dropdown dropdown-hover mx-2">
|
|
||||||
<a role="button" class="nav-link ps-2 d-flex cursor-pointer align-items-center" id="dropdownMenuUser" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<i class="material-icons opacity-6 me-2 text-md">contacts</i>
|
|
||||||
User Mgmt
|
|
||||||
<img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-2 d-lg-block d-none">
|
|
||||||
<img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-1 d-lg-none d-block ms-auto">
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-animation ms-n3 dropdown-md p-3 border-radius-xl mt-0 mt-lg-3" aria-labelledby="dropdownMenuPages">
|
|
||||||
<div class="d-none d-lg-flex">
|
|
||||||
<ul class="list-group w-100">
|
|
||||||
<li class="nav-item dropdown dropdown-hover dropdown-subitem list-group-item border-0 p-0">
|
|
||||||
<a class="dropdown-item ps-3 border-radius-md mb-1" href="/user/tenant">
|
|
||||||
<span>Tenant Registration</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item ps-3 border-radius-md mb-1" href="/user/user">
|
|
||||||
<span>User Registration</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item dropdown dropdown-hover mx-2">
|
|
||||||
<a role="button" class="nav-link ps-2 d-flex cursor-pointer align-items-center" id="dropdownMenuUser" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<i class="material-icons opacity-6 me-2 text-md">contacts</i>
|
|
||||||
Account
|
|
||||||
<img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-2 d-lg-block d-none">
|
|
||||||
<img src="{{url_for('static', filename='/assets/img/down-arrow-dark.svg')}}" alt="down-arrow" class="arrow ms-1 d-lg-none d-block ms-auto">
|
|
||||||
</a>
|
|
||||||
<div class="dropdown-menu dropdown-menu-animation ms-n3 dropdown-md p-3 border-radius-xl mt-0 mt-lg-3" aria-labelledby="dropdownMenuPages">
|
|
||||||
<div class="d-none d-lg-flex">
|
|
||||||
<ul class="list-group w-100">
|
|
||||||
<li class="nav-item dropdown dropdown-hover dropdown-subitem list-group-item border-0 p-0">
|
|
||||||
<a class="dropdown-item ps-3 border-radius-md mb-1" href="/login">
|
|
||||||
<span>Login</span>
|
|
||||||
</a>
|
|
||||||
<a class="dropdown-item ps-3 border-radius-md mb-1" href="/logout">
|
|
||||||
<span>Logout</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- End Navbar -->
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user