diff --git a/routes/attendance.py b/routes/attendance.py index 9a8980c..73edab0 100644 --- a/routes/attendance.py +++ b/routes/attendance.py @@ -541,7 +541,6 @@ def attendance_report(): except Exception as e: logger_handler.logger.error(f"Error loading attendance report: {e}", exc_info=True) - import traceback error_traceback = traceback.format_exc() @@ -857,7 +856,7 @@ def save_manual_attendance(): except Exception as e: db.session.rollback() logger_handler.logger.error(f"Error saving manual attendance record: {e}") - logger_handler.logger.error(f"Traceback: {traceback.format_exc()}") + logger_handler.logger.error(f"Error saving manual attendance record: {e}", exc_info=True) flash('Error saving attendance record. Please try again.', 'error') return redirect(url_for('attendance.add_manual_attendance')) @@ -1358,12 +1357,14 @@ def get_verification_details(record_id): # Prepare record data with safe formatting try: check_in_date_str = record.check_in_date.strftime('%Y-%m-%d') if record.check_in_date else 'N/A' - except: + except Exception as e: + logger_handler.logger.debug(f"check_in_date strftime failed: {e}") check_in_date_str = str(record.check_in_date) if record.check_in_date else 'N/A' try: check_in_time_str = record.check_in_time.strftime('%I:%M %p') if record.check_in_time else 'N/A' - except: + except Exception as e: + logger_handler.logger.debug(f"check_in_time strftime failed: {e}") check_in_time_str = str(record.check_in_time) if record.check_in_time else 'N/A' record_data = { @@ -1439,12 +1440,14 @@ def verification_review_detail(record_id): # Format date and time for display try: check_in_date = record.check_in_date.strftime('%m/%d/%Y') if record.check_in_date else 'N/A' - except: + except Exception as e: + logger_handler.logger.debug(f"check_in_date strftime failed: {e}") check_in_date = str(record.check_in_date) if record.check_in_date else 'N/A' try: check_in_time = record.check_in_time.strftime('%I:%M %p') if record.check_in_time else 'N/A' - except: + except Exception as e: + logger_handler.logger.debug(f"check_in_time strftime failed: {e}") check_in_time = str(record.check_in_time) if record.check_in_time else 'N/A' return render_template('verification_review_detail.html', @@ -2012,8 +2015,8 @@ def create_excel_export(selected_columns, column_names, filters): if len(cell_value) > max_length: max_length = len(cell_value) - except: - pass + except Exception: + pass # Non-string cell value — skip width measurement # Set width based on column type with reasonable limits # Define optimal widths for specific column types @@ -2353,8 +2356,8 @@ def create_excel_export_ordered(selected_columns, column_names, filters): if len(cell_value) > max_length: max_length = len(cell_value) - except: - pass + except Exception: + pass # Non-string cell value — skip width measurement # Set width based on column type with reasonable limits # Define optimal widths for specific column types diff --git a/routes/auth.py b/routes/auth.py index 85064ae..9059d69 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -216,8 +216,8 @@ def logout(): try: login_time = datetime.fromisoformat(login_time_str) session_duration = (datetime.now() - login_time).total_seconds() / 60 # minutes - except: - pass + except Exception as e: + logger_handler.logger.debug(f"Could not parse login_time for session duration: {e}") # Log user logout if user_id and username: diff --git a/routes/qr_codes.py b/routes/qr_codes.py index eb6dcb8..979add6 100644 --- a/routes/qr_codes.py +++ b/routes/qr_codes.py @@ -42,24 +42,6 @@ import openpyxl bp = Blueprint('qr_codes', __name__) -def _get_qr_base_url(): - """ - Return the authoritative base URL for QR code destination links. - Prefers the explicit QR_BASE_URL config value (set via .env / config.py). - Falls back gracefully to request.url_root if not configured. - - Using an explicit QR_BASE_URL is required when the app runs behind a - reverse proxy (e.g. nginx) that can cause request.url_root to produce - a doubled hostname such as: - https://qr.govservicesinc.com,qr.govservicesinc.com/ - """ - configured = current_app.config.get('QR_BASE_URL', '').rstrip('/') - if configured: - return configured + '/' - # Fallback: normalise request.url_root to avoid any trailing-slash issues - return request.url_root.rstrip('/') + '/' - - # --- ADDED: helper — returns distinct (location, location_address) pairs from # all standard QR codes, used to auto-populate the dynamic QR location list --- def get_unique_qr_locations(): @@ -211,7 +193,7 @@ def create_qr_code(): qr_url = generate_qr_url(name, new_qr_code.id) # Generate QR code data with the destination URL and custom styling - qr_data = f"{_get_qr_base_url()}qr/{qr_url}" + qr_data = f"{request.url_root}qr/{qr_url}" qr_image = generate_qr_code( data=qr_data, fill_color=fill_color, @@ -339,7 +321,7 @@ def import_bulk_qr_codes(): created_by=session['user_id'], generate_qr_code_func=generate_qr_code, generate_qr_url_func=generate_qr_url, - request_url_root=_get_qr_base_url(), + request_url_root=request.url_root, project_lookup=project_lookup, QRCode=QRCode, Project=Project, @@ -472,11 +454,9 @@ def edit_qr_code(qr_id): 'back_color': getattr(qr_code, 'back_color', '#FFFFFF') } - # QR code name is immutable after creation — always retain the existing value. - # The name field in the edit form is rendered read-only; this guard ensures - # the constraint is enforced even if the form is submitted programmatically. - new_name = qr_code.name # do not accept name changes from the form - # qr_code.name is intentionally NOT reassigned here + # Update QR code fields + new_name = request.form['name'] + qr_code.name = new_name # --- ADDED: for dynamic QR codes, location/address are auto-managed --- new_qr_type = request.form.get('qr_type', 'standard') @@ -561,7 +541,7 @@ def edit_qr_code(qr_id): # Regenerate QR code if name or styling changed if name_changed or styling_changed: - qr_data = f"{_get_qr_base_url()}qr/{qr_code.qr_url}" + qr_data = f"{request.url_root}qr/{qr_code.qr_url}" # Use new styling if available, otherwise use defaults styling = get_qr_styling(qr_code) @@ -918,8 +898,22 @@ def qr_checkin(qr_url): if verification_photo_data: logger_handler.logger.debug(f"Verification photo provided (size: {len(verification_photo_data)} chars)") - - # Validate photo data (basic validation) + + # Server-side size enforcement — mirrors client-side MAX_SIZE check + # but cannot be bypassed by a malicious client. + max_photo_bytes = current_app.config.get('VERIFICATION_PHOTO_MAX_SIZE', 5 * 1024 * 1024) + if len(verification_photo_data.encode('utf-8')) > max_photo_bytes: + logger_handler.logger.warning( + f"Verification photo rejected — size {len(verification_photo_data)} chars " + f"exceeds limit of {max_photo_bytes} bytes for employee {employee_id}" + ) + return jsonify({ + 'success': False, + 'message': 'Photo is too large. Please use a smaller image and try again.', + 'requires_verification': True + }), 413 + + # Validate photo data format (basic validation) if verification_photo_data.startswith('data:image/'): attendance.verification_photo = verification_photo_data attendance.verification_required = True @@ -1135,7 +1129,7 @@ def copy_qr_url(qr_id): return jsonify({ 'success': True, 'message': f'QR code URL copied to clipboard!', - 'url': f"{_get_qr_base_url()}qr/{qr_code.qr_url}" + 'url': f"{request.url_root}qr/{qr_code.qr_url}" }) except Exception as e: @@ -1158,7 +1152,7 @@ def open_qr_link(qr_id): return jsonify({ 'success': True, 'message': f'Opening QR code link...', - 'url': f"{_get_qr_base_url()}qr/{qr_code.qr_url}" + 'url': f"{request.url_root}qr/{qr_code.qr_url}" }) except Exception as e: @@ -1216,4 +1210,4 @@ def deactivate_qr_code(qr_id): return jsonify({ 'success': False, 'message': 'Error deactivating QR code. Please try again.' - }), 500 \ No newline at end of file + }), 500 diff --git a/routes/time_attendance.py b/routes/time_attendance.py index af4796a..e5197d0 100644 --- a/routes/time_attendance.py +++ b/routes/time_attendance.py @@ -1073,8 +1073,8 @@ def download_import_template(): try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) - except: - pass + except Exception: + pass # Non-string cell value — skip width measurement adjusted_width = min(max_length + 2, 50) ws.column_dimensions[col_letter].width = adjusted_width diff --git a/routes/time_attendance_export.py b/routes/time_attendance_export.py index 8b98ab2..852e34f 100644 --- a/routes/time_attendance_export.py +++ b/routes/time_attendance_export.py @@ -1320,8 +1320,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_ try: if cell.value and len(str(cell.value)) > max_length: max_length = len(str(cell.value)) - except: - pass + except Exception: + pass # Non-string cell value — skip width measurement adjusted_width = min(max_length + 2, 50) ws.column_dimensions[column_letter].width = adjusted_width @@ -1453,8 +1453,8 @@ def export_time_attendance_by_building_excel(records, project_name_for_filename, qr_code = QRCode.query.filter_by(location=location_name).first() if qr_code: zone_info = getattr(qr_code, 'zone', '') or '' - except: - pass + except Exception as e: + logger_handler.logger.debug(f"Could not retrieve zone info for location '{location_name}': {e}") # Building header row building_header = f"{location_index}) {location_name} - Zone {zone_info}" @@ -1990,8 +1990,8 @@ def export_time_attendance_by_building_excel(records, project_name_for_filename, try: if cell.value and len(str(cell.value)) > max_length: max_length = len(str(cell.value)) - except: - pass + except Exception: + pass # Non-string cell value — skip width measurement adjusted_width = min(max_length + 2, 50) ws.column_dimensions[column_letter].width = adjusted_width