Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User-related REST API calls to /wp/v2/users/me?context=edit in Preferences Persistence even if user is not logged in #60325

Closed
therealgilles opened this issue Mar 31, 2024 · 6 comments
Labels
REST API Interaction Related to REST API [Status] Needs More Info Follow-up required in order to be actionable. [Type] Bug An existing feature does not function as intended

Comments

@therealgilles
Copy link

therealgilles commented Mar 31, 2024

Description

The code below makes REST API calls to /wp/v2/users/me?context=edit even when the user is not logged in.


On my site, this results in a 401 (unauthorized) response, maybe because of my REST API security settings. I deem this call unnecessary as the code should have a way to skip them if the user is not logged in.

Should this script even be loaded if a user is not logged in?

Step-by-step reproduction instructions

Load any WP page. Wait for the REST API to be triggered.

Screenshots, screen recording, code snippet

No response

Environment info

WordPress 6.4.3
My theme is not using Gutenberg blocks.

Please confirm that you have searched existing issues in the repo.

Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

No

@therealgilles therealgilles added the [Type] Bug An existing feature does not function as intended label Mar 31, 2024
@jordesign jordesign added the REST API Interaction Related to REST API label Apr 2, 2024
@talldan
Copy link
Contributor

talldan commented Apr 2, 2024

@therealgilles Thanks for reporting this. Please could you provide some more detailed reproduction steps?

I tried to reproduce the error you're seeing but couldn't. The preferences API is fairly limited at the moment to the block editor (AFAIK) and isn't used on any WP page.

The only way I found to reproduce it was to log out in a separate tab, and try changing preferences in the block editor, which resulted in a 403 (Forbidden) error, but the same happens when trying other actions like saving/publishing a post. The block editor will (eventually) show a login overlay once it detects the user is logged out.

It could be that you have a plugin active that uses the preferences API, and is making requests on all WP Pages, so you could try testing with plugins disabled (on a staging site?).

@talldan talldan added the [Status] Needs More Info Follow-up required in order to be actionable. label Apr 2, 2024
@therealgilles
Copy link
Author

therealgilles commented Apr 3, 2024

UPDATE: Let me double-check on my side...

Hi @talldan, thank you for the reply.

Here is the code where the script is loaded:
https://build.trac.wordpress.org/browser/trunk/wp-includes/script-loader.php#L308

I don't see any condition based on whether we're on a page or on the /wp-admin side.

Then the script is executed here:
https://build.trac.wordpress.org/browser/trunk/wp-includes/script-loader.php#L390

Again, there is no check as to whether it is the /wp-admin side or not.

Am I missing something?

PS: If I am correct, I see the user ID is passed to the script, so it should be a pretty easy fix to prevent the API calls.

@talldan
Copy link
Contributor

talldan commented Apr 3, 2024

Am I missing something?

In the code you linked to, that script is specifically loaded only after the wp-preferences dependency using add_inline_script (https://developer.wordpress.org/reference/functions/wp_add_inline_script/), I could be wrong, but I don't think wp-preferences is loaded outside of the admin, and even then only loaded for the editors.

Still, the script can be loaded, but it shouldn't (and in my testing I didn't see it) trigger an HTTP request like you're describing. setPersistenceLayer is performing some very cheap configuration, it assigns the persistence layer in the preferences package (a variable assignment). Here's the branch of code that it calls through to:

if ( action.type === 'SET_PERSISTENCE_LAYER' ) {
const { persistenceLayer: persistence, persistedData } = action;
persistenceLayer = persistence;
return persistedData;
}

The code that makes the HTTP request you describe is instead wp.preferences.set() function (usually called when a user changes a preference, the value is then persisted to the server), the code branch for that is here:

const nextState = reducer( state, action );
if ( action.type === 'SET_PREFERENCE_VALUE' ) {
persistenceLayer?.set( nextState );
}

It calls through to here where the api fetch call is made (debounced to avoid to many http requests):

function set( newData ) {
const dataWithTimestamp = {
...newData,
_modified: new Date().toISOString(),
};
cache = dataWithTimestamp;
// Store data in local storage as a fallback. If for some reason the
// api request does not complete or becomes unavailable, this data
// can be used to restore preferences.
localStorage.setItem(
localStorageRestoreKey,
JSON.stringify( dataWithTimestamp )
);
// The user meta endpoint seems susceptible to errors when consecutive
// requests are made in quick succession. Ensure there's a gap between
// any consecutive requests.
//
// Catch and do nothing with errors from the REST API.
debouncedApiFetch( {
path: '/wp/v2/users/me',
method: 'PUT',
// `keepalive` will still send the request in the background,
// even when a browser unload event might interrupt it.
// This should hopefully make things more resilient.
// This does have a size limit of 64kb, but the data is usually
// much less.
keepalive: true,
data: {
meta: {
persisted_preferences: dataWithTimestamp,
},
},
} ).catch( () => {} );
}

Hopefully that makes it clearer how it's supposed to work. I'm not sure what's happening in your environment, but it could be that a plugin is loading wp-preferences when it shouldn't and making some changes to preferences.

Let me know if you spot any issues with what I mention above.

@therealgilles
Copy link
Author

You must be right. Let me try to find the faulty plugin.

@therealgilles
Copy link
Author

Here is a stack trace. It looks like the call to wp_script_is() or wp_scripts() lead to wp_default_packages() being called and the scripts being loaded.

=> require(["\/...\/public_html\/wp-blog-header.php"]) / /.../public_html/index.php, line 17
=> require_once(["\/...\/public_html\/wp-load.php"]) / /.../public_html/wp-blog-header.php, line 13
=> require_once(["\/...\/public_html\/wp-config.php"]) / /.../public_html/wp-load.php, line 50
=> require_once(["\/...\/public_html\/wp-settings.php"]) / /.../public_html/wp-config.php, line 142
=> do_action(["init"]) / /.../public_html/wp-settings.php, line 695
=> do_action([[""]]) / /.../public_html/wp-includes/plugin.php, line 517
=> apply_filters([null,[""]]) / /.../public_html/wp-includes/class-wp-hook.php, line 348
=> register_in_wp / /.../public_html/wp-includes/class-wp-hook.php, line 322
=> wp_script_is(["tribe-common","registered"]) / /.../public_html/wp-content/plugins/the-events-calendar/common/src/Tribe/Assets.php, line 264
=> wp_scripts / /.../public_html/wp-includes/functions.wp-scripts.php, line 425
=> __construct / /.../public_html/wp-includes/functions.wp-scripts.php, line 24
=> init / /.../public_html/wp-includes/class-wp-scripts.php, line 149
=> do_action_ref_array(["wp_default_scripts",[{"registered":{"utils":{"handle":"utils","src":"\/wp-includes\/js\/utils.min.js","deps":[],"ver":false,"args":null,"extra":{"data":"var userSettings = {\"url\":\"\\\/\",\"uid\":...) / /.../public_html/wp-includes/class-wp-scripts.php, line 166
=> do_action([[{"registered":{"utils":{"handle":"utils","src":"\/wp-includes\/js\/utils.min.js","deps":[],"ver":false,"args":null,"extra":{"data":"var userSettings = {\"url\":\"\\\/\",\"uid\":\"0\",\"time\":\"1712...) / /.../public_html/wp-includes/plugin.php, line 565
=> apply_filters([null,[{"registered":{"utils":{"handle":"utils","src":"\/wp-includes\/js\/utils.min.js","deps":[],"ver":false,"args":null,"extra":{"data":"var userSettings = {\"url\":\"\\\/\",\"uid\":\"0\",\"time\":\...) / /.../public_html/wp-includes/class-wp-hook.php, line 348
wp_default_packages([{"registered":{"utils":{"handle":"utils","src":"\/wp-includes\/js\/utils.min.js","deps":[],"ver":false,"args":null,"extra":{"data":"var userSettings = {\"url\":\"\\\/\",\"uid\":\"0\",\"time\":\"17121...) / /.../public_html/wp-includes/class-wp-hook.php, line 324

@therealgilles
Copy link
Author

I tracked it down to the Updraft Central plugin. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
REST API Interaction Related to REST API [Status] Needs More Info Follow-up required in order to be actionable. [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

No branches or pull requests

3 participants