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
|
- **Modular Architecture** - Struktur code yang terorganisir dan mudah di-scale
|
||||||
- **JWT Authentication** - Secure authentication dengan role-based access
|
- **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
|
- **CRUD Master Data** - Locations, Gates, Tariffs dengan audit logging
|
||||||
- **Realtime Dashboard** - SSE (Server-Sent Events) untuk update real-time
|
- **Realtime Dashboard** - SSE (Server-Sent Events) untuk update real-time
|
||||||
- **Data Aggregation** - Daily & Hourly summary untuk reporting
|
- **Data Aggregation** - Daily & Hourly summary untuk reporting
|
||||||
@@ -137,6 +138,14 @@ JWT_ISSUER=api-btekno
|
|||||||
|
|
||||||
# API Key
|
# API Key
|
||||||
RETRIBUSI_API_KEY=your-api-key-here
|
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
|
## 📡 API Endpoints
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Bootstrap;
|
namespace App\Bootstrap;
|
||||||
|
|
||||||
|
use App\Middleware\CorsMiddleware;
|
||||||
use Slim\App;
|
use Slim\App;
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
use Slim\Middleware\BodyParsingMiddleware;
|
use Slim\Middleware\BodyParsingMiddleware;
|
||||||
@@ -19,6 +20,9 @@ class AppBootstrap
|
|||||||
{
|
{
|
||||||
$app = AppFactory::create();
|
$app = AppFactory::create();
|
||||||
|
|
||||||
|
// Add CORS middleware FIRST (before routing)
|
||||||
|
$app->add(new CorsMiddleware());
|
||||||
|
|
||||||
// Add body parsing middleware
|
// Add body parsing middleware
|
||||||
$app->addBodyParsingMiddleware();
|
$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