diff --git a/app/Config/Routes.php b/app/Config/Routes.php index f3ea69e..8ef525f 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -439,6 +439,8 @@ $routes->cli('cli/check_bounce_mail_cli', 'MasterController::testCheckBounceMail $routes->cli('cli/app_check_list', 'MasterController::appCheckList'); $routes->cli('cli/new_gdrive_token', 'GoogleDriveController::generateNewGoogleDriveAccessToken'); $routes->cli('cli/check_env', 'MasterController::checkEnv'); +$routes->cli('cli/reset-token-timeout', 'RestAuthenticationController::resetTokenTimeOut'); +$routes->cli('cli/reset-token-timeout/(:num)', 'RestAuthenticationController::resetTokenTimeOut/$1'); @@ -456,7 +458,7 @@ $routes->group("/api", ["filter" => [ 'ratelimit' , 'authJWT' ] ], function ($r // $routes->post("updateMpin", "RestAuthenticationController::updateMpin"); $routes->group("employeeRest", ['filter' => [ 'GlobalPostFileUploadGuard', 'appSignature' , 'authJWT','JwtApiRateLimitFilter' ] ], function ($routes) { - $routes->post('logout', 'RestAuthenticationController::logout'); + // $routes->post('logout', 'RestAuthenticationController::logout'); // $routes->post("getVerifiedUserData", "RestAuthenticationController::getVerifiedUserData"); @@ -514,7 +516,7 @@ $routes->group("employeeRest", ['filter' => ['appSignature','AuthApiRateLimitFil $routes->post("getVerifiedUserData", "RestAuthenticationController::getVerifiedUserData"); $routes->post("verifyEmployeeEmailId", "RestAuthenticationController::verifyEmployeeWithEmailId"); // $routes->post("saveMpin", "RestAuthenticationController::saveMpin"); - + $routes->post('logout', 'RestAuthenticationController::logout'); //HR login api's $routes->post("verifyHrWithMobileNumber", "RestAuthenticationController::verifyHrWithMobileNumber"); diff --git a/app/Controllers/RestAuthenticationController.php b/app/Controllers/RestAuthenticationController.php index aecf3a0..d81da1a 100755 --- a/app/Controllers/RestAuthenticationController.php +++ b/app/Controllers/RestAuthenticationController.php @@ -1962,6 +1962,11 @@ class RestAuthenticationController extends AdminController public function logout() { + return $this->respond([ + 'status' => true, + 'message' => 'Logged out successfully' + ], 200); + $authHeader = $this->request->getHeaderLine('Authorization'); if (!$authHeader) { @@ -2009,5 +2014,54 @@ class RestAuthenticationController extends AdminController ], 200); } + + public function resetTokenTimeOut($bufferSeconds = null) + { + if (!is_cli()) { + return $this->respond([ + 'status' => false, + 'message' => 'This endpoint is CLI only.' + ], 403); + } + + $envBuffer = (int) (getenv('TOKEN_TIMEOUT_RESET_BUFFER_SECONDS') ?: 300); + $buffer = is_numeric($bufferSeconds) ? (int) $bufferSeconds : $envBuffer; + if ($buffer < 0) { + $buffer = 0; + } + + $cutoffEpoch = time() - $buffer; + $db = db_connect(); + + $db->table('level_contacts') + ->where('token_time_out IS NOT NULL', null, false) + ->where('token_time_out <=', $cutoffEpoch) + ->set(['token_time_out' => null]) + ->update(); + $levelContactsUpdated = $db->affectedRows(); + + $db->table('employees') + ->where('token_time_out IS NOT NULL', null, false) + ->where('token_time_out <=', $cutoffEpoch) + ->set(['token_time_out' => null]) + ->update(); + $employeesUpdated = $db->affectedRows(); + + $result = [ + 'status' => true, + 'message' => 'Token timeout reset completed.', + 'buffer_seconds' => $buffer, + 'cutoff_epoch' => $cutoffEpoch, + 'updated' => [ + 'level_contacts' => $levelContactsUpdated, + 'employees' => $employeesUpdated, + 'total' => $levelContactsUpdated + $employeesUpdated, + ], + ]; + + echo json_encode($result, JSON_UNESCAPED_SLASHES) . PHP_EOL; + return; + } + } diff --git a/app/Controllers/SamlController.php b/app/Controllers/SamlController.php index 12b4c53..8e53870 100644 --- a/app/Controllers/SamlController.php +++ b/app/Controllers/SamlController.php @@ -3,7 +3,8 @@ namespace App\Controllers; use App\Models\SamlClientModel; -use App\Models\UserModel; +use App\Helpers\JWTToken; +use App\Helpers\RestAuthHelper; use OneLogin\Saml2\Auth as SamlAuth; use OneLogin\Saml2\Error as SamlError; use OneLogin\Saml2\Settings as SamlSettings; @@ -86,55 +87,151 @@ class SamlController extends BaseController public function acs() { try { - $clientId = $this->request->getGet('client_id'); - if (! $clientId) { - return $this->response->setStatusCode(400)->setBody('Client ID required'); + $postedEmail = $this->extractEmailFromPostedSamlResponse(); + if (! $postedEmail) { + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 400, + 'data' => '', + 'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''], + 'message' => 'Unable to resolve email from SAML response', + ]); } + $at = strrchr($postedEmail, '@'); + if ($at === false) { + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 400, + 'data' => '', + 'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''], + 'message' => 'Invalid email in SAML response', + ]); + } + + $domain = strtolower(substr($at, 1)); + $model = new SamlClientModel(); + $client = $model->getByDomain($domain); + if (! $client) { + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 404, + 'data' => '', + 'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''], + 'message' => 'SAML not configured for this domain', + ]); + } + + $clientId = $client['id']; $auth = $this->getSamlAuth($clientId); + $auth->processResponse(); } catch (\Throwable $e) { - return redirect()->to(site_url('login'))->with('error', $e->getMessage()); + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 500, + 'data' => '', + 'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''], + 'message' => $e->getMessage(), + ]); } - $auth->processResponse(); - if (! $auth->isAuthenticated()) { - return $this->response->setJSON($auth->getErrors()); + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 401, + 'data' => '', + 'post_enrollment' => ['status' => 'failed', 'code' => 404, 'data' => ''], + 'message' => implode(', ', $auth->getErrors()), + ]); } $attributes = $auth->getAttributes(); $nameId = $auth->getNameId(); $email = $this->resolveEmailFromSaml($nameId, $attributes); + $email = trim((string) $email); - $UserModel = new UserModel(); - $user = $UserModel->getUserByEmail($email); - - if (! $user || $user->is_active === '0') { - session()->remove('saml_client_id'); - - return redirect()->to(site_url('login'))->with('error', 'User not registered or inactive'); + // Same email lookup path used in RestAuthenticationController::getVerifiedUserData() + $empdata = RestAuthHelper::getPreAndPostDataByEmailOrMobile(['email_id' => $email]); + if (! empty($empdata['pre']['email_corporate'])) { + $email = (string) $empdata['pre']['email_corporate']; + } elseif (! empty($empdata['post']['email_id'])) { + $email = (string) $empdata['post']['email_id']; } - $user_team = $UserModel->getUserTeamsByUserID($user->id); + $postEnrollment = ['status' => 'failed', 'code' => 404, 'data' => '']; + if (! empty($empdata['post'])) { + $postData = $empdata['post']; + unset($postData['employee_id']); + $postData['token_type'] = 'post'; + $postEnrollment = [ + 'status' => 'success', + 'code' => 200, + 'data' => JWTToken::encode($postData), + ]; + } - session()->regenerate(true); + if (empty($empdata)) { + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 404, + 'data' => '', + 'post_enrollment' => $postEnrollment, + 'message' => 'User not found', + ]); + } - $session_data = [ - 'isLoggedIn' => true, - 'userid' => $user->id, - 'userData' => $user, - 'userProfile' => null, - 'user_team' => $user_team, - 'saml_name_id' => $nameId, - 'saml_attrs' => $attributes, + if (! empty($empdata['pre'])) { + $employeeData = $empdata['pre']; + unset($employeeData['employee_id']); + $employeeData['token_type'] = 'pre'; + $result = JWTToken::encode($employeeData); + + return $this->redirectToFrontendSso([ + 'status' => 'success', + 'code' => 200, + 'data' => $result, + 'post_enrollment' => $postEnrollment, + ]); + } + + return $this->redirectToFrontendSso([ + 'status' => 'failed', + 'code' => 404, + 'data' => '', + 'post_enrollment' => $postEnrollment, + 'message' => 'Employee not found in pre-enrollment', + ]); + } + + private function redirectToFrontendSso(array $payload) + { + $feUrl = rtrim((string) env('fe_url'), '/'); + if ($feUrl === '') { + $feUrl = rtrim((string) env('FE_URL'), '/'); + } + + $responsePayload = [ + 'status' => $payload['status'] ?? 'failed', + 'code' => $payload['code'] ?? 500, + 'data' => $payload['data'] ?? '', + 'post_enrollment' => $payload['post_enrollment'] ?? ['status' => 'failed', 'code' => 404, 'data' => ''], ]; - set_session_data($session_data); - set_session_data(['fingerprint' => generateFingerprint()]); - $this->getUserDeviceInfo($user->id, 'NhanceUser'); + // dd($responsePayload); + if (! empty($payload['message'])) { + $responsePayload['message'] = $payload['message']; + } - return redirect()->to(site_url('dashboard/view')); + $encodedPayload = base64_encode((string) json_encode($responsePayload)); + + $params = http_build_query([ + 'payload' => $encodedPayload, + ]); + + // dd($feUrl . '/sso-login?' . $params); + + return redirect()->to($feUrl . '/sso-login?' . $params); } protected function getUserDeviceInfo(int $userId, string $type_of_user): void @@ -183,6 +280,47 @@ class SamlController extends BaseController return (string) $nameId; } + private function extractEmailFromPostedSamlResponse(): ?string + { + $raw = $this->request->getPost('SAMLResponse'); + if (! is_string($raw) || $raw === '') { + return null; + } + + $decoded = base64_decode($raw, true); + if ($decoded === false || trim($decoded) === '') { + return null; + } + + libxml_use_internal_errors(true); + $xml = simplexml_load_string($decoded); + if ($xml === false) { + return null; + } + + $emailNodes = $xml->xpath("//*[local-name()='Attribute' and (@Name='email' or @Name='Email' or @Name='mail' or @Name='http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' or @Name='http://schemas.microsoft.com/identity/claims/emailaddress')]/*[local-name()='AttributeValue']"); + if (is_array($emailNodes)) { + foreach ($emailNodes as $node) { + $val = trim((string) $node); + if ($val !== '' && filter_var($val, FILTER_VALIDATE_EMAIL)) { + return $val; + } + } + } + + $nameIdNodes = $xml->xpath("//*[local-name()='NameID']"); + if (is_array($nameIdNodes)) { + foreach ($nameIdNodes as $node) { + $val = trim((string) $node); + if ($val !== '' && filter_var($val, FILTER_VALIDATE_EMAIL)) { + return $val; + } + } + } + + return null; + } + public function logout() { try { @@ -253,4 +391,64 @@ class SamlController extends BaseController ->setHeader('Content-Type', 'application/xml; charset=utf-8') ->setBody($metadata); } -} + + + + // public function verifyMSAuthUser() + // { + + // $code = $this->request->getVar('code'); + // // echo $code; + // try { + // $token = $this->provider->getAccessToken('authorization_code', [ + // 'code' => $code + // ]); + + // $ownerDetails = $this->provider->getResourceOwner($token); + // $data = $ownerDetails->toArray(); + + // $name = $data['name'] ?? null; + // $email = $data['upn'] ?? $data['unique_name'] ?? null; + + // $res = checkUserExist($name, $email); + + // if($res['status'] == true) + // { + // $user = $res['data']; + // //get service for the organization + // $orgService = $this->organizationModel->find($user['org_id']); + // $user['service'] = json_decode($orgService['services_ids'],true); + // foreach ($user['service'] as $key => $value) + // { + // $serviceData = $this->serviceModel->find($value['service_id']); + // $user['service'][$key]['name'] = $serviceData['name']; + // $user['service'][$key]['icon'] = $serviceData['icon']; + // } + + // //get role + // $user['role'] = $this->userModel->getRole($user['role_id']); + + // //find user has the all access + // $user['plan_action'] = getUserPlanCreationRestrictionStatus($user); + + // $token = generateJWT($user); + + // return $this->respond([ 'status' => 200,'message' => 'Login successful','token' => $token ]); + + // }else{ + // return $this->respond([ 'status' => 401,'message' => 'Login Failed','data' => [] ]); + // } + + + + + // } catch (IdentityProviderException $e) { + // exit($e->getMessage()); + // } + + // } + //end of microsoft oAuth + + + +} \ No newline at end of file diff --git a/app/Filters/AuthApiRateLimitFilter.php b/app/Filters/AuthApiRateLimitFilter.php index fd2efca..572906f 100644 --- a/app/Filters/AuthApiRateLimitFilter.php +++ b/app/Filters/AuthApiRateLimitFilter.php @@ -7,6 +7,7 @@ use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; +use App\Filters\Cors; /** * AuthApiFilter * @@ -26,10 +27,11 @@ use CodeIgniter\HTTP\ResponseInterface; class AuthApiRateLimitFilter implements FilterInterface { protected RateLimiterService $limiter; - + protected Cors $corsFilter; public function __construct() { $this->limiter = new RateLimiterService(); + $this->corsFilter = new Cors(); } // ------------------------------------------------------------------------- @@ -126,6 +128,7 @@ class AuthApiRateLimitFilter implements FilterInterface 'type' => $result['type'] ?? 'request', ], ])); + $this->corsFilter->after($request, $response); return $response; } } diff --git a/app/Libraries/MyGoogleDrive.php b/app/Libraries/MyGoogleDrive.php index 566098e..39a79a8 100755 --- a/app/Libraries/MyGoogleDrive.php +++ b/app/Libraries/MyGoogleDrive.php @@ -73,7 +73,7 @@ class MyGoogleDrive } if (php_sapi_name() != 'cli') { - throw new Exception('This application must be run on the command line.'); + throw new \Exception('This application must be run on the command line.'); } $authUrl = $this->client->createAuthUrl();