From a6413da67a4579866f0fd0323ddb23a7b6b9654a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 18 Sep 2026 17:00:53 +0000
Subject: [PATCH] =Cleaning up the Integrations setup a bit, started the Helcim integration work too

---
 inc/rest/routes/LoginRoutes.php |  232 +++++++++++++++++++++------------------------------------
 1 files changed, 86 insertions(+), 146 deletions(-)

diff --git a/inc/rest/routes/LoginRoutes.php b/inc/rest/routes/LoginRoutes.php
index 0ef1550..9ff7f51 100644
--- a/inc/rest/routes/LoginRoutes.php
+++ b/inc/rest/routes/LoginRoutes.php
@@ -1,6 +1,11 @@
 <?php
 namespace JVBase\rest\routes;
 
+use Exception;
+use JVBase\Instance;
+use JVBase\managers\Emails;
+use JVBase\managers\MagicLinks;
+use JVBase\managers\Referrals;
 use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
@@ -21,6 +26,7 @@
  */
 class LoginRoutes extends Rest
 {
+	use Instance;
 	protected ?string $requestId = null;
 	protected bool $hasMagicLink = false;
 
@@ -30,8 +36,8 @@
 		$this->cacheTtl = WEEK_IN_SECONDS;
 
 		parent::__construct();
+		$this->hasMagicLink = Site::has('magic_link');
 
-		$this->hasMagicLink = Site::has('magicLink');
 	}
 
 	public function registerRoutes(): void
@@ -94,7 +100,8 @@
 			->register();
 
 		// Magic link endpoint
-		if ($this->hasMagicLink) {
+
+		if (Site::has('magic_link')) {
 			Route::for('auth/magic')
 				->post([$this, 'handleMagicLink'])
 				->args([
@@ -135,29 +142,27 @@
 	 */
 	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
-		$user = wp_authenticate($email, $password);
-
+		$user = $this->login($email, $password, $remember);
 		if (is_wp_error($user)) {
-			$this->auditLog('login_failed', [
-				'email' => $email,
-				'error' => $user->get_error_code(),
-			]);
-
 			return $this->error(
 				$this->getLoginErrorMessage($user),
 				$user->get_error_code(),
@@ -165,21 +170,6 @@
 			);
 		}
 
-		// Set auth cookie
-		wp_clear_auth_cookie();
-		wp_set_current_user($user->ID);
-		wp_set_auth_cookie($user->ID, $remember);
-
-		// Store session fingerprint
-		$this->storeSessionFingerprint($user->ID, $request);
-
-		do_action('wp_login', $user->user_login, $user);
-
-		$this->auditLog('login_success', [
-			'user_id' => $user->ID,
-			'email' => $email,
-		]);
-
 		// Get redirect URL
 		$redirect = $this->getRedirectUrl($user, $redirect_to);
 
@@ -191,6 +181,40 @@
 		]);
 	}
 
+	public function login(string $email, string $password, bool $remember, ?WP_REST_Request $request = null):WP_User|WP_Error
+	{
+		// Attempt authentication
+		$user = wp_authenticate($email, $password);
+
+		if (is_wp_error($user)) {
+			$this->auditLog('login_failed', [
+				'email' => $email,
+				'error' => $user->get_error_code(),
+			]);
+
+			return $user;
+		}
+
+		// Set auth cookie
+		wp_clear_auth_cookie();
+		wp_set_current_user($user->ID);
+		wp_set_auth_cookie($user->ID, $remember);
+
+		// Store session fingerprint
+		if ($request) {
+			$this->storeSessionFingerprint($user->ID, $request);
+		}
+
+
+		do_action('wp_login', $user->user_login, $user);
+
+		$this->auditLog('login_success', [
+			'user_id' => $user->ID,
+			'email' => $email,
+		]);
+		return $user;
+	}
+
 	/**
 	 * Handle user registration
 	 */
@@ -239,7 +263,7 @@
 		);
 
 		if (is_wp_error($user_id)) {
-			$this->logError('Registration failed', [
+			$this->logError('Registration failed', 'handleRegister', [
 				'email' => $email,
 				'error' => $user_id->get_error_message(),
 			]);
@@ -273,7 +297,7 @@
 
 		// Send magic link for email verification
 		if ($this->hasMagicLink) {
-			JVB()->magicLink()->sendMagicLink($email, 'signup', [
+			MagicLinks::getInstance()->sendMagicLink($email, 'signup', [
 				'name' => $name,
 				'role' => $role,
 			]);
@@ -324,10 +348,10 @@
 
 		// Use magic link if available, otherwise standard WP reset
 		if ($this->hasMagicLink) {
-			$result = JVB()->magicLink()->sendMagicLink($email, 'reset');
+			$result = MagicLinks::getInstance()->sendMagicLink($email, 'reset');
 
 			if (is_wp_error($result)) {
-				$this->logError('Magic link send failed', [
+				$this->logError('Magic link send failed', 'handleLostPassword',[
 					'email' => $email,
 					'error' => $result->get_error_message(),
 				]);
@@ -336,12 +360,12 @@
 			// Standard WordPress password reset
 			$key = get_password_reset_key($user);
 			if (is_wp_error($key)) {
-				$this->logError('Reset key generation failed', [
+				$this->logError('Reset key generation failed', 'handleLostPassword', [
 					'email' => $email,
 					'error' => $key->get_error_message(),
 				]);
 			} else {
-				$success = JVB()->email()->sendPasswordResetEmail($user, $key);
+				$success = Emails::getInstance()->sendPasswordResetEmail($user, $key);
 			}
 		}
 
@@ -392,8 +416,7 @@
 		if (is_wp_error($user)) {
 			return $this->error(
 				'Invalid or expired reset link.',
-				'invalid_key',
-				400
+				'invalid_key'
 			);
 		}
 
@@ -415,11 +438,11 @@
 	 */
 	public function handleMagicLink(WP_REST_Request $request): WP_REST_Response
 	{
-		if (!$this->hasMagicLink) {
+
+		if (!Site::has('magic_link')) {
 			return $this->error(
 				'Magic link authentication is not enabled.',
-				'feature_disabled',
-				400
+				'feature_disabled'
 			);
 		}
 
@@ -442,10 +465,10 @@
 		}
 
 		// Send magic link
-		$result = JVB()->magicLink()->sendMagicLink($email, $type, $context);
+		$result = MagicLinks::getInstance()->sendMagicLink($email, $type, $context);
 
 		if (is_wp_error($result)) {
-			$this->logError('Magic link send failed', [
+			$this->logError('Magic link send failed', 'handleMagicLink', [
 				'email' => $email,
 				'type' => $type,
 				'error' => $result->get_error_message(),
@@ -453,8 +476,7 @@
 
 			return $this->error(
 				$result->get_error_message(),
-				$result->get_error_code(),
-				400
+				$result->get_error_code()
 			);
 		}
 
@@ -495,87 +517,6 @@
 		]);
 	}
 
-	/************************************************************
-	 * SESSION FINGERPRINTING
-	 *
-	 * Detects session hijacking by validating that session hasn't
-	 * moved to a different device/network. Uses IP class (not full IP)
-	 * to allow mobile network changes without breaking sessions.
-	 ************************************************************/
-
-	/**
-	 * Store session fingerprint for hijacking detection
-	 */
-	protected function storeSessionFingerprint(int $user_id, WP_REST_Request $request): void
-	{
-		if (!defined('JVB_SESSION_FINGERPRINT') || !JVB_SESSION_FINGERPRINT) {
-			return;
-		}
-
-		$fingerprint = $this->generateSessionFingerprint($request);
-		update_user_meta($user_id, BASE . 'session_fingerprint', $fingerprint);
-		update_user_meta($user_id, BASE . 'session_timestamp', time());
-	}
-
-	/**
-	 * Generate session fingerprint for hijacking detection
-	 */
-	protected function generateSessionFingerprint(WP_REST_Request $request): string
-	{
-		return hash('sha256', implode('|', [
-			$request->get_header('User-Agent') ?? '',
-			// Use IP class instead of full IP to allow for mobile network changes
-			$this->getIPClass(
-				$request->get_header('X-Forwarded-For')
-					?: $request->get_header('X-Real-IP')
-					?: $_SERVER['REMOTE_ADDR'] ?? ''
-			)
-		]));
-	}
-
-	/**
-	 * Get IP class (first 3 octets) for session validation
-	 * Allows for minor IP changes (common with mobile networks)
-	 */
-	protected function getIPClass(string $ip): string
-	{
-		$parts = explode('.', $ip);
-		return implode('.', array_slice($parts, 0, 3));
-	}
-
-	/**
-	 * Validate session fingerprint against stored value
-	 */
-	protected function validateSessionFingerprint(int $user_id, WP_REST_Request $request): bool
-	{
-		// Only enforce if enabled in config
-		if (!defined('JVB_SESSION_FINGERPRINT') || !JVB_SESSION_FINGERPRINT) {
-			return true;
-		}
-
-		$stored = get_user_meta($user_id, BASE . 'session_fingerprint', true);
-		$current = $this->generateSessionFingerprint($request);
-
-		if (empty($stored)) {
-			// First request - store fingerprint
-			update_user_meta($user_id, BASE . 'session_fingerprint', $current);
-			update_user_meta($user_id, BASE . 'session_timestamp', time());
-			return true;
-		}
-
-		// Compare using timing-safe comparison
-		return hash_equals($stored, $current);
-	}
-
-	/**
-	 * Clear session fingerprint (call on logout)
-	 */
-	protected function clearSessionFingerprint(int $user_id): void
-	{
-		delete_user_meta($user_id, BASE . 'session_fingerprint');
-		delete_user_meta($user_id, BASE . 'session_timestamp');
-	}
-
 	/**************************************************************
 	 * HELPERS
 	 **************************************************************/
@@ -654,13 +595,13 @@
 		}
 
 		try {
-			JVB()->referrals()->processReferralCode($user_id, $referral_code);
-		} catch (\Exception $e) {
-			$this->logError('Referral processing failed', [
+			Referrals::getInstance()->processReferralCode($user_id, $referral_code);
+		} catch (Exception $e) {
+			$this->logError('Referral processing failed', 'processReferralCode', [
 				'user_id' => $user_id,
 				'code' => $referral_code,
 				'error' => $e->getMessage(),
-			], 'warning');
+			]);
 		}
 	}
 
@@ -697,37 +638,36 @@
 	 */
 	protected function sanitizeFieldValue(mixed $value, string $type):string|int
 	{
-		switch ($type) {
-			case 'email':
-				return sanitize_email($value);
-			case 'url':
-				return esc_url_raw($value);
-			case 'textarea':
-				return sanitize_textarea_field($value);
-			case 'number':
-				return absint($value);
-			default:
-				return sanitize_text_field($value);
-		}
+		return match ($type) {
+			'email' => sanitize_email($value),
+			'url' => esc_url_raw($value),
+			'textarea' => sanitize_textarea_field($value),
+			'number' => absint($value),
+			default => sanitize_text_field($value),
+		};
+	}
+
+	public static function auth():array
+	{
+		return (new self)->buildAuth();
 	}
 
 	protected function buildAuth(?int $user = null): array
 	{
-		if (is_user_logged_in()) {
-			$user = ($user) ?: get_current_user_id();
+		$userId = $user ?? (is_user_logged_in() ? get_current_user_id() : 0);
+
+		if ($userId) {
 			return [
 				'authenticated' => true,
-				'user' => $user,
-				'nonces' => $this->getUserNonces($user)
+				'user'          => $userId,
+				'nonces'        => $this->getUserNonces($userId),
 			];
 		}
 
 		return [
 			'authenticated' => false,
-			'user' => false,
-			'nonces' => [
-				'wp_rest' => wp_create_nonce('wp_rest')
-			]
+			'user'          => false,
+			'nonces'        => ['wp_rest' => wp_create_nonce('wp_rest')],
 		];
 	}
 	protected function getUserNonces(int $userID):array {
@@ -740,7 +680,7 @@
 		if (Site::has('favourites')) {
 			$nonces['favourites'] = wp_create_nonce('favourites-'.$userID);
 		}
-		if (!empty(Registrar::getFeatured('karma'))) {
+		if (!empty(Registrar::withFeature('karma'))) {
 			$nonces['votes'] = wp_create_nonce('votes-'.$userID);
 		}
 		if (Site::has('notifications')) {

--
Gitblit v1.10.0