docs: Add module guide dan security guidelines untuk API documentation
This commit is contained in:
111
MODULE_GUIDE.md
Normal file
111
MODULE_GUIDE.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Panduan Menambah Module Baru
|
||||||
|
|
||||||
|
## 📦 Struktur Module
|
||||||
|
|
||||||
|
Setiap module baru mengikuti pola yang sama:
|
||||||
|
|
||||||
|
```
|
||||||
|
src/Modules/ModuleBaru/
|
||||||
|
├── ModuleBaruRoutes.php # Register routes
|
||||||
|
├── Controller.php # Request handler
|
||||||
|
└── Service.php # Business logic
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Langkah Menambah Module
|
||||||
|
|
||||||
|
### 1. Buat Struktur Folder
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p src/Modules/ModuleBaru
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Buat Routes File
|
||||||
|
|
||||||
|
`src/Modules/ModuleBaru/ModuleBaruRoutes.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Modules\ModuleBaru;
|
||||||
|
|
||||||
|
use Slim\App;
|
||||||
|
|
||||||
|
class ModuleBaruRoutes
|
||||||
|
{
|
||||||
|
public static function register(App $app): void
|
||||||
|
{
|
||||||
|
$app->group('/modulebaru', function ($group) {
|
||||||
|
$group->group('/v1', function ($v1Group) {
|
||||||
|
// Routes di sini
|
||||||
|
$v1Group->get('/endpoint', [Controller::class, 'method']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Register di public/index.php
|
||||||
|
|
||||||
|
Tambahkan di bagian "Register module routes":
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Modules\ModuleBaru\ModuleBaruRoutes;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Register module routes
|
||||||
|
HealthRoutes::register($app);
|
||||||
|
AuthRoutes::register($app);
|
||||||
|
RetribusiRoutes::register($app);
|
||||||
|
ModuleBaruRoutes::register($app); // <-- Tambahkan di sini
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Update OpenAPI Spec (Opsional)
|
||||||
|
|
||||||
|
Edit `public/docs/openapi.json` dan tambahkan endpoint baru di `paths`.
|
||||||
|
|
||||||
|
## ✅ Contoh: Module "Parkir"
|
||||||
|
|
||||||
|
```php
|
||||||
|
// src/Modules/Parkir/ParkirRoutes.php
|
||||||
|
namespace App\Modules\Parkir;
|
||||||
|
|
||||||
|
use App\Middleware\JwtMiddleware;
|
||||||
|
use Slim\App;
|
||||||
|
|
||||||
|
class ParkirRoutes
|
||||||
|
{
|
||||||
|
public static function register(App $app): void
|
||||||
|
{
|
||||||
|
$jwtMiddleware = new JwtMiddleware();
|
||||||
|
|
||||||
|
$app->group('/parkir', function ($group) use ($jwtMiddleware) {
|
||||||
|
$group->group('/v1', function ($v1Group) use ($jwtMiddleware) {
|
||||||
|
$v1Group->get('/zones', [ParkirController::class, 'getZones'])
|
||||||
|
->add($jwtMiddleware);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Best Practices
|
||||||
|
|
||||||
|
1. **Isolasi**: Setiap module independen
|
||||||
|
2. **Naming**: Gunakan namespace yang jelas
|
||||||
|
3. **Routes**: Group by module name
|
||||||
|
4. **Versioning**: Gunakan `/v1` untuk future compatibility
|
||||||
|
5. **Middleware**: Apply di level group atau route
|
||||||
|
|
||||||
|
## 📝 Checklist Module Baru
|
||||||
|
|
||||||
|
- [ ] Buat folder `src/Modules/ModuleName/`
|
||||||
|
- [ ] Buat `ModuleNameRoutes.php` dengan method `register()`
|
||||||
|
- [ ] Buat Controller & Service (jika perlu)
|
||||||
|
- [ ] Register di `public/index.php`
|
||||||
|
- [ ] Test routes
|
||||||
|
- [ ] Update OpenAPI spec (opsional)
|
||||||
|
- [ ] Commit & push
|
||||||
|
|
||||||
204
SECURITY.md
Normal file
204
SECURITY.md
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
# Security Guidelines - API Documentation
|
||||||
|
|
||||||
|
## 🔒 Keamanan API Documentation
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
|
||||||
|
Saat ini API documentation (Swagger UI) di `/docs` **PUBLIC** - bisa diakses tanpa authentication.
|
||||||
|
|
||||||
|
### ⚠️ Security Considerations
|
||||||
|
|
||||||
|
**Risiko jika docs public:**
|
||||||
|
1. **Information Disclosure**: Endpoint structure terlihat semua orang
|
||||||
|
2. **Attack Surface**: Attacker tahu semua endpoint yang ada
|
||||||
|
3. **API Key Exposure**: Jika ada contoh API key di docs (jangan!)
|
||||||
|
4. **Business Logic**: Flow bisnis bisa terlihat
|
||||||
|
|
||||||
|
**Tapi juga ada benefit:**
|
||||||
|
1. **Developer Experience**: Developer mudah test API
|
||||||
|
2. **Onboarding**: New developer cepat paham
|
||||||
|
3. **Integration**: Partner bisa lihat spec dengan mudah
|
||||||
|
|
||||||
|
## 🛡️ Opsi Security untuk Docs
|
||||||
|
|
||||||
|
### Opsi 1: Public (Current - Recommended untuk Internal API)
|
||||||
|
|
||||||
|
**Cocok untuk:**
|
||||||
|
- Internal API (tidak public internet)
|
||||||
|
- API dengan authentication kuat (JWT + Role)
|
||||||
|
- Development/Staging environment
|
||||||
|
|
||||||
|
**Kelebihan:**
|
||||||
|
- Mudah digunakan
|
||||||
|
- Developer friendly
|
||||||
|
- Tidak perlu setup tambahan
|
||||||
|
|
||||||
|
**Kekurangan:**
|
||||||
|
- Endpoint structure terlihat
|
||||||
|
- Perlu pastikan tidak ada sensitive info di docs
|
||||||
|
|
||||||
|
### Opsi 2: JWT Protected (Recommended untuk Production)
|
||||||
|
|
||||||
|
Protect `/docs` dengan JWT middleware.
|
||||||
|
|
||||||
|
**Implementasi:**
|
||||||
|
|
||||||
|
Update `public/index.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Middleware\JwtMiddleware;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Docs route - dengan JWT protection
|
||||||
|
$jwtMiddleware = new JwtMiddleware();
|
||||||
|
|
||||||
|
$app->get('/docs', function ($request, $response) {
|
||||||
|
// ... existing code ...
|
||||||
|
})->add($jwtMiddleware);
|
||||||
|
|
||||||
|
$app->get('/docs/openapi.json', function ($request, $response) {
|
||||||
|
// ... existing code ...
|
||||||
|
})->add($jwtMiddleware);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cocok untuk:**
|
||||||
|
- Production API yang sensitive
|
||||||
|
- Public API dengan authentication
|
||||||
|
- API yang perlu kontrol akses
|
||||||
|
|
||||||
|
### Opsi 3: IP Whitelist (Paling Ketat)
|
||||||
|
|
||||||
|
Hanya allow IP tertentu yang bisa akses docs.
|
||||||
|
|
||||||
|
**Implementasi:**
|
||||||
|
|
||||||
|
Buat middleware `src/Middleware/IpWhitelistMiddleware.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Middleware;
|
||||||
|
|
||||||
|
use App\Config\AppConfig;
|
||||||
|
use App\Support\ResponseHelper;
|
||||||
|
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 IpWhitelistMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
private array $allowedIps;
|
||||||
|
|
||||||
|
public function __construct(array $allowedIps)
|
||||||
|
{
|
||||||
|
$this->allowedIps = $allowedIps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(
|
||||||
|
ServerRequestInterface $request,
|
||||||
|
RequestHandlerInterface $handler
|
||||||
|
): ResponseInterface {
|
||||||
|
$serverParams = $request->getServerParams();
|
||||||
|
$clientIp = $serverParams['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||||
|
|
||||||
|
// Check X-Forwarded-For (behind proxy)
|
||||||
|
if (isset($serverParams['HTTP_X_FORWARDED_FOR'])) {
|
||||||
|
$ips = explode(',', $serverParams['HTTP_X_FORWARDED_FOR']);
|
||||||
|
$clientIp = trim($ips[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($clientIp, $this->allowedIps, true)) {
|
||||||
|
$responseFactory = new ResponseFactory();
|
||||||
|
$response = $responseFactory->createResponse();
|
||||||
|
return ResponseHelper::json(
|
||||||
|
$response,
|
||||||
|
['error' => 'forbidden', 'message' => 'Access denied'],
|
||||||
|
403
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Gunakan di `public/index.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use App\Middleware\IpWhitelistMiddleware;
|
||||||
|
use App\Config\AppConfig;
|
||||||
|
|
||||||
|
// Get allowed IPs from ENV
|
||||||
|
$allowedIps = explode(',', AppConfig::get('DOCS_ALLOWED_IPS', ''));
|
||||||
|
$ipWhitelist = new IpWhitelistMiddleware($allowedIps);
|
||||||
|
|
||||||
|
$app->get('/docs', function ($request, $response) {
|
||||||
|
// ... existing code ...
|
||||||
|
})->add($ipWhitelist);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cocok untuk:**
|
||||||
|
- Very sensitive API
|
||||||
|
- Internal company network only
|
||||||
|
- High security requirement
|
||||||
|
|
||||||
|
### Opsi 4: Basic Auth (Simple)
|
||||||
|
|
||||||
|
Protect dengan HTTP Basic Authentication.
|
||||||
|
|
||||||
|
**Implementasi:**
|
||||||
|
|
||||||
|
Buat middleware atau gunakan nginx basic auth:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /docs {
|
||||||
|
auth_basic "API Documentation";
|
||||||
|
auth_basic_user_file /path/to/.htpasswd;
|
||||||
|
# ... rest of config
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Recommendations
|
||||||
|
|
||||||
|
### Untuk API Internal (Current Setup)
|
||||||
|
|
||||||
|
**Status**: ✅ **AMAN** jika:
|
||||||
|
- API hanya diakses dari internal network
|
||||||
|
- Semua endpoint sudah protected dengan JWT/API Key
|
||||||
|
- Tidak ada sensitive data di OpenAPI spec
|
||||||
|
- Server tidak exposed ke public internet
|
||||||
|
|
||||||
|
**Action**:
|
||||||
|
- Pastikan tidak ada contoh API key/password di docs
|
||||||
|
- Review OpenAPI spec sebelum deploy
|
||||||
|
- Monitor access logs
|
||||||
|
|
||||||
|
### Untuk Public API
|
||||||
|
|
||||||
|
**Status**: ⚠️ **PERLU PROTECTION** jika:
|
||||||
|
- API exposed ke public internet
|
||||||
|
- Ada sensitive business logic
|
||||||
|
- Compliance requirement (GDPR, dll)
|
||||||
|
|
||||||
|
**Action**:
|
||||||
|
- Implement JWT protection untuk `/docs`
|
||||||
|
- Atau IP whitelist untuk internal team
|
||||||
|
- Review dan sanitize OpenAPI spec
|
||||||
|
|
||||||
|
## 🔐 Best Practices
|
||||||
|
|
||||||
|
1. **Jangan hardcode credentials** di OpenAPI spec
|
||||||
|
2. **Gunakan environment variables** untuk examples
|
||||||
|
3. **Review secara berkala** endpoint yang di-expose
|
||||||
|
4. **Monitor access logs** untuk `/docs`
|
||||||
|
5. **Update docs** jika ada perubahan security
|
||||||
|
|
||||||
|
## 🚨 Jika Perlu Protect Docs Sekarang
|
||||||
|
|
||||||
|
Saya bisa implement JWT protection untuk `/docs` dengan cepat. Tinggal kasih tahu mau pakai opsi mana!
|
||||||
|
|
||||||
@@ -27,36 +27,38 @@ $app->get('/', function ($request, $response) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Docs route - serve Swagger UI
|
// Docs route - serve Swagger UI
|
||||||
|
// NOTE: Saat ini PUBLIC. Jika perlu protect, tambahkan JwtMiddleware atau IpWhitelistMiddleware
|
||||||
$app->get('/docs', function ($request, $response) {
|
$app->get('/docs', function ($request, $response) {
|
||||||
$docsPath = __DIR__ . '/docs/index.html';
|
$docsPath = __DIR__ . '/docs/index.html';
|
||||||
|
|
||||||
if (!file_exists($docsPath)) {
|
if (!file_exists($docsPath)) {
|
||||||
$response->getBody()->write('<h1>Documentation not found</h1>');
|
$response->getBody()->write('<h1>Documentation not found</h1>');
|
||||||
return $response
|
return $response
|
||||||
->withStatus(404)
|
->withStatus(404)
|
||||||
->withHeader('Content-Type', 'text/html');
|
->withHeader('Content-Type', 'text/html');
|
||||||
}
|
}
|
||||||
|
|
||||||
$html = file_get_contents($docsPath);
|
$html = file_get_contents($docsPath);
|
||||||
$response->getBody()->write($html);
|
$response->getBody()->write($html);
|
||||||
|
|
||||||
return $response->withHeader('Content-Type', 'text/html');
|
return $response->withHeader('Content-Type', 'text/html');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve OpenAPI JSON
|
// Serve OpenAPI JSON
|
||||||
|
// NOTE: Saat ini PUBLIC. Jika perlu protect, tambahkan middleware
|
||||||
$app->get('/docs/openapi.json', function ($request, $response) {
|
$app->get('/docs/openapi.json', function ($request, $response) {
|
||||||
$openApiPath = __DIR__ . '/docs/openapi.json';
|
$openApiPath = __DIR__ . '/docs/openapi.json';
|
||||||
|
|
||||||
if (!file_exists($openApiPath)) {
|
if (!file_exists($openApiPath)) {
|
||||||
$response->getBody()->write(json_encode(['error' => 'OpenAPI spec not found']));
|
$response->getBody()->write(json_encode(['error' => 'OpenAPI spec not found']));
|
||||||
return $response
|
return $response
|
||||||
->withStatus(404)
|
->withStatus(404)
|
||||||
->withHeader('Content-Type', 'application/json');
|
->withHeader('Content-Type', 'application/json');
|
||||||
}
|
}
|
||||||
|
|
||||||
$json = file_get_contents($openApiPath);
|
$json = file_get_contents($openApiPath);
|
||||||
$response->getBody()->write($json);
|
$response->getBody()->write($json);
|
||||||
|
|
||||||
return $response->withHeader('Content-Type', 'application/json');
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -70,4 +72,3 @@ RealtimeRoutes::register($app);
|
|||||||
|
|
||||||
// Run application
|
// Run application
|
||||||
$app->run();
|
$app->run();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user