@nawa-captcha/server 0.0.0-stage → 0.1.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 (258) hide show
  1. package/README.md +65 -2
  2. package/dist/binding.d.ts +22 -0
  3. package/dist/binding.d.ts.map +1 -0
  4. package/dist/binding.js +46 -0
  5. package/dist/binding.js.map +1 -0
  6. package/dist/catalog.d.ts +12 -0
  7. package/dist/catalog.d.ts.map +1 -0
  8. package/dist/catalog.js +42 -0
  9. package/dist/catalog.js.map +1 -0
  10. package/dist/crypto.d.ts +44 -0
  11. package/dist/crypto.d.ts.map +1 -0
  12. package/dist/crypto.js +108 -0
  13. package/dist/crypto.js.map +1 -0
  14. package/dist/engine.d.ts +245 -0
  15. package/dist/engine.d.ts.map +1 -0
  16. package/dist/engine.js +407 -0
  17. package/dist/engine.js.map +1 -0
  18. package/dist/express.d.ts +47 -0
  19. package/dist/express.d.ts.map +1 -0
  20. package/dist/express.js +107 -0
  21. package/dist/express.js.map +1 -0
  22. package/dist/heuristics.d.ts +33 -0
  23. package/dist/heuristics.d.ts.map +1 -0
  24. package/dist/heuristics.js +152 -0
  25. package/dist/heuristics.js.map +1 -0
  26. package/dist/http.d.ts +51 -0
  27. package/dist/http.d.ts.map +1 -0
  28. package/dist/http.js +70 -0
  29. package/dist/http.js.map +1 -0
  30. package/dist/i18n.d.ts +237 -0
  31. package/dist/i18n.d.ts.map +1 -0
  32. package/dist/i18n.js +92 -0
  33. package/dist/i18n.js.map +1 -0
  34. package/dist/index.d.ts +24 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +22 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/primitives.browser.d.ts +15 -0
  39. package/dist/primitives.browser.d.ts.map +1 -0
  40. package/dist/primitives.browser.js +328 -0
  41. package/dist/primitives.browser.js.map +1 -0
  42. package/dist/primitives.d.ts +17 -0
  43. package/dist/primitives.d.ts.map +1 -0
  44. package/dist/primitives.js +58 -0
  45. package/dist/primitives.js.map +1 -0
  46. package/dist/puzzles/accessible.d.ts +43 -0
  47. package/dist/puzzles/accessible.d.ts.map +1 -0
  48. package/dist/puzzles/accessible.js +317 -0
  49. package/dist/puzzles/accessible.js.map +1 -0
  50. package/dist/puzzles/audio.d.ts +42 -0
  51. package/dist/puzzles/audio.d.ts.map +1 -0
  52. package/dist/puzzles/audio.js +109 -0
  53. package/dist/puzzles/audio.js.map +1 -0
  54. package/dist/puzzles/biggest.d.ts +5 -0
  55. package/dist/puzzles/biggest.d.ts.map +1 -0
  56. package/dist/puzzles/biggest.js +40 -0
  57. package/dist/puzzles/biggest.js.map +1 -0
  58. package/dist/puzzles/clock.d.ts +8 -0
  59. package/dist/puzzles/clock.d.ts.map +1 -0
  60. package/dist/puzzles/clock.js +68 -0
  61. package/dist/puzzles/clock.js.map +1 -0
  62. package/dist/puzzles/color-name.d.ts +5 -0
  63. package/dist/puzzles/color-name.d.ts.map +1 -0
  64. package/dist/puzzles/color-name.js +73 -0
  65. package/dist/puzzles/color-name.js.map +1 -0
  66. package/dist/puzzles/common.d.ts +75 -0
  67. package/dist/puzzles/common.d.ts.map +1 -0
  68. package/dist/puzzles/common.js +268 -0
  69. package/dist/puzzles/common.js.map +1 -0
  70. package/dist/puzzles/count.d.ts +8 -0
  71. package/dist/puzzles/count.d.ts.map +1 -0
  72. package/dist/puzzles/count.js +38 -0
  73. package/dist/puzzles/count.js.map +1 -0
  74. package/dist/puzzles/dice.d.ts +8 -0
  75. package/dist/puzzles/dice.d.ts.map +1 -0
  76. package/dist/puzzles/dice.js +61 -0
  77. package/dist/puzzles/dice.js.map +1 -0
  78. package/dist/puzzles/find-shape.d.ts +8 -0
  79. package/dist/puzzles/find-shape.d.ts.map +1 -0
  80. package/dist/puzzles/find-shape.js +45 -0
  81. package/dist/puzzles/find-shape.js.map +1 -0
  82. package/dist/puzzles/grid.d.ts +44 -0
  83. package/dist/puzzles/grid.d.ts.map +1 -0
  84. package/dist/puzzles/grid.js +33 -0
  85. package/dist/puzzles/grid.js.map +1 -0
  86. package/dist/puzzles/hold.d.ts +24 -0
  87. package/dist/puzzles/hold.d.ts.map +1 -0
  88. package/dist/puzzles/hold.js +68 -0
  89. package/dist/puzzles/hold.js.map +1 -0
  90. package/dist/puzzles/icons-objects.d.ts +3 -0
  91. package/dist/puzzles/icons-objects.d.ts.map +1 -0
  92. package/dist/puzzles/icons-objects.js +5 -0
  93. package/dist/puzzles/icons-objects.js.map +1 -0
  94. package/dist/puzzles/icons-weather.d.ts +3 -0
  95. package/dist/puzzles/icons-weather.d.ts.map +1 -0
  96. package/dist/puzzles/icons-weather.js +5 -0
  97. package/dist/puzzles/icons-weather.js.map +1 -0
  98. package/dist/puzzles/icons.d.ts +14 -0
  99. package/dist/puzzles/icons.d.ts.map +1 -0
  100. package/dist/puzzles/icons.js +49 -0
  101. package/dist/puzzles/icons.js.map +1 -0
  102. package/dist/puzzles/index.d.ts +63 -0
  103. package/dist/puzzles/index.d.ts.map +1 -0
  104. package/dist/puzzles/index.js +93 -0
  105. package/dist/puzzles/index.js.map +1 -0
  106. package/dist/puzzles/jigsaw.d.ts +22 -0
  107. package/dist/puzzles/jigsaw.d.ts.map +1 -0
  108. package/dist/puzzles/jigsaw.js +38 -0
  109. package/dist/puzzles/jigsaw.js.map +1 -0
  110. package/dist/puzzles/math-compare.d.ts +8 -0
  111. package/dist/puzzles/math-compare.d.ts.map +1 -0
  112. package/dist/puzzles/math-compare.js +47 -0
  113. package/dist/puzzles/math-compare.js.map +1 -0
  114. package/dist/puzzles/math.d.ts +16 -0
  115. package/dist/puzzles/math.d.ts.map +1 -0
  116. package/dist/puzzles/math.js +106 -0
  117. package/dist/puzzles/math.js.map +1 -0
  118. package/dist/puzzles/maze.d.ts +18 -0
  119. package/dist/puzzles/maze.d.ts.map +1 -0
  120. package/dist/puzzles/maze.js +138 -0
  121. package/dist/puzzles/maze.js.map +1 -0
  122. package/dist/puzzles/odd-one-out.d.ts +5 -0
  123. package/dist/puzzles/odd-one-out.d.ts.map +1 -0
  124. package/dist/puzzles/odd-one-out.js +36 -0
  125. package/dist/puzzles/odd-one-out.js.map +1 -0
  126. package/dist/puzzles/odd-rotation.d.ts +8 -0
  127. package/dist/puzzles/odd-rotation.d.ts.map +1 -0
  128. package/dist/puzzles/odd-rotation.js +48 -0
  129. package/dist/puzzles/odd-rotation.js.map +1 -0
  130. package/dist/puzzles/order-numbers.d.ts +5 -0
  131. package/dist/puzzles/order-numbers.d.ts.map +1 -0
  132. package/dist/puzzles/order-numbers.js +32 -0
  133. package/dist/puzzles/order-numbers.js.map +1 -0
  134. package/dist/puzzles/order-size.d.ts +5 -0
  135. package/dist/puzzles/order-size.d.ts.map +1 -0
  136. package/dist/puzzles/order-size.js +30 -0
  137. package/dist/puzzles/order-size.js.map +1 -0
  138. package/dist/puzzles/order-time.d.ts +5 -0
  139. package/dist/puzzles/order-time.d.ts.map +1 -0
  140. package/dist/puzzles/order-time.js +27 -0
  141. package/dist/puzzles/order-time.js.map +1 -0
  142. package/dist/puzzles/order.d.ts +30 -0
  143. package/dist/puzzles/order.d.ts.map +1 -0
  144. package/dist/puzzles/order.js +61 -0
  145. package/dist/puzzles/order.js.map +1 -0
  146. package/dist/puzzles/pairs.d.ts +15 -0
  147. package/dist/puzzles/pairs.d.ts.map +1 -0
  148. package/dist/puzzles/pairs.js +63 -0
  149. package/dist/puzzles/pairs.js.map +1 -0
  150. package/dist/puzzles/pattern.d.ts +8 -0
  151. package/dist/puzzles/pattern.d.ts.map +1 -0
  152. package/dist/puzzles/pattern.js +68 -0
  153. package/dist/puzzles/pattern.js.map +1 -0
  154. package/dist/puzzles/pow.d.ts +23 -0
  155. package/dist/puzzles/pow.d.ts.map +1 -0
  156. package/dist/puzzles/pow.js +52 -0
  157. package/dist/puzzles/pow.js.map +1 -0
  158. package/dist/puzzles/rotate-match.d.ts +8 -0
  159. package/dist/puzzles/rotate-match.d.ts.map +1 -0
  160. package/dist/puzzles/rotate-match.js +34 -0
  161. package/dist/puzzles/rotate-match.js.map +1 -0
  162. package/dist/puzzles/rotate-object.d.ts +5 -0
  163. package/dist/puzzles/rotate-object.d.ts.map +1 -0
  164. package/dist/puzzles/rotate-object.js +33 -0
  165. package/dist/puzzles/rotate-object.js.map +1 -0
  166. package/dist/puzzles/rotate.d.ts +24 -0
  167. package/dist/puzzles/rotate.d.ts.map +1 -0
  168. package/dist/puzzles/rotate.js +40 -0
  169. package/dist/puzzles/rotate.js.map +1 -0
  170. package/dist/puzzles/shadow-drag.d.ts +20 -0
  171. package/dist/puzzles/shadow-drag.d.ts.map +1 -0
  172. package/dist/puzzles/shadow-drag.js +64 -0
  173. package/dist/puzzles/shadow-drag.js.map +1 -0
  174. package/dist/puzzles/shadow-pick.d.ts +5 -0
  175. package/dist/puzzles/shadow-pick.d.ts.map +1 -0
  176. package/dist/puzzles/shadow-pick.js +38 -0
  177. package/dist/puzzles/shadow-pick.js.map +1 -0
  178. package/dist/puzzles/slide-unlock.d.ts +11 -0
  179. package/dist/puzzles/slide-unlock.d.ts.map +1 -0
  180. package/dist/puzzles/slide-unlock.js +79 -0
  181. package/dist/puzzles/slide-unlock.js.map +1 -0
  182. package/dist/puzzles/slider.d.ts +23 -0
  183. package/dist/puzzles/slider.d.ts.map +1 -0
  184. package/dist/puzzles/slider.js +74 -0
  185. package/dist/puzzles/slider.js.map +1 -0
  186. package/dist/puzzles/spell.d.ts +8 -0
  187. package/dist/puzzles/spell.d.ts.map +1 -0
  188. package/dist/puzzles/spell.js +41 -0
  189. package/dist/puzzles/spell.js.map +1 -0
  190. package/dist/puzzles/story.d.ts +5 -0
  191. package/dist/puzzles/story.d.ts.map +1 -0
  192. package/dist/puzzles/story.js +101 -0
  193. package/dist/puzzles/story.js.map +1 -0
  194. package/dist/puzzles/symmetry.d.ts +8 -0
  195. package/dist/puzzles/symmetry.d.ts.map +1 -0
  196. package/dist/puzzles/symmetry.js +49 -0
  197. package/dist/puzzles/symmetry.js.map +1 -0
  198. package/dist/puzzles/tap-order.d.ts +20 -0
  199. package/dist/puzzles/tap-order.d.ts.map +1 -0
  200. package/dist/puzzles/tap-order.js +37 -0
  201. package/dist/puzzles/tap-order.js.map +1 -0
  202. package/dist/puzzles/trace-letters.d.ts +5 -0
  203. package/dist/puzzles/trace-letters.d.ts.map +1 -0
  204. package/dist/puzzles/trace-letters.js +19 -0
  205. package/dist/puzzles/trace-letters.js.map +1 -0
  206. package/dist/puzzles/trace.d.ts +32 -0
  207. package/dist/puzzles/trace.d.ts.map +1 -0
  208. package/dist/puzzles/trace.js +81 -0
  209. package/dist/puzzles/trace.js.map +1 -0
  210. package/dist/render/audio.d.ts +53 -0
  211. package/dist/render/audio.d.ts.map +1 -0
  212. package/dist/render/audio.js +236 -0
  213. package/dist/render/audio.js.map +1 -0
  214. package/dist/render/clock.d.ts +6 -0
  215. package/dist/render/clock.d.ts.map +1 -0
  216. package/dist/render/clock.js +23 -0
  217. package/dist/render/clock.js.map +1 -0
  218. package/dist/render/glyphs.d.ts +18 -0
  219. package/dist/render/glyphs.d.ts.map +1 -0
  220. package/dist/render/glyphs.js +70 -0
  221. package/dist/render/glyphs.js.map +1 -0
  222. package/dist/render/pictograms.d.ts +60 -0
  223. package/dist/render/pictograms.d.ts.map +1 -0
  224. package/dist/render/pictograms.js +167 -0
  225. package/dist/render/pictograms.js.map +1 -0
  226. package/dist/render/png.d.ts +11 -0
  227. package/dist/render/png.d.ts.map +1 -0
  228. package/dist/render/png.js +107 -0
  229. package/dist/render/png.js.map +1 -0
  230. package/dist/render/raster.d.ts +61 -0
  231. package/dist/render/raster.d.ts.map +1 -0
  232. package/dist/render/raster.js +389 -0
  233. package/dist/render/raster.js.map +1 -0
  234. package/dist/render/shapes.d.ts +25 -0
  235. package/dist/render/shapes.d.ts.map +1 -0
  236. package/dist/render/shapes.js +157 -0
  237. package/dist/render/shapes.js.map +1 -0
  238. package/dist/rng.d.ts +25 -0
  239. package/dist/rng.d.ts.map +1 -0
  240. package/dist/rng.js +93 -0
  241. package/dist/rng.js.map +1 -0
  242. package/dist/selection.d.ts +54 -0
  243. package/dist/selection.d.ts.map +1 -0
  244. package/dist/selection.js +85 -0
  245. package/dist/selection.js.map +1 -0
  246. package/dist/store.d.ts +66 -0
  247. package/dist/store.d.ts.map +1 -0
  248. package/dist/store.js +100 -0
  249. package/dist/store.js.map +1 -0
  250. package/dist/tokens.d.ts +70 -0
  251. package/dist/tokens.d.ts.map +1 -0
  252. package/dist/tokens.js +103 -0
  253. package/dist/tokens.js.map +1 -0
  254. package/dist/types.d.ts +62 -0
  255. package/dist/types.d.ts.map +1 -0
  256. package/dist/types.js +61 -0
  257. package/dist/types.js.map +1 -0
  258. package/package.json +68 -4
package/dist/rng.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Source of randomness for puzzle generation. Production always uses {@link cryptoRng} (CSPRNG).
3
+ * {@link seededRng} exists so tests can assert that generation is deterministic for a seed; it is
4
+ * never used by the engine.
5
+ */
6
+ export interface Rng {
7
+ /** Uniform float in [0, 1). */
8
+ next(): number;
9
+ /** Uniform integer in [min, maxExclusive); fractional bounds are rounded inward. */
10
+ int(min: number, maxExclusive: number): number;
11
+ /** Uniform float in [min, max). */
12
+ float(min?: number, max?: number): number;
13
+ pick<T>(items: readonly T[]): T;
14
+ shuffle<T>(items: T[]): T[];
15
+ sample<T>(items: readonly T[], count: number): T[];
16
+ bytes(n: number): Uint8Array;
17
+ /** base64url id with `bytes` bytes of entropy. */
18
+ id(bytes?: number): string;
19
+ }
20
+ export declare function bytesToB64url(bytes: Uint8Array): string;
21
+ /** CSPRNG-backed randomness (node:crypto or crypto.getRandomValues). */
22
+ export declare const cryptoRng: Rng;
23
+ /** Deterministic sfc32 generator seeded from SHA-256(seed). For tests and tooling only. */
24
+ export declare function seededRng(seed: string | number): Rng;
25
+ //# sourceMappingURL=rng.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng.d.ts","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,GAAG;IAClB,+BAA+B;IAC/B,IAAI,IAAI,MAAM,CAAC;IACf,oFAAoF;IACpF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/C,mCAAmC;IACnC,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;IAChC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC5B,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IACnD,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,kDAAkD;IAClD,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAID,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAavD;AAuCD,wEAAwE;AACxE,eAAO,MAAM,SAAS,EAAE,GAQvB,CAAC;AAEF,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,CA2BpD"}
package/dist/rng.js ADDED
@@ -0,0 +1,93 @@
1
+ import { randomBytes, randomInt, sha256 } from './primitives.js';
2
+ const B64URL = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
3
+ export function bytesToB64url(bytes) {
4
+ let out = '';
5
+ let i = 0;
6
+ for (; i + 2 < bytes.length; i += 3) {
7
+ const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
8
+ out += B64URL[n >> 18] + B64URL[(n >> 12) & 63] + B64URL[(n >> 6) & 63] + B64URL[n & 63];
9
+ }
10
+ if (i < bytes.length) {
11
+ const n = (bytes[i] << 16) | ((bytes[i + 1] ?? 0) << 8);
12
+ out += B64URL[n >> 18] + B64URL[(n >> 12) & 63];
13
+ if (i + 1 < bytes.length)
14
+ out += B64URL[(n >> 6) & 63];
15
+ }
16
+ return out;
17
+ }
18
+ function build(next, bytes, intImpl) {
19
+ const rng = {
20
+ next,
21
+ int(min, maxExclusive) {
22
+ const lo = Math.ceil(min);
23
+ const hi = Math.floor(maxExclusive);
24
+ if (hi <= lo)
25
+ return lo;
26
+ return intImpl ? intImpl(lo, hi) : lo + Math.floor(next() * (hi - lo));
27
+ },
28
+ float(min = 0, max = 1) {
29
+ return min + next() * (max - min);
30
+ },
31
+ pick(items) {
32
+ const item = items[rng.int(0, items.length)];
33
+ if (item === undefined)
34
+ throw new Error('nawa-captcha: pick from empty list');
35
+ return item;
36
+ },
37
+ shuffle(items) {
38
+ for (let i = items.length - 1; i > 0; i -= 1) {
39
+ const j = rng.int(0, i + 1);
40
+ const a = items[i];
41
+ items[i] = items[j];
42
+ items[j] = a;
43
+ }
44
+ return items;
45
+ },
46
+ sample(items, count) {
47
+ return rng.shuffle([...items]).slice(0, count);
48
+ },
49
+ bytes,
50
+ id(n = 16) {
51
+ return bytesToB64url(bytes(n));
52
+ },
53
+ };
54
+ return rng;
55
+ }
56
+ /** CSPRNG-backed randomness (node:crypto or crypto.getRandomValues). */
57
+ export const cryptoRng = build(() => {
58
+ const b = randomBytes(6);
59
+ // 48 bits of entropy is plenty for geometry jitter.
60
+ return ((b[0] * 2 ** 40) + (b[1] * 2 ** 32) + (((b[2] << 24) | (b[3] << 16) | (b[4] << 8) | b[5]) >>> 0)) / 2 ** 48;
61
+ }, randomBytes, (lo, hi) => randomInt(lo, hi));
62
+ /** Deterministic sfc32 generator seeded from SHA-256(seed). For tests and tooling only. */
63
+ export function seededRng(seed) {
64
+ const h = sha256(new TextEncoder().encode(`nawa-guard-seed:${seed}`));
65
+ const dv = new DataView(h.buffer, h.byteOffset, h.byteLength);
66
+ let a = dv.getUint32(0);
67
+ let b = dv.getUint32(4);
68
+ let c = dv.getUint32(8);
69
+ let d = dv.getUint32(12);
70
+ const next = () => {
71
+ a >>>= 0;
72
+ b >>>= 0;
73
+ c >>>= 0;
74
+ d >>>= 0;
75
+ let t = (a + b) | 0;
76
+ a = b ^ (b >>> 9);
77
+ b = (c + (c << 3)) | 0;
78
+ c = (c << 21) | (c >>> 11);
79
+ d = (d + 1) | 0;
80
+ t = (t + d) | 0;
81
+ c = (c + t) | 0;
82
+ return (t >>> 0) / 4294967296;
83
+ };
84
+ for (let i = 0; i < 12; i += 1)
85
+ next();
86
+ return build(next, (n) => {
87
+ const out = new Uint8Array(n);
88
+ for (let i = 0; i < n; i += 1)
89
+ out[i] = Math.floor(next() * 256);
90
+ return out;
91
+ });
92
+ }
93
+ //# sourceMappingURL=rng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng.js","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAsBjE,MAAM,MAAM,GAAG,kEAAkE,CAAC;AAElF,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QACnE,GAAG,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAE,GAAG,MAAM,CAAC,CAAC,GAAG,EAAE,CAAE,CAAC;IAC/F,CAAC;IACD,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACzD,GAAG,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE,CAAE,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,CAAE,CAAC;QAClD,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM;YAAE,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAE,CAAC;IAC1D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,KAAK,CAAC,IAAkB,EAAE,KAAgC,EAAE,OAA4C;IAC/G,MAAM,GAAG,GAAQ;QACf,IAAI;QACJ,GAAG,CAAC,GAAG,EAAE,YAAY;YACnB,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YACpC,IAAI,EAAE,IAAI,EAAE;gBAAE,OAAO,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACzE,CAAC;QACD,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC;YACpB,OAAO,GAAG,GAAG,IAAI,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,KAAK;YACR,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7C,IAAI,IAAI,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YAC9E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,KAAK;YACX,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7C,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5B,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;gBACpB,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;gBACrB,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACf,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,CAAC,KAAK,EAAE,KAAK;YACjB,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACjD,CAAC;QACD,KAAK;QACL,EAAE,CAAC,CAAC,GAAG,EAAE;YACP,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;KACF,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,SAAS,GAAQ,KAAK,CACjC,GAAG,EAAE;IACH,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IACzB,oDAAoD;IACpD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;AAC5H,CAAC,EACD,WAAW,EACX,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,CAAC,CAC9B,CAAC;AAEF,2FAA2F;AAC3F,MAAM,UAAU,SAAS,CAAC,IAAqB;IAC7C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,GAAW,EAAE;QACxB,CAAC,MAAM,CAAC,CAAC;QACT,CAAC,MAAM,CAAC,CAAC;QACT,CAAC,MAAM,CAAC,CAAC;QACT,CAAC,MAAM,CAAC,CAAC;QACT,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAClB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC3B,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC;IAChC,CAAC,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC;QAAE,IAAI,EAAE,CAAC;IACvC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;QACvB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC;QACjE,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { Rng } from './rng.js';
2
+ import { type ChallengeType } from './types.js';
3
+ /**
4
+ * How the server picks a challenge type for a request:
5
+ * - `adaptive` (default): the difficulty ladder (`ladder[level]`), limited to the allowed types;
6
+ * failures from the same client move it up the ladder.
7
+ * - `random`: a weighted random pick from the allowed types.
8
+ * - `sequence`: the allowed types in turn, per client and action (a rotation).
9
+ */
10
+ export type SelectionMode = 'random' | 'sequence' | 'adaptive';
11
+ /** Per-action override. Any field left out falls back to the global option. */
12
+ export interface ActionPolicy {
13
+ /** Allow-list for this action (replaces the global `types`). */
14
+ readonly types?: readonly ChallengeType[];
15
+ readonly weights?: Partial<Record<ChallengeType, number>>;
16
+ readonly mode?: SelectionMode;
17
+ /** Number of different puzzles to solve in a row before a pass token is issued (1-3). */
18
+ readonly steps?: number;
19
+ readonly ladder?: readonly (readonly ChallengeType[])[];
20
+ /** Base proof-of-work difficulty (bits) for `pow` and the accessible PoW tax on this action. */
21
+ readonly powBits?: number;
22
+ }
23
+ export interface ResolvedPolicy {
24
+ readonly types: readonly ChallengeType[];
25
+ readonly weights: Partial<Record<ChallengeType, number>>;
26
+ readonly mode: SelectionMode;
27
+ readonly steps: number;
28
+ readonly ladder: readonly (readonly ChallengeType[])[];
29
+ readonly powBits: number;
30
+ }
31
+ export declare const DEFAULT_LADDER: readonly (readonly ChallengeType[])[];
32
+ /** Types used by `random`/`sequence` when no allow-list is configured (everything visual). */
33
+ export declare const DEFAULT_POOL: readonly ChallengeType[];
34
+ export declare const MAX_STEPS = 3;
35
+ export declare function isChallengeType(v: unknown): v is ChallengeType;
36
+ /** Validates a configured type list; unknown names are a configuration error, not silently dropped. */
37
+ export declare function checkTypes(list: readonly unknown[] | undefined, where: string): ChallengeType[] | undefined;
38
+ export declare function checkSteps(steps: number | undefined, where: string): number | undefined;
39
+ /**
40
+ * Parses the browser's optional narrowing hint (`types: ['slider', 'rotate']` or "slider,rotate").
41
+ * Unknown names are ignored; the result is only ever used to NARROW the server's allow-list.
42
+ */
43
+ export declare function parseClientTypes(value: unknown): ChallengeType[] | undefined;
44
+ /** The pool a challenge is drawn from: allow-list ∩ client narrowing (ignored if it leaves nothing). */
45
+ export declare function effectivePool(policy: ResolvedPolicy, clientTypes: readonly ChallengeType[] | undefined): ChallengeType[];
46
+ export declare function weightedPick(rng: Rng, pool: readonly ChallengeType[], weights: Partial<Record<ChallengeType, number>>): ChallengeType;
47
+ /** Random pool without the text/audio alternatives (chosen by the person, never drawn at random). */
48
+ export declare function drawable(pool: readonly ChallengeType[]): ChallengeType[];
49
+ /**
50
+ * Adaptive pick: the ladder rung for `level` limited to the pool. If that rung has nothing allowed,
51
+ * the nearest lower rung is used, then higher ones, then the whole pool.
52
+ */
53
+ export declare function ladderPool(ladder: readonly (readonly ChallengeType[])[], level: number, pool: readonly ChallengeType[]): ChallengeType[];
54
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;AAE/D,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;IAC9B,yFAAyF;IACzF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,aAAa,EAAE,CAAC,EAAE,CAAC;IACxD,gGAAgG;IAChG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,aAAa,EAAE,CAAC,EAAE,CAAC;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,EAAE,SAAS,CAAC,SAAS,aAAa,EAAE,CAAC,EAI/D,CAAC;AAEF,8FAA8F;AAC9F,eAAO,MAAM,YAAY,EAAE,SAAS,aAAa,EAAsF,CAAC;AAExI,eAAO,MAAM,SAAS,IAAI,CAAC;AAE3B,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,aAAa,CAE9D;AAED,uGAAuG;AACvG,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,GAAG,SAAS,CAM3G;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,SAAS,CAM5E;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,GAAG,aAAa,EAAE,CAMxH;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,aAAa,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG,aAAa,CAUrI;AAED,qGAAqG;AACrG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAGxE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,aAAa,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAQxI"}
@@ -0,0 +1,85 @@
1
+ import { CHALLENGE_TYPES } from './types.js';
2
+ export const DEFAULT_LADDER = [
3
+ ['slider', 'rotate'],
4
+ ['icons', 'order', 'trace', 'slider', 'rotate-object', 'shadow-drag', 'jigsaw', 'pairs', 'find-shape', 'tap-order', 'icons-objects'],
5
+ ['icons', 'trace', 'math', 'order', 'maze', 'spell', 'symmetry', 'rotate-match', 'story', 'order-time', 'color-name', 'trace-letters'],
6
+ ];
7
+ /** Types used by `random`/`sequence` when no allow-list is configured (everything visual). */
8
+ export const DEFAULT_POOL = CHALLENGE_TYPES.filter((t) => t !== 'pow' && t !== 'accessible' && t !== 'audio');
9
+ export const MAX_STEPS = 3;
10
+ export function isChallengeType(v) {
11
+ return typeof v === 'string' && CHALLENGE_TYPES.includes(v);
12
+ }
13
+ /** Validates a configured type list; unknown names are a configuration error, not silently dropped. */
14
+ export function checkTypes(list, where) {
15
+ if (list === undefined)
16
+ return undefined;
17
+ if (!Array.isArray(list))
18
+ throw new Error(`nawa-captcha: ${where} must be an array of challenge types`);
19
+ const bad = list.filter((t) => !isChallengeType(t));
20
+ if (bad.length)
21
+ throw new Error(`nawa-captcha: unknown challenge type(s) in ${where}: ${bad.join(', ')}. Known: ${CHALLENGE_TYPES.join(', ')}`);
22
+ return [...new Set(list)];
23
+ }
24
+ export function checkSteps(steps, where) {
25
+ if (steps === undefined)
26
+ return undefined;
27
+ if (!Number.isInteger(steps) || steps < 1 || steps > MAX_STEPS)
28
+ throw new Error(`nawa-captcha: ${where} must be an integer from 1 to ${MAX_STEPS}`);
29
+ return steps;
30
+ }
31
+ /**
32
+ * Parses the browser's optional narrowing hint (`types: ['slider', 'rotate']` or "slider,rotate").
33
+ * Unknown names are ignored; the result is only ever used to NARROW the server's allow-list.
34
+ */
35
+ export function parseClientTypes(value) {
36
+ if (value === undefined || value === null || value === '')
37
+ return undefined;
38
+ const raw = typeof value === 'string' ? value.split(',') : Array.isArray(value) ? value : undefined;
39
+ if (!raw || raw.length > CHALLENGE_TYPES.length)
40
+ return undefined;
41
+ const out = raw.map((t) => (typeof t === 'string' ? t.trim() : t)).filter(isChallengeType);
42
+ return out.length ? [...new Set(out)] : undefined;
43
+ }
44
+ /** The pool a challenge is drawn from: allow-list ∩ client narrowing (ignored if it leaves nothing). */
45
+ export function effectivePool(policy, clientTypes) {
46
+ const usable = policy.types.filter((t) => (policy.weights[t] ?? 1) > 0);
47
+ const base = usable.length ? usable : [...policy.types];
48
+ if (!clientTypes?.length)
49
+ return base;
50
+ const narrowed = base.filter((t) => clientTypes.includes(t));
51
+ return narrowed.length ? narrowed : base;
52
+ }
53
+ export function weightedPick(rng, pool, weights) {
54
+ const ws = pool.map((t) => Math.max(0, weights[t] ?? 1));
55
+ const total = ws.reduce((a, b) => a + b, 0);
56
+ if (!(total > 0))
57
+ return rng.pick(pool);
58
+ let x = rng.next() * total;
59
+ for (let i = 0; i < pool.length; i += 1) {
60
+ x -= ws[i];
61
+ if (x < 0)
62
+ return pool[i];
63
+ }
64
+ return pool[pool.length - 1];
65
+ }
66
+ /** Random pool without the text/audio alternatives (chosen by the person, never drawn at random). */
67
+ export function drawable(pool) {
68
+ const visual = pool.filter((t) => t !== 'accessible' && t !== 'audio');
69
+ return visual.length ? visual : [...pool];
70
+ }
71
+ /**
72
+ * Adaptive pick: the ladder rung for `level` limited to the pool. If that rung has nothing allowed,
73
+ * the nearest lower rung is used, then higher ones, then the whole pool.
74
+ */
75
+ export function ladderPool(ladder, level, pool) {
76
+ const top = Math.min(level, ladder.length - 1);
77
+ const order = [...Array.from({ length: top + 1 }, (_, i) => top - i), ...Array.from({ length: ladder.length - top - 1 }, (_, i) => top + 1 + i)];
78
+ for (const rung of order) {
79
+ const hit = (ladder[rung] ?? []).filter((t) => pool.includes(t));
80
+ if (hit.length)
81
+ return hit;
82
+ }
83
+ return [...pool];
84
+ }
85
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Storage used for one-time redemption (challenge ids, pass-token ids), attempt counters, rate
3
+ * limits and adaptive difficulty. Every operation must be atomic in a multi-instance deployment,
4
+ * which is why the interface is two primitives rather than get/set.
5
+ */
6
+ export interface NawaCaptchaStore {
7
+ /** Sets `key` only if absent. Resolves true when this call created it (i.e. first redemption). */
8
+ setIfAbsent(key: string, ttlMs: number): Promise<boolean>;
9
+ /** Increments a counter (creating it with `ttlMs` if absent) and resolves the new value. */
10
+ increment(key: string, ttlMs: number): Promise<number>;
11
+ /** Current counter value, or 0. */
12
+ count(key: string): Promise<number>;
13
+ delete(key: string): Promise<void>;
14
+ }
15
+ export interface MemoryStoreOptions {
16
+ /** Upper bound on live entries; least-recently-used entries are evicted beyond it. */
17
+ readonly maxEntries?: number;
18
+ readonly now?: () => number;
19
+ }
20
+ /**
21
+ * Single-process LRU store with TTLs. Fine for one Node instance and for tests. Behind a load
22
+ * balancer use Redis (or any shared atomic store), otherwise a token redeemed on instance A
23
+ * could be replayed on instance B.
24
+ */
25
+ export declare class MemoryStore implements NawaCaptchaStore {
26
+ private readonly map;
27
+ private readonly max;
28
+ private readonly now;
29
+ constructor(options?: MemoryStoreOptions);
30
+ get size(): number;
31
+ private live;
32
+ private put;
33
+ private evict;
34
+ setIfAbsent(key: string, ttlMs: number): Promise<boolean>;
35
+ increment(key: string, ttlMs: number): Promise<number>;
36
+ count(key: string): Promise<number>;
37
+ delete(key: string): Promise<void>;
38
+ }
39
+ /** The subset of the ioredis client used by {@link ioredisStore}. */
40
+ export interface IoRedisLike {
41
+ set(key: string, value: string, px: 'PX', ttl: number, nx: 'NX'): Promise<'OK' | null>;
42
+ eval(script: string, numKeys: number, ...args: Array<string | number>): Promise<unknown>;
43
+ get(key: string): Promise<string | null>;
44
+ del(key: string): Promise<number>;
45
+ }
46
+ /** The subset of the node-redis v4+ client used by {@link nodeRedisStore}. */
47
+ export interface NodeRedisLike {
48
+ set(key: string, value: string, options: {
49
+ PX: number;
50
+ NX: true;
51
+ }): Promise<string | null>;
52
+ eval(script: string, options: {
53
+ keys: string[];
54
+ arguments: string[];
55
+ }): Promise<unknown>;
56
+ get(key: string): Promise<string | null>;
57
+ del(key: string): Promise<number>;
58
+ }
59
+ /**
60
+ * Redis adapter for ioredis: `ioredisStore(new Redis(url))`. SET NX PX gives atomic one-time
61
+ * redemption; INCR+PEXPIRE runs as a Lua script so a counter can never be left without a TTL.
62
+ */
63
+ export declare function ioredisStore(client: IoRedisLike, prefix?: string): NawaCaptchaStore;
64
+ /** Redis adapter for node-redis v4+: `nodeRedisStore(createClient({ url }))` (after `connect()`). */
65
+ export declare function nodeRedisStore(client: NodeRedisLike, prefix?: string): NawaCaptchaStore;
66
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/dist/store.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Single-process LRU store with TTLs. Fine for one Node instance and for tests. Behind a load
3
+ * balancer use Redis (or any shared atomic store), otherwise a token redeemed on instance A
4
+ * could be replayed on instance B.
5
+ */
6
+ export class MemoryStore {
7
+ map = new Map();
8
+ max;
9
+ now;
10
+ constructor(options = {}) {
11
+ this.max = options.maxEntries ?? 100_000;
12
+ this.now = options.now ?? Date.now;
13
+ }
14
+ get size() {
15
+ return this.map.size;
16
+ }
17
+ live(key) {
18
+ const e = this.map.get(key);
19
+ if (!e)
20
+ return undefined;
21
+ if (e.expiresAt <= this.now()) {
22
+ this.map.delete(key);
23
+ return undefined;
24
+ }
25
+ // Re-insert to mark as most recently used.
26
+ this.map.delete(key);
27
+ this.map.set(key, e);
28
+ return e;
29
+ }
30
+ put(key, entry) {
31
+ this.map.set(key, entry);
32
+ if (this.map.size > this.max)
33
+ this.evict();
34
+ }
35
+ evict() {
36
+ const now = this.now();
37
+ for (const [k, e] of this.map) {
38
+ if (this.map.size <= this.max)
39
+ return;
40
+ if (e.expiresAt <= now)
41
+ this.map.delete(k);
42
+ }
43
+ while (this.map.size > this.max) {
44
+ const oldest = this.map.keys().next();
45
+ if (oldest.done)
46
+ return;
47
+ this.map.delete(oldest.value);
48
+ }
49
+ }
50
+ async setIfAbsent(key, ttlMs) {
51
+ if (this.live(key))
52
+ return false;
53
+ this.put(key, { value: 1, expiresAt: this.now() + ttlMs });
54
+ return true;
55
+ }
56
+ async increment(key, ttlMs) {
57
+ const e = this.live(key);
58
+ if (e) {
59
+ e.value += 1;
60
+ return e.value;
61
+ }
62
+ this.put(key, { value: 1, expiresAt: this.now() + ttlMs });
63
+ return 1;
64
+ }
65
+ async count(key) {
66
+ return this.live(key)?.value ?? 0;
67
+ }
68
+ async delete(key) {
69
+ this.map.delete(key);
70
+ }
71
+ }
72
+ // ------------------------------------------------------------------------------------ Redis
73
+ const INCR_SCRIPT = "local v = redis.call('INCR', KEYS[1]) if v == 1 then redis.call('PEXPIRE', KEYS[1], ARGV[1]) end return v";
74
+ /**
75
+ * Redis adapter for ioredis: `ioredisStore(new Redis(url))`. SET NX PX gives atomic one-time
76
+ * redemption; INCR+PEXPIRE runs as a Lua script so a counter can never be left without a TTL.
77
+ */
78
+ // The default key prefix keeps its pre-rename value so stores stay compatible across the upgrade.
79
+ export function ioredisStore(client, prefix = 'nawa-guard:') {
80
+ return {
81
+ setIfAbsent: async (key, ttlMs) => (await client.set(prefix + key, '1', 'PX', Math.max(1, Math.ceil(ttlMs)), 'NX')) === 'OK',
82
+ increment: async (key, ttlMs) => Number(await client.eval(INCR_SCRIPT, 1, prefix + key, Math.max(1, Math.ceil(ttlMs)))),
83
+ count: async (key) => Number((await client.get(prefix + key)) ?? 0),
84
+ delete: async (key) => {
85
+ await client.del(prefix + key);
86
+ },
87
+ };
88
+ }
89
+ /** Redis adapter for node-redis v4+: `nodeRedisStore(createClient({ url }))` (after `connect()`). */
90
+ export function nodeRedisStore(client, prefix = 'nawa-guard:') {
91
+ return {
92
+ setIfAbsent: async (key, ttlMs) => (await client.set(prefix + key, '1', { PX: Math.max(1, Math.ceil(ttlMs)), NX: true })) === 'OK',
93
+ increment: async (key, ttlMs) => Number(await client.eval(INCR_SCRIPT, { keys: [prefix + key], arguments: [String(Math.max(1, Math.ceil(ttlMs)))] })),
94
+ count: async (key) => Number((await client.get(prefix + key)) ?? 0),
95
+ delete: async (key) => {
96
+ await client.del(prefix + key);
97
+ },
98
+ };
99
+ }
100
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import { type BindMode } from './binding.js';
2
+ import { type Keyring } from './crypto.js';
3
+ import type { ChallengeType } from './types.js';
4
+ export declare const CHALLENGE_PREFIX = "ngc1";
5
+ export declare const PASS_PREFIX = "ngp1";
6
+ /** Upper bound for anything that claims to be a token; checked before any parsing. */
7
+ export declare const MAX_TOKEN_LENGTH = 8192;
8
+ /** Contents of a challenge token. Sealed with AES-256-GCM: the client can neither read nor alter it. */
9
+ export interface ChallengeClaims {
10
+ readonly v: 1;
11
+ /** Random challenge id; the one-time-redemption and attempt-counter key. */
12
+ readonly id: string;
13
+ readonly typ: ChallengeType;
14
+ readonly sk: string;
15
+ readonly act: string;
16
+ readonly lang: string;
17
+ /** Issued-at / expiry in unix milliseconds (ms precision is needed for minimum solve time). */
18
+ readonly iat: number;
19
+ readonly exp: number;
20
+ /** Difficulty level that produced this challenge. */
21
+ readonly lvl: number;
22
+ readonly cbm: BindMode;
23
+ readonly cb?: string;
24
+ /** Type-specific solution. Never leaves the server in readable form. */
25
+ readonly sol: unknown;
26
+ /** Multi-step chains only: this step (1-based), total steps, types already solved, and the pool to pick from. */
27
+ readonly stp?: number;
28
+ readonly of?: number;
29
+ readonly done?: readonly ChallengeType[];
30
+ readonly pool?: readonly ChallengeType[];
31
+ }
32
+ /** Contents of a pass token. Signed (HMAC-SHA256), readable by design; holds no secret. */
33
+ export interface PassTokenClaims {
34
+ readonly v: 1;
35
+ readonly jti: string;
36
+ readonly sk: string;
37
+ readonly act: string;
38
+ readonly typ: ChallengeType;
39
+ readonly lvl: number;
40
+ /** Unix seconds. */
41
+ readonly iat: number;
42
+ readonly exp: number;
43
+ readonly cbm: BindMode;
44
+ readonly cb?: string;
45
+ /**
46
+ * Multi-step only (`steps` > 1): every solved type in order; `typ` is the last one. Optional and
47
+ * additive, so single-step tokens are byte-for-byte the v1 format.
48
+ */
49
+ readonly stp?: readonly ChallengeType[];
50
+ }
51
+ export declare function sealChallenge(keys: Keyring, claims: ChallengeClaims): string;
52
+ export declare function openChallenge(keys: Keyring, token: string): {
53
+ claims: ChallengeClaims;
54
+ kid: string;
55
+ } | undefined;
56
+ /**
57
+ * Format (documented in README and mirrored by the .NET verifier):
58
+ * ngp1.<kid>.<base64url(JSON claims)>.<base64url(HMAC-SHA256(K_pass, "ngp1.<kid>.<payload>"))>
59
+ */
60
+ export declare function signPassToken(keys: Keyring, claims: PassTokenClaims): string;
61
+ export type PassTokenParse = {
62
+ ok: true;
63
+ claims: PassTokenClaims;
64
+ kid: string;
65
+ } | {
66
+ ok: false;
67
+ error: 'malformed' | 'unknown-key' | 'bad-signature';
68
+ };
69
+ export declare function parsePassToken(keys: Keyring, token: string): PassTokenParse;
70
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}