commit 3ae282607f706fb5809708fb4d74fe2515630d51 Author: Power BI Dev Date: Tue Apr 28 23:22:31 2026 +0700 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2525cb4 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +APP_PORT=8099 +SPOTA_BASE_URL=https://spota.untan.ac.id/steven/API +SPOTA_ALLOWED_ENDPOINTS=login.php,getListTugasAkhir.php,getListMahasiswa.php,getListDosen.php,sendMail.php + +# Optional. If set, callers must send X-Proxy-Token: value or ?proxy_token=value. +PROXY_TOKEN= + +CONNECT_TIMEOUT=10 +REQUEST_TIMEOUT=30 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1e6884a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM php:8.2-apache-bookworm + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl libcurl4-openssl-dev \ + && docker-php-ext-install curl \ + && a2enmod rewrite \ + && printf 'ServerName localhost\n' > /etc/apache2/conf-available/server-name.conf \ + && a2enconf server-name \ + && rm -rf /var/lib/apt/lists/* + +COPY public/ /var/www/html/ + +EXPOSE 80 diff --git a/README.md b/README.md new file mode 100644 index 0000000..b558d5f --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# SPOTA Proxy + +Aplikasi proxy kecil dan terpisah untuk meneruskan request dari project KP ke SPOTA melalui server yang masih bisa mengakses `spota.untan.ac.id`, misalnya `ifserver1`. + +Proxy ini dibuat sebagai produk/deployable terpisah dari `service-portal-kp`. Deploy di `ifserver1`, lalu project KP di `ifserver2` memanggil proxy ini, bukan langsung ke SPOTA. + +## Endpoint + +Default endpoint yang diizinkan: + +- `/login.php` +- `/getListTugasAkhir.php` +- `/getListMahasiswa.php` +- `/getListDosen.php` +- `/sendMail.php` +- `/healthz` + +Endpoint selain daftar `SPOTA_ALLOWED_ENDPOINTS` akan ditolak dengan `404` supaya service ini tidak menjadi open proxy bebas. + +## Deploy Coolify di ifserver1 + +Gunakan resource Docker Compose terpisah. + +Konfigurasi yang disarankan: + +```text +Repository: repository spota-proxy ini +Branch: main +Base directory: spota-proxy +Compose file: docker-compose.yml +Port aplikasi: 8099 +``` + +Jika repository hanya berisi folder ini, kosongkan `Base directory`. + +Environment minimal: + +```env +APP_PORT=8099 +SPOTA_BASE_URL=https://spota.untan.ac.id/steven/API +SPOTA_ALLOWED_ENDPOINTS=login.php,getListTugasAkhir.php,getListMahasiswa.php,getListDosen.php,sendMail.php +PROXY_TOKEN=ganti_dengan_token_panjang +CONNECT_TIMEOUT=10 +REQUEST_TIMEOUT=30 +``` + +`PROXY_TOKEN` opsional, tetapi disarankan jika service dapat diakses dari luar network internal. + +## Tes dari ifserver1 + +```bash +curl -i http://127.0.0.1:8099/healthz +``` + +Tes akses SPOTA lewat proxy: + +```bash +curl -i -X POST "http://127.0.0.1:8099/login.php" \ + -H "X-Proxy-Token: ganti_dengan_token_panjang" \ + -d "username=USERNAME_SPOTA" \ + -d "password=PASSWORD_SPOTA" +``` + +Jika username/password salah tetapi proxy sehat, respons akan tetap berasal dari SPOTA, misalnya: + +```json +{"status":0,"msg":"Username dan password tidak cocok!!!"} +``` + +## Tes dari ifserver2 + +```bash +curl -i http://IP_IFSERVER1:8099/healthz +``` + +```bash +curl -i -X POST "http://IP_IFSERVER1:8099/login.php" \ + -H "X-Proxy-Token: ganti_dengan_token_panjang" \ + -d "username=USERNAME_SPOTA" \ + -d "password=PASSWORD_SPOTA" +``` + +Jika menggunakan domain internal di Coolify, ganti `http://IP_IFSERVER1:8099` dengan URL domain tersebut. + +## Integrasi ke Project KP + +Ubah base URL SPOTA di project KP dari: + +```text +https://spota.untan.ac.id/steven/API +``` + +menjadi URL proxy di `ifserver1`, contoh: + +```text +http://IP_IFSERVER1:8099 +``` + +Jika `PROXY_TOKEN` diaktifkan, request dari project KP juga harus mengirim header: + +```text +X-Proxy-Token: ganti_dengan_token_panjang +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..70d08ca --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +services: + spota-proxy: + build: + context: . + dockerfile: Dockerfile + container_name: spota-proxy + restart: unless-stopped + ports: + - "${APP_PORT:-8099}:80" + environment: + SPOTA_BASE_URL: "${SPOTA_BASE_URL:-https://spota.untan.ac.id/steven/API}" + SPOTA_ALLOWED_ENDPOINTS: "${SPOTA_ALLOWED_ENDPOINTS:-login.php,getListTugasAkhir.php,getListMahasiswa.php,getListDosen.php,sendMail.php}" + PROXY_TOKEN: "${PROXY_TOKEN:-}" + CONNECT_TIMEOUT: "${CONNECT_TIMEOUT:-10}" + REQUEST_TIMEOUT: "${REQUEST_TIMEOUT:-30}" + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost/healthz >/dev/null || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..51c6bee --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,3 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^ index.php [QSA,L] diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..e8a8d16 --- /dev/null +++ b/public/index.php @@ -0,0 +1,102 @@ + 1, 'msg' => 'SPOTA proxy is running')); +} + +$endpoint = basename($endpoint); +$allowedEndpoints = array_filter(array_map('trim', explode(',', proxy_env('SPOTA_ALLOWED_ENDPOINTS', 'login.php')))); + +if (!in_array($endpoint, $allowedEndpoints, true)) { + proxy_json(404, array('status' => 0, 'msg' => 'Endpoint not allowed')); +} + +$token = proxy_env('PROXY_TOKEN', ''); +if ($token !== '') { + $providedToken = proxy_header('X-Proxy-Token'); + if ($providedToken === '' && isset($_GET['proxy_token'])) { + $providedToken = $_GET['proxy_token']; + unset($_GET['proxy_token']); + } + + if (!hash_equals($token, $providedToken)) { + proxy_json(401, array('status' => 0, 'msg' => 'Unauthorized')); + } +} + +$baseUrl = rtrim(proxy_env('SPOTA_BASE_URL', 'https://spota.untan.ac.id/steven/API'), '/'); +$targetUrl = $baseUrl.'/'.$endpoint; + +if (!empty($_GET)) { + $targetUrl .= '?'.http_build_query($_GET); +} + +$method = strtoupper($_SERVER['REQUEST_METHOD']); +$body = file_get_contents('php://input'); +$headers = array('Accept: application/json'); +$contentType = isset($_SERVER['CONTENT_TYPE']) ? trim($_SERVER['CONTENT_TYPE']) : ''; + +if ($contentType !== '') { + $headers[] = 'Content-Type: '.$contentType; +} + +$ch = curl_init($targetUrl); +curl_setopt_array($ch, array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CONNECTTIMEOUT => (int) proxy_env('CONNECT_TIMEOUT', '10'), + CURLOPT_TIMEOUT => (int) proxy_env('REQUEST_TIMEOUT', '30'), + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, + CURLOPT_HTTPHEADER => $headers, +)); + +if ($method === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body !== '' ? $body : http_build_query($_POST)); +} elseif ($method !== 'GET') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + if ($body !== '') { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } +} + +$response = curl_exec($ch); + +if ($response === false) { + error_log('SPOTA proxy failed for '.$endpoint.': '.curl_error($ch)); + curl_close($ch); + proxy_json(502, array('status' => 0, 'msg' => 'Tidak dapat terhubung ke server SPOTA.')); +} + +$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$responseType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); +curl_close($ch); + +http_response_code($httpCode > 0 ? $httpCode : 200); +header('Content-Type: '.($responseType !== null && $responseType !== '' ? $responseType : 'application/json')); +echo $response; diff --git a/public/login.php b/public/login.php new file mode 100644 index 0000000..96011eb --- /dev/null +++ b/public/login.php @@ -0,0 +1,3 @@ +