Improve nonce handling by rejecting stale values (https://github.com/woocommerce/woocommerce-blocks/pull/3770)

* Improve nonce handling by rejecting previous nonces from cache

* use timestamp instead of previous nonce

* Switch back to time()

* Seconds not ms

* Add comment about the date code
This commit is contained in:
Mike Jolley 2021-02-01 17:09:18 +00:00 committed by GitHub
parent 799e6f26fe
commit d9e2f62540
4 changed files with 58 additions and 8 deletions

View File

@ -5,6 +5,7 @@ module.exports = {
],
globals: {
wcStoreApiNonce: 'readonly',
wcStoreApiNonceTimestamp: 'readonly',
fetchMock: true,
jQuery: 'readonly',
IntersectionObserver: 'readonly',

View File

@ -3,9 +3,18 @@
*/
import apiFetch from '@wordpress/api-fetch';
// @ts-ignore wcStoreApiNonce is window global
// Cache for the initial nonce initialized from hydration.
let nonce = wcStoreApiNonce || '';
// Stores the current nonce for the middleware.
let currentNonce = '';
let currentTimestamp = 0;
try {
const storedNonceValue = window.localStorage.getItem( 'storeApiNonce' );
const storedNonce = storedNonceValue ? JSON.parse( storedNonceValue ) : {};
currentNonce = storedNonce?.nonce || '';
currentTimestamp = storedNonce?.timestamp || 0;
} catch {
// We can ignore an error from JSON parse.
}
/**
* Returns whether or not this is a non GET wc/store API request.
@ -28,12 +37,44 @@ const isStoreApiGetRequest = ( options ) => {
* @param {Object} headers Headers object.
*/
const setNonce = ( headers ) => {
const newNonce = headers?.get( 'X-WC-Store-API-Nonce' );
if ( newNonce ) {
nonce = newNonce;
const nonce = headers?.get( 'X-WC-Store-API-Nonce' ) || '';
const timestamp = headers?.get( 'X-WC-Store-API-Nonce-Timestamp' ) || 0;
if ( nonce ) {
updateNonce( nonce, timestamp );
}
};
/**
* Updates the stored nonce within localStorage so it is persisted between page loads.
*
* @param {string} nonce Incoming nonce string.
* @param {number} timestamp Timestamp from server of nonce.
*/
const updateNonce = ( nonce, timestamp ) => {
// If the "new" nonce matches the current nonce, we don't need to update.
if ( nonce === currentNonce ) {
return;
}
// Only update the nonce if newer. It might be coming from cache.
if ( currentTimestamp && timestamp < currentTimestamp ) {
return;
}
currentNonce = nonce;
currentTimestamp = timestamp || Date.now() / 1000; // Convert ms to seconds to match php time()
// Update the persisted values.
window.localStorage.setItem(
'storeApiNonce',
JSON.stringify( {
nonce: currentNonce,
timestamp: currentTimestamp,
} )
);
};
/**
* Nonce middleware which updates the nonce after a request, if given.
*
@ -47,7 +88,7 @@ const storeNonceMiddleware = ( options, next ) => {
const existingHeaders = options.headers || {};
options.headers = {
...existingHeaders,
'X-WC-Store-API-Nonce': nonce,
'X-WC-Store-API-Nonce': currentNonce,
};
}
return next( options, next );
@ -55,3 +96,7 @@ const storeNonceMiddleware = ( options, next ) => {
apiFetch.use( storeNonceMiddleware );
apiFetch.setNonce = setNonce;
// @ts-ignore wcStoreApiNonce is window global cache for the initial nonce initialized from hydration.
// @ts-ignore wcStoreApiNonceTimestamp is window global cache for the initial nonce initialized from hydration.
updateNonce( wcStoreApiNonce, wcStoreApiNonceTimestamp );

View File

@ -63,7 +63,10 @@ class Assets {
// Inline data.
wp_add_inline_script(
'wc-blocks-middleware',
"var wcStoreApiNonce = '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "';",
"
var wcStoreApiNonce = '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "';
var wcStoreApiNonceTimestamp = '" . esc_js( time() ) . "';
",
'before'
);

View File

@ -81,6 +81,7 @@ abstract class AbstractRoute implements RouteInterface {
}
$response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
$response->header( 'X-WC-Store-API-Nonce-Timestamp', time() );
$response->header( 'X-WC-Store-API-User', get_current_user_id() );
return $response;
}