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 | 269 ++++++++++++++++++-----------------------------------
1 files changed, 94 insertions(+), 175 deletions(-)
diff --git a/inc/rest/routes/LoginRoutes.php b/inc/rest/routes/LoginRoutes.php
index dc8d528..9ff7f51 100644
--- a/inc/rest/routes/LoginRoutes.php
+++ b/inc/rest/routes/LoginRoutes.php
@@ -1,9 +1,15 @@
<?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;
-use JVBase\utility\Features;
+use JVBase\base\Site;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
@@ -20,6 +26,7 @@
*/
class LoginRoutes extends Rest
{
+ use Instance;
protected ?string $requestId = null;
protected bool $hasMagicLink = false;
@@ -29,8 +36,8 @@
$this->cacheTtl = WEEK_IN_SECONDS;
parent::__construct();
+ $this->hasMagicLink = Site::has('magic_link');
- $this->hasMagicLink = Features::forSite()->has('magicLink');
}
public function registerRoutes(): void
@@ -93,7 +100,8 @@
->register();
// Magic link endpoint
- if ($this->hasMagicLink) {
+
+ if (Site::has('magic_link')) {
Route::for('auth/magic')
->post([$this, 'handleMagicLink'])
->args([
@@ -134,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(),
@@ -164,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);
@@ -190,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
*/
@@ -238,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(),
]);
@@ -261,7 +286,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);
}
@@ -272,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,
]);
@@ -323,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(),
]);
@@ -335,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 {
- $this->sendPasswordResetEmail($user, $key);
+ $success = Emails::getInstance()->sendPasswordResetEmail($user, $key);
}
}
@@ -391,8 +416,7 @@
if (is_wp_error($user)) {
return $this->error(
'Invalid or expired reset link.',
- 'invalid_key',
- 400
+ 'invalid_key'
);
}
@@ -414,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'
);
}
@@ -441,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(),
@@ -452,8 +476,7 @@
return $this->error(
$result->get_error_message(),
- $result->get_error_code(),
- 400
+ $result->get_error_code()
);
}
@@ -494,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
**************************************************************/
@@ -630,17 +572,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();
}
/**
@@ -648,18 +590,18 @@
*/
protected function processReferralCode(int $user_id, string $referral_code): void
{
- if (!Features::forSite()->has('referrals')) {
+ if (!Site::has('referrals')) {
return;
}
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');
+ ]);
}
}
@@ -696,75 +638,52 @@
*/
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),
+ };
}
- /**
- * 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'
- );
-
- $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
- );
-
- return wp_mail($user->user_email, $subject, $message);
+ 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 {
$nonces = [
'wp_rest' => wp_create_nonce('wp_rest'),
];
- if (Features::forSite()->has('dashboard')) {
+ if (Site::has('dashboard')) {
$nonces['dash'] = wp_create_nonce('dash-'.$userID);
}
- if (Features::forSite()->has('favourites')) {
+ if (Site::has('favourites')) {
$nonces['favourites'] = wp_create_nonce('favourites-'.$userID);
}
- if (Features::anyContentHas('karma') ||
- Features::anyTaxonomyHas('karma') ||
- Features::anyUserHas('karma')) {
+ if (!empty(Registrar::withFeature('karma'))) {
$nonces['votes'] = wp_create_nonce('votes-'.$userID);
}
- if (Features::forSite()->has('notifications')) {
+ if (Site::has('notifications')) {
$nonces['notifications'] = wp_create_nonce('notifications-'.$userID);
}
return $nonces;
--
Gitblit v1.10.0