From 4d15ddb92a1f9e029595049948a959071007e3ff Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Thu, 8 Jan 2026 16:58:50 +0530 Subject: [PATCH] GWM : VAPT Scan Vulnerabilities --- app/Config/Routes.php | 2 + .../RestAuthenticationController.php | 81 ++++++++- app/Filters/AuthJWT.php | 160 ++++++++++++------ app/Helpers/JWTToken.php | 93 +++++++--- app/Helpers/utility_helper.php | 26 +++ 5 files changed, 285 insertions(+), 77 deletions(-) diff --git a/app/Config/Routes.php b/app/Config/Routes.php index eaf7741..6524071 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -455,6 +455,8 @@ $routes->group("/api", ["filter" => "authJWT"], function ($routes) { $routes->group("employeeRest", ['filter' => ['appSignature' , 'authJWT'] ], function ($routes) { + $routes->post('logout', 'RestAuthenticationController::logout'); + // $routes->post("getVerifiedUserData", "RestAuthenticationController::getVerifiedUserData"); $routes->post("storeFireBase", "EmployeeRestController::storeFireBase"); diff --git a/app/Controllers/RestAuthenticationController.php b/app/Controllers/RestAuthenticationController.php index 22e834e..15469ec 100755 --- a/app/Controllers/RestAuthenticationController.php +++ b/app/Controllers/RestAuthenticationController.php @@ -166,7 +166,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'] ])->setStatusCode(429); // Too Many Requests + } + $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithMobileNumber: Employee verified with ID = " . $employeeData['employee_id']); log_message('error', ' '); log_message('error', '************************ PRE END ********************************'); @@ -264,10 +271,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', ' '); @@ -323,8 +331,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'] ])->setStatusCode(429); // Too Many Requests + } $this->myLogger->logme("error", "REST-AUTH-CONTROLLER - verifyEmployeeWithEmailId: Valid employee found, generating OTP"); @@ -578,6 +593,12 @@ class RestAuthenticationController extends AdminController if ($HrData) { + + //check the resend otp (with in 60 seconds don't allow another otp to send) + $check = canSendOtp($HrData); + if (!$check['allowed']) { + return $this->respond(['status' => false,'message' => 'OTP already sent. Please wait before retrying.','retry_after_seconds' => $check['retry_after'] ])->setStatusCode(429); // Too Many Requests + } $sql = "UPDATE level_contacts SET otp = ? WHERE mobile = ? AND contact_type = 'client' AND is_active = 1"; @@ -637,6 +658,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'] ])->setStatusCode(429); // Too Many Requests + } @@ -1868,5 +1895,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/AuthJWT.php b/app/Filters/AuthJWT.php index 262a5e5..9530ebd 100755 --- a/app/Filters/AuthJWT.php +++ b/app/Filters/AuthJWT.php @@ -18,65 +18,115 @@ 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(); + } + + $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/Helpers/JWTToken.php b/app/Helpers/JWTToken.php index 41d76b5..62b7281 100755 --- a/app/Helpers/JWTToken.php +++ b/app/Helpers/JWTToken.php @@ -18,9 +18,13 @@ use App\Models\LevelContactModel; class JWTToken { + + private const ALLOWED_ALG = 'HS512'; + + public static function encode($data =null) { - $secret_Key ="secret"; + $secret_Key = env('JWT_SECRET'); $request_data = (array)$data; @@ -47,30 +51,79 @@ class JWTToken } } - public static function validateJWT($jwt) + // public static function validateJWT($jwt) + // { + // $jwtParts = explode(' ', $jwt); + + // // print_r($jwtParts); + // if (count($jwtParts) != 2 || $jwtParts[0] == 'Bearer') { + // return false; + // } + + // $token = $jwtParts[1]; + + // try { + // $decoded = JWT::decode($token, new Key(env('JWT_SECRET'), 'HS512')); + // return json_encode(['status' => true, 'message' => 'Token is valid', 'decoded' => (array) $decoded]); + // } catch (ExpiredException $e) { + // return json_encode(['status' => false, 'message' => 'Token has expired']); + // } catch (BeforeValidException $e) { + // return json_encode(['status' => false, 'message' => 'Token is not yet valid']); + // } catch (SignatureInvalidException $e) { + // return json_encode(['status' => false, 'message' => 'Token signature is invalid']); + // } catch (\Exception $e) { + // return json_encode(['status' => false, 'message' => 'An error occurred while decoding the token']); + // } + + // } + + public static function validateJWT(string $authHeader) { - $jwtParts = explode(' ', $jwt); + // 1️⃣ Validate Authorization header + if (!preg_match('/^Bearer\s(\S+)$/', $authHeader, $matches)) { + return ['status' => false, 'message' => 'Invalid Authorization header']; + } - // print_r($jwtParts); - if (count($jwtParts) != 2 || $jwtParts[0] == 'Bearer') { - return false; - } + $token = $matches[1]; - $token = $jwtParts[1]; + // 2️⃣ Decode JWT header manually + $jwtParts = explode('.', $token); + if (count($jwtParts) !== 3) { + return ['status' => false, 'message' => 'Malformed JWT']; + } - try { - $decoded = JWT::decode($token, new Key("secret", 'HS512')); - return json_encode(['status' => true, 'message' => 'Token is valid', 'decoded' => (array) $decoded]); - } catch (ExpiredException $e) { - return json_encode(['status' => false, 'message' => 'Token has expired']); - } catch (BeforeValidException $e) { - return json_encode(['status' => false, 'message' => 'Token is not yet valid']); - } catch (SignatureInvalidException $e) { - return json_encode(['status' => false, 'message' => 'Token signature is invalid']); - } catch (\Exception $e) { - return json_encode(['status' => false, 'message' => 'An error occurred while decoding the token']); - } + $header = json_decode(base64_decode(strtr($jwtParts[0], '-_', '+/')), true); + // 3️⃣ Reject missing or NONE algorithm + if ( + empty($header['alg']) || + $header['alg'] === 'none' || + $header['alg'] !== self::ALLOWED_ALG + ) { + return ['status' => false, 'message' => 'Invalid or unsupported JWT algorithm']; + } + + // 4️⃣ Enforce signature validation + try { + $decoded = JWT::decode( + $token, + new Key(env('JWT_SECRET'), self::ALLOWED_ALG) + ); + + return [ + 'status' => true, + 'decoded' => (array) $decoded + ]; + + } catch (ExpiredException $e) { + return ['status' => false, 'message' => 'Token expired']; + } catch (BeforeValidException $e) { + return ['status' => false, 'message' => 'Token not yet valid']; + } catch (SignatureInvalidException $e) { + return ['status' => false, 'message' => 'Invalid token signature']; + } catch (\Exception $e) { + return ['status' => false, 'message' => 'Token validation failed']; + } } diff --git a/app/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 3372200..5ddf424 100755 --- a/app/Helpers/utility_helper.php +++ b/app/Helpers/utility_helper.php @@ -666,3 +666,29 @@ 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]; + } +} +