feat(Production): Complete production deployment infrastructure

- 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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -15,7 +15,15 @@ final readonly class Cookie
public bool $secure = false,
public bool $httpOnly = false,
public SameSite $sameSite = SameSite::Lax,
public bool $partitioned = false,
) {
// CHIPS (Cookies Having Independent Partitioned State) requirements:
// Partitioned cookies MUST have Secure=true AND SameSite=None
if ($this->partitioned && (!$this->secure || $this->sameSite !== SameSite::None)) {
throw new \InvalidArgumentException(
'Partitioned cookies require Secure=true and SameSite=None'
);
}
}
public function toHeaderString(): string
@@ -44,6 +52,10 @@ final readonly class Cookie
$cookie .= '; SameSite=' . $this->sameSite->value;
if ($this->partitioned) {
$cookie .= '; Partitioned';
}
return $cookie;
}

View File

@@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
namespace App\Framework\Http\Cookies;
use App\Framework\Config\EnvKey;
use App\Framework\Config\Environment;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
/**
* Service for creating cookies with framework-compliant patterns.
*
* Supports CHIPS (Cookies Having Independent Partitioned State) for third-party contexts.
*/
final readonly class CookieService
{
private const MAX_COOKIE_SIZE = 4096;
public function __construct(
private Environment $environment
) {
}
/**
* Create a standard cookie.
*/
public function create(
string $name,
string $value,
?Duration $maxAge = null,
string $path = '/',
?string $domain = null,
?bool $secure = null,
bool $httpOnly = true,
SameSite $sameSite = SameSite::Lax
): Cookie {
// Security: enforce secure in production
$isProduction = $this->environment->get(EnvKey::APP_ENV) === 'production';
$secure = $secure ?? $isProduction;
// Calculate expiration timestamp
$expires = $maxAge ? Timestamp::now()->add($maxAge)->toTimestamp() : null;
return new Cookie(
name: $name,
value: $value,
expires: $expires,
path: $path,
domain: $domain ?? '',
secure: $secure,
httpOnly: $httpOnly,
sameSite: $sameSite,
partitioned: false
);
}
/**
* Create a CHIPS (partitioned) cookie for third-party contexts.
*
* Use cases:
* - Embedded widgets (chat, support, social media)
* - Third-party analytics in iframes
* - Payment provider embeds (Stripe, PayPal)
* - OAuth flows in iframes
*
* Requirements:
* - Must be Secure (HTTPS only)
* - Must have SameSite=None
* - Partitioned attribute set
*
* @throws \InvalidArgumentException if cookie validation fails
*/
public function createPartitioned(
string $name,
string $value,
?Duration $maxAge = null,
string $path = '/',
?string $domain = null,
bool $httpOnly = true
): Cookie {
$expires = $maxAge ? Timestamp::now()->add($maxAge)->toTimestamp() : null;
// CHIPS cookies MUST be Secure and SameSite=None
return new Cookie(
name: $name,
value: $value,
expires: $expires,
path: $path,
domain: $domain ?? '',
secure: true, // Required for Partitioned
httpOnly: $httpOnly,
sameSite: SameSite::None, // Required for Partitioned
partitioned: true // CHIPS attribute
);
}
/**
* Create a session cookie (no expiration).
*/
public function createSessionCookie(
string $name,
string $value,
bool $httpOnly = true,
SameSite $sameSite = SameSite::Lax
): Cookie {
return $this->create(
name: $name,
value: $value,
maxAge: null, // Session cookie
httpOnly: $httpOnly,
sameSite: $sameSite
);
}
/**
* Create a partitioned session cookie for third-party embeds.
*/
public function createPartitionedSession(
string $name,
string $value,
bool $httpOnly = true
): Cookie {
return new Cookie(
name: $name,
value: $value,
expires: null, // Session cookie
path: '/',
domain: '',
secure: true,
httpOnly: $httpOnly,
sameSite: SameSite::None,
partitioned: true
);
}
/**
* Create a remember-me cookie (typically 30 days).
*/
public function createRememberMeCookie(
string $value,
?Duration $maxAge = null
): Cookie {
return $this->create(
name: 'remember_me',
value: $value,
maxAge: $maxAge ?? Duration::fromDays(30),
httpOnly: true,
sameSite: SameSite::Strict
);
}
/**
* Create a third-party analytics cookie (partitioned).
*/
public function createAnalyticsCookie(
string $userId,
?Duration $maxAge = null
): Cookie {
return $this->createPartitioned(
name: '_analytics',
value: $userId,
maxAge: $maxAge ?? Duration::fromDays(365),
httpOnly: false // JavaScript access needed for analytics
);
}
/**
* Create a third-party widget state cookie (partitioned).
*/
public function createWidgetStateCookie(
string $widgetId,
string $state,
?Duration $maxAge = null
): Cookie {
return $this->createPartitioned(
name: "widget_{$widgetId}",
value: $state,
maxAge: $maxAge ?? Duration::fromDays(30),
httpOnly: true
);
}
/**
* Create a cookie deletion instruction.
*
* Sets expiration to past time to delete cookie.
*/
public function delete(string $name, string $path = '/'): Cookie
{
// Set expiration to 1 second ago (past time deletes cookie)
return new Cookie(
name: $name,
value: '',
expires: time() - 1,
path: $path,
domain: '',
secure: false,
httpOnly: false,
sameSite: SameSite::Lax,
partitioned: false
);
}
/**
* Validate cookie size is within browser limits.
*/
public function validateSize(Cookie $cookie): bool
{
return strlen($cookie->toHeaderString()) <= self::MAX_COOKIE_SIZE;
}
/**
* Check if cookie name is valid.
*/
public function isValidName(string $name): bool
{
// Cookie name must not contain: ( ) < > @ , ; : \ " / [ ] ? = { } space tab
return preg_match('/^[a-zA-Z0-9_-]+$/', $name) === 1;
}
}