rswag-online/frontend/vendor/@encryptid/sdk/encryptid.browser.js

72 lines
33 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var b={rpId:"jeffemmett.com",rpName:"EncryptID",origin:typeof window<"u"?window.location.origin:"",userVerification:"required",timeout:60000},u=null;function R(){if(u)u.abort(),u=null}function a(i){let t=new Uint8Array(i),o="";for(let r=0;r<t.byteLength;r++)o+=String.fromCharCode(t[r]);return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function L(i){let t=i.replace(/-/g,"+").replace(/_/g,"/"),o="=".repeat((4-t.length%4)%4),r=atob(t+o),n=new Uint8Array(r.length);for(let s=0;s<r.length;s++)n[s]=r.charCodeAt(s);return n.buffer}function C(){return crypto.getRandomValues(new Uint8Array(32)).buffer}async function q(i){let o=new TextEncoder().encode(`encryptid-prf-salt-${i}-v1`);return crypto.subtle.digest("SHA-256",o)}async function Q(i,t,o={}){R();let r={...b,...o};if(!window.PublicKeyCredential)throw Error("WebAuthn is not supported in this browser");let n=await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),s=crypto.getRandomValues(new Uint8Array(32)),e=C(),d=await q("master-key"),p={publicKey:{challenge:new Uint8Array(e),rp:{id:r.rpId,name:r.rpName},user:{id:s,name:i,displayName:t},pubKeyCredParams:[{alg:-7,type:"public-key"},{alg:-257,type:"public-key"}],authenticatorSelection:{residentKey:"required",requireResidentKey:!0,userVerification:r.userVerification,authenticatorAttachment:n?"platform":void 0},attestation:"none",timeout:r.timeout,extensions:{prf:{eval:{first:new Uint8Array(d)}},credProps:!0}}},v=await navigator.credentials.create(p);if(!v)throw Error("Failed to create credential");let M=v.response,T=v.getClientExtensionResults()?.prf?.enabled===!0,J=M.getPublicKey();if(!J)throw Error("Failed to get public key from credential");return{credentialId:a(v.rawId),publicKey:J,userId:a(s.buffer),username:i,createdAt:Date.now(),prfSupported:T,transports:M.getTransports?.()}}async function D(i,t={}){R();let o={...b,...t};if(!window.PublicKeyCredential)throw Error("WebAuthn is not supported in this browser");let r=C(),n=await q("master-key"),s=i?[{type:"public-key",id:new Uint8Array(L(i))}]:void 0,e={publicKey:{challenge:new Uint8Array(r),rpId:o.rpId,allowCredentials:s,userVerification:o.userVerification,timeout:o.timeout,extensions:{prf:{eval:{first:new Uint8Array(n)}}}}},d=await navigator.credentials.get(e);if(!d)throw Error("Authentication failed");let p=d.response,v=d.getClientExtensionResults()?.prf?.results;return{credentialId:a(d.rawId),userId:p.userHandle?a(p.userHandle):"",prfOutput:v?.first,signature:p.signature,authenticatorData:p.authenticatorData}}async function V(){if(!window.PublicKeyCredential)return!1;if(typeof PublicKeyCredential.isConditionalMediationAvailable==="function")return PublicKeyCredential.isConditionalMediationAvailable();return!1}async function X(i={}){if(!await V())return null;R(),u=new AbortController;let o={...b,...i},r=C(),n=await q("master-key");try{let s=await navigator.credentials.get({publicKey:{challenge:new Uint8Array(r),rpId:o.rpId,userVerification:o.userVerification,timeout:o.timeout,extensions:{prf:{eval:{first:new Uint8Array(n)}}}},mediation:"conditional",signal:u.signal});if(u=null,!s)return null;let e=s.response,d=s.getClientExtensionResults()?.prf?.results;return{credentialId:a(s.rawId),userId:e.userHandle?a(e.userHandle):"",prfOutput:d?.first,signature:e.signature,authenticatorData:e.authenticatorData}}catch{return null}}async function m(){let i={webauthn:!1,platformAuthenticator:!1,conditionalUI:!1,prfExtension:!1};if(!window.PublicKeyCredential)return i;i.webauthn=!0;try{i.platformAuthenticator=await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()}catch{i.platformAuthenticator=!1}return i.conditionalUI=await V(),i.prfExtension=!0,i}var K="https://encryptid.jeffemmett.com";class z{serverUrl;constructor(i=K){this.serverUrl=i.replace(/\/$/,"")}async registerStart(i,t){let o=await fetch(`${this.serverUrl}/api/register/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:i,displayName:t||i})});if(!o.ok){let r=await o.json().catch(()=>({error:"Registration start failed"}));throw Error(r.error||`HTTP ${o.status}`)}return o.json()}async registerComplete(i,t,o,r){let n=t.response,s=n.getPublicKey(),e=await fetch(`${this.serverUrl}/api/register/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({challenge:i,userId:o,username:r,credential:{credentialId:a(t.rawId),publicKey:s?a(s):"",transports:n.getTransports?.()||[]}})});if(!e.ok){let d=await e.json().catch(()=>({error:"Registration complete failed"}));throw Error(d.error||`HTTP ${e.status}`)}return e.json()}async authStart(i){let t=await fetch(`${this.serverUrl}/api/auth/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i?{credentialId:i}:{})});if(!t.ok){let o=await t.json().catch(()=>({error:"Auth start failed"}));throw Error(o.error||`HTTP ${t.status}`)}return t.json()}async authComplete(i,t){let o=t.response,r=t.getClientExtensionResults()?.prf?.results,n=await fetch(`${this.serverUrl}/api/auth/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({challenge:i,credential:{credentialId:a(t.rawId),signature:a(o.signature),authenticatorData:a(o.authenticatorData),prfOutput:r?.first?a(r.first):null}})});if(!n.ok){let s=await n.json().catch(()=>({error:"Auth complete failed"}));throw Error(s.error||`HTTP ${n.status}`)}return n.json()}async verifySession(i){return(await fetch(`${this.serverUrl}/api/session/verify`,{headers:{Authorization:`Bearer ${i}`}})).json()}async refreshToken(i){let t=await fetch(`${this.serverUrl}/api/session/refresh`,{method:"POST",headers:{Authorization:`Bearer ${i}`}});if(!t.ok){let o=await t.json().catch(()=>({error:"Token refresh failed"}));throw Error(o.error||`HTTP ${t.status}`)}return t.json()}async listCredentials(i){let t=await fetch(`${this.serverUrl}/api/user/credentials`,{headers:{Authorization:`Bearer ${i}`}});if(!t.ok)throw Error("Failed to list credentials");return t.json()}async setRecoveryEmail(i,t){let o=await fetch(`${this.serverUrl}/api/recovery/email/set`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify({email:t})});if(!o.ok){let r=await o.json().catch(()=>({error:"Failed to set recovery email"}));throw Error(r.error||`HTTP ${o.status}`)}return o.json()}async requestEmailRecovery(i){let t=await fetch(`${this.serverUrl}/api/recovery/email/request`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:i})});if(!t.ok){let o=await t.json().catch(()=>({error:"Recovery request failed"}));throw Error(o.error||`HTTP ${t.status}`)}return t.json()}async verifyRecoveryToken(i){let t=await fetch(`${this.serverUrl}/api/recovery/email/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:i})});if(!t.ok){let o=await t.json().catch(()=>({error:"Recovery verification failed"}));throw Error(o.error||`HTTP ${t.status}`)}return t.json()}async register(i,t,o){let{options:r,userId:n}=await this.registerStart(i,t),s={publicKey:{...r,challenge:l(r.challenge),user:{...r.user,id:l(r.user.id)},pubKeyCredParams:r.pubKeyCredParams,extensions:{credProps:!0,prf:{eval:{first:new Uint8Array(32)}}}}},e=await navigator.credentials.create(s);if(!e)throw Error("Failed to create credential");return this.registerComplete(r.challenge,e,n,i)}async authenticate(i,t){let{options:o}=await this.authStart(i),r={publicKey:{challenge:l(o.challenge),rpId:o.rpId,userVerification:o.userVerification,timeout:o.timeout,allowCredentials:o.allowCredentials?.map((d)=>({type:d.type,id:l(d.id),transports:d.transports})),extensions:{prf:{eval:{first:new Uint8Array(32)}}}}},n=await navigator.credentials.get(r);if(!n)throw Error("Authentication failed");let s=await this.authComplete(o.challenge,n),e=n.getClientExtensionResults()?.prf?.results;return{...s,prfOutput:e?.first}}}function l(i){let t=i.replace(/-/g,"+").replace(/_/g,"/"),o="=".repeat((4-t.length%4)%4),r=atob(t+o),n=new Uint8Array(r.length);for(let s=0;s<r.length;s++)n[s]=r.charCodeAt(s);return n}var c;((n)=>{n[n.BASIC=1]="BASIC";n[n.STANDARD=2]="STANDARD";n[n.ELEVATED=3]="ELEVATED";n[n.CRITICAL=4]="CRITICAL"})(c||={});var Z={"rspace:view-public":{minAuthLevel:1},"rspace:view-private":{minAuthLevel:2},"rspace:edit-board":{minAuthLevel:2},"rspace:create-board":{minAuthLevel:2},"rspace:delete-board":{minAuthLevel:3,maxAgeSeconds:300},"rspace:encrypt-board":{minAuthLevel:3,requiresCapability:"encrypt"},"rwallet:view-balance":{minAuthLevel:1},"rwallet:view-history":{minAuthLevel:2},"rwallet:send-small":{minAuthLevel:2,requiresCapability:"wallet"},"rwallet:send-large":{minAuthLevel:3,requiresCapability:"wallet",maxAgeSeconds:60},"rwallet:add-guardian":{minAuthLevel:4,maxAgeSeconds:60},"rwallet:remove-guardian":{minAuthLevel:4,maxAgeSeconds:60},"rvote:view-proposals":{minAuthLevel:1},"rvote:cast-vote":{minAuthLevel:3,requiresCapability:"sign",maxAgeSeconds:300},"rvote:delegate":{minAuthLevel:3,requiresCapability:"wallet"},"rfiles:list-files":{minAuthLevel:2},"rfiles:download-own":{minAuthLevel:2,requiresCapability:"encrypt"},"rfiles:upload":{minAuthLevel:2,requiresCapability:"encrypt"},"rfiles:share":{minAuthLevel:3,requiresCapability:"encrypt"},"rfiles:delete":{minAuthLevel:3,maxAgeSeconds:300},"rfiles:export-keys":{minAuthLevel:4,maxAgeSeconds:60},"rmaps:view-public":{minAuthLevel:1},"rmaps:add-location":{minAuthLevel:2},"rmaps:edit-location":{minAuthLevel:2,requiresCapability:"sign"},"account:view-profile":{minAuthLevel:2},"account:edit-profile":{minAuthLevel:3},"account:export-data":{minAuthLevel:4,maxAgeSeconds:60},"account:delete":{minAuthLevel:4,maxAgeSeconds:60},"rspace:create-space":{minAuthLevel:2},"rspace:configure-space":{minAuthLevel:3,maxAgeSeconds:300},"rspace:delete-space":{minAuthLevel:4,maxAgeSeconds:60},"rspace:invite-member":{minAuthLevel:2},"rspace:remove-member":{minAuthLevel:3,maxAgeSeconds:300},"rspace:change-visibility":{minAuthLevel:3,maxAgeSeconds:300},"rfunds:create-space":{minAuthLevel:2},"rfunds:edit-flows":{minAuthLevel:2},"rfunds:share-space":{minAuthLevel:2}},x="encryptid_session",A=300000;class j{session=null;refreshTimer=null;constructor(){this.restoreSession()}async createSession(i,t,o){let r=Math.floor(Date.now()/1000),n={iss:"https://encryptid.jeffemmett.com",sub:t,aud:["rspace.online","rwallet.online","rvote.online","rfiles.online","rmaps.online"],iat:r,exp:r+900,jti:a(crypto.getRandomValues(new Uint8Array(16)).buffer),eid:{credentialId:i.credentialId,authLevel:3,authTime:r,capabilities:o,recoveryConfigured:!1}},s=this.createUnsignedToken(n),e=this.createRefreshToken(t);return this.session={accessToken:s,refreshToken:e,claims:n,lastAuthTime:Date.now()},this.persistSession(),this.scheduleRefresh(),this.session}getSession(){return this.session}getDID(){return this.session?.claims.sub??null}getAccessToken(){return this.session?.accessToken??null}getAuthLevel(){if(!this.session)return 1;let i=Math.floor(Date.now()/1000);if(i>=this.session.claims.exp)return 1;let t=i-this.session.claims.eid.authTime;if(t<60)return 3;if(t<900)return 2;return 1}canPerform(i){let t=Z[i];if(!t)return{allowed:!1,reason:"Unknown operation"};if(!this.session)return{allowed:!1,reason:"Not authenticated"};let o=this.getAuthLevel();if(o<t.minAuthLevel)return{allowed:!1,reason:`Requires ${c[t.minAuthLevel]} auth level (current: ${c[o]})`};if(t.requiresCapability){if(!this.session.claims.eid.capabilities[t.requiresCapability])return{allowed:!1,reason:`Requires ${t.requiresCapability} capability`}}if(t.maxAgeSeconds){let r=Math.floor(Date.now()/1000)-this.session.claims.eid.authTime;if(r>t.maxAgeSeconds)return{allowed:!1,reason:`Authentication too old (${r}s > ${t.maxAgeSeconds}s)`}}return{allowed:!0}}requiresFreshAuth(i){let t=Z[i];if(!t)return!0;if(t.minAuthLevel>=4)return!0;if(t.maxAgeSeconds&&t.maxAgeSeconds<=60)return!0;return!1}upgradeAuthLevel(i=3){if(!this.session)return;this.session.claims.eid.authLevel=i,this.session.claims.eid.authTime=Math.floor(Date.now()/1000),this.session.lastAuthTime=Date.now(),this.persistSession()}clearSession(){if(this.session=null,this.refreshTimer)clearTimeout(this.refreshTimer),this.refreshTimer=null;try{localStorage.removeItem(x)}catch{}}isValid(){if(!this.session)return!1;return Math.floor(Date.now()/1000)<this.session.claims.exp}createUnsignedToken(i){return`${btoa(JSON.stringify({alg:"none",typ:"JWT"}))}.${btoa(JSON.stringify(i))}.`}createRefreshToken(i){return btoa(JSON.stringify({sub:i,iat:Math.floor(Date.now()/1000),exp:Math.floor(Date.now()/1000)+604800,jti:a(crypto.getRandomValues(new Uint8Array(16)).buffer)}))}persistSession(){if(!this.session)return;try{localStorage.setItem(x,JSON.stringify(this.session))}catch{}}restoreSession(){try{let i=localStorage.getItem(x);if(i){let t=JSON.parse(i);if(Math.floor(Date.now()/1000)<t.claims.exp)this.session=t,this.scheduleRefresh();else localStorage.removeItem(x)}}catch{}}scheduleRefresh(){if(!this.session)return;if(this.refreshTimer)clearTimeout(this.refreshTimer);let t=this.session.claims.exp*1000-A,o=Math.max(t-Date.now(),0);this.refreshTimer=setTimeout(()=>this.refreshTokens(),o)}async refreshTokens(){if(!this.session)return;let i=Math.floor(Date.now()/1000);this.session.claims.eid.authLevel=Math.min(this.session.claims.eid.authLevel,2),this.session.claims.iat=i,this.session.claims.exp=i+900,this.session.claims.jti=a(crypto.getRandomValues(new Uint8Array(16)).buffer),this.session.accessToken=this.createUnsignedToken(this.session.claims),this.persistSession(),this.scheduleRefresh()}}var $=null;function g(){if(!$)$=new j;return $}class F{masterKey=null;derivedKeys=null;fromPRF=!1;async initFromPRF(i){this.masterKey=await crypto.subtle.importKey("raw",i,{name:"HKDF"},!1,["deriveKey","deriveBits"]),this.fromPRF=!0,this.derivedKeys=null}async initFromPassphrase(i,t){let o=new TextEncoder,r=await crypto.subtle.importKey("raw",o.encode(i),{name:"PBKDF2"},!1,["deriveBits"]),n=await crypto.subtle.deriveBits({name:"PBKDF2",salt:t,iterations:600000,hash:"SHA-256"},r,256);this.masterKey=await crypto.subtle.importKey("raw",n,{name:"HKDF"},!1,["deriveKey","deriveBits"]),this.fromPRF=!1,this.derivedKeys=null}static generateSalt(){return crypto.getRandomValues(new Uint8Array(32))}isInitialized(){return this.masterKey!==null}async getKeys(){if(!this.masterKey)throw Error("Key manager not initialized");if(this.derivedKeys)return this.derivedKeys;let[i,t,o]=await Promise.all([this.deriveEncryptionKey(),this.deriveSigningKeyPair(),this.deriveDIDSeed()]),r=await this.generateDID(o);return this.derivedKeys={encryptionKey:i,signingKeyPair:t,didSeed:o,did:r,fromPRF:this.fromPRF},this.derivedKeys}async deriveEncryptionKey(){let i=new TextEncoder;return crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:i.encode("encryptid-encryption-key-v1"),info:i.encode("AES-256-GCM")},this.masterKey,{name:"AES-GCM",length:256},!1,["encrypt","decrypt","wrapKey","unwrapKey"])}async deriveSigningKeyPair(){return crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign","verify"])}async deriveDIDSeed(){let i=new TextEncoder,t=await crypto.subtle.deriveBits({name:"HKDF",hash:"SHA-256",salt:i.encode("encryptid-did-key-v1"),info:i.encode("Ed25519-seed")},this.masterKey,256);return new Uint8Array(t)}async generateDID(i){let t=await crypto.subtle.digest("SHA-256",i),o=new Uint8Array(t).slice(0,32),r=new Uint8Array([237,1]),n=new Uint8Array(34);return n.set(r),n.set(o,2),`did:key:z${a(n.buffer).replace(/-/g,"").replace(/_/g,"")}`}clear(){this.masterKey=null,this.derivedKeys=null,this.fromPRF=!1}}var E=null;function y(){if(!E)E=new F;return E}var ii='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="passkey-icon"><circle cx="12" cy="10" r="3"/><path d="M12 13v8"/><path d="M9 18h6"/><circle cx="12" cy="10" r="7"/></svg>',ti=`
:host { --eid-primary: #06b6d4; --eid-primary-hover: #0891b2; --eid-bg: #0f172a; --eid-bg-hover: #1e293b; --eid-text: #f1f5f9; --eid-text-secondary: #94a3b8; --eid-radius: 8px; display: inline-block; font-family: system-ui, -apple-system, sans-serif; }
.login-btn { display: flex; align-items: center; gap: 12px; padding: 12px 24px; background: var(--eid-primary); color: white; border: none; border-radius: var(--eid-radius); font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3); }
.login-btn:hover { background: var(--eid-primary-hover); transform: translateY(-1px); }
.login-btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
.login-btn.outline { background: transparent; border: 2px solid var(--eid-primary); color: var(--eid-primary); }
.login-btn.outline:hover { background: var(--eid-primary); color: white; }
.login-btn.small { padding: 8px 16px; font-size: 0.875rem; }
.login-btn.large { padding: 16px 32px; font-size: 1.125rem; }
.passkey-icon { width: 24px; height: 24px; }
.user-info { display: flex; align-items: center; gap: 12px; padding: 8px 16px; background: var(--eid-bg); border-radius: var(--eid-radius); color: var(--eid-text); cursor: pointer; }
.user-avatar { width: 36px; height: 36px; border-radius: 50%; background: var(--eid-primary); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 0.875rem; }
.user-did { font-size: 0.75rem; color: var(--eid-text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.auth-level { font-size: 0.625rem; padding: 2px 6px; border-radius: 4px; }
.auth-level.elevated { background: rgba(34, 197, 94, 0.2); color: #22c55e; }
.auth-level.standard { background: rgba(234, 179, 8, 0.2); color: #eab308; }
.dropdown { position: absolute; top: 100%; right: 0; margin-top: 8px; background: var(--eid-bg); border-radius: var(--eid-radius); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3); min-width: 200px; z-index: 100; overflow: hidden; }
.dropdown-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; color: var(--eid-text); cursor: pointer; transition: background 0.2s; }
.dropdown-item:hover { background: var(--eid-bg-hover); }
.dropdown-item.danger { color: #ef4444; }
.dropdown-divider { height: 1px; background: #334155; margin: 4px 0; }
.loading-spinner { width: 20px; height: 20px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
`;class Y extends HTMLElement{shadow;loading=!1;showDropdown=!1;capabilities=null;static get observedAttributes(){return["size","variant","label","show-user"]}constructor(){super();this.shadow=this.attachShadow({mode:"open"})}async connectedCallback(){if(this.capabilities=await m(),this.capabilities.conditionalUI)this.startConditionalAuth();this.render(),document.addEventListener("click",(i)=>{if(!this.contains(i.target))this.showDropdown=!1,this.render()})}attributeChangedCallback(){this.render()}get size(){return this.getAttribute("size")||"medium"}get variant(){return this.getAttribute("variant")||"primary"}get label(){return this.getAttribute("label")||"Sign in with Passkey"}get showUser(){return this.hasAttribute("show-user")}render(){let i=g(),t=i.isValid(),o=i.getDID(),r=i.getAuthLevel();this.shadow.innerHTML=`<style>${ti}</style><div class="login-container" style="position:relative">
${t&&this.showUser?this.renderUserInfo(o,r):this.renderLoginButton()}
${this.showDropdown?this.renderDropdown():""}
</div>`,this.attachEventListeners()}renderLoginButton(){let i=this.size==="medium"?"":this.size,t=this.variant==="primary"?"":this.variant;return`<button class="login-btn ${i} ${t}" ${this.loading?"disabled":""}>
${this.loading?'<div class="loading-spinner"></div>':ii}
<span>${this.loading?"Authenticating...":this.label}</span></button>`}renderUserInfo(i,t){let o=i.slice(0,20)+"..."+i.slice(-8),r=i.slice(8,10).toUpperCase(),n=c[t].toLowerCase();return`<div class="user-info"><div class="user-avatar">${r}</div>
<div><div class="user-did">${o}</div><span class="auth-level ${n}">${n}</span></div></div>`}renderDropdown(){return`<div class="dropdown">
<div class="dropdown-item" data-action="profile">Profile</div>
<div class="dropdown-item" data-action="recovery">Recovery Settings</div>
<div class="dropdown-item" data-action="upgrade">Upgrade Auth Level</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item danger" data-action="logout">Sign Out</div></div>`}attachEventListeners(){if(g().isValid()&&this.showUser)this.shadow.querySelector(".user-info")?.addEventListener("click",()=>{this.showDropdown=!this.showDropdown,this.render()}),this.shadow.querySelectorAll(".dropdown-item").forEach((t)=>{t.addEventListener("click",(o)=>{o.stopPropagation(),this.handleDropdownAction(t.dataset.action)})});else this.shadow.querySelector(".login-btn")?.addEventListener("click",()=>this.handleLogin())}async handleLogin(){if(this.loading)return;this.loading=!0,this.render();try{let i=await D(),t=y();if(i.prfOutput)await t.initFromPRF(i.prfOutput);let o=await t.getKeys();await g().createSession(i,o.did,{encrypt:!0,sign:!0,wallet:!1}),this.dispatchEvent(new CustomEvent("login-success",{detail:{did:o.did,credentialId:i.credentialId,prfAvailable:!!i.prfOutput},bubbles:!0}))}catch(i){if(i.name==="NotAllowedError"||i.message?.includes("No credential"))this.dispatchEvent(new CustomEvent("login-register-needed",{bubbles:!0}));else this.dispatchEvent(new CustomEvent("login-error",{detail:{error:i.message},bubbles:!0}))}finally{this.loading=!1,this.render()}}async handleDropdownAction(i){if(this.showDropdown=!1,i==="logout")g().clearSession(),y().clear(),this.dispatchEvent(new CustomEvent("logout",{bubbles:!0}));else if(i==="upgrade")try{await D(),g().upgradeAuthLevel(3),this.dispatchEvent(new CustomEvent("auth-upgraded",{detail:{level:3},bubbles:!0}))}catch{}else this.dispatchEvent(new CustomEvent("navigate",{detail:{path:`/${i}`},bubbles:!0}));this.render()}async startConditionalAuth(){try{let i=await X();if(i){let t=y();if(i.prfOutput)await t.initFromPRF(i.prfOutput);let o=await t.getKeys();await g().createSession(i,o.did,{encrypt:!0,sign:!0,wallet:!1}),this.dispatchEvent(new CustomEvent("login-success",{detail:{did:o.did,credentialId:i.credentialId,viaConditionalUI:!0},bubbles:!0})),this.render()}}catch{}}async register(i,t){this.loading=!0,this.render();try{let o=await Q(i,t);this.dispatchEvent(new CustomEvent("register-success",{detail:{credentialId:o.credentialId,prfSupported:o.prfSupported},bubbles:!0})),await this.handleLogin()}catch(o){this.dispatchEvent(new CustomEvent("register-error",{detail:{error:o.message},bubbles:!0}))}finally{this.loading=!1,this.render()}}}customElements.define("encryptid-login",Y);class S{config=null;activeRequest=null;constructor(){this.loadConfig()}async initializeRecovery(i=3){return this.config={threshold:i,delaySeconds:172800,guardians:[],guardianListHash:"",updatedAt:Date.now()},await this.saveConfig(),this.config}async addGuardian(i){if(!this.config)throw Error("Recovery not initialized");if(this.config.guardians.length>=7)throw Error("Maximum of 7 guardians allowed");let t={...i,id:a(crypto.getRandomValues(new Uint8Array(16)).buffer),addedAt:Date.now()};return this.config.guardians.push(t),this.config.guardianListHash=await this.hashGuardianList(),this.config.updatedAt=Date.now(),await this.saveConfig(),t}async removeGuardian(i){if(!this.config)throw Error("Recovery not initialized");let t=this.config.guardians.findIndex((r)=>r.id===i);if(t===-1)throw Error("Guardian not found");if(this.config.guardians.filter((r)=>r.id!==i).reduce((r,n)=>r+n.weight,0)<this.config.threshold)throw Error("Cannot remove guardian: would make recovery impossible");this.config.guardians.splice(t,1),this.config.guardianListHash=await this.hashGuardianList(),this.config.updatedAt=Date.now(),await this.saveConfig()}async setThreshold(i){if(!this.config)throw Error("Recovery not initialized");let t=this.config.guardians.reduce((o,r)=>o+r.weight,0);if(i>t)throw Error("Threshold cannot exceed total guardian weight");if(i<1)throw Error("Threshold must be at least 1");this.config.threshold=i,this.config.updatedAt=Date.now(),await this.saveConfig()}async setDelay(i){if(!this.config)throw Error("Recovery not initialized");if(i<3600||i>604800)throw Error("Delay must be between 1 hour and 7 days");this.config.delaySeconds=i,this.config.updatedAt=Date.now(),await this.saveConfig()}getConfig(){return this.config}isConfigured(){if(!this.config)return!1;return this.config.guardians.reduce((i,t)=>i+t.weight,0)>=this.config.threshold}async verifyGuardian(i){if(!this.config)throw Error("Recovery not initialized");let t=this.config.guardians.find((o)=>o.id===i);if(!t)throw Error("Guardian not found");return t.lastVerified=Date.now(),await this.saveConfig(),!0}async initiateRecovery(i){if(!this.config)throw Error("Recovery not configured");if(this.activeRequest?.status==="pending")throw Error("Recovery already in progress");let t=Date.now();return this.activeRequest={id:a(crypto.getRandomValues(new Uint8Array(16)).buffer),accountDID:"",newCredentialId:i,initiatedAt:t,completesAt:t+this.config.delaySeconds*1000,status:"pending",approvals:[],approvalWeight:0},this.activeRequest}async approveRecovery(i,t){if(!this.activeRequest||this.activeRequest.status!=="pending")throw Error("No pending recovery request");if(!this.config)throw Error("Recovery not configured");let o=this.config.guardians.find((r)=>r.id===i);if(!o)throw Error("Guardian not found");if(this.activeRequest.approvals.some((r)=>r.guardianId===i))throw Error("Guardian already approved");if(this.activeRequest.approvals.push({guardianId:i,approvedAt:Date.now(),signature:t}),this.activeRequest.approvalWeight+=o.weight,this.activeRequest.approvalWeight>=this.config.threshold)this.activeRequest.status="approved";return this.activeRequest}async cancelRecovery(){if(!this.activeRequest||this.activeRequest.status!=="pending")throw Error("No pending recovery request to cancel");this.activeRequest.status="cancelled",this.activeRequest=null}async completeRecovery(){if(!this.activeRequest)throw Error("No recovery request");if(this.activeRequest.status!=="approved")throw Error("Recovery not approved");if(Date.now()<this.activeRequest.completesAt){let i=this.activeRequest.completesAt-Date.now();throw Error(`Time-lock not expired. ${Math.ceil(i/1000/60)} minutes remaining.`)}this.activeRequest.status="completed",this.activeRequest=null}getActiveRequest(){return this.activeRequest}async hashGuardianList(){if(!this.config)return"";let i=this.config.guardians.map((o)=>o.id).sort().join(","),t=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(i));return a(t)}async saveConfig(){if(!this.config)return;try{localStorage.setItem("encryptid_recovery",JSON.stringify(this.config))}catch{}}loadConfig(){try{let i=localStorage.getItem("encryptid_recovery");if(i)this.config=JSON.parse(i)}catch{}}}var k=null;function G(){if(!k)k=new S;return k}function N(i){switch(i){case"secondary_passkey":return{name:"Backup Passkey",description:"Another device you own (phone, YubiKey, etc.)",icon:"key",setupInstructions:"Register a passkey on a second device you control."};case"trusted_contact":return{name:"Trusted Contact",description:"A friend or family member with their own EncryptID",icon:"user",setupInstructions:"Ask a trusted person to create an EncryptID account."};case"hardware_key":return{name:"Hardware Security Key",description:"A YubiKey or similar device stored offline",icon:"shield",setupInstructions:"Register a hardware security key and store it safely."};case"institutional":return{name:"Recovery Service",description:"A professional recovery service provider",icon:"building",setupInstructions:"Connect with a trusted recovery service."};case"time_delayed_self":return{name:"Time-Delayed Self",description:"Recover yourself after a waiting period",icon:"clock",setupInstructions:"Set up a recovery option that requires waiting before completing."};default:return{name:"Unknown",description:"Unknown guardian type",icon:"question",setupInstructions:""}}}class I extends HTMLElement{shadow;constructor(){super();this.shadow=this.attachShadow({mode:"open"})}connectedCallback(){let i=G();if(!i.getConfig())i.initializeRecovery(3).then(()=>this.render());else this.render()}render(){let t=G().getConfig(),o=t?.guardians??[],r=t?.threshold??3,n=o.reduce((e,d)=>e+d.weight,0),s=n>=r;this.shadow.innerHTML=`
<style>
:host { display: block; font-family: system-ui, sans-serif; color: #f1f5f9; }
.setup { background: #0f172a; border-radius: 8px; padding: 24px; max-width: 600px; }
h2 { margin: 0 0 8px; font-size: 1.5rem; }
.subtitle { color: #94a3b8; font-size: 0.875rem; margin: 0 0 24px; }
.status { display: flex; align-items: center; gap: 16px; padding: 16px; background: #1e293b; border-radius: 8px; margin-bottom: 24px; }
.dot { width: 12px; height: 12px; border-radius: 50%; background: ${s?"#22c55e":"#eab308"}; }
.guardian { display: flex; align-items: center; gap: 16px; padding: 16px; background: #1e293b; border-radius: 8px; margin-bottom: 12px; border: 1px solid #475569; }
.icon { width: 48px; height: 48px; border-radius: 50%; background: #334155; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
.info { flex: 1; }
.name { font-weight: 500; }
.type { font-size: 0.75rem; color: #94a3b8; }
</style>
<div class="setup">
<h2>Social Recovery</h2>
<p class="subtitle">Set up guardians to recover your account without seed phrases</p>
<div class="status">
<div class="dot"></div>
<div>
<div>${s?"Recovery Configured":"Setup Incomplete"}</div>
<div style="font-size:0.75rem;color:#94a3b8">${n}/${r} guardians</div>
</div>
</div>
${o.map((e)=>{let d=N(e.type);return`<div class="guardian"><div class="icon">${d.icon==="key"?"\uD83D\uDD11":d.icon==="user"?"\uD83D\uDC64":d.icon==="shield"?"\uD83D\uDEE1":d.icon==="building"?"\uD83C\uDFE2":"⏰"}</div><div class="info"><div class="name">${e.name}</div><div class="type">${d.name}</div></div></div>`}).join("")}
</div>
`}}customElements.define("encryptid-guardian-setup",I);var w="encryptid_token",f="encryptid_user",oi="https://encryptid.jeffemmett.com",h=new z(oi);async function U(){let i=await h.authenticate(),t={did:i.did,username:i.username,token:i.token};return localStorage.setItem(w,i.token),localStorage.setItem(f,JSON.stringify(t)),t}async function B(i,t){let o=await h.register(i,t),r={did:o.did,username:i,token:o.token};return localStorage.setItem(w,o.token),localStorage.setItem(f,JSON.stringify(r)),r}function P(){localStorage.removeItem(w),localStorage.removeItem(f),localStorage.removeItem("encryptid_session")}function O(){return!!localStorage.getItem(w)}function W(){let i=localStorage.getItem(f);if(!i)return null;try{return JSON.parse(i)}catch{return null}}function H(){return localStorage.getItem(w)}function ri(i){if(O())return!0;let t=i||window.location.href;return window.location.href=`/?login=required&return=${encodeURIComponent(t)}`,!1}async function ni(i){let t=H();if(!t)throw Error("Not authenticated");await h.setRecoveryEmail(t,i)}async function si(i){await h.requestEmailRecovery(i)}function ei(i){let t=document.getElementById(i);if(!t){console.warn(`EncryptID: Container #${i} not found`);return}function o(){let r=W();if(r)t.innerHTML=`
<div style="display:flex;align-items:center;gap:12px;padding:8px 16px;background:rgba(6,182,212,0.1);border-radius:8px;font-family:system-ui,sans-serif;">
<span style="color:#06b6d4;font-weight:500;">${r.username||r.did.slice(0,16)+"..."}</span>
<button id="eid-signout" style="background:none;border:1px solid #64748b;color:#94a3b8;padding:4px 12px;border-radius:6px;cursor:pointer;font-size:0.875rem;">Sign Out</button>
</div>`,document.getElementById("eid-signout")?.addEventListener("click",()=>{P(),o(),t.dispatchEvent(new CustomEvent("encryptid-logout",{bubbles:!0}))});else t.innerHTML=`
<button id="eid-signin" style="display:flex;align-items:center;gap:8px;padding:10px 20px;background:#06b6d4;color:white;border:none;border-radius:8px;cursor:pointer;font-weight:500;font-family:system-ui,sans-serif;font-size:0.95rem;">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:20px;height:20px;">
<circle cx="12" cy="10" r="3"/><path d="M12 13v8"/><path d="M9 18h6"/><circle cx="12" cy="10" r="7"/>
</svg>
Sign in with Passkey
</button>`,document.getElementById("eid-signin")?.addEventListener("click",async()=>{try{let n=await U();o(),t.dispatchEvent(new CustomEvent("encryptid-login",{bubbles:!0,detail:n}))}catch(n){if(n.name==="NotAllowedError"||n.message?.includes("No credential")){let s=prompt("No passkey found. Register with a username:");if(s)try{let e=await B(s);o(),t.dispatchEvent(new CustomEvent("encryptid-login",{bubbles:!0,detail:e}))}catch(e){alert(`Registration failed: ${e.message}`)}}else console.error("EncryptID auth error:",n)}})}o()}async function ai(){let i=H();if(!i)return!1;try{if((await h.verifySession(i)).valid)return!0;try{let o=await h.refreshToken(i);localStorage.setItem(w,o.token);let r=W();if(r)r.token=o.token,localStorage.setItem(f,JSON.stringify(r));return!0}catch{return P(),!1}}catch{return!1}}var _={client:h,authenticate:U,register:B,logout:P,isAuthenticated:O,getUser:W,getToken:H,requireAuth:ri,setRecoveryEmail:ni,requestRecovery:si,renderAuthButton:ei,verifySession:ai,detectCapabilities:m,AuthLevel:c,VERSION:"0.1.0"};if(typeof window<"u")window.EncryptID=_;var Xi=_;export{Xi as default};