altcha
Version:
Privacy-first CAPTCHA widget, compliant with global regulations (GDPR/HIPAA/CCPA/LGDP/DPDPA/PIPL) and WCAG accessible. No tracking, self-verifying.
5 lines (4 loc) • 9.59 kB
JavaScript
(function(c,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(c=typeof globalThis<"u"?globalThis:c||self,a(c["[name]"]={}))})(this,function(c){"use strict";var ge=Object.defineProperty;var _=c=>{throw TypeError(c)};var we=(c,a,u)=>a in c?ge(c,a,{enumerable:!0,configurable:!0,writable:!0,value:u}):c[a]=u;var y=(c,a,u)=>we(c,typeof a!="symbol"?a+"":a,u),R=(c,a,u)=>a.has(c)||_("Cannot "+u);var E=(c,a,u)=>(R(c,a,"read from private field"),u?u.call(c):a.get(c)),P=(c,a,u)=>a.has(c)?_("Cannot add the same private member more than once"):a instanceof WeakSet?a.add(c):a.set(c,u);var h=(c,a,u)=>(R(c,a,"access private method"),u);var w,b,l,k,H,O,v,q,M,$,S,B,G,Y;const a={generateKey:u,exportKey:D,importKey:V,decrypt:X,encrypt:J};async function u(t=256){return crypto.subtle.generateKey({name:"AES-GCM",length:t},!0,["encrypt","decrypt"])}async function D(t){return new Uint8Array(await crypto.subtle.exportKey("raw",t))}async function V(t){return crypto.subtle.importKey("raw",t,{name:"AES-GCM"},!0,["encrypt","decrypt"])}async function J(t,i,e=16){const r=crypto.getRandomValues(new Uint8Array(e));return{encrypted:new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:r},t,i)),iv:r}}async function X(t,i,e){return new Uint8Array(await crypto.subtle.decrypt({name:"AES-GCM",iv:e},t,i))}function Q(t,i=!1){return i&&(t=t.replace(/_/g,"/").replace(/-/g,"+")+"=".repeat(3-(3+t.length)%4)),Uint8Array.from(atob(t),e=>e.charCodeAt(0))}function x(t,i=!1){const e=btoa(String.fromCharCode(...t));return i?e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""):e}function L(t,i=80){let e="";for(;t.length>0;)e+=t.slice(0,i)+`
`,t=t.slice(i);return e}function C(t){return Q(t.split(/\r?\n/).filter(i=>!i.startsWith("-----")).join(""))}const m="RSA-OAEP",U="SHA-256",W=2048,Z=new Uint8Array([1,0,1]),ee={generateKeyPair:te,encrypt:ne,decrypt:re,exportPrivateKey:I,exportPrivateKeyPem:se,exportPublicKey:A,exportPublicKeyPem:ie,exportPublicKeyFromPrivateKey:ae,importPrivateKey:z,importPrivateKeyPem:oe,importPublicKey:T,importPublicKeyPem:N};async function te(){return crypto.subtle.generateKey({name:m,modulusLength:W,publicExponent:Z,hash:U},!0,["encrypt","decrypt"])}async function ne(t,i){return new Uint8Array(await crypto.subtle.encrypt({name:m},t,i))}async function re(t,i){return new Uint8Array(await crypto.subtle.decrypt({name:m},t,i))}async function A(t){return new Uint8Array(await crypto.subtle.exportKey("spki",t))}async function I(t){return new Uint8Array(await crypto.subtle.exportKey("pkcs8",t))}async function ie(t){return`-----BEGIN PUBLIC KEY-----
`+L(x(await A(t)),64)+"-----END PUBLIC KEY-----"}async function se(t){return`-----BEGIN PRIVATE KEY-----
`+L(x(await I(t)),64)+"-----END PRIVATE KEY-----"}async function T(t){return crypto.subtle.importKey("spki",t,{name:m,hash:U},!0,["encrypt"])}async function N(t){return T(C(t))}async function z(t){return crypto.subtle.importKey("pkcs8",t,{name:m,hash:U},!0,["decrypt"])}async function oe(t){return z(C(t))}async function ae(t){const i=await crypto.subtle.exportKey("jwk",t);delete i.d,delete i.dp,delete i.dq,delete i.q,delete i.qi,i.key_ops=["encrypt"];const e=await crypto.subtle.importKey("jwk",i,{name:m,hash:U},!0,["encrypt"]);return A(e)}const ce=new Uint8Array([1,0,1]),le=256,pe=16;async function ue(t,i,e={}){const{aesIVLength:r=pe,aesKeyLength:n=le}=e,o=await a.generateKey(n),{encrypted:p,iv:s}=await a.encrypt(o,i,r),d=await ee.encrypt(t,await a.exportKey(o));return new Uint8Array([...ce,...new Uint8Array([d.length]),...new Uint8Array([s.length]),...d,...s,...p])}class F{constructor(i){this.context=i}static register(i){typeof globalThis.altchaPlugins!="object"&&(globalThis.altchaPlugins=[]),globalThis.altchaPlugins.includes(i)||globalThis.altchaPlugins.push(i)}destroy(){}onErrorChange(i){}onStateChange(i){}}y(F,"pluginName");class K extends F{constructor(e){super(e);P(this,l);y(this,"pendingFiles",[]);y(this,"uploadHandles",[]);y(this,"elForm");P(this,w,h(this,l,M).bind(this));P(this,b,h(this,l,$).bind(this));this.elForm=this.context.el.closest("form"),this.elForm&&(this.elForm.addEventListener("change",E(this,w)),this.elForm.addEventListener("submit",E(this,b),{capture:!0}))}addFile(e,r){this.pendingFiles.find(([n,o])=>n===e&&o===r)||this.pendingFiles.push([e,r])}destroy(){this.elForm&&(this.elForm.removeEventListener("change",E(this,w)),this.elForm.removeEventListener("submit",E(this,b)))}async uploadPendingFiles(){var r;const e=async()=>{const n=this.pendingFiles[0];if(n&&await h(this,l,S).call(this,h(this,l,H).call(this,n)),this.pendingFiles.length)return e()};try{await e()}catch(n){return this.context.log("upload failed",n),this.context.dispatch("uploaderror",{error:n}),!1}this.pendingFiles.length===0&&(h(this,l,k).call(this),(r=this.elForm)==null||r.requestSubmit())}}w=new WeakMap,b=new WeakMap,l=new WeakSet,k=function(){var r,n,o;const e=this.uploadHandles.reduce((p,s)=>(p[s.fieldName]||(p[s.fieldName]=[]),s.fileId&&p[s.fieldName].push(s.fileId),p),{});for(const p in e){const s=document.createElement("input");s.name=p,s.type="hidden",s.value=e[p].join(","),(n=(r=this.elForm)==null?void 0:r.querySelector(`[name="${p}"]`))==null||n.setAttribute("disabled","disabled"),(o=this.elForm)==null||o.appendChild(s)}},H=function(e){const r=this.pendingFiles.findIndex(([o,p])=>o===e[0]&&p===e[1]);if(r<0)throw new Error("Cannot create upload handle.");const n=new de(e[0],e[1]);return this.uploadHandles.push(n),this.pendingFiles.splice(r,1),h(this,l,O).call(this,n),h(this,l,v).call(this),n},O=function(e){this.context.dispatch("upload",{handle:e})},v=function(){const e=this.pendingFiles.reduce((n,[o,p])=>n+p.size,0)+this.uploadHandles.reduce((n,{uploadSize:o})=>n+o,0),r=this.uploadHandles.reduce((n,{loaded:o})=>n+o,0);this.context.dispatch("uploadprogress",{bytesLoaded:r,bytesTotal:e,pendingFiles:this.pendingFiles,uploadHandles:this.uploadHandles})},q=function(){if(this.elForm){const e=this.elForm.getAttribute("action"),r=this.elForm.getAttribute("data-upload-url");if(r)return r;const n=new URL(e||location.origin);return n.pathname=n.pathname+"/file",n.toString()}return null},M=function(e){const r=e.target;if(r&&r.type==="file"){const n=r.files;if(n!=null&&n.length)for(const o of n)this.addFile(r.name,o)}},$=function(e){const r=e.target;r!=null&&r.hasAttribute("data-code-challenge-form")||this.pendingFiles.length&&(e.preventDefault(),e.stopPropagation(),this.uploadPendingFiles())},S=async function(e,r){const n=h(this,l,q).call(this);if(!n)throw new Error("Upload url not specified.");const o={"content-type":"application/json"};r&&(o.authorization="Altcha payload="+r);const p=await fetch(n,{body:JSON.stringify({name:e.file.name||"file",size:e.file.size,type:e.file.type||"application/octet-stream"}),credentials:"include",headers:o,method:"POST"});if(p.status===401)return h(this,l,B).call(this,p,e);if(p.status!==200)throw new Error(`Unexpected server response ${p.status}.`);const s=await p.json();let d=e.file;if(s.encrypted&&s.encryptionPublicKey){const f=await N(s.encryptionPublicKey),ye=await new Response(new ReadableStream({async start(j){const he=e.file.stream().getReader();for(;;){const{done:fe,value:me}=await he.read();if(fe)break;j.enqueue(me)}j.close()}})).arrayBuffer();d=await ue(f,new Uint8Array(ye))}return e.uploadSize=d instanceof Uint8Array?d.byteLength:e.file.size,await h(this,l,Y).call(this,s.uploadUrl,e,d,{"content-type":e.file.type||"application/octet-stream"}),s.finalizeUrl&&await h(this,l,G).call(this,s.finalizeUrl,e.uploadSize),e.fileId=s.fileId,e.resolve({encrypted:s.encrypted,fileId:s.fileId}),e.promise},B=async function(e,r){var n;try{const o=e.headers.get("www-authenticate"),p=(n=o==null?void 0:o.match(/challenge=(.*),/))==null?void 0:n[1];if(!p)throw new Error("Unable to retrieve altcha challenge from www-authenticate header.");const s=JSON.parse(p);if(s&&"challenge"in s){const{solution:d}=await this.context.solve(s);if(d&&"number"in d)return h(this,l,S).call(this,r,btoa(JSON.stringify({...s,number:d.number})));throw new Error("Invalid challenge solution.")}}catch(o){throw this.context.log(o),new Error("Unable to solve altcha challenge for upload.")}},G=async function(e,r){const n=await fetch(e,{body:JSON.stringify({uploadedBytes:r}),headers:{"content-type":"application/json"},method:"POST"});if(n.status>204)throw new Error(`Unexpected server response ${n.status}.`);return!0},Y=async function(e,r,n,o={}){var p;return e=new URL(e,((p=this.elForm)==null?void 0:p.getAttribute("action"))||location.origin).toString(),new Promise((s,d)=>{const f=new XMLHttpRequest;r.controller.signal.addEventListener("abort",()=>{f.abort()}),f.upload.addEventListener("progress",g=>{r.setProgress(g.loaded),h(this,l,v).call(this)}),f.addEventListener("error",g=>{d(new Error("Upload failed."))}),f.addEventListener("load",()=>{f.status>=400?d(new Error(`Server responded with ${f.status}`)):s(void 0)}),f.open("PUT",e);for(const g in o)f.setRequestHeader(g,o[g]);f.send(n)})},y(K,"pluginName","upload");class de{constructor(i,e){y(this,"controller",new AbortController);y(this,"promise");y(this,"fileId");y(this,"loaded",0);y(this,"progress",0);y(this,"uploadSize",0);y(this,"resolve");y(this,"reject");this.fieldName=i,this.file=e,this.uploadSize=this.file.size,this.promise=new Promise((r,n)=>{this.resolve=r,this.reject=n})}abort(){this.controller.abort()}setProgress(i){this.loaded=i,this.progress=this.file.size&&i?Math.min(1,i/this.file.size):0}}F.register(K),c.PluginUpload=K,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});