Files
api/services/nginx/app/classes/cors_policy.php
T

189 lines
5.6 KiB
PHP

<?php
namespace classes;
class cors_policy
{
public const ALLOWED_HEADERS = 'Content-Type, Authorization, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version, Cache-Control, Pragma, *';
public const ALLOWED_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
public const MAX_AGE_SECONDS = '86400';
private const REQUIRED_ALLOWED_ORIGINS = [
'https://truckwash.io',
'https://www.truckwash.io',
'https://api.truckwash.io',
'https://api.truckwash.io:4433',
'https://api-v2.truckwash.io',
'https://web.truckwash.dk',
'https://api.truckwash.dk',
'https://truckwash.dk',
'https://www.truckwash.dk',
'https://staging.truckwash.io',
'http://localhost',
'https://localhost',
'http://localhost:4433',
'https://localhost:4433',
'https://twdev.jeppeb.dk',
'http://localhost:5173',
];
public static function normalizeOrigin(?string $value): string
{
$value = trim((string)$value);
if ($value === '' || $value === '*') {
return $value;
}
if (preg_match('#^https?://#i', $value) !== 1) {
return '';
}
$parts = parse_url($value);
if (!is_array($parts) || empty($parts['scheme']) || empty($parts['host'])) {
return '';
}
$scheme = strtolower((string)$parts['scheme']);
if (!in_array($scheme, ['http', 'https'], true)) {
return '';
}
$host = strtolower((string)$parts['host']);
$port = isset($parts['port']) ? ':' . (int)$parts['port'] : '';
return $scheme . '://' . $host . $port;
}
/**
* @return array<int,string>
*/
public static function requiredAllowedOrigins(): array
{
return self::REQUIRED_ALLOWED_ORIGINS;
}
/**
* @return array<int,string>
*/
public static function allowedOrigins(string $corsConfig): array
{
$origins = [];
foreach (self::splitOrigins($corsConfig) as $configuredOrigin) {
if ($configuredOrigin === '*') {
return ['*'];
}
$origin = self::normalizeOrigin($configuredOrigin);
if ($origin !== '') {
$origins[$origin] = true;
}
}
foreach (self::REQUIRED_ALLOWED_ORIGINS as $requiredOrigin) {
$origin = self::normalizeOrigin($requiredOrigin);
if ($origin !== '') {
$origins[$origin] = true;
}
}
return array_keys($origins);
}
public static function withRequiredOrigins(string $corsConfig): string
{
$allowedOrigins = self::allowedOrigins($corsConfig);
if ($allowedOrigins === ['*']) {
return '*';
}
return implode(',', $allowedOrigins);
}
public static function isOriginAllowed(?string $origin, string $corsConfig): bool
{
$origin = self::normalizeOrigin($origin);
if ($origin === '' || $origin === '*') {
return false;
}
$allowedOrigins = self::allowedOrigins($corsConfig);
return in_array('*', $allowedOrigins, true) || in_array($origin, $allowedOrigins, true);
}
/**
* @return array<string,string>
*/
public static function responseHeaders(?string $origin, string $corsConfig): array
{
$origin = self::normalizeOrigin($origin);
if ($origin === '' || !self::isOriginAllowed($origin, $corsConfig)) {
return [];
}
return [
'Access-Control-Allow-Origin' => $origin,
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Headers' => self::ALLOWED_HEADERS,
'Access-Control-Allow-Methods' => self::ALLOWED_METHODS,
'Access-Control-Max-Age' => self::MAX_AGE_SECONDS,
'Vary' => 'Origin',
];
}
/**
* @return array{allowed:bool,status:int,headers:array<string,string>,body:string}
*/
public static function preflightResponse(?string $origin, string $corsConfig): array
{
$headers = self::responseHeaders($origin, $corsConfig);
if ($headers === []) {
return [
'allowed' => false,
'status' => 403,
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode(['success' => false, 'message' => 'CORS origin not allowed']) ?: '',
];
}
$headers['Content-Type'] = 'application/json';
return [
'allowed' => true,
'status' => 200,
'headers' => $headers,
'body' => '',
];
}
public static function applyResponseHeaders(string $corsConfig, ?string $origin = null): bool
{
$headers = self::responseHeaders($origin ?? ($_SERVER['HTTP_ORIGIN'] ?? ''), $corsConfig);
if ($headers === []) {
return false;
}
self::emitHeaders($headers);
return true;
}
/**
* @param array<string,string> $headers
*/
public static function emitHeaders(array $headers): void
{
foreach ($headers as $name => $value) {
header($name . ': ' . $value, strtolower((string)$name) !== 'vary');
}
}
/**
* @return array<int,string>
*/
private static function splitOrigins(string $corsConfig): array
{
return array_values(array_filter(
array_map('trim', explode(',', $corsConfig)),
static fn(string $origin): bool => $origin !== ''
));
}
}