From 3aada9949d51024a92a8b5c6cb70d12f9c3cac16 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 21 Dec 2025 19:59:48 +0000
Subject: [PATCH] =auth refactored via rest, referral system set up for Jane, some javascript consolidation

---
 inc/rest/routes/LoginRoutes.php |  229 +++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 166 insertions(+), 63 deletions(-)

diff --git a/inc/rest/routes/LoginRoutes.php b/inc/rest/routes/LoginRoutes.php
index ad47bff..8c663c9 100644
--- a/inc/rest/routes/LoginRoutes.php
+++ b/inc/rest/routes/LoginRoutes.php
@@ -1,14 +1,12 @@
 <?php
 namespace JVBase\rest\routes;
 
-use JVBase\managers\EmailManager;
-use JVBase\managers\LoginManager;
-use JVBase\managers\MagicLinkManager;
 use JVBase\rest\RestRouteManager;
 use JVBase\utility\Features;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
+use WP_Session_Tokens;
 use WP_User;
 
 if (!defined('ABSPATH')) {
@@ -17,19 +15,12 @@
 
 class LoginRoutes extends RestRouteManager
 {
-	protected EmailManager $emailManager;
-	protected MagicLinkManager $magic_link;
-	protected LoginManager $loginManager;
 	protected ?string $requestId = null;
 
 	public function __construct()
 	{
 		$this->cache_name = 'auth';
 		$this->cache_ttl = WEEK_IN_SECONDS;
-		$this->emailManager = new EmailManager();
-		if (Features::forSite()->has('magicLink')) {
-			$this->magic_link = new MagicLinkManager();
-		}
 
 		parent::__construct();
 	}
@@ -113,7 +104,7 @@
 		]);
 
 		register_rest_route($this->namespace, '/auth/register', [
-			'method'	=> 'POST',
+			'methods'	=> 'POST',
 			'callback'	=> [$this, 'handleRegister'],
 			'permission_callback' => [$this, 'checkRateLimit'],
 		]);
@@ -153,7 +144,11 @@
 				429
 			);
 		}
+		return $this->login($username, $password, $remember, $request);
+	}
 
+	public function login(string $username, string $password, bool $remember, ?WP_REST_Request $request = null):WP_REST_Response|bool
+	{
 		// Attempt login
 		$user = wp_signon([
 			'user_login'	=> $username,
@@ -166,11 +161,11 @@
 			// Track failed attempt
 			$this->trackFailedLogin($username);
 
-			return $this->error(
+			return ($request) ? $this->error(
 				'Invalid username or password',
 				'login_failed',
 				401
-			);
+			) : false;
 		}
 
 		// Clear failed attempts on success
@@ -181,7 +176,9 @@
 		wp_set_auth_cookie($user->ID, $remember);
 
 		// Store session fingerprint for hijacking protection
-		$this->storeSessionFingerprint($user->ID, $request);
+		if ($request) {
+			$this->storeSessionFingerprint($user->ID, $request);
+		}
 
 		// Trigger WordPress login action
 		do_action('wp_login', $user->user_login, $user);
@@ -192,11 +189,33 @@
 			'remember' => $remember
 		]);
 
-		return $this->success([
+		return ($request) ? $this->success([
 			'message' => 'Login successful',
 			'user' => $this->formatUserData($user),
-			'redirect' => $this->getLoginRedirect($user)
-		]);
+			'redirect' => $this->getRedirect($user, $request->get_param('redirect_to')),
+			'auth' => $this->buildAuth($user->ID)
+		]) : true;
+	}
+
+	protected function getUserNonces(int $userID):array {
+		$nonces = [
+			'wp_rest'	=> wp_create_nonce('wp_rest'),
+		];
+		if (Features::forSite()->has('dashboard')) {
+			$nonces['dash'] = wp_create_nonce('dash-'.$userID);
+		}
+		if (Features::forSite()->has('favourites')) {
+			$nonces['favourites'] = wp_create_nonce('favourites-'.$userID);
+		}
+		if (Features::anyContentHas('karma') ||
+			Features::anyTaxonomyHas('karma') ||
+			Features::anyUserHas('karma')) {
+			$nonces['votes'] = wp_create_nonce('votes-'.$userID);
+		}
+		if (Features::forSite()->has('notifications')) {
+			$nonces['notifications'] = wp_create_nonce('notifications-'.$userID);
+		}
+		return $nonces;
 	}
 
 	/**
@@ -216,27 +235,65 @@
 		wp_logout();
 
 		return $this->success([
-			'message' => 'Logged out successfully'
+			'message' => 'Logged out successfully',
+			'redirect' => $this->getRedirect(get_userdata($user_id), $request->get_param('redirect_to'), 'logout')
 		]);
 	}
 
+	protected function buildAuth(?int $user = null): array
+	{
+		if (is_user_logged_in()) {
+			$user = ($user) ?: get_current_user_id();
+			return [
+				'authenticated' => true,
+				'user' => $user,
+				'nonces' => $this->getUserNonces($user),
+				'session_id' => $this->getSessionId($user)
+			];
+		}
+
+		return [
+			'authenticated' => false,
+			'currentUser' => false,
+			'nonces' => [
+				'wp_rest' => wp_create_nonce('wp_rest')
+			],
+			'session_id' => null
+		];
+	}
+
+	/**
+	 * Get unique session identifier that changes on login/logout
+	 */
+	protected function getSessionId(int $user_id): string
+	{
+		// Use WordPress session tokens
+		$sessions = WP_Session_Tokens::get_instance($user_id);
+		$token = wp_get_session_token(); // Current session token
+
+		if (!$token) {
+			// Fallback to user-specific hash that changes on password reset
+			return md5($user_id . get_user_meta($user_id, 'session_tokens', true));
+		}
+
+		return md5($token);
+	}
+
 	/**
 	 * Get current authentication status
 	 */
 	public function getAuthStatus(WP_REST_Request $request): WP_REST_Response
 	{
-		if (!is_user_logged_in()) {
-			return $this->success([
-				'authenticated' => false
-			]);
-		}
 
-		$user = wp_get_current_user();
+		$responseData = $this->buildAuth();
 
-		return $this->success([
-			'authenticated' => true,
-			'user' => $this->formatUserData($user)
-		]);
+		$response = $this->success($responseData);
+
+		// Add caching headers
+		$response->header('Cache-Control', 'private, max-age=300'); // 5 minutes
+		$response->header('Vary', 'Cookie'); // Important for nginx
+
+		return $response;
 	}
 
 	/**
@@ -365,6 +422,7 @@
 
 		$name = sanitize_text_field($data['name'] ?? '');
 		$email = sanitize_email($data['email'] ?? '');
+		$referral_code = $request->get_param('referral_code')??'';
 		$user_type = sanitize_text_field($data['user_select'] ?? 'subscriber');
 
 		// Validate fields
@@ -376,14 +434,6 @@
 			return $this->error('Email is required', 'missing_email', 400, 'email');
 		}
 
-		// Spam prevention
-		if ($user_type === 'subscriber' && count(JVB_USER) > 0) {
-			$registerable = array_filter(JVB_USER, fn($config) => $config['can_register'] ?? false);
-			if (!empty($registerable)) {
-				return $this->error('Please select a valid account type', 'invalid_user_type', 400, 'user_select');
-			}
-		}
-
 		// Check if role can register
 		if ($user_type !== 'subscriber') {
 			if (!isset(JVB_USER[$user_type]) || empty(JVB_USER[$user_type]['can_register'])) {
@@ -396,6 +446,19 @@
 			return $this->error('Email already registered', 'duplicate_email', 400, 'email');
 		}
 
+		// Validate referral code if provided
+		$referrer_id = null;
+		if ($referral_code) {
+			$code = strtoupper(sanitize_text_field($referral_code));
+			$referrer = JVB()->referrals()->getUserByReferralCode($code);
+
+			if (!$referrer) {
+				return $this->error('Invalid referral code', 'invalid_code', 400);
+			}
+
+			$referrer_id = $referrer->ID;
+		}
+
 		// Allow WP plugins to add registration errors
 		$errors = new WP_Error();
 		$errors = apply_filters('registration_errors', $errors, $email, $email);
@@ -408,27 +471,42 @@
 			);
 		}
 
-		// Create user
-		$user_id = wp_create_user($email, wp_generate_password(), $email);
+		// Update user data
+		$role = ($referrer_id) ? get_option(BASE . 'referral_role', BASE . 'client') : jvbCheckBase($user_type);
+		$userData = [
+			'user_login'	=> $email,
+			'user_email'	=> $email,
+			'display_name'	=> $name,
+			'first_name'	=> strtok($name, ' '),
+			'role'			=> $role
+		];
 
-		if (is_wp_error($user_id)) {
-			return $this->error($user_id->get_error_message(), 'user_creation_failed', 500);
+		// Add password if provided, otherwise generate one
+		$password = $request->get_param('password');
+		if ($password) {
+			$userData['user_pass'] = $password;
+		} else {
+			$userData['user_pass'] = wp_generate_password(20, true, true);
 		}
 
-		// Update user data
-		wp_update_user([
-			'ID' => $user_id,
-			'display_name' => $name,
-			'first_name' => strtok($name,' ')
-		]);
+		$user_id = wp_insert_user($userData);
+
+		if (is_wp_error($user_id)) {
+			return $this->error(
+				$user_id->get_error_message(),
+				'registration_failed',
+				500
+			);
+		}
+
+		// Process referral if code was provided
+		if ($referrer_id) {
+			update_user_meta($user_id, BASE . 'pending_referral_code', $referral_code);
+		}
 
 		// Set role
-		$user = new WP_User($user_id);
-		if ($user_type === 'subscriber') {
-			$user->set_role('subscriber');
-		} else {
-			$role = JVB_USER[$user_type]['role'] ?? 'subscriber';
-			$user->set_role($role);
+		$user = get_userdata($user_id);
+		if ($user_type !== 'subscriber') {
 
 			// Check if needs approval
 			if (Features::forMembership()->has('memberVerified') &&
@@ -446,9 +524,6 @@
 			wp_mkdir_p($target_dir);
 		}
 
-		// Save additional fields
-		update_user_meta($user_id, BASE . 'user_type', $user_type);
-
 		// Process additional fields from form
 		foreach ($data as $key => $value) {
 			if (in_array($key, ['name', 'email', 'action', 'request_id', 'user_select', 'cf-turnstile-response'])) {
@@ -457,13 +532,31 @@
 			update_user_meta($user_id, BASE . $key, sanitize_text_field($value));
 		}
 
+		$redirect = $this->getRedirect($user, $request->get_param('redirect_to'), 'register');
+
 		// Handle token handlers
 		do_action('jvbUserRegistered', $user_id, $email, $data);
+		$magic_link_result = JVB()->magicLink()?->sendMagicLink(
+			$email,
+			'login',
+			[
+				'user_id' => $user_id,
+				'redirect' => $redirect
+			]
+		);
 
+		if (is_wp_error($magic_link_result)) {
+			return $this->error(
+				'Account created but failed to send verification email. Please use password reset.',
+				'magic_link_failed',
+				500
+			);
+		}
 
 		return $this->success([
 			'message' => 'Registration successful! Check your email.',
-			'user_id' => $user_id
+			'user_id' => $user_id,
+			'redirect' => $redirect
 		]);
 	}
 	/**************************************************************
@@ -488,18 +581,28 @@
 		];
 	}
 
-	/**
-	 * Get login redirect URL based on user role
-	 */
-	protected function getLoginRedirect(WP_User $user): string
+	protected function getRedirect(WP_User $user, string $url, string $context = 'login'):string
 	{
+		if (!empty($url)) {
+			$url = sanitize_url($url);
+			if (wp_validate_redirect($url)) {
+				return $url;
+			}
+		}
+
+		// Redirect to custom dashboard for members
+		if (function_exists('isOurPeople') && isOurPeople()) {
+			return home_url('/dash');
+		}
+
+		// Admins can go to wp-admin if they want (but only if not using custom dashboard)
 		if (user_can($user, 'manage_options')) {
 			return admin_url();
 		}
 
-		// Redirect to dashboard for members
-		if (function_exists('isOurPeople') && isOurPeople()) {
-			return home_url('/dash');
+		$custom_redirect = get_option(BASE . 'after_'.$context.'_redirect');
+		if ($custom_redirect) {
+			return $custom_redirect;
 		}
 
 		return home_url();

--
Gitblit v1.10.0