refactor security to Flask-Security - Part 2
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
from flask import Flask
|
||||
from flask_security import SQLAlchemyUserDatastore
|
||||
@@ -29,7 +30,9 @@ def create_app(config_file=None):
|
||||
|
||||
register_blueprints(app)
|
||||
|
||||
print(app.config.get('SQLALCHEMY_DATABASE_URI'))
|
||||
if app.config['DEBUG'] is True:
|
||||
app.logger.setLevel(logging.DEBUG)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
@@ -45,8 +48,6 @@ def register_extensions(app):
|
||||
def register_blueprints(app):
|
||||
from .views.user_views import user_bp
|
||||
app.register_blueprint(user_bp)
|
||||
from .views.auth_views import auth_bp
|
||||
app.register_blueprint(auth_bp)
|
||||
|
||||
|
||||
def register_api(app):
|
||||
|
||||
@@ -76,7 +76,7 @@ class User(db.Model, UserMixin):
|
||||
login_count = db.Column(db.Integer, nullable=False, default=0)
|
||||
|
||||
# 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)
|
||||
|
||||
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>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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://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>
|
||||
{% block scripts %}
|
||||
{%- endblock scripts %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<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>
|
||||
<div class="container">
|
||||
<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' %}
|
||||
|
||||
{% 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 %}
|
||||
<form action="" method="post" novalidate>
|
||||
<form action="" method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
<p>
|
||||
{{ form.user_name.label }}<br>
|
||||
{{ form.user_name(size=80) }}
|
||||
@@ -16,6 +20,10 @@
|
||||
{{ form.password.label }}<br>
|
||||
{{ form.password(size=80) }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.confirm_password.label }}<br>
|
||||
{{ form.confirm_password(size=80) }}
|
||||
</p>
|
||||
<p>
|
||||
{{ form.first_name.label }}<br>
|
||||
{{ form.first_name(size=80) }}
|
||||
@@ -28,18 +36,6 @@
|
||||
{{ form.is_active.label }}<br>
|
||||
{{ form.is_active() }}
|
||||
</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>
|
||||
{{ form.valid_to.label }}<br>
|
||||
{{ form.valid_to() }}
|
||||
@@ -51,3 +47,8 @@
|
||||
<p>{{ form.submit() }}</p>
|
||||
</form>
|
||||
{% 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
|
||||
import uuid
|
||||
from datetime import datetime as dt, timezone as tz
|
||||
from flask import request, redirect, url_for, flash, render_template, Blueprint, session
|
||||
from flask_security import hash_password
|
||||
@@ -67,6 +68,10 @@ def tenant():
|
||||
def user():
|
||||
form = UserForm()
|
||||
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)
|
||||
new_user = User(
|
||||
user_name=form.user_name.data,
|
||||
@@ -79,6 +84,7 @@ def user():
|
||||
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
|
||||
@@ -100,3 +106,18 @@ def user():
|
||||
flash(f'Failed to add user. Error: {str(e)}')
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user