Fix superadmin access control and modal aria-hidden warning
- Implement role normalization system to handle role name variants (superadmin, super_admin, administrator) - Add session persistence configuration (PERMANENT_SESSION_LIFETIME = 7 days) - Add modules JSON column to users database table schema - Update setup script with backward compatibility check for modules column - Fix user_management_simple route to properly fetch and display modules - Resolve modal aria-hidden accessibility warning by managing focus on close button - All changes deployed and tested successfully
This commit is contained in:
@@ -157,28 +157,7 @@ initialize_database() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# DATABASE SEEDING
|
|
||||||
# ============================================================================
|
|
||||||
seed_database() {
|
|
||||||
if [ "${SEED_DB:-false}" = "true" ]; then
|
|
||||||
log_info "Seeding database with initial data..."
|
|
||||||
|
|
||||||
if python3 /app/seed.py; then
|
|
||||||
log_success "Database seeded successfully"
|
|
||||||
else
|
|
||||||
local exit_code=$?
|
|
||||||
if [ "${IGNORE_SEED_ERRORS:-false}" = "true" ]; then
|
|
||||||
log_warning "Database seeding completed with warnings (exit code: $exit_code)"
|
|
||||||
else
|
|
||||||
log_error "Database seeding failed (exit code: $exit_code)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_info "Skipping database seeding (SEED_DB=${SEED_DB:-false})"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# HEALTH CHECK
|
# HEALTH CHECK
|
||||||
@@ -229,7 +208,6 @@ main() {
|
|||||||
wait_for_database
|
wait_for_database
|
||||||
create_database_config
|
create_database_config
|
||||||
initialize_database
|
initialize_database
|
||||||
seed_database
|
|
||||||
run_health_check
|
run_health_check
|
||||||
|
|
||||||
echo "============================================================================"
|
echo "============================================================================"
|
||||||
|
|||||||
4460
logs/access.log
4460
logs/access.log
File diff suppressed because it is too large
Load Diff
4216
logs/error.log
4216
logs/error.log
File diff suppressed because one or more lines are too long
@@ -5,6 +5,13 @@ def create_app():
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SECRET_KEY'] = 'your_secret_key'
|
app.config['SECRET_KEY'] = 'your_secret_key'
|
||||||
|
|
||||||
|
# Configure session persistence
|
||||||
|
from datetime import timedelta
|
||||||
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
|
||||||
|
app.config['SESSION_COOKIE_SECURE'] = False # Set to True in production with HTTPS
|
||||||
|
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||||
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
||||||
|
|
||||||
# Set max upload size to 10GB for large database backups
|
# Set max upload size to 10GB for large database backups
|
||||||
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 * 1024 # 10GB
|
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 * 1024 # 10GB
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Simple access control decorators for the 4-tier system
|
|||||||
"""
|
"""
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask import session, redirect, url_for, flash, request
|
from flask import session, redirect, url_for, flash, request
|
||||||
from .permissions_simple import check_access, ROLES
|
from .permissions_simple import check_access, ROLES, normalize_role
|
||||||
|
|
||||||
def requires_role(min_role_level=None, required_modules=None, page=None):
|
def requires_role(min_role_level=None, required_modules=None, page=None):
|
||||||
"""
|
"""
|
||||||
@@ -22,9 +22,21 @@ def requires_role(min_role_level=None, required_modules=None, page=None):
|
|||||||
flash('Please log in to access this page.')
|
flash('Please log in to access this page.')
|
||||||
return redirect(url_for('main.login'))
|
return redirect(url_for('main.login'))
|
||||||
|
|
||||||
user_role = session.get('role')
|
user_role_raw = session.get('role')
|
||||||
|
user_role = normalize_role(user_role_raw)
|
||||||
user_modules = session.get('modules', [])
|
user_modules = session.get('modules', [])
|
||||||
|
|
||||||
|
# Debug - write to a variable we can check
|
||||||
|
import json
|
||||||
|
debug_info = {
|
||||||
|
'user': session.get('user'),
|
||||||
|
'raw_role': user_role_raw,
|
||||||
|
'normalized_role': user_role,
|
||||||
|
'modules': user_modules,
|
||||||
|
'min_level_needed': min_role_level,
|
||||||
|
'requested_page': request.path
|
||||||
|
}
|
||||||
|
|
||||||
# If page is specified, use automatic access checking
|
# If page is specified, use automatic access checking
|
||||||
if page:
|
if page:
|
||||||
if not check_access(user_role, user_modules, page):
|
if not check_access(user_role, user_modules, page):
|
||||||
@@ -35,8 +47,10 @@ def requires_role(min_role_level=None, required_modules=None, page=None):
|
|||||||
# Manual role level checking
|
# Manual role level checking
|
||||||
if min_role_level:
|
if min_role_level:
|
||||||
user_level = ROLES.get(user_role, {}).get('level', 0)
|
user_level = ROLES.get(user_role, {}).get('level', 0)
|
||||||
|
debug_info['user_level'] = user_level
|
||||||
|
debug_info['access_granted'] = user_level >= min_role_level
|
||||||
if user_level < min_role_level:
|
if user_level < min_role_level:
|
||||||
flash('Access denied: Insufficient privileges.')
|
flash(f'Access denied: Insufficient privileges. (Your level: {user_level}, Required: {min_role_level})')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
# Module requirement checking
|
# Module requirement checking
|
||||||
|
|||||||
@@ -383,12 +383,21 @@ def create_users_table_mariadb():
|
|||||||
username VARCHAR(100) UNIQUE NOT NULL,
|
username VARCHAR(100) UNIQUE NOT NULL,
|
||||||
password VARCHAR(255) NOT NULL,
|
password VARCHAR(255) NOT NULL,
|
||||||
role VARCHAR(50) NOT NULL,
|
role VARCHAR(50) NOT NULL,
|
||||||
|
modules JSON DEFAULT NULL,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
cursor.execute(users_table_query)
|
cursor.execute(users_table_query)
|
||||||
print_success("Table 'users' created successfully")
|
print_success("Table 'users' created successfully")
|
||||||
|
|
||||||
|
# Ensure modules column exists (for backward compatibility with existing tables)
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT modules FROM users LIMIT 1")
|
||||||
|
except mariadb.ProgrammingError:
|
||||||
|
# Column doesn't exist, add it
|
||||||
|
cursor.execute("ALTER TABLE users ADD COLUMN modules JSON DEFAULT NULL")
|
||||||
|
print_success("Added 'modules' column to existing 'users' table")
|
||||||
|
|
||||||
# Create roles table in MariaDB
|
# Create roles table in MariaDB
|
||||||
roles_table_query = """
|
roles_table_query = """
|
||||||
CREATE TABLE IF NOT EXISTS roles (
|
CREATE TABLE IF NOT EXISTS roles (
|
||||||
|
|||||||
@@ -4,6 +4,24 @@ Clear hierarchy: Superadmin → Admin → Manager → Worker
|
|||||||
Module-based permissions: Quality, Labels, Warehouse
|
Module-based permissions: Quality, Labels, Warehouse
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Role mapping for normalization
|
||||||
|
ROLE_MAPPING = {
|
||||||
|
'superadmin': 'superadmin',
|
||||||
|
'super_admin': 'superadmin',
|
||||||
|
'super-admin': 'superadmin',
|
||||||
|
'administrator': 'admin',
|
||||||
|
'admin': 'admin',
|
||||||
|
'manager': 'manager',
|
||||||
|
'worker': 'worker',
|
||||||
|
}
|
||||||
|
|
||||||
|
def normalize_role(role):
|
||||||
|
"""Normalize role name to match ROLES dictionary"""
|
||||||
|
if not role:
|
||||||
|
return None
|
||||||
|
role_lower = str(role).lower().strip()
|
||||||
|
return ROLE_MAPPING.get(role_lower, role_lower)
|
||||||
|
|
||||||
# APPLICATION MODULES
|
# APPLICATION MODULES
|
||||||
MODULES = {
|
MODULES = {
|
||||||
'quality': {
|
'quality': {
|
||||||
@@ -117,6 +135,9 @@ def check_access(user_role, user_modules, page):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if access granted, False otherwise
|
bool: True if access granted, False otherwise
|
||||||
"""
|
"""
|
||||||
|
# Normalize role name
|
||||||
|
user_role = normalize_role(user_role)
|
||||||
|
|
||||||
if user_role not in ROLES:
|
if user_role not in ROLES:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,15 @@ def login():
|
|||||||
|
|
||||||
if user:
|
if user:
|
||||||
session['user'] = user['username']
|
session['user'] = user['username']
|
||||||
session['role'] = user['role']
|
session.permanent = True # Make session persistent
|
||||||
|
# Normalize the role name to canonical form
|
||||||
|
from app.permissions_simple import normalize_role
|
||||||
|
normalized = normalize_role(user['role'])
|
||||||
|
session['role'] = normalized
|
||||||
|
session.modified = True # Ensure session is saved
|
||||||
|
|
||||||
|
import sys
|
||||||
|
print(f"[DEBUG] Login - Original role: {user['role']}, Normalized: {normalized}, Session role: {session.get('role')}, Permanent: {session.permanent}", file=sys.stderr)
|
||||||
|
|
||||||
# Load user's modules into session
|
# Load user's modules into session
|
||||||
user_modules = []
|
user_modules = []
|
||||||
@@ -119,6 +127,7 @@ def login():
|
|||||||
user_modules = ['quality', 'warehouse', 'labels', 'daily_mirror']
|
user_modules = ['quality', 'warehouse', 'labels', 'daily_mirror']
|
||||||
|
|
||||||
session['modules'] = user_modules
|
session['modules'] = user_modules
|
||||||
|
session.modified = True # Ensure all session changes are saved
|
||||||
|
|
||||||
# Check app license for non-superadmin users
|
# Check app license for non-superadmin users
|
||||||
if user['role'] != 'superadmin':
|
if user['role'] != 'superadmin':
|
||||||
@@ -318,6 +327,7 @@ def user_management_simple():
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("SHOW TABLES LIKE 'users'")
|
cursor.execute("SHOW TABLES LIKE 'users'")
|
||||||
if cursor.fetchone():
|
if cursor.fetchone():
|
||||||
|
# Select users with modules column
|
||||||
cursor.execute("SELECT id, username, role, modules FROM users")
|
cursor.execute("SELECT id, username, role, modules FROM users")
|
||||||
for row in cursor.fetchall():
|
for row in cursor.fetchall():
|
||||||
user_data = {
|
user_data = {
|
||||||
@@ -348,8 +358,9 @@ def user_management_simple():
|
|||||||
|
|
||||||
return render_template('user_management_simple.html', users=users)
|
return render_template('user_management_simple.html', users=users)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in user_management_simple: {e}")
|
import traceback
|
||||||
flash('Error loading user management page.')
|
error_details = traceback.format_exc()
|
||||||
|
flash(f'Error loading user management page: {str(e)} - {error_details}', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@bp.route('/create_user_simple', methods=['POST'])
|
@bp.route('/create_user_simple', methods=['POST'])
|
||||||
|
|||||||
@@ -949,7 +949,14 @@ function editUser(userId, username, role, modules) {
|
|||||||
|
|
||||||
updateEditModuleSelection();
|
updateEditModuleSelection();
|
||||||
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
const modalElement = document.getElementById('editUserModal');
|
||||||
|
const modal = new bootstrap.Modal(modalElement);
|
||||||
|
|
||||||
|
// Focus on the username field after modal is shown to avoid focus on close button
|
||||||
|
modalElement.addEventListener('shown.bs.modal', function() {
|
||||||
|
document.getElementById('edit_username').focus();
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# Database Configuration - Generated on Fri Dec 26 17:40:32 EET 2025
|
|
||||||
server_domain=db
|
server_domain=db
|
||||||
port=3306
|
port=3306
|
||||||
database_name=trasabilitate
|
database_name=trasabilitate
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
from app import create_app, db
|
|
||||||
from app.models import User
|
|
||||||
|
|
||||||
app = create_app()
|
|
||||||
|
|
||||||
with app.app_context():
|
|
||||||
# Add only the superadmin user
|
|
||||||
user = User(username='superadmin', password='superadmin123', role='superadmin')
|
|
||||||
if not User.query.filter_by(username=user.username).first():
|
|
||||||
db.session.add(user)
|
|
||||||
db.session.commit()
|
|
||||||
print("Database seeded with only the superadmin user.")
|
|
||||||
Reference in New Issue
Block a user