summaryrefslogtreecommitdiffstats
path: root/includes/class-cepis-woo-token.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/class-cepis-woo-token.php')
-rw-r--r--includes/class-cepis-woo-token.php76
1 files changed, 76 insertions, 0 deletions
diff --git a/includes/class-cepis-woo-token.php b/includes/class-cepis-woo-token.php
new file mode 100644
index 0000000..1d1b9f4
--- /dev/null
+++ b/includes/class-cepis-woo-token.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * The token that guards the server-side proxy route.
+ *
+ * @package CepisWooCommerce
+ */
+
+declare(strict_types=1);
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * A short-lived nonce for the proxy route, derived the way WordPress derives
+ * its own but without binding to a login session.
+ *
+ * The bundled widget fetches with `credentials: "omit"` — deliberately, since
+ * a CEP lookup has no business carrying the shopper's cookies. The proxy
+ * therefore never sees an authentication cookie and every request reaches it
+ * anonymous, whether or not the shopper is logged in. A cookie-bound
+ * {@see wp_create_nonce()} would embed a session token in the page that the
+ * request could not present, so it would verify for guests and fail for
+ * everyone with an account: the worst possible failure mode, invisible in
+ * testing and broken in production.
+ *
+ * What the token has to defend against is the only threat this endpoint
+ * actually has — a third party pointing their own site at this store's proxy
+ * and using it as a free CEP gateway. A value tied to the site's salts and to
+ * {@see wp_nonce_tick()} does exactly that: it cannot be produced without the
+ * salts, and it stops working within a day.
+ *
+ * Same lifetime as a WordPress nonce: the current tick and the one before it
+ * are both accepted, so a checkout page held open overnight keeps working.
+ */
+final class Cepis_Woo_Token {
+
+ /**
+ * Namespaces the hash, so a token cannot be replayed against anything else
+ * on the site that hashes with the `nonce` salt.
+ */
+ private const ACTION = 'cepis_woo_proxy_lookup';
+
+ /**
+ * Mint the token to embed in the checkout page.
+ */
+ public static function create(): string {
+ return self::hash( wp_nonce_tick( self::ACTION ) );
+ }
+
+ /**
+ * Check a token presented by the browser.
+ *
+ * Compared with {@see hash_equals()} so that a wrong token takes the same
+ * time to reject as a right one.
+ *
+ * @param string $token Token as it arrived in the request.
+ */
+ public static function verify( string $token ): bool {
+ if ( '' === $token ) {
+ return false;
+ }
+
+ $tick = wp_nonce_tick( self::ACTION );
+
+ return hash_equals( self::hash( $tick ), $token )
+ || hash_equals( self::hash( $tick - 1 ), $token );
+ }
+
+ /**
+ * Derive the token for one tick.
+ *
+ * @param float $tick Nonce tick to derive from.
+ */
+ private static function hash( float $tick ): string {
+ return substr( wp_hash( $tick . '|' . self::ACTION, 'nonce' ), -12, 10 );
+ }
+}