@anonympins/fingerprint 0.3.6 → 0.3.7
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 +20 -1
- package/README.md +119 -6
- package/package.json +1 -1
- package/src/js/build-client.js +4 -5
- package/src/js/fingerprint.client.js +2 -2
- package/src/js/fingerprint.js +202 -54
- package/src/js/library.js +1 -1
- package/src/js/mongodb-store.js +79 -52
- package/src/js/optimization.worker.js +2 -2
- package/src/js/problem-manager.js +2 -2
- package/src/php/DirectFingerprint.php +145 -80
- package/src/php/FingerprintEngine.php +19 -3
- package/src/php/ProblemManager.php +1 -1
- package/src/php/RequestContext.php +90 -90
- package/src/php/Store/MongoDbStore.php +105 -0
- package/src/php/Store/RedisStore.php +54 -0
- package/src/php/Tests/ChallengeUtilsTest.php +1 -1
- package/src/php/Tests/FingerprintBuilderTest.php +1 -1
- package/src/php/Tests/FingerprintEngineTest.php +4 -4
- package/src/php/Tests/IpReputationTest.php +4 -4
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +1 -1
- package/src/php/Tests/MetricsTest.php +46 -0
- package/src/php/Tests/PowTest.php +2 -2
- package/src/php/Tests/ProblemManagerTest.php +5 -3
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Utils/MetricsManager.php +167 -0
- package/src/php/Utils/RequestUtils.php +1142 -1142
- package/src/php/bin/auto-tune.php +2 -2
package/src/js/mongodb-store.js
CHANGED
|
@@ -1,53 +1,80 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Creates a store adapter for MongoDB.
|
|
3
|
-
* This adapter uses a collection as a key-value store and leverages MongoDB's TTL indexes
|
|
4
|
-
* for automatic expiration of documents.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a store adapter for a MongoDB collection.
|
|
9
|
-
* It's recommended to pass the `db` object and let the adapter handle the collection.
|
|
10
|
-
*
|
|
11
|
-
* **Note:** For TTL to work, you must create a TTL index on the `expiresAt` field in your collection.
|
|
12
|
-
* In the mongo shell, run:
|
|
13
|
-
* `db.yourCollectionName.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })`
|
|
14
|
-
*
|
|
15
|
-
* @param {import('mongodb').Db} db - An instance of a MongoDB Db object.
|
|
16
|
-
* @param {string} [collectionName='fingerprint_store'] - The name of the collection to use.
|
|
17
|
-
* @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
|
|
18
|
-
*/
|
|
19
|
-
export function createMongoDbStore(db, collectionName = 'fingerprint_store') {
|
|
20
|
-
const collection = db.collection(collectionName);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Creates a store adapter for MongoDB.
|
|
3
|
+
* This adapter uses a collection as a key-value store and leverages MongoDB's TTL indexes
|
|
4
|
+
* for automatic expiration of documents.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a store adapter for a MongoDB collection.
|
|
9
|
+
* It's recommended to pass the `db` object and let the adapter handle the collection.
|
|
10
|
+
*
|
|
11
|
+
* **Note:** For TTL to work, you must create a TTL index on the `expiresAt` field in your collection.
|
|
12
|
+
* In the mongo shell, run:
|
|
13
|
+
* `db.yourCollectionName.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })`
|
|
14
|
+
*
|
|
15
|
+
* @param {import('mongodb').Db} db - An instance of a MongoDB Db object.
|
|
16
|
+
* @param {string} [collectionName='fingerprint_store'] - The name of the collection to use.
|
|
17
|
+
* @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
|
|
18
|
+
*/
|
|
19
|
+
export function createMongoDbStore(db, collectionName = 'fingerprint_store') {
|
|
20
|
+
const collection = db.collection(collectionName);
|
|
21
|
+
|
|
22
|
+
// Custom replacer/reviver to handle Set serialization (identical to Redis/SQL stores)
|
|
23
|
+
const replacer = (k, v) => (v instanceof Set ? Array.from(v) : v);
|
|
24
|
+
const reviver = (k, v) => (k === 'ips' && Array.isArray(v) ? new Set(v) : v);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
async get(key) {
|
|
28
|
+
const doc = await collection.findOne({ _id: key });
|
|
29
|
+
if (!doc) return null;
|
|
30
|
+
|
|
31
|
+
// Active expiration check to bypass eventual consistency of MongoDB's 60s TTL cleanup daemon
|
|
32
|
+
if (doc.expiresAt && new Date(doc.expiresAt) < new Date()) {
|
|
33
|
+
await this.delete(key);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(doc.value, reviver);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// Fallback for legacy un-serialized raw values
|
|
41
|
+
return doc.value;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
async set(key, value, ttl) {
|
|
45
|
+
const stringValue = JSON.stringify(value, replacer);
|
|
46
|
+
const doc = {
|
|
47
|
+
_id: key,
|
|
48
|
+
value: stringValue,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (ttl && ttl > 0) {
|
|
52
|
+
// Set the expiration date for the TTL index.
|
|
53
|
+
doc.expiresAt = new Date(Date.now() + ttl * 1000);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await collection.updateOne(
|
|
57
|
+
{ _id: key },
|
|
58
|
+
{ $set: doc },
|
|
59
|
+
{ upsert: true }
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
async has(key) {
|
|
63
|
+
const doc = await collection.findOne({ _id: key }, { projection: { expiresAt: 1 } });
|
|
64
|
+
if (!doc) return false;
|
|
65
|
+
|
|
66
|
+
if (doc.expiresAt && new Date(doc.expiresAt) < new Date()) {
|
|
67
|
+
await this.delete(key);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
async delete(key) {
|
|
73
|
+
await collection.deleteOne({ _id: key });
|
|
74
|
+
},
|
|
75
|
+
async init() {
|
|
76
|
+
// Automates index configuration
|
|
77
|
+
await collection.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
53
80
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Ce script s'exécute sur un thread séparé pour ne pas bloquer l'interface utilisateur.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import {parentPort, workerData} from 'worker_threads';
|
|
8
|
+
import {Optimization} from './library.js'; // Assurez-vous que le chemin est correct
|
|
9
9
|
|
|
10
10
|
if (parentPort) {
|
|
11
11
|
parentPort.on('message', async () => { // Le message est vide, on utilise workerData
|
|
@@ -1,81 +1,146 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
$
|
|
42
|
-
$
|
|
43
|
-
$
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Intégration directe du moteur de fingerprinting pour les applications PHP sans framework PSR.
|
|
11
|
+
* Cette classe interagit directement avec les superglobales PHP et les fonctions de réponse.
|
|
12
|
+
*/
|
|
13
|
+
class DirectFingerprint
|
|
14
|
+
{
|
|
15
|
+
private array $securityConfig;
|
|
16
|
+
private FingerprintEngine $engine;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param array $securityConfig La configuration de sécurité pour le moteur.
|
|
20
|
+
*/
|
|
21
|
+
public function __construct(array $securityConfig)
|
|
22
|
+
{
|
|
23
|
+
$this->engine = new FingerprintEngine($securityConfig);
|
|
24
|
+
$this->securityConfig = $securityConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Protège le point d'entrée actuel.
|
|
29
|
+
* Analyse la requête entrante et, si nécessaire, envoie une réponse de challenge/blocage et termine le script.
|
|
30
|
+
* Si la requête est autorisée, la méthode retourne simplement et le reste du script peut s'exécuter.
|
|
31
|
+
*
|
|
32
|
+
* @return array{score: float, vector: array}|null Les données du fingerprint si la requête est autorisée, null sinon.
|
|
33
|
+
*/
|
|
34
|
+
public function protect(): ?array
|
|
35
|
+
{
|
|
36
|
+
// 1. Construire le contexte de la requête à partir des superglobales PHP.
|
|
37
|
+
$body = $_POST ?: json_decode(file_get_contents('php://input'), true);
|
|
38
|
+
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
|
39
|
+
|
|
40
|
+
$context = new RequestContext(
|
|
41
|
+
$_SERVER['REMOTE_ADDR'] ?? '127.0.0.1',
|
|
42
|
+
parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?: '/',
|
|
43
|
+
$headers,
|
|
44
|
+
$_GET,
|
|
45
|
+
$body,
|
|
46
|
+
$_COOKIE,
|
|
47
|
+
$_SERVER['SERVER_PROTOCOL'] ?? '1.1'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// 2. Traiter la requête avec le moteur.
|
|
51
|
+
$decision = $this->engine->processRequest($context);
|
|
52
|
+
|
|
53
|
+
// 3. Agir sur la décision.
|
|
54
|
+
if (isset($context->newCookieForResponse)) {
|
|
55
|
+
$cookie = $context->newCookieForResponse;
|
|
56
|
+
setcookie($cookie['name'], $cookie['value'], $cookie['options']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
switch ($decision['action']) {
|
|
60
|
+
case 'block':
|
|
61
|
+
case 'challenge':
|
|
62
|
+
http_response_code($decision['status'] ?? 403);
|
|
63
|
+
if (is_array($decision['body'])) {
|
|
64
|
+
header('Content-Type: application/json');
|
|
65
|
+
echo json_encode($decision['body']);
|
|
66
|
+
} else {
|
|
67
|
+
header('Content-Type: text/html; charset=utf-8');
|
|
68
|
+
echo $decision['body'];
|
|
69
|
+
}
|
|
70
|
+
exit(); // Termine le script.
|
|
71
|
+
|
|
72
|
+
case 'redirect':
|
|
73
|
+
if (isset($decision['cookie'])) {
|
|
74
|
+
setcookie($decision['cookie']['name'], $decision['cookie']['value'], $decision['cookie']['options']);
|
|
75
|
+
}
|
|
76
|
+
header('Location: ' . $decision['path'], true, 302);
|
|
77
|
+
exit(); // Termine le script.
|
|
78
|
+
|
|
79
|
+
case 'next':
|
|
80
|
+
default:
|
|
81
|
+
// La requête est autorisée, on retourne les informations du fingerprint.
|
|
82
|
+
return ['score' => $decision['score'], 'vector' => $decision['vector']];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles a request to the /metrics endpoint, applying authorization rules.
|
|
88
|
+
* If metrics are enabled and authorized, it outputs Prometheus formatted metrics and exits.
|
|
89
|
+
* Otherwise, it handles unauthorized access or returns a 404 if metrics are not enabled.
|
|
90
|
+
*
|
|
91
|
+
* @param RequestContext $context The current request context.
|
|
92
|
+
*/
|
|
93
|
+
public function handleMetricsRequest(RequestContext $context): void
|
|
94
|
+
{
|
|
95
|
+
// 2. Appliquer le callback d'autorisation personnalisé si défini.
|
|
96
|
+
$authorizationCallback = $this->securityConfig['metricsAuthorizationCallback'] ?? null;
|
|
97
|
+
if (is_callable($authorizationCallback)) {
|
|
98
|
+
$decision = call_user_func($authorizationCallback, $context);
|
|
99
|
+
|
|
100
|
+
if (is_bool($decision)) {
|
|
101
|
+
if (!$decision) {
|
|
102
|
+
http_response_code(403); // Forbidden
|
|
103
|
+
echo "Access to metrics denied.";
|
|
104
|
+
exit();
|
|
105
|
+
}
|
|
106
|
+
} elseif (is_array($decision) && isset($decision['action'])) {
|
|
107
|
+
switch ($decision['action']) {
|
|
108
|
+
case 'block':
|
|
109
|
+
http_response_code($decision['status'] ?? 403);
|
|
110
|
+
echo $decision['body'] ?? "Access denied.";
|
|
111
|
+
exit();
|
|
112
|
+
case 'redirect':
|
|
113
|
+
header('Location: ' . $decision['path'], true, $decision['status'] ?? 302);
|
|
114
|
+
exit();
|
|
115
|
+
case 'next':
|
|
116
|
+
// Autorisé, continuer pour servir les métriques
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
// Action inconnue, refuser par défaut
|
|
120
|
+
http_response_code(403);
|
|
121
|
+
echo "Invalid authorization decision.";
|
|
122
|
+
exit();
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
// Retour inattendu du callback, refuser par défaut
|
|
126
|
+
http_response_code(403);
|
|
127
|
+
echo "Invalid authorization callback response.";
|
|
128
|
+
exit();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 3. Si autorisé, servir les métriques.
|
|
133
|
+
header('Content-Type: text/plain; version=0.0.4; charset=utf-8');
|
|
134
|
+
echo MetricsManager::getPrometheusMetrics();
|
|
135
|
+
exit();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Returns Prometheus formatted metrics if enabled in the security configuration.
|
|
140
|
+
* @return string|null
|
|
141
|
+
*/
|
|
142
|
+
public function getPrometheusMetrics(): ?string
|
|
143
|
+
{
|
|
144
|
+
return MetricsManager::getPrometheusMetrics();
|
|
145
|
+
}
|
|
81
146
|
}
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint;
|
|
6
6
|
|
|
7
|
-
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
8
|
-
use Anonympins\Fingerprint\Store\StoreManager; // Correction de l'import
|
|
9
7
|
use Anonympins\Fingerprint\Challenge\ChallengeUtils;
|
|
8
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
9
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
10
10
|
use Anonympins\Fingerprint\Utils\BlockList;
|
|
11
11
|
use Anonympins\Fingerprint\Utils\Logger;
|
|
12
|
-
use Anonympins\Fingerprint\Utils\
|
|
12
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
13
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
14
|
+
|
|
15
|
+
// Correction de l'import
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Le moteur principal de la bibliothèque de fingerprinting.
|
|
@@ -525,6 +528,7 @@
|
|
|
525
528
|
// 1. Vérifier les listes blanches
|
|
526
529
|
if ($this->checkAllowlists($context)) {
|
|
527
530
|
return ['action' => 'next', 'score' => 0.0, 'vector' => ['whitelisted' => 100.0]];
|
|
531
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'whitelisted']);
|
|
528
532
|
}
|
|
529
533
|
|
|
530
534
|
// Initialiser le vecteur de suspicion
|
|
@@ -602,6 +606,7 @@
|
|
|
602
606
|
if ($isValid) {
|
|
603
607
|
$store->delete("secret:{$powNonce}");
|
|
604
608
|
$ticketTtl = $this->securityConfig['ticketMaxAge'] ?? 3600000;
|
|
609
|
+
MetricsManager::incrementCounter('challenges_solved_total');
|
|
605
610
|
$this->log('Challenge solution valid - issuing ticket', ['ticketMaxAge' => $ticketTtl]);
|
|
606
611
|
|
|
607
612
|
return [
|
|
@@ -618,6 +623,7 @@
|
|
|
618
623
|
}
|
|
619
624
|
}
|
|
620
625
|
// Si la solution est invalide ou le nonce est expiré, on pénalise fortement pour la suite.
|
|
626
|
+
MetricsManager::incrementCounter('challenges_failed_total');
|
|
621
627
|
$this->log('Challenge solution invalid or context expired', ['nonce' => $powNonce], 'warn');
|
|
622
628
|
$suspicionVector['honeypotScore'] = 100.0;
|
|
623
629
|
}
|
|
@@ -627,6 +633,7 @@
|
|
|
627
633
|
$powCookie = $context->cookies['pow_clearance'] ?? null;
|
|
628
634
|
if (ChallengeUtils::isTicketValid($context->clientIp, $powCookie)) {
|
|
629
635
|
$hasValidTicket = true;
|
|
636
|
+
MetricsManager::incrementCounter('tickets_valid_total');
|
|
630
637
|
// On ne retourne pas tout de suite pour permettre le re-challenge
|
|
631
638
|
// $this->log('Valid clearance ticket found');
|
|
632
639
|
// return ['action' => 'next', 'score' => 0.0, 'vector' => ['ticket_valid' => 100]];
|
|
@@ -660,6 +667,7 @@
|
|
|
660
667
|
// Mettre à jour les métriques du sous-réseau après le calcul du score final
|
|
661
668
|
if ($finalScore > ($thresholds['low'] ?? 20)) {
|
|
662
669
|
RequestUtils::updateSubnetMetrics($context, $deviceId, $finalScore);
|
|
670
|
+
MetricsManager::observeValue('suspicion_score', $finalScore, ['action' => 'high_score_subnet_update']);
|
|
663
671
|
}
|
|
664
672
|
|
|
665
673
|
// Logique pour challenger les nouveaux appareils (déplacée ici pour avoir le score final)
|
|
@@ -695,6 +703,7 @@
|
|
|
695
703
|
$blockThreshold = $thresholds['block'] ?? 95;
|
|
696
704
|
if ($finalScore >= $blockThreshold) {
|
|
697
705
|
if ($this->logger) {
|
|
706
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'blocked']);
|
|
698
707
|
$this->logger->log('info', 'request_blocked', ['deviceId' => $deviceId, 'score' => $finalScore, 'vector' => $suspicionVector]);
|
|
699
708
|
}
|
|
700
709
|
$decision = ['action' => 'block', 'status' => 403, 'body' => 'Forbidden', 'score' => $finalScore, 'vector' => $suspicionVector];
|
|
@@ -703,6 +712,7 @@
|
|
|
703
712
|
$decision['intendedAction'] = $decision['action'];
|
|
704
713
|
$decision['action'] = 'next';
|
|
705
714
|
unset($decision['status'], $decision['body']);
|
|
715
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'dry_run_block']);
|
|
706
716
|
}
|
|
707
717
|
$response = $decision;
|
|
708
718
|
} else {
|
|
@@ -715,6 +725,7 @@
|
|
|
715
725
|
$decision = ['action' => 'block', 'status' => 403, 'body' => 'Forbidden', 'score' => $finalScore, 'vector' => $suspicionVector];
|
|
716
726
|
// Apply dry run logic here as well
|
|
717
727
|
if ($this->dryRun) {
|
|
728
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'dry_run_block']);
|
|
718
729
|
$this->log("[Dry Run] Intended action: {$decision['action']}", ['score' => $decision['score']]);
|
|
719
730
|
$decision['intendedAction'] = $decision['action'];
|
|
720
731
|
$decision['action'] = 'next';
|
|
@@ -735,6 +746,7 @@
|
|
|
735
746
|
$decision = ['action' => 'challenge', 'score' => $finalScore, 'vector' => $suspicionVector, 'status' => 403];
|
|
736
747
|
|
|
737
748
|
if ($this->dryRun) {
|
|
749
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'dry_run_challenge']);
|
|
738
750
|
$this->log("[Dry Run] Intended action: {$decision['action']}", ['score' => $decision['score']]);
|
|
739
751
|
$decision['intendedAction'] = $decision['action'];
|
|
740
752
|
$decision['action'] = 'next';
|
|
@@ -742,6 +754,7 @@
|
|
|
742
754
|
return $decision;
|
|
743
755
|
}
|
|
744
756
|
|
|
757
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'challenged']);
|
|
745
758
|
$this->log('Suspicious request - selecting challenge type', ['finalScore' => $finalScore]);
|
|
746
759
|
|
|
747
760
|
$nonce = bin2hex(random_bytes(16));
|
|
@@ -853,12 +866,15 @@
|
|
|
853
866
|
$response = $decision;
|
|
854
867
|
} elseif ($hasValidTicket) {
|
|
855
868
|
// Si on arrive ici avec un ticket valide et un score bas, on autorise
|
|
869
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
856
870
|
$this->log('Valid clearance ticket found and score is low - allowing request');
|
|
857
871
|
$response = ['action' => 'next', 'score' => 0.0, 'vector' => ['ticket_valid' => 100], 'intendedAction' => 'next'];
|
|
858
872
|
} else {
|
|
859
873
|
// 6. Si le score est bas et qu'il n'y a pas de ticket, autoriser la requête
|
|
874
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
860
875
|
$this->log('Request passed - no challenge required', ['finalScore' => $finalScore]);
|
|
861
876
|
if ($this->logger) {
|
|
877
|
+
MetricsManager::observeValue('suspicion_score', $finalScore, ['action' => 'passed']);
|
|
862
878
|
$this->logger->log('info', 'request_passed', ['deviceId' => $deviceId, 'score' => $finalScore, 'vector' => $suspicionVector]);
|
|
863
879
|
}
|
|
864
880
|
$response = ['action' => 'next', 'score' => $finalScore, 'vector' => $suspicionVector, 'intendedAction' => 'next'];
|
|
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint;
|
|
6
6
|
|
|
7
|
-
use Anonympins\Fingerprint\Store\IStore;
|
|
8
7
|
use Anonympins\Fingerprint\Optimization\FunctionRegistry;
|
|
9
8
|
use Anonympins\Fingerprint\Optimization\ProblemInitializers;
|
|
9
|
+
use Anonympins\Fingerprint\Store\IStore;
|
|
10
10
|
|
|
11
11
|
class ProblemManager
|
|
12
12
|
{
|