= isset($message) && !empty($message) ? $message : '404 PAGE NOT FOUND' ?>

diff --git a/.env.sample b/.env.sample
index ebc9ab1d..f9ab9b80 100755
--- a/.env.sample
+++ b/.env.sample
@@ -108,3 +108,5 @@ CORS_MAX_AGE=7200
CORS_DEBUG=true
APP_SIGNATURE =
+TOKENTIMEOUT =
+JWT_SECRET =
\ No newline at end of file
diff --git a/app/Config/Acl.php b/app/Config/Acl.php
new file mode 100644
index 00000000..f2f4ecca
--- /dev/null
+++ b/app/Config/Acl.php
@@ -0,0 +1,157 @@
+ ['public' => true],
+ '#^/logout#' => ['public' => true],
+ '#^/auth#' => ['public' => true],
+ '#^/oauth2callback#' => ['public' => true],
+ '#^/loginPos#' => ['public' => true],
+ '#^/getVerifyPosMobileNo#' => ['public' => true],
+ '#^/getVerifiedPosUserData#' => ['public' => true],
+ '#^/swagger#' => ['roles' => [ADMIN_ROLE_ID]],
+ '#^/fedeploy#' => ['roles' => [ADMIN_ROLE_ID]],
+
+ // ===================== PUBLIC DOWNLOADS / FORMS =====================
+ '#^/download-#' => ['public' => true],
+ '#^/claim-form-download#' => ['public' => true],
+ '#^/claims-feedback-form#' => ['public' => true],
+ '#^/autobookstackLogin#' => ['public' => true],
+
+ // ===================== DASHBOARD =====================
+ '#^/dashboard#' => [
+ 'roles' => [ ADMIN_ROLE_ID,HEAD_ROLE_ID, MANAGER_ROLE_ID, STAFF_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== USER MANAGEMENT =====================
+ '#^/user#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== CLIENT =====================
+ '#^/client#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== EMPLOYEE / ENROLLMENT =====================
+ '#^/employee#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [ENROLLMENT_TEAM_ID]
+ ],
+
+ // ===================== MASTERS =====================
+ '#^/master#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => []
+ ],
+ '#^/util#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== POLICY TRANSACTION / BDS =====================
+ '#^/policy_tranction#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
+ ],
+ '#^/bds_upload#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
+ ],
+
+ // ===================== REPORTS =====================
+ '#^/bdsReport#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
+ ],
+
+ // ===================== PAYOUT / COMMISSION =====================
+ '#^/payout#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
+ ],
+ '#^/commission#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [FINANCE_TEAM_ID,POS_TEAM_ID]
+ ],
+
+ // ===================== CLAIMS / TICKETS =====================
+ '#^/ticket#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [CLAIMS_TEAM_ID]
+ ],
+ '#^/claim_mis#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [CLAIMS_TEAM_ID]
+ ],
+
+ // ===================== LEADS / SALES =====================
+ '#^/leads#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [BUSINESS_SUPPORT_TEAM_ID, SALES_TEAM_ID]
+ ],
+ '#^/rfq#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID],
+ 'teams' => [SALES_TEAM_ID]
+ ],
+ '#^/sales#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID],
+ 'teams' => [SALES_TEAM_ID]
+ ],
+
+ // ===================== CMS / CONTENT =====================
+ '#^/add_image_index#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
+ 'teams' => []
+ ],
+ '#^/frontend_content#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
+ 'teams' => []
+ ],
+ '#^/FAQ#' => [
+ 'roles' => [ HEAD_ROLE_ID,ADMIN_ROLE_ID, MANAGER_ROLE_ID, ACCOUNT_MANAGER_ROLE_ID,STAFF_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== LOGS =====================
+ '#^/logs#' => [
+ 'roles' => [ADMIN_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== INTERNAL TEST =====================
+ '#^/test#' => [
+ 'roles' => [ADMIN_ROLE_ID],
+ 'teams' => []
+ ],
+
+ // ===================== API (JWT / SIGNED) =====================
+ '#^/api#' => ['public' => true],
+ '#^/employeeRest#' => ['public' => true],
+ '#^/clientApi#' => ['public' => true],
+
+ // ===================== WEBHOOKS / 3RD PARTY =====================
+ '#^/dispatchWebhookData#' => ['public' => true],
+ '#^/retrieveWebhookData#' => ['public' => true],
+ // '#^/ICICI#' => ['public' => true],
+ // '#^/Vidal#' => ['public' => true],
+ // '#^/MediAssist#' => ['public' => true],
+
+ // ===================== CLI =====================
+ '#^/cli/#' => ['public' => true],
+
+ // ===================== DEFAULT DENY (ZERO TRUST) =====================
+ '#^/#' => [
+ 'roles' => [ADMIN_ROLE_ID],
+ 'teams' => []
+ ],
+ ];
+}
diff --git a/app/Config/Filters.php b/app/Config/Filters.php
index d552bb46..7476309d 100755
--- a/app/Config/Filters.php
+++ b/app/Config/Filters.php
@@ -18,6 +18,7 @@ use App\Filters\VerifyAppSignature;
use App\Filters\Cors;
use App\Filters\SecurityInputFilter;
use App\Filters\GlobalPostFileUploadGuard;
+use App\Filters\AclFilter;
use App\Filters\AuthJWT;
@@ -47,6 +48,7 @@ class Filters extends BaseConfig
'Cors' => Cors::class,
'SecurityInputFilter' => SecurityInputFilter::class,
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
+ 'AclFilter' => AclFilter::class,
];
/**
@@ -60,6 +62,7 @@ class Filters extends BaseConfig
'before' => [
'HttpRequestLog' => ['except' => 'cli/*'],
'Cors',
+ 'AclFilter' => ['except' => 'login', 'logout', 'auth/*', 'oauth2callback', 'download-*', 'claim-form-download', 'claims-feedback-form', 'autobookstackLogin'],
'SecurityInputFilter',
'GlobalPostFileUploadGuard'
// 'csrf',
diff --git a/app/Filters/AclFilter.php b/app/Filters/AclFilter.php
new file mode 100644
index 00000000..0f6e0c17
--- /dev/null
+++ b/app/Filters/AclFilter.php
@@ -0,0 +1,156 @@
+getPath(), '/');
+
+ // Base path: /PHP828APPS/ruc/nhance
+ $basePath = rtrim(parse_url(base_url(), PHP_URL_PATH), '/');
+
+ // Remove base path
+ if ($basePath && str_starts_with($fullPath, $basePath)) {
+ $path = substr($fullPath, strlen($basePath));
+ } else {
+ $path = $fullPath;
+ }
+
+ // Remove index.php if present
+ if (str_starts_with($path, '/index.php')) {
+ $path = substr($path, strlen('/index.php'));
+ }
+
+ // Normalize
+ $path = '/' . ltrim($path, '/');
+
+ // Fallback
+ if ($path === '') {
+ $path = '/';
+ }
+
+ // echo'
BASH PATH: ' . base_url();
+ // echo'
ACL RAW PATH: ' . $fullPath;
+ // echo'
ACL BASE PATH: ' . $basePath;
+ // echo'
ACL FINAL PATH: ' . $path;
+ // ===================== LOAD ACL =====================
+ $acl = new Acl();
+ $rules = $acl->rules;
+ // print_rr($rules);die;
+ // ===================== MATCH RULE =====================
+ $matchedRule = null;
+
+ foreach ($rules as $pattern => $rule) {
+ // echo "$pattern".'---------
';
+ if (preg_match($pattern, $path)) {
+ // echo "matched - $pattern";
+ $matchedRule = $rule;
+ break; // FIRST MATCH WINS
+ }
+ }
+
+ // print_r($matchedRule);//die;
+ // ===================== NO RULE = DENY =====================
+ if ($matchedRule === null) {
+ return $this->deny($path, 'No ACL rule matched');
+ }
+
+ // ===================== PUBLIC ROUTE =====================
+ if (!empty($matchedRule['public'])) {
+ return; // ALLOW
+ }
+
+ // ===================== AUTH CHECK =====================
+ if (!check_session()) {
+ // For API requests return 401 JSON
+ if ($request->isAJAX() || str_starts_with($path, '/api') || str_starts_with($path, '/employeeRest')) {
+ return service('response')
+ ->setStatusCode(401)
+ ->setJSON(['error' => 'Unauthorized']);
+ }
+
+ // For web redirect to login
+ return AuthLogout::logout();
+ }
+
+ // ===================== GET USER CONTEXT =====================
+
+ $userRole = check_role(); //
+ $userTeams = user_team(); // must return array of TEAM IDs
+
+ $allowedRoles = $matchedRule['roles'] ?? [];
+ $allowedTeams = $matchedRule['teams'] ?? [];
+ // ===================== ROLE FIRST =====================
+ if (!empty($allowedRoles) && in_array((int)$userRole, $allowedRoles, true)) {
+ return; // ALLOW
+ }
+ // ===================== TEAM FALLBACK =====================
+ if (!empty($allowedTeams) && is_array($userTeams)) {
+ foreach ($userTeams as $teamId) {
+ if (in_array($teamId, $allowedTeams, true)) {
+ return; // ALLOW
+ }
+ }
+ }
+ // ===================== DENY =====================
+ return $this->deny($path, 'Role/Team not permitted');
+ }
+
+ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
+ {
+ // nothing
+ }
+
+ // ===================== DENY HANDLER =====================
+ protected function deny(string $path, string $reason)
+ {
+ log_message('error', 'ACL BLOCKED: {user} {path} - {reason}', [
+ 'user' => session()->get('userid') ?? 'guest',
+ 'path' => $path,
+ 'reason' => $reason,
+ ]);
+
+ // API / AJAX → JSON
+ $request = service('request');
+ if ($request->isAJAX() || str_starts_with($path, '/api') || str_starts_with($path, '/employeeRest')) {
+ return service('response')
+ ->setStatusCode(403)
+ ->setJSON([
+ 'error' => 'Forbidden',
+ 'message' => 'You do not have permission to access this resource'
+ ]);
+ }
+
+ $response = service('response');
+ $response->setStatusCode(403);
+ $response->setBody(view('errors/404', [
+ 'message' => '403 Access denied - You do not have permission to access this resource'
+ ]));
+
+ return $response;
+ // Web → nice 403 page or simple text
+ return service('response')
+ ->setStatusCode(403)
+ ->setBody('403 Forbidden - Access denied - You do not have permission to access this resource');
+ }
+}
diff --git a/app/Helpers/session_helper.php b/app/Helpers/session_helper.php
index 640de0e7..bd54744c 100755
--- a/app/Helpers/session_helper.php
+++ b/app/Helpers/session_helper.php
@@ -118,7 +118,8 @@ if (!function_exists('check_role')) {
{
// $ci =& get_instance();
$session = \Config\Services::session();
- return $session->get('role');
+ return $role_id = isset(get_session_userdata()->role) ? get_session_userdata()->role : null;
+
}
}
diff --git a/app/Libraries/AuthLogout.php b/app/Libraries/AuthLogout.php
index cfb10051..ac7786cb 100644
--- a/app/Libraries/AuthLogout.php
+++ b/app/Libraries/AuthLogout.php
@@ -22,7 +22,7 @@ class AuthLogout
setcookie(
session_name(), // DO NOT hardcode cookie name
- '',
+ null,
time() - 42000,
$params['path'],
$params['domain'],
diff --git a/app/Views/errors/404.php b/app/Views/errors/404.php
index f271c312..69c6d42e 100755
--- a/app/Views/errors/404.php
+++ b/app/Views/errors/404.php
@@ -162,7 +162,7 @@
