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:
@@ -5,6 +5,13 @@ def create_app():
|
||||
app = Flask(__name__)
|
||||
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
|
||||
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 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):
|
||||
"""
|
||||
@@ -22,9 +22,21 @@ def requires_role(min_role_level=None, required_modules=None, page=None):
|
||||
flash('Please log in to access this page.')
|
||||
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', [])
|
||||
|
||||
# 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:
|
||||
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
|
||||
if min_role_level:
|
||||
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:
|
||||
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'))
|
||||
|
||||
# Module requirement checking
|
||||
|
||||
@@ -383,12 +383,21 @@ def create_users_table_mariadb():
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) NOT NULL,
|
||||
modules JSON DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
"""
|
||||
cursor.execute(users_table_query)
|
||||
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
|
||||
roles_table_query = """
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
|
||||
@@ -4,6 +4,24 @@ Clear hierarchy: Superadmin → Admin → Manager → Worker
|
||||
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
|
||||
MODULES = {
|
||||
'quality': {
|
||||
@@ -117,6 +135,9 @@ def check_access(user_role, user_modules, page):
|
||||
Returns:
|
||||
bool: True if access granted, False otherwise
|
||||
"""
|
||||
# Normalize role name
|
||||
user_role = normalize_role(user_role)
|
||||
|
||||
if user_role not in ROLES:
|
||||
return False
|
||||
|
||||
|
||||
@@ -103,7 +103,15 @@ def login():
|
||||
|
||||
if user:
|
||||
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
|
||||
user_modules = []
|
||||
@@ -119,6 +127,7 @@ def login():
|
||||
user_modules = ['quality', 'warehouse', 'labels', 'daily_mirror']
|
||||
|
||||
session['modules'] = user_modules
|
||||
session.modified = True # Ensure all session changes are saved
|
||||
|
||||
# Check app license for non-superadmin users
|
||||
if user['role'] != 'superadmin':
|
||||
@@ -318,6 +327,7 @@ def user_management_simple():
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SHOW TABLES LIKE 'users'")
|
||||
if cursor.fetchone():
|
||||
# Select users with modules column
|
||||
cursor.execute("SELECT id, username, role, modules FROM users")
|
||||
for row in cursor.fetchall():
|
||||
user_data = {
|
||||
@@ -348,8 +358,9 @@ def user_management_simple():
|
||||
|
||||
return render_template('user_management_simple.html', users=users)
|
||||
except Exception as e:
|
||||
print(f"Error in user_management_simple: {e}")
|
||||
flash('Error loading user management page.')
|
||||
import traceback
|
||||
error_details = traceback.format_exc()
|
||||
flash(f'Error loading user management page: {str(e)} - {error_details}', 'danger')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
@bp.route('/create_user_simple', methods=['POST'])
|
||||
|
||||
@@ -949,7 +949,14 @@ function editUser(userId, username, role, modules) {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user