fix: strictly enforce IP restriction, fix empty whitelist fallback, and update SW to v2

This commit is contained in:
DevSoftTech Server 2026-10-22 15:33:26 +05:30
parent 1bd8f57371
commit 5219b8bfc6
3 changed files with 58 additions and 49 deletions

View File

@ -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) {

View File

@ -509,7 +509,8 @@
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/v1/opd/queue/live', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
var json = JSON.parse(xhr.responseText);
if (json.status === 1) {
@ -545,6 +546,12 @@
} 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();
}
}
};
xhr.send();

View File

@ -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;
}