Add Password change to profile, change tables layout
This commit is contained in:
+2
-1
@@ -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
@@ -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'))
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}">
|
||||
|
||||
@@ -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')]
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user