PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
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
← All changes | app/Ajax/LoginAjax.php +85 -12 3.0.2.7 → 3.0.16 View file →
@@ -24,11 +24,71 @@
24 24 add_action('wp_ajax_yatra_ajax_login', [$this, 'handleAjaxLogin']);
25 25
26 26 // Register AJAX action for non-logged-in users
27 27 add_action('wp_ajax_nopriv_yatra_ajax_login', [$this, 'handleAjaxLogin']);
28 +
29 + // Password reset request (delegates to WordPress core's reset flow).
30 + add_action('wp_ajax_yatra_ajax_lost_password', [$this, 'handleAjaxLostPassword']);
31 + add_action('wp_ajax_nopriv_yatra_ajax_lost_password', [$this, 'handleAjaxLostPassword']);
28 32 }
29 33
30 34 /**
35 + * Handle AJAX password-reset request.
36 + *
37 + * This is a thin, on-site entry point that delegates to WordPress core's
38 + * retrieve_password(): core generates the reset key and sends its standard
39 + * reset email. We deliberately do NOT reimplement reset-token crypto. A
40 + * generic success message is always returned to avoid account enumeration.
41 + */
42 + public function handleAjaxLostPassword(): void
43 + {
44 + // Rate limiting (shared with login attempts).
45 + $this->checkRateLimit();
46 +
47 + // Verify nonce (same nonces the login form issues).
48 + $nonce = $_POST['yatra_login_nonce'] ?? $_POST['nonce'] ?? '';
49 + if (!wp_verify_nonce($nonce, 'yatra_login_action') && !wp_verify_nonce($nonce, 'yatra_login_nonce')) {
50 + wp_send_json_error([
51 + 'success' => false,
52 + 'code' => 'security_check_failed',
53 + 'message' => __('Security check failed. Please refresh the page and try again.', 'yatra'),
54 + ]);
55 + }
56 +
57 + if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
58 + wp_send_json_error([
59 + 'success' => false,
60 + 'code' => 'invalid_method',
61 + 'message' => __('Invalid request method.', 'yatra'),
62 + ]);
63 + }
64 +
65 + $user_login = sanitize_text_field(wp_unslash($_POST['user_login'] ?? $_POST['log'] ?? ''));
66 +
67 + if ($user_login === '') {
68 + wp_send_json_error([
69 + 'success' => false,
70 + 'code' => 'empty_user_login',
71 + 'message' => __('Please enter your email address or username.', 'yatra'),
72 + ]);
73 + }
74 +
75 + // Populate $_POST['user_login'] for cross-version compatibility:
76 + // retrieve_password() reads it directly on WordPress older than 5.7,
77 + // and the explicit argument is used on 5.7+ (extra args are ignored on
78 + // older cores, so this is safe either way).
79 + $_POST['user_login'] = $user_login;
80 + retrieve_password($user_login);
81 +
82 + // Always report success regardless of whether the account exists —
83 + // prevents user/email enumeration (mirrors WordPress core behavior).
84 + wp_send_json_success([
85 + 'success' => true,
86 + 'message' => __('If an account exists for that email, a password reset link has been sent. Please check your inbox.', 'yatra'),
87 + ]);
88 + }
89 +
90 + /**
31 91 * Handle AJAX login request
32 92 */
33 93 public function handleAjaxLogin(): void
34 94 {
@@ -110,23 +170,38 @@
110 170 */
111 171 private function checkRateLimit(): void
112 172 {
113 173 $ip = $this->getClientIp();
114 - $transient_key = 'yatra_login_limit_' . md5($ip);
115 - $attempts = get_transient($transient_key) ?: 0;
174 + $enabled = (bool) apply_filters('yatra_login_rate_limit_enabled', true, $ip);
175 + if (!$enabled) {
176 + return;
177 + }
178 +
179 + // Defaults: 10 attempts per 1 minute (can be overridden via filters).
180 + $maxAttempts = (int) apply_filters('yatra_login_rate_limit_max_attempts', 10, $ip);
181 + $windowSeconds = (int) apply_filters('yatra_login_rate_limit_window_seconds', MINUTE_IN_SECONDS, $ip);
182 + $transient_key = (string) apply_filters('yatra_login_rate_limit_transient_key', 'yatra_login_limit_' . md5($ip), $ip);
183 +
184 + $attempts = (int) (get_transient($transient_key) ?: 0);
116 185
117 - // Allow 5 attempts per 15 minutes
118 - if ($attempts >= 5) {
186 + // Allow N attempts per window
187 + if ($maxAttempts > 0 && $attempts >= $maxAttempts) {
119 188 $this->logSecurityEvent('rate_limit_exceeded', ['ip' => $ip]);
189 + $minutes = (int) max(1, ceil(max(1, $windowSeconds) / 60));
120 190 wp_send_json_error([
121 191 'success' => false,
122 192 'code' => 'rate_limit_exceeded',
123 - 'message' => __('Too many login attempts. Please try again in 15 minutes.', 'yatra')
193 + 'message' => sprintf(
194 + /* translators: %d = minutes to wait */
195 + __('Too many login attempts. Please try again in %d minute(s).', 'yatra'),
196 + $minutes
197 + ),
124 198 ]);
125 199 }
126 200
127 201 // Increment counter
128 - set_transient($transient_key, $attempts + 1, 15 * MINUTE_IN_SECONDS);
202 + $ttl = $windowSeconds > 0 ? $windowSeconds : MINUTE_IN_SECONDS;
203 + set_transient($transient_key, $attempts + 1, $ttl);
129 204 }
130 205
131 206 /**
132 207 * Validate login input
@@ -207,9 +282,9 @@
207 282 // Provide user-friendly error messages
208 283 if (in_array($error_code, ['invalid_username', 'incorrect_password'], true)) {
209 284 $error_message = __('Invalid username or password. Please try again.', 'yatra');
210 285 } elseif ($error_code === 'email_not_verified') {
211 - $error_message = $error_message; // Use the specific message
286 + // Keep the specific message from the error
212 287 } else {
213 288 $error_message = __('Login failed. Please try again.', 'yatra');
214 289 }
215 290
@@ -261,9 +336,9 @@
261 336 */
262 337 private function validateRedirectUrl(string $redirect_url): string
263 338 {
264 339 if (empty($redirect_url)) {
265 - return home_url('/my-account');
340 + return home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
266 341 }
267 342
268 343 $redirect_url = sanitize_url($redirect_url);
269 344
@@ -268,9 +343,9 @@
268 343 $redirect_url = sanitize_url($redirect_url);
269 344
270 345 // Validate URL is safe
271 346 if (!wp_http_validate_url($redirect_url)) {
272 - return home_url('/my-account');
347 + return home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
273 348 }
274 349
275 350 // Only allow redirects to same host
276 351 $redirect_host = parse_url($redirect_url, PHP_URL_HOST);
@@ -276,9 +351,9 @@
276 351 $redirect_host = parse_url($redirect_url, PHP_URL_HOST);
277 352 $site_host = parse_url(home_url(), PHP_URL_HOST);
278 353
279 354 if ($redirect_host !== $site_host) {
280 - return home_url('/my-account');
355 + return home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
281 356 }
282 357
283 358 return $redirect_url;
284 359 }
@@ -340,9 +415,8 @@
340 415 'ip' => $this->getClientIp(),
341 416 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
342 417 ], $data);
343 418
344 - error_log('Yatra Login Security: ' . json_encode($log_data));
345 419 }
346 420
347 421 /**
348 422 * Log login attempts
@@ -352,7 +426,6 @@
352 426 if (!defined('WP_DEBUG') || !WP_DEBUG) {
353 427 return;
354 428 }
355 429
356 - error_log('Yatra Login Attempt: ' . $username . ' from IP: ' . $this->getClientIp());
357 430 }
358 431 }