diff --git a/.env.sample b/.env.sample index 36880c7..c302d13 100644 --- a/.env.sample +++ b/.env.sample @@ -183,3 +183,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 0000000..1fbea05 --- /dev/null +++ b/app/Config/Acl.php @@ -0,0 +1,85 @@ + ['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' => [ HEAD_ROLE_ID,ADMIN_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' => [] + ], + + // ===================== 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], + // ===================== 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 76924a2..96d34a1 100755 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -16,6 +16,14 @@ use App\Filters\VerifyAppSignature; use App\Filters\AuthJWT; use App\Filters\Cors; +use App\Filters\GlobalPostFileUploadGuard; +use App\Filters\SecurityInputFilter; +use App\Filters\AclFilter; +use App\Filters\RateLimitFilter; +use App\Filters\JwtApiRateLimitFilter; +use App\Filters\AuthApiRateLimitFilter; + + class Filters extends BaseConfig { @@ -39,6 +47,12 @@ class Filters extends BaseConfig 'CloseDbConnection' => CloseDbConnection::class, 'Cors' => Cors::class, 'appSignature' => VerifyAppSignature::class, + 'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class, + 'SecurityInputFilter' => SecurityInputFilter::class, + 'AclFilter' => AclFilter::class, + 'ratelimit' => RateLimitFilter::class, + 'AuthApiRateLimitFilter' => AuthApiRateLimitFilter::class, + 'JwtApiRateLimitFilter' => JwtApiRateLimitFilter::class, ]; @@ -52,8 +66,10 @@ class Filters extends BaseConfig public array $globals = [ 'before' => [ 'HttpRequestLog' => ['except' => 'cli/*'], + 'AclFilter' => ['except' => ['login', 'logout', 'auth/*', 'oauth2callback','claim-form-download', 'claims-feedback-form', 'autobookstackLogin','/employeeRest/*','processjob', 'getPreEmployeePolicyCount']], 'Cors', - // 'csrf', + 'SecurityInputFilter' => ['except' => ['/client/notification/create','test_mail'] ], + 'GlobalPostFileUploadGuard', // 'invalidchars', ], 'after' => [ diff --git a/app/Config/RateLimiter.php b/app/Config/RateLimiter.php new file mode 100644 index 0000000..99d8b60 --- /dev/null +++ b/app/Config/RateLimiter.php @@ -0,0 +1,89 @@ + 60, // max requests + 'window' => 60, // window in seconds + 'violation_soft' => 3, // violations before soft block + ]; + + /* + |-------------------------------------------------------------------------- + | Auth API Routes (verifyMobile, verifyOTP, etc.) + |-------------------------------------------------------------------------- + */ + public array $authApi = [ + 'limit' => 10, // max requests per window + 'window' => 180, // window in seconds (3 min) + 'violation_soft' => 3, // failed attempts before soft block + ]; + + /* + |-------------------------------------------------------------------------- + | User-Level Progressive Block Durations (seconds) + | 0 = permanent until manual unblock + |-------------------------------------------------------------------------- + */ + public array $userBlock = [ + 'soft_duration' => 0, // permanent, manual unblock only + 'medium_duration' => 7200, // 2 hours + 'hard_duration' => 86400, // 24 hours + // attempts while at a block level before escalating to next + 'medium_trigger' => 1, // attempts during soft → medium + 'hard_trigger' => 1, // attempts during medium → hard + ]; + + /* + |-------------------------------------------------------------------------- + | IP-Level Throttle & Progressive Block (independent of user) + |-------------------------------------------------------------------------- + */ + public array $ipBlock = [ + 'limit' => 120, // max requests per window + 'window' => 60, // window in seconds + 'violation_soft' => 5, // violations before soft block + 'soft_duration' => 0, // permanent, manual unblock only + 'medium_duration' => 7200, // 2 hours + 'hard_duration' => 86400, // 24 hours + 'medium_trigger' => 1, // attempts during soft → medium + 'hard_trigger' => 1, // attempts during medium → hard + ]; + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefixes + |-------------------------------------------------------------------------- + */ + public array $cacheKeys = [ + 'ip_count' => 'rl_ip_count_', + 'ip_violations' => 'rl_ip_viol_', + 'ip_block' => 'rl_ip_block_', + 'ip_block_hits' => 'rl_ip_blkhit_', + 'user_count' => 'rl_usr_count_', + 'user_violations' => 'rl_usr_viol_', + 'user_block' => 'rl_usr_block_', + 'user_block_hits' => 'rl_usr_blkhit_', + ]; + + /* + |-------------------------------------------------------------------------- + | HTTP Status Codes per block level + |-------------------------------------------------------------------------- + */ + public array $statusCodes = [ + 'throttle' => 429, + 'soft' => 429, + 'medium' => 403, + 'hard' => 451, + ]; +} diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 6a9c84e..cb4c782 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -442,7 +442,7 @@ $routes->cli('cli/check_env', 'MasterController::checkEnv'); -$routes->group("/api", ["filter" => "authJWT"], function ($routes) { +$routes->group("/api", ["filter" => [ 'ratelimit' , 'authJWT' ] ], function ($routes) { $routes->post("logined", "RestAuthenticationController::logined"); $routes->post("getId", "RestAuthenticationController::getUserIdFromToken"); }); @@ -453,8 +453,10 @@ $routes->group("/api", ["filter" => "authJWT"], function ($routes) { // $routes->post("employeeRest/createOrUpdateEmployeePolicySiAmount", "EmployeeRestController::createOrUpdateEmployeePolicySiAmount"); // $routes->post("employeeRest/calculatePremium", "EmployeeRestController::calculatePremium"); + // $routes->post("updateMpin", "RestAuthenticationController::updateMpin"); +$routes->group("employeeRest", ['filter' => [ 'GlobalPostFileUploadGuard', 'appSignature' , 'authJWT','JwtApiRateLimitFilter' ] ], function ($routes) { -$routes->group("employeeRest", ['filter' => ['appSignature' , 'authJWT'] ], function ($routes) { + $routes->post('logout', 'RestAuthenticationController::logout'); // $routes->post("getVerifiedUserData", "RestAuthenticationController::getVerifiedUserData"); @@ -501,10 +503,11 @@ $routes->group("employeeRest", ['filter' => ['appSignature' , 'authJWT'] ], func $routes->post("hrFileUpload", "EmployeeRestController::hrFileUpload"); $routes->get("copyActiveEmployeeAndDependentDetails", "EmployeeRestController::copyActiveEmployeeAndDependentDetails"); + $routes->get("getExcelFileErrors/(:any)", "EmployeeController::getExcelFileErrors/$1"); }); -$routes->group("employeeRest", ['filter' => ['appSignature'] ], function ($routes) { +$routes->group("employeeRest", ['filter' => ['appSignature','AuthApiRateLimitFilter'] ], function ($routes) { //Employee login api's $routes->post("verifyEmployeeNumber", "RestAuthenticationController::verifyEmployeeWithMobileNumber"); @@ -537,7 +540,7 @@ $routes->group("employeeRest", ['filter' => ['appSignature'] ], function ($route }); -$routes->post("getPreEmployeePolicyCount","EmployeeRestController::getPreEmployeePolicyCount", ['filter' => ['appSignature']]); +$routes->post("getPreEmployeePolicyCount","EmployeeRestController::getPreEmployeePolicyCount", ['filter' => ['ratelimit','appSignature']]); $routes->get("getEmployeeActiveOrInactivePolicy", "EmployeeRestController::getEmployeeActiveOrInactivePolicy"); $routes->get("sendPushNotification", "EmployeeRestController::sendPushNotification"); @@ -549,7 +552,7 @@ $routes->get("getBackToEnrolledDetails", "EmployeeRestController::getBackToEnrol //crone job $routes->cli('cli/enrollOpendAndClose', 'DashboardController::updatePolicyEnrollmentStatus'); $routes->cli("cli/sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail"); - +$routes->cli('cli/update-emp-policy-status', 'ClientController::updateEmpAndPolicyStatus'); $routes->get('enrollOpendAndClose', 'DashboardController::updatePolicyEnrollmentStatus'); $routes->get("sendCroneRemainderMail", "DashboardController::sendCroneRemainderMail"); // $routes->get("getEmployeePolicy", "EmployeeRestController::getEmployeePolicy"); diff --git a/app/Config/Services.php b/app/Config/Services.php index fab85a5..d37961a 100755 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -8,6 +8,7 @@ use App\Libraries\MyLogger; use App\Libraries\GmailAPI; use App\Libraries\MyGoogleDrive; use App\Libraries\DataServiceSqlite; +use App\Libraries\RateLimiterService; use App\Controllers\Home; /** @@ -80,5 +81,14 @@ class Services extends BaseService return new MyGoogleDrive(); } + + public static function limiter($getShared = true) + { + if ($getShared) { + return static::getSharedInstance('limiter'); + } + + return new RateLimiterService(); + } } diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php index f6ae2ae..b3718c0 100755 --- a/app/Controllers/EmployeeController.php +++ b/app/Controllers/EmployeeController.php @@ -179,8 +179,15 @@ class EmployeeController extends AdminController } //handles employee & dependent bulk upload with events like inception,addition,deletion, correction and SI enhancements - public function employeesUplodWithEvents() - { + public function employeesUplodWithEvents($post_data = []) + { + if(empty($post_data)){ + $post_data = $this->request->getPost(); + $post_data = array_merge($post_data, $this->request->getFiles()); + $is_post_request = true; + }else{ + $is_post_request = false; + } // $empDataServiceController = new EmpDataServiceController(); // !dd($empDataServiceController->importInceptionUpdateTPAandUHID(['file_id' => 79])); @@ -233,82 +240,193 @@ class EmployeeController extends AdminController // $this->truncateFileData(747, 5) ; // print_rr($this->cloneWorksheet()); // die(); - if ($this->request->getMethod() == 'post') { + + // old + // if ($this->request->getMethod() == 'post') { + + // //validate uploaded file + // $filename = ''; + // $fileSize = ''; + // $validated = $this->validate([ + // 'emplist' => [ + // 'uploaded[emplist]', + // 'mime_in[emplist,application/vnd.ms-excel,application/vnd,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.spreadsheet]', + // 'max_size[emplist,16384]', + // ], + // ]); + + // if ($validated) + // { + // $avatar = $this->request->getFile('emplist'); + // if (!$avatar) { + // $this->myLogger->logme("error", 'File not found'); + // return $this->respond(['dataStatus' => false, 'code' => 400, 'message' => 'File not found'], 400); + // } + + // $is_moved = $avatar->move(WRITEPATH . 'uploads/excel/'); + // if ($is_moved) { + // $filename = $avatar->getName(); + // $fileSize = $avatar->getSize(); // File size in bytes + // $fileSize = $fileSize / (1024 * 1024); // Convert to MB + // // Handle successful upload, e.g., log success or further processing + // $this->myLogger->logme("error", 'File move successful'); + + // } else { + // $this->myLogger->logme("error", 'File move failed'); + // return $this->respond(['dataStatus' => false, 'code' => 500, 'message' => 'File move failed'], 500); + // } + // } else { + // $this->myLogger->logme("error", 'Upload failed Invalid file'); + // return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'Invalid file'], 404); + // } + + // //process post variable entry in file table + // $loggedInUserID = get_session_userid(); + // // dd($loggedInUserID); + // // $loggedInUserID = 8; + + // $client_id = $this->request->getPost('client_id'); + // $policy_id = $this->request->getPost('policy_id'); + // $branch_id = $this->request->getPost('branch_id'); + // $enrollment_open_date = $this->request->getPost('enrollment_open_date'); + // $enrollment_close_date = $this->request->getPost('enrollment_close_date'); + // $action = $this->request->getPost('upload-action-type'); + // $status = 'inprogress'; + + // $enrollment_open_date = change_date_format($enrollment_open_date, 'd/m/Y', 'Y-m-d'); + // $enrollment_close_date = change_date_format($enrollment_close_date, 'd/m/Y', 'Y-m-d'); + + // $file_id = $this->fileModel->insert(['file_name' => $filename, 'client_id' => $client_id, 'policy_id' => $policy_id, 'created_by' => $loggedInUserID, 'status' => $status, 'action' => $action, 'client_branch_id' => $branch_id,'uploaded_by' => 1, 'enrollment_open_date' => $enrollment_open_date, 'enrollment_close_date' => $enrollment_close_date]); //here field policy_id have client_policy_id and not policy id from policy master + // $this->myLogger->logme("error", '{file_id} uploaded success', ['file_id' => $file_id]); + + // //start validation process + // if($fileSize < 1) // if file size less than 1 + // { + // $empServiceController = new EmployeeServiceController(); + // $result = $empServiceController->excelFileFormatValidation(['file_id' => $file_id]); + // $this->myLogger->logme("error", '{file_id} is less than 1MB, validating on the fly', ['file_id' => $file_id]); + // //endof validation process + // if (isset($result['error_summary']) && count($result['error_summary'])) { + // return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'file rejected with errors'], 200); + // } + // } + // else //if file size greater than 1 add the file as job + // { + // $job_details = new Jobs(); + // $r = Jobs::addJob(['job_name' => 'excelFileFormatValidation','payload' => ['file_id' => $file_id]]); + // $this->myLogger->logme("error", '{file_id} is greather than 1MB, validating with job queue', ['file_id' => $file_id]); + // } + + // return $this->respond(['dataStatus' => true, 'code' => 200, 'data' => 'file upload success'], 200); + // } + + if (!empty($post_data) || $this->request->is('post') == 'post') { //validate uploaded file $filename = ''; $fileSize = ''; - $validated = $this->validate([ - 'emplist' => [ - 'uploaded[emplist]', - 'mime_in[emplist,application/vnd.ms-excel,application/vnd,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.spreadsheet]', - 'max_size[emplist,16384]', - ], - ]); - - if ($validated) - { - $avatar = $this->request->getFile('emplist'); - if (!$avatar) { - $this->myLogger->logme("error", 'File not found'); + + if (empty($post_data)) { + $validated = $this->validate([ + 'emplist' => [ + 'uploaded[emplist]', + 'mime_in[emplist,application/vnd.ms-excel,application/vnd,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.oasis.opendocument.spreadsheet]', + 'max_size[emplist,16384]', + ], + ]); + } else { + $validated = validateExcelFile($post_data['emplist']); + } + + if ($validated) { + + $avatar = isset($post_data['emplist']) ? $post_data['emplist'] : $this->request->getFile('emplist'); + if (!$avatar) { + $this->myLogger->logme("error", 'File not found'); + if (!$is_post_request) { + return ['status' => false, 'message' => 'File not found']; + } else { return $this->respond(['dataStatus' => false, 'code' => 400, 'message' => 'File not found'], 400); } + } - $is_moved = $avatar->move(WRITEPATH . 'uploads/excel/'); - if ($is_moved) { - $filename = $avatar->getName(); - $fileSize = $avatar->getSize(); // File size in bytes - $fileSize = $fileSize / (1024 * 1024); // Convert to MB - // Handle successful upload, e.g., log success or further processing - $this->myLogger->logme("error", 'File move successful'); - + $is_moved = $avatar->move(WRITEPATH . 'uploads/excel/'); + if ($is_moved) { + $filename = $avatar->getName(); + $fileSize = $avatar->getSize(); // File size in bytes + $fileSize = $fileSize / (1024 * 1024); // Convert to MB + // Handle successful upload, e.g., log success or further processing + $this->myLogger->logme("error", 'File move successful'); + } else { + $this->myLogger->logme("error", 'File move failed'); + if (!$is_post_request) { + return ['status' => false, 'message' => 'File move failed']; } else { - $this->myLogger->logme("error", 'File move failed'); return $this->respond(['dataStatus' => false, 'code' => 500, 'message' => 'File move failed'], 500); } + } } else { $this->myLogger->logme("error", 'Upload failed Invalid file'); - return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'Invalid file'], 404); + if (!$is_post_request) { + return ['status' => false, 'message' => 'Invalid file']; + } else { + return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'Invalid file'], 404); + } } //process post variable entry in file table - $loggedInUserID = get_session_userid(); - // dd($loggedInUserID); - // $loggedInUserID = 8; + $loggedInUserID = $post_data['created_by'] ?? get_session_userid(); - $client_id = $this->request->getPost('client_id'); - $policy_id = $this->request->getPost('policy_id'); - $branch_id = $this->request->getPost('branch_id'); - $enrollment_open_date = $this->request->getPost('enrollment_open_date'); - $enrollment_close_date = $this->request->getPost('enrollment_close_date'); - $action = $this->request->getPost('upload-action-type'); - $status = 'inprogress'; + // $client_id = isset($post_data['client_id']) ? $post_data['client_id'] : $this->request->getPost('client_id'); + // $policy_id = isset($post_data['policy_id']) ? $post_data['policy_id'] : $this->request->getPost('policy_id'); + // $branch_id = isset($post_data['client_branch_id']) ? $post_data['client_branch_id'] : $this->request->getPost('branch_id'); + // $action = isset($post_data['file_action']) ? $post_data['file_action'] : $this->request->getPost('upload-action-type'); + // $enrollment_open_date = isset($post_data['enrollment_open_date']) ? $post_data['enrollment_open_date'] : $this->request->getPost('enrollment_open_date') ?? null; + // $enrollment_close_date = isset($post_data['enrollment_close_date']) ? $post_data['enrollment_close_date'] : $this->request->getPost('enrollment_close_date') ?? null; + // $status = 'inprogress'; + // $hr_id = $post_data['created_by'] ?? null; + + $client_id = $post_data['client_id'] ?? null; + $policy_id = $post_data['policy_id'] ?? null; + $branch_id = $post_data['client_branch_id'] ?? null; + $action = "enrollment"; + $enrollment_open_date = $post_data['enrollment_open_date'] ?? null; + $enrollment_close_date = $post_data['enrollment_close_date'] ?? null; + $status = 'inprogress'; + $hr_id = $post_data['created_by'] ?? null; $enrollment_open_date = change_date_format($enrollment_open_date, 'd/m/Y', 'Y-m-d'); $enrollment_close_date = change_date_format($enrollment_close_date, 'd/m/Y', 'Y-m-d'); - $file_id = $this->fileModel->insert(['file_name' => $filename, 'client_id' => $client_id, 'policy_id' => $policy_id, 'created_by' => $loggedInUserID, 'status' => $status, 'action' => $action, 'client_branch_id' => $branch_id,'uploaded_by' => 1, 'enrollment_open_date' => $enrollment_open_date, 'enrollment_close_date' => $enrollment_close_date]); //here field policy_id have client_policy_id and not policy id from policy master + $file_id = $this->fileModel->insert(['file_name' => $filename, 'client_id' => $client_id, 'policy_id' => $policy_id, 'created_by' => $loggedInUserID, 'status' => $status, 'action' => $action, 'client_branch_id' => $branch_id, 'uploaded_by' => 1, 'enrollment_open_date' => $enrollment_open_date, 'enrollment_close_date' => $enrollment_close_date, 'hr_id' => $hr_id]); //here field policy_id have client_policy_id and not policy id from policy master $this->myLogger->logme("error", '{file_id} uploaded success', ['file_id' => $file_id]); //start validation process - if($fileSize < 1) // if file size less than 1 + if ($fileSize < 1) // if file size less than 1 { $empServiceController = new EmployeeServiceController(); $result = $empServiceController->excelFileFormatValidation(['file_id' => $file_id]); $this->myLogger->logme("error", '{file_id} is less than 1MB, validating on the fly', ['file_id' => $file_id]); //endof validation process if (isset($result['error_summary']) && count($result['error_summary'])) { - return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'file rejected with errors'], 200); + if(!$is_post_request){ + return ['status' => false, 'message' => 'File rejected with errors', 'file_id' => $file_id]; + }else{ + return $this->respond(['dataStatus' => false, 'code' => 404, 'message' => 'File rejected with errors'], 200); + } } - } - else //if file size greater than 1 add the file as job + } else //if file size greater than 1 add the file as job { $job_details = new Jobs(); - $r = Jobs::addJob(['job_name' => 'excelFileFormatValidation','payload' => ['file_id' => $file_id]]); + $r = Jobs::addJob(['job_name' => 'excelFileFormatValidation', 'payload' => ['file_id' => $file_id]]); $this->myLogger->logme("error", '{file_id} is greather than 1MB, validating with job queue', ['file_id' => $file_id]); } - return $this->respond(['dataStatus' => true, 'code' => 200, 'data' => 'file upload success'], 200); + if (!$is_post_request) { + return ['status' => true, 'message' => 'File upload successs, Data validation is in-progress', 'file_id' => $file_id]; + } else { + return $this->respond(['dataStatus' => true, 'code' => 200, 'data' => 'file upload success'], 200); + } } $data['page_name'] = 'View Inception'; @@ -335,7 +453,7 @@ class EmployeeController extends AdminController ->select([ 'files.id','files.file_name','files.created_by','files.created_at','files.is_active','files.status','files.client_id','files.policy_id','files.client_branch_id','files.action','files.uploaded_by', 'enrollment_open_date', 'enrollment_close_date', 'up.emp_code', - 'up.first_name', + // 'up.first_name', 'c.short_name', 'cb.branch_name', 'cp.id as client_policy_id', @@ -349,8 +467,23 @@ class EmployeeController extends AdminController WHERE e.file_id = files.id AND ep.client_policy_id = files.policy_id) as total', 'policy_type.policy_type' , 'cp.policy_no' , + "CASE + WHEN files.hr_id IS NOT NULL THEN + CONCAT( + ( + SELECT lc.name + FROM level_contacts lc + WHERE lc.id = files.hr_id + LIMIT 1 + ), + ' (HR)' + ) + ELSE up.first_name + END AS first_name + ", + 'files.hr_id' ]) - ->join('user_profiles up', 'files.created_by = up.id') + ->join('user_profiles up', 'files.created_by = up.id', 'left') ->join('client_policy cp', 'files.policy_id = cp.id', 'left') ->join('client_branch cb', 'files.client_branch_id = cb.id', 'left') ->join('policy_type', 'policy_type.id = cp.policy_type_id') @@ -378,19 +511,53 @@ class EmployeeController extends AdminController // dd($data['fileList']);die(); - if ($this->request->getMethod() == "get") { + if ($_SERVER['REQUEST_METHOD'] == "GET") { $this->loadLayout('import_export', $data); } } - public function getExcelFileErrors() - { - $file_id = $this->request->uri->getSegment(3); + public function getExcelFileErrors($file_id, $retun_type = null) + { + + $file_data = $this->fileModel->where('id', $file_id)->first(); + $error = json_decode($file_data['reason'] ?? '{}', true); + if(!empty($error) && $retun_type == 'api' && $file_data['status'] == 'failed'){ + $send = isset($error['error_summary'][5]) || isset($error['error_summary'][6]) ? true : false; + if($send){ + + $string = $error['error_data'] ?? 'System error'; + + $errorMap = [ + "Column order conflict" => "Invalid file format. Please use the sample file.", + ]; + + $message = $string; // Default to the original error + + foreach ($errorMap as $keyword => $friendlyMessage) { + if (strpos($string, $keyword) !== false) { + $message = $friendlyMessage; + break; // Stop looking once we find a match + } + } + + return $this->respond(['status' => false, 'code' => 404, 'message' => $message, 'data' => []], 200); + } + } + + // $file_id = $this->request->uri->getSegment(3); $empServiceController = new EmployeeServiceController(); // Render views and capture output $result = $empServiceController->getExcelErrorData($file_id); + if($retun_type == 'api'){ + if(!empty($result)){ + return $this->respond(['status' => true, 'code' => 200, 'message' => 'Error data feteched successfully', 'data' => $result], 200); + }else{ + return $this->respond(['status' => false, 'code' => 404, 'message' => 'Failed to fetch error data', 'data' => []], 200); + } + } + if ($result != 0) { $result['file_id'] = $file_id; @@ -2833,7 +3000,7 @@ class EmployeeController extends AdminController if (!is_dir($exportDir)) { mkdir($exportDir, 0777, true); } else { - chmod($exportDir, 0777); + // chmod($exportDir, 0777); } $filename = 'inception_export_' . date('Ymd_His') . '.xlsx'; diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index ac633a2..c5d99b3 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -141,7 +141,7 @@ class EmployeeRestController extends AdminController return $this->respond(['status' => 'success','code' => 200,'data' => $result, 'AccountManagerDetails'=> isset($AccountManagerDetails[0]) ? $AccountManagerDetails[0] : null ],200); } else { $result = "No Match's"; - return $this->respond(['status' => 'failed','code' => 404,'data' => $result],404); + return $this->respond(['status' => 'failed','code' => 404,'data' => $result],200); } } catch (\Throwable $th) { return $this->respond(['status' => 'failed','code' => 500,'data' => $th],500); @@ -162,7 +162,7 @@ class EmployeeRestController extends AdminController return $this->respond(['status' => 'success','code' => 200,'data' => $result],200); } else { $result = "No Match's"; - return $this->respond(['status' => 'failed','code' => 404,'data' => $result],404); + return $this->respond(['status' => 'failed','code' => 404,'data' => $result],200); } } } catch (\Throwable $th) { @@ -190,7 +190,7 @@ class EmployeeRestController extends AdminController } else { - return $this->respond(['status' => 'failed','code' => 404,'data' => []],404); + return $this->respond(['status' => 'failed','code' => 404,'data' => []],200); } } catch (\Throwable $th) { return $this->respond(['status' => 'failed','code' => 500,'data' => $th],500); @@ -220,7 +220,7 @@ class EmployeeRestController extends AdminController return $this->respond(['status' => 'success','code' => 200,'data' => $result], 200); } else { $result = "No Matches"; - return $this->respond(['status' => 'failed','code' => 404,'data' => $result], 404); + return $this->respond(['status' => 'failed','code' => 404,'data' => $result], 200); } } } catch (\Exception $e) { @@ -235,8 +235,17 @@ class EmployeeRestController extends AdminController try { $data = $this->request->getJSON(); + $clientPolicyData = $this->clientPolicyModel->where('id',$data[0]->client_policy_id) + ->where('is_active', 1) + ->first(); + + if(in_array($clientPolicyData['policy_type_id'], [3,4,5])){ + $openForEnrollment = $this->findThePolicyIsOpenForEnrollment($clientPolicyData['base_policy'], $data[0]->emp_code); + }else{ + $openForEnrollment = $this->findThePolicyIsOpenForEnrollment($data[0]->client_policy_id, $data[0]->emp_code); + } + //enrolment check - $openForEnrollment = $this->findThePolicyIsOpenForEnrollment($data[0]->client_policy_id, $data[0]->emp_code); if($openForEnrollment == false){ return $this->respond(['status' => 'failed','code' => 404,'data' => [],'message' => 'Enrollment closed.'], 200); } @@ -308,7 +317,7 @@ class EmployeeRestController extends AdminController } } } catch (\Exception $e) { - return $this->respond(['status' => 'failed','code' => 500,'data' => $e->getMessage(),'message' => "Action failed."], 500); + return $this->respond(['status' => 'failed','code' => 500,'data' => $e->getMessage() . $e->getLine(),'message' => "Action failed."], 500); } } @@ -395,6 +404,8 @@ class EmployeeRestController extends AdminController }else{ + $enrollment_dates = $this->getEnrollmentDates($client_policy_id, $emp_code); + $data['employee_id']= $employee_id; $data['client_policy_id']= $client_policy_id; $data['basic_cover_si']= $basic_cover_si; @@ -405,6 +416,9 @@ class EmployeeRestController extends AdminController if(!empty($other_employee_policy_data)){ $data['enrollment_open_date']= $other_employee_policy_data['enrollment_open_date'] ?? null; $data['enrollment_close_date']= $other_employee_policy_data['enrollment_close_date'] ?? null; + }else{ + $data['enrollment_open_date'] = $enrollment_dates['enrollment_open_date'] ?? null; + $data['enrollment_close_date'] = $enrollment_dates['enrollment_close_date'] ?? null; } // dd($data); @@ -550,7 +564,7 @@ class EmployeeRestController extends AdminController $this->employeeModel->where('id', $this->request->getGet('id') ) ->where('is_active', 1 ) - ->set(array('is_active'=> 0 )) + ->set(['emp_status' => 'truncated', 'is_active' => 0]) ->update(); } @@ -564,13 +578,13 @@ class EmployeeRestController extends AdminController $query->where('client_policy_id', $clientPolicyId); } - $query->set(['is_active' => 0])->update(); + $query->set(['status' => 'truncated', 'is_active' => 0])->update(); return $this->respond(['status' => 'success','code' => 200,'data' =>[] ], 200); }else{ - return $this->respond(['status' => 'failed','code' => 404,'data' => [] ], 404); + return $this->respond(['status' => 'failed','code' => 404,'data' => [] ], 200); } @@ -590,8 +604,18 @@ class EmployeeRestController extends AdminController // print_r($requestData); // dd($requestData); - $openForEnrollment = $this->findThePolicyIsOpenForEnrollment($requestData[0]->client_policy_id, $requestData[0]->emp_code); - if($openForEnrollment == false){ return $this->respond(['status' => 'failed','code' => 404,'data' => 'Enrollment closed'], 404); } + + $clientPolicyData = $this->clientPolicyModel->where('id',$requestData[0]->client_policy_id) + ->where('is_active', 1) + ->first(); + + if(in_array($clientPolicyData['policy_type_id'], [3,4,5])){ + $openForEnrollment = $this->findThePolicyIsOpenForEnrollment($clientPolicyData['base_policy'], $requestData[0]->emp_code); + }else{ + $openForEnrollment = $this->findThePolicyIsOpenForEnrollment($requestData[0]->client_policy_id, $requestData[0]->emp_code); + } + + if($openForEnrollment == false){ return $this->respond(['status' => 'failed','code' => 404,'data' => 'Enrollment closed'], 200); } foreach ($requestData as $key => $value) { @@ -600,6 +624,7 @@ class EmployeeRestController extends AdminController ->where('client_policy_id', $value->client_policy_id) ->where('is_active', 1 ) ->findAll(); + // dd($checkIfExist); if ($checkIfExist) { @@ -608,10 +633,14 @@ class EmployeeRestController extends AdminController }else{ + $enrollment_dates = $this->getEnrollmentDates($value->client_policy_id, $requestData[0]->emp_code); + $data['employee_id']= $value->employee_id; $data['client_policy_id']= $value->client_policy_id; $data['basic_cover_si']= $value->basic_cover_si; $data['status'] = 'draft'; + $data['enrollment_open_date'] = $enrollment_dates['enrollment_open_date'] ?? null; + $data['enrollment_close_date'] = $enrollment_dates['enrollment_close_date'] ?? null; $this->employeePolicyModel->insert($data); } @@ -658,12 +687,12 @@ class EmployeeRestController extends AdminController public function getEmployeeAndDependenceByClientId() { try { - $empData = $this->employeePolicyModel->getEmployeePolicy( client_id:$this->request->getGet('client_id'),policy_id: $this->request->getGet('client_policy_id'),status:0,branch_id:$this->request->getGet('client_branch_id')); + $empData = $this->employeePolicyModel->getEmployeePolicy( client_id:$this->request->getGet('client_id'),policy_id: $this->request->getGet('client_policy_id'),status:0,branch_id:$this->request->getGet('client_branch_id'), status_type: 'hr'); if ($empData) { return $this->respond(['status' => 'success', 'code' => 200, 'data' => $empData], 200); } else { - return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 404); + return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200); } @@ -776,7 +805,7 @@ class EmployeeRestController extends AdminController if ($ClientPolicyData) { return $this->respond(['status' => 'success', 'code' => 200, 'data' => $result], 200); } else { - return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 404); + return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200); } @@ -786,8 +815,57 @@ class EmployeeRestController extends AdminController } - // Upload the Employee Detail in DB by Sheet Data public function employeeUpload() + { + try{ + + $post_data = [ + 'client_id' => $this->request->getPost('client_id') ?? null, + 'client_branch_id' => $this->request->getPost('client_branch_id'), + 'policy_id' => $this->request->getPost('policy_id'), + 'enrollment_open_date' => $this->request->getPost('enrollment_open_date'), + 'enrollment_close_date' => $this->request->getPost('enrollment_close_date'), + 'file_action' => "enrollment", + 'created_by' => $this->request->getPost('created_by') ?? null, + 'emplist' => $this->request->getFile('file') + ]; + + if (is_string($post_data['client_id']) && preg_match('/^[a-f0-9]{32}$/i', $post_data['client_id'])) { + $client_data = $this->clientModel->where('MD5(id)', $post_data['client_id'])->first(); + $post_data['client_id'] = $client_data['id']; + } + + + // print_r($post_data); die; + + if(empty($post_data['client_id'])){ + return $this->respondCreated(['status' => false, 'message' => 'Client is required', 'data' => []]); + } + + $employeeController = new EmployeeController(); + $responce = $employeeController->employeesUplodWithEvents($post_data); + // print_r($responce); die; + + return $this->respond($responce, 200); + + // if(!$responce['status']){ + // $file_data = $this->getDataFromFilesTable(['file_id' => $responce['file_id']]); + // $responce['data'] = $file_data; + // return $this->respond($responce, 200); + // }else{ + // $responce['data'] = []; + // return $this->respond($responce, 200); + // } + + + + }catch(\Exception $e){ + return $this->respondCreated(['status' => false, 'message' => $e->getMessage(), 'data' => []]); + } + } + + // Upload the Employee Detail in DB by Sheet Data (DO NOT REMOVE THIS) + public function employeeUploadOld() { try { $file = $this->request->getFile('file'); @@ -795,6 +873,11 @@ class EmployeeRestController extends AdminController $client_branch_id = $this->request->getPost('client_branch_id'); $policy_id = $this->request->getPost('policy_id'); + if (is_string($client_id) && preg_match('/^[a-f0-9]{32}$/i', $client_id)) { + $client_data = $this->clientModel->where('MD5(id)', $client_id)->first(); + $client_id = $client_data['id']; + } + $client_data = $this->clientModel->where('id', $client_id)->first(); $notification = $this->notificationModel->where('client_id',$client_id)->where('template_name','member_welcome_mail')->first(); @@ -1128,7 +1211,7 @@ class EmployeeRestController extends AdminController $this->fileModel->where('id', $file_id)->set(['status' => 'success','reason' => '','error_data' => ''])->update(); return $this->respond(['status' => 'success', 'code' => 200, 'message' => "Success" ], 200); }else{ - return $this->respond(['status' => 'failed', 'code' => 404, 'message' => "Client id and Client Policy id is Not Match!" ], 404); + return $this->respond(['status' => 'failed', 'code' => 404, 'message' => "Client id and Client Policy id is Not Match!" ], 200); } } catch (\Throwable $th) { $this->myLogger->logme("error", ($th->getMessage().' --- '.$th->getLine() . '----' . $th->getTraceAsString())); @@ -1145,9 +1228,78 @@ class EmployeeRestController extends AdminController return $this->respond(['status' => 'failed','code' => 500,'data' => $th->getMessage(), 'error_data' => $errorData], 500); } - + } + + public function getDataFromFilesTable($search_data) + { + $file_download_base = base_url('util/download-file-list/'); + $builder = $this->fileModel + ->select(" + files.id, + files.client_id, + files.client_branch_id, + files.policy_id, + cp.policy_no, + files.file_name, + files.action as file_action, + files.created_at, + files.created_by, + files.updated_at, + files.updated_by, + c.short_name, + cb.branch_name, + lc.name as first_name, + CONCAT(UCASE(LEFT(files.status, 1)), LCASE(SUBSTRING(files.status, 2))) as status, + CASE + WHEN status = 'failed' THEN 1 + ELSE 0 + END AS file_error_status, + CONCAT('{$file_download_base}', files.id, '/api') AS file_download_link + ", false) + ->join('clients c', 'files.client_id = c.id AND c.is_active = 1', 'left') + ->join('client_branch cb', 'files.client_branch_id = cb.id AND cb.is_active = 1', 'left') + ->join('client_policy cp', 'files.policy_id = cp.id AND cp.is_active = 1', 'left') + ->join('level_contacts lc', 'files.hr_id = lc.id AND lc.contact_type = "client" AND lc.is_active = 1', 'left'); + + + if (isset($search_data['policy_id']) && !empty($search_data['policy_id'])) { + $builder->where("files.policy_id", $search_data['policy_id']); + } + + if (isset($search_data['created_by']) && !empty($search_data['created_by'])) { + $builder->where("files.hr_id", $search_data['created_by']); + } + + if (isset($search_data['policy_no']) && !empty($search_data['policy_no'])) { + $builder->where("cp.policy_no", $search_data['policy_no']); + } + + if (isset($search_data['client_id']) && !empty($search_data['client_id'])) { + if (is_string($search_data['client_id']) && preg_match('/^[a-f0-9]{32}$/i', $search_data['client_id'])) { + $builder->where("MD5(files.client_id)", $search_data['client_id']); + } else { + $builder->where("files.client_id", $search_data['client_id']); + } + } + + if (isset($search_data['file_id']) && !empty($search_data['file_id'])) { + $builder->where("files.id", $search_data['file_id']); + } + + + // Execute query + $data = $builder->get()->getResultArray(); + + if (!empty($data)) { + return $data; + } + + return []; + } + + public function getHrFileUploadErrorDetails($file_id) + { - } @@ -1463,13 +1615,13 @@ class EmployeeRestController extends AdminController $array->mapped_family_floaters = $data; $array->type = "GMC"; - $array->family_floaters_of_dependent_and_si_value = $dependent_and_si_value > 0 ? $dependent_and_si_value : 0; + $array->family_floaters_of_dependent_and_si_value = $dependent_and_si_value > 0 ? $dependent_and_si_value : 0.0; if($array->is_premium_summery == 1){ - $array->family_floaters_of_dependent_and_si_premium_value = $dependent_and_si_premium_value > 0 ? round($dependent_and_si_premium_value) : 0; - $array->family_floaters_of_dependent_and_gst_value = $dependent_and_si_gst_value > 0 ? round($dependent_and_si_gst_value) : 0; + $array->family_floaters_of_dependent_and_si_premium_value = $dependent_and_si_premium_value > 0 ? round($dependent_and_si_premium_value) : 0.0; + $array->family_floaters_of_dependent_and_gst_value = $dependent_and_si_gst_value > 0 ? round($dependent_and_si_gst_value) : 0.0; }else{ - $array->family_floaters_of_dependent_and_si_premium_value = 0; - $array->family_floaters_of_dependent_and_gst_value = 0; + $array->family_floaters_of_dependent_and_si_premium_value = 0.0; + $array->family_floaters_of_dependent_and_gst_value = 0.0; } $checkGmcParentsPolicyExist = $this->clientPolicyModel->select(" @@ -1531,7 +1683,8 @@ class EmployeeRestController extends AdminController } if(!empty($result) && $this->request->getGet('policy') == 'GMC'){ - return $this->respond(['status' => 'success','code' => 200,'data' => $result], 200); + return $this->respond(['status' => 'success','code' => 200,'data' => $result], 200); + // echo json_encode(['status' => 'success','code' => 200,'data' => $result], JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION); }else{ return $this->respond(['status' => 'success','code' => 200,'data' => []], 200); } @@ -1775,13 +1928,13 @@ class EmployeeRestController extends AdminController $array->mapped_family_floaters = $data; $array->type = "GMC - Parents"; - $array->family_floaters_of_dependent_and_si_value = $dependent_and_si_value > 0 ? $dependent_and_si_value : 0; + $array->family_floaters_of_dependent_and_si_value = $dependent_and_si_value > 0 ? $dependent_and_si_value : 0.0; if($array->is_premium_summery == 1){ - $array->family_floaters_of_dependent_and_si_premium_value = $dependent_and_si_premium_value > 0 ? round($dependent_and_si_premium_value) : 0; - $array->family_floaters_of_dependent_and_gst_value = $dependent_and_si_gst_value > 0 ? round($dependent_and_si_gst_value) : 0; + $array->family_floaters_of_dependent_and_si_premium_value = $dependent_and_si_premium_value > 0 ? round($dependent_and_si_premium_value) : 0.0; + $array->family_floaters_of_dependent_and_gst_value = $dependent_and_si_gst_value > 0 ? round($dependent_and_si_gst_value) : 0.0; }else{ - $array->family_floaters_of_dependent_and_si_premium_value = 0; - $array->family_floaters_of_dependent_and_gst_value = 0; + $array->family_floaters_of_dependent_and_si_premium_value = 0.0; + $array->family_floaters_of_dependent_and_gst_value = 0.0; } return $array; @@ -2015,10 +2168,10 @@ class EmployeeRestController extends AdminController // return $restAuthController->callThirdPartyGETAPI($queryParams, 'getClientDetails'); // } else { - // return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 404); + // return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200); // } // } else { - // return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 404); + // return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200); // } // } catch (\Exception $e) { // return $this->respond(['status' => 'failed', 'code' => 500, 'data' => $e->getMessage()], 500); @@ -2036,15 +2189,19 @@ class EmployeeRestController extends AdminController if (!empty($pre_client_id)) { + if (is_string($pre_client_id) && preg_match('/^[a-f0-9]{32}$/i', $pre_client_id)) { + $client = $this->clientModel->where('MD5(id)', $pre_client_id)->first(); + }else{ + $client = $this->clientModel->where('id', $pre_client_id)->first(); + } - $client = $this->clientModel->where('id', $pre_client_id)->first(); if ($client) { $client['client_logo'] = base_url() . 'public/uploads/logo/' . $client['client_logo']; $clientPolicy = $this->clientPolicyModel - ->where('client_id', $pre_client_id) + ->where('client_id', $client['id'] ?? null) ->where('client_branch_id', $pre_branch_id) ->findAll(); @@ -2060,7 +2217,7 @@ class EmployeeRestController extends AdminController ], 200); } else { - return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 404); + return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200); } } elseif (!empty($post_client_id)) { @@ -2074,7 +2231,7 @@ class EmployeeRestController extends AdminController } else { - return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 404); + return $this->respond(['status' => 'failed', 'code' => 404, 'data' => []], 200); } } catch (\Exception $e) { @@ -2139,12 +2296,18 @@ class EmployeeRestController extends AdminController try { $emp_code = $this->request->getGet('emp_code'); + $client_id = $this->request->getGet('client_id'); + $client_branch_id = $this->request->getGet('client_branch_id'); + $addOnEmployeeData = $this->employeeModel->where('is_active', 1 ) ->where('emp_code',$this->request->getGet('emp_code')) ->where('client_id',$this->request->getGet('client_id')) ->where('client_branch_id',$this->request->getGet('client_branch_id')) ->where('is_addon_value',1)->findAll(); + + $policyIds = $this->getEmployeeAddOnPolicies($emp_code, $client_id, $client_branch_id); + // dd($policyIds); $clientPolicy = $this->clientPolicyModel->select(" @@ -2175,7 +2338,11 @@ class EmployeeRestController extends AdminController ->where('policy_status', 1) ->where('is_active', 1) ->where('enrolment_visibility', 1) + ->whereIn('id', $policyIds) ->findAll(); + if(empty($policyIds)){ + return $this->respond(['status' => 'failed','code' => 404,'data' => []], 200); + } if(count($clientPolicy)) { @@ -2219,6 +2386,13 @@ class EmployeeRestController extends AdminController } + if(isset($array['base_policy']) && !empty($array['base_policy'])) + { + $openForEnrollmentValue = (int) $this->findThePolicyIsOpenForEnrollment($array['base_policy'], $emp_code); + }else{ + $openForEnrollmentValue = $array['open_for_enrollment']; + } + $policyTypeData = $this->policyTypeModel->where('id',$array['policy_type_id'])->get()->getRow(); $responce['policy_name'] = $policyTypeData->long_name; @@ -2228,7 +2402,7 @@ class EmployeeRestController extends AdminController $responce['client_id'] = $array['client_id']; $responce['client_policy_id'] = $array['id']; $responce['is_addon'] = $array['is_addon']; - $responce['OpenForEnrollment'] = $array['open_for_enrollment']; + $responce['OpenForEnrollment'] = $openForEnrollmentValue; $responce['policy_terms'] = $decodedArray; $responce['policy_type_id'] = $array['policy_type_id']; //$responce['is_member_modify_allowed'] = $array['is_member_modify_allowed']; @@ -2424,13 +2598,13 @@ class EmployeeRestController extends AdminController } $responce['family_floaters_of_dependent_and_si_array'] = $data; - $responce['family_floaters_of_dependent_and_si_value'] = $dependent_and_si_value > 0 ? $dependent_and_si_value : 0; + $responce['family_floaters_of_dependent_and_si_value'] = $dependent_and_si_value > 0 ? $dependent_and_si_value : 0.0; if($array['is_premium_summery']){ - $responce['family_floaters_of_dependent_and_si_premium_value'] = $dependent_and_si_premium_value > 0 ? round($dependent_and_si_premium_value) : 0; - $responce['family_floaters_of_dependent_and_gst_value'] = $dependent_and_si_gst_value > 0 ? round($dependent_and_si_gst_value) : 0; + $responce['family_floaters_of_dependent_and_si_premium_value'] = $dependent_and_si_premium_value > 0 ? round($dependent_and_si_premium_value) : 0.0; + $responce['family_floaters_of_dependent_and_gst_value'] = $dependent_and_si_gst_value > 0 ? round($dependent_and_si_gst_value) : 0.0; }else{ - $responce['family_floaters_of_dependent_and_si_premium_value'] = 0; - $responce['family_floaters_of_dependent_and_gst_value'] = 0; + $responce['family_floaters_of_dependent_and_si_premium_value'] = 0.0; + $responce['family_floaters_of_dependent_and_gst_value'] = 0.0; } @@ -2439,7 +2613,8 @@ class EmployeeRestController extends AdminController if($this->request->getGet('policy') == 'GMC-DEPENDENT-ADDON'){ - return $this->respond(['status' => 'success','code' => 200,'data' => ['gmc_dependent_addon'=>$responce]], 200); + return $this->respond(['status' => 'success','code' => 200,'data' => ['gmc_dependent_addon'=>$responce]], 200); + // echo json_encode(['status' => 'success','code' => 200,'data' => ['gmc_dependent_addon'=>$responce]], JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION); } //array_push($PolicyData, $responce); @@ -2606,7 +2781,18 @@ class EmployeeRestController extends AdminController $array_list = []; foreach ($client_policy_id as $key => $value) { - $policy = $this->clientPolicyModel->where('id',$value)->where('open_for_enrollment',1)->find(); + + $clientPolicyData = $this->clientPolicyModel->where('client_id',$client_id) + ->where('id',$value) + ->where('is_active', 1) + ->first(); + + if(in_array($clientPolicyData['policy_type_id'], [3,4,5])){ + $policy = $this->findThePolicyIsOpenForEnrollment($clientPolicyData['base_policy'], $emp_code); + }else{ + $policy = $this->findThePolicyIsOpenForEnrollment($value, $emp_code); + } + if($policy) { $this->myLogger->logme("error", 'client policy id = '.$value.' is open for enrollment'); @@ -2649,7 +2835,6 @@ class EmployeeRestController extends AdminController } }else { $this->myLogger->logme("error", 'client policy id = '.$value.' is not open for enrollment');} - } @@ -2914,7 +3099,7 @@ class EmployeeRestController extends AdminController return $employee_data_group_by_family; }else{ // dd ($employee_data_group_by_family); - return $this->respond(['status' => 'success','code' => (count($employee_data_group_by_family) ? 200 : 404),'data' => [$employee_data_group_by_family] ], 200); + return $this->respond(['status' => 'success','code' => (count($employee_data_group_by_family) ? 200 : 200),'data' => [$employee_data_group_by_family] ], 200); } } @@ -2940,12 +3125,12 @@ class EmployeeRestController extends AdminController } if(count($policyId) == 0){ - return $this->respond(['status' => 'failed','code' => (count($policyId) ? 200 : 404),'data' => [] ], 200); + return $this->respond(['status' => 'failed','code' => (count($policyId) ? 200 : 200),'data' => [] ], 200); } $ClientPolicyData = $this->clientPolicyModel->select('client_policy.id as client_policy_id , client_policy.client_id as client_id, client_policy.policy_type_id as policy_type_id, client_policy.is_addon as is_addon , client_policy.open_for_enrollment as OpenForEnrollment , client_policy.inception_type as inception_type, client_policy.policy_no as policy_no, client_policy.insurer_id as insurer_id, DATE_FORMAT(client_policy.policy_end_date, "%d-%m-%Y") AS policy_expiry_date ') - ->where('client_policy.client_id', $this->request->getGet('client_id') ) + ->where('md5(client_policy.client_id)', $this->request->getGet('client_id') ) ->where('client_policy.client_branch_id', $this->request->getGet('client_branch_id') ) ->where('client_policy.is_active', 1 ) ->where('client_policy.policy_status', 1) @@ -2988,9 +3173,9 @@ class EmployeeRestController extends AdminController if($result) { - return $this->respond(['status' => 'success','code' => (count($result) ? 200 : 404),'data' => $result ], 200); + return $this->respond(['status' => 'success','code' => (count($result) ? 200 : 200),'data' => $result ], 200); }else{ - return $this->respond(['status' => 'failed','code' => (count($result) ? 200 : 404),'data' => [] ], 200); + return $this->respond(['status' => 'failed','code' => (count($result) ? 200 : 200),'data' => [] ], 200); } } @@ -3391,7 +3576,7 @@ class EmployeeRestController extends AdminController } else { - return $this->respond(['status' => 'failed','code' => 404,'data' => 'No Data'],404); + return $this->respond(['status' => 'failed','code' => 404,'data' => 'No Data'],200); } } catch (\Throwable $th) { return $this->respond(['status' => 'failed','code' => 500,'data' => $th],500); @@ -3415,7 +3600,7 @@ class EmployeeRestController extends AdminController } else { - return $this->respond(['status' => 'failed','code' => 404,'data' => 'No Data'],404); + return $this->respond(['status' => 'failed','code' => 404,'data' => 'No Data'],200); } } catch (\Throwable $th) { return $this->respond(['status' => 'failed','code' => 500,'data' => $th],500); @@ -3476,7 +3661,7 @@ class EmployeeRestController extends AdminController return $this->respond(['status' => 'failed', 'code' => 400, 'message' => 'Firebase Token is required'], 400); } } else { - return $this->respond(['status' => 'failed', 'code' => 404, 'message' => 'Employee not found'], 404); + return $this->respond(['status' => 'failed', 'code' => 404, 'message' => 'Employee not found'], 200); } } catch (\Throwable $th) { log_message('error', 'An error occurred: ' . $th->getMessage()); @@ -4064,13 +4249,13 @@ class EmployeeRestController extends AdminController $file_id = $this->request->getGet('id') ?? $id; // Find record - $record = $this->hrFileUploadModel->find($file_id); + $record = $this->fileModel->where('id', $file_id)->first(); if (!$record) { return $this->failNotFound("File record not found"); } - $uploadPath = WRITEPATH . 'uploads/hr_files/'; + $uploadPath = WRITEPATH . 'uploads/excel/'; $filePath = $uploadPath . $record['file_name']; if (!file_exists($filePath)) { @@ -4078,8 +4263,8 @@ class EmployeeRestController extends AdminController } // Force file download - return $this->response->download($filePath, null) - ->setFileName($record['file_name']); + return $this->response->download($filePath, null)->setFileName($record['file_name']); + } catch (\Exception $e) { return $this->failServerError($e->getMessage()); } @@ -4088,6 +4273,28 @@ class EmployeeRestController extends AdminController public function hrFileList() { try { + + $request = service('request'); + $search_data = $request->getGetPost() ?? []; + + // Fetch results + $data = $this->getDataFromFilesTable($search_data); + + return $this->respond([ + 'status' => true, + 'message' => 'File list fetched successfully', + 'data' => $data + ]); + + } catch (\Exception $e) { + return $this->failServerError($e->getMessage()); + } + } + + public function hrFileListOld() + { + try { + $request = service('request'); $builder = $this->hrFileUploadModel; @@ -4254,4 +4461,100 @@ class EmployeeRestController extends AdminController } } + public function getEnrollmentDates($client_policy_id, $emp_code) + { + $enrollment_open_date = null; + $enrollment_close_date = null; + + $clientPolicyData = $this->clientPolicyModel->where('id', $client_policy_id) + ->where('is_active', 1) + ->first(); + + if(in_array($clientPolicyData['policy_type_id'] ?? null, [3,4,5])){ + + $policy = $this->employeePolicyModel + ->select('employee_polices.enrollment_open_date, employee_polices.enrollment_close_date') + ->join('employees', 'employee_polices.employee_id = employees.id') + ->where('employees.emp_code',$emp_code) + ->where('employee_polices.client_policy_id', $clientPolicyData['base_policy']) + ->where('employee_polices.enrollment_open_date <= CURDATE()', null, false) + ->where('employee_polices.enrollment_close_date >= CURDATE()', null, false) + ->where('employee_polices.enrollment_open_date is not null') + ->where('employee_polices.enrollment_close_date is not null') + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->whereIn('employee_polices.status', ['draft', 'enrolled']) + ->whereIn('employees.emp_status', ['draft', 'enrolled']) + ->first(); + + $enrollment_open_date = $policy['enrollment_open_date'] ?? null; + $enrollment_close_date = $policy['enrollment_close_date'] ?? null; + } + + return [ + 'enrollment_open_date' => $enrollment_open_date, + 'enrollment_close_date' => $enrollment_close_date, + ]; + } + + public function getEmployeeAddOnPolicies($emp_code, $client_id, $client_branch_id): array + { + $data = $this->employeeModel + ->select(" + client_policy.id AS gmc_client_policy_id, + + ( + SELECT id FROM client_policy + WHERE policy_type_id = 3 + AND base_policy = gmc_client_policy_id + LIMIT 1 + ) AS gmc_parent_policy_id, + + ( + SELECT id FROM client_policy + WHERE policy_type_id = 4 + AND base_policy = gmc_client_policy_id + LIMIT 1 + ) AS gmc_topup_policy_id + ") + ->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->join('client_policy', 'employee_polices.client_policy_id = client_policy.id') + ->whereIn('employees.emp_status', ['draft', 'enrolled']) + ->whereIn('employee_polices.status', ['draft', 'enrolled']) + ->where([ + 'employees.is_active' => 1, + 'employee_polices.is_active' => 1, + 'client_policy.policy_type_id' => 2, + 'employees.family_floater_key' => 'self', + 'employees.emp_code' => $emp_code ?? null, + 'employees.client_id' => $client_id ?? null, + 'employees.client_branch_id' => $client_branch_id ?? null, + ]) + ->orderBy('employee_polices.id', 'desc') + ->first(); + + // dd($data); + // dd(db_connect()->getLastQuery()); + + if (!empty($data) && !empty($data['gmc_parent_policy_id'])) { + + $getParentTopUp = $this->clientPolicyModel + ->select('id AS gmc_parent_topup_policy_id') + ->where('is_active', 1) + ->where('policy_type_id', 5) // parent Top-Up + ->where('base_policy', $data['gmc_parent_policy_id']) + ->first(); + } + + if (empty($data)) { + return []; + } + + $policyIds[] = $data['gmc_topup_policy_id']; + $policyIds[] = $data['gmc_parent_policy_id']; + $policyIds[] = $getParentTopUp['gmc_parent_topup_policy_id'] ?? null; + + return $policyIds; + } + } \ No newline at end of file diff --git a/app/Controllers/EmployeeServiceController.php b/app/Controllers/EmployeeServiceController.php index c52ebd7..69c1d0f 100755 --- a/app/Controllers/EmployeeServiceController.php +++ b/app/Controllers/EmployeeServiceController.php @@ -765,6 +765,14 @@ class EmployeeServiceController extends AdminController // get policy and rack details $policy_details = $this->clientPolicyModel->getPolicyDetails($file['client_id'],$file['policy_id']); + + if(empty($policy_details)){ + $message = "Policy configuration is incomplete. Cannot proceed."; + $this->myLogger->logme('error',($message . ' for file id ' . $file_id)); + $this->fileModel->where('id', $file_id)->set(['status' => 'failed','reason' => json_encode(['error_summary' => array_count_values([5]),'error_data' => $message])])->update(); + return array('error_summary' => [5], 'error_data' => $message); + } + $policy_terms = json_decode($policy_details[0]->policy_terms); $policy_terms = (array) $policy_terms;// convert obj to array $default_age_ratio = isset($policy_terms['age_ratio']) ? json_decode(json_encode($policy_terms['age_ratio']),true) : []; @@ -775,6 +783,13 @@ class EmployeeServiceController extends AdminController $slab_details = $this->policiesModel->getPolicySlabRatesForEmpOnboard($file['policy_id'],$file['client_id']); // dd($slab_details); + if(empty($slab_details) || (isset($slab_details['slab_rates']) && empty($slab_details['slab_rates']))){ + $message = "Policy configuration is incomplete. Cannot proceed."; + $this->myLogger->logme('error',($message . ' for file id ' . $file_id)); + $this->fileModel->where('id', $file_id)->set(['status' => 'failed','reason' => json_encode(['error_summary' => array_count_values([5]),'error_data' => $message])])->update(); + return array('error_summary' => [5], 'error_data' => $message); + } + //remove header unset($excel_data[0]); $relationship = $this->general_relationships; diff --git a/app/Controllers/LoginController.php b/app/Controllers/LoginController.php index 830fb02..73f7bb0 100755 --- a/app/Controllers/LoginController.php +++ b/app/Controllers/LoginController.php @@ -9,6 +9,7 @@ use Psr\Log\LoggerInterface; use App\Models\UserModel; use App\Models\AuthHistoryModel; +use App\Libraries\AuthLogout; class LoginController extends BaseController { @@ -30,20 +31,22 @@ class LoginController extends BaseController public function receiveGoogleOAuthResponse() { $UserModel = new UserModel(); - //echo 'DONE';die(); + // echo 'DONE';die(); if ($this->request->getGet('code')) { $code = (string) $this->request->getGet('code'); log_message('error', 'Get OAuth Responce Code Sucessfully'); log_message('error', 'OAuthResponceCode : `'.$code.'`'); $value = googleOAuthLogin($this->request->getGet('code')); + // log_message('error', "OAuthResponceCode :" . json_encode($value)); if($value){ + // print_r($value);//die; $user = $UserModel->getUserByEmail($value->email); if($user){ if($user->is_active !== '0'){ $user_team = $UserModel->getUserTeamsByUserID($user->id); // dd($user_team); - + session()->regenerate(true); $session_data = [ 'isLoggedIn' => True , 'userid' => $user->id, @@ -54,25 +57,27 @@ 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' => generateFingerprint()]); log_message('error', 'Set The UserId : `'. $user->id .'` in Session'); log_message('error', 'User Login Sucessfully'); $this->getUserDeviceInfo($user->id, 'NhanceUser'); - return redirect()->to(base_url('/dashboard/view')); + return redirect()->to(base_url('/dashboard/view')); }else{ log_message('error', 'User Not Active'); session()->setFlashdata('error', 'User Not Active'); - return redirect()->to(base_url('login')); + return AuthLogout::logout(); } }else{ log_message('error', 'User Not Registered'); session()->setFlashdata('error', 'User Not Registered'); - return redirect()->to(base_url('login')); + return AuthLogout::logout(); } } @@ -94,14 +99,16 @@ class LoginController extends BaseController // setcookie('session_data', '', time() - 3600, $path); // return redirect()->to(base_url('login')); - $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()->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')); + + return AuthLogout::logout(); } diff --git a/app/Controllers/MasterController.php b/app/Controllers/MasterController.php index e7751e5..812b23e 100755 --- a/app/Controllers/MasterController.php +++ b/app/Controllers/MasterController.php @@ -156,6 +156,7 @@ class MasterController extends AdminController $this->myLogger->logme('error','Edit Insurer Onboarding function called'); $headerData['page_name'] = 'Edit Insurer Master'; + print_r($headerData);die; $types = array( (object) array('id' => 'pvt', 'name' => 'PVT'), @@ -1742,6 +1743,7 @@ class MasterController extends AdminController 'cache' => WRITEPATH . 'cache', 'sample_import_excel' => ROOTPATH . 'public/sample_import_excel', 'lead_files' => WRITEPATH . 'uploads/lead_files/', + 'exports' => WRITEPATH . 'exports/', ]; foreach ($folders as $folderName => $folderPath) { diff --git a/app/Controllers/RestAuthenticationController.php b/app/Controllers/RestAuthenticationController.php index 22e834e..e29c0a7 100755 --- a/app/Controllers/RestAuthenticationController.php +++ b/app/Controllers/RestAuthenticationController.php @@ -142,6 +142,7 @@ class RestAuthenticationController extends AdminController log_message('error', ' '); log_message('error', '************************ PRE END ********************************'); $result = ['user_verification' => false , 'message' => "User not found"]; + recordRateLimitFailure(); return $this->respond(['status' => 'failed','code' => 404,'data' => $result],200); } @@ -166,7 +167,14 @@ class RestAuthenticationController extends AdminController $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithMobileNumber: Final employee data to verify = " . json_encode($employeeData)); 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']], 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', '************************ PRE END ********************************'); @@ -264,10 +272,11 @@ class RestAuthenticationController extends AdminController $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithEmailId: Received Email ID = " . $email); $empdata = RestAuthHelper::getPreAndPostDataByEmailOrMobile(['email_id' => $email]); - // print_r($empdata); die; + $otp = random_int(100000, 999999); + //only retail policy if(empty($empdata)){ $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithEmailId: No employee data found both PRE & POST"); log_message('error', ' '); @@ -312,6 +321,7 @@ class RestAuthenticationController extends AdminController } $result = ['user_verification' => false , 'message' => "User not found"]; + recordRateLimitFailure(); return $this->respond(['status' => 'failed','code' => 404,'data' => $result],200); } @@ -323,8 +333,15 @@ class RestAuthenticationController extends AdminController $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithEmailId: Using PRE data"); } + + 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']], 429); // Too Many Requests + } $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithEmailId: Valid employee found, generating OTP"); @@ -430,10 +447,12 @@ class RestAuthenticationController extends AdminController $otp = isset($this->request->getJSON()->otp) ? $this->request->getJSON()->otp : null; if(empty($otp)){ + recordRateLimitFailure(); return $this->respond(['status' => 'OTP is required','code' => 400,'message' => 'OTP is required'], 200); } if (empty($mobile_number) && empty($email_id)) { + recordRateLimitFailure(); return $this->respond(['status' => 'failed','code' => 400,'message' => 'Mobile number or Email ID is required'], 200); } @@ -457,9 +476,11 @@ class RestAuthenticationController extends AdminController // Call the third-party API function $apiResponse = $this->callThirdPartyAPI($retailApiParams, 'getVerifiedRetailUserData'); // print_r($apiResponse); die; + recordRateLimitFailure(); return $this->respond(['status' => 'failed', 'code' => 200, 'data' => [], 'post_enrollment' => json_decode($apiResponse, true)], 200); } + recordRateLimitFailure(); return $this->respond(['status' => 'Invalid OTP','code' => 404,'data' => "", 'message' => "Invalid OTP"],200); } @@ -578,6 +599,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']], 429); // Too Many Requests + } $sql = "UPDATE level_contacts SET otp = ? WHERE mobile = ? AND contact_type = 'client' AND is_active = 1"; @@ -637,6 +664,12 @@ class RestAuthenticationController extends AdminController $data->otp = $otp; 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']], 429); // Too Many Requests + } @@ -770,6 +803,8 @@ class RestAuthenticationController extends AdminController $decoded = json_decode($HRAccessData['allowed_modules'], true); $getAllhrData[$key]['allowed_modules'] = $decoded; + $HRAccessData['pre_client_id'] = md5($HRAccessData['pre_client_id']); + $HRAccessData['post_client_id'] = md5($HRAccessData['post_client_id']); $token = JWTToken::encode($HRAccessData); $getAllhrData[$key]['token'] = $token; @@ -951,6 +986,12 @@ class RestAuthenticationController extends AdminController $old_mpin = $this->request->getJSON()->old_mpin; $mpin = $this->request->getJSON()->new_mpin; + if($old_mpin == $mpin) + { + $result = ['mpin_verification' => false , 'message' => "New MPIN must be different from the old MPIN"]; + return $this->respond(['status' => 'failed','code' => 404,'data' => $result],200); + } + $empdata = RestAuthHelper::getPreAndPostDataByEmailOrMobile(['email_id' => $email_id, 'mobile_number' => $mobile_number, 'old_mpin' => $old_mpin ]); // print_r($empdata); die; @@ -959,7 +1000,7 @@ class RestAuthenticationController extends AdminController log_message('error', ' '); log_message('error', '************************ PRE END ********************************'); - $result = ['user_verification' => false , 'message' => "User not found"]; + $result = ['mpin_verification' => false , 'message' => "Old MPIN is incorrect"]; return $this->respond(['status' => 'failed','code' => 404,'data' => $result],200); } @@ -1238,7 +1279,7 @@ class RestAuthenticationController extends AdminController $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - checkMpin: Mpin - Exist"); log_message('error', ' '); log_message('error', '************************ PRE END ********************************'); - return $this->respond(['status' => 'success','code' => 200,'data' => "", 'message' => "Mpin - exist" , 'Mpin' =>$employeeData["mpin"], 'is_mpin_skipped' => $employeeData['is_mpin_skipped'], 'is_biometric_enabled' => $employeeData['is_biometric_enabled']],200); + return $this->respond(['status' => 'success','code' => 200,'data' => "", 'message' => "Mpin - exist" , 'Mpin' =>$employeeData["mpin"], 'is_mpin_skipped' => $employeeData['is_mpin_skipped'] ?? 0, 'is_biometric_enabled' => $employeeData['is_biometric_enabled'] ?? 0 ],200); } else { $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - checkMpin: Mpin - not found in PRE so call the thirdpartapi to the POST to check the MPIN"); log_message('error', ' '); @@ -1868,5 +1909,55 @@ 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); + } + } diff --git a/app/Filters/AclFilter.php b/app/Filters/AclFilter.php new file mode 100644 index 0000000..0f6e0c1 --- /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/Filters/AuthApiRateLimitFilter.php b/app/Filters/AuthApiRateLimitFilter.php new file mode 100644 index 0000000..fd2efca --- /dev/null +++ b/app/Filters/AuthApiRateLimitFilter.php @@ -0,0 +1,131 @@ +post('api/auth/verify-otp', 'AuthController::verifyOtp', ['filter' => 'authApiRateLimit']); + * + * Register in app/Config/Filters.php: + * 'AuthApiRateLimitFilter' => \App\Filters\AuthApiRateLimitFilter::class + */ +class AuthApiRateLimitFilter implements FilterInterface +{ + protected RateLimiterService $limiter; + + public function __construct() + { + $this->limiter = new RateLimiterService(); + } + + // ------------------------------------------------------------------------- + // BEFORE — runs before the controller + // ------------------------------------------------------------------------- + + public function before(RequestInterface $request, $arguments = null) + { + // $this->limiter->unblockUser('9698262411');die; + $fingerprint = generateFingerprint(exclude_ua: true); + // echo $fingerprint;die; + // 1. IP-level check + $ipResult = $this->limiter->checkIp($fingerprint, 'authApi'); + if ($ipResult) { + return $this->jsonResponse($ipResult); + } + + // 2. User-level block check (identity may not be present yet on first hit) + $identity = resolveIdentity($request); + // echo $identity;die; + if ($identity) { + $userResult = $this->limiter->checkUser($identity); + if ($userResult) { + return $this->jsonResponse($userResult); + } + } + + // Store resolved identity in request for use in after() + if ($identity) { + $request->setGlobal('rateLimitIdentity', $identity); + } + $request->setGlobal('rateLimitFingerprint', $fingerprint); + + return null; // pass through + } + + // ------------------------------------------------------------------------- + // AFTER — runs after the controller; records failures on bad responses + // ------------------------------------------------------------------------- + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + + // Routes where rate-limit failure should be recorded + // $allowedRoutes = [ + // 'employeeRest/verifyEmployeeNumber', + // 'employeeRest/getVerifiedUserData', + // 'employeeRest/verifyEmployeeEmailId', + // 'employeeRest/verifyHrWithMobileNumber', + // 'employeeRest/verifyHrWithEmail', + // 'employeeRest/verifyHrWithEmail', + // 'employeeRest/getVerifiedHrData', + // ]; + + // $currentPath = service('request')->getPath(); + // if (!in_array($currentPath, $allowedRoutes)) { + // return; // Don't record failures for unrelated routes + // } + + // Only act on failed responses (4xx from auth failures) + $statusCode = $response->getStatusCode(); + // print_r($response);die(); + if ($statusCode < 400 || $statusCode === 429 || $statusCode === 403 || $statusCode === 451) { + return; // 2xx/3xx = success; 429/403/451 already handled + } + + $fingerprint = $request->getVar('rateLimitFingerprint') ?? generateFingerprint(exclude_ua: true); + + $identity = $request->getVar('rateLimitIdentity') + ?? resolveIdentity($request); + + // Record failure at IP level + $this->limiter->recordIpFailure($fingerprint); + + // Record failure at user level + if ($identity) { + $this->limiter->recordUserFailure($identity, 'authApi'); + } + } + + /** + * Build and return a JSON response for blocked/throttled requests. + */ + protected function jsonResponse(array $result): ResponseInterface + { + $response = service('response'); + $response->setStatusCode($result['status']); + $response->setContentType('application/json'); + $response->setBody(json_encode([ + 'success' => false, + 'error' => [ + 'code' => strtoupper('RATE_LIMIT_' . $result['level']), + 'message' => $result['message'], + 'type' => $result['type'] ?? 'request', + ], + ])); + return $response; + } +} diff --git a/app/Filters/AuthJWT.php b/app/Filters/AuthJWT.php index 262a5e5..4eac9e5 100755 --- a/app/Filters/AuthJWT.php +++ b/app/Filters/AuthJWT.php @@ -18,65 +18,116 @@ use App\Models\LevelContactModel; class AuthJWT implements FilterInterface { + // public function before(RequestInterface $request, $arguments = null) + // { + // $jwt = $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(); + // } + // } + public function before(RequestInterface $request, $arguments = null) { - $jwt = $request->getHeaderLine('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(); + $id = $decoded['pre_hr_id'] ?? null; + } + + $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) diff --git a/app/Filters/AuthMVC.php b/app/Filters/AuthMVC.php index 0a9f7a6..285f586 100755 --- a/app/Filters/AuthMVC.php +++ b/app/Filters/AuthMVC.php @@ -5,12 +5,27 @@ 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 = generateFingerprint(); + + if (session()->get('fingerprint') !== $fp) { + return AuthLogout::logout(); } } diff --git a/app/Filters/Cors.php b/app/Filters/Cors.php index c47f276..c9ea212 100644 --- a/app/Filters/Cors.php +++ b/app/Filters/Cors.php @@ -38,7 +38,7 @@ class Cors implements FilterInterface * * @var string */ - protected string $allowedMethods = 'GET,POST,PUT,PATCH,DELETE,OPTIONS'; + protected string $allowedMethods = 'GET,POST,OPTIONS'; /** * HTTP headers allowed in CORS requests @@ -107,11 +107,11 @@ class Cors implements FilterInterface ); } - $this->log('CORS filter initialized', [ - 'allowed_origins' => $this->allowedOrigins, - 'allow_credentials' => $this->allowCredentials, - 'allowed_methods' => $this->allowedMethods, - ]); + // $this->log('CORS filter initialized', [ + // 'allowed_origins' => $this->allowedOrigins, + // 'allow_credentials' => $this->allowCredentials, + // 'allowed_methods' => $this->allowedMethods, + // ]); } /** @@ -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/GlobalPostFileUploadGuard.php b/app/Filters/GlobalPostFileUploadGuard.php new file mode 100644 index 0000000..35476b6 --- /dev/null +++ b/app/Filters/GlobalPostFileUploadGuard.php @@ -0,0 +1,147 @@ + ['jpg', 'jpeg'], + 'image/png' => ['png'], + 'image/gif' => ['gif'], + 'image/webp' => ['webp'], + 'image/svg+xml' => ['svg'], + 'application/pdf' => ['pdf'], + 'application/msword' => ['doc'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'], + 'application/vnd.oasis.opendocument.text' => ['odt'], + 'text/rtf' => ['rtf'], + 'application/rtf' => ['rtf'], + 'application/vnd.ms-excel' => ['xls'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'], + 'application/vnd.oasis.opendocument.spreadsheet' => ['ods'], + 'text/csv' => ['csv'], + 'application/csv' => ['csv'], + 'text/plain' => ['txt', 'csv'], + ]; + + protected array $blockedExtensions = [ + 'php', 'phtml', 'html', 'pht', 'phar', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps', + 'cgi', 'fcgi', 'pl', 'py', 'rb', 'lua', 'tcl', 'go', 'rs', 'jar', 'class', + 'exe', 'dll', 'com', 'bat', 'cmd', 'msi', 'vbs', 'ps1', 'scr', + 'sh', 'bash', 'zsh', 'apk', 'app', 'deb', 'rpm', 'bin', 'run', + 'js', 'mjs', 'jsp', 'asp', 'aspx', 'cer', 'swf', + 'env', 'ini', 'user.ini', 'htaccess', 'htpasswd', 'conf', 'config', 'log', 'sql', + 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso', + 'lnk', 'url', 'reg', 'sys', 'drv', 'vxd', 'tmp', 'bak', 'old', 'backup', 'key', 'pem' + ]; + + public function before(RequestInterface $request, $arguments = null) + { + if ($request->getMethod() !== 'post') { + return; + } + + $files = $request->getFiles(); + if (empty($files)) { + return; + } + + foreach ($files as $inputName => $fileData) { + $this->validateFileInput($fileData, $inputName); + } + } + + private function validateFileInput($fileData, string $inputName): void + { + if (is_array($fileData)) { + foreach ($fileData as $file) { + $this->validateSingleFile($file, $inputName); + } + } else { + $this->validateSingleFile($fileData, $inputName); + } + } + + private function validateSingleFile($file, string $inputName): void + { + $request = Services::request(); + $clientIp = $request->getIPAddress(); + $uri = $request->getUri()->getPath(); + + if (!$file->isValid()) { + if ($file->getError() === UPLOAD_ERR_INI_SIZE || $file->getError() === UPLOAD_ERR_FORM_SIZE) { + $this->block("File exceeds server-side size limit", $clientIp, $uri, $inputName, $file->getClientName(), 'unknown', 'unknown', 0); + } + return; + } + + $originalName = $file->getClientName(); + $extension = strtolower($file->getExtension()); + $mime = $file->getMimeType(); + $size = $file->getSize(); + + // --- 1. Fixed Null Byte & Path Traversal Check --- + if (preg_match('/\0|[\/\\\]/', $originalName)) { + $this->block("Malicious filename characters", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size); + } + + // --- 2. Double Extension Attack Check --- + if (preg_match('/\.(php|phtml|html|phar|exe|sh|bat|cmd|js|jsp|asp|aspx|py|pl)\./i', $originalName)) { + $this->block("Double extension attack", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size); + } + + // --- 3. Forbidden Extension --- + if (in_array($extension, $this->blockedExtensions, true)) { + $this->block("Forbidden extension", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size); + } + + // --- 4. File Size Limit --- + if ($size > $this->maxFileSize) { + $this->block("File too large", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size); + } + + // --- 5. MIME Allow-list Check --- + if (!array_key_exists($mime, $this->allowedMimeMap)) { + $this->block("MIME type not allowed ($mime)", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size); + } + + // --- 6. MIME-Extension Consistency --- + if (!in_array($extension, $this->allowedMimeMap[$mime], true)) { + $this->block("MIME-extension mismatch", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size); + } + } + + private function block(string $reason, string $ip, string $uri, string $field, string $filename, string $mime, string $ext, int $size): void + { + log_message('critical', + '[UPLOAD_BLOCKED] {reason} | IP: {ip} | URI: {uri} | Field: {field} | File: {file} | MIME: {mime} | EXT: {ext} | SIZE: {size}', + ['reason'=>$reason, 'ip'=>$ip, 'uri'=>$uri, 'field'=>$field, 'file'=>$filename, 'mime'=>$mime, 'ext'=>$ext, 'size'=>$size] + ); + + $response = Services::response(); + $response->setStatusCode(403) + ->setJSON([ + 'status' => 'error', + 'message' => 'File upload rejected: Security policy violation.', + 'debug' => (ENVIRONMENT === 'development') ? $reason : null + ]) + ->send(); + exit; + } + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) {} +} \ No newline at end of file diff --git a/app/Filters/JwtApiRateLimitFilter.php b/app/Filters/JwtApiRateLimitFilter.php new file mode 100644 index 0000000..6e0b84f --- /dev/null +++ b/app/Filters/JwtApiRateLimitFilter.php @@ -0,0 +1,154 @@ +get('api/profile', 'ProfileController::index', ['filter' => 'jwtApiRateLimit']); + * + * Register in app/Config/Filters.php: + * 'JwtApiRateLimitFilter' => \App\Filters\JwtApiRateLimitFilter::class + */ +class JwtApiRateLimitFilter implements FilterInterface +{ + protected RateLimiterService $limiter; + + public function __construct() + { + $this->limiter = new RateLimiterService(); + } + + // ------------------------------------------------------------------------- + // BEFORE — runs before the controller + // ------------------------------------------------------------------------- + + public function before(RequestInterface $request, $arguments = null) + { + $fingerprint = generateFingerprint(exclude_ua: true); + // echo $fingerprint;die; + // 1. IP-level throttle + block check + $ipResult = $this->limiter->checkIp($fingerprint, 'jwtApi'); + if ($ipResult) { + return $this->jsonResponse($ipResult); + } + + // 2. Resolve user identity from JWT + // Uses your existing JWT helper functions. + // If neither returns a value, fall back to IP-only limiting. + // $identity = $this->resolveIdentityFromJwt(); + $identity = ''; + + if ($identity) { + // User-level throttle (request count based for JWT routes) + $userResult = $this->limiter->checkUserThrottle($identity, 'jwtApi'); + if ($userResult) { + return $this->jsonResponse($userResult); + } + } + + // Stash for after() use + $request->setGlobal('rateLimitFingerprint', $fingerprint); + if ($identity) { + $request->setGlobal('rateLimitIdentity', $identity); + } + + return null; // pass through + } + + // ------------------------------------------------------------------------- + // AFTER — records IP failure on controller-level bad responses + // ------------------------------------------------------------------------- + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + $statusCode = $response->getStatusCode(); + + // Only act on auth-related failures from the controller (401, 422, etc.) + // 429/403/451 are already handled by before(); skip 2xx/3xx. + if ($statusCode < 400 || in_array($statusCode, [429, 403, 451])) { + return; + } + + $fingerprint = $request->getGlobal('rateLimitFingerprint') ?? generateFingerprint(exclude_ua: true); + + // $identity = $request->getGlobal('rateLimitIdentity') ?? $this->resolveIdentityFromJwt(); + $identity = $request->getGlobal('rateLimitIdentity') ?? ''; + + $this->limiter->recordIpFailure($fingerprint); + + if ($identity) { + $this->limiter->recordUserFailure($identity, 'jwtApi'); + } + } + + // ------------------------------------------------------------------------- + // HELPERS + // ------------------------------------------------------------------------- + + /** + * Resolve user identity from JWT using your existing helper functions. + * Tries email first, then mobile. Returns null if JWT is absent/invalid. + * + * IMPORTANT: Replace getEmailFromJWT() / getMobileFromJWT() with your + * actual function names if they differ. + */ + protected function resolveIdentityFromJwt(): ?string + { + try { + // Try email from JWT + if (function_exists('getEmailFromJWT')) { + $email = getEmailFromJWT(); + if ($email) { + return strtolower(trim($email)); + } + } + + // Try mobile from JWT + if (function_exists('getMobileFromJWT')) { + $mobile = getMobileFromJWT(); + if ($mobile) { + return trim($mobile); + } + } + } catch (\Throwable $e) { + // JWT invalid or expired — fall through to IP-only limiting + log_message('debug', '[RateLimiter] JWT identity resolution failed: ' . $e->getMessage()); + } + + return null; + } + + /** + * Build and return a JSON response for blocked/throttled requests. + */ + protected function jsonResponse(array $result): ResponseInterface + { + $response = service('response'); + $response->setStatusCode($result['status']); + $response->setContentType('application/json'); + $response->setBody(json_encode([ + 'success' => false, + 'error' => [ + 'code' => strtoupper('RATE_LIMIT_' . $result['level']), + 'message' => $result['message'], + 'type' => $result['type'] ?? 'request', + ], + ])); + return $response; + } +} diff --git a/app/Filters/RateLimitFilter.php b/app/Filters/RateLimitFilter.php new file mode 100644 index 0000000..31efe81 --- /dev/null +++ b/app/Filters/RateLimitFilter.php @@ -0,0 +1,34 @@ +getIPAddress()); + + if ($throttler->check($key, 25, MINUTE) === false) { + return service('response') + ->setStatusCode(429) + ->setJSON([ + 'status' => 'error', + 'message' => 'Too many requests. Try again later.' + ]); + } + } + + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + // nothing + } +} diff --git a/app/Filters/SecurityInputFilter.php b/app/Filters/SecurityInputFilter.php new file mode 100644 index 0000000..c23f790 --- /dev/null +++ b/app/Filters/SecurityInputFilter.php @@ -0,0 +1,130 @@ +/i', + + // JavaScript execution vectors + '/javascript\s*:/i', + '/vbscript\s*:/i', + '/data\s*:\s*text\/html/i', + + // Inline event handlers (strong signal) + '/on\w+\s*=\s*["\']?/i', + + // Dangerous HTML tags + '/<\s*iframe\b/i', + '/<\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', + + // SVG-based execution (modern bypass) + '/<\s*svg\b/i', + '/<\s*math\b/i', + + // Meta refresh redirect + '/<\s*meta\b[^>]*http-equiv\s*=\s*["\']?refresh/i', + + // HTML injection via src/href + '/<\s*\w+\b[^>]*(src|href)\s*=\s*["\']?\s*(javascript|data)\s*:/i' + ]; + + + public function before(RequestInterface $request, $arguments = null) + { + $logger = Services::mylogger(); + $response = Services::response(); + + // Collect all user-controlled input + $inputs = array_merge( + $request->getGet(), + $request->getPost() + ); + + if (empty($inputs)) { + return; + } + + foreach ($inputs as $field => $value) { + if (is_array($value)) { + $value = json_encode($value); + } + + // Step 1: Canonicalization (VERY IMPORTANT) + $canonical = $this->canonicalize($value); + + // Step 2: Trim (hygiene) + $canonical = trim($canonical); + + // Step 3: Detection (signal-only) + if ($this->detectXss($canonical)) { + + // 🔐 Log intent, not data + $logger->logme('critical','SECURITY_BLOCKED_REQUEST - '. json_encode([ + 'ip' => $request->getIPAddress(), + 'method' => $request->getMethod(), + 'uri' => current_url(), + 'field' => $field, + 'attack' => 'XSS_PATTERN', + 'length' => strlen($canonical), + 'hash' => hash('sha256', $canonical), + ])); + + // ⛔ Block request + return $response + ->setStatusCode(403) + ->setJSON([ + 'status' => 403, + 'error' => 'Forbidden', + 'message' => 'Malicious input detected' + ]); + } + } + } + + public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) + { + // no-op + } + + /** + * Canonicalization prevents encoded bypass + */ + private function canonicalize(string $value): string + { + $value = urldecode($value); + $value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + // Remove invisible control characters + return preg_replace('/[\x00-\x1F\x7F]/u', '', $value); + } + + private function detectXss(string $value): bool + { + foreach ($this->xssPatterns as $pattern) { + if (preg_match($pattern, $value)) { + return true; + } + } + return false; + } +} diff --git a/app/Helpers/EmployeeHelper.php b/app/Helpers/EmployeeHelper.php index e33e6dd..15a3f36 100644 --- a/app/Helpers/EmployeeHelper.php +++ b/app/Helpers/EmployeeHelper.php @@ -43,12 +43,16 @@ class EmployeeHelper if($is_addon_value == 1){ $is_addon_value = 0 ; }else{ $is_addon_value = 1 ; } - $employeeData = $this->employeeModel->where('emp_code',$data[0]->emp_code) - ->where('client_id',$data[0]->client_id) - ->where('client_branch_id',$data[0]->client_branch_id) - ->where('is_active', 1 ) - ->where('is_addon_value',$is_addon_value) - ->findAll(); + $employeeData = $this->employeeModel + ->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->where('employees.emp_code',$data[0]->emp_code) + ->where('employees.client_id',$data[0]->client_id) + ->where('employees.client_branch_id',$data[0]->client_branch_id) + ->where('employees.is_active', 1 ) + ->where('employee_polices.client_policy_id', $client_policy_id) + ->where('employee_polices.is_active', 1) + ->where('is_addon_value',$is_addon_value) + ->findAll(); $array = $array[0]; diff --git a/app/Helpers/HttpRequestHelper.php b/app/Helpers/HttpRequestHelper.php index a3a3da6..53268b3 100755 --- a/app/Helpers/HttpRequestHelper.php +++ b/app/Helpers/HttpRequestHelper.php @@ -4,24 +4,176 @@ namespace App\Helpers; class HttpRequestHelper { - public static function getRequestInfo() + public static function getRequestInfo(): array { $request = service('request'); + + $uaString = $request->getHeaderLine('User-Agent'); + + // Detect platform & browser using robust fallback logic + [$platform, $browser] = self::detectFromUserAgent($uaString); + $data = [ - 'ip' => $request->getIPAddress(), - 'platform' => $request->getUserAgent()->getPlatform(), - 'browser' => ($request->getUserAgent()->getBrowser().' '.$request->getUserAgent()->getVersion()), - 'method' => $request->getMethod(), - 'endpoint' => $request->uri->getPath(), - 'getparams' => $request->uri->getSegments(), - 'postparams' => $request->getPost() + 'ip' => $request->getIPAddress(), + 'real_ip' => getRealClientIP(), + 'platform' => $platform, + 'browser' => $browser, + 'method' => strtoupper($request->getMethod()), + 'endpoint' => $request->uri->getPath(), + 'getparams' => json_encode($request->uri->getSegments(), JSON_UNESCAPED_UNICODE), + 'postparams' => self::sanitizePostForLog($request->getPost()), ]; - $data['method'] = (isset($data['method']) ? strtoupper($data['method']) : $data['method']); - $data['getparams'] = is_array($data['getparams']) ? json_encode($data['getparams']) : $data['getparams']; - $data['postparams'] = is_array($data['postparams']) ? json_encode($data['postparams']) : $data['postparams']; return $data; } - public static function add($payload) - {return $payload['a'] + $payload['b'];} + + /** + * Detect platform & browser from UA string (reliable fallback) + */ + private static function detectFromUserAgent(string $ua): array + { + $uaLower = strtolower($ua); + + // ========================= + // PLATFORM DETECTION + // ========================= + $platform = 'Unknown'; + + if (str_contains($uaLower, 'windows nt 11') || str_contains($uaLower, 'windows 11')) { + $platform = 'Windows 11'; + } elseif (str_contains($uaLower, 'windows nt 10')) { + $platform = 'Windows 10'; + } elseif (str_contains($uaLower, 'windows nt 6.3')) { + $platform = 'Windows 8.1'; + } elseif (str_contains($uaLower, 'windows nt 6.2')) { + $platform = 'Windows 8'; + } elseif (str_contains($uaLower, 'windows nt 6.1')) { + $platform = 'Windows 7'; + } elseif (str_contains($uaLower, 'windows nt 6.0')) { + $platform = 'Windows Vista'; + } elseif (str_contains($uaLower, 'windows nt 5.1') || str_contains($uaLower, 'windows xp')) { + $platform = 'Windows XP'; + } elseif (str_contains($uaLower, 'android')) { + $platform = 'Android'; + } elseif (str_contains($uaLower, 'iphone')) { + $platform = 'iOS (iPhone)'; + } elseif (str_contains($uaLower, 'ipad')) { + $platform = 'iOS (iPad)'; + } elseif (str_contains($uaLower, 'ipod')) { + $platform = 'iOS (iPod)'; + } elseif (str_contains($uaLower, 'mac os') || str_contains($uaLower, 'macintosh')) { + $platform = 'Mac OS'; + } elseif (str_contains($uaLower, 'cros')) { + $platform = 'Chrome OS'; + } elseif (str_contains($uaLower, 'linux')) { + $platform = 'Linux'; + } elseif (str_contains($uaLower, 'freebsd')) { + $platform = 'FreeBSD'; + } elseif (str_contains($uaLower, 'openbsd')) { + $platform = 'OpenBSD'; + } elseif (str_contains($uaLower, 'netbsd')) { + $platform = 'NetBSD'; + } elseif (str_contains($uaLower, 'unix')) { + $platform = 'Unix'; + } elseif (str_contains($uaLower, 'symbian')) { + $platform = 'Symbian'; + } elseif (str_contains($uaLower, 'blackberry')) { + $platform = 'BlackBerry'; + } elseif (str_contains($uaLower, 'tizen')) { + $platform = 'Tizen'; + } elseif (str_contains($uaLower, 'webos')) { + $platform = 'WebOS'; + } elseif (str_contains($uaLower, 'kaios')) { + $platform = 'KaiOS'; + } elseif (str_contains($uaLower, 'harmonyos')) { + $platform = 'HarmonyOS'; + } elseif (str_contains($uaLower, 'watchos')) { + $platform = 'watchOS'; + } elseif (str_contains($uaLower, 'tv os') || str_contains($uaLower, 'tvos')) { + $platform = 'tvOS'; + } + + // ========================= + // BROWSER / CLIENT DETECTION + // ========================= + $browser = 'Unknown'; + + // Bots & tools first + if (preg_match('/googlebot|bingbot|slurp|duckduckbot|baiduspider|yandexbot|sogou|exabot|facebot|ia_archiver/i', $ua)) { + $browser = 'Search Bot'; + } elseif (preg_match('/postman/i', $ua)) { + $browser = 'Postman'; + } elseif (preg_match('/insomnia/i', $ua)) { + $browser = 'Insomnia'; + } elseif (preg_match('/curl/i', $ua)) { + $browser = 'curl'; + } elseif (preg_match('/wget/i', $ua)) { + $browser = 'wget'; + } + + // Real browsers + elseif (preg_match('/edg\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Edge ' . $m[1]; + } elseif (preg_match('/opr\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Opera ' . $m[1]; + } elseif (preg_match('/vivaldi\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Vivaldi ' . $m[1]; + } elseif (preg_match('/brave\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Brave ' . $m[1]; + } elseif (preg_match('/chrome\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Chrome ' . $m[1]; + } elseif (preg_match('/firefox\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Firefox ' . $m[1]; + } elseif (preg_match('/safari\/([\d\.]+)/i', $ua, $m)) { + $browser = 'Safari ' . $m[1]; + } elseif (preg_match('/msie\s([\d\.]+)/i', $ua, $m) || preg_match('/trident\/.*rv:([\d\.]+)/i', $ua, $m)) { + $browser = 'Internet Explorer ' . $m[1]; + } + + // In-app browsers + elseif (preg_match('/fbav|fban/i', $ua)) { + $browser = 'Facebook In-App Browser'; + } elseif (preg_match('/instagram/i', $ua)) { + $browser = 'Instagram In-App Browser'; + } elseif (preg_match('/linkedinapp/i', $ua)) { + $browser = 'LinkedIn In-App Browser'; + } elseif (preg_match('/twitter/i', $ua)) { + $browser = 'Twitter/X In-App Browser'; + } + + return [$platform, $browser]; + } + + + /** + * Remove sensitive fields before logging POST + */ + private static function sanitizePostForLog(array $post): string + { + if (empty($post)) { + return json_encode([]); + } + + $sensitiveKeys = [ + 'password', 'pass', 'pwd', + 'token', 'access_token', 'refresh_token', + 'secret', 'api_key', 'authorization', + 'otp', 'pin' + ]; + + foreach ($post as $k => $v) { + foreach ($sensitiveKeys as $sk) { + if (stripos($k, $sk) !== false) { + $post[$k] = '***MASKED***'; + } + } + } + + return json_encode($post, JSON_UNESCAPED_UNICODE); + } + + public static function add($payload) + { + return $payload['a'] + $payload['b']; + } } diff --git a/app/Helpers/JWTToken.php b/app/Helpers/JWTToken.php index 41d76b5..f7b32ba 100755 --- a/app/Helpers/JWTToken.php +++ b/app/Helpers/JWTToken.php @@ -18,24 +18,29 @@ 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; try{ $token = JWT::encode($request_data ,$secret_Key,'HS512'); $id = $request_data['id']; - $data["token_time_out"] = time() + getenv('TOKENTIMEOUT'); + $update["token_time_out"] = time() + getenv('TOKENTIMEOUT'); if(isset($data['emp_code'])){ $model = new EmployeeModel(); - $model->update($id, $data); + $model->update($id, $update); }else{ $models = new LevelContactModel(); - $models->update($id, $data); + $id = $request_data['pre_hr_id']; + $models->update($id, $update); } @@ -47,30 +52,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/session_helper.php b/app/Helpers/session_helper.php index 3d312d6..ccbdd51 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; @@ -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/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 6adc430..bac046f 100755 --- a/app/Helpers/utility_helper.php +++ b/app/Helpers/utility_helper.php @@ -557,7 +557,7 @@ if (!function_exists('change_date_format')) { $date = DateTime::createFromFormat($source_format, $date_str); if (!$date) { // throw new Exception("Invalid date string for source format: $source_format"); - log_message('error', "❌ Date format error : Invalid date string | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}"); + // log_message('error', "❌ Date format error : Invalid date string | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}"); return null; } return $date->format($output_format); @@ -568,7 +568,7 @@ if (!function_exists('change_date_format')) { $date = DateTime::createFromFormat($source_format, $date_str); if (!$date) { // throw new Exception("Invalid date string for source format: $source_format"); - log_message('error', "❌ Date format error : Invalid date string | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}"); + // log_message('error', "❌ Date format error : Invalid date string | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}"); return null; } return $date->format('Y-m-d'); // MySQL default format @@ -585,15 +585,15 @@ if (!function_exists('change_date_format')) { // If no format matches, throw an exception $allowed_placeholders = implode(', ', $allowed_formats); // throw new Exception("Invalid date string format. Allowed formats: $allowed_placeholders"); - log_message( - 'error', - "❌ Date format error: Invalid date string. Allowed formats: {$allowed_placeholders} | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}" - ); + // log_message( + // 'error', + // "❌ Date format error: Invalid date string. Allowed formats: {$allowed_placeholders} | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}" + // ); return null; } } catch (Exception $e) { // return "Error: " . $e->getMessage(); - log_message('error', "❌ Date format error: {$e->getMessage()} | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}"); + // log_message('error', "❌ Date format error: {$e->getMessage()} | Params => date_str: {$date_str}, source_format: {$source_format}, output_format: {$output_format}"); return null; } @@ -677,6 +677,32 @@ if (!function_exists('check_pay_by_employee_or_company')) { } +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]; + } +} + if (!function_exists('getLatestGMCPolicy')) { function getLatestGMCPolicy(array $empPolicy) @@ -709,3 +735,148 @@ if (!function_exists('getLatestGMCPolicy')) { } +if (!function_exists('validateExcelFile')) { + + function validateExcelFile($file) + { + $allowed = [ + 'application/vnd.ms-excel','application/vnd', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/octet-stream' + ]; + + + // if ($file->getError() !== UPLOAD_ERR_OK) return 'Upload error'; + // if ($file->getSize() > (16 * 1024 * 1024)) return 'File too large'; + // if (!in_array($file->getClientMimeType(), $allowed, true)) return 'Invalid file type'; + + if ($file->getError() !== UPLOAD_ERR_OK) return false; + if ($file->getSize() > (16 * 1024 * 1024)) return false; + if (!in_array($file->getClientMimeType(), $allowed, true)) return false; + + return true; + } +} +function getRealClientIP() +{ + $request = service('request'); + + if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) { + return $_SERVER['HTTP_CF_CONNECTING_IP']; + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; + } + + return $request->getIPAddress(); +} + +function generateFingerprint(bool $exclude_ua = false): string +{ + $request = service('request'); + + $ua = $request->getUserAgent()->getAgentString(); + // echo $ua; + // die; + $ip = getRealClientIP(); + + // Normalize localhost + if ($ip === '127.0.0.1' || $ip === '::1') { + $ipGroup = 'localhost'; + } + // IPv4 handling + elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + $parts = explode('.', $ip); + // Use /24 subnet (first 3 octets) + $ipGroup = $parts[0] . '.' . $parts[1] . '.' . $parts[2]; + } + // IPv6 handling + elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + // Use first 4 blocks of IPv6 (rough /64 grouping) + $blocks = explode(':', $ip); + $ipGroup = implode(':', array_slice($blocks, 0, 4)); + } + // Fallback + else { + $ipGroup = 'unknown'; + } + + // return $ua . '_' . $ipGroup; + if($exclude_ua){ + return hash('sha256', $ipGroup); + } + return hash('sha256', $ua . '|' . $ipGroup); + +} + + +/** + * Extract identity from POST body or GET params. + * Looks for 'email' or 'mobile_number'. + */ + function resolveIdentity($request): ?string + { + // Try POST body first + $email = $request->getPost('email'); + // print_r($email);die; + $mobile = $request->getPost('mobile_number'); + + // Fallback to GET params + if (! $email && ! $mobile) { + $email = $request->getGet('email'); + $mobile = $request->getGet('mobile_number'); + } + // Fallback to JSON params + if (! $email && ! $mobile) { + $req_data = $request->getJSON(); + // print_r( $req_data); + + $mobile = $req_data->mobile_number ?? null; + // return trim($mobile_number); + + $email = $req_data->email ?? null; + + if (!$email) + { + $email = $req_data->email_id ?? null; + } + // return trim($email); + } + + if ($email) { + return strtolower(trim($email)); + } + + if ($mobile) { + return trim($mobile); + } + + return null; + } + + + function recordRateLimitFailure(string $context = 'authApi'): void + { + /** @var IncomingRequest $request */ + $request = \Config\Services::request(); + + $limiter = \Config\Services::limiter(); // or your custom limiter service + + $fingerprint = $request->getVar('rateLimitFingerprint') + ?? generateFingerprint(exclude_ua: true); + + + + $identity = $request->getVar('rateLimitIdentity') + ?? resolveIdentity($request); + + // Record IP-level failure + $limiter->recordIpFailure($fingerprint); + + // Record user-level failure + if (!empty($identity)) { + $limiter->recordUserFailure($identity, $context); + } + } \ No newline at end of file diff --git a/app/Libraries/AuthLogout.php b/app/Libraries/AuthLogout.php new file mode 100644 index 0000000..ac7786c --- /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 + null, + 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/app/Libraries/RateLimiterService.php b/app/Libraries/RateLimiterService.php new file mode 100644 index 0000000..3bcb564 --- /dev/null +++ b/app/Libraries/RateLimiterService.php @@ -0,0 +1,386 @@ +config = config('RateLimiter'); + $this->cache = \Config\Services::cache(); + } + + // ========================================================================= + // PUBLIC — IP LEVEL + // ========================================================================= + + /** + * Check & throttle by fingerprint (IP+UA based). + * Returns null on pass, or an array ['level'=>..., 'message'=>...] on block. + */ + public function checkIp(string $fingerprint, string $routeType = 'jwtApi'): ?array + { + // echo $fingerprint;die; + // 1. Is the IP already blocked? + $blockInfo = $this->getIpBlock($fingerprint); + // print_rr($blockInfo);die(); + if ($blockInfo) { + // Count hit while blocked → maybe escalate + $this->recordIpBlockHit($fingerprint, $blockInfo['level']); + return $this->blockedResponse('ip', $blockInfo['level']); + } + + // 2. Throttle check + $cfg = $this->config->ipBlock; + $countKey = $this->config->cacheKeys['ip_count'] . $fingerprint; + $count = (int) ($this->cache->get($countKey) ?? 0); + + if ($count === 0) { + $this->cache->save($countKey, 1, $cfg['window']); + } else { + $this->cache->save($countKey, $count + 1, $cfg['window']); + } + + if (($count + 1) > $cfg['limit']) { + // Over limit → record violation + $violated = $this->incrementIpViolation($fingerprint); + if ($violated >= $cfg['violation_soft']) { + $this->blockIp($fingerprint, 'soft'); + return $this->blockedResponse('ip', 'soft'); + } + return [ + 'level' => 'throttle', + 'message' => 'Too many requests. Please slow down.', + 'status' => $this->config->statusCodes['throttle'], + ]; + } + + return null; + } + + /** + * Record a "bad outcome" for IP (e.g. controller calls this after failed auth). + * Same escalation path as throttle violations. + */ + public function recordIpFailure(string $fingerprint): ?array + { + $blockInfo = $this->getIpBlock($fingerprint); + if ($blockInfo) { + $this->recordIpBlockHit($fingerprint, $blockInfo['level']); + return $this->blockedResponse('ip', $blockInfo['level']); + } + + $violated = $this->incrementIpViolation($fingerprint); + $cfg = $this->config->ipBlock; + + if ($violated >= $cfg['violation_soft']) { + $this->blockIp($fingerprint, 'soft'); + return $this->blockedResponse('ip', 'soft'); + } + + return null; + } + + /** + * Manually block an IP at a given level. + */ + public function blockIp(string $fingerprint, string $level = 'soft'): void + { + $cfg = $this->config->ipBlock; + $blockKey = $this->config->cacheKeys['ip_block'] . $fingerprint; + + $duration = $this->blockDuration($cfg, $level); + + $data = [ + 'level' => $level, + 'blocked_at' => time(), + 'fingerprint'=> $fingerprint, + 'ip' => getRealClientIP(), + ]; + + // Duration 0 = store for 10 years (permanent until manual unblock) + $ttl = $duration > 0 ? $duration : (10 * 365 * 24 * 3600); + $this->cache->save($blockKey, $data, $ttl); + } + + /** + * Manually unblock an IP. Clears block, violations, and counters. + */ + public function unblockIp(string $fingerprint): void + { + $keys = $this->config->cacheKeys; + $this->cache->delete($keys['ip_block'] . $fingerprint); + $this->cache->delete($keys['ip_violations'] . $fingerprint); + $this->cache->delete($keys['ip_count'] . $fingerprint); + $this->cache->delete($keys['ip_block_hits'] . $fingerprint); + } + + /** + * Get current IP block info or null if not blocked. + */ + public function getIpBlock(string $fingerprint): ?array + { + $blockKey = $this->config->cacheKeys['ip_block'] . $fingerprint; + $data = $this->cache->get($blockKey); + return $data ?: null; + } + + // ========================================================================= + // PUBLIC — USER LEVEL + // ========================================================================= + + /** + * Check if a user (by email or mobile) is blocked. + * Returns null on pass, or block response array on block. + */ + public function checkUser(string $identity): ?array + { + $blockInfo = $this->getUserBlock($identity); + if ($blockInfo) { + $this->recordUserBlockHit($identity, $blockInfo['level']); + return $this->blockedResponse('user', $blockInfo['level']); + } + return null; + } + + /** + * Record a failed attempt for a user identity. + * Called from controller after() or manually after a failed verification. + * Handles escalation: free → soft → medium → hard + */ + public function recordUserFailure(string $identity, string $routeType = 'authApi'): ?array + { + $blockInfo = $this->getUserBlock($identity); + + if ($blockInfo) { + // Already blocked — count hit and maybe escalate + $this->recordUserBlockHit($identity, $blockInfo['level']); + return $this->blockedResponse('user', $blockInfo['level']); + } + + // Not blocked yet — increment violation count + $violated = $this->incrementUserViolation($identity, $routeType); + $cfg = $this->config->userBlock; + $routeCfg = $this->config->{$routeType}; + + if ($violated >= $routeCfg['violation_soft']) { + $this->blockUser($identity, 'soft'); + return $this->blockedResponse('user', 'soft'); + } + + return null; + } + + /** + * Manually block a user identity at a given level. + */ + public function blockUser(string $identity, string $level = 'soft'): void + { + $cfg = $this->config->userBlock; + $blockKey = $this->config->cacheKeys['user_block'] . $this->hashIdentity($identity); + + $duration = $this->blockDuration($cfg, $level); + $ttl = $duration > 0 ? $duration : (10 * 365 * 24 * 3600); + + $data = [ + 'level' => $level, + 'blocked_at' => time(), + 'identity' => $identity, + ]; + + $this->cache->save($blockKey, $data, $ttl); + } + + /** + * Manually unblock a user identity. Independent — does NOT touch IP block. + */ + public function unblockUser(string $identity): void + { + $keys = $this->config->cacheKeys; + $hashed = $this->hashIdentity($identity); + + $this->cache->delete($keys['user_block'] . $hashed); + $this->cache->delete($keys['user_violations'] . $hashed); + $this->cache->delete($keys['user_count'] . $hashed); + $this->cache->delete($keys['user_block_hits'] . $hashed); + } + + /** + * Get current user block info or null if not blocked. + */ + public function getUserBlock(string $identity): ?array + { + $blockKey = $this->config->cacheKeys['user_block'] . $this->hashIdentity($identity); + $data = $this->cache->get($blockKey); + return $data ?: null; + } + + // ========================================================================= + // USER THROTTLE (for JWT API routes — request count based) + // ========================================================================= + + /** + * Throttle check for a known user on JWT routes. + * Increments request counter; if over limit records violation. + */ + public function checkUserThrottle(string $identity, string $routeType = 'jwtApi'): ?array + { + $blockCheck = $this->checkUser($identity); + if ($blockCheck) { + return $blockCheck; + } + + $cfg = $this->config->{$routeType}; + $hashed = $this->hashIdentity($identity); + $countKey = $this->config->cacheKeys['user_count'] . $hashed; + $count = (int) ($this->cache->get($countKey) ?? 0); + + if ($count === 0) { + $this->cache->save($countKey, 1, $cfg['window']); + } else { + $this->cache->save($countKey, $count + 1, $cfg['window']); + } + + if (($count + 1) > $cfg['limit']) { + $violated = $this->incrementUserViolation($identity, $routeType); + if ($violated >= $cfg['violation_soft']) { + $this->blockUser($identity, 'soft'); + return $this->blockedResponse('user', 'soft'); + } + return [ + 'level' => 'throttle', + 'message' => 'Too many requests. Please slow down.', + 'status' => $this->config->statusCodes['throttle'], + ]; + } + + return null; + } + + // ========================================================================= + // PRIVATE HELPERS + // ========================================================================= + + /** + * Increment IP violation counter and return new count. + */ + protected function incrementIpViolation(string $fingerprint): int + { + $key = $this->config->cacheKeys['ip_violations'] . $fingerprint; + $count = (int) ($this->cache->get($key) ?? 0) + 1; + // Keep violation record for the block window duration + $this->cache->save($key, $count, $this->config->ipBlock['window'] * 10); + return $count; + } + + /** + * Record a hit while IP is already blocked; escalate if thresholds met. + */ + protected function recordIpBlockHit(string $fingerprint, string $currentLevel): void + { + $cfg = $this->config->ipBlock; + $hitKey = $this->config->cacheKeys['ip_block_hits'] . $fingerprint; + $hits = (int) ($this->cache->get($hitKey) ?? 0) + 1; + $this->cache->save($hitKey, $hits, 10 * 365 * 24 * 3600); + + if ($currentLevel === 'soft' && $hits >= $cfg['medium_trigger']) { + $this->cache->delete($hitKey); + $this->blockIp($fingerprint, 'medium'); + } elseif ($currentLevel === 'medium' && $hits >= $cfg['hard_trigger']) { + $this->cache->delete($hitKey); + $this->blockIp($fingerprint, 'hard'); + } + } + + /** + * Increment user violation counter and return new count. + */ + protected function incrementUserViolation(string $identity, string $routeType): int + { + $hashed = $this->hashIdentity($identity); + $key = $this->config->cacheKeys['user_violations'] . $hashed; + $count = (int) ($this->cache->get($key) ?? 0) + 1; + $window = $this->config->{$routeType}['window'] ?? 180; + $this->cache->save($key, $count, $window * 10); + return $count; + } + + /** + * Record a hit while user is already blocked; escalate if thresholds met. + */ + protected function recordUserBlockHit(string $identity, string $currentLevel): void + { + $cfg = $this->config->userBlock; + $hashed = $this->hashIdentity($identity); + $hitKey = $this->config->cacheKeys['user_block_hits'] . $hashed; + $hits = (int) ($this->cache->get($hitKey) ?? 0) + 1; + $this->cache->save($hitKey, $hits, 10 * 365 * 24 * 3600); + + if ($currentLevel === 'soft' && $hits >= $cfg['medium_trigger']) { + $this->cache->delete($hitKey); + $this->blockUser($identity, 'medium'); + } elseif ($currentLevel === 'medium' && $hits >= $cfg['hard_trigger']) { + $this->cache->delete($hitKey); + $this->blockUser($identity, 'hard'); + } + } + + /** + * Resolve block duration from config based on level. + */ + protected function blockDuration(array $cfg, string $level): int + { + return match ($level) { + 'soft' => $cfg['soft_duration'], + 'medium' => $cfg['medium_duration'], + 'hard' => $cfg['hard_duration'], + default => 0, + }; + } + + /** + * Build a standardised blocked response array. + */ + protected function blockedResponse(string $type, string $level): array + { + $messages = [ + 'soft' => 'Your access has been temporarily suspended. Please contact support.', + 'medium' => 'Your access has been restricted due to repeated violations.', + 'hard' => 'Your access has been permanently blocked. Please contact support.', + ]; + + return [ + 'level' => $level, + 'type' => $type, + 'message' => $messages[$level] ?? 'Access denied.', + 'status' => $this->config->statusCodes[$level], + ]; + } + + /** + * Hash user identity (email or mobile) for cache key safety. + */ + protected function hashIdentity(string $identity): string + { + // return strtolower(trim($identity)); + return hash('sha256', strtolower(trim($identity))); + } +} diff --git a/app/Models/EmployeePolicyModel.php b/app/Models/EmployeePolicyModel.php index 306e6ee..92c2905 100755 --- a/app/Models/EmployeePolicyModel.php +++ b/app/Models/EmployeePolicyModel.php @@ -82,10 +82,19 @@ class EmployeePolicyModel extends Model } // ---------------------------------------------------------------------------------------------------------- - public function getEmployeePolicy($client_id = 0, $policy_id = 0, $status = [], $branch_id = 0, $emp_code = "", $emp_name = "") + public function getEmployeePolicy($client_id = 0, $policy_id = 0, $status = [], $branch_id = 0, $emp_code = "", $emp_name = "", $status_type = "") { // dd($status); + $status_query = "employee_polices.status"; // Default fallback + + if ($status_type == "hr") { + $status_query = "CASE + WHEN employee_polices.status = 'enrolled' THEN 'under process' + ELSE employee_polices.status + END as status"; + } + $result = $this->select([ 'employee_polices.*', 'policy_type.policy_type as policy_name', @@ -147,8 +156,8 @@ class EmployeePolicyModel extends Model THEN "Newly Added" ELSE NULL END) AS newly_added', - - ]) + $status_query + ], false) ->join('employees emp', 'employee_polices.employee_id = emp.id') ->join('client_policy cp', 'employee_polices.client_policy_id = cp.id') //cp - client policy ->join('policies pm', 'cp.policy_id = pm.id', 'left') //pm - policy master @@ -163,8 +172,10 @@ class EmployeePolicyModel extends Model ->orderBy('employee_polices.employee_id', 'ASC'); - // Conditionally add where clauses - if ($client_id != 0 && !empty($client_id)) { + // Conditionally add where clauses + if (is_string($client_id) && preg_match('/^[a-f0-9]{32}$/i', $client_id)) { + $result->where('MD5(emp.client_id)', $client_id); + } else if ($client_id != 0 && !empty($client_id)) { $result->where('emp.client_id', $client_id); } if ($branch_id != 0 && !empty($branch_id)) { diff --git a/app/Models/FileModel.php b/app/Models/FileModel.php index 2029ea9..c61a23b 100755 --- a/app/Models/FileModel.php +++ b/app/Models/FileModel.php @@ -23,6 +23,7 @@ class FileModel extends Model "updated_by", "enrollment_open_date", "enrollment_close_date", + "hr_id", ]; diff --git a/app/Views/UserList.php b/app/Views/UserList.php index be654b8..b526ca6 100755 --- a/app/Views/UserList.php +++ b/app/Views/UserList.php @@ -290,7 +290,7 @@ $(document).ready(function () { var student_id = $(this).attr('data-id'); $.get(''+student_id, function (data) { console.log(data); - toastr.success('User removed successfully', 'success'); + toastr.success('User removed successfully', 'Success'); window.location.reload() }) } diff --git a/app/Views/cd_master_list.php b/app/Views/cd_master_list.php index 92bde59..f8a3278 100755 --- a/app/Views/cd_master_list.php +++ b/app/Views/cd_master_list.php @@ -312,7 +312,7 @@ document.addEventListener("DOMContentLoaded", function () { has('success')) : ?> - toastr.success('getFlashdata('success') ?>', 'success'); + toastr.success('getFlashdata('success') ?>', 'Success'); var endDatePicker = flatpickr("#opening_date", { @@ -410,7 +410,7 @@ document.addEventListener("DOMContentLoaded", function () { console.log(res) if(res.status == true){ - toastr.warning(res.message, 'warning'); + toastr.warning(res.message, 'Warning'); $('#cd_ac_no').val(''); } }, @@ -446,10 +446,10 @@ document.addEventListener("DOMContentLoaded", function () { // console.log(res.status == true); if(res){ if (res.status == true) { - toastr.success('CD removed successfully.', 'success'); + toastr.success('CD removed successfully.', 'Success'); location.reload(); } else { - toastr.warning('Failed to remove CD.', 'warning'); + toastr.warning('Failed to remove CD.', 'Warning'); } } }, diff --git a/app/Views/client_branch.php b/app/Views/client_branch.php index 449d1e8..b8e9f29 100755 --- a/app/Views/client_branch.php +++ b/app/Views/client_branch.php @@ -609,7 +609,7 @@ $('body').on('click', '.btnBranchEdit', function() { setTimeout(function() { $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); - toastr.warning('Something Wrong!', 'warning'); + toastr.warning('Something Wrong!', 'Warning'); }, 1000); } }); @@ -811,10 +811,10 @@ function removeClientBranch(element) { // console.log(res.status == true); if (res) { if (res.status == true) { - toastr.success(res.message, 'success'); + toastr.success(res.message, 'Success'); location.reload(); } else { - toastr.warning(res.message, 'warning'); + toastr.warning(res.message, 'Warning'); } } }, @@ -824,7 +824,7 @@ function removeClientBranch(element) { setTimeout(function() { $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); - console.log('Something Wrong!', 'warning'); + console.log('Something Wrong!', 'Warning'); }, 1000); } }); @@ -938,7 +938,7 @@ function validateInput(input, table, field, submitBtnId){ checkDuplicateTableFieldValue(table, field, value, function(isDuplicate) { if (isDuplicate) { - toastr.warning(message, 'WARNING'); + toastr.warning(message, 'Warning'); // $(input).val('') $('#'+submitBtnId).prop('disabled', true); } else{ @@ -972,7 +972,7 @@ function validateDuplicateByClientBranch(input, field, submitButId) { if (isLocalDuplicate) { console.log(`r u n Local`); console.log(`duplicate found for ${field}`); - toastr.warning(message, 'WARNING'); + toastr.warning(message, 'Warning'); $('#' + submitButId).prop('disabled', true); return; } @@ -999,7 +999,7 @@ function validateDuplicateByClientBranch(input, field, submitButId) { if (response.isDuplicate) { console.log(`r u n Server`); console.log(`duplicate found for ${field}`); - toastr.warning(message, 'WARNING'); + toastr.warning(message, 'Warning'); $('#' + submitButId).prop('disabled', true); } else { console.log(`No duplicate for ${field}`); @@ -1044,13 +1044,13 @@ function checkAllFieldsValid(submitButId) { $('#' + submitButId).prop('disabled', true); // show correct message based on what’s duplicated if (emailDuplicates && mobileDuplicates) { - toastr.warning("Email and Mobile values are duplicate!", "WARNING"); + toastr.warning("Email and Mobile values are duplicate!", "Warning"); console.log('Both Email and Mobile duplicates'); } else if (emailDuplicates) { - toastr.warning("Email duplicate!", "WARNING"); + toastr.warning("Email duplicate!", "Warning"); console.log('Cross Check Email duplicates'); } else if (mobileDuplicates) { - toastr.warning("Mobile duplicate!", "WARNING"); + toastr.warning("Mobile duplicate!", "Warning"); console.log('Cross Check Mobile duplicates'); } console.log(`btn Dis - true`); @@ -1096,9 +1096,9 @@ function removeLevelContacts(id){ console.log('Data fetched successfully:', response); if (response.status == true) { - toastr.success(response.message, 'SUCCESS'); + toastr.success(response.message, 'Success'); } else { - toastr.warning(response.message, 'WARNING'); + toastr.warning(response.message, 'Warning'); } }, function(xhr, status, error) { diff --git a/app/Views/client_kyc.php b/app/Views/client_kyc.php index 99bab2b..2c96495 100755 --- a/app/Views/client_kyc.php +++ b/app/Views/client_kyc.php @@ -212,7 +212,7 @@ setTimeout(function() { $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); - toastr.warning('Something Wrong!', 'warning'); + toastr.warning('Something Wrong!', 'Warning'); }, 1000); } @@ -333,7 +333,7 @@ setTimeout(function() { $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); - toastr.warning('Something Wrong!', 'warning'); + toastr.warning('Something Wrong!', 'Warning'); }, 1000); } @@ -438,7 +438,7 @@ setTimeout(function() { $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); - toastr.warning('Something Wrong!', 'warning'); + toastr.warning('Something Wrong!', 'Warning'); }, 1000); } }); diff --git a/app/Views/client_list.php b/app/Views/client_list.php index 89ff403..a65ff46 100755 --- a/app/Views/client_list.php +++ b/app/Views/client_list.php @@ -402,7 +402,7 @@ // if(res){ // if (res.status == true) { - // toastr.success('Client removed successfully.', 'success'); + // toastr.success('Client removed successfully.', 'Success'); // location.reload(); // } else { // Swal.fire({ @@ -410,7 +410,7 @@ // text: res.message, // icon: "warning" // }); - // // toastr.warning(res.message, 'warning'); + // // toastr.warning(res.message, 'Warning'); // } // } // }, @@ -419,7 +419,7 @@ // console.error(status, error); // $('.loader').fadeOut(); // $('.loader-mask').delay(350).fadeOut('slow'); - // console.log('Something Wrong!', 'warning'); + // console.log('Something Wrong!', 'Warning'); // } // }); // } @@ -466,15 +466,15 @@ $('.loader-mask').delay(350).fadeOut('slow'); if (res.status == true) { - toastr.success(res.message, 'success'); + toastr.success(res.message, 'Success'); location.reload(); } else { Swal.fire({ - title: "warning!", + title: "Warning!", text: res.message, icon: "warning" }); - // toastr.warning(res.message, 'warning'); + // toastr.warning(res.message, 'Warning'); } }, error: function (xhr, status, error) { @@ -563,10 +563,10 @@ if(res.status == true){ let client_url = '' + res.client_id + '?client_policy_id=' + res.client_policy_id+'#police-tab'; - toastr.success(res.message, 'SUCCESS') + toastr.success(res.message, 'Success') window.location.href = client_url }else{ - toastr.success(res.message, 'WARNING') + toastr.warning(res.message, 'Warning') } $('.close').click() diff --git a/app/Views/client_onboarding.php b/app/Views/client_onboarding.php index dc37b06..08e9359 100755 --- a/app/Views/client_onboarding.php +++ b/app/Views/client_onboarding.php @@ -200,7 +200,7 @@ body { if(check_client_id[0].value == '' || check_client_id[0].value == null || check_client_id[0].value == 0){ $('#btnBranchAdd').hide(); - toastr.warning('Please Add the Client!', 'warning',{timeOut: 2000}); + toastr.warning('Please Add the Client!', 'Warning',{timeOut: 2000}); }else{ if ($('#btnBranchBack')[0].style.display == 'none') { $('#btnBranchAdd').show(); @@ -215,7 +215,7 @@ body { if(check_client_id[0].value == '' || check_client_id[0].value == null || check_client_id[0].value == 0){ $('#relation_form').hide(); - toastr.warning('Please Add the Client!', 'warning',{timeOut: 2000}); + toastr.warning('Please Add the Client!', 'Warning',{timeOut: 2000}); }else{ $('#relation_form').show(); } @@ -228,7 +228,7 @@ body { if(check_client_id[0].value == '' || check_client_id[0].value == null || check_client_id[0].value == 0){ $('#BtnAdd').hide(); - toastr.warning('Please Add the Client!', 'warning',{timeOut: 2000}); + toastr.warning('Please Add the Client!', 'Warning',{timeOut: 2000}); }else{ if ($('#btnPolicyBack')[0].style.display == 'none') { $('#BtnAdd').show(); @@ -243,7 +243,7 @@ body { if(check_client_id[0].value == '' || check_client_id[0].value == null || check_client_id[0].value == 0){ $('#btnBranchAdd').hide(); - toastr.warning('Please Add the Client!', 'warning',{timeOut: 2000}); + toastr.warning('Please Add the Client!', 'Warning',{timeOut: 2000}); }else{ if ($('#btnBranchBack')[0].style.display == 'none') { $('#btnBranchAdd').show(); @@ -259,7 +259,7 @@ body { if(check_client_id[0].value == '' || check_client_id[0].value == null || check_client_id[0].value == 0){ $('#notification-tab').hide(); - toastr.warning('Please Add the Client!', 'warning',{timeOut: 2000}); + toastr.warning('Please Add the Client!', 'Warning',{timeOut: 2000}); }else{ if ($('#notification-tab')[0].style.display == 'none') { $('#notification-tab').show(); diff --git a/app/Views/client_others_tab.php b/app/Views/client_others_tab.php index e7f0979..193eb8b 100755 --- a/app/Views/client_others_tab.php +++ b/app/Views/client_others_tab.php @@ -59,9 +59,9 @@ $(document).ready(function() { $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); if (res.status == true) { - toastr.success(res.message, 'SUCCESS'); + toastr.success(res.message, 'Success'); } else { - toastr.warning(res.message, 'WARNING'); + toastr.warning(res.message, 'Warning'); } }, diff --git a/app/Views/client_policy.php b/app/Views/client_policy.php index 0da8700..ad919af 100755 --- a/app/Views/client_policy.php +++ b/app/Views/client_policy.php @@ -332,7 +332,7 @@ input:checked + .slider:before { -->
- +
@@ -1686,7 +1686,7 @@ $('#policy_type').change(function() { } if ($(this).val() == 5) { - toastr.warning('Please Change the Policy Terms after Submit the Policy!', 'INFO'); + toastr.warning('Please Change the Policy Terms after Submit the Policy!', 'Info'); } if ($(this).val() == '4' || $(this).val() == '5') { @@ -1891,7 +1891,7 @@ function fetchClientBranch() { // //console.log(res.data.length); if (res.data.length == 0) { - toastr.warning('The client does not have any branches.', 'warning'); + toastr.warning('The client does not have any branches.', 'Warning'); return; } @@ -2018,15 +2018,15 @@ function removepolicy(element) { if (res) { if (res.status == true) { - toastr.success('Policy removed successfully.', 'success'); + toastr.success('Policy removed successfully.', 'Success'); location.reload(); } else { Swal.fire({ - title: "warning!", + title: "Warning!", text: res.message, icon: "warning" }); - // toastr.warning('Failed to remove policy', 'warning'); + // toastr.warning('Failed to remove policy', 'Warning'); } } }, @@ -2060,7 +2060,7 @@ $('#policy_no').change(function() { console.log(res) if (res.status == true) { - toastr.warning(res.message, 'warning'); + toastr.warning(res.message, 'Warning'); $('#policy_no').val(''); } }, @@ -2331,10 +2331,10 @@ function featchClient() { if (res.status == true) { let client_url = '' + res.client_id + '?client_policy_id=' + res.client_policy_id + '#police-tab'; - toastr.success(res.message, 'SUCCESS') + toastr.success(res.message, 'Success') window.location.href = client_url; } else { - toastr.success(res.message, 'WARNING'); + toastr.warning(res.message, 'Warning'); } $('.close').click() diff --git a/app/Views/employee_data_list.php b/app/Views/employee_data_list.php index c3f2c24..c8f094b 100755 --- a/app/Views/employee_data_list.php +++ b/app/Views/employee_data_list.php @@ -536,7 +536,7 @@ function get_emp_master_data_for_update_ajax(id) { $('#gender').prop('disabled', false); $('#dob').prop('disabled', false); - toastr.warning(res.message, 'WARNING'); + toastr.warning(res.message, 'Warning'); } }, error: function(xhr, status, error) { @@ -583,7 +583,7 @@ function update_emp_master_data_submit_function(event, form) { $('.loader-mask').delay(350).fadeOut('slow'); if (response.status == true) { - toastr.success(response.message, 'SUCCESS'); + toastr.success(response.message, 'Success'); $('.close').click(); @@ -599,7 +599,7 @@ function update_emp_master_data_submit_function(event, form) { fetchEmpolyeeList(); } else { - toastr.error(response.message, 'ERROR'); + toastr.error(response.message, 'Error'); } }, error: function(xhr, status, error) { @@ -616,7 +616,7 @@ function update_emp_master_data_submit_function(event, form) { console.error('Response Text: ', xhr.responseText); } - toastr.warning('Error uploading file', 'WARNING'); + toastr.warning('Error uploading file', 'Warning'); console.error('Upload error:', error); }, complete: function() { @@ -657,9 +657,9 @@ function send_mail_for_individual_employee_ecard(id) { $('.loader-mask').delay(350).fadeOut('slow'); if (res.status == true) { - toastr.success(res.message, 'SUCCESS'); + toastr.success(res.message, 'Success'); } else { - toastr.error(res.message, 'ERROR'); + toastr.error(res.message, 'Error'); } }, error: function(xhr, status, error) { @@ -698,7 +698,7 @@ function validateInput(input, table, field) { checkDuplicateTableFieldValue(table, field, data, function(isDuplicate) { if (isDuplicate) { - toastr.warning(message, 'WARNING'); + toastr.warning(message, 'Warning'); $("#btnSubmit").prop('disabled', true); }else{ $("#btnSubmit").prop('disabled', false); @@ -800,12 +800,12 @@ function get_emp_history(input, emp_id) { showModal(); } else { let message = response.message; - toastr.error(message, 'ERROR'); + toastr.error(message, 'Error'); } }, function(xhr, status, error) { console.error('Error fetching data:', error); console.error(xhr.responseText); - toastr.error('An error occurred while fetching the report page.', 'ERROR'); + toastr.error('An error occurred while fetching the report page.', 'Error'); $('.loader').fadeOut(); $('.loader-mask').delay(350).fadeOut('slow'); @@ -844,7 +844,7 @@ function downloadInception(){ empName: empName }, success: function(response) { - if (response.status === 'success') { + if (response.status === 'Success') { $('', { href: response.downloadUrl, download: '', diff --git a/app/Views/employee_list.php b/app/Views/employee_list.php index 3a779fb..6761bd8 100755 --- a/app/Views/employee_list.php +++ b/app/Views/employee_list.php @@ -302,14 +302,51 @@ function init() { function handleItemClick(e, item) { e.preventDefault(); - // Execute the stored onclick handler const onclickAttr = item.getAttribute('data-onclick'); if (onclickAttr) { - eval(onclickAttr); + // Regex to separate function name from the inside of the parentheses + // Example: myFunc(this, '123') -> match[1]="myFunc", match[2]="this, '123'" + const match = onclickAttr.match(/^(\w+)\((.*)\)$/); + + if (match) { + const funcName = match[1]; + const argsRaw = match[2]; + + if (typeof window[funcName] === 'function') { + // Parse the arguments string into a real array + const args = argsRaw.split(',').map(arg => { + let cleaned = arg.trim(); + + // 1. Handle the 'this' keyword + if (cleaned === 'this') return item; + + // 2. Handle 'event' keyword + if (cleaned === 'event') return e; + + // 3. Handle strings (remove single or double quotes) + if ((cleaned.startsWith("'") && cleaned.endsWith("'")) || + (cleaned.startsWith('"') && cleaned.endsWith('"'))) { + return cleaned.substring(1, cleaned.length - 1); + } + + // 4. Handle numbers + if (!isNaN(cleaned) && cleaned !== "") { + return Number(cleaned); + } + + return cleaned; + }); + + // Execute function: + // .apply(item, args) sets the 'this' inside the function to the clicked element + window[funcName].apply(item, args); + } + } } + // Handle standard navigation if it's a link const href = item.getAttribute('href'); - if (href && href !== '#') { + if (href && href !== '#' && !href.includes('javascript:void(0)')) { window.location.href = href; } @@ -691,9 +728,9 @@ function sendManualEcard(input) $('.loader-mask').delay(350).fadeOut('slow'); if(res.status == false){ - toastr.info(res.message, 'INFO'); + toastr.info(res.message, 'Info'); }else{ - toastr.success(res.message, 'SUCCESS'); + toastr.success(res.message, 'Success'); } }, diff --git a/app/Views/employee_upload.php b/app/Views/employee_upload.php index 98bb2c9..2409597 100755 --- a/app/Views/employee_upload.php +++ b/app/Views/employee_upload.php @@ -38,7 +38,7 @@ option:disabled {

-
@@ -183,7 +183,7 @@ $('#file_upload').hide(); $(document).ready(function() { has('success1')): ?> - toastr.success('getFlashdata('success1') ?>', 'success'); + toastr.success('getFlashdata('success1') ?>', 'Success'); // Initialize select2 $("#client_id").select2(); @@ -247,22 +247,22 @@ $(document).ready(function() { if(checkValues() == 'client'){ - toastr.warning('Client is Required', 'warning') + toastr.warning('Client is Required', 'Warning') return false; }else if(checkValues() == 'policy'){ - toastr.warning('Policy is Required', 'warning') + toastr.warning('Policy is Required', 'Warning') return false; }else if(checkValues() == 'branch'){ - toastr.warning('Branch is Required', 'warning') + toastr.warning('Branch is Required', 'Warning') return false; }else if(checkValues() == 'event'){ - toastr.warning('Event is Required', 'warning') + toastr.warning('Event is Required', 'Warning') return false; }else{ @@ -291,7 +291,7 @@ $(document).ready(function() { // $('#policy_id').mouseover(); console.log('required'); // alert('please choose policy for this uploaing event '); - toastr.warning('please choose policy for this uploaing event', 'warning') + toastr.warning('please choose policy for this uploaing event', 'Warning') return false; } else { @@ -330,7 +330,7 @@ $(document).ready(function() { .data !== "") { toastr.success( 'File upload successs, Data validation is in-progress', - 'success'); + 'Success'); window.location.reload(true); } else if (response.code === 404 && response.dataStatus === false) { console.error('no data found', response); @@ -850,7 +850,7 @@ $('#fetch_enrolled_data').click(function() var action = $('#upload-action-type').val(); // console.log(client_id + '-' + policy_id); if (client_id == '0' || policy_id == '0') { - toastr.warning('please select the client and policy for this uploaing event', 'warning') + toastr.warning('please select the client and policy for this uploaing event', 'Warning') $('#full-width-modal').modal('hide'); return; } diff --git a/app/Views/enrollment_list.php b/app/Views/enrollment_list.php index 8d3810d..495d959 100755 --- a/app/Views/enrollment_list.php +++ b/app/Views/enrollment_list.php @@ -811,13 +811,13 @@ $('.loader-mask').delay(350).fadeOut('slow'); if (response.code === 200 && response.dataStatus === true && response.data !== "") { - toastr.success('File upload success, Data validation is in-progress', 'success'); + toastr.success('File upload success, Data validation is in-progress', 'Success'); setTimeout(function() { window.location.href = ''; }, 600); } else if (response.code === 404 && response.dataStatus === false) { console.error('No data found', response); - toastr.error(response.message, 'error'); + toastr.error(response.message, 'Error'); setTimeout(function() { window.location.href = ''; }, 600); @@ -908,9 +908,9 @@ $('.loader-mask').delay(350).fadeOut('slow'); if(res.status == false){ - toastr.info(res.message, 'INFO'); + toastr.info(res.message, 'Info'); }else{ - toastr.success(res.message, 'SUCCESS'); + toastr.success(res.message, 'Success'); } }, diff --git a/app/Views/file_list.php b/app/Views/file_list.php index 0810319..993ebe6 100755 --- a/app/Views/file_list.php +++ b/app/Views/file_list.php @@ -89,7 +89,7 @@ echo '
' . $file['status'] . ''; } - } else if ($file['status'] == 'success') { + } else if ($file['status'] == 'Success') { if ($file['action'] == 'inception' || $file['action'] == 'addition' || $file['action'] == 'dependent_addtion') { $tool_tip_text = "Live(s) : {$file['employee_count']}" . " | Total premium : ₹ " . format_indian_number($file['total'], 2, ','); @@ -113,13 +113,22 @@ class="mdi mdi-dots-horizontal"> + + + + + + + + + + + + + + + + + + +
+
+ +
+ + Notification +
+
+
    + +
+
+ + +
+ + TO DOs +
+
+
    + +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/Views/layout/header.php b/app/Views/layout/header.php index 586695f..7bd6b71 100755 --- a/app/Views/layout/header.php +++ b/app/Views/layout/header.php @@ -51,7 +51,7 @@ - + @@ -59,7 +59,7 @@ - + diff --git a/app/Views/layout/header_new.php b/app/Views/layout/header_new.php index 297f7f2..e021d8a 100644 --- a/app/Views/layout/header_new.php +++ b/app/Views/layout/header_new.php @@ -50,34 +50,34 @@ /assets/css/jodit.css" rel="stylesheet" type="text/css" /> - - + + - - + + - - + + - - + + - + - + @@ -329,84 +329,8 @@ }); - - @@ -420,24 +344,219 @@ -
+
+ + +
+ + + +