Jake Vanderwerf
2 days ago 235ce5716edc2f7cbe80fdccf26eac7269587839
inc/rest/routes/LoginRoutes.php
@@ -1,9 +1,11 @@
<?php
namespace JVBase\rest\routes;
use JVBase\managers\Cache;
use JVBase\registrar\Registrar;
use JVBase\rest\Rest;
use JVBase\rest\Route;
use JVBase\utility\Features;
use JVBase\base\Site;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
@@ -30,7 +32,7 @@
      parent::__construct();
      $this->hasMagicLink = Features::forSite()->has('magicLink');
      $this->hasMagicLink = Site::has('magicLink');
   }
   public function registerRoutes(): void
@@ -39,7 +41,8 @@
      Route::for('auth/status')
         ->get([$this, 'getAuthStatus'])
         ->auth('public')
         ->rateLimit(60, 60);
         ->rateLimit()
         ->register();
      // Standard login
      Route::for('auth/login')
@@ -51,7 +54,8 @@
            'redirect_to' => 'string',
         ])
         ->auth('public')
         ->rateLimit(5, 300);
         ->rateLimit(5, 300)
         ->register();
      // User registration
      Route::for('auth/register')
@@ -64,7 +68,8 @@
            'redirect_to' => 'string',
         ])
         ->auth('public')
         ->rateLimit(3, 3600);
         ->rateLimit(3, 3600)
         ->register();
      // Request password reset
      Route::for('auth/lostpassword')
@@ -73,7 +78,8 @@
            'user_email' => 'email|required',
         ])
         ->auth('public')
         ->rateLimit(3, 3600);
         ->rateLimit(3, 3600)
         ->register();
      // Reset password with token
      Route::for('auth/resetpass')
@@ -85,7 +91,8 @@
            'pass2' => 'string|required',
         ])
         ->auth('public')
         ->rateLimit(5, 300);
         ->rateLimit(5, 300)
         ->register();
      // Magic link endpoint
      if ($this->hasMagicLink) {
@@ -97,14 +104,16 @@
               'redirect_to' => 'string',
            ])
            ->auth('public')
            ->rateLimit(5, 3600);
            ->rateLimit(5, 3600)
            ->register();
      }
      // Logout endpoint
      Route::for('auth/logout')
         ->post([$this, 'handleLogout'])
         ->auth('logged_in')
         ->rateLimit(10, 60);
         ->rateLimit(10)
         ->register();
   }
   /**
@@ -112,36 +121,14 @@
    */
   public function getAuthStatus(WP_REST_Request $request): WP_REST_Response
   {
      $user = wp_get_current_user();
      $authenticated = $user->exists();
      $data = $this->buildAuth();
      $response = $this->success($data);
      $response = [
         'authenticated' => $authenticated,
         'user' => false,
         'nonces' => [
            'wp_rest' => wp_create_nonce('wp_rest'),
         ],
         'session_id' => session_id() ?: wp_generate_password(32, false),
      ];
      // Add caching headers
      $response->header('Cache-Control', 'private, max-age=300'); // 5 minutes
      $response->header('Vary', 'Cookie'); // Important for nginx
      if ($authenticated) {
         // Validate session fingerprint
         if (!$this->validateSessionFingerprint($user->ID, $request)) {
            wp_logout();
            $response['authenticated'] = false;
            $response['session_invalid'] = true;
         } else {
            $response['user'] = [
               'id' => $user->ID,
               'name' => $user->display_name,
               'email' => $user->user_email,
               'roles' => $user->roles,
               'link' => get_user_meta($user->ID, BASE . 'link', true),
            ];
         }
      }
      return $this->success($response);
      return $response;
   }
   /**
@@ -149,18 +136,23 @@
    */
   public function handleLogin(WP_REST_Request $request): WP_REST_Response
   {
      error_log('Handling login...');
      $email = sanitize_email($request->get_param('user_email'));
      $password = $request->get_param('user_password');
      $remember = (bool) $request->get_param('remember_me');
      $redirect_to = $request->get_param('redirect_to');
      // Verify Turnstile
      if (!$this->verifyTurnstile($request->get_param('cf-turnstile-response') ?? '')) {
         error_log('[handleLogin]Turnstile failed');
         return $this->error(
            'Security verification failed. Please try again.',
            'turnstile_failed',
            403
         );
      } else {
         error_log('[handleLogin]Turnstile succeeded');
      }
      // Attempt authentication
@@ -201,20 +193,7 @@
      return $this->success([
         'message' => 'Login successful',
         'redirect' => $redirect,
         'auth' => [
            'authenticated' => true,
            'user' => [
               'id' => $user->ID,
               'name' => $user->display_name,
               'email' => $user->user_email,
               'roles' => $user->roles,
               'link' => get_user_meta($user->ID, BASE . 'link', true),
            ],
            'nonces' => [
               'wp_rest' => wp_create_nonce('wp_rest'),
            ],
            'session_id' => session_id() ?: wp_generate_password(32, false),
         ]
         'auth' => $this->buildAuth($user->ID)
      ]);
   }
@@ -289,7 +268,7 @@
      ]);
      // Process referral code if provided
      if (!empty($referral_code) && Features::forSite()->has('referrals')) {
      if (!empty($referral_code) && Site::has('referrals')) {
         $this->processReferralCode($user_id, $referral_code);
      }
@@ -368,7 +347,7 @@
               'error' => $key->get_error_message(),
            ]);
         } else {
            $this->sendPasswordResetEmail($user, $key);
            $success = JVB()->email()->sendPasswordResetEmail($user, $key);
         }
      }
@@ -658,17 +637,17 @@
      }
      // Check if role is valid and can register
      $role_config = JVB_USER[$user_select] ?? null;
      $registrar = Registrar::getInstance($user_select);
      if (!$role_config) {
      if (!$registrar) {
         return new WP_Error('invalid_role', 'Invalid role selected.');
      }
      if (!($role_config['can_register'] ?? false)) {
      if (!($registrar->hasFeature('can_register') ?? false)) {
         return new WP_Error('role_not_allowed', 'This role cannot be selected during registration.');
      }
      return BASE . $user_select;
      return $registrar->getBased();
   }
   /**
@@ -676,7 +655,7 @@
    */
   protected function processReferralCode(int $user_id, string $referral_code): void
   {
      if (!Features::forSite()->has('referrals')) {
      if (!Site::has('referrals')) {
         return;
      }
@@ -738,23 +717,45 @@
      }
   }
   /**
    * Send password reset email (fallback if magic links not available)
    */
   protected function sendPasswordResetEmail(WP_User $user, string $key): bool
   public static function auth():array
   {
      $reset_url = network_site_url(
         "wp-login.php?action=rp&key=$key&login=" . rawurlencode($user->user_login),
         'login'
      );
      return (new self)->buildAuth();
   }
      $subject = 'Password Reset Request';
      $message = sprintf(
         "Hello %s,\n\nYou requested a password reset. Click the link below to reset your password:\n\n%s\n\nIf you didn't request this, please ignore this email.",
         $user->display_name,
         $reset_url
      );
   protected function buildAuth(?int $user = null): array
   {
      $userId = $user ?? (is_user_logged_in() ? get_current_user_id() : 0);
      return wp_mail($user->user_email, $subject, $message);
      if ($userId) {
         return [
            'authenticated' => true,
            'user'          => $userId,
            'nonces'        => $this->getUserNonces($userId),
         ];
      }
      return [
         'authenticated' => false,
         'user'          => false,
         'nonces'        => ['wp_rest' => wp_create_nonce('wp_rest')],
      ];
   }
   protected function getUserNonces(int $userID):array {
      $nonces = [
         'wp_rest'   => wp_create_nonce('wp_rest'),
      ];
      if (Site::has('dashboard')) {
         $nonces['dash'] = wp_create_nonce('dash-'.$userID);
      }
      if (Site::has('favourites')) {
         $nonces['favourites'] = wp_create_nonce('favourites-'.$userID);
      }
      if (!empty(Registrar::withFeature('karma'))) {
         $nonces['votes'] = wp_create_nonce('votes-'.$userID);
      }
      if (Site::has('notifications')) {
         $nonces['notifications'] = wp_create_nonce('notifications-'.$userID);
      }
      return $nonces;
   }
}