# User Management System ## Overview A secure user management front-end that allows you to create and manage users. Each user record contains: - **Username**: Unique identifier - **Password**: Securely hashed using PBKDF2-SHA256 (industry-standard) - **Default Location**: Primary location (required) - one of: Lindsborg, Iola, KC, or BMD - **Active Status**: Whether the user account is active or inactive (toggleable in user list) - **Location Settings**: Per-location configuration with: - **Accessible**: Whether the user can access this location ## User Interface Features ### Table-Based Location Configuration The form uses an intuitive table layout with: - **Default Location Column**: Radio buttons to select ONE primary location (required) - **Accessible Column**: Checkboxes to mark which locations the user can access ### User List with Active Toggle Each user in the list shows: - **Username** and **Location Information** - **Active/Inactive Toggle**: Click to enable or disable the user account - Active users have full border color - Inactive users are dimmed with reduced opacity - **Delete Button**: Remove the user permanently ### Smart Header Checkboxes The accessible column has a header checkbox that: - Shows three states: checked ✓, unchecked ☐, or indeterminate ⊟ (mixed) - **Clicking cycles**: If off or mixed → all on, if on → all off - **Auto-updates**: When you check/uncheck individual rows, the header shows: - ✓ if all are checked - ☐ if none are checked - ⊟ if some are checked (mixed state) ### Extensible Design The table structure is designed to easily add more columns in the future: - Permission columns (Permission 1, Permission 2, etc.) - Custom attributes - Feature flags - Any other per-location settings Each new column can have the same header checkbox behavior. ## Security Features - **Password Hashing**: Passwords are hashed using `pbkdf2:sha256` algorithm - **Not Plain Text**: Passwords are never stored in plain text - **Cryptographically Secure**: Uses Werkzeug's secure password hashing - **Cannot Be Decoded**: Hashed passwords cannot be reversed back to plain text ## How to Use ### 1. Access the User Manager Navigate to: `http://localhost:8080/users` ### 2. Add New Users - Fill in the form with username, password, and location - Click "Add User" button - User will be added with a securely hashed password ### 3. View Users - All users are displayed in a list showing username and location - Password hashes are NOT displayed for security ### 4. Download Users JSON - Click "📥 Download Users JSON" button - Downloads a `users.json` file containing all users - Password field contains the secure hash (not plain text) ### 5. Delete Users - Click "Delete" next to any user to remove them - Click "🗑️ Clear All Users" to remove all users at once ## API Endpoints ### GET /users Displays the user management interface. ### GET /api/users Returns all users in JSON format. **Response:** ```json { "status": "success", "users": [ { "username": "john_doe", "password": "pbkdf2:sha256:600000$...", "defaultLocation": "LINDS", "active": true, "locationSettings": { "LINDS": { "accessible": true }, "IOLA": { "accessible": true }, "KC": { "accessible": false }, "BMD": { "accessible": false } } } ] } ``` ### POST /api/users Adds a new user. **Request Body:** ```json { "username": "jane_smith", "password": "mySecurePassword123", "defaultLocation": "KC", "active": true, "locationSettings": { "LINDS": { "accessible": true }, "IOLA": { "accessible": true }, "KC": { "accessible": true }, "BMD": { "accessible": true } } } ``` **Response:** ```json { "status": "success", "message": "User added successfully", "users": [...] } ``` ### DELETE /api/users/{index} Deletes a user by their index position. ### PATCH /api/users/{index}/active Toggle user active status. **Request Body:** ```json { "active": true } ``` ### DELETE /api/users/clear Clears all users from the system. ### GET /api/users/download Downloads all users as a JSON file. ## Password Security ### How Passwords Are Stored Passwords are hashed using PBKDF2-SHA256 with the following properties: - **Algorithm**: PBKDF2 (Password-Based Key Derivation Function 2) - **Hash Function**: SHA-256 - **Iterations**: 600,000+ (computationally expensive for attackers) - **Salt**: Automatically generated unique salt per password ### Example Hash Format ``` pbkdf2:sha256:600000$AbCdEfGh$1234567890abcdef... ``` Components: - `pbkdf2:sha256` - Algorithm identifier - `600000` - Number of iterations - `$AbCdEfGh` - Random salt - `$1234567890abcdef...` - Actual hash ### Password Verification To verify a password, use Werkzeug's `check_password_hash()`: ```python from werkzeug.security import check_password_hash # user['password'] contains the hash if check_password_hash(user['password'], provided_password): print("Password is correct!") ``` ## Data Storage Users are stored in: `app/data/users.json` **Example users.json:** ```json [ { "username": "admin", "password": "pbkdf2:sha256:600000$r7K8L9M0$a1b2c3d4e5f6...", "defaultLocation": "LINDS", "active": true, "locationSettings": { "LINDS": { "accessible": true }, "IOLA": { "accessible": true }, "KC": { "accessible": false }, "BMD": { "accessible": false } } }, { "username": "user1", "password": "pbkdf2:sha256:600000$n5O6P7Q8$x9y8z7w6v5u4...", "defaultLocation": "KC", "active": false, "locationSettings": { "LINDS": { "accessible": true }, "IOLA": { "accessible": true }, "KC": { "accessible": true }, "BMD": { "accessible": true } } } ] ``` ### Location Codes - **LINDS** = Lindsborg - **IOLA** = Iola - **KC** = KC - **BMD** = BMD ## Integration Example ### Authenticate and Get User Info ```python from werkzeug.security import check_password_hash import json def authenticate_user(username, password): """Authenticate a user by username and password""" with open('app/data/users.json', 'r') as f: users = json.load(f) # Find user user = next((u for u in users if u['username'] == username), None) if user and check_password_hash(user['password'], password): return True, user return False, None # Usage success, user_data = authenticate_user('john_doe', 'password123') if success: print(f"Welcome {user_data['username']}!") print(f"Default location: {user_data['defaultLocation']}") print(f"Account active: {user_data.get('active', True)}") # Check if user can access a location if user_data['locationSettings']['KC']['accessible']: print("User can access KC") ``` ### Check User Permissions for a Location ```python def can_access_location(user_data, location_code): """Check if user can access a specific location""" return user_data.get('locationSettings', {}).get(location_code, {}).get('accessible', False) def is_user_active(user_data): """Check if user account is active""" return user_data.get('active', True) # Usage if not is_user_active(user_data): print("User account is inactive") return if can_access_location(user_data, 'LINDS'): print("User can access Lindsborg") ``` ### Get All Accessible Locations for a User ```python def get_accessible_locations(user_data): """Get all locations where user has access""" accessible_locations = [] for location_code, settings in user_data.get('locationSettings', {}).items(): if settings.get('accessible', False): accessible_locations.append(location_code) return accessible_locations # Usage accessible = get_accessible_locations(user_data) print(f"User can access: {', '.join(accessible)}") ``` ## Notes - Username must be unique - Minimum password length: 6 characters - Default location is required (one of: Lindsborg, Iola, KC, BMD) - **Default location is automatically marked as accessible** when creating a user - Accessible checkboxes are optional for other locations - New users are **active by default** - Toggle active status in the user list section - Users are stored locally in JSON format - This is a separate endpoint from the main Product Finder app ## Adding New Permissions/Columns The system is designed to be easily extensible. To add new permission columns: ### 1. Update the HTML table Add a new column header and cells in `user_manager.html`: ```html