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('= session()->getFlashdata('success') ?>', 'success');
+ toastr.success('= session()->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 = '= base_url('client/list/') ?>' + 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 {
-->