PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / trunk
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment vtrunk
3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 122 releases
firebox / Inc / Core / API / ApiKeyAuth.php

ApiKeyAuth.php in FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment trunk, at Inc/Core/API/ApiKeyAuth.php

150 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package FireBox
4 * @version 3.1.13
5 *
6 * @author FirePlugins <info@fireplugins.com>
7 * @link https://www.fireplugins.com
8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 */
11
12 namespace FireBox\Core\API;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 use FireBox\Core\Helpers\RateLimit;
20
21 /**
22 * Shared authentication for the key-protected ("closed") REST routes.
23 *
24 * These routes return every stored submission, so the key is as sensitive as the data.
25 * It is accepted from a request header in preference to the URL path: a secret in a path
26 * segment is written to access logs, proxy logs and browser history.
27 */
28 class ApiKeyAuth
29 {
30 /**
31 * Header carrying the key.
32 *
33 * WP_REST_Request::get_header() normalises "X-FireBox-Api-Key" to this form.
34 *
35 * @var string
36 */
37 const HEADER = 'x_firebox_api_key';
38
39 /**
40 * Shortest key we will authenticate against.
41 *
42 * This defaults to 1 — that is, "not blank" — deliberately. Sites have existing keys
43 * of arbitrary length and raising the bar here would silently break their
44 * integrations on upgrade. Brute force is instead made impractical by the failure
45 * throttle below, and guessing is made harder by the constant-time comparison.
46 *
47 * Sites that want a hard floor can raise it via the firebox/api/minimum_key_length
48 * filter; 32 is a sensible value once existing keys have been rotated.
49 *
50 * @var int
51 */
52 const MINIMUM_KEY_LENGTH = 1;
53
54 /**
55 * Authenticates a request against the configured API key.
56 *
57 * @param \WP_REST_Request $request
58 *
59 * @return bool
60 */
61 public static function authenticate($request)
62 {
63 $stored = \FireBox\Core\Helpers\Settings::findSettingsOption('api_key');
64 $stored = is_scalar($stored) ? trim((string) $stored) : '';
65
66 if (strlen($stored) < self::getMinimumKeyLength())
67 {
68 return false;
69 }
70
71 $provided = self::extractKey($request);
72
73 if ($provided === '')
74 {
75 return false;
76 }
77
78 /**
79 * Throttle failures so the key cannot be brute-forced. Successful requests are
80 * not counted, so a legitimate integration polling the API is unaffected.
81 *
82 * This throttle is what makes MINIMUM_KEY_LENGTH above safe to leave at 1, so it
83 * fails closed: where a caller cannot be identified every caller shares one
84 * bucket, rather than the limit lifting and the key becoming guessable at will.
85 */
86 if (RateLimit::isLimited('api_key_auth', 10, false))
87 {
88 return false;
89 }
90
91 // Constant-time comparison: a plain === leaks how much of the key matched.
92 if (!hash_equals($stored, $provided))
93 {
94 RateLimit::hit('api_key_auth', 5 * MINUTE_IN_SECONDS, false);
95 return false;
96 }
97
98 return true;
99 }
100
101 /**
102 * Reads the key from the request, preferring the header over the URL path.
103 *
104 * @param \WP_REST_Request $request
105 *
106 * @return string
107 */
108 private static function extractKey($request)
109 {
110 if (!is_object($request))
111 {
112 return '';
113 }
114
115 $header = method_exists($request, 'get_header') ? $request->get_header(self::HEADER) : '';
116 $header = is_scalar($header) ? trim((string) $header) : '';
117
118 if ($header !== '')
119 {
120 return $header;
121 }
122
123 /**
124 * Deprecated: the key as a URL path segment. Retained so existing integrations
125 * keep working; prefer the header.
126 */
127 $param = method_exists($request, 'get_param') ? $request->get_param('api_key') : '';
128
129 return is_scalar($param) ? trim((string) $param) : '';
130 }
131
132 /**
133 * Returns the minimum acceptable key length.
134 *
135 * @return int
136 */
137 private static function getMinimumKeyLength()
138 {
139 /**
140 * Allows a site with a shorter legacy key to keep using it while it rotates.
141 *
142 * @param int $length
143 */
144 $length = (int) apply_filters('firebox/api/minimum_key_length', self::MINIMUM_KEY_LENGTH);
145
146 // Never allow this to be reduced to nothing.
147 return max(1, $length);
148 }
149 }
150