feat: Implement user and role management with repositories
- Added RoleRepository and UserRepository for managing roles and users. - Implemented methods for creating, retrieving, and assigning roles to users. - Introduced functions to ensure default roles and an admin user exist in the system. - Updated UnitOfWork to include user and role repositories. - Created new security module for password hashing and JWT token management. - Added tests for authentication flows, including registration, login, and password reset. - Enhanced HTML templates for user registration, login, and password management with error handling. - Added a logo image to the static assets.
This commit is contained in:
@@ -1,17 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Forgot Password{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% extends "base.html" %} {% block title %}Forgot Password{% endblock %} {%
|
||||
block content %}
|
||||
<div class="container">
|
||||
<h1>Forgot Password</h1>
|
||||
<form id="forgot-password-form">
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<button type="submit">Reset Password</button>
|
||||
</form>
|
||||
<p>Remember your password? <a href="/login">Login here</a></p>
|
||||
<h1>Forgot Password</h1>
|
||||
{% if errors %}
|
||||
<div class="alert alert-error">
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %} {% if message %}
|
||||
<div class="alert alert-info">{{ message }}</div>
|
||||
{% endif %}
|
||||
<form id="forgot-password-form" method="post" action="{{ form_action }}">
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required />
|
||||
</div>
|
||||
<button type="submit">Reset Password</button>
|
||||
</form>
|
||||
<p>Remember your password? <a href="/login">Login here</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% extends "base.html" %} {% block title %}Login{% endblock %} {% block content
|
||||
%}
|
||||
<div class="container">
|
||||
<h1>Login</h1>
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<p>Don't have an account? <a href="/register">Register here</a></p>
|
||||
<p><a href="/forgot-password">Forgot password?</a></p>
|
||||
<h1>Login</h1>
|
||||
{% if errors %}
|
||||
<div class="alert alert-error">
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form id="login-form" method="post" action="{{ form_action }}">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
value="{{ username | default('') }}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required />
|
||||
</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<p>Don't have an account? <a href="/register">Register here</a></p>
|
||||
<p><a href="/forgot-password">Forgot password?</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,80 +1,60 @@
|
||||
{% set dashboard_href = request.url_for('dashboard.home') if request else '/' %}
|
||||
{% set projects_href = request.url_for('projects.project_list_page') if request else '/projects/ui' %}
|
||||
{% set project_create_href = request.url_for('projects.create_project_form') if request else '/projects/create' %}
|
||||
|
||||
{% set nav_groups = [
|
||||
{
|
||||
"label": "Workspace",
|
||||
"links": [
|
||||
{"href": dashboard_href, "label": "Dashboard", "match_prefix": "/"},
|
||||
{"href": projects_href, "label": "Projects", "match_prefix": "/projects"},
|
||||
{"href": project_create_href, "label": "New Project", "match_prefix": "/projects/create"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "Insights",
|
||||
"links": [
|
||||
{"href": "/ui/simulations", "label": "Simulations"},
|
||||
{"href": "/ui/reporting", "label": "Reporting"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"label": "Configuration",
|
||||
"links": [
|
||||
{
|
||||
"href": "/ui/settings",
|
||||
"label": "Settings",
|
||||
"children": [
|
||||
{"href": "/theme-settings", "label": "Themes"},
|
||||
{"href": "/ui/currencies", "label": "Currency Management"},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] %}
|
||||
{% set projects_href = request.url_for('projects.project_list_page') if request
|
||||
else '/projects/ui' %} {% set project_create_href =
|
||||
request.url_for('projects.create_project_form') if request else
|
||||
'/projects/create' %} {% set login_href = request.url_for('auth.login_form') if
|
||||
request else '/login' %} {% set register_href =
|
||||
request.url_for('auth.register_form') if request else '/register' %} {% set
|
||||
forgot_href = request.url_for('auth.password_reset_request_form') if request
|
||||
else '/forgot-password' %} {% set nav_groups = [ { "label": "Workspace",
|
||||
"links": [ {"href": dashboard_href, "label": "Dashboard", "match_prefix": "/"},
|
||||
{"href": projects_href, "label": "Projects", "match_prefix": "/projects"},
|
||||
{"href": project_create_href, "label": "New Project", "match_prefix":
|
||||
"/projects/create"}, ], }, { "label": "Insights", "links": [ {"href":
|
||||
"/ui/simulations", "label": "Simulations"}, {"href": "/ui/reporting", "label":
|
||||
"Reporting"}, ], }, { "label": "Configuration", "links": [ { "href":
|
||||
"/ui/settings", "label": "Settings", "children": [ {"href": "/theme-settings",
|
||||
"label": "Themes"}, {"href": "/ui/currencies", "label": "Currency Management"},
|
||||
], }, ], }, { "label": "Account", "links": [ {"href": login_href, "label":
|
||||
"Login", "match_prefix": "/login"}, {"href": register_href, "label": "Register",
|
||||
"match_prefix": "/register"}, {"href": forgot_href, "label": "Forgot Password",
|
||||
"match_prefix": "/forgot-password"}, ], }, ] %}
|
||||
|
||||
<nav class="sidebar-nav" aria-label="Primary navigation">
|
||||
{% set current_path = request.url.path if request else "" %}
|
||||
{% for group in nav_groups %}
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">{{ group.label }}</div>
|
||||
<div class="sidebar-section-links">
|
||||
{% for link in group.links %}
|
||||
{% set href = link.href %}
|
||||
{% set match_prefix = link.get('match_prefix', href) %}
|
||||
{% if match_prefix == '/' %}
|
||||
{% set is_active = current_path == '/' %}
|
||||
{% else %}
|
||||
{% set is_active = current_path.startswith(match_prefix) %}
|
||||
{% endif %}
|
||||
<div class="sidebar-link-block">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
class="sidebar-link{% if is_active %} is-active{% endif %}"
|
||||
>
|
||||
{{ link.label }}
|
||||
</a>
|
||||
{% if link.children %}
|
||||
<div class="sidebar-sublinks">
|
||||
{% for child in link.children %}
|
||||
{% set child_prefix = child.get('match_prefix', child.href) %}
|
||||
{% if child_prefix == '/' %}
|
||||
{% set child_active = current_path == '/' %}
|
||||
{% else %}
|
||||
{% set child_active = current_path.startswith(child_prefix) %}
|
||||
{% endif %}
|
||||
<a
|
||||
href="{{ child.href }}"
|
||||
class="sidebar-sublink{% if child_active %} is-active{% endif %}"
|
||||
>
|
||||
{{ child.label }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% set current_path = request.url.path if request else "" %} {% for group in
|
||||
nav_groups %}
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-label">{{ group.label }}</div>
|
||||
<div class="sidebar-section-links">
|
||||
{% for link in group.links %} {% set href = link.href %} {% set
|
||||
match_prefix = link.get('match_prefix', href) %} {% if match_prefix == '/'
|
||||
%} {% set is_active = current_path == '/' %} {% else %} {% set is_active =
|
||||
current_path.startswith(match_prefix) %} {% endif %}
|
||||
<div class="sidebar-link-block">
|
||||
<a
|
||||
href="{{ href }}"
|
||||
class="sidebar-link{% if is_active %} is-active{% endif %}"
|
||||
>
|
||||
{{ link.label }}
|
||||
</a>
|
||||
{% if link.children %}
|
||||
<div class="sidebar-sublinks">
|
||||
{% for child in link.children %} {% set child_prefix =
|
||||
child.get('match_prefix', child.href) %} {% if child_prefix == '/' %}
|
||||
{% set child_active = current_path == '/' %} {% else %} {% set
|
||||
child_active = current_path.startswith(child_prefix) %} {% endif %}
|
||||
<a
|
||||
href="{{ child.href }}"
|
||||
class="sidebar-sublink{% if child_active %} is-active{% endif %}"
|
||||
>
|
||||
{{ child.label }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Register{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% extends "base.html" %} {% block title %}Register{% endblock %} {% block
|
||||
content %}
|
||||
<div class="container">
|
||||
<h1>Register</h1>
|
||||
<form id="register-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<p>Already have an account? <a href="/login">Login here</a></p>
|
||||
<h1>Register</h1>
|
||||
{% if errors %}
|
||||
<div class="alert alert-error">
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form id="register-form" method="post" action="{{ form_action }}">
|
||||
<div class="form-group">
|
||||
<label for="username">Username:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
value="{{ form_data.username if form_data else '' }}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value="{{ form_data.email if form_data else '' }}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required />
|
||||
</div>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<p>Already have an account? <a href="/login">Login here</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
36
templates/reset_password.html
Normal file
36
templates/reset_password.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends "base.html" %} {% block title %}Reset Password{% endblock %} {% block
|
||||
content %}
|
||||
<div class="container">
|
||||
<h1>Reset Password</h1>
|
||||
{% if errors %}
|
||||
<div class="alert alert-error">
|
||||
<ul>
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form id="reset-password-form" method="post" action="{{ form_action }}">
|
||||
<input type="hidden" name="token" value="{{ token | default('') }}" />
|
||||
<div class="form-group">
|
||||
<label for="password">New Password:</label>
|
||||
<input type="password" id="password" name="password" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirm Password:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit">Update Password</button>
|
||||
</form>
|
||||
<p>
|
||||
Remembered your password?
|
||||
<a href="{{ request.url_for('auth.login_form') }}">Return to login</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user