From 5219b8bfc607612144905a0e5da6caf1fe988fe4 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Thu, 22 Oct 2026 15:33:26 +0530 Subject: [PATCH] fix: strictly enforce IP restriction, fix empty whitelist fallback, and update SW to v2 --- app/Controllers/Api/v1/DoctorApi.php | 32 +++++++------ public/App/Queue/index.html | 67 +++++++++++++++------------- public/App/Queue/sw.js | 8 ++-- 3 files changed, 58 insertions(+), 49 deletions(-) diff --git a/app/Controllers/Api/v1/DoctorApi.php b/app/Controllers/Api/v1/DoctorApi.php index 3f84ef3..1f2508b 100644 --- a/app/Controllers/Api/v1/DoctorApi.php +++ b/app/Controllers/Api/v1/DoctorApi.php @@ -875,12 +875,17 @@ class DoctorApi extends BaseController if (! $this->isQueueAccessAllowed()) { return $this->response ->setStatusCode(403) + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->setHeader('Pragma', 'no-cache') ->setBody($this->renderQueueAccessDeniedHtml()); } $pwaPath = FCPATH . 'App/Queue/index.html'; if (file_exists($pwaPath)) { - return $this->response->setBody(file_get_contents($pwaPath)); + return $this->response + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->setHeader('Pragma', 'no-cache') + ->setBody(file_get_contents($pwaPath)); } return redirect()->to(base_url('app')); @@ -894,6 +899,7 @@ class DoctorApi extends BaseController $swPath = FCPATH . 'App/Queue/sw.js'; if (file_exists($swPath)) { return $this->response->setHeader('Content-Type', 'application/javascript') + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->setHeader('Service-Worker-Allowed', '/') ->setBody(file_get_contents($swPath)); } @@ -908,6 +914,7 @@ class DoctorApi extends BaseController $manifestPath = FCPATH . 'App/Queue/manifest.json'; if (file_exists($manifestPath)) { return $this->response->setHeader('Content-Type', 'application/json') + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->setBody(file_get_contents($manifestPath)); } return $this->response->setStatusCode(404); @@ -915,18 +922,10 @@ class DoctorApi extends BaseController /** * Verifies if the incoming request is authorized to access the OPD TV Queue Display / API. - * Access is allowed if: - * 1. User has an active authenticated HMS session (CodeIgniter Shield / user_id) - * 2. TV_QUEUE_IP_RESTRICTION_ENABLED setting is set to '0' (disabled) - * 3. Request origin matches allowed IPs/subnets (TV_QUEUE_ALLOWED_IPS) + * When TV_QUEUE_IP_RESTRICTION_ENABLED is '1', only allowed IPs/subnets can access. */ private function isQueueAccessAllowed(): bool { - // 1. Logged in staff / administrator can always view - if ((function_exists('auth') && auth()->loggedIn()) || (session()->has('user_id') && (int) session()->get('user_id') > 0)) { - return true; - } - $db = db_connect(); $enabledRow = $db->table('hospital_setting') ->select('s_value') @@ -939,21 +938,26 @@ class DoctorApi extends BaseController return true; } - // 2. Resolve client IP + // 1. Resolve client IP $clientIp = trim((string) $this->request->getIPAddress()); if ($clientIp === '' || $clientIp === '0.0.0.0') { $clientIp = trim((string) ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1')); } - // 3. Fetch allowed IP rules + // 2. Fetch allowed IP rules $allowedRow = $db->table('hospital_setting') ->select('s_value') ->where('s_name', 'TV_QUEUE_ALLOWED_IPS') ->get(1) ->getRowArray(); - $defaultAllowed = "127.0.0.1\n::1\n10.*\n192.168.*\n172.16.*\n103.251.140.182"; - $allowedIpsRaw = !empty($allowedRow['s_value']) ? (string) $allowedRow['s_value'] : $defaultAllowed; + // If setting exists in DB (even if empty because user deleted all IPs), respect it! + // Only if setting was never saved in DB, use default list. + if ($allowedRow !== null) { + $allowedIpsRaw = (string) $allowedRow['s_value']; + } else { + $allowedIpsRaw = "127.0.0.1\n::1\n10.*\n192.168.*\n172.16.*\n103.251.140.182"; + } $rules = preg_split('/[\r\n,]+/', $allowedIpsRaw, -1, PREG_SPLIT_NO_EMPTY); foreach ($rules as $rule) { diff --git a/public/App/Queue/index.html b/public/App/Queue/index.html index 1efb69d..fe5c7dd 100644 --- a/public/App/Queue/index.html +++ b/public/App/Queue/index.html @@ -509,41 +509,48 @@ var xhr = new XMLHttpRequest(); xhr.open('GET', '/api/v1/opd/queue/live', true); xhr.onreadystatechange = function() { - if (xhr.readyState === 4 && xhr.status === 200) { - try { - var json = JSON.parse(xhr.responseText); - if (json.status === 1) { - if (json.trackers) trackersData = json.trackers; - if (json.ads) adsData = json.ads; - if (json.hospital_name) { - var hospEl = document.getElementById('hdr_hosp_name'); - if (hospEl) hospEl.textContent = json.hospital_name; - } - if (json.footer_ticker) { - var tickerEl = document.getElementById('ticker_text'); - if (tickerEl) { - tickerEl.innerHTML = ' ' + json.footer_ticker; + if (xhr.readyState === 4) { + if (xhr.status === 200) { + try { + var json = JSON.parse(xhr.responseText); + if (json.status === 1) { + if (json.trackers) trackersData = json.trackers; + if (json.ads) adsData = json.ads; + if (json.hospital_name) { + var hospEl = document.getElementById('hdr_hosp_name'); + if (hospEl) hospEl.textContent = json.hospital_name; } - } - if (json.left_ad) { - var leftAdContainer = document.getElementById('tracker_left_ad_container'); - var leftAdContent = document.getElementById('tracker_left_ad_content'); - if (leftAdContainer && leftAdContent && json.left_ad.enabled) { - leftAdContainer.style.display = 'block'; - if (json.left_ad.image_url) { - leftAdContent.innerHTML = ''; - } else { - leftAdContent.innerHTML = '
' + (json.left_ad.text || '24x7 Emergency Services Available') + '
'; + if (json.footer_ticker) { + var tickerEl = document.getElementById('ticker_text'); + if (tickerEl) { + tickerEl.innerHTML = ' ' + json.footer_ticker; } - } else if (leftAdContainer) { - leftAdContainer.style.display = 'none'; } + if (json.left_ad) { + var leftAdContainer = document.getElementById('tracker_left_ad_container'); + var leftAdContent = document.getElementById('tracker_left_ad_content'); + if (leftAdContainer && leftAdContent && json.left_ad.enabled) { + leftAdContainer.style.display = 'block'; + if (json.left_ad.image_url) { + leftAdContent.innerHTML = ''; + } else { + leftAdContent.innerHTML = '
' + (json.left_ad.text || '24x7 Emergency Services Available') + '
'; + } + } else if (leftAdContainer) { + leftAdContainer.style.display = 'none'; + } + } + renderTrackers(); + renderShowcase(); } - renderTrackers(); - renderShowcase(); + } catch (e) { + console.log('JSON parse error'); } - } catch (e) { - console.log('JSON parse error'); + } else if (xhr.status === 403) { + // Access forbidden: wipe sensitive data and reload to show 403 screen + trackersData = []; + adsData = []; + window.location.reload(); } } }; diff --git a/public/App/Queue/sw.js b/public/App/Queue/sw.js index 3b9b005..8ae2687 100644 --- a/public/App/Queue/sw.js +++ b/public/App/Queue/sw.js @@ -1,7 +1,5 @@ -const CACHE_NAME = 'hms-opd-queue-v1'; +const CACHE_NAME = 'hms-opd-queue-v2'; const ASSETS_TO_CACHE = [ - '/app/queue', - '/App/Queue/index.html', '/App/Queue/manifest.json', '/assets/img/logo.png', '/assets/img/pwa-icon-512.png', @@ -35,8 +33,8 @@ self.addEventListener('activate', (event) => { }); self.addEventListener('fetch', (event) => { - // API endpoints always fetch fresh network data - if (event.request.url.includes('/api/v1/')) { + // Navigation requests (HTML documents) and API endpoints must always go straight to network + if (event.request.mode === 'navigate' || event.request.url.includes('/api/v1/') || event.request.url.includes('/app/queue')) { event.respondWith(fetch(event.request)); return; }