| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | 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; |
| | |
| | | |
| | | parent::__construct(); |
| | | |
| | | $this->hasMagicLink = Features::forSite()->has('magicLink'); |
| | | $this->hasMagicLink = Site::has('magicLink'); |
| | | } |
| | | |
| | | public function registerRoutes(): void |
| | |
| | | Route::for('auth/status') |
| | | ->get([$this, 'getAuthStatus']) |
| | | ->auth('public') |
| | | ->rateLimit(60, 60); |
| | | ->rateLimit() |
| | | ->register(); |
| | | |
| | | // Standard login |
| | | Route::for('auth/login') |
| | |
| | | 'redirect_to' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(5, 300); |
| | | ->rateLimit(5, 300) |
| | | ->register(); |
| | | |
| | | // User registration |
| | | Route::for('auth/register') |
| | |
| | | 'redirect_to' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(3, 3600); |
| | | ->rateLimit(3, 3600) |
| | | ->register(); |
| | | |
| | | // Request password reset |
| | | Route::for('auth/lostpassword') |
| | |
| | | 'user_email' => 'email|required', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(3, 3600); |
| | | ->rateLimit(3, 3600) |
| | | ->register(); |
| | | |
| | | // Reset password with token |
| | | Route::for('auth/resetpass') |
| | |
| | | 'pass2' => 'string|required', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(5, 300); |
| | | ->rateLimit(5, 300) |
| | | ->register(); |
| | | |
| | | // Magic link endpoint |
| | | if ($this->hasMagicLink) { |
| | |
| | | '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(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | 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) |
| | | ]); |
| | | } |
| | | |
| | |
| | | ]); |
| | | |
| | | // 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); |
| | | } |
| | | |
| | |
| | | 'error' => $key->get_error_message(), |
| | | ]); |
| | | } else { |
| | | $this->sendPasswordResetEmail($user, $key); |
| | | $success = JVB()->email()->sendPasswordResetEmail($user, $key); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // 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(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | protected function processReferralCode(int $user_id, string $referral_code): void |
| | | { |
| | | if (!Features::forSite()->has('referrals')) { |
| | | if (!Site::has('referrals')) { |
| | | return; |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Send password reset email (fallback if magic links not available) |
| | | */ |
| | | protected function sendPasswordResetEmail(WP_User $user, string $key): bool |
| | | protected function buildAuth(?int $user = null): array |
| | | { |
| | | $reset_url = network_site_url( |
| | | "wp-login.php?action=rp&key=$key&login=" . rawurlencode($user->user_login), |
| | | 'login' |
| | | ); |
| | | if (is_user_logged_in()) { |
| | | $user = ($user) ?: get_current_user_id(); |
| | | return [ |
| | | 'authenticated' => true, |
| | | 'user' => $user, |
| | | 'nonces' => $this->getUserNonces($user) |
| | | ]; |
| | | } |
| | | |
| | | $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 [ |
| | | '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::getFeatured('karma'))) { |
| | | $nonces['votes'] = wp_create_nonce('votes-'.$userID); |
| | | } |
| | | if (Site::has('notifications')) { |
| | | $nonces['notifications'] = wp_create_nonce('notifications-'.$userID); |
| | | } |
| | | return $nonces; |
| | | } |
| | | } |