@anonympins/fingerprint 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/build-client.js +8 -10
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +1531 -1463
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +190 -16
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +340 -376
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +154 -56
- package/src/js/pow.solver.js +180 -92
- package/src/js/pow.worker.js +48 -48
- package/src/js/redis-store.js +1 -1
- package/src/js/tests/fingerprint.client.init.test.js +146 -143
- package/src/js/tests/fingerprint.test.js +124 -10
- package/src/js/tests/ip-reputation.test.js +1 -0
- package/src/js/upow-model-task.js +14 -15
- package/src/php/AutoTuner.php +34 -34
- package/src/php/Challenge/ChallengeUtils.php +57 -11
- package/src/php/Config/SecurityProfiles.php +20 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +147 -148
- package/src/php/FingerprintEngine.php +44 -17
- package/src/php/Ja3AnomalyDetector.php +33 -32
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +26 -25
- package/src/php/Optimization/ProblemInitializers.php +52 -52
- package/src/php/ProblemManager.php +31 -34
- package/src/php/RequestContext.php +8 -1
- package/src/php/Store/IStore.php +7 -8
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +5 -5
- package/src/php/Store/RedisStore.php +5 -5
- package/src/php/Store/StoreManager.php +35 -35
- package/src/php/Tests/ChallengeUtilsTest.php +453 -453
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintEngineTest.php +38 -0
- package/src/php/Tests/IpReputationTest.php +10 -0
- package/src/php/Tests/MaliciousPatternsTest.php +103 -103
- package/src/php/Tests/MetricsTest.php +97 -97
- package/src/php/Tests/ProblemManagerTest.php +376 -376
- package/src/php/Tests/QuicFingerprintTest.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +471 -471
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
- package/src/php/Tests/config/ed25519_key.json +3 -3
- package/src/php/Utils/BlockList.php +99 -99
- package/src/php/Utils/Env.php +49 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +129 -7
- package/src/php/Utils/TLSClientHelloParser.php +369 -369
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +554 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
- package/src/php/WordPress/package.php +218 -0
- package/src/php/bin/auto-tune.php +118 -118
|
@@ -19,7 +19,7 @@ class ProblemManager
|
|
|
19
19
|
private bool $initialized = false;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Private constructor enforces singleton usage.
|
|
23
23
|
*/
|
|
24
24
|
private function __construct(string $configPath, IStore $store)
|
|
25
25
|
{
|
|
@@ -29,8 +29,8 @@ class ProblemManager
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Retrieves the ProblemManager singleton instance.
|
|
33
|
+
* Must be initialized with configPath and store.
|
|
34
34
|
*/
|
|
35
35
|
public static function getInstance(?string $configPath = null, ?IStore $store = null): self
|
|
36
36
|
{
|
|
@@ -39,7 +39,7 @@ class ProblemManager
|
|
|
39
39
|
$defaultPath = dirname(__DIR__, 2) . '/config/problems.config.json';
|
|
40
40
|
$configPath = file_exists($defaultPath) ? $defaultPath : null;
|
|
41
41
|
}
|
|
42
|
-
//
|
|
42
|
+
// Error if accessed before initialization
|
|
43
43
|
if ($configPath === null || $store === null) {
|
|
44
44
|
throw new \RuntimeException("ProblemManager must be initialized with configPath and store.");
|
|
45
45
|
}
|
|
@@ -53,7 +53,7 @@ class ProblemManager
|
|
|
53
53
|
return self::$instance !== null && self::$instance->initialized;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Loads and parses problem definitions from configuration file.
|
|
57
57
|
*/
|
|
58
58
|
|
|
59
59
|
private function loadProblems(): void
|
|
@@ -83,7 +83,7 @@ class ProblemManager
|
|
|
83
83
|
}
|
|
84
84
|
$problem['state'] = $storedState;
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// Dynamic function and data resolution
|
|
87
87
|
if (isset($problem['workUnit']['scoreFunction'])) {
|
|
88
88
|
$problem['workUnit']['scoreFunction'] = FunctionRegistry::get($problem['workUnit']['scoreFunction']);
|
|
89
89
|
if ($problem['workUnit']['scoreFunction'] === null) {
|
|
@@ -105,7 +105,7 @@ class ProblemManager
|
|
|
105
105
|
}
|
|
106
106
|
$this->problems[] = $problem;
|
|
107
107
|
}
|
|
108
|
-
$this->initialized = true; //
|
|
108
|
+
$this->initialized = true; // Mark initialized only after successful load
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
public function dispatchWork(float $suspicionFactor): ?array
|
|
@@ -175,10 +175,10 @@ class ProblemManager
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
|
-
*
|
|
178
|
+
* Integrates client solution into problem state.
|
|
179
179
|
*
|
|
180
|
-
* @param string $problemId
|
|
181
|
-
* @param array $solutionData
|
|
180
|
+
* @param string $problemId Problem identifier.
|
|
181
|
+
* @param array $solutionData Client-submitted solution payload.
|
|
182
182
|
*/
|
|
183
183
|
public function integrateSolution(string $problemId, array $solutionData): void
|
|
184
184
|
{
|
|
@@ -192,13 +192,13 @@ class ProblemManager
|
|
|
192
192
|
|
|
193
193
|
$storeKey = "problem-state:{$problem['id']}";
|
|
194
194
|
|
|
195
|
-
//
|
|
195
|
+
// Integration logic depends on problem type
|
|
196
196
|
switch ($problem['workUnit']['type']) {
|
|
197
197
|
case 'simulated_annealing_iterations':
|
|
198
198
|
if (isset($solutionData['solution']) && isset($solutionData['energy'])) {
|
|
199
199
|
$scoreFunction = $problem['workUnit']['scoreFunction'] ?? null;
|
|
200
200
|
if (!$scoreFunction) {
|
|
201
|
-
error_log("[ProblemManager]
|
|
201
|
+
error_log("[ProblemManager] No score function defined for {$problemId}.");
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
204
204
|
// DoS mitigation: validate solution size and structure
|
|
@@ -211,15 +211,15 @@ class ProblemManager
|
|
|
211
211
|
error_log("[ProblemManager] Solution payload size exceeds safe limit for {$problemId}.");
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
|
-
// 1.
|
|
214
|
+
// 1. Never trust client-reported score. Recalculate server-side.
|
|
215
215
|
$recalculatedEnergy = $scoreFunction($solutionData['solution'], $problem['payload'] ?? []);
|
|
216
216
|
|
|
217
217
|
$currentBest = (float)($problem['state']['bestEnergy'] ?? INF);
|
|
218
218
|
|
|
219
|
-
// 2.
|
|
219
|
+
// 2. Compare verified score
|
|
220
220
|
if ($recalculatedEnergy < $currentBest) { // @phpstan-ignore-line
|
|
221
221
|
$problem['state']['bestSolution'] = $solutionData['solution'];
|
|
222
|
-
$problem['state']['bestEnergy'] = $recalculatedEnergy; // 3.
|
|
222
|
+
$problem['state']['bestEnergy'] = $recalculatedEnergy; // 3. Store verified score
|
|
223
223
|
$problem['state']['lastUpdate'] = (new \DateTime())->format(\DateTime::ATOM);
|
|
224
224
|
$stateChanged = true;
|
|
225
225
|
error_log("[ProblemManager] New best solution for {$problemId}: {$recalculatedEnergy}"); // @phpstan-ignore-line
|
|
@@ -239,7 +239,7 @@ class ProblemManager
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
//
|
|
242
|
+
// Sample-based verification (parity with JS)
|
|
243
243
|
$fitnessFunction = FunctionRegistry::get('portfolio.calculateMetrics');
|
|
244
244
|
if ($fitnessFunction) {
|
|
245
245
|
$sampleSize = min(5, count($solutionData['population']));
|
|
@@ -251,8 +251,8 @@ class ProblemManager
|
|
|
251
251
|
$recalculated = $fitnessFunction($individual['chromosome'], $problem['payload'] ?? []);
|
|
252
252
|
if (isset($individual['fitness']) && $individual['fitness'] !== -1) {
|
|
253
253
|
if (abs($individual['fitness'] - $recalculated) > 1e-4) {
|
|
254
|
-
error_log("[ProblemManager]
|
|
255
|
-
return; //
|
|
254
|
+
error_log("[ProblemManager] Cheating detected for {$problemId}! Reported: {$individual['fitness']}, recalculated: {$recalculated}");
|
|
255
|
+
return; // Reject inconsistent population
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
$solutionData['population'][$key]['fitness'] = $recalculated;
|
|
@@ -266,7 +266,7 @@ class ProblemManager
|
|
|
266
266
|
}
|
|
267
267
|
break;
|
|
268
268
|
case 'multi_objective_genetic_algorithm':
|
|
269
|
-
//
|
|
269
|
+
// Merge candidate Pareto front for multi-objective problems
|
|
270
270
|
if (isset($solutionData['paretoFront']) && is_array($solutionData['paretoFront'])) {
|
|
271
271
|
$stateChanged = $this->_integrateParetoFront($problem, $solutionData['paretoFront']);
|
|
272
272
|
}
|
|
@@ -275,24 +275,21 @@ class ProblemManager
|
|
|
275
275
|
error_log("[ProblemManager] Integration not implemented for useful work type: {$problem['workUnit']['type']}");
|
|
276
276
|
break;
|
|
277
277
|
}
|
|
278
|
-
//
|
|
278
|
+
// Persist updated state to datastore
|
|
279
279
|
if ($stateChanged) {
|
|
280
280
|
$this->store->set($storeKey, $problem['state']);
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
|
-
*
|
|
285
|
+
* Integrates a new Pareto front into the problem state.
|
|
286
286
|
*
|
|
287
|
-
* @param array &$problem
|
|
288
|
-
* @param array $newFront
|
|
287
|
+
* @param array &$problem Target problem passed by reference.
|
|
288
|
+
* @param array $newFront Candidate Pareto front submitted by client.
|
|
289
289
|
*/
|
|
290
290
|
private function _integrateParetoFront(array &$problem, array $newFront): bool
|
|
291
291
|
{
|
|
292
|
-
//
|
|
293
|
-
// Pour cet exemple, nous remplaçons simplement le front, mais une vraie implémentation
|
|
294
|
-
// fusionnerait les deux fronts et recalculerait le meilleur.
|
|
295
|
-
// On vérifie si le nouveau front est différent de l'actuel pour éviter des écritures inutiles.
|
|
292
|
+
// Verify content differences before updating to prevent redundant writes
|
|
296
293
|
$currentFront = $problem['state']['paretoFront'] ?? [];
|
|
297
294
|
if (!empty($newFront) && json_encode($newFront) !== json_encode($currentFront)) {
|
|
298
295
|
$problem['state']['paretoFront'] = $newFront;
|
|
@@ -304,9 +301,9 @@ class ProblemManager
|
|
|
304
301
|
}
|
|
305
302
|
|
|
306
303
|
/**
|
|
307
|
-
*
|
|
304
|
+
* Ensures that the problem has an initial solution. Generates one if missing.
|
|
308
305
|
*
|
|
309
|
-
* @param array &$problem
|
|
306
|
+
* @param array &$problem Problem passed by reference.
|
|
310
307
|
*/
|
|
311
308
|
private function ensureInitialSolution(array &$problem): void
|
|
312
309
|
{
|
|
@@ -328,7 +325,7 @@ class ProblemManager
|
|
|
328
325
|
}
|
|
329
326
|
|
|
330
327
|
/**
|
|
331
|
-
*
|
|
328
|
+
* Formats problem state for external consumers.
|
|
332
329
|
*/
|
|
333
330
|
private function formatSolution(array $problem): ?array
|
|
334
331
|
{
|
|
@@ -354,10 +351,10 @@ class ProblemManager
|
|
|
354
351
|
}
|
|
355
352
|
|
|
356
353
|
/**
|
|
357
|
-
*
|
|
354
|
+
* Retrieves the best known solution(s) for loaded problems.
|
|
358
355
|
*
|
|
359
|
-
* @param string|null $problemId
|
|
360
|
-
* @return array|null
|
|
356
|
+
* @param string|null $problemId Optional problem ID filter.
|
|
357
|
+
* @return array|null Associative array or list of solutions.
|
|
361
358
|
*/
|
|
362
359
|
public function getBestSolutions(?string $problemId = null): ?array
|
|
363
360
|
{
|
|
@@ -387,7 +384,7 @@ class ProblemManager
|
|
|
387
384
|
}
|
|
388
385
|
|
|
389
386
|
/**
|
|
390
|
-
*
|
|
387
|
+
* Resets singleton instance.
|
|
391
388
|
* @internal Uniquement pour les tests.
|
|
392
389
|
*/
|
|
393
390
|
public static function resetInstanceForTests(): void
|
|
@@ -25,6 +25,7 @@ class RequestContext
|
|
|
25
25
|
public ?string $httpVersion = null;
|
|
26
26
|
public int $requestTimestamp = 0;
|
|
27
27
|
public ?string $tlsSessionId = null;
|
|
28
|
+
public bool $isHttps = false;
|
|
28
29
|
|
|
29
30
|
/** @var ?array{type: string, name: string} */
|
|
30
31
|
public ?array $graphqlOperation = null;
|
|
@@ -67,7 +68,8 @@ class RequestContext
|
|
|
67
68
|
$body,
|
|
68
69
|
array $cookies,
|
|
69
70
|
?string $httpVersion,
|
|
70
|
-
?int $requestTimestamp = null
|
|
71
|
+
?int $requestTimestamp = null,
|
|
72
|
+
?bool $isHttps = null
|
|
71
73
|
) {
|
|
72
74
|
$this->clientIp = $clientIp;
|
|
73
75
|
$this->path = $path;
|
|
@@ -78,6 +80,11 @@ class RequestContext
|
|
|
78
80
|
$this->cookies = $cookies;
|
|
79
81
|
$this->httpVersion = $httpVersion;
|
|
80
82
|
$this->requestTimestamp = $requestTimestamp ?? (int)(microtime(true) * 1000);
|
|
83
|
+
$this->isHttps = $isHttps ?? (
|
|
84
|
+
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
|
|
85
|
+
(isset($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443) ||
|
|
86
|
+
($this->getHeader('x-forwarded-proto') === 'https')
|
|
87
|
+
);
|
|
81
88
|
|
|
82
89
|
// Extraire les empreintes TLS/HTTP2/TCP si elles sont fournies par les en-têtes
|
|
83
90
|
$this->ja3 = $this->headers['x-ja3-hash'] ?? null;
|
package/src/php/Store/IStore.php
CHANGED
|
@@ -5,43 +5,42 @@ declare(strict_types=1);
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Store;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Interface
|
|
9
|
-
* Utilisé pour stocker les données des appareils, les secrets des challenges, etc.
|
|
8
|
+
* Interface for a persistent storage adapter (device data, challenge nonces, tickets).
|
|
10
9
|
*/
|
|
11
10
|
interface IStore
|
|
12
11
|
{
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* Retrieves a value associated with a key.
|
|
15
14
|
* @param string $key
|
|
16
15
|
* @return mixed|null
|
|
17
16
|
*/
|
|
18
17
|
public function get(string $key);
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
|
-
*
|
|
20
|
+
* Stores a value associated with a key, with an optional TTL.
|
|
22
21
|
* @param string $key
|
|
23
22
|
* @param mixed $value
|
|
24
|
-
* @param int|null $ttl
|
|
23
|
+
* @param int|null $ttl Time to live in seconds.
|
|
25
24
|
* @return void
|
|
26
25
|
*/
|
|
27
26
|
public function set(string $key, $value, ?int $ttl = null): void;
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
*
|
|
29
|
+
* Checks whether a key exists in storage.
|
|
31
30
|
* @param string $key
|
|
32
31
|
* @return bool
|
|
33
32
|
*/
|
|
34
33
|
public function has(string $key): bool;
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
*
|
|
36
|
+
* Deletes a key from storage.
|
|
38
37
|
* @param string $key
|
|
39
38
|
* @return void
|
|
40
39
|
*/
|
|
41
40
|
public function delete(string $key): void;
|
|
42
41
|
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
43
|
+
* Clears all keys from storage.
|
|
45
44
|
* @return void
|
|
46
45
|
*/
|
|
47
46
|
public function clear(): void;
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Store;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
class InMemoryStore implements IStore
|
|
12
|
-
{
|
|
13
|
-
/**
|
|
14
|
-
* @var array<string, array{value: mixed, expiresAt: int|null}>
|
|
15
|
-
*/
|
|
16
|
-
private array $data = [];
|
|
17
|
-
|
|
18
|
-
public function get(string $key)
|
|
19
|
-
{
|
|
20
|
-
if (!isset($this->data[$key])) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
$item = $this->data[$key];
|
|
25
|
-
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
26
|
-
$this->delete($key); //
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return $item['value'];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public function set(string $key, $value, ?int $ttl = null): void
|
|
34
|
-
{
|
|
35
|
-
$expiresAt = $ttl !== null ? time() + $ttl : null;
|
|
36
|
-
|
|
37
|
-
$this->data[$key] = ['value' => $value, 'expiresAt' => $expiresAt];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public function has(string $key): bool
|
|
41
|
-
{
|
|
42
|
-
if (!isset($this->data[$key])) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
$item = $this->data[$key];
|
|
47
|
-
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
48
|
-
$this->delete($key);
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public function delete(string $key): void
|
|
56
|
-
{
|
|
57
|
-
unset($this->data[$key]);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
*/
|
|
63
|
-
public function clear(): void
|
|
64
|
-
{
|
|
65
|
-
$this->data = [];
|
|
66
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* In-memory implementation of the IStore interface.
|
|
9
|
+
* Suitable for local development, tests, or single-instance deployments.
|
|
10
|
+
*/
|
|
11
|
+
class InMemoryStore implements IStore
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @var array<string, array{value: mixed, expiresAt: int|null}>
|
|
15
|
+
*/
|
|
16
|
+
private array $data = [];
|
|
17
|
+
|
|
18
|
+
public function get(string $key)
|
|
19
|
+
{
|
|
20
|
+
if (!isset($this->data[$key])) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$item = $this->data[$key];
|
|
25
|
+
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
26
|
+
$this->delete($key); // Delete expired item
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return $item['value'];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
34
|
+
{
|
|
35
|
+
$expiresAt = $ttl !== null ? time() + $ttl : null;
|
|
36
|
+
|
|
37
|
+
$this->data[$key] = ['value' => $value, 'expiresAt' => $expiresAt];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public function has(string $key): bool
|
|
41
|
+
{
|
|
42
|
+
if (!isset($this->data[$key])) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
$item = $this->data[$key];
|
|
47
|
+
if ($item['expiresAt'] !== null && $item['expiresAt'] < time()) {
|
|
48
|
+
$this->delete($key);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public function delete(string $key): void
|
|
56
|
+
{
|
|
57
|
+
unset($this->data[$key]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Clears all data from the store. Useful for testing.
|
|
62
|
+
*/
|
|
63
|
+
public function clear(): void
|
|
64
|
+
{
|
|
65
|
+
$this->data = [];
|
|
66
|
+
}
|
|
67
67
|
}
|
|
@@ -5,8 +5,8 @@ declare(strict_types=1);
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Store;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* MongoDB storage adapter for the Fingerprint engine.
|
|
9
|
+
* Utilizes the official mongodb/mongodb driver.
|
|
10
10
|
*/
|
|
11
11
|
class MongoDbStore implements IStore
|
|
12
12
|
{
|
|
@@ -31,7 +31,7 @@ class MongoDbStore implements IStore
|
|
|
31
31
|
private float $lastReconnectAttempt = 0.0;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* @param \MongoDB\Collection $collection
|
|
34
|
+
* @param \MongoDB\Collection $collection Target collection for fingerprint data storage.
|
|
35
35
|
*/
|
|
36
36
|
public function __construct($collection)
|
|
37
37
|
{
|
|
@@ -69,7 +69,7 @@ class MongoDbStore implements IStore
|
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// Active TTL check to bypass MongoDB daemon 60-second cleanup latency
|
|
73
73
|
if (isset($doc['expiresAt'])) {
|
|
74
74
|
$expiresAt = $doc['expiresAt'];
|
|
75
75
|
if ($expiresAt instanceof \MongoDB\BSON\UTCDateTime) {
|
|
@@ -168,7 +168,7 @@ class MongoDbStore implements IStore
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
*
|
|
171
|
+
* Automatically initializes required TTL index in MongoDB collection.
|
|
172
172
|
*/
|
|
173
173
|
public function init(): void
|
|
174
174
|
{
|
|
@@ -5,13 +5,13 @@ declare(strict_types=1);
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Store;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Compatible
|
|
8
|
+
* Redis storage adapter for the Fingerprint engine.
|
|
9
|
+
* Compatible with phpredis and predis clients.
|
|
10
10
|
*/
|
|
11
11
|
class RedisStore implements IStore
|
|
12
12
|
{
|
|
13
13
|
/**
|
|
14
|
-
* @var mixed
|
|
14
|
+
* @var mixed An instance of \Redis or \Predis\Client
|
|
15
15
|
*/
|
|
16
16
|
private $redis;
|
|
17
17
|
|
|
@@ -31,7 +31,7 @@ class RedisStore implements IStore
|
|
|
31
31
|
private float $lastReconnectAttempt = 0.0;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* @param mixed $redis
|
|
34
|
+
* @param mixed $redis Pre-configured and connected Redis client instance.
|
|
35
35
|
*/
|
|
36
36
|
public function __construct($redis)
|
|
37
37
|
{
|
|
@@ -46,7 +46,7 @@ class RedisStore implements IStore
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
$now = microtime(true);
|
|
49
|
-
//
|
|
49
|
+
// Retry connection every 5 seconds
|
|
50
50
|
if ($now - $this->lastReconnectAttempt > 5.0) {
|
|
51
51
|
$this->lastReconnectAttempt = $now;
|
|
52
52
|
try {
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Store;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
class StoreManager
|
|
11
|
-
{
|
|
12
|
-
private static ?IStore $store = null;
|
|
13
|
-
|
|
14
|
-
public static function getStore(): IStore
|
|
15
|
-
{
|
|
16
|
-
if (self::$store === null) {
|
|
17
|
-
self::$store = new InMemoryStore();
|
|
18
|
-
}
|
|
19
|
-
return self::$store;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public static function configureStore(IStore $externalStore): void
|
|
23
|
-
{
|
|
24
|
-
self::$store = $externalStore;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @param mixed $store
|
|
31
|
-
*/
|
|
32
|
-
public static function setStore($store): void
|
|
33
|
-
{
|
|
34
|
-
self::$store = $store;
|
|
35
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Manages the global store singleton instance.
|
|
9
|
+
*/
|
|
10
|
+
class StoreManager
|
|
11
|
+
{
|
|
12
|
+
private static ?IStore $store = null;
|
|
13
|
+
|
|
14
|
+
public static function getStore(): IStore
|
|
15
|
+
{
|
|
16
|
+
if (self::$store === null) {
|
|
17
|
+
self::$store = new InMemoryStore();
|
|
18
|
+
}
|
|
19
|
+
return self::$store;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public static function configureStore(IStore $externalStore): void
|
|
23
|
+
{
|
|
24
|
+
self::$store = $externalStore;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sets the active store instance (useful for dependency injection and tests).
|
|
29
|
+
*
|
|
30
|
+
* @param mixed $store
|
|
31
|
+
*/
|
|
32
|
+
public static function setStore($store): void
|
|
33
|
+
{
|
|
34
|
+
self::$store = $store;
|
|
35
|
+
}
|
|
36
36
|
}
|