@anonympins/fingerprint 0.7.5 → 0.8.1

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +1 -1
  3. package/package.json +3 -2
  4. package/public/anonympins-bot-mitigation-pow.zip +0 -0
  5. package/src/js/asn-lookup.js +223 -0
  6. package/src/js/fingerprint.client.js +1784 -1772
  7. package/src/js/fingerprint.client.obfuscated.js +1 -1
  8. package/src/js/fingerprint.js +236 -40
  9. package/src/js/fingerprint.utils.js +252 -2
  10. package/src/js/pow.worker.js +2 -2
  11. package/src/js/tests/asn-lookup.test.js +57 -0
  12. package/src/js/tests/cross_parity.test.js +3 -3
  13. package/src/js/tests/dns-circuit-breaker.test.js +6 -6
  14. package/src/js/tests/fingerprint.builder.test.js +76 -78
  15. package/src/js/tests/fingerprint.client.test.js +16 -17
  16. package/src/js/tests/fingerprint.test.js +19 -12
  17. package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
  18. package/src/js/tests/library.test.js +94 -95
  19. package/src/js/tests/obfuscation.test.js +8 -8
  20. package/src/js/tests/quicFingerprint.test.js +47 -0
  21. package/src/js/tests/tcpFingerprint.test.js +4 -4
  22. package/src/js/upow-model-task.js +155 -112
  23. package/src/php/AsnLookupEngine.php +173 -0
  24. package/src/php/Challenge/ChallengeUtils.php +430 -117
  25. package/src/php/Config/SecurityProfiles.php +75 -62
  26. package/src/php/DirectFingerprint.php +80 -13
  27. package/src/php/FingerprintBuilder.php +13 -13
  28. package/src/php/FingerprintClient.php +6 -6
  29. package/src/php/FingerprintEngine.php +2141 -1837
  30. package/src/php/Ja3AnomalyDetector.php +22 -22
  31. package/src/php/NetworkProfile.php +68 -0
  32. package/src/php/Optimization/FunctionRegistry.php +4 -4
  33. package/src/php/Optimization/Optimization.php +6 -6
  34. package/src/php/ProblemManager.php +12 -2
  35. package/src/php/RequestContext.php +11 -11
  36. package/src/php/Store/InMemoryStore.php +96 -66
  37. package/src/php/Store/MongoDbStore.php +2 -2
  38. package/src/php/Store/RedisStore.php +1 -1
  39. package/src/php/Store/StoreManager.php +48 -35
  40. package/src/php/Tests/AsnLookupTest.php +61 -0
  41. package/src/php/Tests/ChallengeUtilsTest.php +31 -0
  42. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  43. package/src/php/Tests/FingerprintClientTest.php +70 -70
  44. package/src/php/Tests/FingerprintEngineTest.php +4 -2
  45. package/src/php/Tests/PowTest.php +37 -39
  46. package/src/php/Tests/QuicFingerprintTest.php +115 -53
  47. package/src/php/Tests/ThreatIntelTest.php +2 -2
  48. package/src/php/Utils/BigInt.php +202 -202
  49. package/src/php/Utils/BlockList.php +13 -13
  50. package/src/php/Utils/Env.php +7 -2
  51. package/src/php/Utils/Logger.php +19 -1
  52. package/src/php/Utils/MaliciousPatterns.php +6 -6
  53. package/src/php/Utils/MetricsManager.php +209 -209
  54. package/src/php/Utils/RequestUtils.php +348 -18
  55. package/src/php/Utils/TLSClientHelloParser.php +385 -369
  56. package/src/php/WordPress/WpDbStore.php +34 -7
  57. package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1222 -0
  58. package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
  59. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
  60. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
  61. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  62. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
  63. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  64. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
  65. package/src/php/WordPress/package.php +88 -99
  66. package/src/php/WordPress/readme.txt +28 -6
  67. package/src/php/bin/auto-tune.php +1 -1
  68. package/public/fingerprint-anti-bot.zip +0 -0
@@ -1,36 +1,49 @@
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
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Store;
6
+
7
+ /**
8
+ * Manages the global store singleton instance for the application.
9
+ */
10
+ class StoreManager
11
+ {
12
+ private static ?IStore $store = null;
13
+
14
+ /**
15
+ * Returns the current global store instance.
16
+ * Lazily instantiates an InMemoryStore if no store has been configured yet.
17
+ *
18
+ * @return IStore The active store instance.
19
+ */
20
+ public static function getStore(): IStore
21
+ {
22
+ if (self::$store === null) {
23
+ self::$store = new InMemoryStore();
24
+ }
25
+ return self::$store;
26
+ }
27
+
28
+ /**
29
+ * Replaces the active store with an externally provided IStore implementation.
30
+ * Typically used at bootstrap to plug in a persistent store (Redis, database, etc.).
31
+ *
32
+ * @param IStore $externalStore The external store implementation to use.
33
+ * @return void
34
+ */
35
+ public static function configureStore(IStore $externalStore): void
36
+ {
37
+ self::$store = $externalStore;
38
+ }
39
+
40
+ /**
41
+ * Sets the active store instance. Useful for dependency injection and testing.
42
+ *
43
+ * @param mixed $store
44
+ */
45
+ public static function setStore($store): void
46
+ {
47
+ self::$store = $store;
48
+ }
36
49
  }
@@ -0,0 +1,61 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\AsnLookupEngine;
8
+ use Anonympins\Fingerprint\NetworkProfile;
9
+ use Anonympins\Fingerprint\Utils\RequestUtils;
10
+ use PHPUnit\Framework\TestCase;
11
+
12
+ class AsnLookupTest extends TestCase
13
+ {
14
+ private AsnLookupEngine $engine;
15
+
16
+ protected function setUp(): void
17
+ {
18
+ parent::setUp();
19
+ $this->engine = AsnLookupEngine::getInstance();
20
+ }
21
+
22
+ public function testDatacenterResolutionAndPriorScore(): void
23
+ {
24
+ $hetzner = $this->engine->lookup('95.216.12.34');
25
+ $this->assertSame('HOSTING', $hetzner->getType());
26
+ $this->assertSame(55.0, $hetzner->getBaseScore());
27
+ $this->assertSame(0.85, $hetzner->getInflectionPoint());
28
+ $this->assertFalse($hetzner->isToleranceRotation());
29
+
30
+ $aws = $this->engine->lookup('54.210.1.20');
31
+ $this->assertSame('HOSTING', $aws->getType());
32
+ $this->assertSame(55.0, $aws->getBaseScore());
33
+ }
34
+
35
+ public function testMobileCgnatResolution(): void
36
+ {
37
+ $cgnat = $this->engine->lookup('100.70.1.25');
38
+ $this->assertSame('CELLULAR', $cgnat->getType());
39
+ $this->assertSame(10.0, $cgnat->getBaseScore());
40
+ $this->assertSame(0.60, $cgnat->getInflectionPoint());
41
+ $this->assertTrue($cgnat->isToleranceRotation());
42
+ }
43
+
44
+ public function testStarlinkSatelliteResolution(): void
45
+ {
46
+ $starlink = $this->engine->lookup('98.97.10.5');
47
+ $this->assertSame('SATELLITE', $starlink->getType());
48
+ $this->assertSame(15.0, $starlink->getBaseScore());
49
+ $this->assertSame(150.0, $starlink->getJitterTolerance());
50
+ }
51
+
52
+ public function testAnalogInconsistencyModulation(): void
53
+ {
54
+ $consistency = 0.80; // Incohérence légère de rendu
55
+ $scoreDatacenter = RequestUtils::calculateAnalogInconsistencyScore($consistency, NetworkProfile::hosting());
56
+ $scoreMobile = RequestUtils::calculateAnalogInconsistencyScore($consistency, NetworkProfile::cellular());
57
+
58
+ $this->assertGreaterThan(55.0, $scoreDatacenter, 'Sur Datacenter (0.80 < 0.85), la suspicion décolle au-dessus de 55.0');
59
+ $this->assertLessThan(10.0, $scoreMobile, 'Sur Mobile (0.80 > 0.60), la variation est entièrement absorbée (< 10.0)');
60
+ }
61
+ }
@@ -263,6 +263,37 @@ class ChallengeUtilsTest extends TestCase
263
263
  Env::clear('ED25519_PUBLIC_KEY');
264
264
  }
265
265
 
266
+ public function testGenerateIssuerPemKeys(): void
267
+ {
268
+ if (!extension_loaded('openssl') || !defined('OPENSSL_KEYTYPE_ED25519')) {
269
+ $this->markTestSkipped('OpenSSL Ed25519 is not supported in this environment.');
270
+ }
271
+
272
+ $pkey = @openssl_pkey_new(["private_key_type" => constant('OPENSSL_KEYTYPE_ED25519')]);
273
+ if (!$pkey || !@openssl_pkey_export($pkey, $testPem)) {
274
+ $this->markTestSkipped('Ed25519 key generation or export is not supported in this PHP/OpenSSL environment.');
275
+ }
276
+
277
+ $tempDir = sys_get_temp_dir() . '/fp-issuer-keys-' . time();
278
+ try {
279
+ $result = ChallengeUtils::generateIssuerPemKeys($tempDir);
280
+ $this->assertFileExists($result['privateKeyPath']);
281
+ $this->assertFileExists($result['publicKeyPath']);
282
+ $this->assertStringContainsString('-----BEGIN PRIVATE KEY-----', $result['privateKey']);
283
+ $this->assertStringContainsString('-----BEGIN PUBLIC KEY-----', $result['publicKey']);
284
+ } finally {
285
+ if (file_exists($tempDir . '/issuer-private.pem')) {
286
+ unlink($tempDir . '/issuer-private.pem');
287
+ }
288
+ if (file_exists($tempDir . '/issuer-public.pem')) {
289
+ unlink($tempDir . '/issuer-public.pem');
290
+ }
291
+ if (is_dir($tempDir)) {
292
+ rmdir($tempDir);
293
+ }
294
+ }
295
+ }
296
+
266
297
  public function testCooperativePoSpaceWorkflow(): void
267
298
  {
268
299
  $clientIp = '127.0.0.1';
@@ -1,106 +1,106 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Tests;
6
-
7
- use Anonympins\Fingerprint\FingerprintEngine;
8
- use PHPUnit\Framework\TestCase;
9
- use ReflectionClass;
10
-
11
- class DnsCircuitBreakerTest extends TestCase
12
- {
13
- private ReflectionClass $reflection;
14
-
15
- protected function setUp(): void
16
- {
17
- parent::setUp();
18
- $this->reflection = new ReflectionClass(FingerprintEngine::class);
19
-
20
- // Réinitialise l'état statique du disjoncteur avant chaque test
21
- $this->setCircuitBreakerState([
22
- 'state' => 'CLOSED',
23
- 'failureCount' => 0,
24
- 'lastStateChange' => 0,
25
- 'threshold' => 5,
26
- 'cooldown' => 1, // 1 seconde de cooldown pour accélérer le test
27
- ]);
28
- }
29
-
30
- private function getCircuitBreakerState(): array
31
- {
32
- $property = $this->reflection->getProperty('dnsCircuitBreaker');
33
- if (PHP_VERSION_ID < 80100) {
34
- $property->setAccessible(true);
35
- }
36
- return $property->getValue();
37
- }
38
-
39
- private function setCircuitBreakerState(array $state): void
40
- {
41
- $property = $this->reflection->getProperty('dnsCircuitBreaker');
42
- if (PHP_VERSION_ID < 80100) {
43
- $property->setAccessible(true);
44
- }
45
- $property->setValue(null, $state);
46
- }
47
-
48
- private function invokeMethod(string $methodName, array $args = [])
49
- {
50
- $method = $this->reflection->getMethod($methodName);
51
- if (PHP_VERSION_ID < 80100) { // PHP 8.1.0
52
- $method->setAccessible(true);
53
- }
54
- return $method->invokeArgs(null, $args);
55
- }
56
-
57
- public function testInitialStateIsClosed(): void
58
- {
59
- $this->assertTrue($this->invokeMethod('canAttemptDns'));
60
- $state = $this->getCircuitBreakerState();
61
- $this->assertSame('CLOSED', $state['state']);
62
- }
63
-
64
- public function testFailureThresholdTripsBreaker(): void
65
- {
66
- for ($i = 0; $i < 4; $i++) {
67
- $this->invokeMethod('recordDnsFailure');
68
- $this->assertTrue($this->invokeMethod('canAttemptDns'));
69
- $state = $this->getCircuitBreakerState();
70
- $this->assertSame('CLOSED', $state['state']);
71
- }
72
-
73
- $this->invokeMethod('recordDnsFailure');
74
- $this->assertFalse($this->invokeMethod('canAttemptDns'));
75
- $state = $this->getCircuitBreakerState();
76
- $this->assertSame('OPEN', $state['state']);
77
- }
78
-
79
- public function testCooldownTransitionsToHalfOpen(): void
80
- {
81
- for ($i = 0; $i < 5; $i++) {
82
- $this->invokeMethod('recordDnsFailure');
83
- }
84
- $this->assertFalse($this->invokeMethod('canAttemptDns'));
85
-
86
- usleep(1100000); // 1.1s
87
-
88
- $this->assertTrue($this->invokeMethod('canAttemptDns'));
89
- $state = $this->getCircuitBreakerState();
90
- $this->assertSame('HALF-OPEN', $state['state']);
91
- }
92
-
93
- public function testSuccessOnHalfOpenResetsBreaker(): void
94
- {
95
- for ($i = 0; $i < 5; $i++) {
96
- $this->invokeMethod('recordDnsFailure');
97
- }
98
- usleep(1100000);
99
- $this->assertTrue($this->invokeMethod('canAttemptDns'));
100
-
101
- $this->invokeMethod('recordDnsSuccess');
102
- $state = $this->getCircuitBreakerState();
103
- $this->assertSame('CLOSED', $state['state']);
104
- $this->assertSame(0, $state['failureCount']);
105
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\FingerprintEngine;
8
+ use PHPUnit\Framework\TestCase;
9
+ use ReflectionClass;
10
+
11
+ class DnsCircuitBreakerTest extends TestCase
12
+ {
13
+ private ReflectionClass $reflection;
14
+
15
+ protected function setUp(): void
16
+ {
17
+ parent::setUp();
18
+ $this->reflection = new ReflectionClass(FingerprintEngine::class);
19
+
20
+ // Reset static circuit breaker state before each test
21
+ $this->setCircuitBreakerState([
22
+ 'state' => 'CLOSED',
23
+ 'failureCount' => 0,
24
+ 'lastStateChange' => 0,
25
+ 'threshold' => 5,
26
+ 'cooldown' => 1, // 1 second cooldown for faster test execution
27
+ ]);
28
+ }
29
+
30
+ private function getCircuitBreakerState(): array
31
+ {
32
+ $property = $this->reflection->getProperty('dnsCircuitBreaker');
33
+ if (PHP_VERSION_ID < 80100) {
34
+ $property->setAccessible(true);
35
+ }
36
+ return $property->getValue();
37
+ }
38
+
39
+ private function setCircuitBreakerState(array $state): void
40
+ {
41
+ $property = $this->reflection->getProperty('dnsCircuitBreaker');
42
+ if (PHP_VERSION_ID < 80100) {
43
+ $property->setAccessible(true);
44
+ }
45
+ $property->setValue(null, $state);
46
+ }
47
+
48
+ private function invokeMethod(string $methodName, array $args = [])
49
+ {
50
+ $method = $this->reflection->getMethod($methodName);
51
+ if (PHP_VERSION_ID < 80100) { // PHP 8.1.0
52
+ $method->setAccessible(true);
53
+ }
54
+ return $method->invokeArgs(null, $args);
55
+ }
56
+
57
+ public function testInitialStateIsClosed(): void
58
+ {
59
+ $this->assertTrue($this->invokeMethod('canAttemptDns'));
60
+ $state = $this->getCircuitBreakerState();
61
+ $this->assertSame('CLOSED', $state['state']);
62
+ }
63
+
64
+ public function testFailureThresholdTripsBreaker(): void
65
+ {
66
+ for ($i = 0; $i < 4; $i++) {
67
+ $this->invokeMethod('recordDnsFailure');
68
+ $this->assertTrue($this->invokeMethod('canAttemptDns'));
69
+ $state = $this->getCircuitBreakerState();
70
+ $this->assertSame('CLOSED', $state['state']);
71
+ }
72
+
73
+ $this->invokeMethod('recordDnsFailure');
74
+ $this->assertFalse($this->invokeMethod('canAttemptDns'));
75
+ $state = $this->getCircuitBreakerState();
76
+ $this->assertSame('OPEN', $state['state']);
77
+ }
78
+
79
+ public function testCooldownTransitionsToHalfOpen(): void
80
+ {
81
+ for ($i = 0; $i < 5; $i++) {
82
+ $this->invokeMethod('recordDnsFailure');
83
+ }
84
+ $this->assertFalse($this->invokeMethod('canAttemptDns'));
85
+
86
+ usleep(1100000); // 1.1s
87
+
88
+ $this->assertTrue($this->invokeMethod('canAttemptDns'));
89
+ $state = $this->getCircuitBreakerState();
90
+ $this->assertSame('HALF-OPEN', $state['state']);
91
+ }
92
+
93
+ public function testSuccessOnHalfOpenResetsBreaker(): void
94
+ {
95
+ for ($i = 0; $i < 5; $i++) {
96
+ $this->invokeMethod('recordDnsFailure');
97
+ }
98
+ usleep(1100000);
99
+ $this->assertTrue($this->invokeMethod('canAttemptDns'));
100
+
101
+ $this->invokeMethod('recordDnsSuccess');
102
+ $state = $this->getCircuitBreakerState();
103
+ $this->assertSame('CLOSED', $state['state']);
104
+ $this->assertSame(0, $state['failureCount']);
105
+ }
106
106
  }
@@ -1,71 +1,71 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Tests;
6
-
7
- use Anonympins\Fingerprint\FingerprintClient;
8
- use PHPUnit\Framework\TestCase;
9
-
10
- class FingerprintClientTest extends TestCase
11
- {
12
- public function testDefaultConfigurationIncludesWasmAndDefaultPath(): void
13
- {
14
- $client = new FingerprintClient('/js/fingerprint.client.js');
15
- $scriptTag = $client->getScriptTag();
16
-
17
- // Vérifie que le script principal est présent
18
- $this->assertStringContainsString('src="/js/fingerprint.client.js"', $scriptTag);
19
-
20
- // Vérifie les configurations WASM par défaut dans l'objet sérialisé pour le JS
21
- $this->assertStringContainsString('"wasm":true', $scriptTag);
22
- $this->assertStringContainsString('"wasmPath":"\/fp.js"', $scriptTag);
23
- $this->assertStringContainsString('wasmScript.src = config.wasmPath;', $scriptTag);
24
- }
25
-
26
- public function testWasmCanBeOverriddenOrDisabled(): void
27
- {
28
- $client = new FingerprintClient('/js/fingerprint.client.js', [
29
- 'wasm' => false,
30
- 'wasmPath' => '/custom/path/to/wasm.js'
31
- ]);
32
- $scriptTag = $client->getScriptTag();
33
-
34
- // Vérifie que les surcharges sont correctement prises en compte
35
- $this->assertStringContainsString('"wasm":false', $scriptTag);
36
- $this->assertStringContainsString('"wasmPath":"\/custom\/path\/to\/wasm.js"', $scriptTag);
37
- }
38
-
39
- public function testGenerateHoneypotFieldAddsFieldToConfig(): void
40
- {
41
- $client = new FingerprintClient('/js/fingerprint.client.js');
42
-
43
- $honeypotFieldHtml = $client->generateHoneypotField('confirm_email_trap');
44
-
45
- // Vérifie le code HTML du champ Honeypot
46
- $this->assertStringContainsString('name="confirm_email_trap"', $honeypotFieldHtml);
47
- $this->assertStringContainsString('tabindex="-1"', $honeypotFieldHtml);
48
-
49
- // Vérifie que le champ s'enregistre dynamiquement dans la configuration JS finale
50
- $scriptTag = $client->getScriptTag();
51
- $this->assertStringContainsString('"honeypots":["confirm_email_trap"]', $scriptTag);
52
- }
53
-
54
- public function testNonceIsGeneratedAndAppliedToScripts(): void
55
- {
56
- $client = new FingerprintClient('/js/fingerprint.client.js');
57
- $nonce = $client->getNonce();
58
-
59
- // Vérifie que le nonce est généré correctement (32 caractères hexa)
60
- $this->assertNotNull($nonce);
61
- $this->assertEquals(32, strlen($nonce));
62
-
63
- $scriptTag = $client->getScriptTag();
64
-
65
- // Le nonce doit être présent sur la balise script HTML
66
- $this->assertStringContainsString('nonce="' . $nonce . '"', $scriptTag);
67
-
68
- // Et configuré dynamiquement lors de l'injection du script de chargement WASM
69
- $this->assertStringContainsString('wasmScript.nonce = \'' . $nonce . '\'', $scriptTag);
70
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\FingerprintClient;
8
+ use PHPUnit\Framework\TestCase;
9
+
10
+ class FingerprintClientTest extends TestCase
11
+ {
12
+ public function testDefaultConfigurationIncludesWasmAndDefaultPath(): void
13
+ {
14
+ $client = new FingerprintClient('/js/fingerprint.client.js');
15
+ $scriptTag = $client->getScriptTag();
16
+
17
+ // Verify main script tag is present
18
+ $this->assertStringContainsString('src="/js/fingerprint.client.js"', $scriptTag);
19
+
20
+ // Verify default WASM configuration serialized for JS runtime
21
+ $this->assertStringContainsString('"wasm":true', $scriptTag);
22
+ $this->assertStringContainsString('"wasmPath":"\/fp.js"', $scriptTag);
23
+ $this->assertStringContainsString('wasmScript.src = config.wasmPath;', $scriptTag);
24
+ }
25
+
26
+ public function testWasmCanBeOverriddenOrDisabled(): void
27
+ {
28
+ $client = new FingerprintClient('/js/fingerprint.client.js', [
29
+ 'wasm' => false,
30
+ 'wasmPath' => '/custom/path/to/wasm.js'
31
+ ]);
32
+ $scriptTag = $client->getScriptTag();
33
+
34
+ // Verify configuration overrides are properly applied
35
+ $this->assertStringContainsString('"wasm":false', $scriptTag);
36
+ $this->assertStringContainsString('"wasmPath":"\/custom\/path\/to\/wasm.js"', $scriptTag);
37
+ }
38
+
39
+ public function testGenerateHoneypotFieldAddsFieldToConfig(): void
40
+ {
41
+ $client = new FingerprintClient('/js/fingerprint.client.js');
42
+
43
+ $honeypotFieldHtml = $client->generateHoneypotField('confirm_email_trap');
44
+
45
+ // Verify HTML markup of the honeypot field
46
+ $this->assertStringContainsString('name="confirm_email_trap"', $honeypotFieldHtml);
47
+ $this->assertStringContainsString('tabindex="-1"', $honeypotFieldHtml);
48
+
49
+ // Verify field dynamically registers with client-side configuration
50
+ $scriptTag = $client->getScriptTag();
51
+ $this->assertStringContainsString('"honeypots":["confirm_email_trap"]', $scriptTag);
52
+ }
53
+
54
+ public function testNonceIsGeneratedAndAppliedToScripts(): void
55
+ {
56
+ $client = new FingerprintClient('/js/fingerprint.client.js');
57
+ $nonce = $client->getNonce();
58
+
59
+ // Verify nonce generation format (32 hex characters)
60
+ $this->assertNotNull($nonce);
61
+ $this->assertEquals(32, strlen($nonce));
62
+
63
+ $scriptTag = $client->getScriptTag();
64
+
65
+ // Nonce must be attached to HTML script tag
66
+ $this->assertStringContainsString('nonce="' . $nonce . '"', $scriptTag);
67
+
68
+ // And dynamically set during WASM loader script injection
69
+ $this->assertStringContainsString('wasmScript.nonce = \'' . $nonce . '\'', $scriptTag);
70
+ }
71
71
  }
@@ -303,11 +303,12 @@ class FingerprintEngineTest extends TestCase
303
303
  $this->markTestSkipped('Ed25519 is not supported in this PHP/OpenSSL environment.');
304
304
  }
305
305
 
306
- $configDir = dirname(__FILE__, 3) . '/config'; // Match engine's behavior
306
+ $configDir = sys_get_temp_dir() . '/anonympins-test-keys-' . time();
307
307
  if (!is_dir($configDir)) {
308
308
  mkdir($configDir, 0777, true);
309
309
  }
310
310
  $keyPath = $configDir . '/ed25519_key.json';
311
+ Env::set('FINGERPRINT_KEY_DIR', $configDir);
311
312
 
312
313
  Env::clear('ED25519_PRIVATE_KEY');
313
314
  Env::clear('ED25519_PUBLIC_KEY');
@@ -338,11 +339,12 @@ class FingerprintEngineTest extends TestCase
338
339
 
339
340
  public function testEd25519KeyLoadingFromDisk(): void
340
341
  {
341
- $configDir = dirname(__FILE__, 3) . '/config'; // Match engine's behavior
342
+ $configDir = sys_get_temp_dir() . '/anonympins-test-keys-' . time();
342
343
  if (!is_dir($configDir)) {
343
344
  mkdir($configDir, 0777, true);
344
345
  }
345
346
  $keyPath = $configDir . '/ed25519_key.json';
347
+ Env::set('FINGERPRINT_KEY_DIR', $configDir);
346
348
 
347
349
  $dummyKeys = [
348
350
  'privateKey' => '-----BEGIN PRIVATE KEY-----\ndummy-php-private\n-----END PRIVATE KEY-----',
@@ -1,40 +1,38 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Tests;
6
-
7
- use Anonympins\Fingerprint\Config\SecurityProfiles;
8
- use Anonympins\Fingerprint\FingerprintEngine;
9
- use Anonympins\Fingerprint\ProblemManager;
10
- use Anonympins\Fingerprint\Store\InMemoryStore;
11
- use Anonympins\Fingerprint\Store\StoreManager;
12
- use PHPUnit\Framework\TestCase;
13
-
14
- class PowTest extends TestCase
15
- {
16
- private FingerprintEngine $engine;
17
-
18
- protected function setUp(): void
19
- {
20
- // 1. Configurer un store en mémoire pour l'isolation des tests.
21
- $store = new InMemoryStore();
22
- StoreManager::configureStore($store);
23
-
24
- // 2. Initialiser le ProblemManager avec une configuration et un store valides.
25
- // C'est l'étape cruciale qui manquait.
26
- $configPath = dirname(__FILE__) . '/config/problems.config.json';
27
- ProblemManager::getInstance($configPath, $store);
28
-
29
- // 3. Créer l'instance du moteur.
30
- $this->engine = new FingerprintEngine(SecurityProfiles::createSecurityProfile('balanced'));
31
- }
32
-
33
- public function testGetProblemsIsExposedForTesting(): void
34
- {
35
- // Cette méthode appelle ProblemManager::getInstance() en interne.
36
- // Grâce au setUp(), l'instance est déjà initialisée et le test passe.
37
- $problems = $this->engine->getProblems();
38
- $this->assertIsArray($problems);
39
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\Config\SecurityProfiles;
8
+ use Anonympins\Fingerprint\FingerprintEngine;
9
+ use Anonympins\Fingerprint\ProblemManager;
10
+ use Anonympins\Fingerprint\Store\InMemoryStore;
11
+ use Anonympins\Fingerprint\Store\StoreManager;
12
+ use PHPUnit\Framework\TestCase;
13
+
14
+ class PowTest extends TestCase
15
+ {
16
+ private FingerprintEngine $engine;
17
+
18
+ protected function setUp(): void
19
+ {
20
+ // 1. Configure an in-memory store for test isolation.
21
+ $store = new InMemoryStore();
22
+ StoreManager::configureStore($store);
23
+
24
+ // 2. Initialize ProblemManager with a valid configuration and store.
25
+ $configPath = dirname(__FILE__) . '/config/problems.config.json';
26
+ ProblemManager::getInstance($configPath, $store);
27
+
28
+ // 3. Create engine instance.
29
+ $this->engine = new FingerprintEngine(SecurityProfiles::createSecurityProfile('balanced'));
30
+ }
31
+
32
+ public function testGetProblemsIsExposedForTesting(): void
33
+ {
34
+ // Calls ProblemManager::getInstance() internally; setUp() ensures valid initialization.
35
+ $problems = $this->engine->getProblems();
36
+ $this->assertIsArray($problems);
37
+ }
40
38
  }