magic_link = new MagicLinkManager(); parent::__construct(); } /** * Register REST routes */ public function registerRoutes(): void { // Send magic link register_rest_route($this->namespace, '/magic', [ 'methods' => 'POST', 'callback' => [$this, 'sendMagicLink'], 'permission_callback' => [$this, 'checkRateLimit'], // 'args' => [ // 'email' => [ // 'required' => true, // 'type' => 'string', // 'format' => 'email', // 'validate_callback' => function($param) { // return is_email($param); // } // ], // 'type' => [ // 'required' => false, // 'type' => 'string', // 'default' => 'login', // 'enum' => ['login', 'signup', 'referral', 'reset'] // ], // 'context' => [ // 'required' => false, // 'type' => 'object', // 'default' => [] // ] // ] ]); // Resend magic link register_rest_route($this->namespace, '/magic/resend', [ 'methods' => 'POST', 'callback' => [$this, 'resendMagicLink'], 'permission_callback' => [$this, 'checkRateLimit'], 'args' => [ 'email' => [ 'required' => true, 'type' => 'string', 'format' => 'email' ], 'type' => [ 'required' => true, 'type' => 'string' ] ] ]); // Check token validity (useful for frontend) register_rest_route($this->namespace, '/magic/verify', [ 'methods' => 'POST', 'callback' => [$this, 'verifyToken'], 'permission_callback' => '__return_true', 'args' => [ 'token' => [ 'required' => true, 'type' => 'string' ], 'email' => [ 'required' => true, 'type' => 'string', 'format' => 'email' ] ] ]); } /** * Send a magic link via email * * @param WP_REST_Request $request * @return WP_REST_Response */ public function sendMagicLink(WP_REST_Request $request): WP_REST_Response { $data = $request->get_json_params(); // Verify Turnstile if (!$this->verifyTurnstile($data['cf-turnstile-response'] ?? '')) { return $this->error('Security verification failed', 'turnstile_failed', 403); } $email = sanitize_email($request->get_param('email')??$request->get_param('user_email')??''); $type = sanitize_text_field($request->get_param('type')) ?? MagicLinkManager::TYPE_LOGIN; $context = $request->get_param('context') ?? []; error_log('SendMagicLink request: '.print_r($email, true)); error_log('Type: '.print_r($type, true)); error_log('Context: '.print_r($context, true)); // Validate email if (!is_email($email)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Invalid email address' ], 400); } // Check if email exists $exists = email_exists($email); if ($type === MagicLinkManager::TYPE_LOGIN && !$exists) { return new WP_REST_Response([ 'success' => true, 'message' => 'Invalid email address' ]); } if ($type === MagicLinkManager::TYPE_SIGNUP && $exists) { // Redirect to login instead $type = MagicLinkManager::TYPE_LOGIN; } // Send the magic link $result = $this->magic_link->sendMagicLink($email, $type, $context); error_log('Result: '.print_r($result, true)); if (is_wp_error($result)) { return new WP_REST_Response([ 'success' => false, 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ], 400); } // Return success (never reveal if user exists or not) return new WP_REST_Response([ 'success' => true, 'message' => 'If an account exists with this email, we\'ve sent a login link.' ], 200); } /** * Resend a magic link * * @param WP_REST_Request $request * @return WP_REST_Response */ public function resendMagicLink(WP_REST_Request $request): WP_REST_Response { // Same as sending, but could add additional logging return $this->sendMagicLink($request); } /** * Verify a token without consuming it * Useful for frontend validation before redirect * * @param WP_REST_Request $request * @return WP_REST_Response */ public function verifyToken(WP_REST_Request $request): WP_REST_Response { $token = sanitize_text_field($request->get_param('token')); $email = sanitize_email($request->get_param('email')); // This returns array|WP_Error - check for error first $token_data = $this->magic_link->verifyToken($token, $email); if (is_wp_error($token_data)) { return new WP_REST_Response([ 'valid' => false, 'message' => $token_data->get_error_message() ], 400); } // Now check the data if (!isset($token_data['email']) || $token_data['email'] !== $email) { return new WP_REST_Response([ 'valid' => false, 'message' => 'Invalid token' ], 400); } // Check expiration - but your cache-based system doesn't store expires_at // If token wasn't expired, it wouldn't have been returned from cache // So just return valid: return new WP_REST_Response([ 'valid' => true, 'type' => $token_data['type'] ?? 'unknown' ], 200); } protected function processReferralSignup(array $token_data): void { // Create user account $user_id = wp_create_user( $token_data['email'], wp_generate_password(20, true, true), $token_data['email'] ); if (is_wp_error($user_id)) { wp_die('Failed to create account: ' . $user_id->get_error_message()); } // Update user info if (!empty($token_data['name'])) { wp_update_user([ 'ID' => $user_id, 'display_name' => $token_data['name'], 'first_name' => $token_data['name'] ]); } // Store referral code in user meta (temporary) // ReferralManager::processReferral will pick this up update_user_meta($user_id, BASE . 'pending_referral_code', $token_data['referral_code']); // Trigger registration actions (this calls processReferral) do_action('user_register', $user_id); // Log the user in wp_set_current_user($user_id); wp_set_auth_cookie($user_id, true); do_action('wp_login', get_user_by('ID', $user_id)->user_login, get_user_by('ID', $user_id)); // Redirect with referral welcome message wp_safe_redirect(home_url('/dash?referral_welcome=1')); exit; } }