Files
igny8/docs/90-REFERENCE/AUTHENTICATION-SECURITY-FIXES.md
2026-01-13 04:42:58 +00:00

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

  1. Plugin could show "connected" without API key validation
  2. Backend /test-connection/ accepted unauthenticated requests
  3. Plugin /status endpoint was public
  4. No tracking of when API key was last validated

After Fixes

  1. Backend /test-connection/ requires JWT authentication
  2. Strong authentication checks with proper 401/403 responses
  3. Plugin /status endpoint requires valid API key
  4. Plugin tracks last successful API key validation
  5. Connection state only shows "connected" with recent validation
  6. 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 /status endpoint requires API key (401/403 without key)
  • Connection state returns to "configured" after 1 hour
  • All API communication properly authenticated

Deployment

  1. Backend changes applied: (container restarted)
  2. 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.py
  • plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.php
  • plugins/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