@anonympins/fingerprint 0.4.5 → 0.4.6

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.
@@ -9,6 +9,98 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ function cyrb53(str, seed = 0) {
13
+ let h1 = 0xdeadbeef ^ seed,
14
+ h2 = 0x41c6ce57 ^ seed;
15
+ for (let i = 0, ch; i < str.length; i++) {
16
+ ch = str.charCodeAt(i);
17
+ h1 = Math.imul(h1 ^ ch, 2654435761);
18
+ h2 = Math.imul(h2 ^ ch, 1597334677);
19
+ }
20
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
21
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
22
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
23
+ }
24
+
25
+ function openDb() {
26
+ return new Promise((resolve, reject) => {
27
+ const request = indexedDB.open("pospace-db", 1);
28
+ request.onupgradeneeded = (e) => {
29
+ const db = e.target.result;
30
+ if (!db.objectStoreNames.contains("blocks")) {
31
+ db.createObjectStore("blocks");
32
+ }
33
+ };
34
+ request.onsuccess = (e) => resolve(e.target.result);
35
+ request.onerror = (e) => reject(e.target.error);
36
+ });
37
+ }
38
+
39
+ function generateBlock(seed, blockIndex, blockSize = 1024) {
40
+ const block = new Uint8Array(blockSize);
41
+ let h = cyrb53(seed + ":" + blockIndex);
42
+ for (let i = 0; i < blockSize; i++) {
43
+ h = Math.imul(h ^ i, 1597334677);
44
+ block[i] = h & 0xff;
45
+ }
46
+ return block;
47
+ }
48
+
49
+ export async function initializeSpace(seed, sizeMb) {
50
+ const db = await openDb();
51
+ const transaction = db.transaction("blocks", "readwrite");
52
+ const store = transaction.objectStore("blocks");
53
+ const numBlocks = sizeMb * 1024;
54
+ const metadataKey = "pospace-metadata";
55
+
56
+ const metaReq = store.get(metadataKey);
57
+ const meta = await new Promise((resolve) => {
58
+ metaReq.onsuccess = () => resolve(metaReq.result);
59
+ });
60
+ if (meta && meta.sizeMb === sizeMb && meta.seed === seed) {
61
+ return;
62
+ }
63
+
64
+ const CHUNK_SIZE = 1000;
65
+ for (let i = 0; i < numBlocks; i += CHUNK_SIZE) {
66
+ const end = Math.min(numBlocks, i + CHUNK_SIZE);
67
+ for (let j = i; j < end; j++) {
68
+ const block = generateBlock(seed, j);
69
+ store.put(block, j);
70
+ }
71
+ await new Promise(r => setTimeout(r, 0));
72
+ }
73
+ store.put({ sizeMb, seed }, metadataKey);
74
+ }
75
+
76
+ export async function solveSpaceChallenge(seed, queries, nonce, clientSecret) {
77
+ const db = await openDb();
78
+ const transaction = db.transaction("blocks", "readonly");
79
+ const store = transaction.objectStore("blocks");
80
+
81
+ let combined = new Uint8Array(queries.length * 1024);
82
+ for (let i = 0; i < queries.length; i++) {
83
+ const idx = queries[i];
84
+ const getReq = store.get(idx);
85
+ let block = await new Promise((resolve) => {
86
+ getReq.onsuccess = () => resolve(getReq.result);
87
+ });
88
+ if (!block) {
89
+ block = generateBlock(seed, idx);
90
+ }
91
+ combined.set(block, i * 1024);
92
+ }
93
+
94
+ const encoder = new TextEncoder();
95
+ const nonceBytes = encoder.encode(nonce + ":" + clientSecret);
96
+ const finalBlock = new Uint8Array(combined.length + nonceBytes.length);
97
+ finalBlock.set(combined);
98
+ finalBlock.set(nonceBytes, combined.length);
99
+
100
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
101
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
102
+ }
103
+
12
104
  /**
13
105
  * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
14
106
  * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
@@ -458,7 +550,7 @@ class ChallengeSolution {
458
550
  * @returns {Promise<ChallengeSolution>} Un objet `ChallengeSolution` encapsulant le résultat.
459
551
  */
460
552
  export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
461
- const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
553
+ const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask, queries, sizeMb } = challenge;
462
554
  let rawSolution = {};
463
555
 
464
556
  switch (type) {
@@ -515,6 +607,10 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
515
607
  rawSolution.work_result = workResult;
516
608
  rawSolution.problem_id = usefulWorkTask.problemId;
517
609
  break;
610
+ case 'pospace':
611
+ await initializeSpace(nonce + ":" + clientSecret, sizeMb || 100);
612
+ rawSolution.hash = await solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret);
613
+ break;
518
614
  default:
519
615
  throw new Error(`Unknown challenge type: ${type}`);
520
616
  }
@@ -66,6 +66,18 @@ describe('Fingerprint & PoW Security Suite', () => {
66
66
  expect(FingerprintBuilder.compare(fp1, ''), "Comparison with empty string should be 0").toBe(0);
67
67
  expect(FingerprintBuilder.compare(null, fp2), "Comparison with null should be 0").toBe(0);
68
68
  });
69
+
70
+ it('should calculate a high crossLayerInconsistencyScore when viewport width exceeds screen width', () => {
71
+ const context = {
72
+ headers: {
73
+ 'x-device-fingerprint': `scr:${cyrb53("1920x1080_24")}`,
74
+ 'sec-ch-viewport-width': '2560'
75
+ }
76
+ };
77
+
78
+ const { crossLayerInconsistencyScore } = __internal.getCrossLayerInconsistency(context);
79
+ expect(crossLayerInconsistencyScore).toBe(20);
80
+ });
69
81
  });
70
82
 
71
83
  describe('JA3 Fingerprinting', () => {
@@ -1635,6 +1647,53 @@ describe('Fingerprint & PoW Security Suite', () => {
1635
1647
  });
1636
1648
  });
1637
1649
 
1650
+ describe('TLS Session Resumption (Cookieless Identity)', () => {
1651
+ const inMemoryStore = {
1652
+ _map: new Map(),
1653
+ async get(key) { return this._map.get(key); },
1654
+ async set(key, value) { this._map.set(key, value); },
1655
+ async has(key) { return this._map.has(key); },
1656
+ async delete(key) { this._map.delete(key); },
1657
+ };
1658
+
1659
+ beforeEach(() => {
1660
+ inMemoryStore._map.clear();
1661
+ configureStore(inMemoryStore);
1662
+ });
1663
+
1664
+ it('should resume device ID using TLS Session ID even if cookies are cleared', async () => {
1665
+ const securityConfig = {
1666
+ weights: { historyScore: 1.0 },
1667
+ thresholds: { low: 20, block: 95 }
1668
+ };
1669
+ const engine = new FingerprintEngine(securityConfig);
1670
+
1671
+ const req1 = {
1672
+ clientIp: '1.2.3.4',
1673
+ path: '/',
1674
+ cookies: {},
1675
+ headers: { 'x-tls-session-id': 'session-xyz-123', 'user-agent': 'test-agent' },
1676
+ rawHeaders: [],
1677
+ httpVersion: '1.1'
1678
+ };
1679
+ const decision1 = await engine.processRequest(req1);
1680
+ const deviceId = decision1.newCookieForResponse.value;
1681
+ expect(deviceId).toBeDefined();
1682
+
1683
+ const req2 = {
1684
+ clientIp: '1.2.3.4',
1685
+ path: '/',
1686
+ cookies: {}, // Cookies cleared!
1687
+ headers: { 'x-tls-session-id': 'session-xyz-123', 'user-agent': 'test-agent' },
1688
+ rawHeaders: [],
1689
+ httpVersion: '1.1'
1690
+ };
1691
+
1692
+ const decision2 = await engine.processRequest(req2);
1693
+ expect(decision2.newCookieForResponse).toBeUndefined(); // Resumed successfully, no new cookie needed
1694
+ });
1695
+ });
1696
+
1638
1697
  describe('getTlsSpoofingScore', () => {
1639
1698
  let getTlsFingerprintMock; // Renamed to reflect it's the mock function
1640
1699
  let getTlsSpoofingScore;
@@ -77,7 +77,7 @@ class AutoTuner
77
77
 
78
78
  echo sprintf("[AutoTuning] Démarrage du cycle d'optimisation complet avec %d points de données assainis.\n", count($sanitizedData));
79
79
 
80
- $paretoFront = OptimizationOperators::solveFullSecurityTuning(['trafficData' => $sanitizedData]);
80
+ $paretoFront = OptimizationOperators::solveFullSecurityTuning(['trafficData' => $sanitizedData, 'currentConfig' => $this->securityConfig], []);
81
81
 
82
82
  if (empty($paretoFront)) {
83
83
  echo "[AutoTuning] L'optimisation n'a retourné aucune solution.\n";
@@ -5,6 +5,7 @@ declare(strict_types=1);
5
5
  namespace Anonympins\Fingerprint\Challenge;
6
6
 
7
7
  use Anonympins\Fingerprint\Store\StoreManager;
8
+ use Anonympins\Fingerprint\FingerprintBuilder;
8
9
  use Anonympins\Fingerprint\Utils\BigInt;
9
10
  use Anonympins\Fingerprint\Utils\RequestUtils;
10
11
 
@@ -23,6 +24,65 @@ class ChallengeUtils
23
24
  '/.git/config_{RANDOM}'
24
25
  ];
25
26
 
27
+ private static function imul(int $a, int $b): int
28
+ {
29
+ $ah = ($a >> 16) & 0xffff;
30
+ $al = $a & 0xffff;
31
+ $bh = ($b >> 16) & 0xffff;
32
+ $bl = $b & 0xffff;
33
+ $lo = $al * $bl;
34
+ $hi = (($lo >> 16) + ($al * $bh) + ($ah * $bl)) & 0xffff;
35
+ return (($hi << 16) | ($lo & 0xffff)) | 0;
36
+ }
37
+
38
+ private static function generateBlock(string $seed, int $blockIndex, int $blockSize = 1024): string
39
+ {
40
+ $block = str_repeat("\x00", $blockSize);
41
+ $h = FingerprintBuilder::cyrb53($seed . ":" . $blockIndex);
42
+
43
+ $h_int = (int)bcmod($h, '4294967296');
44
+ for ($i = 0; $i < $blockSize; $i++) {
45
+ $h_int = self::imul($h_int ^ $i, 1597334677);
46
+ $block[$i] = chr($h_int & 0xff);
47
+ }
48
+ return $block;
49
+ }
50
+
51
+ public static function generateSpaceChallenge(string $clientIp, string $nonce, float $suspicionFactor, string $originalUrl, array $securityConfig): array
52
+ {
53
+ $pospaceConfig = $securityConfig['pospace'] ?? [];
54
+ $sizeMb = $pospaceConfig['sizeMb'] ?? 100;
55
+ $numQueries = $pospaceConfig['numQueries'] ?? 10;
56
+
57
+ $queries = [];
58
+ $maxBlocks = $sizeMb * 1024;
59
+ while (count($queries) < $numQueries) {
60
+ $idx = random_int(0, $maxBlocks - 1);
61
+ if (!in_array($idx, $queries, true)) {
62
+ $queries[] = $idx;
63
+ }
64
+ }
65
+
66
+ return [
67
+ 'type' => 'pospace',
68
+ 'nonce' => $nonce,
69
+ 'sizeMb' => $sizeMb,
70
+ 'queries' => $queries,
71
+ 'path' => $originalUrl
72
+ ];
73
+ }
74
+
75
+ public static function verifySpacePoW(string $nonce, string $solution, array $queries, string $seed, string $clientSecret): bool
76
+ {
77
+ $combined = '';
78
+ foreach ($queries as $idx) {
79
+ $combined .= self::generateBlock($seed, (int)$idx);
80
+ }
81
+ $finalBlock = $combined . $nonce . ":" . $clientSecret;
82
+ $hash = hash('sha256', $finalBlock);
83
+ return hash_equals($hash, $solution);
84
+ }
85
+
26
86
  /**
27
87
  * Récupère la clé secrète pour les PoW depuis les variables d'environnement.
28
88
  */
@@ -401,6 +461,43 @@ class ChallengeUtils
401
461
  return file_get_contents($solverPath) ?: '';
402
462
  }
403
463
 
464
+ public static function generateSpaceChallengePage(array $challengeDetails, string $clientSecret, array $securityConfig): string
465
+ {
466
+ $nonce = $challengeDetails['nonce'];
467
+ $sizeMb = $challengeDetails['sizeMb'];
468
+ $queries = $challengeDetails['queries'];
469
+ $path = $challengeDetails['path'];
470
+
471
+ $solverCode = self::getPowSolverCode();
472
+ $queriesJson = json_encode($queries);
473
+
474
+ $challengeScript = <<<JS
475
+ async function solve() {
476
+ const nonce = "{$nonce}";
477
+ const path = "{$path}";
478
+ const clientSecret = "{$clientSecret}";
479
+ const queries = {$queriesJson};
480
+ const sizeMb = {$sizeMb};
481
+
482
+ document.getElementById('loader').innerText = '⚙️ Checking persistent local storage...';
483
+ await new Promise(r => setTimeout(r, 10));
484
+
485
+ try {
486
+ await window.initializeSpace(nonce + ":" + clientSecret, sizeMb);
487
+ document.getElementById('loader').innerText = '⚙️ Generating Proof of Space...';
488
+ const hash = await window.solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret);
489
+
490
+ window.location.href = path + "?pow_type=pospace&pow_nonce=" + nonce + "&pow_solution_space=" + hash;
491
+ } catch(e) {
492
+ document.getElementById('loader').innerText = "Error initializing local storage: " + e.message;
493
+ }
494
+ }
495
+ solve();
496
+ JS;
497
+
498
+ return "<html><head><title>Security Check</title></head><body style=\"font-family:sans-serif; text-align:center; padding-top:50px;\"><h1>Security Check (Level 2)</h1><p>We are verifying your storage allocation. This may take a few seconds on first load.</p><div id=\"loader\" style=\"margin:20px;\">⚙️ Initializing storage space...</div><script>{$solverCode}</script><script>{$challengeScript}</script></body></html>";
499
+ }
500
+
404
501
  /**
405
502
  * Génère le contenu HTML pour un challenge combiné CPU + Mémoire.
406
503
  * @param array $cpuChallengeDetails
@@ -225,6 +225,16 @@
225
225
  $deviceData = null;
226
226
  $newCookie = null;
227
227
 
228
+ // Cookieless Identity Tracking: Attempt to restore device ID using TLS session resume ID
229
+ $tlsSessionId = $context->tlsSessionId;
230
+ if (!$deviceId && $tlsSessionId) {
231
+ $resumedDeviceId = $store->get("tls-session:{$tlsSessionId}");
232
+ if ($resumedDeviceId) {
233
+ $deviceId = $resumedDeviceId;
234
+ $this->log('Identity resumed via TLS Session ID', ['deviceId' => $deviceId, 'tlsSessionId' => $tlsSessionId]);
235
+ }
236
+ }
237
+
228
238
  if ($deviceId) {
229
239
  $deviceData = $store->get("device:{$deviceId}");
230
240
  }
@@ -275,6 +285,11 @@
275
285
  }
276
286
  }
277
287
 
288
+ // Bind the current TLS session ID to the device ID
289
+ if ($deviceId && $tlsSessionId) {
290
+ $store->set("tls-session:{$tlsSessionId}", $deviceId, 3600); // 1h cache duration
291
+ }
292
+
278
293
  return [
279
294
  'deviceId' => $deviceId,
280
295
  'deviceData' => $deviceData,
@@ -515,6 +530,53 @@
515
530
  ];
516
531
  }
517
532
 
533
+
534
+ private function decodePolymorphicFingerprint(string $fpString, array $mapping): string
535
+ {
536
+ if (empty($mapping['keys'])) {
537
+ return $fpString;
538
+ }
539
+ $reverseKeys = array_flip($mapping['keys']);
540
+ $parts = explode('|', $fpString);
541
+ $mappedParts = [];
542
+ foreach ($parts as $part) {
543
+ $pair = explode(':', $part, 2);
544
+ if (count($pair) === 2) {
545
+ $origKey = $reverseKeys[$pair[0]] ?? $pair[0];
546
+ $mappedParts[] = "{$origKey}:{$pair[1]}";
547
+ } else {
548
+ $mappedParts[] = $part;
549
+ }
550
+ }
551
+ return implode('|', $mappedParts);
552
+ }
553
+ private function translatePolymorphicHeaders(RequestContext $context): void
554
+ {
555
+ $store = StoreManager::getStore();
556
+ $activeMappings = $store->get('active-polymorphic-mappings') ?: [];
557
+ if (!is_array($activeMappings)) {
558
+ return;
559
+ }
560
+
561
+ foreach ($activeMappings as $mapping) {
562
+ $devFpHeader = strtolower($mapping['headers']['x-device-fingerprint'] ?? '');
563
+ $behaviorHeader = strtolower($mapping['headers']['x-behavior-metrics'] ?? '');
564
+
565
+ if (!empty($devFpHeader) && isset($context->headers[$devFpHeader])) {
566
+ $context->headers['x-device-fingerprint'] = $context->headers[$devFpHeader];
567
+ if (!empty($behaviorHeader) && isset($context->headers[$behaviorHeader])) {
568
+ $context->headers['x-behavior-metrics'] = $context->headers[$behaviorHeader];
569
+ }
570
+
571
+ $clientFp = $context->headers['x-device-fingerprint'];
572
+ if ($clientFp && is_string($clientFp)) {
573
+ $context->headers['x-device-fingerprint'] = $this->decodePolymorphicFingerprint($clientFp, $mapping);
574
+ }
575
+ break;
576
+ }
577
+ }
578
+ }
579
+
518
580
  /**
519
581
  * Traite une requête entrante et retourne une décision.
520
582
  * @param RequestContext $context Le contexte de la requête.
@@ -522,8 +584,21 @@
522
584
  */
523
585
  public function processRequest(RequestContext $context): array
524
586
  {
587
+ $this->translatePolymorphicHeaders($context);
588
+
525
589
  // Initialiser le vecteur de suspicion pour éviter les erreurs de type.
526
590
  $suspicionVector = [];
591
+
592
+ $this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
593
+
594
+ // Parse GraphQL query if applicable
595
+ if ($context->path === '/graphql' && !empty($context->body)) {
596
+ $gqlInfo = RequestUtils::parseGraphQLQuery(is_array($context->body) ? $context->body : []);
597
+ if ($gqlInfo) {
598
+ $context->graphqlOperation = $gqlInfo;
599
+ }
600
+ }
601
+
527
602
 
528
603
  $this->log('Processing request', ['clientIp' => $context->clientIp, 'path' => $context->path]);
529
604
 
@@ -549,6 +624,7 @@
549
624
  $isChallengeSubmission = $powNonce && (
550
625
  isset($context->query['pow_solution']) ||
551
626
  isset($context->query['pow_solution_cpu']) ||
627
+ isset($context->query['pow_solution_space']) ||
552
628
  (isset($context->query['pow_type']) && $context->query['pow_type'] === 'useful_work_task')
553
629
  );
554
630
  if ($isChallengeSubmission) {
@@ -587,6 +663,28 @@
587
663
  $isValid = $isValid && $isMemValid;
588
664
  }
589
665
  }
666
+ } elseif ($powType === 'pospace') {
667
+ $powSolutionSpace = $context->query['pow_solution_space'] ?? null;
668
+ if ($powSolutionSpace && isset($challengeContext['queries'])) {
669
+ $isSpaceValid = ChallengeUtils::verifySpacePoW(
670
+ $powNonce,
671
+ $powSolutionSpace,
672
+ $challengeContext['queries'],
673
+ $powNonce . ":" . $challengeContext['clientSecret'],
674
+ $challengeContext['clientSecret']
675
+ );
676
+ $isValid = $isSpaceValid;
677
+ if ($isValid) {
678
+ $ticketTtl = $this->securityConfig['ticketMaxAge'] ?? 3600000;
679
+ $expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
680
+ $ticket = ChallengeUtils::generateStatelessTicket([
681
+ 'expiry' => $expiry,
682
+ 'originalIp' => $context->clientIp,
683
+ 'deviceId' => '',
684
+ 'deviceHash' => ''
685
+ ]);
686
+ }
687
+ }
590
688
  } elseif ($powType === 'useful_work_task') {
591
689
  $problemId = $context->query['pow_problem_id'] ?? null;
592
690
  $workResultJson = $context->query['pow_solution_work_result'] ?? null;
@@ -622,10 +720,6 @@
622
720
  $this->log('Challenge solution valid - issuing ticket', ['ticketMaxAge' => $ticketTtl]);
623
721
 
624
722
  return [
625
- // ... (le reste de la logique de redirection)
626
- 'action' => 'redirect',
627
- 'path' => RequestUtils::cleanUrlFromPowParams($challengeContext['originalPath'] ?? '/', $context->query),
628
- 'score' => 0.0,
629
723
  'action' => 'redirect',
630
724
  'path' => RequestUtils::cleanUrlFromPowParams($challengeContext['originalPath'] ?? '/', $context->query),
631
725
  'score' => 0.0,
@@ -791,7 +885,11 @@
791
885
 
792
886
  $nonce = bin2hex(random_bytes(16));
793
887
  $clientSecret = bin2hex(random_bytes(16));
794
-
888
+ $highThreshold = $thresholds['high'] ?? 75;
889
+
890
+ $suspicionFactor = ($finalScore - $lowThreshold) / (($thresholds['high'] ?? 75) - $lowThreshold);
891
+ $suspicionFactor = max(0, min(1.5, $suspicionFactor));
892
+
795
893
  // --- NOUVELLE LOGIQUE uPoW ---
796
894
  $shouldUseUsefulWork = ($this->securityConfig['enableUsefulWork'] ?? false) && (
797
895
  ($this->securityConfig['forceUsefulWork'] ?? false) || (random_int(0, 255) / 255) > 0.5
@@ -843,9 +941,39 @@
843
941
 
844
942
  // --- FIN DE LA LOGIQUE uPoW (le reste est le fallback) ---
845
943
 
846
- $suspicionFactor = ($finalScore - $lowThreshold) / (($thresholds['high'] ?? 75) - $lowThreshold);
847
- $suspicionFactor = max(0, min(1.5, $suspicionFactor));
848
-
944
+ if ($this->securityConfig['enableProofOfSpace'] ?? false) {
945
+ $spaceChallenge = ChallengeUtils::generateSpaceChallenge($context->clientIp, $nonce, $suspicionFactor, $context->path, $this->securityConfig);
946
+ $store->set("secret:{$nonce}", [
947
+ 'clientSecret' => $clientSecret,
948
+ 'suspicionScore' => $finalScore,
949
+ 'queries' => $spaceChallenge['queries'],
950
+ 'sizeMb' => $spaceChallenge['sizeMb'],
951
+ 'fingerprint' => RequestUtils::getCompositeDeviceHash($context),
952
+ 'originalPath' => $context->path,
953
+ ], $this->securityConfig['challengeTtl'] ?? 300);
954
+
955
+ if ($deviceData) {
956
+ $deviceData['lastChallengeNonce'] = $nonce;
957
+ $store->set("device:{$deviceId}", $deviceData); // @phpstan-ignore-line
958
+ }
959
+
960
+ if ($isApiRequest) {
961
+ $decision['body'] = [
962
+ 'challenge' => [
963
+ 'type' => 'pospace',
964
+ 'nonce' => $nonce,
965
+ 'clientSecret' => $clientSecret,
966
+ 'queries' => $spaceChallenge['queries'],
967
+ 'sizeMb' => $spaceChallenge['sizeMb'],
968
+ ]
969
+ ];
970
+ } else {
971
+ $page = ChallengeUtils::generateSpaceChallengePage($spaceChallenge, $clientSecret, $this->securityConfig);
972
+ $decision['body'] = $page;
973
+ }
974
+ return $decision;
975
+ }
976
+
849
977
  $cpuChallengeDetails = [
850
978
  'type' => 'cpu_target',
851
979
  'nonce' => $nonce,