feat: Add CORS middleware untuk akses dari browser lokal
This commit is contained in:
@@ -6,6 +6,7 @@ Sistem API Retribusi berbasis Slim Framework 4 dengan arsitektur modular untuk i
|
||||
|
||||
- **Modular Architecture** - Struktur code yang terorganisir dan mudah di-scale
|
||||
- **JWT Authentication** - Secure authentication dengan role-based access
|
||||
- **CORS Support** - Cross-Origin Resource Sharing untuk akses dari browser
|
||||
- **CRUD Master Data** - Locations, Gates, Tariffs dengan audit logging
|
||||
- **Realtime Dashboard** - SSE (Server-Sent Events) untuk update real-time
|
||||
- **Data Aggregation** - Daily & Hourly summary untuk reporting
|
||||
@@ -137,6 +138,14 @@ JWT_ISSUER=api-btekno
|
||||
|
||||
# API Key
|
||||
RETRIBUSI_API_KEY=your-api-key-here
|
||||
|
||||
# CORS (Cross-Origin Resource Sharing)
|
||||
# Set '*' untuk allow semua origin (development)
|
||||
# Atau list origin yang diizinkan dipisah koma: http://localhost:3000,https://app.example.com
|
||||
CORS_ALLOWED_ORIGINS=*
|
||||
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
|
||||
CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-API-KEY,Accept,Origin
|
||||
CORS_ALLOW_CREDENTIALS=true
|
||||
```
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Bootstrap;
|
||||
|
||||
use App\Middleware\CorsMiddleware;
|
||||
use Slim\App;
|
||||
use Slim\Factory\AppFactory;
|
||||
use Slim\Middleware\BodyParsingMiddleware;
|
||||
@@ -19,6 +20,9 @@ class AppBootstrap
|
||||
{
|
||||
$app = AppFactory::create();
|
||||
|
||||
// Add CORS middleware FIRST (before routing)
|
||||
$app->add(new CorsMiddleware());
|
||||
|
||||
// Add body parsing middleware
|
||||
$app->addBodyParsingMiddleware();
|
||||
|
||||
|
||||
129
src/Middleware/CorsMiddleware.php
Normal file
129
src/Middleware/CorsMiddleware.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Middleware;
|
||||
|
||||
use App\Config\AppConfig;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Slim\Psr7\Factory\ResponseFactory;
|
||||
|
||||
class CorsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private array $allowedOrigins;
|
||||
private array $allowedMethods;
|
||||
private array $allowedHeaders;
|
||||
private bool $allowCredentials;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Load allowed origins from ENV or use defaults
|
||||
$originsEnv = AppConfig::get('CORS_ALLOWED_ORIGINS', '*');
|
||||
$this->allowedOrigins = $originsEnv === '*'
|
||||
? ['*']
|
||||
: array_map('trim', explode(',', $originsEnv));
|
||||
|
||||
// Allowed HTTP methods
|
||||
$methodsEnv = AppConfig::get('CORS_ALLOWED_METHODS', 'GET,POST,PUT,DELETE,OPTIONS');
|
||||
$this->allowedMethods = array_map('trim', explode(',', $methodsEnv));
|
||||
|
||||
// Allowed headers
|
||||
$headersEnv = AppConfig::get(
|
||||
'CORS_ALLOWED_HEADERS',
|
||||
'Content-Type,Authorization,X-API-KEY,Accept,Origin'
|
||||
);
|
||||
$this->allowedHeaders = array_map('trim', explode(',', $headersEnv));
|
||||
|
||||
// Allow credentials
|
||||
$this->allowCredentials = AppConfig::get('CORS_ALLOW_CREDENTIALS', 'true') === 'true';
|
||||
}
|
||||
|
||||
public function process(
|
||||
ServerRequestInterface $request,
|
||||
RequestHandlerInterface $handler
|
||||
): ResponseInterface {
|
||||
$origin = $request->getHeaderLine('Origin');
|
||||
|
||||
// Handle preflight OPTIONS request
|
||||
if ($request->getMethod() === 'OPTIONS') {
|
||||
$responseFactory = new ResponseFactory();
|
||||
$response = $responseFactory->createResponse(204); // No Content
|
||||
return $this->addCorsHeaders($response, $origin);
|
||||
}
|
||||
|
||||
// Process the request
|
||||
$response = $handler->handle($request);
|
||||
|
||||
// Add CORS headers to response
|
||||
return $this->addCorsHeaders($response, $origin);
|
||||
}
|
||||
|
||||
private function addCorsHeaders(
|
||||
ResponseInterface $response,
|
||||
string $origin
|
||||
): ResponseInterface {
|
||||
// Determine allowed origin
|
||||
$allowedOrigin = $this->getAllowedOrigin($origin);
|
||||
|
||||
if ($allowedOrigin) {
|
||||
$response = $response->withHeader('Access-Control-Allow-Origin', $allowedOrigin);
|
||||
}
|
||||
|
||||
if ($this->allowCredentials && $allowedOrigin !== '*') {
|
||||
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
|
||||
$response = $response->withHeader(
|
||||
'Access-Control-Allow-Methods',
|
||||
implode(', ', $this->allowedMethods)
|
||||
);
|
||||
|
||||
$response = $response->withHeader(
|
||||
'Access-Control-Allow-Headers',
|
||||
implode(', ', $this->allowedHeaders)
|
||||
);
|
||||
|
||||
$response = $response->withHeader('Access-Control-Max-Age', '86400'); // 24 hours
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getAllowedOrigin(string $origin): ?string
|
||||
{
|
||||
// If no origin header, return null (not a CORS request)
|
||||
if (empty($origin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If wildcard is allowed, return it
|
||||
if (in_array('*', $this->allowedOrigins, true)) {
|
||||
return '*';
|
||||
}
|
||||
|
||||
// Check if origin is in allowed list
|
||||
if (in_array($origin, $this->allowedOrigins, true)) {
|
||||
return $origin;
|
||||
}
|
||||
|
||||
// Check for localhost variations
|
||||
$localhostPatterns = [
|
||||
'http://localhost',
|
||||
'http://127.0.0.1',
|
||||
'http://localhost:',
|
||||
'http://127.0.0.1:',
|
||||
];
|
||||
|
||||
foreach ($localhostPatterns as $pattern) {
|
||||
if (str_starts_with($origin, $pattern)) {
|
||||
return $origin;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: return first allowed origin (fallback)
|
||||
return $this->allowedOrigins[0] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user