PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / app / Ajax / LoginAjax.php

LoginAjax.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.9, at app/Ajax/LoginAjax.php

372 lines 11.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Ajax;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10
11 /**
12 * Login AJAX Handler
13 *
14 * Handles AJAX login requests
15 */
16 class LoginAjax
17 {
18 /**
19 * Constructor - Initialize AJAX hooks
20 */
21 public function __construct()
22 {
23 // Register AJAX action for logged-in users
24 add_action('wp_ajax_yatra_ajax_login', [$this, 'handleAjaxLogin']);
25
26 // Register AJAX action for non-logged-in users
27 add_action('wp_ajax_nopriv_yatra_ajax_login', [$this, 'handleAjaxLogin']);
28 }
29
30 /**
31 * Handle AJAX login request
32 */
33 public function handleAjaxLogin(): void
34 {
35 // Rate limiting check
36 $this->checkRateLimit();
37
38 // Verify nonce with multiple checks
39 $nonce = $_POST['yatra_login_nonce'] ?? $_POST['nonce'] ?? '';
40 if (!wp_verify_nonce($nonce, 'yatra_login_action') && !wp_verify_nonce($nonce, 'yatra_login_nonce')) {
41 $this->logSecurityEvent('nonce_verification_failed', [
42 'nonce' => substr($nonce, 0, 8) . '...',
43 'ip' => $this->getClientIp()
44 ]);
45
46 wp_send_json_error([
47 'success' => false,
48 'code' => 'security_check_failed',
49 'message' => __('Security check failed. Please refresh the page and try again.', 'yatra')
50 ]);
51 }
52
53 // Validate request method
54 if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
55 wp_send_json_error([
56 'success' => false,
57 'code' => 'invalid_method',
58 'message' => __('Invalid request method.', 'yatra')
59 ]);
60 }
61
62 // Get and sanitize form data
63 $username = sanitize_user($_POST['log'] ?? $_POST['username'] ?? '');
64 $password = $_POST['pwd'] ?? $_POST['password'] ?? '';
65 $remember = isset($_POST['rememberme']) && $_POST['rememberme'] === 'forever';
66 $redirect_to = $this->validateRedirectUrl($_POST['redirect_to'] ?? '');
67
68 // Enhanced input validation
69 $validation_result = $this->validateLoginInput($username, $password);
70 if (!$validation_result['valid']) {
71 wp_send_json_error([
72 'success' => false,
73 'code' => $validation_result['code'],
74 'message' => $validation_result['message']
75 ]);
76 }
77
78 // Log login attempt (security)
79 $this->logLoginAttempt($username);
80
81 // Attempt authentication with security checks
82 $user = $this->authenticateUser($username, $password);
83
84 if (is_wp_error($user)) {
85 $this->handleAuthenticationError($user);
86 return; // Exit early
87 }
88
89 // Additional security checks
90 if (!$this->isUserAllowedToLogin($user)) {
91 wp_send_json_error([
92 'success' => false,
93 'code' => 'user_not_allowed',
94 'message' => __('Your account is not allowed to login. Please contact support.', 'yatra')
95 ]);
96 }
97
98 // Successful login - set up secure session
99 $this->setupUserSession($user, $remember);
100
101 // Prepare secure success response
102 $response_data = $this->prepareSuccessResponse($user, $redirect_to);
103
104 // Send success response
105 wp_send_json_success($response_data);
106 }
107
108 /**
109 * Check rate limiting for login attempts
110 */
111 private function checkRateLimit(): void
112 {
113 $ip = $this->getClientIp();
114 $enabled = (bool) apply_filters('yatra_login_rate_limit_enabled', true, $ip);
115 if (!$enabled) {
116 return;
117 }
118
119 // Defaults: 10 attempts per 1 minute (can be overridden via filters).
120 $maxAttempts = (int) apply_filters('yatra_login_rate_limit_max_attempts', 10, $ip);
121 $windowSeconds = (int) apply_filters('yatra_login_rate_limit_window_seconds', MINUTE_IN_SECONDS, $ip);
122 $transient_key = (string) apply_filters('yatra_login_rate_limit_transient_key', 'yatra_login_limit_' . md5($ip), $ip);
123
124 $attempts = (int) (get_transient($transient_key) ?: 0);
125
126 // Allow N attempts per window
127 if ($maxAttempts > 0 && $attempts >= $maxAttempts) {
128 $this->logSecurityEvent('rate_limit_exceeded', ['ip' => $ip]);
129 $minutes = (int) max(1, ceil(max(1, $windowSeconds) / 60));
130 wp_send_json_error([
131 'success' => false,
132 'code' => 'rate_limit_exceeded',
133 'message' => sprintf(
134 /* translators: %d = minutes to wait */
135 __('Too many login attempts. Please try again in %d minute(s).', 'yatra'),
136 $minutes
137 ),
138 ]);
139 }
140
141 // Increment counter
142 $ttl = $windowSeconds > 0 ? $windowSeconds : MINUTE_IN_SECONDS;
143 set_transient($transient_key, $attempts + 1, $ttl);
144 }
145
146 /**
147 * Validate login input
148 */
149 private function validateLoginInput(string $username, string $password): array
150 {
151 if (empty($username)) {
152 return [
153 'valid' => false,
154 'code' => 'empty_username',
155 'message' => __('Please enter your username or email.', 'yatra')
156 ];
157 }
158
159 if (empty($password)) {
160 return [
161 'valid' => false,
162 'code' => 'empty_password',
163 'message' => __('Please enter your password.', 'yatra')
164 ];
165 }
166
167 if (strlen($username) > 60) {
168 return [
169 'valid' => false,
170 'code' => 'invalid_username_length',
171 'message' => __('Username is too long.', 'yatra')
172 ];
173 }
174
175 if (strlen($password) > 72) {
176 return [
177 'valid' => false,
178 'code' => 'invalid_password_length',
179 'message' => __('Password is too long.', 'yatra')
180 ];
181 }
182
183 return ['valid' => true];
184 }
185
186 /**
187 * Authenticate user with enhanced security
188 */
189 private function authenticateUser(string $username, string $password)
190 {
191 // Use WordPress authentication with additional security
192 $user = wp_authenticate($username, $password);
193
194 if (is_wp_error($user)) {
195 return $user;
196 }
197
198 // Check if user is verified (if email verification is required)
199 if ($this->isEmailVerificationRequired() && !get_user_meta($user->ID, 'yatra_email_verified', true)) {
200 return new WP_Error('email_not_verified',
201 __('Your email address has not been verified. Please check your email.', 'yatra')
202 );
203 }
204
205 return $user;
206 }
207
208 /**
209 * Handle authentication errors
210 */
211 private function handleAuthenticationError(WP_Error $error): void
212 {
213 $error_code = $error->get_error_code();
214 $error_message = $error->get_error_message();
215
216 // Log security events
217 $this->logSecurityEvent('authentication_failed', [
218 'error_code' => $error_code,
219 'error_message' => $error_message
220 ]);
221
222 // Provide user-friendly error messages
223 if (in_array($error_code, ['invalid_username', 'incorrect_password'], true)) {
224 $error_message = __('Invalid username or password. Please try again.', 'yatra');
225 } elseif ($error_code === 'email_not_verified') {
226 // Keep the specific message from the error
227 } else {
228 $error_message = __('Login failed. Please try again.', 'yatra');
229 }
230
231 wp_send_json_error([
232 'success' => false,
233 'code' => $error_code,
234 'message' => $error_message
235 ]);
236 }
237
238 /**
239 * Setup secure user session
240 */
241 private function setupUserSession(\WP_User $user, bool $remember): void
242 {
243 wp_set_current_user($user->ID);
244 wp_set_auth_cookie($user->ID, $remember);
245
246 // Update user metadata
247 update_user_meta($user->ID, 'yatra_last_login', current_time('mysql'));
248 update_user_meta($user->ID, 'yatra_last_login_ip', $this->getClientIp());
249
250 // Clear failed login attempts
251 $transient_key = 'yatra_login_limit_' . md5($this->getClientIp());
252 delete_transient($transient_key);
253 }
254
255 /**
256 * Prepare secure success response
257 */
258 private function prepareSuccessResponse(\WP_User $user, string $redirect_to): array
259 {
260 // Apply filter for custom redirect logic
261 $redirect_url = apply_filters('yatra_login_redirect_url', $redirect_to, $user);
262
263 return [
264 'success' => true,
265 'message' => __('Login successful! Redirecting...', 'yatra'),
266 'user_id' => $user->ID,
267 'user_login' => $user->user_login,
268 'display_name' => $user->display_name,
269 'redirect_url' => esc_url($redirect_url),
270 'timestamp' => time()
271 ];
272 }
273
274 /**
275 * Validate redirect URL for security
276 */
277 private function validateRedirectUrl(string $redirect_url): string
278 {
279 if (empty($redirect_url)) {
280 return home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
281 }
282
283 $redirect_url = sanitize_url($redirect_url);
284
285 // Validate URL is safe
286 if (!wp_http_validate_url($redirect_url)) {
287 return home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
288 }
289
290 // Only allow redirects to same host
291 $redirect_host = parse_url($redirect_url, PHP_URL_HOST);
292 $site_host = parse_url(home_url(), PHP_URL_HOST);
293
294 if ($redirect_host !== $site_host) {
295 return home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
296 }
297
298 return $redirect_url;
299 }
300
301 /**
302 * Check if user is allowed to login
303 */
304 private function isUserAllowedToLogin(\WP_User $user): bool
305 {
306 // Check if user is active
307 if (in_array('inactive', (array) $user->roles, true)) {
308 return false;
309 }
310
311 // Apply additional checks via filter
312 return apply_filters('yatra_user_allowed_to_login', true, $user);
313 }
314
315 /**
316 * Check if email verification is required
317 */
318 private function isEmailVerificationRequired(): bool
319 {
320 return apply_filters('yatra_require_email_verification', false);
321 }
322
323 /**
324 * Get client IP address
325 */
326 private function getClientIp(): string
327 {
328 $ip_keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
329
330 foreach ($ip_keys as $key) {
331 if (!empty($_SERVER[$key])) {
332 $ips = explode(',', $_SERVER[$key]);
333 $ip = trim($ips[0]);
334 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
335 return $ip;
336 }
337 }
338 }
339
340 return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
341 }
342
343 /**
344 * Log security events
345 */
346 private function logSecurityEvent(string $event, array $data = []): void
347 {
348 if (!defined('WP_DEBUG') || !WP_DEBUG) {
349 return;
350 }
351
352 $log_data = array_merge([
353 'event' => $event,
354 'timestamp' => current_time('mysql'),
355 'ip' => $this->getClientIp(),
356 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
357 ], $data);
358
359 }
360
361 /**
362 * Log login attempts
363 */
364 private function logLoginAttempt(string $username): void
365 {
366 if (!defined('WP_DEBUG') || !WP_DEBUG) {
367 return;
368 }
369
370 }
371 }
372