Files
2026-04-11 00:04:09 -05:00

10 KiB

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:

{
  "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:

{
  "username": "jane_smith",
  "password": "mySecurePassword123",
  "defaultLocation": "KC",
  "active": true,
  "locationSettings": {
    "LINDS": { "accessible": true },
    "IOLA": { "accessible": true },
    "KC": { "accessible": true },
    "BMD": { "accessible": true }
  }
}

Response:

{
  "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:

{
  "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():

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:

[
  {
    "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

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

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

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:

<!-- In the table header -->
<th class="checkbox-header" onclick="toggleHeaderCheckbox('newPermission')">
    <input type="checkbox" id="headerNewPermission" 
           onclick="event.stopPropagation(); toggleAllCheckboxes('newPermission')">
    Permission Name
</th>

<!-- In each table row -->
<td class="checkbox-cell">
    <input type="checkbox" class="newPermission-checkbox" 
           data-location="LINDS" onchange="updateHeaderCheckbox('newPermission')">
</td>

2. Update the JavaScript form submission

Modify the form submission handler to collect the new permission:

locationSettings[loc.code] = {
    active: activeCheckbox ? activeCheckbox.checked : false,
    accessible: accessibleCheckbox ? accessibleCheckbox.checked : false,
    newPermission: newPermCheckbox ? newPermCheckbox.checked : false  // Add this
};

3. Update the backend (optional)

The backend already handles any properties in locationSettings, so no changes are required unless you want validation.

4. Reset header checkbox on form submit

Add to the form reset section:

document.getElementById('headerNewPermission').checked = false;
document.getElementById('headerNewPermission').indeterminate = false;

That's it! The system will automatically save and load the new permission data.