Add Password change to profile, change tables layout

This commit is contained in:
ghostersk
2025-05-27 13:27:36 +01:00
parent 510501ce7e
commit c613a564ce
7 changed files with 107 additions and 30 deletions
+2 -1
View File
@@ -6,8 +6,9 @@ from .models import User, Settings, AllowedDomain
from extensions import db
import re
def validate_password_strength(password):
def validate_password_strength(form, field):
"""Validate password based on current settings"""
password = field.data
settings = Settings.query.first()
if not settings:
return # No settings found, allow any password
+52 -1
View File
@@ -1933,4 +1933,55 @@ def clear_error_logs():
'success': True,
'message': f'Deleted {deleted_count} error logs older than {days_to_keep} days',
'deleted_count': deleted_count
})
})
@auth.route('/change_password', methods=['POST'])
@login_required
def change_password():
try:
form = ChangePasswordForm()
if form.validate_on_submit():
# Check if current password is correct
if not bcrypt.check_password_hash(current_user.password, form.current_password.data):
flash('Current password is incorrect.', 'danger')
return redirect(url_for('auth.profile'))
# Set new password
hashed_password = bcrypt.generate_password_hash(form.new_password.data).decode('utf-8')
current_user.password = hashed_password
db.session.commit()
# Log password change
logger.info(
"User changed their password: %s (ID: %s) from IP %s",
current_user.username,
current_user.id,
request.remote_addr,
extra={
'ip_address': request.remote_addr,
'user_agent': request.headers.get('User-Agent'),
'user_id': current_user.id,
'username': current_user.username,
'action': 'password_change'
}
)
flash('Your password has been updated!', 'success')
return redirect(url_for('auth.profile'))
else:
for field, errors in form.errors.items():
for error in errors:
flash(f'{error}', 'danger')
return redirect(url_for('auth.profile'))
except Exception as e:
logger.exception("Error in change_password function", extra={
'ip_address': request.remote_addr,
'user_agent': request.headers.get('User-Agent'),
'user_id': current_user.id if current_user.is_authenticated else None,
'username': current_user.username if current_user.is_authenticated else None,
'error': str(e)
})
flash('An error occurred while changing your password. Please try again.', 'danger')
return redirect(url_for('auth.profile'))
+2 -2
View File
@@ -21,7 +21,7 @@
<form method="POST" action="{{ url_for('auth.create_company_api_key', company_id=company.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<label for="description" class="form-label">Site Name</label>
<input type="text" class="form-control" id="description" name="description" required>
</div>
<button type="submit" class="btn btn-primary">Generate New Key</button>
@@ -41,7 +41,7 @@
<table class="table table-striped" id="apiKeysTable">
<thead>
<tr>
<th>Description</th>
<th>Site Name</th>
<th>Key</th>
<th>Created</th>
<th>Last Used</th>
+2 -3
View File
@@ -28,9 +28,8 @@
<select class="form-select" id="api_key" name="api_key" required>
<option value="">Select API Key</option>
{% for api_key in api_keys %}
<option value="{{ api_key.id }}">
{{ api_key.key[:8] }}...{{ api_key.key[-8:] }}
{% if api_key.description %}({{ api_key.description }}){% endif %}
<option value="{{ api_key.id }}">
{{ api_key.description }} - {{ api_key.key[:8] }}...{{ api_key.key[-8:] }}
</option>
{% endfor %}
</select>
+20
View File
@@ -15,6 +15,26 @@
<hr>
<h5>Change Password</h5>
<form method="POST" action="{{ url_for('auth.change_password') }}">
{{ change_password_form.hidden_tag() }}
<div class="mb-3">
{{ change_password_form.current_password.label(class="form-label") }}
{{ change_password_form.current_password(class="form-control") }}
</div>
<div class="mb-3">
{{ change_password_form.new_password.label(class="form-label") }}
{{ change_password_form.new_password(class="form-control") }}
</div>
<div class="mb-3">
{{ change_password_form.confirm_password.label(class="form-label") }}
{{ change_password_form.confirm_password(class="form-control") }}
</div>
{{ change_password_form.submit(class="btn btn-primary mb-3") }}
</form>
<hr>
<h5>Two-Factor Authentication</h5>
<p>Status:
<span class="badge {% if current_user.mfa_enabled %}bg-success{% else %}bg-warning{% endif %}">
+22 -23
View File
@@ -208,31 +208,31 @@
<table id="logsTable" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>Timestamp</th>
<th>Event Type</th>
<th>User Name</th>
{% if current_user.is_global_admin() and not selected_company_id %}
<th>Company</th>
{% endif %}
<th>Site</th>
<th>Computer Name</th>
<th>IP Address</th>
<th>Site</th>
<th>Timestamp</th>
{% if current_user.is_global_admin() and not selected_company_id %}
<th>Company</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for log in logs %}
<tr>
<td>{{ log.event_type }}</td>
<td>{{ log.user_name }}</td>
<td>{{ log.computer_name }}</td>
<td>{{ log.ip_address }}</td>
<td>{{ log.api_key.description if log.api_key else 'N/A' }}</td>
<td data-order="{{ log.timestamp.strftime('%Y%m%d%H%M%S') }}">
{{ log.timestamp|format_datetime }}
</td>
<td>{{ log.event_type }}</td>
<td>{{ log.user_name }}</td>
{% if current_user.is_global_admin() and not selected_company_id %}
<td>{{ log.company.name if log.company else 'N/A' }}</td>
<td>{{ log.company.name if log.company else 'N/A' }}</td>
{% endif %}
<td>{{ log.api_key.description if log.api_key else 'N/A' }}</td>
<td>{{ log.computer_name }}</td>
<td>{{ log.ip_address }}</td>
</tr>
{% endfor %}
</tbody>
@@ -310,14 +310,15 @@
var table = $('#logsTable').DataTable({
pageLength: 50,
lengthMenu: [[50, 100, 200, 500, 1000], [50, 100, 200, 500, 1000]],
order: [[{% if current_user.is_global_admin() and not selected_company_id %}6{% else %}5{% endif %}, 'desc']], // Sort by timestamp column descending
order: [[0, 'desc']], // Sort by timestamp column descending
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
columnDefs: [
{
targets: [2, 3], // Computer Name and IP Address columns
visible: false // Hide by default
targets: [5, 6], // Computer Name and IP Address columns are now at indices 5 and 6
visible: false, // Hide by default
searchable: true // Still allow searching in these columns
}
],
buttons: [
@@ -363,15 +364,13 @@
// Column names for the visibility controls
var columnNames = [
'Timestamp',
'Event Type',
'User Name',
'Computer Name',
'IP Address',
'User Name',
{% if current_user.is_global_admin() and not selected_company_id %}'Company',{% endif %}
'Site',
'Timestamp'
{% if current_user.is_global_admin() and not selected_company_id %},
'Company'
{% endif %}
'Computer Name',
'IP Address'
];
// Load saved column visibility from localStorage
@@ -384,10 +383,10 @@
console.log('Error parsing saved column visibility:', e);
}
}
// Default visibility - hide Computer Name (2) and IP Address (3)
// Default visibility - hide Computer Name (5) and IP Address (6)
var defaultVisibility = {};
columnNames.forEach(function(name, index) {
defaultVisibility[index] = index !== 2 && index !== 3;
defaultVisibility[index] = index !== 5 && index !== 6;
});
return defaultVisibility;
}
@@ -253,6 +253,13 @@
endDate: moment(),
locale: {
format: 'YYYY-MM-DD HH:mm'
},
ranges: {
'Last 2 Days': [moment().subtract(2, 'days'), moment()],
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
'This Month': [moment().startOf('month'), moment().endOf('month')],
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
}
});