From c613a564ced723d30c57456176fe52a1152f6903 Mon Sep 17 00:00:00 2001 From: ghostersk Date: Tue, 27 May 2025 13:27:36 +0100 Subject: [PATCH] Add Password change to profile, change tables layout --- auth/forms.py | 3 +- auth/routes.py | 53 ++++++++++++++++++++++- templates/auth/company_api_keys.html | 4 +- templates/auth/download_agent.html | 5 +-- templates/auth/profile.html | 20 +++++++++ templates/frontend/dashboard.html | 45 ++++++++++--------- templates/frontend/time_spent_report.html | 7 +++ 7 files changed, 107 insertions(+), 30 deletions(-) diff --git a/auth/forms.py b/auth/forms.py index d87276d..6d97745 100644 --- a/auth/forms.py +++ b/auth/forms.py @@ -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 diff --git a/auth/routes.py b/auth/routes.py index 5127b49..1a13325 100644 --- a/auth/routes.py +++ b/auth/routes.py @@ -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 - }) \ No newline at end of file + }) + +@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')) \ No newline at end of file diff --git a/templates/auth/company_api_keys.html b/templates/auth/company_api_keys.html index 286da65..e2f9a4b 100644 --- a/templates/auth/company_api_keys.html +++ b/templates/auth/company_api_keys.html @@ -21,7 +21,7 @@
- +
@@ -41,7 +41,7 @@ - + diff --git a/templates/auth/download_agent.html b/templates/auth/download_agent.html index dff81d3..5b8c270 100644 --- a/templates/auth/download_agent.html +++ b/templates/auth/download_agent.html @@ -28,9 +28,8 @@ diff --git a/templates/auth/profile.html b/templates/auth/profile.html index e94ca7f..6802b64 100644 --- a/templates/auth/profile.html +++ b/templates/auth/profile.html @@ -15,6 +15,26 @@
+
Change Password
+ + {{ change_password_form.hidden_tag() }} +
+ {{ change_password_form.current_password.label(class="form-label") }} + {{ change_password_form.current_password(class="form-control") }} +
+
+ {{ change_password_form.new_password.label(class="form-label") }} + {{ change_password_form.new_password(class="form-control") }} +
+
+ {{ change_password_form.confirm_password.label(class="form-label") }} + {{ change_password_form.confirm_password(class="form-control") }} +
+ {{ change_password_form.submit(class="btn btn-primary mb-3") }} + + +
+
Two-Factor Authentication

Status: diff --git a/templates/frontend/dashboard.html b/templates/frontend/dashboard.html index 2dbcc72..1899a83 100644 --- a/templates/frontend/dashboard.html +++ b/templates/frontend/dashboard.html @@ -208,31 +208,31 @@

DescriptionSite Name Key Created Last Used
+ + {% if current_user.is_global_admin() and not selected_company_id %} + + {% endif %} + - - - {% if current_user.is_global_admin() and not selected_company_id %} - - {% endif %} {% for log in logs %} - - - - - + + {% if current_user.is_global_admin() and not selected_company_id %} - + {% endif %} + + + {% endfor %} @@ -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; } diff --git a/templates/frontend/time_spent_report.html b/templates/frontend/time_spent_report.html index 89b8aa8..26f1d0d 100644 --- a/templates/frontend/time_spent_report.html +++ b/templates/frontend/time_spent_report.html @@ -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')] } });
Timestamp Event Type User NameCompanySite Computer Name IP AddressSiteTimestampCompany
{{ log.event_type }}{{ log.user_name }}{{ log.computer_name }}{{ log.ip_address }}{{ log.api_key.description if log.api_key else 'N/A' }} {{ log.timestamp|format_datetime }} {{ log.event_type }}{{ log.user_name }}{{ log.company.name if log.company else 'N/A' }}{{ log.company.name if log.company else 'N/A' }}{{ log.api_key.description if log.api_key else 'N/A' }}{{ log.computer_name }}{{ log.ip_address }}