""" User Management Blueprint Handles user CRUD operations """ from flask import Blueprint, render_template, request, jsonify, send_file, session from werkzeug.security import generate_password_hash, check_password_hash from blueprints.auth import login_required, can_user, get_current_user from io import BytesIO import json import os users_bp = Blueprint('users', __name__, url_prefix='/users') # Path to users file USERS_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', 'users.json') def load_users(): """Load users from JSON file""" if os.path.exists(USERS_FILE): try: with open(USERS_FILE, 'r') as f: return json.load(f) except: return [] return [] def save_users(users): """Save users to JSON file""" os.makedirs(os.path.dirname(USERS_FILE), exist_ok=True) with open(USERS_FILE, 'w') as f: json.dump(users, f, indent=2) def permission_required(permission): """Decorator to require specific permission""" from functools import wraps def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if not can_user(permission): return jsonify({ 'status': 'error', 'message': 'Permission denied' }), 403 return f(*args, **kwargs) return decorated_function return decorator # Routes @users_bp.route('/') @login_required def user_manager(): """Serve the user management page""" if not can_user('manage_users'): return "

403 - Permission Denied

You don't have permission to manage users.

", 403 current_user_id = session.get('user_id', -1) return render_template('user_manager.html', current_user_id=current_user_id) @users_bp.route('/api', methods=['GET']) @login_required @permission_required('manage_users') def get_users(): """Get all users""" users = load_users() return jsonify({ 'status': 'success', 'users': users }) @users_bp.route('/api', methods=['POST']) @login_required @permission_required('manage_users') def add_user(): """Add a new user with hashed password""" try: data = request.json # Validate required fields if not data.get('username') or not data.get('password') or not data.get('defaultLocation'): return jsonify({ 'status': 'error', 'message': 'Username, password, and default location are required' }), 400 users = load_users() # Check if username already exists if any(user['username'] == data['username'] for user in users): return jsonify({ 'status': 'error', 'message': 'Username already exists' }), 400 # Hash the password hashed_password = generate_password_hash(data['password'], method='pbkdf2:sha256') # Get location settings location_settings = data.get('locationSettings', {}) default_location = data['defaultLocation'] # Ensure default location is accessible if default_location not in location_settings: location_settings[default_location] = {} location_settings[default_location]['accessible'] = True # Create new user new_user = { 'username': data['username'], 'password': hashed_password, 'defaultLocation': default_location, 'locationSettings': location_settings, 'permissions': data.get('permissions', {}), 'active': data.get('active', True) } users.append(new_user) save_users(users) return jsonify({ 'status': 'success', 'message': 'User added successfully', 'users': users }) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e) }), 500 @users_bp.route('/api/', methods=['DELETE']) @login_required @permission_required('manage_users') def delete_user(user_index): """Delete a specific user by index""" try: users = load_users() if user_index < 0 or user_index >= len(users): return jsonify({ 'status': 'error', 'message': 'Invalid user index' }), 400 users.pop(user_index) save_users(users) return jsonify({ 'status': 'success', 'message': 'User deleted successfully', 'users': users }) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e) }), 500 @users_bp.route('/api//active', methods=['PATCH']) @login_required @permission_required('manage_users') def toggle_user_active(user_index): """Toggle user active status""" try: data = request.json users = load_users() if user_index < 0 or user_index >= len(users): return jsonify({ 'status': 'error', 'message': 'Invalid user index' }), 400 users[user_index]['active'] = data.get('active', True) save_users(users) return jsonify({ 'status': 'success', 'message': 'User status updated', 'users': users }) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e) }), 500 @users_bp.route('/api//change-password', methods=['POST']) @login_required @permission_required('manage_users') def change_user_password(user_index): """Change a user's password with current user verification""" try: data = request.json new_password = data.get('newPassword') current_user_password = data.get('currentUserPassword') if not new_password or not current_user_password: return jsonify({ 'status': 'error', 'message': 'New password and current user password are required' }), 400 if len(new_password) < 6: return jsonify({ 'status': 'error', 'message': 'Password must be at least 6 characters long' }), 400 # Verify current user's password current_user = get_current_user() if not current_user: return jsonify({ 'status': 'error', 'message': 'Not authenticated' }), 401 if not check_password_hash(current_user['password'], current_user_password): return jsonify({ 'status': 'error', 'message': 'Your password is incorrect. Password change denied for security reasons.' }), 403 # Load users and validate index users = load_users() if user_index < 0 or user_index >= len(users): return jsonify({ 'status': 'error', 'message': 'Invalid user index' }), 400 # Update password hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256') target_username = users[user_index]['username'] users[user_index]['password'] = hashed_password save_users(users) return jsonify({ 'status': 'success', 'message': f'Password changed successfully for user "{target_username}"' }) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e) }), 500 @users_bp.route('/api/download') @login_required @permission_required('manage_users') def download_users(): """Download users as JSON file""" try: users = load_users() if not users: return jsonify({ 'status': 'error', 'message': 'No users to download' }), 400 json_data = json.dumps(users, indent=2) return send_file( BytesIO(json_data.encode()), mimetype='application/json', as_attachment=True, download_name='users.json' ) except Exception as e: return jsonify({ 'status': 'error', 'message': str(e) }), 500