- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
247 lines
7.3 KiB
PHP
247 lines
7.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\Http\Session\Session;
|
|
use App\Framework\Http\Session\SessionId;
|
|
use App\Framework\LiveComponents\ComponentEventDispatcher;
|
|
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
|
|
use App\Framework\LiveComponents\LiveComponentHandler;
|
|
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentData;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
use App\Framework\Security\CsrfToken;
|
|
use App\Framework\Security\CsrfTokenGenerator;
|
|
use App\Framework\View\LiveComponentRenderer;
|
|
use App\Framework\View\TemplateRenderer;
|
|
|
|
echo "=== CSRF Integration Test ===\n\n";
|
|
|
|
// Create real Session instance for testing
|
|
$sessionId = SessionId::fromString(bin2hex(random_bytes(16)));
|
|
$clock = new SystemClock();
|
|
$randomGenerator = new SecureRandomGenerator();
|
|
$csrfGenerator = new CsrfTokenGenerator($randomGenerator);
|
|
|
|
$session = Session::fromArray($sessionId, $clock, $csrfGenerator, []);
|
|
|
|
// Test 1: Token Generation in Renderer
|
|
echo "Test 1: Token Generation in LiveComponentRenderer\n";
|
|
echo "------------------------------------------------\n";
|
|
|
|
$mockTemplateRenderer = new class () implements TemplateRenderer {
|
|
public function render(\App\Framework\View\RenderContext $context): string
|
|
{
|
|
return '';
|
|
}
|
|
|
|
public function renderPartial(\App\Framework\View\RenderContext $context): string
|
|
{
|
|
return '';
|
|
}
|
|
};
|
|
|
|
$renderer = new LiveComponentRenderer($mockTemplateRenderer, $session);
|
|
|
|
$componentId = 'counter:test123';
|
|
$html = $renderer->renderWithWrapper(
|
|
$componentId,
|
|
'<div>Counter: 0</div>',
|
|
['count' => 0]
|
|
);
|
|
|
|
echo "\nGenerated HTML:\n";
|
|
echo substr($html, 0, 200) . "...\n";
|
|
|
|
// Extract token from HTML
|
|
if (preg_match('/data-csrf-token="([^"]+)"/', $html, $matches)) {
|
|
$extractedToken = $matches[1];
|
|
echo "\n✓ CSRF token found in HTML: " . substr($extractedToken, 0, 16) . "...\n";
|
|
} else {
|
|
echo "\n✗ CSRF token NOT found in HTML!\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Test 2: Token Validation in Handler
|
|
echo "\n\nTest 2: Token Validation in LiveComponentHandler\n";
|
|
echo "------------------------------------------------\n";
|
|
|
|
$eventDispatcher = new ComponentEventDispatcher();
|
|
$handler = new LiveComponentHandler($eventDispatcher, $session);
|
|
|
|
// Create test component
|
|
$testComponent = new class (ComponentId::fromString($componentId)) implements LiveComponentContract {
|
|
public function __construct(private ComponentId $id, private int $count = 0)
|
|
{
|
|
}
|
|
|
|
public function getId(): ComponentId
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getData(): ComponentData
|
|
{
|
|
return ComponentData::fromArray(['count' => $this->count]);
|
|
}
|
|
|
|
public function increment(): ComponentData
|
|
{
|
|
echo " [Component] Executing increment action\n";
|
|
$this->count++;
|
|
|
|
return $this->getData();
|
|
}
|
|
};
|
|
|
|
// Test 2a: Valid Token
|
|
echo "\nTest 2a: Valid CSRF Token\n";
|
|
|
|
try {
|
|
$csrfToken = CsrfToken::fromString($extractedToken);
|
|
$params = ActionParameters::fromArray([], $csrfToken);
|
|
|
|
$result = $handler->handle($testComponent, 'increment', $params);
|
|
|
|
echo "✓ Action executed successfully!\n";
|
|
echo " New state: count = " . $result->state->data['count'] . "\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ Failed: " . $e->getMessage() . "\n";
|
|
exit(1);
|
|
}
|
|
|
|
// Test 2b: Missing Token
|
|
echo "\nTest 2b: Missing CSRF Token\n";
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray([]);
|
|
$handler->handle($testComponent, 'increment', $params);
|
|
|
|
echo "✗ Should have thrown exception for missing token!\n";
|
|
exit(1);
|
|
} catch (\InvalidArgumentException $e) {
|
|
echo "✓ Correctly rejected: " . $e->getMessage() . "\n";
|
|
}
|
|
|
|
// Test 2c: Invalid Token
|
|
echo "\nTest 2c: Invalid CSRF Token\n";
|
|
|
|
try {
|
|
$invalidToken = CsrfToken::fromString(bin2hex(random_bytes(16)));
|
|
$params = ActionParameters::fromArray([], $invalidToken);
|
|
$handler->handle($testComponent, 'increment', $params);
|
|
|
|
echo "✗ Should have thrown exception for invalid token!\n";
|
|
exit(1);
|
|
} catch (\RuntimeException $e) {
|
|
echo "✓ Correctly rejected: " . $e->getMessage() . "\n";
|
|
}
|
|
|
|
// Test 3: Token Isolation Between Components
|
|
echo "\n\nTest 3: Token Isolation Between Components\n";
|
|
echo "------------------------------------------------\n";
|
|
|
|
$componentA = 'counter:instanceA';
|
|
$componentB = 'counter:instanceB';
|
|
|
|
// Render component A
|
|
$htmlA = $renderer->renderWithWrapper($componentA, '<div>A</div>', []);
|
|
preg_match('/data-csrf-token="([^"]+)"/', $htmlA, $matchesA);
|
|
$tokenA = CsrfToken::fromString($matchesA[1]);
|
|
|
|
echo "Component A token: " . substr($matchesA[1], 0, 16) . "...\n";
|
|
|
|
// Try to use token A with component B
|
|
$testComponentB = new class (ComponentId::fromString($componentB)) implements LiveComponentContract {
|
|
public function __construct(private ComponentId $id)
|
|
{
|
|
}
|
|
|
|
public function getId(): ComponentId
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getData(): ComponentData
|
|
{
|
|
return ComponentData::fromArray([]);
|
|
}
|
|
|
|
public function action(): ComponentData
|
|
{
|
|
return $this->getData();
|
|
}
|
|
};
|
|
|
|
try {
|
|
$params = ActionParameters::fromArray([], $tokenA);
|
|
$handler->handle($testComponentB, 'action', $params);
|
|
|
|
echo "✗ Should have rejected token from different component!\n";
|
|
exit(1);
|
|
} catch (\RuntimeException $e) {
|
|
echo "✓ Correctly isolated: Token from component A rejected by component B\n";
|
|
}
|
|
|
|
// Test 4: End-to-End Flow
|
|
echo "\n\nTest 4: Complete End-to-End CSRF Flow\n";
|
|
echo "------------------------------------------------\n";
|
|
|
|
$e2eComponentId = 'counter:e2e';
|
|
|
|
echo "1. Rendering component...\n";
|
|
$htmlE2E = $renderer->renderWithWrapper($e2eComponentId, '<div>E2E Test</div>', ['count' => 0]);
|
|
|
|
echo "2. Extracting CSRF token from HTML...\n";
|
|
preg_match('/data-csrf-token="([^"]+)"/', $htmlE2E, $matchesE2E);
|
|
$tokenE2E = CsrfToken::fromString($matchesE2E[1]);
|
|
|
|
echo "3. Simulating client action with extracted token...\n";
|
|
$e2eComponent = new class (ComponentId::fromString($e2eComponentId)) implements LiveComponentContract {
|
|
public function __construct(private ComponentId $id, private int $count = 0)
|
|
{
|
|
}
|
|
|
|
public function getId(): ComponentId
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getData(): ComponentData
|
|
{
|
|
return ComponentData::fromArray(['count' => $this->count]);
|
|
}
|
|
|
|
public function increment(): ComponentData
|
|
{
|
|
$this->count++;
|
|
|
|
return $this->getData();
|
|
}
|
|
};
|
|
|
|
$paramsE2E = ActionParameters::fromArray([], $tokenE2E);
|
|
$resultE2E = $handler->handle($e2eComponent, 'increment', $paramsE2E);
|
|
|
|
echo "4. Verifying result...\n";
|
|
if ($resultE2E->state->data['count'] === 1) {
|
|
echo "✓ E2E Flow completed successfully!\n";
|
|
} else {
|
|
echo "✗ E2E Flow failed: unexpected state\n";
|
|
exit(1);
|
|
}
|
|
|
|
echo "\n=== All Tests Passed! ===\n";
|
|
echo "\nSummary:\n";
|
|
echo " ✓ Token generation in renderer\n";
|
|
echo " ✓ Token validation in handler\n";
|
|
echo " ✓ Missing token rejection\n";
|
|
echo " ✓ Invalid token rejection\n";
|
|
echo " ✓ Token isolation between components\n";
|
|
echo " ✓ Complete end-to-end flow\n";
|
|
echo "\nCSRF Integration is working correctly!\n";
|