PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
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 2.0.10 2.0.11 All 82 releases
yatra / app / Controllers / AuthController.php

AuthController.php in Yatra – Travel Booking & Tour Operator Software 3.0.4, at app/Controllers/AuthController.php

640 lines 24.5 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\Controllers;
6
7 /**
8 * Authentication Controller
9 * Handles user registration, login, email verification for Yatra customers
10 *
11 * @package Yatra
12 */
13 class AuthController
14 {
15 /**
16 * Register REST API routes
17 */
18 public static function registerRoutes(): void
19 {
20 register_rest_route('yatra/v1', '/auth/login', [
21 'methods' => 'POST',
22 'callback' => [self::class, 'login'],
23 'permission_callback' => '__return_true',
24 ]);
25
26 register_rest_route('yatra/v1', '/auth/register', [
27 'methods' => 'POST',
28 'callback' => [self::class, 'register'],
29 'permission_callback' => '__return_true',
30 ]);
31
32 register_rest_route('yatra/v1', '/auth/resend-verification', [
33 'methods' => 'POST',
34 'callback' => [self::class, 'resendVerification'],
35 'permission_callback' => '__return_true',
36 ]);
37 }
38
39 /**
40 * Register Yatra Customer role
41 */
42 public static function registerCustomerRole(): void
43 {
44 if (!get_role('yatra_customer')) {
45 add_role(
46 'yatra_customer',
47 __('Yatra Customer', 'yatra'),
48 [
49 'read' => true,
50 'edit_posts' => false,
51 'delete_posts' => false,
52 ]
53 );
54 }
55 }
56
57 /**
58 * Block unverified users from WordPress login
59 *
60 * @param \WP_User|\WP_Error|null $user
61 * @param string $username
62 * @param string $password
63 * @return \WP_User|\WP_Error|null
64 */
65 public static function blockUnverifiedUserLogin($user, string $username, string $password)
66 {
67 if (is_wp_error($user)) {
68 return $user;
69 }
70
71 if (!($user instanceof \WP_User)) {
72 return $user;
73 }
74
75 $has_verification_token = get_user_meta($user->ID, 'yatra_verification_token', true);
76 $email_verified = get_user_meta($user->ID, 'yatra_email_verified', true);
77
78 $needs_verification = !empty($has_verification_token);
79
80 if (!$needs_verification) {
81 $meta_exists = metadata_exists('user', $user->ID, 'yatra_email_verified');
82 $needs_verification = $meta_exists && !$email_verified;
83 }
84
85 if ($needs_verification) {
86 $resend_url = add_query_arg([
87 'action' => 'yatra_resend_verification',
88 'email' => urlencode($user->user_email),
89 '_wpnonce' => wp_create_nonce('yatra_resend_verification'),
90 ], wp_login_url());
91
92 $message = sprintf(
93 /* translators: %s: resend verification link */
94 __('<strong>Error:</strong> Please verify your email address before logging in. Check your inbox for the verification link. <br><br><a href="%s">Click here to resend verification email</a>', 'yatra'),
95 esc_url($resend_url)
96 );
97
98 return new \WP_Error('email_not_verified', $message);
99 }
100
101 return $user;
102 }
103
104 /**
105 * Handle resend verification from WordPress login page
106 */
107 public static function handleWpLoginResendVerification(): void
108 {
109 if (!isset($_GET['action']) || $_GET['action'] !== 'yatra_resend_verification') {
110 return;
111 }
112
113 // Verify nonce
114 if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_text_field($_GET['_wpnonce']), 'yatra_resend_verification')) {
115 wp_die(__('Your session has expired. Please go back and try again.', 'yatra'));
116 }
117
118 $email = isset($_GET['email']) ? sanitize_email(urldecode($_GET['email'])) : '';
119
120 if (empty($email) || !is_email($email)) {
121 wp_safe_redirect(add_query_arg('login_error', 'invalid_email', wp_login_url()));
122 exit;
123 }
124
125 $user = get_user_by('email', $email);
126
127 if (!$user) {
128 // Redirect with generic success message (don't reveal if email exists)
129 wp_safe_redirect(add_query_arg('checkemail', 'resent', wp_login_url()));
130 exit;
131 }
132
133 // Check if already verified
134 $email_verified = get_user_meta($user->ID, 'yatra_email_verified', true);
135 if ($email_verified === '1') {
136 wp_safe_redirect(add_query_arg('login_error', 'already_verified', wp_login_url()));
137 exit;
138 }
139
140 // Check rate limiting
141 $last_sent = get_user_meta($user->ID, 'yatra_verification_last_sent', true);
142 if ($last_sent && (time() - (int) $last_sent) < 120) {
143 $remaining = 120 - (time() - (int) $last_sent);
144 wp_safe_redirect(add_query_arg([
145 'login_error' => 'rate_limited',
146 'wait' => $remaining,
147 ], wp_login_url()));
148 exit;
149 }
150
151 // Generate new token and send email
152 $verification_token = wp_generate_password(32, false);
153 $secure_token = base64_encode($user->ID . '|' . $verification_token . '|' . time());
154 $secure_token = str_replace(['+', '/', '='], ['-', '_', ''], $secure_token);
155
156 update_user_meta($user->ID, 'yatra_verification_token', $verification_token);
157 update_user_meta($user->ID, 'yatra_verification_token_expiry', time() + (24 * 60 * 60));
158 update_user_meta($user->ID, 'yatra_verification_last_sent', time());
159
160 // Send email
161 $first_name = get_user_meta($user->ID, 'first_name', true) ?: $user->display_name;
162 self::sendVerificationEmail($user->ID, $email, $first_name, $secure_token, true);
163
164 wp_safe_redirect(add_query_arg('checkemail', 'resent', wp_login_url()));
165 exit;
166 }
167
168 /**
169 * Add custom messages to WordPress login page
170 */
171 public static function customLoginMessages(string $message): string
172 {
173 if (isset($_GET['checkemail']) && $_GET['checkemail'] === 'resent') {
174 $message = '<p class="message">' . __('A new verification link has been sent to your email address. Please check your inbox and spam folder.', 'yatra') . '</p>';
175 }
176
177 if (isset($_GET['email_verified']) && $_GET['email_verified'] === '1') {
178 $message = '<p class="message">' . __('Your email has been verified successfully! You can now log in.', 'yatra') . '</p>';
179 }
180
181 return $message;
182 }
183
184 /**
185 * Add custom error messages to WordPress login page
186 */
187 public static function customLoginErrors(\WP_Error $errors): \WP_Error
188 {
189 if (isset($_GET['login_error'])) {
190 switch ($_GET['login_error']) {
191 case 'invalid_email':
192 $errors->add('invalid_email', __('<strong>Error:</strong> Invalid email address.', 'yatra'));
193 break;
194 case 'already_verified':
195 $errors->add('already_verified', __('<strong>Notice:</strong> Your email is already verified. Please log in.', 'yatra'));
196 break;
197 case 'rate_limited':
198 $wait = isset($_GET['wait']) ? (int) $_GET['wait'] : 120;
199 $errors->add('rate_limited', sprintf(
200 /* translators: %d: number of seconds to wait */
201 __('<strong>Error:</strong> Please wait %d seconds before requesting another verification email.', 'yatra'),
202 $wait
203 ));
204 break;
205 }
206 }
207
208 return $errors;
209 }
210
211 /**
212 * Handle login request
213 */
214 public static function login(\WP_REST_Request $request): \WP_REST_Response
215 {
216 $username = sanitize_user($request->get_param('username') ?? '');
217 $password = $request->get_param('password') ?? '';
218 $remember = !empty($request->get_param('remember'));
219
220 if (empty($username) || empty($password)) {
221 return new \WP_REST_Response([
222 'success' => false,
223 'message' => __('Please enter your username/email and password.', 'yatra'),
224 ], 400);
225 }
226
227 // Get user to check verification status
228 $user_check = get_user_by('login', $username);
229 if (!$user_check) {
230 $user_check = get_user_by('email', $username);
231 }
232
233 // Check email verification for Yatra users
234 if ($user_check) {
235 $has_verification_token = get_user_meta($user_check->ID, 'yatra_verification_token', true);
236 $email_verified = get_user_meta($user_check->ID, 'yatra_email_verified', true);
237
238 if (!empty($has_verification_token)) {
239 return new \WP_REST_Response([
240 'success' => false,
241 'message' => __('Please verify your email address before logging in. Check your inbox for the verification link.', 'yatra'),
242 'needs_verification' => true,
243 'email' => $user_check->user_email,
244 ], 403);
245 }
246
247 $meta_exists = metadata_exists('user', $user_check->ID, 'yatra_email_verified');
248 if ($meta_exists && !$email_verified) {
249 return new \WP_REST_Response([
250 'success' => false,
251 'message' => __('Please verify your email address before logging in. Check your inbox for the verification link.', 'yatra'),
252 'needs_verification' => true,
253 'email' => $user_check->user_email,
254 ], 403);
255 }
256 }
257
258 // Authenticate
259 $user = wp_signon([
260 'user_login' => $username,
261 'user_password' => $password,
262 'remember' => $remember,
263 ], is_ssl());
264
265 if (is_wp_error($user)) {
266 $error_message = wp_strip_all_tags($user->get_error_message());
267 if (strpos($error_message, 'incorrect') !== false || strpos($error_message, 'Invalid') !== false) {
268 $error_message = __('Invalid username/email or password. Please try again.', 'yatra');
269 }
270 return new \WP_REST_Response([
271 'success' => false,
272 'message' => $error_message,
273 ], 401);
274 }
275
276 wp_set_current_user($user->ID);
277
278 return new \WP_REST_Response([
279 'success' => true,
280 'message' => __('Login successful! Redirecting...', 'yatra'),
281 'user_id' => $user->ID,
282 ]);
283 }
284
285 /**
286 * Handle registration request
287 */
288 public static function register(\WP_REST_Request $request): \WP_REST_Response
289 {
290 if (!\Yatra\Services\SettingsService::isEnabled('customer_registration')) {
291 return new \WP_REST_Response([
292 'success' => false,
293 'message' => __('New customer registration is disabled.', 'yatra'),
294 ], 403);
295 }
296
297 $first_name = sanitize_text_field($request->get_param('first_name') ?? '');
298 $last_name = sanitize_text_field($request->get_param('last_name') ?? '');
299 $email = sanitize_email($request->get_param('email') ?? '');
300 $phone = sanitize_text_field($request->get_param('phone') ?? '');
301 $password = $request->get_param('password') ?? '';
302 $confirm_password = $request->get_param('confirm_password') ?? '';
303
304 // Validation
305 if (empty($first_name)) {
306 return new \WP_REST_Response([
307 'success' => false,
308 'message' => __('Please enter your first name.', 'yatra'),
309 ], 400);
310 }
311
312 if (empty($last_name)) {
313 return new \WP_REST_Response([
314 'success' => false,
315 'message' => __('Please enter your last name.', 'yatra'),
316 ], 400);
317 }
318
319 if (empty($email) || !is_email($email)) {
320 return new \WP_REST_Response([
321 'success' => false,
322 'message' => __('Please enter a valid email address.', 'yatra'),
323 ], 400);
324 }
325
326 if (empty($password) || strlen($password) < 8) {
327 return new \WP_REST_Response([
328 'success' => false,
329 'message' => __('Password must be at least 8 characters long.', 'yatra'),
330 ], 400);
331 }
332
333 if ($password !== $confirm_password) {
334 return new \WP_REST_Response([
335 'success' => false,
336 'message' => __('Passwords do not match.', 'yatra'),
337 ], 400);
338 }
339
340 if (email_exists($email)) {
341 return new \WP_REST_Response([
342 'success' => false,
343 'message' => __('An account with this email already exists. Please login instead.', 'yatra'),
344 ], 409);
345 }
346
347 // Generate username
348 $username = sanitize_user(current(explode('@', $email)));
349 $original_username = $username;
350 $counter = 1;
351
352 while (username_exists($username)) {
353 $username = $original_username . $counter;
354 $counter++;
355 }
356
357 // Create user
358 $user_id = wp_create_user($username, $password, $email);
359
360 if (is_wp_error($user_id)) {
361 $error_message = wp_strip_all_tags($user_id->get_error_message());
362 return new \WP_REST_Response([
363 'success' => false,
364 'message' => $error_message,
365 ], 500);
366 }
367
368 // Assign Yatra Customer role
369 $user = new \WP_User($user_id);
370 $user->set_role('yatra_customer');
371
372 // Update user meta
373 wp_update_user([
374 'ID' => $user_id,
375 'first_name' => $first_name,
376 'last_name' => $last_name,
377 'display_name' => $first_name . ' ' . $last_name,
378 ]);
379
380 if (!empty($phone)) {
381 update_user_meta($user_id, 'billing_phone', $phone);
382 update_user_meta($user_id, 'phone', $phone);
383 }
384
385 // Email verification
386 update_user_meta($user_id, 'yatra_email_verified', '0');
387
388 $verification_token = wp_generate_password(32, false);
389 $secure_token = base64_encode($user_id . '|' . $verification_token . '|' . time());
390 $secure_token = str_replace(['+', '/', '='], ['-', '_', ''], $secure_token);
391
392 update_user_meta($user_id, 'yatra_verification_token', $verification_token);
393 update_user_meta($user_id, 'yatra_verification_token_expiry', time() + (24 * 60 * 60));
394
395 // Send verification email
396 self::sendVerificationEmail($user_id, $email, $first_name, $secure_token);
397
398 // Admin notification
399 wp_new_user_notification($user_id, null, 'admin');
400
401 return new \WP_REST_Response([
402 'success' => true,
403 'message' => __('Registration successful! Please check your email to verify your account before logging in.', 'yatra'),
404 'user_id' => $user_id,
405 'require_verification' => true,
406 ]);
407 }
408
409 /**
410 * Handle resend verification request
411 */
412 public static function resendVerification(\WP_REST_Request $request): \WP_REST_Response
413 {
414 $email = sanitize_email($request->get_param('email') ?? '');
415
416 if (empty($email) || !is_email($email)) {
417 return new \WP_REST_Response([
418 'success' => false,
419 'message' => __('Please enter a valid email address.', 'yatra'),
420 ], 400);
421 }
422
423 $user = get_user_by('email', $email);
424
425 if (!$user) {
426 return new \WP_REST_Response([
427 'success' => true,
428 'message' => __('If an account with this email exists and is pending verification, a new verification link has been sent.', 'yatra'),
429 ]);
430 }
431
432 $email_verified = get_user_meta($user->ID, 'yatra_email_verified', true);
433
434 if ($email_verified === '1' || $email_verified === 1 || $email_verified === true) {
435 return new \WP_REST_Response([
436 'success' => false,
437 'message' => __('This email is already verified. You can login now.', 'yatra'),
438 ], 400);
439 }
440
441 $meta_exists = metadata_exists('user', $user->ID, 'yatra_email_verified');
442 $has_verification_token = get_user_meta($user->ID, 'yatra_verification_token', true);
443
444 if (!$meta_exists && empty($has_verification_token)) {
445 return new \WP_REST_Response([
446 'success' => true,
447 'message' => __('If an account with this email exists and is pending verification, a new verification link has been sent.', 'yatra'),
448 ]);
449 }
450
451 // Rate limiting
452 $last_sent = get_user_meta($user->ID, 'yatra_verification_last_sent', true);
453 if ($last_sent && (time() - (int) $last_sent) < 120) {
454 $remaining = 120 - (time() - (int) $last_sent);
455 return new \WP_REST_Response([
456 'success' => false,
457 'message' => __('Please wait before requesting another verification email.', 'yatra'),
458 'rate_limited' => true,
459 'remaining_seconds' => $remaining,
460 ], 429);
461 }
462
463 // Generate new token
464 $verification_token = wp_generate_password(32, false);
465 $secure_token = base64_encode($user->ID . '|' . $verification_token . '|' . time());
466 $secure_token = str_replace(['+', '/', '='], ['-', '_', ''], $secure_token);
467
468 update_user_meta($user->ID, 'yatra_verification_token', $verification_token);
469 update_user_meta($user->ID, 'yatra_verification_token_expiry', time() + (24 * 60 * 60));
470 update_user_meta($user->ID, 'yatra_verification_last_sent', time());
471
472 // Send email
473 $first_name = get_user_meta($user->ID, 'first_name', true) ?: $user->display_name;
474 self::sendVerificationEmail($user->ID, $email, $first_name, $secure_token, true);
475
476 return new \WP_REST_Response([
477 'success' => true,
478 'message' => __('A new verification link has been sent to your email address. Please check your inbox.', 'yatra'),
479 ]);
480 }
481
482 /**
483 * Handle email verification
484 */
485 public static function handleEmailVerification(): void
486 {
487 $secure_token = (string) get_query_var('yatra_verify_email');
488 if ($secure_token === '' && isset($_GET['yatra_verify_email'])) {
489 $raw = wp_unslash($_GET['yatra_verify_email']);
490 $secure_token = is_string($raw) ? preg_replace('/[^a-zA-Z0-9_-]/', '', $raw) ?? '' : '';
491 }
492
493 if ($secure_token === '') {
494 self::showVerificationError(__('Invalid verification link.', 'yatra'));
495
496 return;
497 }
498
499 $previewToken = defined('YATRA_EMAIL_VERIFICATION_PREVIEW_TOKEN')
500 ? (string) YATRA_EMAIL_VERIFICATION_PREVIEW_TOKEN
501 : 'preview-verify-token';
502 if ($secure_token === $previewToken) {
503 self::showEmailVerificationPreviewNotice();
504
505 return;
506 }
507
508 // Decode token
509 $secure_token = str_replace(['-', '_'], ['+', '/'], $secure_token);
510 $decoded = base64_decode($secure_token);
511
512 if (!$decoded || strpos($decoded, '|') === false) {
513 self::showVerificationError(__('Invalid verification link.', 'yatra'));
514 return;
515 }
516
517 $parts = explode('|', $decoded);
518 if (count($parts) < 2) {
519 self::showVerificationError(__('Invalid verification link.', 'yatra'));
520 return;
521 }
522
523 $user_id = (int) $parts[0];
524 $token = $parts[1];
525
526 if ($user_id <= 0 || empty($token)) {
527 self::showVerificationError(__('Invalid verification link.', 'yatra'));
528 return;
529 }
530
531 $stored_token = get_user_meta($user_id, 'yatra_verification_token', true);
532 $token_expiry = get_user_meta($user_id, 'yatra_verification_token_expiry', true);
533
534 if (empty($stored_token) || $stored_token !== $token) {
535 self::showVerificationError(__('Invalid or expired verification link.', 'yatra'));
536 return;
537 }
538
539 if ($token_expiry && time() > (int) $token_expiry) {
540 self::showVerificationError(__('This verification link has expired. Please register again.', 'yatra'));
541 return;
542 }
543
544 // Mark as verified
545 update_user_meta($user_id, 'yatra_email_verified', '1');
546 delete_user_meta($user_id, 'yatra_verification_token');
547 delete_user_meta($user_id, 'yatra_verification_token_expiry');
548
549 $redirect_url = add_query_arg(['email_verified' => '1'], yatra_get_checkout_url());
550 wp_safe_redirect($redirect_url);
551 exit;
552 }
553
554 /**
555 * Send verification email
556 */
557 private static function sendVerificationEmail(int $_user_id, string $email, string $first_name, string $secure_token, bool $isResend = false): void
558 {
559 $verificationUrl = function_exists('yatra_get_email_verification_url')
560 ? yatra_get_email_verification_url($secure_token)
561 : home_url('/yatra-verify-email/' . rawurlencode($secure_token) . '/');
562
563 $siteName = get_bloginfo('name');
564 $introParagraph = $isResend
565 ? sprintf(
566 /* translators: %s: site name */
567 __('You requested a new verification link for your account at %s. Click the button below to verify your email address.', 'yatra'),
568 $siteName
569 )
570 : sprintf(
571 /* translators: %s: site name */
572 __('Thank you for registering at %s. Please verify your email address to activate your account.', 'yatra'),
573 $siteName
574 );
575
576 $footerNote = $isResend
577 ? __('If you did not request this email, you can ignore it.', 'yatra')
578 : __('If you did not create this account, you can ignore this email.', 'yatra');
579
580 $expiryNoticeHtml = esc_html(
581 sprintf(
582 /* translators: %d: hours until expiry */
583 __('This verification link expires in %d hours for your security.', 'yatra'),
584 24
585 )
586 );
587
588 \Yatra\Services\TransactionalEmailTemplateService::sendIfEnabled(
589 \Yatra\Services\TransactionalEmailTemplateService::TYPE_CUSTOMER_EMAIL_VERIFICATION,
590 $email,
591 [
592 'customer_first_name' => $first_name,
593 'customer_name' => $first_name,
594 'verification_link' => $verificationUrl,
595 'intro_paragraph' => $introParagraph,
596 'footer_note' => $footerNote,
597 'expiry_notice_html' => $expiryNoticeHtml,
598 ]
599 );
600 }
601
602 /**
603 * Show verification error page
604 */
605 private static function showVerificationError(string $message): void
606 {
607 wp_die(
608 '<div style="text-align: center; padding: 50px; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;">
609 <h1 style="color: #dc2626; margin-bottom: 20px;">Verification Failed</h1>
610 <p style="color: #4b5563; font-size: 16px; margin-bottom: 30px;">' . esc_html($message) . '</p>
611 <a href="' . esc_url(home_url()) . '" style="display: inline-block; background: #3b82f6; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">Go to Homepage</a>
612 </div>',
613 __('Verification Failed', 'yatra'),
614 ['response' => 400]
615 );
616 }
617
618 /**
619 * Inform users that the link is only for email template previews.
620 */
621 private static function showEmailVerificationPreviewNotice(): void
622 {
623 $body = '<div style="text-align: center; padding: 50px; max-width: 520px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;">
624 <h1 style="color: #1e40af; margin-bottom: 16px;">' . esc_html__('Sample verification link', 'yatra') . '</h1>
625 <p style="color: #4b5563; font-size: 16px; line-height: 1.6; margin-bottom: 24px;">' . esc_html__(
626 'This URL is used only in email previews and test messages. It does not verify an account. Use the link from your real verification email to activate your account.',
627 'yatra'
628 ) . '</p>
629 <a href="' . esc_url(home_url()) . '" style="display: inline-block; background: #3b82f6; color: #fff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">' . esc_html__('Go to homepage', 'yatra') . '</a>
630 </div>';
631
632 wp_die(
633 $body,
634 __('Email verification (preview)', 'yatra'),
635 ['response' => 200]
636 );
637 }
638 }
639
640