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') ?? []; // 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 = JVB()->magicLink()?->sendMagicLink($email, $type, $context); 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 = JVB()->magicLink()?->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); } }