8.4 KiB
Authentication Security Fixes - January 13, 2026
Critical Security Issues Fixed
Overview
Fixed multiple critical security vulnerabilities where API connections could appear successful without proper authentication, and status endpoints were publicly accessible without API key validation.
Issues Fixed
1. Backend /test-connection/ Endpoint Security
Issue: Backend endpoint allowed unauthenticated requests via AllowAny permission class
Location: /data/app/igny8/backend/igny8_core/modules/integration/views.py
Before:
@action(detail=False, methods=['post'], url_path='test-connection',
permission_classes=[AllowAny], throttle_classes=[NoThrottle])
def test_connection_collection(self, request):
After:
@action(detail=False, methods=['post'], url_path='test-connection')
def test_connection_collection(self, request):
Impact: Now requires proper Django REST Framework authentication (JWT token)
2. Backend Authentication Check Logic
Issue: Weak authentication validation logic that could be bypassed
Location: /data/app/igny8/backend/igny8_core/modules/integration/views.py
Before:
# Weak check with fallbacks
if not hasattr(request, 'user') or not getattr(request.user, 'is_authenticated', False):
return error_response('Authentication required', None, status.HTTP_403_FORBIDDEN, request)
try:
if site.account != request.user.account:
return error_response('Site does not belong to your account', None, status.HTTP_403_FORBIDDEN, request)
except Exception:
return error_response('Authentication failed', None, status.HTTP_403_FORBIDDEN, request)
After:
# Strong authentication check
if not request.user or not request.user.is_authenticated:
return error_response('Authentication required', None, status.HTTP_401_UNAUTHORIZED, request)
if not hasattr(request.user, 'account') or not request.user.account:
return error_response('User account not found', None, status.HTTP_403_FORBIDDEN, request)
if site.account_id != request.user.account_id:
return error_response('Site does not belong to your account', None, status.HTTP_403_FORBIDDEN, request)
Impact: Proper 401 for missing auth, clear account validation, uses account_id directly
3. WordPress Plugin /status Endpoint Security
Issue: Status endpoint was public, returning connection info without authentication
Location: /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.php
Before:
register_rest_route('igny8/v1', '/status', array(
'methods' => 'GET',
'callback' => array($this, 'get_status'),
'permission_callback' => '__return_true', // Public endpoint
));
After:
register_rest_route('igny8/v1', '/status', array(
'methods' => 'GET',
'callback' => array($this, 'get_status'),
'permission_callback' => array($this, 'check_permission'), // Requires API key
));
Impact: Status endpoint now requires valid API key in header
4. WordPress Plugin Status Response Enhancement
Issue: Status response didn't track API key validation timestamp
Location: /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.php
Before:
public function get_status($request) {
// No validation tracking
$data = array(
'connected' => !empty($api_key) && $api->is_authenticated(),
'has_api_key' => !empty($api_key),
'last_health_check' => get_option('igny8_last_api_health_check', 0),
// ...
);
}
After:
public function get_status($request) {
// Update validation timestamp when status is checked with valid API key
update_option('igny8_last_api_health_check', time());
$data = array(
'connected' => !empty($api_key) && $api->is_authenticated(),
'has_api_key' => !empty($api_key),
'api_key_validated' => true, // Since check_permission passed
'last_health_check' => time(),
// ...
);
}
Impact: Tracks when API key was last successfully validated
5. WordPress Plugin Connection State Logic
Issue: Plugin showed "connected" status without validating API key with backend
Location: /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/includes/functions.php
Before:
function igny8_get_connection_state() {
$api_key = igny8_get_secure_option('igny8_api_key');
$integration_id = get_option('igny8_integration_id');
$last_structure_sync = get_option('igny8_last_structure_sync');
if (empty($api_key)) {
return 'not_connected';
}
// Shows "connected" if API key exists, even without validation!
if (!empty($api_key) && !empty($integration_id) && !empty($last_structure_sync)) {
return 'connected';
}
return 'configured';
}
After:
function igny8_get_connection_state() {
$api_key = igny8_get_secure_option('igny8_api_key');
$integration_id = get_option('igny8_integration_id');
$last_structure_sync = get_option('igny8_last_structure_sync');
// SECURITY: Must have API key to show ANY connection state
if (empty($api_key)) {
return 'not_connected';
}
// SECURITY: Only show 'connected' if API key was recently validated
// Check if we have a recent successful API health check (within last hour)
$last_health_check = get_option('igny8_last_api_health_check', 0);
$one_hour_ago = time() - 3600;
if (!empty($api_key) && !empty($integration_id) && !empty($last_structure_sync) && $last_health_check > $one_hour_ago) {
return 'connected';
}
// If we have API key but no recent validation, show as 'configured' not 'connected'
return 'configured';
}
Impact:
- Only shows "connected" if API key was validated within last hour
- Shows "configured" if key exists but hasn't been validated recently
- Prevents false "connected" status on fresh installations
Security Flow Summary
Before Fixes
- ❌ Plugin could show "connected" without API key validation
- ❌ Backend
/test-connection/accepted unauthenticated requests - ❌ Plugin
/statusendpoint was public - ❌ No tracking of when API key was last validated
After Fixes
- ✅ Backend
/test-connection/requires JWT authentication - ✅ Strong authentication checks with proper 401/403 responses
- ✅ Plugin
/statusendpoint requires valid API key - ✅ Plugin tracks last successful API key validation
- ✅ Connection state only shows "connected" with recent validation
- ✅ Clear distinction between "configured" (key exists) and "connected" (key validated)
Authentication Flow
Correct Flow (After Fixes)
1. User adds API key in WordPress plugin settings
→ Status: "configured"
2. IGNY8 backend calls /wp-json/igny8/v1/verify-key
→ Plugin validates API key from X-IGNY8-API-KEY header
→ Updates igny8_last_api_health_check timestamp
→ Returns 200 OK
3. Plugin connection state check
→ Sees igny8_last_api_health_check < 1 hour old
→ Status: "connected"
4. After 1 hour without health check
→ Status: returns to "configured"
→ Next test connection will re-validate and update timestamp
Testing Checklist
- Fresh plugin installation shows "not_connected"
- Adding API key shows "configured", NOT "connected"
- Test connection with valid API key shows "connected"
- Test connection with invalid API key shows error
- Backend
/test-connection/requires authentication (401 without token) - Plugin
/statusendpoint requires API key (401/403 without key) - Connection state returns to "configured" after 1 hour
- All API communication properly authenticated
Deployment
- Backend changes applied: ✅ (container restarted)
- Plugin changes ready: ✅ (needs deployment to WordPress site)
Next Steps:
- Deploy updated plugin to WordPress site (massagersmart.com)
- Test complete authentication flow
- Verify no false "connected" states
- Verify proper error messages for missing/invalid API keys
Files Modified
backend/igny8_core/modules/integration/views.pyplugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.phpplugins/wordpress/source/igny8-wp-bridge/includes/functions.php
Date: January 13, 2026
Status: Implemented, Backend Deployed, Plugin Ready for Deployment
Priority: CRITICAL - Security Vulnerability Fixes