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:sha256algorithm - 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.jsonfile 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 identifier600000- 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.