altcha 3.0.0-beta.2 → 3.0.0-beta.4
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/README.md +5 -2
- package/dist/external/altcha.js +247 -36
- package/dist/external/altcha.min.js +1 -1
- package/dist/external/altcha.umd.cjs +247 -36
- package/dist/external/altcha.umd.min.cjs +1 -1
- package/dist/lib/index.d.ts +18 -6
- package/dist/lib/index.js +3 -1
- package/dist/lib/index.min.js +1 -1
- package/dist/lib/index.umd.cjs +3 -1
- package/dist/lib/index.umd.min.cjs +1 -1
- package/dist/main/altcha.i18n.js +253 -40
- package/dist/main/altcha.i18n.min.js +1 -1
- package/dist/main/altcha.i18n.umd.cjs +253 -40
- package/dist/main/altcha.i18n.umd.min.cjs +1 -1
- package/dist/main/altcha.js +253 -40
- package/dist/main/altcha.min.js +1 -1
- package/dist/main/altcha.umd.cjs +253 -40
- package/dist/main/altcha.umd.min.cjs +1 -1
- package/dist/plugins/obfuscation.plugin.js +308 -14
- package/dist/plugins/obfuscation.plugin.min.js +1 -1
- package/dist/plugins/obfuscation.plugin.umd.cjs +308 -14
- package/dist/plugins/obfuscation.plugin.umd.min.cjs +1 -1
- package/dist/types/generic.d.ts +4 -1
- package/dist/types/index.d.ts +18 -6
- package/dist/workers/argon2id.js +3 -2
- package/dist/workers/pbkdf2.js +3 -2
- package/dist/workers/scrypt.js +3 -2
- package/dist/workers/sha.js +3 -2
- package/package.json +2 -2
- package/dist/external/index.d.ts +0 -1
|
@@ -2,6 +2,114 @@
|
|
|
2
2
|
typeof define === "function" && define.amd ? define(factory) : factory();
|
|
3
3
|
})((function() {
|
|
4
4
|
"use strict";
|
|
5
|
+
const noop = () => {
|
|
6
|
+
};
|
|
7
|
+
function safe_not_equal(a, b) {
|
|
8
|
+
return a != a ? b == b : a !== b || a !== null && typeof a === "object" || typeof a === "function";
|
|
9
|
+
}
|
|
10
|
+
function subscribe_to_store(store2, run, invalidate) {
|
|
11
|
+
if (store2 == null) {
|
|
12
|
+
run(void 0);
|
|
13
|
+
return noop;
|
|
14
|
+
}
|
|
15
|
+
const unsub = untrack(
|
|
16
|
+
() => store2.subscribe(
|
|
17
|
+
run,
|
|
18
|
+
// @ts-expect-error
|
|
19
|
+
invalidate
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
|
|
23
|
+
}
|
|
24
|
+
const subscriber_queue = [];
|
|
25
|
+
function writable(value, start = noop) {
|
|
26
|
+
let stop = null;
|
|
27
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
28
|
+
function set(new_value) {
|
|
29
|
+
if (safe_not_equal(value, new_value)) {
|
|
30
|
+
value = new_value;
|
|
31
|
+
if (stop) {
|
|
32
|
+
const run_queue = !subscriber_queue.length;
|
|
33
|
+
for (const subscriber of subscribers) {
|
|
34
|
+
subscriber[1]();
|
|
35
|
+
subscriber_queue.push(subscriber, value);
|
|
36
|
+
}
|
|
37
|
+
if (run_queue) {
|
|
38
|
+
for (let i = 0; i < subscriber_queue.length; i += 2) {
|
|
39
|
+
subscriber_queue[i][0](subscriber_queue[i + 1]);
|
|
40
|
+
}
|
|
41
|
+
subscriber_queue.length = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function update(fn) {
|
|
47
|
+
set(fn(
|
|
48
|
+
/** @type {T} */
|
|
49
|
+
value
|
|
50
|
+
));
|
|
51
|
+
}
|
|
52
|
+
function subscribe(run, invalidate = noop) {
|
|
53
|
+
const subscriber = [run, invalidate];
|
|
54
|
+
subscribers.add(subscriber);
|
|
55
|
+
if (subscribers.size === 1) {
|
|
56
|
+
stop = start(set, update) || noop;
|
|
57
|
+
}
|
|
58
|
+
run(
|
|
59
|
+
/** @type {T} */
|
|
60
|
+
value
|
|
61
|
+
);
|
|
62
|
+
return () => {
|
|
63
|
+
subscribers.delete(subscriber);
|
|
64
|
+
if (subscribers.size === 0 && stop) {
|
|
65
|
+
stop();
|
|
66
|
+
stop = null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { set, update, subscribe };
|
|
71
|
+
}
|
|
72
|
+
function get(store2) {
|
|
73
|
+
let value;
|
|
74
|
+
subscribe_to_store(store2, (_) => value = _)();
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
let untracking = false;
|
|
78
|
+
function untrack(fn) {
|
|
79
|
+
var previous_untracking = untracking;
|
|
80
|
+
try {
|
|
81
|
+
untracking = true;
|
|
82
|
+
return fn();
|
|
83
|
+
} finally {
|
|
84
|
+
untracking = previous_untracking;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function store(defaultValue) {
|
|
88
|
+
const scope = {
|
|
89
|
+
get: (name) => {
|
|
90
|
+
return get(scope.store)[name];
|
|
91
|
+
},
|
|
92
|
+
set: (name, value) => {
|
|
93
|
+
if (typeof name === "string") {
|
|
94
|
+
Object.assign(get(scope.store), {
|
|
95
|
+
[name]: value
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
Object.assign(get(scope.store), name);
|
|
99
|
+
}
|
|
100
|
+
scope.store.set(get(scope.store));
|
|
101
|
+
},
|
|
102
|
+
store: writable(defaultValue)
|
|
103
|
+
};
|
|
104
|
+
return scope;
|
|
105
|
+
}
|
|
106
|
+
globalThis.$altcha = globalThis.$altcha || {
|
|
107
|
+
algorithms: /* @__PURE__ */ new Map(),
|
|
108
|
+
defaults: store({}),
|
|
109
|
+
i18n: store({}),
|
|
110
|
+
instances: /* @__PURE__ */ new Set(),
|
|
111
|
+
plugins: /* @__PURE__ */ new Set()
|
|
112
|
+
};
|
|
5
113
|
class BasePlugin {
|
|
6
114
|
constructor(host) {
|
|
7
115
|
this.host = host;
|
|
@@ -18,6 +126,42 @@
|
|
|
18
126
|
async onVerify(value) {
|
|
19
127
|
}
|
|
20
128
|
}
|
|
129
|
+
function getDigest(algorithm) {
|
|
130
|
+
switch (algorithm) {
|
|
131
|
+
case "PBKDF2/SHA-512":
|
|
132
|
+
return "SHA-512";
|
|
133
|
+
case "PBKDF2/SHA-384":
|
|
134
|
+
return "SHA-384";
|
|
135
|
+
case "PBKDF2/SHA-256":
|
|
136
|
+
default:
|
|
137
|
+
return "SHA-256";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function deriveKey(parameters, salt, password) {
|
|
141
|
+
const { algorithm, cost, keyLength = 32 } = parameters;
|
|
142
|
+
const passwordKey = await crypto.subtle.importKey(
|
|
143
|
+
"raw",
|
|
144
|
+
password,
|
|
145
|
+
{ name: "PBKDF2" },
|
|
146
|
+
false,
|
|
147
|
+
["deriveKey"]
|
|
148
|
+
);
|
|
149
|
+
const derivedKey = await crypto.subtle.deriveKey(
|
|
150
|
+
{
|
|
151
|
+
name: "PBKDF2",
|
|
152
|
+
salt,
|
|
153
|
+
iterations: cost,
|
|
154
|
+
hash: getDigest(algorithm)
|
|
155
|
+
},
|
|
156
|
+
passwordKey,
|
|
157
|
+
{ name: "AES-GCM", length: keyLength * 8 },
|
|
158
|
+
true,
|
|
159
|
+
["encrypt"]
|
|
160
|
+
);
|
|
161
|
+
return {
|
|
162
|
+
derivedKey: new Uint8Array(await crypto.subtle.exportKey("raw", derivedKey))
|
|
163
|
+
};
|
|
164
|
+
}
|
|
21
165
|
function bufferStartsWith(buffer, prefix) {
|
|
22
166
|
if (prefix.length > buffer.length) {
|
|
23
167
|
return false;
|
|
@@ -54,9 +198,45 @@
|
|
|
54
198
|
async function delay(ms) {
|
|
55
199
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
200
|
}
|
|
201
|
+
async function hmac(algorithm, data, keyStr) {
|
|
202
|
+
const key = await crypto.subtle.importKey(
|
|
203
|
+
"raw",
|
|
204
|
+
new TextEncoder().encode(keyStr),
|
|
205
|
+
{
|
|
206
|
+
name: "HMAC",
|
|
207
|
+
hash: { name: algorithm }
|
|
208
|
+
},
|
|
209
|
+
false,
|
|
210
|
+
["sign", "verify"]
|
|
211
|
+
);
|
|
212
|
+
const signature = await crypto.subtle.sign(
|
|
213
|
+
"HMAC",
|
|
214
|
+
key,
|
|
215
|
+
typeof data === "string" ? new TextEncoder().encode(data) : data
|
|
216
|
+
);
|
|
217
|
+
return new Uint8Array(signature);
|
|
218
|
+
}
|
|
219
|
+
function sortKeys(obj) {
|
|
220
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
221
|
+
return obj;
|
|
222
|
+
}
|
|
223
|
+
return Object.keys(obj).sort().reduce((acc, key) => {
|
|
224
|
+
const value = obj[key];
|
|
225
|
+
if (value !== void 0) {
|
|
226
|
+
acc[key] = sortKeys(value);
|
|
227
|
+
}
|
|
228
|
+
return acc;
|
|
229
|
+
}, {});
|
|
230
|
+
}
|
|
57
231
|
function timeDuration(start) {
|
|
58
232
|
return Math.floor((performance.now() - start) * 10) / 10;
|
|
59
233
|
}
|
|
234
|
+
var HmacAlgorithm = /* @__PURE__ */ ((HmacAlgorithm2) => {
|
|
235
|
+
HmacAlgorithm2["SHA_256"] = "SHA-256";
|
|
236
|
+
HmacAlgorithm2["SHA_384"] = "SHA-384";
|
|
237
|
+
HmacAlgorithm2["SHA_512"] = "SHA-512";
|
|
238
|
+
return HmacAlgorithm2;
|
|
239
|
+
})(HmacAlgorithm || {});
|
|
60
240
|
var State = /* @__PURE__ */ ((State2) => {
|
|
61
241
|
State2["CODE"] = "code";
|
|
62
242
|
State2["ERROR"] = "error";
|
|
@@ -91,6 +271,62 @@
|
|
|
91
271
|
return this.buffer;
|
|
92
272
|
}
|
|
93
273
|
}
|
|
274
|
+
async function createChallenge(options) {
|
|
275
|
+
const {
|
|
276
|
+
algorithm,
|
|
277
|
+
counter,
|
|
278
|
+
counterMode = "uint32",
|
|
279
|
+
cost,
|
|
280
|
+
deriveKey: deriveKey2,
|
|
281
|
+
data,
|
|
282
|
+
expiresAt,
|
|
283
|
+
hmacAlgorithm = HmacAlgorithm.SHA_256,
|
|
284
|
+
hmacKeySignatureSecret,
|
|
285
|
+
hmacSignatureSecret,
|
|
286
|
+
keyLength = 32,
|
|
287
|
+
keyPrefix = "00",
|
|
288
|
+
keyPrefixLength = keyLength / 2,
|
|
289
|
+
memoryCost,
|
|
290
|
+
parallelism
|
|
291
|
+
} = options;
|
|
292
|
+
const parameters = {
|
|
293
|
+
algorithm,
|
|
294
|
+
nonce: bufferToHex(crypto.getRandomValues(new Uint8Array(16))),
|
|
295
|
+
salt: bufferToHex(crypto.getRandomValues(new Uint8Array(16))),
|
|
296
|
+
cost,
|
|
297
|
+
keyLength,
|
|
298
|
+
memoryCost,
|
|
299
|
+
parallelism,
|
|
300
|
+
keyPrefix,
|
|
301
|
+
expiresAt: expiresAt instanceof Date ? Math.floor(expiresAt.getTime() / 1e3) : expiresAt,
|
|
302
|
+
data
|
|
303
|
+
};
|
|
304
|
+
let deriveKeyResult = null;
|
|
305
|
+
if (counter !== void 0) {
|
|
306
|
+
const nonceBuf = hexToBuffer(parameters.nonce);
|
|
307
|
+
deriveKeyResult = await deriveKey2(
|
|
308
|
+
parameters,
|
|
309
|
+
hexToBuffer(parameters.salt),
|
|
310
|
+
new PasswordBuffer(nonceBuf, counterMode).setCounter(counter)
|
|
311
|
+
);
|
|
312
|
+
if (deriveKeyResult.parameters) {
|
|
313
|
+
Object.assign(parameters, deriveKeyResult.parameters);
|
|
314
|
+
}
|
|
315
|
+
parameters.keyPrefix = bufferToHex(deriveKeyResult.derivedKey.slice(0, keyPrefixLength));
|
|
316
|
+
}
|
|
317
|
+
if (!hmacSignatureSecret) {
|
|
318
|
+
return {
|
|
319
|
+
parameters: sortKeys(parameters)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return signChallenge(
|
|
323
|
+
hmacAlgorithm,
|
|
324
|
+
parameters,
|
|
325
|
+
deriveKeyResult?.derivedKey,
|
|
326
|
+
hmacSignatureSecret,
|
|
327
|
+
hmacKeySignatureSecret
|
|
328
|
+
);
|
|
329
|
+
}
|
|
94
330
|
async function solveChallenge(options) {
|
|
95
331
|
const {
|
|
96
332
|
challenge,
|
|
@@ -98,7 +334,7 @@
|
|
|
98
334
|
counterMode = "uint32",
|
|
99
335
|
counterStart = 0,
|
|
100
336
|
counterStep = 1,
|
|
101
|
-
deriveKey,
|
|
337
|
+
deriveKey: deriveKey2,
|
|
102
338
|
timeout = 9e4
|
|
103
339
|
} = options;
|
|
104
340
|
const { nonce, keyPrefix, salt } = challenge.parameters;
|
|
@@ -114,7 +350,7 @@
|
|
|
114
350
|
if (controller?.signal.aborted || timeout && counter % 10 === 0 && performance.now() - start > timeout) {
|
|
115
351
|
return null;
|
|
116
352
|
}
|
|
117
|
-
const { derivedKey } = await
|
|
353
|
+
const { derivedKey } = await deriveKey2(
|
|
118
354
|
challenge.parameters,
|
|
119
355
|
saltBuf,
|
|
120
356
|
password.setCounter(counter)
|
|
@@ -142,7 +378,8 @@
|
|
|
142
378
|
controller = new AbortController(),
|
|
143
379
|
createWorker,
|
|
144
380
|
onOutOfMemory = (c) => c > 1 ? Math.floor(c / 2) : 0,
|
|
145
|
-
counterMode
|
|
381
|
+
counterMode,
|
|
382
|
+
timeout = 9e4
|
|
146
383
|
} = options;
|
|
147
384
|
const workersConcurrency = Math.min(16, Math.max(1, concurrency));
|
|
148
385
|
const workersInstances = [];
|
|
@@ -183,6 +420,7 @@
|
|
|
183
420
|
counterMode,
|
|
184
421
|
counterStart: i,
|
|
185
422
|
counterStep: workersConcurrency,
|
|
423
|
+
timeout,
|
|
186
424
|
type: "work"
|
|
187
425
|
});
|
|
188
426
|
});
|
|
@@ -214,8 +452,24 @@
|
|
|
214
452
|
}
|
|
215
453
|
return solution || null;
|
|
216
454
|
}
|
|
455
|
+
async function signChallenge(algorithm, parameters, derivedKey, hmacSignatureSecret, hmacKeySignatureSecret) {
|
|
456
|
+
if (derivedKey && hmacKeySignatureSecret) {
|
|
457
|
+
parameters.keySignature = bufferToHex(
|
|
458
|
+
await hmac(algorithm, derivedKey, hmacKeySignatureSecret)
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
parameters = sortKeys(parameters);
|
|
462
|
+
return {
|
|
463
|
+
parameters,
|
|
464
|
+
signature: bufferToHex(await hmac(algorithm, JSON.stringify(parameters), hmacSignatureSecret))
|
|
465
|
+
};
|
|
466
|
+
}
|
|
217
467
|
async function deobfuscate(obfuscatedData, options = {}) {
|
|
218
|
-
|
|
468
|
+
let {
|
|
469
|
+
concurrency = Math.max(1, Math.min(4, navigator.hardwareConcurrency)),
|
|
470
|
+
createWorker,
|
|
471
|
+
deriveKey: deriveKey$1 = deriveKey
|
|
472
|
+
} = options;
|
|
219
473
|
let challenge = null;
|
|
220
474
|
try {
|
|
221
475
|
challenge = JSON.parse(atob(obfuscatedData));
|
|
@@ -227,21 +481,20 @@
|
|
|
227
481
|
}
|
|
228
482
|
const cipher = challenge.cipher;
|
|
229
483
|
let solution = null;
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
} else {
|
|
236
|
-
const createWorker = globalThis.$altcha.algorithms.get(challenge.parameters.algorithm);
|
|
237
|
-
if (!createWorker) {
|
|
238
|
-
throw new Error(`Unsupported algorithm ${challenge.parameters.algorithm}.`);
|
|
239
|
-
}
|
|
484
|
+
if (!createWorker && "$altcha" in globalThis) {
|
|
485
|
+
createWorker = globalThis.$altcha.algorithms.get(challenge.parameters.algorithm);
|
|
486
|
+
}
|
|
487
|
+
if (createWorker) {
|
|
240
488
|
solution = await solveChallengeWorkers({
|
|
241
489
|
challenge,
|
|
242
490
|
concurrency,
|
|
243
491
|
createWorker
|
|
244
492
|
});
|
|
493
|
+
} else {
|
|
494
|
+
solution = await solveChallenge({
|
|
495
|
+
challenge,
|
|
496
|
+
deriveKey: deriveKey$1
|
|
497
|
+
});
|
|
245
498
|
}
|
|
246
499
|
if (!solution) {
|
|
247
500
|
throw new Error("Unable to find solution.");
|
|
@@ -263,7 +516,48 @@
|
|
|
263
516
|
);
|
|
264
517
|
return new TextDecoder().decode(result);
|
|
265
518
|
}
|
|
519
|
+
async function obfuscate(str, options = {}) {
|
|
520
|
+
const { deriveKey: deriveKey$1 = deriveKey } = options;
|
|
521
|
+
const counterMin = options?.counterMin || 20;
|
|
522
|
+
const counterMax = options?.counterMax || 200;
|
|
523
|
+
const { parameters } = await createChallenge({
|
|
524
|
+
algorithm: "PBKDF2/SHA-256",
|
|
525
|
+
cost: 5e3,
|
|
526
|
+
deriveKey: deriveKey$1,
|
|
527
|
+
counter: Math.floor(Math.random() * (counterMax - counterMin + 1)) + counterMin,
|
|
528
|
+
keyPrefixLength: 32,
|
|
529
|
+
...options
|
|
530
|
+
});
|
|
531
|
+
const key = await crypto.subtle.importKey(
|
|
532
|
+
"raw",
|
|
533
|
+
hexToBuffer(parameters.keyPrefix),
|
|
534
|
+
{ name: "AES-GCM" },
|
|
535
|
+
false,
|
|
536
|
+
["encrypt"]
|
|
537
|
+
);
|
|
538
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
539
|
+
const data = await crypto.subtle.encrypt(
|
|
540
|
+
{ name: "AES-GCM", iv },
|
|
541
|
+
key,
|
|
542
|
+
new TextEncoder().encode(str)
|
|
543
|
+
);
|
|
544
|
+
return btoa(
|
|
545
|
+
JSON.stringify({
|
|
546
|
+
parameters: {
|
|
547
|
+
...parameters,
|
|
548
|
+
// Return only half the derived key
|
|
549
|
+
keyPrefix: parameters.keyPrefix.slice(0, parameters.keyLength || 32)
|
|
550
|
+
},
|
|
551
|
+
cipher: {
|
|
552
|
+
iv: bufferToHex(iv),
|
|
553
|
+
data: bufferToHex(data)
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
);
|
|
557
|
+
}
|
|
266
558
|
class ObfuscationPlugin extends BasePlugin {
|
|
559
|
+
static deobfuscate = deobfuscate;
|
|
560
|
+
static obfuscate = obfuscate;
|
|
267
561
|
elTrigger = null;
|
|
268
562
|
activate() {
|
|
269
563
|
this.elTrigger = this.host.querySelector("button");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";
|
|
1
|
+
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";const e=()=>{};function t(t,r,n){if(null==t)return r(void 0),e;const a=function(e){var t=i;try{return i=!0,e()}finally{i=t}}(()=>t.subscribe(r,n));return a.unsubscribe?()=>a.unsubscribe():a}const r=[];function n(t,n=e){let a=null;const i=new Set;function o(e){if(o=e,((n=t)!=n?o==o:n!==o||null!==n&&"object"==typeof n||"function"==typeof n)&&(t=e,a)){const e=!r.length;for(const e of i)e[1](),r.push(e,t);if(e){for(let e=0;e<r.length;e+=2)r[e][0](r[e+1]);r.length=0}}var n,o}function s(e){o(e(t))}return{set:o,update:s,subscribe:function(r,c=e){const l=[r,c];return i.add(l),1===i.size&&(a=n(o,s)||e),r(t),()=>{i.delete(l),0===i.size&&a&&(a(),a=null)}}}}function a(e){let r;return t(e,e=>r=e)(),r}let i=!1;function o(e){const t={get:e=>a(t.store)[e],set:(e,r)=>{"string"==typeof e?Object.assign(a(t.store),{[e]:r}):Object.assign(a(t.store),e),t.store.set(a(t.store))},store:n(e)};return t}globalThis.$altcha=globalThis.$altcha||{algorithms:new Map,defaults:o({}),i18n:o({}),instances:new Set,plugins:new Set};class s{constructor(e){this.host=e}static register(e){"$altcha"in globalThis&&!globalThis.$altcha.plugins.has(e)&&globalThis.$altcha.plugins.add(e)}async onFetchChallenge(e){}async onRequestServerVerification(e,t){}async onVerify(e){}}function c(e){switch(e){case"PBKDF2/SHA-512":return"SHA-512";case"PBKDF2/SHA-384":return"SHA-384";default:return"SHA-256"}}async function l(e,t,r){const{algorithm:n,cost:a,keyLength:i=32}=e,o=await crypto.subtle.importKey("raw",r,{name:"PBKDF2"},!1,["deriveKey"]),s=await crypto.subtle.deriveKey({name:"PBKDF2",salt:t,iterations:a,hash:c(n)},o,{name:"AES-GCM",length:8*i},!0,["encrypt"]);return{derivedKey:new Uint8Array(await crypto.subtle.exportKey("raw",s))}}function u(e,t){if(t.length>e.length)return!1;for(let r=0;r<t.length;r++)if(e[r]!==t[r])return!1;return!0}function h(e){return Array.from(new Uint8Array(e)).map(e=>e.toString(16).padStart(2,"0")).join("")}function f(e){if(e.length%2!=0)throw new Error(`Hex string must have an even length. Got: ${e}`);const t=new ArrayBuffer(e.length/2),r=new DataView(t);for(let t=0;t<e.length;t+=2){const n=e.substring(t,t+2),a=parseInt(n,16);r.setUint8(t/2,a)}return new Uint8Array(t)}async function y(e){await new Promise(t=>setTimeout(t,e))}async function g(e,t,r){const n=await crypto.subtle.importKey("raw",(new TextEncoder).encode(r),{name:"HMAC",hash:{name:e}},!1,["sign","verify"]),a=await crypto.subtle.sign("HMAC",n,"string"==typeof t?(new TextEncoder).encode(t):t);return new Uint8Array(a)}function d(e){return"object"!=typeof e||null===e||Array.isArray(e)?e:Object.keys(e).sort().reduce((t,r)=>{const n=e[r];return void 0!==n&&(t[r]=d(n)),t},{})}function m(e){return Math.floor(10*(performance.now()-e))/10}var w=(e=>(e.SHA_256="SHA-256",e.SHA_384="SHA-384",e.SHA_512="SHA-512",e))(w||{}),p=(e=>(e.CODE="code",e.ERROR="error",e.VERIFIED="verified",e.VERIFYING="verifying",e.UNVERIFIED="unverified",e.EXPIRED="expired",e))(p||{});class b{constructor(e,t="uint32"){this.nonce=e,this.mode=t,this.buffer=new Uint8Array(this.nonce.length+this.COUNTER_BYTES),this.buffer.set(this.nonce,0),this.dataView=new DataView(this.buffer.buffer)}COUNTER_BYTES=4;buffer;dataView;encoder=new TextEncoder;setCounter(e){return"string"===this.mode?function(e,t){const r=new Uint8Array(e.length+t.length);return r.set(e,0),r.set(t,e.length),r}(this.nonce,this.encoder.encode(e.toString())):(this.dataView.setUint32(this.nonce.length,e,!1),this.buffer)}}async function S(e){const{algorithm:t,counter:r,counterMode:n="uint32",cost:a,deriveKey:i,data:o,expiresAt:s,hmacAlgorithm:c=w.SHA_256,hmacKeySignatureSecret:l,hmacSignatureSecret:u,keyLength:y=32,keyPrefix:m="00",keyPrefixLength:p=y/2,memoryCost:S,parallelism:v}=e,A={algorithm:t,nonce:h(crypto.getRandomValues(new Uint8Array(16))),salt:h(crypto.getRandomValues(new Uint8Array(16))),cost:a,keyLength:y,memoryCost:S,parallelism:v,keyPrefix:m,expiresAt:s instanceof Date?Math.floor(s.getTime()/1e3):s,data:o};let E=null;if(void 0!==r){const e=f(A.nonce);E=await i(A,f(A.salt),new b(e,n).setCounter(r)),E.parameters&&Object.assign(A,E.parameters),A.keyPrefix=h(E.derivedKey.slice(0,p))}return u?async function(e,t,r,n,a){r&&a&&(t.keySignature=h(await g(e,r,a)));return t=d(t),{parameters:t,signature:h(await g(e,JSON.stringify(t),n))}}(c,A,E?.derivedKey,u,l):{parameters:d(A)}}async function v(e){const{challenge:t,concurrency:r=navigator.hardwareConcurrency,controller:n=new AbortController,createWorker:a,onOutOfMemory:i=e=>e>1?Math.floor(e/2):0,counterMode:o,timeout:s=9e4}=e,c=Math.min(16,Math.max(1,r)),l=[],u=()=>{for(const e of l)e.terminate()};for(let e=0;e<c;e++)l.push(await a(t.parameters.algorithm));let h=null;try{h=await Promise.race(l.map((e,r)=>(n.signal.addEventListener("abort",()=>{e.postMessage({type:"abort"})}),new Promise((n,a)=>{e.addEventListener("error",e=>{a(e)}),e.addEventListener("message",t=>{if(t.data){for(const t of l)t!==e&&t.postMessage({type:"abort"});if(t.data.error)return a(new Error(t.data.error))}n(t.data)}),e.postMessage({challenge:t,counterMode:o,counterStart:r,counterStep:c,timeout:s,type:"work"})}))))}catch(r){if(r instanceof Error&&!!r?.message?.includes("Out of memory")&&i){u();const r=i(c);if(r)return v({...e,challenge:t,controller:n,concurrency:r,createWorker:a})}throw r}finally{u()}return n.signal.aborted?null:h||null}async function A(e,t={}){let{concurrency:r=Math.max(1,Math.min(4,navigator.hardwareConcurrency)),createWorker:n,deriveKey:a=l}=t,i=null;try{i=JSON.parse(atob(e))}catch{throw new Error("Unable to parse obfuscated data.")}if(!i||"object"!=typeof i||!("parameters"in i)||!("cipher"in i))throw new Error("Invalid obfuscated data format.");const o=i.cipher;let s=null;if(!n&&"$altcha"in globalThis&&(n=globalThis.$altcha.algorithms.get(i.parameters.algorithm)),s=n?await v({challenge:i,concurrency:r,createWorker:n}):await async function(e){const{challenge:t,controller:r,counterMode:n="uint32",counterStart:a=0,counterStep:i=1,deriveKey:o,timeout:s=9e4}=e,{nonce:c,keyPrefix:l,salt:g}=t.parameters,d=f(c),w=f(g),p=l.length%2==0?f(l):null,S=new b(d,n),v=performance.now();let A=a,E="",T=v;for(;;){if(r?.signal.aborted||s&&A%10==0&&performance.now()-v>s)return null;const{derivedKey:e}=await o(t.parameters,w,S.setCounter(A));if(A%10==0&&performance.now()-T>200&&(await y(0),T=performance.now()),p?u(e,p):h(e).startsWith(l)){E=h(e);break}A+=i}return{counter:A,derivedKey:E,time:m(v)}}({challenge:i,deriveKey:a}),!s)throw new Error("Unable to find solution.");const c=await crypto.subtle.importKey("raw",f(s.derivedKey),{name:"AES-GCM"},!1,["decrypt"]),g=await crypto.subtle.decrypt({name:"AES-GCM",iv:f(o.iv)},c,f(o.data));return(new TextDecoder).decode(g)}async function E(e,t={}){const{deriveKey:r=l}=t,n=t?.counterMin||20,a=t?.counterMax||200,{parameters:i}=await S({algorithm:"PBKDF2/SHA-256",cost:5e3,deriveKey:r,counter:Math.floor(Math.random()*(a-n+1))+n,keyPrefixLength:32,...t}),o=await crypto.subtle.importKey("raw",f(i.keyPrefix),{name:"AES-GCM"},!1,["encrypt"]),s=crypto.getRandomValues(new Uint8Array(12)),c=await crypto.subtle.encrypt({name:"AES-GCM",iv:s},o,(new TextEncoder).encode(e));return btoa(JSON.stringify({parameters:{...i,keyPrefix:i.keyPrefix.slice(0,i.keyLength||32)},cipher:{iv:h(s),data:h(c)}}))}s.register(class extends s{static deobfuscate=A;static obfuscate=E;elTrigger=null;activate(){this.elTrigger=this.host.querySelector("button"),this.elTrigger&&(this.elTrigger.addEventListener("click",this.onTriggerClick.bind(this)),this.host.configure({floatingAnchor:this.elTrigger}))}destroy(){}async onVerify(e){const{minDuration:t=500}=e,r=performance.now(),n=this.host.getAttribute("data-obfuscated");if(n){this.host.reset(p.VERIFYING);try{const e=await A(n);await this.#e(Math.max(0,t-(performance.now()-r))),this.#t(e)}catch(e){this.host.setState(p.ERROR,String(e))}finally{this.host.setState(p.VERIFIED)}return null}}onTriggerClick(e){e.preventDefault(),this.host.show(),this.host.verify().then(()=>{this.host.hide()})}#t(e){let t;if(e.match(/^(mailto|tel|sms|https?):/)){const[r]=e.slice(e.indexOf(":")+1).replace(/^\/\//,"").split("?");t=document.createElement("a"),t.href=e,t.innerText=r}else t=document.createTextNode(e);this.elTrigger&&t&&(this.elTrigger.after(t),this.elTrigger.parentElement?.removeChild(this.elTrigger))}async#e(e){await new Promise(t=>setTimeout(t,e))}})});
|
package/dist/types/generic.d.ts
CHANGED
|
@@ -4,15 +4,18 @@ export {};
|
|
|
4
4
|
export declare class AltchaWidgetElement extends HTMLElement {
|
|
5
5
|
auto?: Configuration['auto'];
|
|
6
6
|
challenge?: string;
|
|
7
|
+
configuration?: string;
|
|
7
8
|
display?: Configuration['display'];
|
|
8
9
|
language?: string;
|
|
9
10
|
name?: string;
|
|
11
|
+
theme?: string;
|
|
10
12
|
type?: Configuration['type'];
|
|
13
|
+
workers?: number;
|
|
11
14
|
configure: (config: Partial<Configuration>) => Promise<void>;
|
|
12
15
|
getConfiguration: () => Configuration;
|
|
13
16
|
getState: () => State;
|
|
14
17
|
hide: () => void;
|
|
15
|
-
reset: (newState
|
|
18
|
+
reset: (newState?: State, err?: string | null) => void;
|
|
16
19
|
setState: (newState: State, err?: string | null) => void;
|
|
17
20
|
show: () => void;
|
|
18
21
|
updateUI: () => void;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -46,11 +46,6 @@ export interface Configuration {
|
|
|
46
46
|
* Enables verbose logging in the browser console for debugging.
|
|
47
47
|
*/
|
|
48
48
|
debug: boolean;
|
|
49
|
-
/**
|
|
50
|
-
* The minimum verification time in milliseconds (adds an artificial delay if the PoW is faster).
|
|
51
|
-
* @default 500
|
|
52
|
-
*/
|
|
53
|
-
minDuration: number;
|
|
54
49
|
/**
|
|
55
50
|
* Prevents the code-challenge modal from stealing focus when opened.
|
|
56
51
|
*/
|
|
@@ -89,10 +84,19 @@ export interface Configuration {
|
|
|
89
84
|
* Hides the ALTCHA logo icon.
|
|
90
85
|
*/
|
|
91
86
|
hideLogo: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Enables the collection of pointer and scroll events for ALTCHA HIS mechanism (defaults to true).
|
|
89
|
+
*/
|
|
90
|
+
humanInteractionSignature: boolean;
|
|
92
91
|
/**
|
|
93
92
|
* The ISO alpha-2 language code for localization (requires corresponding i18n file).
|
|
94
93
|
*/
|
|
95
94
|
language: string;
|
|
95
|
+
/**
|
|
96
|
+
* The minimum verification time in milliseconds (adds an artificial delay if the PoW is faster).
|
|
97
|
+
* @default 500
|
|
98
|
+
*/
|
|
99
|
+
minDuration: number;
|
|
96
100
|
/**
|
|
97
101
|
* Forces the widget into a failed state with a mock error for UI testing.
|
|
98
102
|
*/
|
|
@@ -106,6 +110,10 @@ export interface Configuration {
|
|
|
106
110
|
* CSS selector for an element to be mirrored inside the overlay modal.
|
|
107
111
|
*/
|
|
108
112
|
overlayContent: string | null;
|
|
113
|
+
/**
|
|
114
|
+
* Configures the placement (top / bottom) for popovers. Defaults to 'auto'.
|
|
115
|
+
*/
|
|
116
|
+
popoverPlacement: 'auto' | 'bottom' | 'top';
|
|
109
117
|
/**
|
|
110
118
|
* Automatically attempts to restart verification with fewer workers if the browser runs out of memory (Argon2 and Scrypt only).
|
|
111
119
|
*/
|
|
@@ -126,6 +134,10 @@ export interface Configuration {
|
|
|
126
134
|
* Mocks a successful verification. Useful for testing environments without network access.
|
|
127
135
|
*/
|
|
128
136
|
test: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* PoW verification timeout in milliseconds. Defaults to 90_000 ms.
|
|
139
|
+
*/
|
|
140
|
+
timeout: number;
|
|
129
141
|
/**
|
|
130
142
|
* The visual style of the interaction element.
|
|
131
143
|
*/
|
|
@@ -384,7 +396,7 @@ export interface WidgetMethods {
|
|
|
384
396
|
getConfiguration: () => Configuration;
|
|
385
397
|
getState: () => State;
|
|
386
398
|
hide: () => void;
|
|
387
|
-
reset: (newState
|
|
399
|
+
reset: (newState?: State, err?: string | null) => void;
|
|
388
400
|
setState: (newState: State, err?: string | null) => void;
|
|
389
401
|
show: () => void;
|
|
390
402
|
updateUI: () => void;
|
package/dist/workers/argon2id.js
CHANGED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
const { deriveKey: deriveKey2 } = options;
|
|
113
113
|
let controller = void 0;
|
|
114
114
|
self.onmessage = async (message) => {
|
|
115
|
-
const { challenge, counterMode, counterStart, counterStep, type } = message.data;
|
|
115
|
+
const { challenge, counterMode, counterStart, counterStep, timeout, type } = message.data;
|
|
116
116
|
if (type === "abort") {
|
|
117
117
|
controller?.abort();
|
|
118
118
|
} else if (type === "work") {
|
|
@@ -125,7 +125,8 @@
|
|
|
125
125
|
counterStart,
|
|
126
126
|
counterStep,
|
|
127
127
|
deriveKey: deriveKey2,
|
|
128
|
-
counterMode
|
|
128
|
+
counterMode,
|
|
129
|
+
timeout
|
|
129
130
|
});
|
|
130
131
|
} catch (err) {
|
|
131
132
|
return self.postMessage({ error: err });
|
package/dist/workers/pbkdf2.js
CHANGED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
const { deriveKey: deriveKey2 } = options;
|
|
113
113
|
let controller = void 0;
|
|
114
114
|
self.onmessage = async (message) => {
|
|
115
|
-
const { challenge, counterMode, counterStart, counterStep, type } = message.data;
|
|
115
|
+
const { challenge, counterMode, counterStart, counterStep, timeout, type } = message.data;
|
|
116
116
|
if (type === "abort") {
|
|
117
117
|
controller?.abort();
|
|
118
118
|
} else if (type === "work") {
|
|
@@ -125,7 +125,8 @@
|
|
|
125
125
|
counterStart,
|
|
126
126
|
counterStep,
|
|
127
127
|
deriveKey: deriveKey2,
|
|
128
|
-
counterMode
|
|
128
|
+
counterMode,
|
|
129
|
+
timeout
|
|
129
130
|
});
|
|
130
131
|
} catch (err) {
|
|
131
132
|
return self.postMessage({ error: err });
|
package/dist/workers/scrypt.js
CHANGED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
const { deriveKey: deriveKey2 } = options;
|
|
113
113
|
let controller = void 0;
|
|
114
114
|
self.onmessage = async (message) => {
|
|
115
|
-
const { challenge, counterMode, counterStart, counterStep, type } = message.data;
|
|
115
|
+
const { challenge, counterMode, counterStart, counterStep, timeout, type } = message.data;
|
|
116
116
|
if (type === "abort") {
|
|
117
117
|
controller?.abort();
|
|
118
118
|
} else if (type === "work") {
|
|
@@ -125,7 +125,8 @@
|
|
|
125
125
|
counterStart,
|
|
126
126
|
counterStep,
|
|
127
127
|
deriveKey: deriveKey2,
|
|
128
|
-
counterMode
|
|
128
|
+
counterMode,
|
|
129
|
+
timeout
|
|
129
130
|
});
|
|
130
131
|
} catch (err) {
|
|
131
132
|
return self.postMessage({ error: err });
|
package/dist/workers/sha.js
CHANGED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
const { deriveKey: deriveKey2 } = options;
|
|
113
113
|
let controller = void 0;
|
|
114
114
|
self.onmessage = async (message) => {
|
|
115
|
-
const { challenge, counterMode, counterStart, counterStep, type } = message.data;
|
|
115
|
+
const { challenge, counterMode, counterStart, counterStep, timeout, type } = message.data;
|
|
116
116
|
if (type === "abort") {
|
|
117
117
|
controller?.abort();
|
|
118
118
|
} else if (type === "work") {
|
|
@@ -125,7 +125,8 @@
|
|
|
125
125
|
counterStart,
|
|
126
126
|
counterStep,
|
|
127
127
|
deriveKey: deriveKey2,
|
|
128
|
-
counterMode
|
|
128
|
+
counterMode,
|
|
129
|
+
timeout
|
|
129
130
|
});
|
|
130
131
|
} catch (err) {
|
|
131
132
|
return self.postMessage({ error: err });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "altcha",
|
|
3
3
|
"description": "Privacy-first CAPTCHA widget, compliant with global regulations (GDPR/HIPAA/CCPA/LGDP/DPDPA/PIPL) and WCAG accessible. No tracking, self-verifying.",
|
|
4
|
-
"version": "3.0.0-beta.
|
|
4
|
+
"version": "3.0.0-beta.4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Daniel Regeci",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"require": "./dist/external/altcha.css"
|
|
51
51
|
},
|
|
52
52
|
"./external": {
|
|
53
|
-
"types": "./dist/
|
|
53
|
+
"types": "./dist/types/generic.d.ts",
|
|
54
54
|
"import": "./dist/external/altcha.js",
|
|
55
55
|
"require": "./dist/external/altcha.umd.cjs"
|
|
56
56
|
},
|
package/dist/external/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
declare module 'altcha/external'; export {};
|