From 3ec11b23fa03fcfc307c163ab7456d1487e988f7 Mon Sep 17 00:00:00 2001 From: velz Date: Thu, 8 Jan 2026 16:05:35 +0530 Subject: [PATCH 01/11] FIX_SESSION_REPLAY --- app/Controllers/LoginController.php | 36 +++++++++++++------ app/Filters/AuthMVC.php | 23 ++++++++++-- app/Filters/Cors.php | 56 ++++++++++++++--------------- app/Filters/SecurityInputFilter.php | 1 + app/Helpers/session_helper.php | 6 ++-- app/Libraries/AuthLogout.php | 42 ++++++++++++++++++++++ public/.htaccess | 4 +-- 7 files changed, 122 insertions(+), 46 deletions(-) create mode 100644 app/Libraries/AuthLogout.php diff --git a/app/Controllers/LoginController.php b/app/Controllers/LoginController.php index 451765c2..779c788b 100755 --- a/app/Controllers/LoginController.php +++ b/app/Controllers/LoginController.php @@ -11,6 +11,8 @@ use CodeIgniter\API\ResponseTrait; use App\Models\UserModel; use App\Models\AuthHistoryModel; +use App\Libraries\AuthLogout; + class LoginController extends BaseController { use ResponseTrait; @@ -45,7 +47,7 @@ class LoginController extends BaseController $user_team = $UserModel->getUserTeamsByUserID($user->id); // dd($user_team); - + session()->regenerate(true); $session_data = [ 'isLoggedIn' => True , 'userid' => $user->id, @@ -56,9 +58,14 @@ class LoginController extends BaseController $path = getenv('cookie.Path'); $domain = getenv('cookie.Domain'); $https = getenv('ccokie.secure'); - setcookie('session_data', json_encode($session_data), time() + 12 * 60 * 60, $path, $domain, $https, true); + // setcookie('session_data', json_encode($session_data), time() + 12 * 60 * 60, $path, $domain, $https, true); set_session_data($session_data); + // Bind session to device + set_session_data(['fingerprint' => hash('sha256', + ($this->request->getUserAgent()->getAgentString() . '|' . ($this->request->getIPAddress() + )))]); + log_message('error', 'Set The UserId : `'. $user->id .'` in Session'); log_message('error', 'User Login Sucessfully'); @@ -91,14 +98,23 @@ class LoginController extends BaseController public function logout() { - $path = getenv('cookie.Path'); - session()->destroy(); - // setcookie('session_data', '', time() - 3600, $path); - $path = getenv('cookie.Path'); - $domain = getenv('cookie.Domain'); - $https = getenv('cookie.secure'); - setcookie('session_data',null, time() -3600, $path, $domain, $https, true); - return redirect()->to(base_url('login')); + // $path = getenv('cookie.Path'); + // session()->regenerate(true); + // session()->destroy(); + + // $path = getenv('cookie.Path'); + // $domain = getenv('cookie.Domain'); + // $https = getenv('cookie.secure'); + + // setcookie('session_data',null, time() - 42000, $path, $domain, $https, true); + // // return redirect()->to(base_url('login')); + // return redirect()->to(base_url('login')) + // ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + // ->setHeader('Pragma', 'no-cache') + // ->setHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); + + + return AuthLogout::logout(); } public function getUserDeviceInfo($userId, $type_of_user) diff --git a/app/Filters/AuthMVC.php b/app/Filters/AuthMVC.php index 1e0be969..efda2976 100755 --- a/app/Filters/AuthMVC.php +++ b/app/Filters/AuthMVC.php @@ -5,13 +5,30 @@ use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use App\Libraries\AuthLogout; + class AuthMVC implements FilterInterface { public function before(RequestInterface $request, $arguments = null) { - if (!check_session() && !check_cookie()) { - - return redirect()->to(base_url('/login')); + + if (!check_session()) + { + return AuthLogout::logout(); + } + + // if (!check_cookie()) + // { + // return AuthLogout::logout(); + // } + + // Fingerprint validation + $fp = hash('sha256', + $request->getUserAgent()->getAgentString() . '|' . $request->getIPAddress() + ); + + if (session()->get('fingerprint') !== $fp) { + return AuthLogout::logout(); } } diff --git a/app/Filters/Cors.php b/app/Filters/Cors.php index 6aae35d9..bb8d5b8b 100644 --- a/app/Filters/Cors.php +++ b/app/Filters/Cors.php @@ -143,7 +143,7 @@ class Cors implements FilterInterface // If wildcard present in configuration, allow any origin if (in_array('*', $this->allowedOrigins, true)) { - $this->log('Origin allowed: wildcard match', ['origin' => $origin]); + // $this->log('Origin allowed: wildcard match', ['origin' => $origin]); return true; } @@ -159,10 +159,10 @@ class Cors implements FilterInterface // 1. Exact match (including scheme and port) // Example: https://example.com matches https://example.com if (strcasecmp($allowed, $origin) === 0) { - $this->log('Origin allowed: exact match', [ - 'origin' => $origin, - 'matched_rule' => $allowed - ]); + // $this->log('Origin allowed: exact match', [ + // 'origin' => $origin, + // 'matched_rule' => $allowed + // ]); return true; } @@ -178,11 +178,11 @@ class Cors implements FilterInterface // Check if origin host ends with the allowed root domain if ($originHost === $allowedRoot || str_ends_with($originHost, '.' . $allowedRoot)) { - $this->log('Origin allowed: wildcard subdomain match', [ - 'origin' => $origin, - 'matched_rule' => $allowed, - 'origin_host' => $originHost - ]); + // $this->log('Origin allowed: wildcard subdomain match', [ + // 'origin' => $origin, + // 'matched_rule' => $allowed, + // 'origin_host' => $originHost + // ]); return true; } } @@ -191,11 +191,11 @@ class Cors implements FilterInterface // Example: example.com matches both http://example.com and https://example.com else { if (strcasecmp($allowed, $originHost) === 0) { - $this->log('Origin allowed: host match (scheme-less)', [ - 'origin' => $origin, - 'matched_rule' => $allowed, - 'origin_host' => $originHost - ]); + // $this->log('Origin allowed: host match (scheme-less)', [ + // 'origin' => $origin, + // 'matched_rule' => $allowed, + // 'origin_host' => $originHost + // ]); return true; } } @@ -320,11 +320,11 @@ class Cors implements FilterInterface // Preflight is sent by browsers before actual cross-origin requests // to check if the actual request is safe to send if ($method === 'OPTIONS') { - $this->log('Preflight request received', [ - 'origin' => $origin, - 'method' => $method, - 'uri' => (string) $request->getUri() - ]); + // $this->log('Preflight request received', [ + // 'origin' => $origin, + // 'method' => $method, + // 'uri' => (string) $request->getUri() + // ]); // Validate origin - reject if not allowed if (empty($origin) || !$this->isOriginAllowed($origin)) { @@ -346,10 +346,10 @@ class Cors implements FilterInterface $response->setStatusCode(204); $response->setBody(''); - $this->log('Preflight approved', [ - 'origin' => $origin, - 'allowed_methods' => $this->allowedMethods - ]); + // $this->log('Preflight approved', [ + // 'origin' => $origin, + // 'allowed_methods' => $this->allowedMethods + // ]); return $response; } @@ -392,10 +392,10 @@ class Cors implements FilterInterface // Add CORS headers to the response $this->addCorsHeaders($response, $request, $origin, false); - $this->log('CORS headers added to response', [ - 'origin' => $origin, - 'status' => $response->getStatusCode() - ]); + // $this->log('CORS headers added to response', [ + // 'origin' => $origin, + // 'status' => $response->getStatusCode() + // ]); } /** diff --git a/app/Filters/SecurityInputFilter.php b/app/Filters/SecurityInputFilter.php index 9ee24857..c23f7907 100644 --- a/app/Filters/SecurityInputFilter.php +++ b/app/Filters/SecurityInputFilter.php @@ -32,6 +32,7 @@ class SecurityInputFilter implements FilterInterface '/<\s*object\b/i', '/<\s*embed\b/i', '/<\s*applet\b/i', + '/<\s*img\b/i', // Image-based execution '/<\s*img\b[^>]*on\w+/i', diff --git a/app/Helpers/session_helper.php b/app/Helpers/session_helper.php index 31162658..640de0e7 100755 --- a/app/Helpers/session_helper.php +++ b/app/Helpers/session_helper.php @@ -21,7 +21,7 @@ if(!function_exists('check_cookie')){ 'userProfile' => $value['userProfile'], 'user_team' => $user_team, ]; - set_session_data($session_data); + // set_session_data($session_data); // $this->getUserDeviceInfo($user->id, 'NhanceUser'); // return redirect()->to(base_url('/dashboard/view')); return true; @@ -33,6 +33,7 @@ if(!function_exists('check_cookie')){ }else{ return false; } + } } if (!function_exists('check_session')) { @@ -40,7 +41,7 @@ if (!function_exists('check_session')) { { // $ci =& get_instance(); $session = \Config\Services::session(); - return $session->get('isLoggedIn'); + return $session->get('isLoggedIn') === true; } } @@ -201,7 +202,6 @@ if (!function_exists('get_chatbot_session_info')) { } -} diff --git a/app/Libraries/AuthLogout.php b/app/Libraries/AuthLogout.php new file mode 100644 index 00000000..cfb10051 --- /dev/null +++ b/app/Libraries/AuthLogout.php @@ -0,0 +1,42 @@ +regenerate(true); + + // Destroy CI session + $session->destroy(); + + // Kill PHP session cookie safely + if (ini_get('session.use_cookies')) { + $params = session_get_cookie_params(); + + setcookie( + session_name(), // DO NOT hardcode cookie name + '', + time() - 42000, + $params['path'], + $params['domain'], + $params['secure'], + $params['httponly'] + ); + } + + session_write_close(); + + // Redirect with anti-cache headers + return redirect()->to(base_url('login')) + ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->setHeader('Pragma', 'no-cache') + ->setHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); + } +} diff --git a/public/.htaccess b/public/.htaccess index c8b54695..f82f3842 100755 --- a/public/.htaccess +++ b/public/.htaccess @@ -6,7 +6,7 @@ Options -Indexes # ---------------------------------------------------------------------- ## ADDED for - block any script execution inside folder of public - + Deny from all # Disable PHP engine @@ -18,7 +18,7 @@ Options -Indexes AddHandler cgi-script .php .pl .py .jsp .asp .sh .cgi # Block access to any script-like files entirely - + ForceType text/plain #Order allow,deny Deny from all From 84d99bf726b670efae7660f03b0e2b87699c8b86 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Thu, 8 Jan 2026 16:56:43 +0530 Subject: [PATCH 02/11] GWM :VAPT Scan Vulnerabilities --- app/Config/Routes.php | 4 +- app/Controllers/ClaimsUploadController.php | 48 ++++++ app/Controllers/FhplApiController.php | 73 ++++++++ .../RestAuthenticationController.php | 74 ++++++++ app/Filters/AuthJWT.php | 162 ++++++++++++------ app/Helpers/JWTToken.php | 92 +++++++--- app/Helpers/utility_helper.php | 26 +++ 7 files changed, 403 insertions(+), 76 deletions(-) create mode 100644 app/Controllers/ClaimsUploadController.php create mode 100644 app/Controllers/FhplApiController.php diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 531d5836..dfa43a0a 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -600,6 +600,8 @@ $routes->group("employeeRest", ['filter' => ['appSignature'] ], function ($route $routes->group("employeeRest", ["filter" => ['appSignature' , 'authJWT']], function ($routes) { + $routes->post('logout', 'RestAuthenticationController::logout'); + $routes->post("ecardRequest", "ApiServiceController::ecardRequest"); $routes->get("getWellnessURL", "ApiServiceController::getWellnessURL"); @@ -754,7 +756,7 @@ $routes->post("retrieveWebhookDataClaim","ClientWebHooksController::pullData_cla //Third party ICICILombard Api Call -$routes->get('generateAuthToken','ICICILombardController::generateAuthToken'); +$routes->get('generateAuthToken','FhplApiController::generateAuthToken'); $routes->get('createEnrollmentBatch','ICICILombardController::createEnrollmentBatch'); $routes->get('getEnrollmentBatchStatus','ICICILombardController::getEnrollmentBatchStatus'); $routes->get('fetchUHIDDetails','ICICILombardController::fetchUHIDDetails'); diff --git a/app/Controllers/ClaimsUploadController.php b/app/Controllers/ClaimsUploadController.php new file mode 100644 index 00000000..02c9a187 --- /dev/null +++ b/app/Controllers/ClaimsUploadController.php @@ -0,0 +1,48 @@ +db = \Config\Database::connect(); + } + + public function uploadDump() + { + $file = $this->request->getFile('file'); + + if (!$file || !$file->isValid()) { + return $this->respond([ + 'status' => 'failed', + 'message' => 'Invalid file' + ], 400); + } + + $data = [ + 'client_id' => $this->request->getPost('client_id'), + 'tpa_id' => $this->request->getPost('tpa_id'), + 'client_policy_id' => $this->request->getPost('client_policy_id'), + 'from_date' => $this->request->getPost('from_date'), + 'to_date' => $this->request->getPost('to_date'), + 'upload_file' => $file->getRandomName(), + // 'uploaded_by' => user_id() + ]; + + $file->move(WRITEPATH . 'uploads/claims_dump', $data['upload_file']); + + $this->db->table('claims_dump_uploads')->insert($data); + + return $this->respond([ + 'status' => 'success', + 'message' => 'Claims dump uploaded' + ]); + } +} diff --git a/app/Controllers/FhplApiController.php b/app/Controllers/FhplApiController.php new file mode 100644 index 00000000..33037652 --- /dev/null +++ b/app/Controllers/FhplApiController.php @@ -0,0 +1,73 @@ +db = \Config\Database::connect(); + $this->fhplTpaId = getenv('FHPL_PRIMARY_KEY_CONSTANT'); + } + + public function generateAuthToken() + { + $url = env('FHPL_TOKEN_URL'); // example: https://uat.fhpl.net/token + + // x-www-form-urlencoded body + $postData = http_build_query([ + 'UserName' => 'TestApi@fhpl', + 'Password' => 'Fhpl@12345', + 'grant_type' => 'password', + ]); + + $ch = curl_init(); + + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => 'GET', // SAME AS POSTMAN + CURLOPT_POSTFIELDS => $postData, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/x-www-form-urlencoded', + 'Accept: application/json', + ], + CURLOPT_TIMEOUT => 30, + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (curl_errno($ch)) { + return $this->response->setJSON([ + 'status' => false, + 'error' => curl_error($ch), + ]); + } + + curl_close($ch); + + return $this->response->setJSON([ + 'status' => $httpCode === 200, + 'http_code' => $httpCode, + 'response' => json_decode($response, true), + ]); + } + + +} diff --git a/app/Controllers/RestAuthenticationController.php b/app/Controllers/RestAuthenticationController.php index bad20540..50dbd80f 100755 --- a/app/Controllers/RestAuthenticationController.php +++ b/app/Controllers/RestAuthenticationController.php @@ -110,6 +110,13 @@ class RestAuthenticationController extends AdminController if (isset($employeeData['employee_id'])) { + + //check the resend otp (with in 60 seconds don't allow another otp to send) + $check = canSendOtp($employeeData); + if (!$check['allowed']) { + return $this->respond(['status' => false,'message' => 'OTP already sent. Please wait before retrying.','retry_after_seconds' => $check['retry_after'] ])->setStatusCode(429); // Too Many Requests + } + $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithMobileNumber: Employee verified with ID = " . $employeeData['employee_id']); log_message('error', ' '); log_message('error', '************************ POST END ********************************'); @@ -217,6 +224,12 @@ class RestAuthenticationController extends AdminController if (isset($employeeData['employee_id'])) { + //check the resend otp (with in 60 seconds don't allow another otp to send) + $check = canSendOtp($employeeData); + if (!$check['allowed']) { + return $this->respond(['status' => false,'message' => 'OTP already sent. Please wait before retrying.','retry_after_seconds' => $check['retry_after'] ])->setStatusCode(429); // Too Many Requests + } + $builder = $this->employeeModel ->where('email_corporate', $email) @@ -518,6 +531,12 @@ class RestAuthenticationController extends AdminController if ($HrData) { + + //check the resend otp (with in 60 seconds don't allow another otp to send) + $check = canSendOtp($HrData); + if (!$check['allowed']) { + return $this->respond(['status' => false,'message' => 'OTP already sent. Please wait before retrying.','retry_after_seconds' => $check['retry_after'] ])->setStatusCode(429); // Too Many Requests + } $sql = "UPDATE level_contacts SET otp = ? WHERE mobile = ? AND contact_type = 'client' AND is_active = 1"; @@ -569,6 +588,12 @@ class RestAuthenticationController extends AdminController if ($HrData) { + //check the resend otp (with in 60 seconds don't allow another otp to send) + $check = canSendOtp($HrData); + if (!$check['allowed']) { + return $this->respond(['status' => false,'message' => 'OTP already sent. Please wait before retrying.','retry_after_seconds' => $check['retry_after'] ])->setStatusCode(429); // Too Many Requests + } + $sql = " UPDATE level_contacts SET otp = ? @@ -2289,4 +2314,53 @@ class RestAuthenticationController extends AdminController } } + public function logout() + { + $authHeader = $this->request->getHeaderLine('Authorization'); + + if (!$authHeader) { + return $this->respond([ + 'status' => false, + 'message' => 'Authorization token missing' + ], 401); + } + + // Validate JWT (your hardened function) + $result = JWTToken::validateJWT($authHeader); + + if ($result['status'] !== true) { + return $this->respond([ + 'status' => false, + 'message' => 'Invalid or expired token' + ], 401); + } + + $decoded = $result['decoded']; + $userId = $decoded['id'] ?? null; + + if (!$userId) { + return $this->respond([ + 'status' => false, + 'message' => 'Invalid token payload' + ], 401); + } + + // Identify user type + if (isset($decoded['emp_code'])) { + $model = new EmployeeModel(); + } else { + $model = new LevelContactModel(); + } + + // Invalidate token server-side + $model->update($userId, [ + 'token_time_out' => null + ]); + + return $this->respond([ + 'status' => true, + 'message' => 'Logged out successfully' + ], 200); + } + } \ No newline at end of file diff --git a/app/Filters/AuthJWT.php b/app/Filters/AuthJWT.php index 484372a7..63ecf655 100755 --- a/app/Filters/AuthJWT.php +++ b/app/Filters/AuthJWT.php @@ -18,67 +18,119 @@ use App\Models\LevelContactModel; class AuthJWT implements FilterInterface { + // public function before(RequestInterface $request, $arguments = null) + // { + // $jwt = $request->getHeader('Authorization'); + + // if ($jwt) { + // if (JWTToken::validateJWT($jwt)) { + // $data = JWTToken::validateJWT($jwt); + // $data = json_decode($data); + + // $id = $data->decoded->id; + // if(isset($data->decoded->emp_code)){ + // $model = new EmployeeModel(); + // $user_data = $model->where('id', $id)->first(); + + // if($user_data['token_time_out'] > time()){ + // $data =["token_time_out" => time() + getenv('TOKENTIMEOUT') ]; + // $model->update($id, $data); + // return true; + // }else{ + + // // if($user_data['token_time_out'] != "" && $user_data['token_time_out'] != NULL) + + // $data =["token_time_out" => '']; + // $model->update($id, $data); + // header('Content-Type: application/json'); + // http_response_code(401); + // // $error = json_encode(["status" => 401, "message" => $data->message]); + // $error = json_encode(["status" => 401, "message" => "Token is Invalid"]); + // echo $error; + // exit; + // } + // }else{ + // $model = new LevelContactModel(); + // $hr_data = $model->where('id', $id)->first(); + + // if($hr_data['token_time_out'] > time()){ + // $data =["token_time_out" => time() + getenv('TOKENTIMEOUT') ]; + // $model->update($id, $data); + // return true; + // }else{ + // $data =["token_time_out" => '']; + // $model->update($id, $data); + // header('Content-Type: application/json'); + // http_response_code(401); + // // $error = json_encode(["status" => 401, "message" => $data->message]); + // $error = json_encode(["status" => 401, "message" => "Token is Invalid"]); + // echo $error; + // exit; + // } + // } + + // } + // } else { + // header('Content-Type: application/json'); + // http_response_code(403); + // $error = json_encode(["status" => 403, "message" => "Access Forbidden!"]); + // echo $error; + // exit(); + // } + // } + public function before(RequestInterface $request, $arguments = null) { - $jwt = $request->getHeader('Authorization'); + $authHeader = $request->getHeaderLine('Authorization'); - if ($jwt) { - if (JWTToken::validateJWT($jwt)) { - $data = JWTToken::validateJWT($jwt); - $data = json_decode($data); - - $id = $data->decoded->id; - if(isset($data->decoded->emp_code)){ - $model = new EmployeeModel(); - $user_data = $model->where('id', $id)->first(); - - if($user_data['token_time_out'] > time()){ - $data =["token_time_out" => time() + getenv('TOKENTIMEOUT') ]; - $model->update($id, $data); - return true; - }else{ - - // if($user_data['token_time_out'] != "" && $user_data['token_time_out'] != NULL) - - $data =["token_time_out" => '']; - $model->update($id, $data); - header('Content-Type: application/json'); - http_response_code(401); - // $error = json_encode(["status" => 401, "message" => $data->message]); - $error = json_encode(["status" => 401, "message" => "Token is Invalid"]); - echo $error; - exit; - } - }else{ - $model = new LevelContactModel(); - $hr_data = $model->where('id', $id)->first(); - - if($hr_data['token_time_out'] > time()){ - $data =["token_time_out" => time() + getenv('TOKENTIMEOUT') ]; - $model->update($id, $data); - return true; - }else{ - $data =["token_time_out" => '']; - $model->update($id, $data); - header('Content-Type: application/json'); - http_response_code(401); - // $error = json_encode(["status" => 401, "message" => $data->message]); - $error = json_encode(["status" => 401, "message" => "Token is Invalid"]); - echo $error; - exit; - } - } - - } - } else { - header('Content-Type: application/json'); - http_response_code(403); - $error = json_encode(["status" => 403, "message" => "Access Forbidden!"]); - echo $error; - exit(); + if (!$authHeader) { + return $this->reject(403, 'Access Forbidden'); } + + $result = JWTToken::validateJWT($authHeader); + + if ($result['status'] !== true) { + return $this->reject(401, $result['message']); + } + + $decoded = $result['decoded']; + $id = $decoded['id'] ?? null; + + if (!$id) { + return $this->reject(401, 'Invalid token payload'); + } + + if (isset($decoded['emp_code'])) { + $model = new EmployeeModel(); + } else { + $model = new LevelContactModel(); + } + + $user = $model->find($id); + + if (!$user || $user['token_time_out'] <= time()) { + $model->update($id, ['token_time_out' => null]); + return $this->reject(401, 'Token expired'); + } + + // Refresh sliding expiration + $model->update($id, [ + 'token_time_out' => time() + getenv('TOKENTIMEOUT') + ]); + + return true; } + private function reject(int $code, string $message) + { + return service('response') + ->setStatusCode($code) + ->setJSON(['status' => $code, 'message' => $message]) + ->send(); + } + + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { // Do something here after the response is sent diff --git a/app/Helpers/JWTToken.php b/app/Helpers/JWTToken.php index 41d76b5c..73978ddb 100755 --- a/app/Helpers/JWTToken.php +++ b/app/Helpers/JWTToken.php @@ -18,9 +18,12 @@ use App\Models\LevelContactModel; class JWTToken { + + private const ALLOWED_ALG = 'HS512'; + public static function encode($data =null) { - $secret_Key ="secret"; + $secret_Key = env('JWT_SECRET'); $request_data = (array)$data; @@ -47,30 +50,79 @@ class JWTToken } } - public static function validateJWT($jwt) + // public static function validateJWT($jwt) + // { + // $jwtParts = explode(' ', $jwt); + + // // print_r($jwtParts); + // if (count($jwtParts) != 2 || $jwtParts[0] == 'Bearer') { + // return false; + // } + + // $token = $jwtParts[1]; + + // try { + // $decoded = JWT::decode($token, new Key(env('JWT_SECRET'), 'HS512')); + // return json_encode(['status' => true, 'message' => 'Token is valid', 'decoded' => (array) $decoded]); + // } catch (ExpiredException $e) { + // return json_encode(['status' => false, 'message' => 'Token has expired']); + // } catch (BeforeValidException $e) { + // return json_encode(['status' => false, 'message' => 'Token is not yet valid']); + // } catch (SignatureInvalidException $e) { + // return json_encode(['status' => false, 'message' => 'Token signature is invalid']); + // } catch (\Exception $e) { + // return json_encode(['status' => false, 'message' => 'An error occurred while decoding the token']); + // } + + // } + + public static function validateJWT(string $authHeader) { - $jwtParts = explode(' ', $jwt); + // 1️⃣ Validate Authorization header + if (!preg_match('/^Bearer\s(\S+)$/', $authHeader, $matches)) { + return ['status' => false, 'message' => 'Invalid Authorization header']; + } - // print_r($jwtParts); - if (count($jwtParts) != 2 || $jwtParts[0] == 'Bearer') { - return false; - } + $token = $matches[1]; - $token = $jwtParts[1]; + // 2️⃣ Decode JWT header manually + $jwtParts = explode('.', $token); + if (count($jwtParts) !== 3) { + return ['status' => false, 'message' => 'Malformed JWT']; + } - try { - $decoded = JWT::decode($token, new Key("secret", 'HS512')); - return json_encode(['status' => true, 'message' => 'Token is valid', 'decoded' => (array) $decoded]); - } catch (ExpiredException $e) { - return json_encode(['status' => false, 'message' => 'Token has expired']); - } catch (BeforeValidException $e) { - return json_encode(['status' => false, 'message' => 'Token is not yet valid']); - } catch (SignatureInvalidException $e) { - return json_encode(['status' => false, 'message' => 'Token signature is invalid']); - } catch (\Exception $e) { - return json_encode(['status' => false, 'message' => 'An error occurred while decoding the token']); - } + $header = json_decode(base64_decode(strtr($jwtParts[0], '-_', '+/')), true); + // 3️⃣ Reject missing or NONE algorithm + if ( + empty($header['alg']) || + $header['alg'] === 'none' || + $header['alg'] !== self::ALLOWED_ALG + ) { + return ['status' => false, 'message' => 'Invalid or unsupported JWT algorithm']; + } + + // 4️⃣ Enforce signature validation + try { + $decoded = JWT::decode( + $token, + new Key(env('JWT_SECRET'), self::ALLOWED_ALG) + ); + + return [ + 'status' => true, + 'decoded' => (array) $decoded + ]; + + } catch (ExpiredException $e) { + return ['status' => false, 'message' => 'Token expired']; + } catch (BeforeValidException $e) { + return ['status' => false, 'message' => 'Token not yet valid']; + } catch (SignatureInvalidException $e) { + return ['status' => false, 'message' => 'Invalid token signature']; + } catch (\Exception $e) { + return ['status' => false, 'message' => 'Token validation failed']; + } } diff --git a/app/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 1585b803..fe7898e3 100755 --- a/app/Helpers/utility_helper.php +++ b/app/Helpers/utility_helper.php @@ -978,3 +978,29 @@ if (!function_exists('generate_ecard_download_link_based_on_tpa')) { } } +if (!function_exists('canSendOtp')) { + function canSendOtp(array $row, int $limitSeconds = 60): array + { + // If OTP does not exist → allow + if (empty($row['otp']) || empty($row['updated_at'])) { + return ['allowed' => true]; + } + + $lastUpdated = strtotime($row['updated_at']); + $currentTime = time(); + + // Calculate expiry time + $allowedAfter = $lastUpdated + $limitSeconds; + + // If still within limit → block + if ($currentTime < $allowedAfter) { + return [ + 'allowed' => false, + 'retry_after' => $allowedAfter - $currentTime + ]; + } + + return ['allowed' => true]; + } +} + From 1c112b630bd5149d10fcca3e16a2fddd7552f634 Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Thu, 8 Jan 2026 18:04:48 +0530 Subject: [PATCH 03/11] GWM : token expiry issue in hr --- app/Filters/AuthJWT.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Filters/AuthJWT.php b/app/Filters/AuthJWT.php index 63ecf655..ec34d902 100755 --- a/app/Filters/AuthJWT.php +++ b/app/Filters/AuthJWT.php @@ -104,6 +104,7 @@ class AuthJWT implements FilterInterface $model = new EmployeeModel(); } else { $model = new LevelContactModel(); + $id = $decoded['post_hr_id'] ?? null; } $user = $model->find($id); From b078bfe837b87851ea133db3ec719724144e3e64 Mon Sep 17 00:00:00 2001 From: velz Date: Fri, 9 Jan 2026 15:59:37 +0530 Subject: [PATCH 04/11] FEAT_RBAC --- .env.sample | 2 + app/Config/Acl.php | 157 +++++++++++++++++++++++++++++++++ app/Config/Filters.php | 3 + app/Filters/AclFilter.php | 156 ++++++++++++++++++++++++++++++++ app/Helpers/session_helper.php | 3 +- app/Libraries/AuthLogout.php | 2 +- app/Views/errors/404.php | 2 +- 7 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 app/Config/Acl.php create mode 100644 app/Filters/AclFilter.php 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 @@
-
+

From 20d63f09cd8d5e609942a75f1c41b1a5e2c234bb Mon Sep 17 00:00:00 2001 From: velz Date: Fri, 9 Jan 2026 18:38:12 +0530 Subject: [PATCH 05/11] FIX_VALIDATE_BUSI_INPUT_VALUES --- app/Config/Autoload.php | 2 +- app/Controllers/ClientController.php | 110 ++++++++++++++++-- .../sanitizeInputArrayAdvanced_helper.php | 84 +++++++++++++ 3 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 app/Helpers/sanitizeInputArrayAdvanced_helper.php diff --git a/app/Config/Autoload.php b/app/Config/Autoload.php index 62b24b8d..58edbca1 100755 --- a/app/Config/Autoload.php +++ b/app/Config/Autoload.php @@ -101,6 +101,6 @@ class Autoload extends AutoloadConfig * @phpstan-var list */ public $helpers = ['uuid','session','utility', 'form', 'url', 'oauth', 'fileupload', - 'excel_import_export', 'file', 'drive','ExcelSanitizeHelper', 'api_helper','exception','sms_helper' + 'excel_import_export', 'file', 'drive','ExcelSanitizeHelper', 'api_helper','exception','sms_helper','sanitizeInputArrayAdvanced' ]; } diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index 0b6470bc..c5900ca0 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -736,14 +736,102 @@ class ClientController extends AdminController public function saveDeposit() { + + $rules = [ + + 'amount' => [ + 'rules' => 'required|numeric|greater_than_equal_to[0]', + 'errors' => [ + 'required' => 'Amount is required', + 'numeric' => 'Amount must be a valid number', + 'greater_than_equal_to' => 'Amount cannot be negative', + ] + ], + + 'client_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Client ID is required', + 'is_natural_no_zero' => 'Client ID must be a positive integer', + ] + ], + + 'insurer_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Insurer ID is required', + 'is_natural_no_zero' => 'Insurer ID must be a positive integer', + ] + ], + + 'cd_ac_pk' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Account PK is required', + 'is_natural_no_zero' => 'Account PK must be a positive integer', + ] + ], + + 'cd_ac_no' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Account number is required', + 'is_natural_no_zero' => 'must be a positive integer', + ] + ], + + 'sub_type_id' => [ + 'rules' => 'required|is_natural_no_zero', + 'errors' => [ + 'required' => 'Sub type ID is required', + 'is_natural_no_zero' => 'Sub type ID must be a positive integer', + ] + ], + + 'description' => [ + 'rules' => 'required|string|min_length[3]|max_length[255]', + 'errors' => [ + 'required' => 'Description is required', + 'string' => 'Description must be text', + 'min_length' => 'Description must be at least 3 characters', + 'max_length' => 'Description must not exceed 255 characters', + ] + ], + + 'transaction_type' => [ + 'rules' => 'required|in_list[Credit,Debit]', + 'errors' => [ + 'required' => 'Transaction type is required', + 'in_list' => 'Transaction type must be either credit or debit', + ] + ], + + ]; + + + if (! $this->validate($rules)) { + return $this->response + ->setStatusCode(400) + ->setJSON([ + 'status' => 'error', + 'message' => 'Input validation failed', + 'errors' => $this->validator->getErrors() + ]); + } + + //sanitize the post params + $post_data = $this->request->getPost(); + $sanitized_post_data = sanitizeInputArrayAdvanced($post_data); // Retrieve form data from POST request $loggedInUserID = get_session_userid(); + // print_rr($sanitized_post_data);die(); + $client_id = $sanitized_post_data['client_id']; + $insurer_id = $sanitized_post_data['insurer_id']; + $record_date = $sanitized_post_data['record_date']; + $cd_ac_pk = $sanitized_post_data['cd_ac_pk']; + $cd_ac_no = $sanitized_post_data['cd_ac_no']; - $client_id = $this->request->getPost('client_id'); - $insurer_id = $this->request->getPost('insurer_id'); - $record_date = $this->request->getPost('record_date'); - $cd_ac_pk = $this->request->getPost('cd_ac_pk'); - $cd_ac_no = $this->request->getPost('cd_ac_no'); + // $CD_Account_Number = $this->CDMasterModel // ->where('client_id', $client_id) @@ -761,16 +849,16 @@ class ClientController extends AdminController } $data = [ - 'amount' => $this->request->getPost('amount'), - 'sub_type_id' => $this->request->getPost('sub_type_id'), - 'client_id' => $this->request->getPost('client_id'), + 'amount' => $sanitized_post_data['amount'], + 'sub_type_id' => $sanitized_post_data['sub_type_id'], + 'client_id' => $sanitized_post_data['client_id'], 'client_policy_id' => null, 'cd_ac_no' => $cd_ac_no ?? null, 'cd_ac_pk' => $cd_ac_pk ?? null, 'endorsement_no' => null, - 'insurer_id' => $this->request->getPost('insurer_id'), - 'description' => $this->request->getPost('description'), - 'transaction_type' => $this->request->getPost('transaction_type') ?: 'Credit', + 'insurer_id' => $sanitized_post_data['insurer_id'], + 'description' => $sanitized_post_data['description'], + 'transaction_type' => $sanitized_post_data['transaction_type'] ?: 'Credit', 'updated_by' => 1, 'record_date' => $record_date ]; diff --git a/app/Helpers/sanitizeInputArrayAdvanced_helper.php b/app/Helpers/sanitizeInputArrayAdvanced_helper.php new file mode 100644 index 00000000..79d7df9b --- /dev/null +++ b/app/Helpers/sanitizeInputArrayAdvanced_helper.php @@ -0,0 +1,84 @@ + $v) { + + if (is_array($v)) { + $data[$k] = sanitizeInputArrayAdvanced($v, $htmlAllowedFields); + continue; + } + + if (!is_string($v)) { + continue; + } + + // 1. Unicode normalization (prevents homoglyph attacks) + if (class_exists('Normalizer')) { + $v = \Normalizer::normalize($v, \Normalizer::FORM_C); + } + + // 2. Remove NULL bytes & control chars + $v = preg_replace('/[\x00-\x1F\x7F]/u', '', $v); + + // 3. Remove invisible unicode chars (zero width, etc) + $v = preg_replace('/[\x{200B}-\x{200F}\x{202A}-\x{202E}\x{2060}-\x{206F}]/u', '', $v); + + // 4. Decode HTML entities (so hidden payloads are exposed) + $v = html_entity_decode($v, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + // 5. Trim + $v = trim($v); + + // 6. If this field is NOT allowed to contain HTML → strip aggressively + if (!in_array($k, $htmlAllowedFields, true)) { + + // Remove all tags + $v = strip_tags($v); + + // Kill any leftover JS protocol + $v = preg_replace('/(javascript:|data:|vbscript:)/i', '', $v); + + } else { + // This is HTML-allowed field → run HTML sanitizer + $v = sanitizeTrustedHtml($v); + } + + $data[$k] = $v; + } + + return $data; +} + +function sanitizeTrustedHtml(string $html): string +{ + // Allowed tags for email templates + $allowedTags = '