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
|
from extensions import db
|
||||||
import re
|
import re
|
||||||
|
|
||||||
def validate_password_strength(password):
|
def validate_password_strength(form, field):
|
||||||
"""Validate password based on current settings"""
|
"""Validate password based on current settings"""
|
||||||
|
password = field.data
|
||||||
settings = Settings.query.first()
|
settings = Settings.query.first()
|
||||||
if not settings:
|
if not settings:
|
||||||
return # No settings found, allow any password
|
return # No settings found, allow any password
|
||||||
|
|||||||
@@ -1934,3 +1934,54 @@ def clear_error_logs():
|
|||||||
'message': f'Deleted {deleted_count} error logs older than {days_to_keep} days',
|
'message': f'Deleted {deleted_count} error logs older than {days_to_keep} days',
|
||||||
'deleted_count': deleted_count
|
'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) }}">
|
<form method="POST" action="{{ url_for('auth.create_company_api_key', company_id=company.id) }}">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<div class="mb-3">
|
<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>
|
<input type="text" class="form-control" id="description" name="description" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Generate New Key</button>
|
<button type="submit" class="btn btn-primary">Generate New Key</button>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<table class="table table-striped" id="apiKeysTable">
|
<table class="table table-striped" id="apiKeysTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Description</th>
|
<th>Site Name</th>
|
||||||
<th>Key</th>
|
<th>Key</th>
|
||||||
<th>Created</th>
|
<th>Created</th>
|
||||||
<th>Last Used</th>
|
<th>Last Used</th>
|
||||||
|
|||||||
@@ -29,8 +29,7 @@
|
|||||||
<option value="">Select API Key</option>
|
<option value="">Select API Key</option>
|
||||||
{% for api_key in api_keys %}
|
{% for api_key in api_keys %}
|
||||||
<option value="{{ api_key.id }}">
|
<option value="{{ api_key.id }}">
|
||||||
{{ api_key.key[:8] }}...{{ api_key.key[-8:] }}
|
{{ api_key.description }} - {{ api_key.key[:8] }}...{{ api_key.key[-8:] }}
|
||||||
{% if api_key.description %}({{ api_key.description }}){% endif %}
|
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -15,6 +15,26 @@
|
|||||||
|
|
||||||
<hr>
|
<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>
|
<h5>Two-Factor Authentication</h5>
|
||||||
<p>Status:
|
<p>Status:
|
||||||
<span class="badge {% if current_user.mfa_enabled %}bg-success{% else %}bg-warning{% endif %}">
|
<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%">
|
<table id="logsTable" class="table table-striped table-bordered" style="width:100%">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
<th>Event Type</th>
|
<th>Event Type</th>
|
||||||
<th>User Name</th>
|
<th>User Name</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 %}
|
{% if current_user.is_global_admin() and not selected_company_id %}
|
||||||
<th>Company</th>
|
<th>Company</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<th>Site</th>
|
||||||
|
<th>Computer Name</th>
|
||||||
|
<th>IP Address</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for log in logs %}
|
{% for log in logs %}
|
||||||
<tr>
|
<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') }}">
|
<td data-order="{{ log.timestamp.strftime('%Y%m%d%H%M%S') }}">
|
||||||
{{ log.timestamp|format_datetime }}
|
{{ log.timestamp|format_datetime }}
|
||||||
</td>
|
</td>
|
||||||
|
<td>{{ log.event_type }}</td>
|
||||||
|
<td>{{ log.user_name }}</td>
|
||||||
{% if current_user.is_global_admin() and not selected_company_id %}
|
{% 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 %}
|
{% 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>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -310,14 +310,15 @@
|
|||||||
var table = $('#logsTable').DataTable({
|
var table = $('#logsTable').DataTable({
|
||||||
pageLength: 50,
|
pageLength: 50,
|
||||||
lengthMenu: [[50, 100, 200, 500, 1000], [50, 100, 200, 500, 1000]],
|
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>>' +
|
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"tr>>' +
|
||||||
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
|
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
|
||||||
columnDefs: [
|
columnDefs: [
|
||||||
{
|
{
|
||||||
targets: [2, 3], // Computer Name and IP Address columns
|
targets: [5, 6], // Computer Name and IP Address columns are now at indices 5 and 6
|
||||||
visible: false // Hide by default
|
visible: false, // Hide by default
|
||||||
|
searchable: true // Still allow searching in these columns
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
buttons: [
|
buttons: [
|
||||||
@@ -363,15 +364,13 @@
|
|||||||
|
|
||||||
// Column names for the visibility controls
|
// Column names for the visibility controls
|
||||||
var columnNames = [
|
var columnNames = [
|
||||||
|
'Timestamp',
|
||||||
'Event Type',
|
'Event Type',
|
||||||
'User Name',
|
'User Name',
|
||||||
'Computer Name',
|
{% if current_user.is_global_admin() and not selected_company_id %}'Company',{% endif %}
|
||||||
'IP Address',
|
|
||||||
'Site',
|
'Site',
|
||||||
'Timestamp'
|
'Computer Name',
|
||||||
{% if current_user.is_global_admin() and not selected_company_id %},
|
'IP Address'
|
||||||
'Company'
|
|
||||||
{% endif %}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Load saved column visibility from localStorage
|
// Load saved column visibility from localStorage
|
||||||
@@ -384,10 +383,10 @@
|
|||||||
console.log('Error parsing saved column visibility:', e);
|
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 = {};
|
var defaultVisibility = {};
|
||||||
columnNames.forEach(function(name, index) {
|
columnNames.forEach(function(name, index) {
|
||||||
defaultVisibility[index] = index !== 2 && index !== 3;
|
defaultVisibility[index] = index !== 5 && index !== 6;
|
||||||
});
|
});
|
||||||
return defaultVisibility;
|
return defaultVisibility;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -253,6 +253,13 @@
|
|||||||
endDate: moment(),
|
endDate: moment(),
|
||||||
locale: {
|
locale: {
|
||||||
format: 'YYYY-MM-DD HH:mm'
|
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