altcha 3.0.0-beta.2 → 3.0.0-beta.3
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 +55 -14
- package/dist/plugins/obfuscation.plugin.min.js +1 -1
- package/dist/plugins/obfuscation.plugin.umd.cjs +55 -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
|
@@ -14,6 +14,42 @@ class BasePlugin {
|
|
|
14
14
|
async onVerify(value) {
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
function getDigest(algorithm) {
|
|
18
|
+
switch (algorithm) {
|
|
19
|
+
case "PBKDF2/SHA-512":
|
|
20
|
+
return "SHA-512";
|
|
21
|
+
case "PBKDF2/SHA-384":
|
|
22
|
+
return "SHA-384";
|
|
23
|
+
case "PBKDF2/SHA-256":
|
|
24
|
+
default:
|
|
25
|
+
return "SHA-256";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function deriveKey(parameters, salt, password) {
|
|
29
|
+
const { algorithm, cost, keyLength = 32 } = parameters;
|
|
30
|
+
const passwordKey = await crypto.subtle.importKey(
|
|
31
|
+
"raw",
|
|
32
|
+
password,
|
|
33
|
+
{ name: "PBKDF2" },
|
|
34
|
+
false,
|
|
35
|
+
["deriveKey"]
|
|
36
|
+
);
|
|
37
|
+
const derivedKey = await crypto.subtle.deriveKey(
|
|
38
|
+
{
|
|
39
|
+
name: "PBKDF2",
|
|
40
|
+
salt,
|
|
41
|
+
iterations: cost,
|
|
42
|
+
hash: getDigest(algorithm)
|
|
43
|
+
},
|
|
44
|
+
passwordKey,
|
|
45
|
+
{ name: "AES-GCM", length: keyLength * 8 },
|
|
46
|
+
true,
|
|
47
|
+
["encrypt"]
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
derivedKey: new Uint8Array(await crypto.subtle.exportKey("raw", derivedKey))
|
|
51
|
+
};
|
|
52
|
+
}
|
|
17
53
|
function bufferStartsWith(buffer, prefix) {
|
|
18
54
|
if (prefix.length > buffer.length) {
|
|
19
55
|
return false;
|
|
@@ -94,7 +130,7 @@ async function solveChallenge(options) {
|
|
|
94
130
|
counterMode = "uint32",
|
|
95
131
|
counterStart = 0,
|
|
96
132
|
counterStep = 1,
|
|
97
|
-
deriveKey,
|
|
133
|
+
deriveKey: deriveKey2,
|
|
98
134
|
timeout = 9e4
|
|
99
135
|
} = options;
|
|
100
136
|
const { nonce, keyPrefix, salt } = challenge.parameters;
|
|
@@ -110,7 +146,7 @@ async function solveChallenge(options) {
|
|
|
110
146
|
if (controller?.signal.aborted || timeout && counter % 10 === 0 && performance.now() - start > timeout) {
|
|
111
147
|
return null;
|
|
112
148
|
}
|
|
113
|
-
const { derivedKey } = await
|
|
149
|
+
const { derivedKey } = await deriveKey2(
|
|
114
150
|
challenge.parameters,
|
|
115
151
|
saltBuf,
|
|
116
152
|
password.setCounter(counter)
|
|
@@ -138,7 +174,8 @@ async function solveChallengeWorkers(options) {
|
|
|
138
174
|
controller = new AbortController(),
|
|
139
175
|
createWorker,
|
|
140
176
|
onOutOfMemory = (c) => c > 1 ? Math.floor(c / 2) : 0,
|
|
141
|
-
counterMode
|
|
177
|
+
counterMode,
|
|
178
|
+
timeout = 9e4
|
|
142
179
|
} = options;
|
|
143
180
|
const workersConcurrency = Math.min(16, Math.max(1, concurrency));
|
|
144
181
|
const workersInstances = [];
|
|
@@ -179,6 +216,7 @@ async function solveChallengeWorkers(options) {
|
|
|
179
216
|
counterMode,
|
|
180
217
|
counterStart: i,
|
|
181
218
|
counterStep: workersConcurrency,
|
|
219
|
+
timeout,
|
|
182
220
|
type: "work"
|
|
183
221
|
});
|
|
184
222
|
});
|
|
@@ -211,7 +249,11 @@ async function solveChallengeWorkers(options) {
|
|
|
211
249
|
return solution || null;
|
|
212
250
|
}
|
|
213
251
|
async function deobfuscate(obfuscatedData, options = {}) {
|
|
214
|
-
|
|
252
|
+
let {
|
|
253
|
+
concurrency = Math.max(1, Math.min(4, navigator.hardwareConcurrency)),
|
|
254
|
+
createWorker,
|
|
255
|
+
deriveKey: deriveKey$1 = deriveKey
|
|
256
|
+
} = options;
|
|
215
257
|
let challenge = null;
|
|
216
258
|
try {
|
|
217
259
|
challenge = JSON.parse(atob(obfuscatedData));
|
|
@@ -223,21 +265,20 @@ async function deobfuscate(obfuscatedData, options = {}) {
|
|
|
223
265
|
}
|
|
224
266
|
const cipher = challenge.cipher;
|
|
225
267
|
let solution = null;
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
231
|
-
} else {
|
|
232
|
-
const createWorker = globalThis.$altcha.algorithms.get(challenge.parameters.algorithm);
|
|
233
|
-
if (!createWorker) {
|
|
234
|
-
throw new Error(`Unsupported algorithm ${challenge.parameters.algorithm}.`);
|
|
235
|
-
}
|
|
268
|
+
if (!createWorker && "$altcha" in globalThis) {
|
|
269
|
+
createWorker = globalThis.$altcha.algorithms.get(challenge.parameters.algorithm);
|
|
270
|
+
}
|
|
271
|
+
if (createWorker) {
|
|
236
272
|
solution = await solveChallengeWorkers({
|
|
237
273
|
challenge,
|
|
238
274
|
concurrency,
|
|
239
275
|
createWorker
|
|
240
276
|
});
|
|
277
|
+
} else {
|
|
278
|
+
solution = await solveChallenge({
|
|
279
|
+
challenge,
|
|
280
|
+
deriveKey: deriveKey$1
|
|
281
|
+
});
|
|
241
282
|
}
|
|
242
283
|
if (!solution) {
|
|
243
284
|
throw new Error("Unable to find solution.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class e{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 t(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
|
|
1
|
+
class e{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 t(e){switch(e){case"PBKDF2/SHA-512":return"SHA-512";case"PBKDF2/SHA-384":return"SHA-384";default:return"SHA-256"}}async function r(e,r,n){const{algorithm:a,cost:o,keyLength:i=32}=e,s=await crypto.subtle.importKey("raw",n,{name:"PBKDF2"},!1,["deriveKey"]),c=await crypto.subtle.deriveKey({name:"PBKDF2",salt:r,iterations:o,hash:t(a)},s,{name:"AES-GCM",length:8*i},!0,["encrypt"]);return{derivedKey:new Uint8Array(await crypto.subtle.exportKey("raw",c))}}function n(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 a(e){return Array.from(new Uint8Array(e)).map(e=>e.toString(16).padStart(2,"0")).join("")}function o(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 i(e){await new Promise(t=>setTimeout(t,e))}function s(e){return Math.floor(10*(performance.now()-e))/10}var c=(e=>(e.CODE="code",e.ERROR="error",e.VERIFIED="verified",e.VERIFYING="verifying",e.UNVERIFIED="unverified",e.EXPIRED="expired",e))(c||{});class l{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 h(e){const{challenge:t,concurrency:r=navigator.hardwareConcurrency,controller:n=new AbortController,createWorker:a,onOutOfMemory:o=e=>e>1?Math.floor(e/2):0,counterMode:i,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 f=null;try{f=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:i,counterStart:r,counterStep:c,timeout:s,type:"work"})}))))}catch(r){if(r instanceof Error&&!!r?.message?.includes("Out of memory")&&o){u();const r=o(c);if(r)return h({...e,challenge:t,controller:n,concurrency:r,createWorker:a})}throw r}finally{u()}return n.signal.aborted?null:f||null}async function u(e,t={}){let{concurrency:c=Math.max(1,Math.min(4,navigator.hardwareConcurrency)),createWorker:u,deriveKey:f=r}=t,g=null;try{g=JSON.parse(atob(e))}catch{throw new Error("Unable to parse obfuscated data.")}if(!g||"object"!=typeof g||!("parameters"in g)||!("cipher"in g))throw new Error("Invalid obfuscated data format.");const d=g.cipher;let w=null;if(!u&&"$altcha"in globalThis&&(u=globalThis.$altcha.algorithms.get(g.parameters.algorithm)),w=u?await h({challenge:g,concurrency:c,createWorker:u}):await async function(e){const{challenge:t,controller:r,counterMode:c="uint32",counterStart:h=0,counterStep:u=1,deriveKey:f,timeout:g=9e4}=e,{nonce:d,keyPrefix:w,salt:y}=t.parameters,m=o(d),p=o(y),b=w.length%2==0?o(w):null,E=new l(m,c),v=performance.now();let T=h,S="",A=v;for(;;){if(r?.signal.aborted||g&&T%10==0&&performance.now()-v>g)return null;const{derivedKey:e}=await f(t.parameters,p,E.setCounter(T));if(T%10==0&&performance.now()-A>200&&(await i(0),A=performance.now()),b?n(e,b):a(e).startsWith(w)){S=a(e);break}T+=u}return{counter:T,derivedKey:S,time:s(v)}}({challenge:g,deriveKey:f}),!w)throw new Error("Unable to find solution.");const y=await crypto.subtle.importKey("raw",o(w.derivedKey),{name:"AES-GCM"},!1,["decrypt"]),m=await crypto.subtle.decrypt({name:"AES-GCM",iv:o(d.iv)},y,o(d.data));return(new TextDecoder).decode(m)}e.register(class extends 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(c.VERIFYING);try{const e=await u(n);await this.#e(Math.max(0,t-(performance.now()-r))),this.#t(e)}catch(e){this.host.setState(c.ERROR,String(e))}finally{this.host.setState(c.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))}});
|
|
@@ -18,6 +18,42 @@
|
|
|
18
18
|
async onVerify(value) {
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
function getDigest(algorithm) {
|
|
22
|
+
switch (algorithm) {
|
|
23
|
+
case "PBKDF2/SHA-512":
|
|
24
|
+
return "SHA-512";
|
|
25
|
+
case "PBKDF2/SHA-384":
|
|
26
|
+
return "SHA-384";
|
|
27
|
+
case "PBKDF2/SHA-256":
|
|
28
|
+
default:
|
|
29
|
+
return "SHA-256";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function deriveKey(parameters, salt, password) {
|
|
33
|
+
const { algorithm, cost, keyLength = 32 } = parameters;
|
|
34
|
+
const passwordKey = await crypto.subtle.importKey(
|
|
35
|
+
"raw",
|
|
36
|
+
password,
|
|
37
|
+
{ name: "PBKDF2" },
|
|
38
|
+
false,
|
|
39
|
+
["deriveKey"]
|
|
40
|
+
);
|
|
41
|
+
const derivedKey = await crypto.subtle.deriveKey(
|
|
42
|
+
{
|
|
43
|
+
name: "PBKDF2",
|
|
44
|
+
salt,
|
|
45
|
+
iterations: cost,
|
|
46
|
+
hash: getDigest(algorithm)
|
|
47
|
+
},
|
|
48
|
+
passwordKey,
|
|
49
|
+
{ name: "AES-GCM", length: keyLength * 8 },
|
|
50
|
+
true,
|
|
51
|
+
["encrypt"]
|
|
52
|
+
);
|
|
53
|
+
return {
|
|
54
|
+
derivedKey: new Uint8Array(await crypto.subtle.exportKey("raw", derivedKey))
|
|
55
|
+
};
|
|
56
|
+
}
|
|
21
57
|
function bufferStartsWith(buffer, prefix) {
|
|
22
58
|
if (prefix.length > buffer.length) {
|
|
23
59
|
return false;
|
|
@@ -98,7 +134,7 @@
|
|
|
98
134
|
counterMode = "uint32",
|
|
99
135
|
counterStart = 0,
|
|
100
136
|
counterStep = 1,
|
|
101
|
-
deriveKey,
|
|
137
|
+
deriveKey: deriveKey2,
|
|
102
138
|
timeout = 9e4
|
|
103
139
|
} = options;
|
|
104
140
|
const { nonce, keyPrefix, salt } = challenge.parameters;
|
|
@@ -114,7 +150,7 @@
|
|
|
114
150
|
if (controller?.signal.aborted || timeout && counter % 10 === 0 && performance.now() - start > timeout) {
|
|
115
151
|
return null;
|
|
116
152
|
}
|
|
117
|
-
const { derivedKey } = await
|
|
153
|
+
const { derivedKey } = await deriveKey2(
|
|
118
154
|
challenge.parameters,
|
|
119
155
|
saltBuf,
|
|
120
156
|
password.setCounter(counter)
|
|
@@ -142,7 +178,8 @@
|
|
|
142
178
|
controller = new AbortController(),
|
|
143
179
|
createWorker,
|
|
144
180
|
onOutOfMemory = (c) => c > 1 ? Math.floor(c / 2) : 0,
|
|
145
|
-
counterMode
|
|
181
|
+
counterMode,
|
|
182
|
+
timeout = 9e4
|
|
146
183
|
} = options;
|
|
147
184
|
const workersConcurrency = Math.min(16, Math.max(1, concurrency));
|
|
148
185
|
const workersInstances = [];
|
|
@@ -183,6 +220,7 @@
|
|
|
183
220
|
counterMode,
|
|
184
221
|
counterStart: i,
|
|
185
222
|
counterStep: workersConcurrency,
|
|
223
|
+
timeout,
|
|
186
224
|
type: "work"
|
|
187
225
|
});
|
|
188
226
|
});
|
|
@@ -215,7 +253,11 @@
|
|
|
215
253
|
return solution || null;
|
|
216
254
|
}
|
|
217
255
|
async function deobfuscate(obfuscatedData, options = {}) {
|
|
218
|
-
|
|
256
|
+
let {
|
|
257
|
+
concurrency = Math.max(1, Math.min(4, navigator.hardwareConcurrency)),
|
|
258
|
+
createWorker,
|
|
259
|
+
deriveKey: deriveKey$1 = deriveKey
|
|
260
|
+
} = options;
|
|
219
261
|
let challenge = null;
|
|
220
262
|
try {
|
|
221
263
|
challenge = JSON.parse(atob(obfuscatedData));
|
|
@@ -227,21 +269,20 @@
|
|
|
227
269
|
}
|
|
228
270
|
const cipher = challenge.cipher;
|
|
229
271
|
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
|
-
}
|
|
272
|
+
if (!createWorker && "$altcha" in globalThis) {
|
|
273
|
+
createWorker = globalThis.$altcha.algorithms.get(challenge.parameters.algorithm);
|
|
274
|
+
}
|
|
275
|
+
if (createWorker) {
|
|
240
276
|
solution = await solveChallengeWorkers({
|
|
241
277
|
challenge,
|
|
242
278
|
concurrency,
|
|
243
279
|
createWorker
|
|
244
280
|
});
|
|
281
|
+
} else {
|
|
282
|
+
solution = await solveChallenge({
|
|
283
|
+
challenge,
|
|
284
|
+
deriveKey: deriveKey$1
|
|
285
|
+
});
|
|
245
286
|
}
|
|
246
287
|
if (!solution) {
|
|
247
288
|
throw new Error("Unable to find solution.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";class e{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 t(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
|
|
1
|
+
!function(e){"function"==typeof define&&define.amd?define(e):e()}(function(){"use strict";class e{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 t(e){switch(e){case"PBKDF2/SHA-512":return"SHA-512";case"PBKDF2/SHA-384":return"SHA-384";default:return"SHA-256"}}async function r(e,r,n){const{algorithm:a,cost:i,keyLength:o=32}=e,s=await crypto.subtle.importKey("raw",n,{name:"PBKDF2"},!1,["deriveKey"]),c=await crypto.subtle.deriveKey({name:"PBKDF2",salt:r,iterations:i,hash:t(a)},s,{name:"AES-GCM",length:8*o},!0,["encrypt"]);return{derivedKey:new Uint8Array(await crypto.subtle.exportKey("raw",c))}}function n(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 a(e){return Array.from(new Uint8Array(e)).map(e=>e.toString(16).padStart(2,"0")).join("")}function i(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 o(e){await new Promise(t=>setTimeout(t,e))}function s(e){return Math.floor(10*(performance.now()-e))/10}var c=(e=>(e.CODE="code",e.ERROR="error",e.VERIFIED="verified",e.VERIFYING="verifying",e.UNVERIFIED="unverified",e.EXPIRED="expired",e))(c||{});class l{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 h(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 f=null;try{f=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 h({...e,challenge:t,controller:n,concurrency:r,createWorker:a})}throw r}finally{u()}return n.signal.aborted?null:f||null}async function u(e,t={}){let{concurrency:c=Math.max(1,Math.min(4,navigator.hardwareConcurrency)),createWorker:u,deriveKey:f=r}=t,d=null;try{d=JSON.parse(atob(e))}catch{throw new Error("Unable to parse obfuscated data.")}if(!d||"object"!=typeof d||!("parameters"in d)||!("cipher"in d))throw new Error("Invalid obfuscated data format.");const g=d.cipher;let y=null;if(!u&&"$altcha"in globalThis&&(u=globalThis.$altcha.algorithms.get(d.parameters.algorithm)),y=u?await h({challenge:d,concurrency:c,createWorker:u}):await async function(e){const{challenge:t,controller:r,counterMode:c="uint32",counterStart:h=0,counterStep:u=1,deriveKey:f,timeout:d=9e4}=e,{nonce:g,keyPrefix:y,salt:w}=t.parameters,m=i(g),p=i(w),b=y.length%2==0?i(y):null,E=new l(m,c),v=performance.now();let T=h,S="",A=v;for(;;){if(r?.signal.aborted||d&&T%10==0&&performance.now()-v>d)return null;const{derivedKey:e}=await f(t.parameters,p,E.setCounter(T));if(T%10==0&&performance.now()-A>200&&(await o(0),A=performance.now()),b?n(e,b):a(e).startsWith(y)){S=a(e);break}T+=u}return{counter:T,derivedKey:S,time:s(v)}}({challenge:d,deriveKey:f}),!y)throw new Error("Unable to find solution.");const w=await crypto.subtle.importKey("raw",i(y.derivedKey),{name:"AES-GCM"},!1,["decrypt"]),m=await crypto.subtle.decrypt({name:"AES-GCM",iv:i(g.iv)},w,i(g.data));return(new TextDecoder).decode(m)}e.register(class extends 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(c.VERIFYING);try{const e=await u(n);await this.#e(Math.max(0,t-(performance.now()-r))),this.#t(e)}catch(e){this.host.setState(c.ERROR,String(e))}finally{this.host.setState(c.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.3",
|
|
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 {};
|