dns-worker 1.1.2 → 1.1.3-canary.b3e2e9f

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.
@@ -429,7 +429,7 @@ echo "Please change your system DNS to 127.0.0.1"
429
429
  JOIN passkeys pk ON upwk.passkey_id = pk.id
430
430
  WHERE pk.user_id = ?
431
431
  LIMIT 1
432
- `).bind(t.id).first()),d?Response.json({encryptedSk:d.encrypted_sk,iv:d.iv}):new Response("Wrapped key not found for this passkey",{status:404})}if(c==="init"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{publicKey:l,passkeyId:d,encryptedSk:u,iv:f,recovery:p}=a;if(!l||!d||!u||!f)return new Response("Missing required fields (publicKey, passkeyId, encryptedSk, iv)",{status:400});let m=await i.getByIdOrCredentialId(d,t.id);if(!m){let w=await i.listByUser(t.id);w.length>0&&(m=w[0])}if(!m)return new Response("Passkey not found or unauthorized",{status:403});let h=Math.floor(Date.now()/1e3),g=typeof l=="string"?l:JSON.stringify(l),b=[e.DB.prepare("INSERT INTO user_log_keys (profile_id, public_key, created_at) VALUES (?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET public_key = excluded.public_key, created_at = excluded.created_at").bind(r.id,g,h),e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(m.id,r.id,u,f,h)];return p&&p.encryptedSk&&p.iv&&p.salt&&b.push(e.DB.prepare("INSERT INTO user_recovery_wrapped_keys (profile_id, encrypted_sk, iv, salt, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, salt = excluded.salt, created_at = excluded.created_at").bind(r.id,p.encryptedSk,p.iv,p.salt,h)),await e.DB.batch(b),en(r.id),Response.json({success:!0})}if(c==="wrap"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{passkeyId:l,encryptedSk:d,iv:u}=a;if(!l||!d||!u)return new Response("Missing required fields (passkeyId, encryptedSk, iv)",{status:400});let f=await i.getByIdOrCredentialId(l,t.id);if(!f){let m=await i.listByUser(t.id);m.length>0&&(f=m[0])}if(!f)return new Response("Passkey not found or unauthorized",{status:403});let p=Math.floor(Date.now()/1e3);return await e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(f.id,r.id,d,u,p).run(),Response.json({success:!0})}return n.method==="DELETE"&&(!c||c==="keys")?(await e.DB.batch([e.DB.prepare("DELETE FROM user_log_keys WHERE profile_id = ?").bind(r.id),e.DB.prepare("DELETE FROM user_passkey_wrapped_keys WHERE profile_id = ?").bind(r.id),e.DB.prepare("DELETE FROM user_recovery_wrapped_keys WHERE profile_id = ?").bind(r.id)]),en(r.id),Response.json({success:!0})):new Response("Not Found",{status:404})}async function cl(n,e,t,r){let o=new URL(n.url).pathname.split("/").filter(Boolean),i=new q(e.DB);if(o.length===2)return t?qc(n,e,t,o):new Response("Unauthorized",{status:401});if(o.length>=3){let c=o[2],a=await i.getById(c);if(!a)return new Response("Profile Not Found",{status:404});if(!t)return new Response("Unauthorized",{status:401});if(!Yn.canAccessProfile(t,a))return new Response("Profile Not Found",{status:404});let l=o[3];if(!l||["rotate_key","settings","test"].includes(l))return el(n,e,t,a,o,r);switch(l){case"access_points":return tl(n,e,t,c,o,r);case"rules":return nl(n,e,t,c,o,r);case"lists":return sl(n,e,t,c,o,r);case"logs":case"analytics":return il(n,e,t,a,o,r);case"e2ee":return al(n,e,t,a,o,r);default:return new Response("Not Found",{status:404})}}return new Response("Not Found",{status:404})}pe();function Bi(n){if(!Number.isFinite(n)||n<=0)return"0 B";let e=["B","KB","MB","GB","TB"],t=Math.min(Math.floor(Math.log(n)/Math.log(1024)),e.length-1),r=n/Math.pow(1024,t);return`${r<10?r.toFixed(2):r<100?r.toFixed(1):r.toFixed(0)} ${e[t]}`}async function ll(n){let e=n.DB;if(typeof e?.getDatabaseSize=="function")try{let t=e.getDatabaseSize();return{size_bytes:t,formatted:Bi(t),mode:"serverfull"}}catch(t){console.warn("[dbSize] Serverfull size calculation warning:",t)}try{let t=await n.DB.prepare("SELECT name FROM sqlite_master WHERE type='table';").all(),r=new Set((t.results||[]).map(i=>i.name)),s=[];r.has("profile_blooms")&&s.push("(SELECT coalesce(sum(length(bloom_filter_chunk)), 0) FROM profile_blooms)"),r.has("list_blooms")&&s.push("(SELECT coalesce(sum(length(bloom_filter_chunk)), 0) FROM list_blooms)"),r.has("profile_blooms_staging")&&s.push("(SELECT coalesce(sum(length(bloom_filter_chunk)), 0) FROM profile_blooms_staging)"),r.has("logs")&&s.push("(SELECT count(*) * 512 FROM logs)"),r.has("user_activity_log")&&s.push("(SELECT count(*) * 256 FROM user_activity_log)"),r.has("domain_hourly_rollups")&&s.push("(SELECT count(*) * 128 FROM domain_hourly_rollups)"),r.has("rules")&&s.push("(SELECT count(*) * 256 FROM rules)"),r.has("lists")&&s.push("(SELECT count(*) * 512 FROM lists)"),r.has("users")&&s.push("(SELECT count(*) * 1024 FROM users)"),r.has("profiles")&&s.push("(SELECT count(*) * 512 FROM profiles)"),r.has("access_points")&&s.push("(SELECT count(*) * 256 FROM access_points)"),r.has("sessions")&&s.push("(SELECT count(*) * 256 FROM sessions)"),r.has("passkeys")&&s.push("(SELECT count(*) * 512 FROM passkeys)"),r.has("kem_keys")&&s.push("(SELECT count(*) * 1024 FROM kem_keys)");let o=65536;if(s.length>0){let i=`SELECT (${s.join(" + ")} + 65536) AS size_bytes;`,c=await n.DB.prepare(i).first();c?.size_bytes!==void 0&&c.size_bytes!==null&&(o=Math.max(65536,Number(c.size_bytes)))}return{size_bytes:o,formatted:Bi(o),mode:"serverless"}}catch(t){return console.warn("[dbSize] Serverless D1 size calculation warning:",t),{size_bytes:65536,formatted:Bi(65536),mode:"serverless"}}}async function dl(n,e,t,r,s){let o=new Y(e.DB,e),i=new Ie(e.DB),c=r[2];if(c==="me"){if(n.method==="GET"){let a=await o.getById(t.id),l=e.MAX_LOG_RETENTION_DAYS!==void 0&&e.MAX_LOG_RETENTION_DAYS!==""?Number(e.MAX_LOG_RETENTION_DAYS):30,d=e.ADMIN_USER_MAX_LOG_RETENTION_DAYS!==void 0&&e.ADMIN_USER_MAX_LOG_RETENTION_DAYS!==""?Math.min(Number(e.ADMIN_USER_MAX_LOG_RETENTION_DAYS),l):l,u=e.NORMAL_USER_MAX_LOG_RETENTION_DAYS!==void 0&&e.NORMAL_USER_MAX_LOG_RETENTION_DAYS!==""?Math.min(Number(e.NORMAL_USER_MAX_LOG_RETENTION_DAYS),l):Math.min(7,l),p=await new ae(e.DB).countByUser(t.id),m=!!a?.totp_enabled||p>0,h=!1;if(a?.totp_recovery_keys)try{let b=typeof a.totp_recovery_keys=="string"?JSON.parse(a.totp_recovery_keys):a.totp_recovery_keys;Array.isArray(b)&&b.length>0&&(h=!0)}catch{h=!0}else a?.totp_recovery_keys_encrypted&&(h=!0);let g=t.role==="admin"?Oe(e.JWT_SECRET)&&!bt(e.JWT_SECRET):!1;return new Response(JSON.stringify({id:t.id,username:a?.username||"",role:t.role,totp_enabled:!!a?.totp_enabled,totp_skip_password:!!a?.totp_skip_password,passkeys_count:p,mfa_enabled:m,has_recovery_keys:h,recovery_keys_encrypted:!!a?.totp_recovery_keys_encrypted,timezone:a?.timezone||null,locale:a?.locale||"en-US",password_version:a?.password_version??1,pin_enabled:!!a?.pin_hash,session_lock_timeout:a?.session_lock_timeout??15,max_log_retention_days:t.role==="admin"?d:u,jwt_secret_warning:g}),{headers:{"Content-Type":"application/json"}})}if(n.method==="PATCH"){let a=await n.json();try{if(a.session_lock_timeout!==void 0){let l=Number(a.session_lock_timeout);await o.updateSessionLockTimeout(t.id,l)}if(a.username!==void 0){let l=a.username;if(!l||!Rt.test(l))return new Response("Username format error",{status:400});await o.updateUsername(t.id,l)}if(a.timezone!==void 0){let l=a.timezone;if(l!==null&&typeof l=="string"&&l!=="")try{Intl.DateTimeFormat(void 0,{timeZone:l})}catch{return new Response("Invalid timezone format",{status:400})}await o.updateTimezone(t.id,l||null)}if(a.locale!==void 0){let l=a.locale;if(l!==null&&typeof l=="string"&&l!=="")try{Intl.DateTimeFormat(l)}catch{return new Response("Invalid locale format",{status:400})}await o.updateLocale(t.id,l||"en-US")}return new Response(JSON.stringify({success:!0}))}catch(l){return l.message?.includes("UNIQUE constraint failed")?new Response("The username is already taken",{status:400}):new Response("Failed to update settings",{status:500})}}return new Response("Method Not Allowed",{status:405})}if(c==="db-size"&&n.method==="GET"){let a=await ll(e);return new Response(JSON.stringify(a),{headers:{"Content-Type":"application/json"}})}if(c==="logs"&&n.method==="DELETE"){await i.deleteByOwner(t.id);let a=e.DB;return typeof a?.vacuum=="function"&&a.vacuum(),new Response(JSON.stringify({success:!0}))}if(c==="delete"&&n.method==="POST"){let{invalidateSession:a}=await Promise.resolve().then(()=>(pe(),yr)),l=n.headers.get("Cookie")||"",d=Le(l),u=d&&ve(d)?.sid||null;return u&&await a(e,u),await o.delete(t.id),new Response(JSON.stringify({success:!0}),{headers:{"Set-Cookie":Qe(),"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}Te();Te();ge();async function ln(n,e,t,r){if(e.passkeyAssertion){let o=caches.default,i=await P.get(o,`webauthn_auth_challenge:${n.id}`);if(!i)return{success:!1,error:"Passkey session expired, please try again",reason:"passkey_session_expired"};let c=new ae(t.DB),l=(await c.listByUser(n.id)).find(u=>u.credential_id===e.passkeyAssertion.id);if(!l)return{success:!1,error:"Passkey not found",reason:"passkey_not_found"};let d=new URL(r.url);try{let{signCount:u}=await Nt({clientDataJSON:e.passkeyAssertion.response.clientDataJSON,authenticatorData:e.passkeyAssertion.response.authenticatorData,signature:e.passkeyAssertion.response.signature,publicKeySpki:l.public_key,algorithm:l.algorithm,expectedChallenge:i.challenge,expectedOrigin:`${d.protocol}//${d.host}`,expectedRpId:i.rpId,previousSignCount:l.sign_count});return await c.updateUsage(l.id,u),await P.delete(o,`webauthn_auth_challenge:${n.id}`),{success:!0,method:"passkey"}}catch{return{success:!1,error:"Invalid Passkey signature",reason:"invalid_passkey"}}}if(e.totpTokenHash)return!n.totp_enabled||!n.totp_secret?{success:!1,error:"TOTP is not enabled",reason:"totp_not_enabled"}:await Kr(n.totp_secret,e.totpTokenHash,e.totpSalt)?{success:!0,method:"totp"}:{success:!1,error:"Invalid TOTP code",reason:"invalid_totp"};let s=e.oldPassword||e.password;if(s)return await yt(s,n.hashed_password,n.password_version??1)?{success:!0,method:"password"}:{success:!1,error:"Current password is incorrect",reason:"wrong_current_password"};if(e.recoveryKey){let o=[];try{o=typeof n.totp_recovery_keys=="string"?JSON.parse(n.totp_recovery_keys):n.totp_recovery_keys||[]}catch{}return await Gn(e.recoveryKey,o)===-1?{success:!1,error:"Invalid Recovery Key",reason:"invalid_recovery_key"}:{success:!0,method:"recovery_key"}}return{success:!1,error:"Authentication required (Passkey, Recovery Key, or Password)",reason:"no_credentials_provided"}}async function ul(n,e,t,r,s){let o=new Y(e.DB,e),i=new oe(e.DB),c=n.headers.get("CF-Connecting-IP")||"127.0.0.1",a=n.headers.get("User-Agent"),l=r[2],d=t.sessionId?await re(t.sessionId,t.id):null;if(l==="password"&&n.method==="POST"){let u=await n.json(),{newPassword:f}=u;if(!f||!St.test(f))return new Response("Password format error",{status:400});let p=await o.getById(t.id);if(!p)return new Response("User not found",{status:404});let m=await ln(p,u,e,n);if(!m.success)return await i.record(t.id,"password_change_fail",c,a,{reason:m.reason},d),new Response(m.error||"Authentication failed",{status:400});let h=await ot(f,2);return await o.updatePassword(t.id,h,2),await i.record(t.id,"password_change_success",c,a,{method:m.method},d),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}if(l==="migrate-password"&&n.method==="POST"){let u=await n.json(),{clientHash:f}=u;if(!f)return new Response("Missing clientHash",{status:400});let p=await o.getById(t.id);if(!p)return new Response("User not found",{status:404});let m=await ln(p,u,e,n);if(!m.success)return await i.record(t.id,"password_change_fail",c,a,{reason:m.reason,flow:"migration"},d),new Response(m.error||"Authentication failed",{status:400});if((p.password_version??1)===1){let h=await ot(f,2);await o.updatePassword(t.id,h,2),await i.record(t.id,"password_change_success",c,a,{method:"migration"},d)}return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}Te();async function pl(n,e,t,r,s){let o=new Y(e.DB,e),i=new oe(e.DB),c=n.headers.get("CF-Connecting-IP")||"127.0.0.1",a=n.headers.get("User-Agent"),l=r[2],d=t.sessionId?await re(t.sessionId,t.id):null;if(l==="totp"){let u=r[3];if(u==="setup"&&n.method==="GET"){let f=await o.getById(t.id);if(f?.totp_enabled)return new Response("TOTP is already enabled",{status:409});let p=Kc(),m=new URL(n.url).hostname,h=Wc(p,f?.username||"user",m);return new Response(JSON.stringify({secret:p,uri:h}),{headers:{"Content-Type":"application/json"}})}if(u==="confirm"&&n.method==="POST"){let{secret:f,totpTokenHash:p,salt:m}=await n.json();if(!f||!p)return new Response("Missing secret or token",{status:400});if(!await Kr(f,p,m))return new Response("Invalid TOTP code",{status:400});let g=go(),b=await Promise.all(g.map(async w=>({hash:await Ge(w)})));return await o.updateTOTP(t.id,f,b),await o.updateTOTPSettings(t.id,!0),await i.record(t.id,"totp_setup",c,a,void 0,d),new Response(JSON.stringify({success:!0,recovery_keys:g}),{headers:{"Content-Type":"application/json"}})}if(u==="test"&&n.method==="POST"){let f=await o.getById(t.id);if(!f?.totp_enabled||!f?.totp_secret)return new Response(JSON.stringify({success:!1,message:"TOTP is not enabled"}),{status:400,headers:{"Content-Type":"application/json"}});let{token:p,totpTokenHash:m,salt:h}=await n.json(),g=m||p;return g?await Kr(f.totp_secret,g,h)?(await i.record(t.id,"totp_verify_success",c,a,{flow:"mfa_test"},d),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})):(await i.record(t.id,"totp_verify_fail",c,a,{flow:"mfa_test"},d),new Response(JSON.stringify({success:!1,message:"Invalid TOTP code"}),{status:400,headers:{"Content-Type":"application/json"}})):new Response(JSON.stringify({success:!1,message:"Missing token"}),{status:400,headers:{"Content-Type":"application/json"}})}if(u==="settings"&&n.method==="PATCH"){let f=await o.getById(t.id),m=await new ae(e.DB).listByUser(t.id);if(!(!!f?.totp_enabled||m.length>0))return new Response("MFA is not enabled",{status:400});let{skip_password:g}=await n.json();return await o.updateTOTPSettings(t.id,!!g),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}if(!u&&n.method==="DELETE"){let{password:f}=await n.json(),p=await o.getById(t.id);if(!p)return new Response("User not found",{status:404});if(!p.totp_skip_password&&(!f||!await yt(f,p.hashed_password,p.password_version??1)))return new Response("Incorrect password",{status:400});let g=(await new ae(e.DB).listByUser(t.id)).length>0;return await o.removeTOTP(t.id,g),await i.record(t.id,"totp_removed",c,a,void 0,d),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}}if(l==="mfa"&&r[3]==="settings"&&n.method==="PATCH"){let u=await o.getById(t.id),p=await new ae(e.DB).listByUser(t.id);if(!(!!u?.totp_enabled||p.length>0))return new Response("MFA is not enabled",{status:400});let{skip_password:h}=await n.json();return await o.updateTOTPSettings(t.id,!!h),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}async function fl(n,e,t,r,s){let o=new Y(e.DB,e),i=await o.getById(t.id);if(!i)return new Response("User not found",{status:404});let c=await n.json(),a=await ln(i,c,e,n);if(!a.success)return new Response(a.error||"Authentication failed",{status:400});if(n.method==="POST"){let{pinHash:l}=c;return!l||!/^[a-fA-F0-9]{64}$/.test(l)?new Response("Invalid PIN hash format",{status:400}):(await o.updatePinHash(t.id,l),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}}))}return n.method==="DELETE"?(await o.updatePinHash(t.id,null),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})):new Response("Method Not Allowed",{status:405})}Te();ge();async function ml(n,e,t,r,s){let o=new Y(e.DB,e),i=new ae(e.DB),c=new oe(e.DB),a=n.headers.get("CF-Connecting-IP")||"127.0.0.1",l=n.headers.get("User-Agent"),d=r[3],u=t.sessionId?await re(t.sessionId,t.id):null;if(!d&&n.method==="GET"){let f=await i.listByUser(t.id);return new Response(JSON.stringify(f),{headers:{"Content-Type":"application/json"}})}if(d==="auth-options"&&n.method==="POST"){let f=await i.listByUser(t.id);if(f.length===0)return new Response("No passkeys registered",{status:400});let p;try{n.headers.get("content-type")?.includes("application/json")&&(p=(await n.json())?.passkeyId)}catch{}let m=p?f.filter(E=>E.id===p):f;if(m.length===0)return new Response("Target passkey not found",{status:404});let h=cn(),b=new URL(n.url).hostname,w=caches.default;await P.set(w,`webauthn_auth_challenge:${t.id}`,{challenge:h,rpId:b},300);let y={challenge:h,rpId:b,timeout:6e4,userVerification:"preferred",allowCredentials:m.map(E=>({id:E.credential_id,passkey_id:E.id,type:"public-key",transports:E.transports?JSON.parse(E.transports):void 0}))};return new Response(JSON.stringify(y),{headers:{"Content-Type":"application/json"}})}if(d==="test"&&n.method==="POST"){let f=caches.default,p=await P.get(f,`webauthn_auth_challenge:${t.id}`);if(!p)return new Response("Test session expired, please try again",{status:400});let m=await n.json(),h=m.credential||m.passkeyAssertion||m;if(!h||!h.response||!h.id)return new Response("Invalid credential data",{status:400});let b=(await i.listByUser(t.id)).find(y=>y.credential_id===h.id);if(!b)return new Response("Passkey not recognized for this account",{status:404});let w=new URL(n.url);try{let{signCount:y}=await Nt({clientDataJSON:h.response.clientDataJSON,authenticatorData:h.response.authenticatorData,signature:h.response.signature,publicKeySpki:b.public_key,algorithm:b.algorithm,expectedChallenge:p.challenge,expectedOrigin:`${w.protocol}//${w.host}`,expectedRpId:p.rpId,previousSignCount:b.sign_count});return await i.updateUsage(b.id,y),await P.delete(f,`webauthn_auth_challenge:${t.id}`),await c.record(t.id,"passkey_verify_success",a,l,{flow:"mfa_test",name:b.name,id:b.id},u),new Response(JSON.stringify({success:!0,passkey:{id:b.id,name:b.name}}),{headers:{"Content-Type":"application/json"}})}catch(y){return await c.record(t.id,"passkey_verify_fail",a,l,{flow:"mfa_test",name:b.name,id:b.id,error:y.message||"failed"},u),console.warn("[Passkey Test] Verification failed:",y.message||y),new Response(y.message||"Passkey verification failed",{status:400})}}if(d==="register"&&r[4]==="options"&&n.method==="POST"){let f=await i.listByUser(t.id),p=cn(),h=new URL(n.url).hostname,g=caches.default;await P.set(g,`webauthn_reg_challenge:${t.id}`,{challenge:p,rpId:h},300);let w=(await o.getById(t.id))?.username||t.username||"",y=w?`${w} (${h})`:h,E={challenge:p,rp:{name:h,id:h},user:{id:ft(new TextEncoder().encode(t.id)),name:w||h,displayName:y},pubKeyCredParams:[{type:"public-key",alg:-7},{type:"public-key",alg:-257}],authenticatorSelection:{userVerification:"preferred",residentKey:"preferred"},timeout:6e4,excludeCredentials:f.map(_=>({id:_.credential_id,type:"public-key",transports:_.transports?JSON.parse(_.transports):void 0}))};return new Response(JSON.stringify(E),{headers:{"Content-Type":"application/json"}})}if(d==="register"&&r[4]==="verify"&&n.method==="POST"){let f=caches.default,p=await P.get(f,`webauthn_reg_challenge:${t.id}`);if(!p)return new Response("Registration session expired, please try again",{status:400});await P.delete(f,`webauthn_reg_challenge:${t.id}`);let m=await n.json(),{name:h,credential:g}=m;if(!g||!g.response)return new Response("Invalid credential data",{status:400});let b=new URL(n.url),w=`${b.protocol}//${b.host}`;try{let y=await Gc({clientDataJSON:g.response.clientDataJSON,attestationObject:g.response.attestationObject,expectedChallenge:p.challenge,expectedOrigin:w,expectedRpId:p.rpId});if(await i.getByCredentialId(y.credentialId))return new Response("This passkey is already registered",{status:409});let _=(h||"").trim();if(!Xo.test(_))return new Response("Invalid Passkey name format",{status:400});let S=Array.isArray(g.response.transports)?g.response.transports:void 0,x=await i.create({userId:t.id,name:_,credentialId:y.credentialId,publicKey:y.publicKeySpki,algorithm:y.algorithm,signCount:y.signCount,transports:S,aaguid:y.aaguid});await o.updateTOTPSettings(t.id,!0);let T=await o.getById(t.id),v=[];if(T){let C=!1;if(T.totp_recovery_keys)try{let L=typeof T.totp_recovery_keys=="string"?JSON.parse(T.totp_recovery_keys):T.totp_recovery_keys;Array.isArray(L)&&L.length>0&&(C=!0)}catch{T.totp_recovery_keys.length>0&&(C=!0)}else T.totp_recovery_keys_encrypted&&(C=!0);if(!C){let L=go(),H=await Promise.all(L.map(async V=>({hash:await Ge(V)})));await o.updateRecoveryKeys(t.id,H),v=L}}return await c.record(t.id,"passkey_registered",a,l,{name:_},u),new Response(JSON.stringify({...x,recovery_keys:v.length>0?v:void 0}),{status:201,headers:{"Content-Type":"application/json"}})}catch(y){return console.warn("[Passkey Registration] Verification failed:",y.message||y),new Response(y.message||"Passkey registration failed",{status:400})}}if(d&&n.method==="PATCH"){let f=d,{name:p}=await n.json(),m=(p||"").trim();if(!m||!Xo.test(m))return new Response("Invalid Passkey name format",{status:400});if(!await i.getById(f,t.id))return new Response("Passkey not found",{status:404});let g=await i.updateName(f,t.id,m);return new Response(JSON.stringify({success:g}),{headers:{"Content-Type":"application/json"}})}if(d&&n.method==="DELETE"){let f=d,p=await i.getById(f,t.id);if(!p)return new Response("Passkey not found",{status:404});await i.delete(f,t.id),await c.record(t.id,"passkey_deleted",a,l,{name:p.name},u);let m=await i.countByUser(t.id),h=await o.getById(t.id);return m===0&&!h?.totp_enabled&&await o.updateTOTPSettings(t.id,!1),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}Te();async function hl(n,e,t,r,s){let o=new Y(e.DB,e),i=new oe(e.DB),c=n.headers.get("CF-Connecting-IP")||"127.0.0.1",a=n.headers.get("User-Agent"),l=r[3],d=t.sessionId?await re(t.sessionId,t.id):null;if(l==="rotate"&&n.method==="POST"){let u=await n.json(),f=await o.getById(t.id);if(!f)return new Response("User not found",{status:404});let p=await ln(f,u,e,n);if(!p.success)return new Response(p.error||"Authentication failed",{status:400});if(p.method!=="passkey"&&p.method!=="recovery_key")return new Response("Recovery Key rotation only permits authentication via existing Passkey or original Recovery Key",{status:403});let m=an(),g=[{hash:await Ge(m)}];return await o.updateRecoveryKeys(t.id,g),await i.record(t.id,"recovery_key_rotated",c,a,{method:p.method},d),new Response(JSON.stringify({success:!0,recovery_key:m}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}async function gl(n,e,t,r,s){switch(r[2]){case"password":case"migrate-password":return ul(n,e,t,r,s);case"totp":case"mfa":return pl(n,e,t,r,s);case"pin":return fl(n,e,t,r,s);case"passkeys":return ml(n,e,t,r,s);case"recovery-keys":return hl(n,e,t,r,s);default:return new Response("Not Found",{status:404})}}pe();async function yl(n,e,t,r,s){let o=new oe(e.DB),i=n.headers.get("CF-Connecting-IP")||"127.0.0.1",c=n.headers.get("User-Agent"),a=r[2];if(a==="activity"&&n.method==="GET"){let l=new URL(n.url).searchParams,d=Math.min(parseInt(l.get("limit")||"20",10),50),u=l.get("before")?parseInt(l.get("before"),10):void 0,f=await o.listByUser(t.id,d,u);return new Response(JSON.stringify(f),{headers:{"Content-Type":"application/json"}})}if(a==="sessions"){if(!r[3]&&n.method==="GET"){let{SessionModel:l}=await Promise.resolve().then(()=>(Be(),pr)),u=await new l(e.DB).getSessionsByUser(t.id),f=n.headers.get("Cookie")||"",p=Le(f),m=p&&ve(p)?.sid||null,h=await Promise.all(u.map(async g=>({...g,id:await re(g.id,t.id),is_current:g.id===m})));return new Response(JSON.stringify(h),{headers:{"Content-Type":"application/json"}})}if(r[3]==="others"&&n.method==="DELETE"){let{SessionModel:l}=await Promise.resolve().then(()=>(Be(),pr)),d=new l(e.DB),u=Le(n.headers.get("Cookie")||""),f=u&&ve(u)?.sid||null;if(!f)return new Response("Current session not found",{status:400});let m=(await d.getSessionsByUser(t.id)).filter(w=>w.id!==f),{invalidateSession:h}=await Promise.resolve().then(()=>(pe(),yr)),{invalidateAuthUserCache:g}=await Promise.resolve().then(()=>(Hr(),Hc));for(let w of m)await h(e,w.id),g(w.id);let b=await re(f,t.id);return await o.record(t.id,"session_revoked",i,c,{reason:"user_revoke_others",count:m.length},b),new Response(JSON.stringify({success:!0,revoked_count:m.length}),{headers:{"Content-Type":"application/json"}})}if(r[3]&&n.method==="DELETE"){let l=r[3],{SessionModel:d}=await Promise.resolve().then(()=>(Be(),pr)),f=await new d(e.DB).getSessionsByUser(t.id),p=null;for(let w of f)if(await re(w.id,t.id)===l){p=w.id;break}if(!p)return new Response("Not Found",{status:404});let{invalidateSession:m}=await Promise.resolve().then(()=>(pe(),yr));await m(e,p);let h=Le(n.headers.get("Cookie")||""),g=h&&ve(h)?.sid||null,b=g?await re(g,t.id):null;return await o.record(t.id,"session_revoked",i,c,{reason:"user_revoke",target_session:l},l),p===g?new Response(JSON.stringify({success:!0,is_current:!0}),{headers:{"Set-Cookie":Qe(),"Content-Type":"application/json"}}):new Response(JSON.stringify({success:!0,is_current:!1}),{headers:{"Content-Type":"application/json"}})}}return new Response("Not Found",{status:404})}async function wl(n,e,t,r,s){let o=new ae(e.DB),i=new q(e.DB),c=r[3];if(c==="status"&&n.method==="GET"){let[a,l]=await Promise.all([i.listByOwner(t.id),o.listByUser(t.id)]),d=null,u=[],f=null;if(a.length>0){let[p,m,h]=await Promise.all([e.DB.prepare("SELECT public_key, created_at, is_active FROM user_log_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?) LIMIT 1").bind(t.id).first(),e.DB.prepare(`
432
+ `).bind(t.id).first()),d?Response.json({encryptedSk:d.encrypted_sk,iv:d.iv}):new Response("Wrapped key not found for this passkey",{status:404})}if(c==="init"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{publicKey:l,passkeyId:d,encryptedSk:u,iv:f,recovery:p}=a;if(!l||!d||!u||!f)return new Response("Missing required fields (publicKey, passkeyId, encryptedSk, iv)",{status:400});let m=await i.getByIdOrCredentialId(d,t.id);if(!m){let w=await i.listByUser(t.id);w.length>0&&(m=w[0])}if(!m)return new Response("Passkey not found or unauthorized",{status:403});let h=Math.floor(Date.now()/1e3),g=typeof l=="string"?l:JSON.stringify(l),b=[e.DB.prepare("INSERT INTO user_log_keys (profile_id, public_key, created_at) VALUES (?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET public_key = excluded.public_key, created_at = excluded.created_at").bind(r.id,g,h),e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(m.id,r.id,u,f,h)];return p&&p.encryptedSk&&p.iv&&p.salt&&b.push(e.DB.prepare("INSERT INTO user_recovery_wrapped_keys (profile_id, encrypted_sk, iv, salt, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, salt = excluded.salt, created_at = excluded.created_at").bind(r.id,p.encryptedSk,p.iv,p.salt,h)),await e.DB.batch(b),en(r.id),Response.json({success:!0})}if(c==="wrap"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{passkeyId:l,encryptedSk:d,iv:u}=a;if(!l||!d||!u)return new Response("Missing required fields (passkeyId, encryptedSk, iv)",{status:400});let f=await i.getByIdOrCredentialId(l,t.id);if(!f){let m=await i.listByUser(t.id);m.length>0&&(f=m[0])}if(!f)return new Response("Passkey not found or unauthorized",{status:403});let p=Math.floor(Date.now()/1e3);return await e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(f.id,r.id,d,u,p).run(),Response.json({success:!0})}return n.method==="DELETE"&&(!c||c==="keys")?(await e.DB.batch([e.DB.prepare("DELETE FROM user_log_keys WHERE profile_id = ?").bind(r.id),e.DB.prepare("DELETE FROM user_passkey_wrapped_keys WHERE profile_id = ?").bind(r.id),e.DB.prepare("DELETE FROM user_recovery_wrapped_keys WHERE profile_id = ?").bind(r.id)]),en(r.id),Response.json({success:!0})):new Response("Not Found",{status:404})}async function cl(n,e,t,r){let o=new URL(n.url).pathname.split("/").filter(Boolean),i=new q(e.DB);if(o.length===2)return t?qc(n,e,t,o):new Response("Unauthorized",{status:401});if(o.length>=3){let c=o[2],a=await i.getById(c);if(!a)return new Response("Profile Not Found",{status:404});if(!t)return new Response("Unauthorized",{status:401});if(!Yn.canAccessProfile(t,a))return new Response("Profile Not Found",{status:404});let l=o[3];if(!l||["rotate_key","settings","test"].includes(l))return el(n,e,t,a,o,r);switch(l){case"access_points":return tl(n,e,t,c,o,r);case"rules":return nl(n,e,t,c,o,r);case"lists":return sl(n,e,t,c,o,r);case"logs":case"analytics":return il(n,e,t,a,o,r);case"e2ee":return al(n,e,t,a,o,r);default:return new Response("Not Found",{status:404})}}return new Response("Not Found",{status:404})}pe();function Bi(n){if(!Number.isFinite(n)||n<=0)return"0 B";let e=["B","KB","MB","GB","TB"],t=Math.min(Math.floor(Math.log(n)/Math.log(1024)),e.length-1),r=n/Math.pow(1024,t);return`${r<10?r.toFixed(2):r<100?r.toFixed(1):r.toFixed(0)} ${e[t]}`}async function ll(n){let e=n.DB;if(typeof e?.getDatabaseSize=="function")try{let t=e.getDatabaseSize();return{size_bytes:t,formatted:Bi(t),mode:"serverfull"}}catch(t){console.warn("[dbSize] Serverfull size calculation warning:",t)}try{let t=await n.DB.prepare("SELECT name FROM sqlite_master WHERE type='table';").all(),r=new Set((t.results||[]).map(i=>i.name)),s=[];r.has("profile_blooms")&&s.push("(SELECT coalesce(sum(length(bloom_filter_chunk)), 0) FROM profile_blooms)"),r.has("list_blooms")&&s.push("(SELECT coalesce(sum(length(bloom_filter_chunk)), 0) FROM list_blooms)"),r.has("profile_blooms_staging")&&s.push("(SELECT coalesce(sum(length(bloom_filter_chunk)), 0) FROM profile_blooms_staging)"),r.has("logs")&&s.push("(SELECT count(*) * 512 FROM logs)"),r.has("user_activity_log")&&s.push("(SELECT count(*) * 256 FROM user_activity_log)"),r.has("domain_hourly_rollups")&&s.push("(SELECT count(*) * 128 FROM domain_hourly_rollups)"),r.has("rules")&&s.push("(SELECT count(*) * 256 FROM rules)"),r.has("lists")&&s.push("(SELECT count(*) * 512 FROM lists)"),r.has("users")&&s.push("(SELECT count(*) * 1024 FROM users)"),r.has("profiles")&&s.push("(SELECT count(*) * 512 FROM profiles)"),r.has("access_points")&&s.push("(SELECT count(*) * 256 FROM access_points)"),r.has("sessions")&&s.push("(SELECT count(*) * 256 FROM sessions)"),r.has("passkeys")&&s.push("(SELECT count(*) * 512 FROM passkeys)"),r.has("kem_keys")&&s.push("(SELECT count(*) * 1024 FROM kem_keys)");let o=65536;if(s.length>0){let i=`SELECT (${s.join(" + ")} + 65536) AS size_bytes;`,c=await n.DB.prepare(i).first();c?.size_bytes!==void 0&&c.size_bytes!==null&&(o=Math.max(65536,Number(c.size_bytes)))}return{size_bytes:o,formatted:Bi(o),mode:"serverless"}}catch(t){return console.warn("[dbSize] Serverless D1 size calculation warning:",t),{size_bytes:65536,formatted:Bi(65536),mode:"serverless"}}}async function dl(n,e,t,r,s){let o=new Y(e.DB,e),i=new Ie(e.DB),c=r[2];if(c==="me"){if(n.method==="GET"){let a=await o.getById(t.id),l=e.MAX_LOG_RETENTION_DAYS!==void 0&&e.MAX_LOG_RETENTION_DAYS!==""?Number(e.MAX_LOG_RETENTION_DAYS):30,d=e.ADMIN_USER_MAX_LOG_RETENTION_DAYS!==void 0&&e.ADMIN_USER_MAX_LOG_RETENTION_DAYS!==""?Math.min(Number(e.ADMIN_USER_MAX_LOG_RETENTION_DAYS),l):l,u=e.NORMAL_USER_MAX_LOG_RETENTION_DAYS!==void 0&&e.NORMAL_USER_MAX_LOG_RETENTION_DAYS!==""?Math.min(Number(e.NORMAL_USER_MAX_LOG_RETENTION_DAYS),l):Math.min(7,l),p=await new ae(e.DB).countByUser(t.id),m=!!a?.totp_enabled||p>0,h=!1;if(a?.totp_recovery_keys)try{let b=typeof a.totp_recovery_keys=="string"?JSON.parse(a.totp_recovery_keys):a.totp_recovery_keys;Array.isArray(b)&&b.length>0&&(h=!0)}catch{h=!0}else a?.totp_recovery_keys_encrypted&&(h=!0);let g=t.role==="admin"?Oe(e.JWT_SECRET)&&!bt(e.JWT_SECRET):!1;return new Response(JSON.stringify({id:t.id,username:a?.username||"",role:t.role,totp_enabled:!!a?.totp_enabled,totp_skip_password:!!a?.totp_skip_password,passkeys_count:p,mfa_enabled:m,has_recovery_keys:h,recovery_keys_encrypted:!!a?.totp_recovery_keys_encrypted,timezone:a?.timezone||null,locale:a?.locale||"en-US",password_version:a?.password_version??1,pin_enabled:!!a?.pin_hash,session_lock_timeout:a?.session_lock_timeout??15,max_log_retention_days:t.role==="admin"?d:u,jwt_secret_warning:g}),{headers:{"Content-Type":"application/json"}})}if(n.method==="PATCH"){let a=await n.json();try{if(a.session_lock_timeout!==void 0){let l=Number(a.session_lock_timeout);await o.updateSessionLockTimeout(t.id,l)}if(a.username!==void 0){let l=a.username;if(!l||!Rt.test(l))return new Response("Username format error",{status:400});await o.updateUsername(t.id,l)}if(a.timezone!==void 0){let l=a.timezone;if(l!==null&&typeof l=="string"&&l!=="")try{Intl.DateTimeFormat(void 0,{timeZone:l})}catch{return new Response("Invalid timezone format",{status:400})}await o.updateTimezone(t.id,l||null)}if(a.locale!==void 0){let l=a.locale;if(l!==null&&typeof l=="string"&&l!=="")try{Intl.DateTimeFormat(l)}catch{return new Response("Invalid locale format",{status:400})}await o.updateLocale(t.id,l||"en-US")}return new Response(JSON.stringify({success:!0}))}catch(l){return l.message?.includes("UNIQUE constraint failed")?new Response("The username is already taken",{status:400}):new Response("Failed to update settings",{status:500})}}return new Response("Method Not Allowed",{status:405})}if(c==="db-size"&&n.method==="GET"){if(t.role!=="admin")return new Response("Forbidden",{status:403});let a=await ll(e);return new Response(JSON.stringify(a),{headers:{"Content-Type":"application/json"}})}if(c==="logs"&&n.method==="DELETE"){await i.deleteByOwner(t.id);let a=e.DB;return typeof a?.vacuum=="function"&&a.vacuum(),new Response(JSON.stringify({success:!0}))}if(c==="delete"&&n.method==="POST"){let{invalidateSession:a}=await Promise.resolve().then(()=>(pe(),yr)),l=n.headers.get("Cookie")||"",d=Le(l),u=d&&ve(d)?.sid||null;return u&&await a(e,u),await o.delete(t.id),new Response(JSON.stringify({success:!0}),{headers:{"Set-Cookie":Qe(),"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}Te();Te();ge();async function ln(n,e,t,r){if(e.passkeyAssertion){let o=caches.default,i=await P.get(o,`webauthn_auth_challenge:${n.id}`);if(!i)return{success:!1,error:"Passkey session expired, please try again",reason:"passkey_session_expired"};let c=new ae(t.DB),l=(await c.listByUser(n.id)).find(u=>u.credential_id===e.passkeyAssertion.id);if(!l)return{success:!1,error:"Passkey not found",reason:"passkey_not_found"};let d=new URL(r.url);try{let{signCount:u}=await Nt({clientDataJSON:e.passkeyAssertion.response.clientDataJSON,authenticatorData:e.passkeyAssertion.response.authenticatorData,signature:e.passkeyAssertion.response.signature,publicKeySpki:l.public_key,algorithm:l.algorithm,expectedChallenge:i.challenge,expectedOrigin:`${d.protocol}//${d.host}`,expectedRpId:i.rpId,previousSignCount:l.sign_count});return await c.updateUsage(l.id,u),await P.delete(o,`webauthn_auth_challenge:${n.id}`),{success:!0,method:"passkey"}}catch{return{success:!1,error:"Invalid Passkey signature",reason:"invalid_passkey"}}}if(e.totpTokenHash)return!n.totp_enabled||!n.totp_secret?{success:!1,error:"TOTP is not enabled",reason:"totp_not_enabled"}:await Kr(n.totp_secret,e.totpTokenHash,e.totpSalt)?{success:!0,method:"totp"}:{success:!1,error:"Invalid TOTP code",reason:"invalid_totp"};let s=e.oldPassword||e.password;if(s)return await yt(s,n.hashed_password,n.password_version??1)?{success:!0,method:"password"}:{success:!1,error:"Current password is incorrect",reason:"wrong_current_password"};if(e.recoveryKey){let o=[];try{o=typeof n.totp_recovery_keys=="string"?JSON.parse(n.totp_recovery_keys):n.totp_recovery_keys||[]}catch{}return await Gn(e.recoveryKey,o)===-1?{success:!1,error:"Invalid Recovery Key",reason:"invalid_recovery_key"}:{success:!0,method:"recovery_key"}}return{success:!1,error:"Authentication required (Passkey, Recovery Key, or Password)",reason:"no_credentials_provided"}}async function ul(n,e,t,r,s){let o=new Y(e.DB,e),i=new oe(e.DB),c=n.headers.get("CF-Connecting-IP")||"127.0.0.1",a=n.headers.get("User-Agent"),l=r[2],d=t.sessionId?await re(t.sessionId,t.id):null;if(l==="password"&&n.method==="POST"){let u=await n.json(),{newPassword:f}=u;if(!f||!St.test(f))return new Response("Password format error",{status:400});let p=await o.getById(t.id);if(!p)return new Response("User not found",{status:404});let m=await ln(p,u,e,n);if(!m.success)return await i.record(t.id,"password_change_fail",c,a,{reason:m.reason},d),new Response(m.error||"Authentication failed",{status:400});let h=await ot(f,2);return await o.updatePassword(t.id,h,2),await i.record(t.id,"password_change_success",c,a,{method:m.method},d),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}if(l==="migrate-password"&&n.method==="POST"){let u=await n.json(),{clientHash:f}=u;if(!f)return new Response("Missing clientHash",{status:400});let p=await o.getById(t.id);if(!p)return new Response("User not found",{status:404});let m=await ln(p,u,e,n);if(!m.success)return await i.record(t.id,"password_change_fail",c,a,{reason:m.reason,flow:"migration"},d),new Response(m.error||"Authentication failed",{status:400});if((p.password_version??1)===1){let h=await ot(f,2);await o.updatePassword(t.id,h,2),await i.record(t.id,"password_change_success",c,a,{method:"migration"},d)}return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}Te();async function pl(n,e,t,r,s){let o=new Y(e.DB,e),i=new oe(e.DB),c=n.headers.get("CF-Connecting-IP")||"127.0.0.1",a=n.headers.get("User-Agent"),l=r[2],d=t.sessionId?await re(t.sessionId,t.id):null;if(l==="totp"){let u=r[3];if(u==="setup"&&n.method==="GET"){let f=await o.getById(t.id);if(f?.totp_enabled)return new Response("TOTP is already enabled",{status:409});let p=Kc(),m=new URL(n.url).hostname,h=Wc(p,f?.username||"user",m);return new Response(JSON.stringify({secret:p,uri:h}),{headers:{"Content-Type":"application/json"}})}if(u==="confirm"&&n.method==="POST"){let{secret:f,totpTokenHash:p,salt:m}=await n.json();if(!f||!p)return new Response("Missing secret or token",{status:400});if(!await Kr(f,p,m))return new Response("Invalid TOTP code",{status:400});let g=go(),b=await Promise.all(g.map(async w=>({hash:await Ge(w)})));return await o.updateTOTP(t.id,f,b),await o.updateTOTPSettings(t.id,!0),await i.record(t.id,"totp_setup",c,a,void 0,d),new Response(JSON.stringify({success:!0,recovery_keys:g}),{headers:{"Content-Type":"application/json"}})}if(u==="test"&&n.method==="POST"){let f=await o.getById(t.id);if(!f?.totp_enabled||!f?.totp_secret)return new Response(JSON.stringify({success:!1,message:"TOTP is not enabled"}),{status:400,headers:{"Content-Type":"application/json"}});let{token:p,totpTokenHash:m,salt:h}=await n.json(),g=m||p;return g?await Kr(f.totp_secret,g,h)?(await i.record(t.id,"totp_verify_success",c,a,{flow:"mfa_test"},d),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})):(await i.record(t.id,"totp_verify_fail",c,a,{flow:"mfa_test"},d),new Response(JSON.stringify({success:!1,message:"Invalid TOTP code"}),{status:400,headers:{"Content-Type":"application/json"}})):new Response(JSON.stringify({success:!1,message:"Missing token"}),{status:400,headers:{"Content-Type":"application/json"}})}if(u==="settings"&&n.method==="PATCH"){let f=await o.getById(t.id),m=await new ae(e.DB).listByUser(t.id);if(!(!!f?.totp_enabled||m.length>0))return new Response("MFA is not enabled",{status:400});let{skip_password:g}=await n.json();return await o.updateTOTPSettings(t.id,!!g),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}if(!u&&n.method==="DELETE"){let{password:f}=await n.json(),p=await o.getById(t.id);if(!p)return new Response("User not found",{status:404});if(!p.totp_skip_password&&(!f||!await yt(f,p.hashed_password,p.password_version??1)))return new Response("Incorrect password",{status:400});let g=(await new ae(e.DB).listByUser(t.id)).length>0;return await o.removeTOTP(t.id,g),await i.record(t.id,"totp_removed",c,a,void 0,d),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}}if(l==="mfa"&&r[3]==="settings"&&n.method==="PATCH"){let u=await o.getById(t.id),p=await new ae(e.DB).listByUser(t.id);if(!(!!u?.totp_enabled||p.length>0))return new Response("MFA is not enabled",{status:400});let{skip_password:h}=await n.json();return await o.updateTOTPSettings(t.id,!!h),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}async function fl(n,e,t,r,s){let o=new Y(e.DB,e),i=await o.getById(t.id);if(!i)return new Response("User not found",{status:404});let c=await n.json(),a=await ln(i,c,e,n);if(!a.success)return new Response(a.error||"Authentication failed",{status:400});if(n.method==="POST"){let{pinHash:l}=c;return!l||!/^[a-fA-F0-9]{64}$/.test(l)?new Response("Invalid PIN hash format",{status:400}):(await o.updatePinHash(t.id,l),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}}))}return n.method==="DELETE"?(await o.updatePinHash(t.id,null),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})):new Response("Method Not Allowed",{status:405})}Te();ge();async function ml(n,e,t,r,s){let o=new Y(e.DB,e),i=new ae(e.DB),c=new oe(e.DB),a=n.headers.get("CF-Connecting-IP")||"127.0.0.1",l=n.headers.get("User-Agent"),d=r[3],u=t.sessionId?await re(t.sessionId,t.id):null;if(!d&&n.method==="GET"){let f=await i.listByUser(t.id);return new Response(JSON.stringify(f),{headers:{"Content-Type":"application/json"}})}if(d==="auth-options"&&n.method==="POST"){let f=await i.listByUser(t.id);if(f.length===0)return new Response("No passkeys registered",{status:400});let p;try{n.headers.get("content-type")?.includes("application/json")&&(p=(await n.json())?.passkeyId)}catch{}let m=p?f.filter(E=>E.id===p):f;if(m.length===0)return new Response("Target passkey not found",{status:404});let h=cn(),b=new URL(n.url).hostname,w=caches.default;await P.set(w,`webauthn_auth_challenge:${t.id}`,{challenge:h,rpId:b},300);let y={challenge:h,rpId:b,timeout:6e4,userVerification:"preferred",allowCredentials:m.map(E=>({id:E.credential_id,passkey_id:E.id,type:"public-key",transports:E.transports?JSON.parse(E.transports):void 0}))};return new Response(JSON.stringify(y),{headers:{"Content-Type":"application/json"}})}if(d==="test"&&n.method==="POST"){let f=caches.default,p=await P.get(f,`webauthn_auth_challenge:${t.id}`);if(!p)return new Response("Test session expired, please try again",{status:400});let m=await n.json(),h=m.credential||m.passkeyAssertion||m;if(!h||!h.response||!h.id)return new Response("Invalid credential data",{status:400});let b=(await i.listByUser(t.id)).find(y=>y.credential_id===h.id);if(!b)return new Response("Passkey not recognized for this account",{status:404});let w=new URL(n.url);try{let{signCount:y}=await Nt({clientDataJSON:h.response.clientDataJSON,authenticatorData:h.response.authenticatorData,signature:h.response.signature,publicKeySpki:b.public_key,algorithm:b.algorithm,expectedChallenge:p.challenge,expectedOrigin:`${w.protocol}//${w.host}`,expectedRpId:p.rpId,previousSignCount:b.sign_count});return await i.updateUsage(b.id,y),await P.delete(f,`webauthn_auth_challenge:${t.id}`),await c.record(t.id,"passkey_verify_success",a,l,{flow:"mfa_test",name:b.name,id:b.id},u),new Response(JSON.stringify({success:!0,passkey:{id:b.id,name:b.name}}),{headers:{"Content-Type":"application/json"}})}catch(y){return await c.record(t.id,"passkey_verify_fail",a,l,{flow:"mfa_test",name:b.name,id:b.id,error:y.message||"failed"},u),console.warn("[Passkey Test] Verification failed:",y.message||y),new Response(y.message||"Passkey verification failed",{status:400})}}if(d==="register"&&r[4]==="options"&&n.method==="POST"){let f=await i.listByUser(t.id),p=cn(),h=new URL(n.url).hostname,g=caches.default;await P.set(g,`webauthn_reg_challenge:${t.id}`,{challenge:p,rpId:h},300);let w=(await o.getById(t.id))?.username||t.username||"",y=w?`${w} (${h})`:h,E={challenge:p,rp:{name:h,id:h},user:{id:ft(new TextEncoder().encode(t.id)),name:w||h,displayName:y},pubKeyCredParams:[{type:"public-key",alg:-7},{type:"public-key",alg:-257}],authenticatorSelection:{userVerification:"preferred",residentKey:"preferred"},timeout:6e4,excludeCredentials:f.map(_=>({id:_.credential_id,type:"public-key",transports:_.transports?JSON.parse(_.transports):void 0}))};return new Response(JSON.stringify(E),{headers:{"Content-Type":"application/json"}})}if(d==="register"&&r[4]==="verify"&&n.method==="POST"){let f=caches.default,p=await P.get(f,`webauthn_reg_challenge:${t.id}`);if(!p)return new Response("Registration session expired, please try again",{status:400});await P.delete(f,`webauthn_reg_challenge:${t.id}`);let m=await n.json(),{name:h,credential:g}=m;if(!g||!g.response)return new Response("Invalid credential data",{status:400});let b=new URL(n.url),w=`${b.protocol}//${b.host}`;try{let y=await Gc({clientDataJSON:g.response.clientDataJSON,attestationObject:g.response.attestationObject,expectedChallenge:p.challenge,expectedOrigin:w,expectedRpId:p.rpId});if(await i.getByCredentialId(y.credentialId))return new Response("This passkey is already registered",{status:409});let _=(h||"").trim();if(!Xo.test(_))return new Response("Invalid Passkey name format",{status:400});let S=Array.isArray(g.response.transports)?g.response.transports:void 0,x=await i.create({userId:t.id,name:_,credentialId:y.credentialId,publicKey:y.publicKeySpki,algorithm:y.algorithm,signCount:y.signCount,transports:S,aaguid:y.aaguid});await o.updateTOTPSettings(t.id,!0);let T=await o.getById(t.id),v=[];if(T){let C=!1;if(T.totp_recovery_keys)try{let L=typeof T.totp_recovery_keys=="string"?JSON.parse(T.totp_recovery_keys):T.totp_recovery_keys;Array.isArray(L)&&L.length>0&&(C=!0)}catch{T.totp_recovery_keys.length>0&&(C=!0)}else T.totp_recovery_keys_encrypted&&(C=!0);if(!C){let L=go(),H=await Promise.all(L.map(async V=>({hash:await Ge(V)})));await o.updateRecoveryKeys(t.id,H),v=L}}return await c.record(t.id,"passkey_registered",a,l,{name:_},u),new Response(JSON.stringify({...x,recovery_keys:v.length>0?v:void 0}),{status:201,headers:{"Content-Type":"application/json"}})}catch(y){return console.warn("[Passkey Registration] Verification failed:",y.message||y),new Response(y.message||"Passkey registration failed",{status:400})}}if(d&&n.method==="PATCH"){let f=d,{name:p}=await n.json(),m=(p||"").trim();if(!m||!Xo.test(m))return new Response("Invalid Passkey name format",{status:400});if(!await i.getById(f,t.id))return new Response("Passkey not found",{status:404});let g=await i.updateName(f,t.id,m);return new Response(JSON.stringify({success:g}),{headers:{"Content-Type":"application/json"}})}if(d&&n.method==="DELETE"){let f=d,p=await i.getById(f,t.id);if(!p)return new Response("Passkey not found",{status:404});await i.delete(f,t.id),await c.record(t.id,"passkey_deleted",a,l,{name:p.name},u);let m=await i.countByUser(t.id),h=await o.getById(t.id);return m===0&&!h?.totp_enabled&&await o.updateTOTPSettings(t.id,!1),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}Te();async function hl(n,e,t,r,s){let o=new Y(e.DB,e),i=new oe(e.DB),c=n.headers.get("CF-Connecting-IP")||"127.0.0.1",a=n.headers.get("User-Agent"),l=r[3],d=t.sessionId?await re(t.sessionId,t.id):null;if(l==="rotate"&&n.method==="POST"){let u=await n.json(),f=await o.getById(t.id);if(!f)return new Response("User not found",{status:404});let p=await ln(f,u,e,n);if(!p.success)return new Response(p.error||"Authentication failed",{status:400});if(p.method!=="passkey"&&p.method!=="recovery_key")return new Response("Recovery Key rotation only permits authentication via existing Passkey or original Recovery Key",{status:403});let m=an(),g=[{hash:await Ge(m)}];return await o.updateRecoveryKeys(t.id,g),await i.record(t.id,"recovery_key_rotated",c,a,{method:p.method},d),new Response(JSON.stringify({success:!0,recovery_key:m}),{headers:{"Content-Type":"application/json"}})}return new Response("Not Found",{status:404})}async function gl(n,e,t,r,s){switch(r[2]){case"password":case"migrate-password":return ul(n,e,t,r,s);case"totp":case"mfa":return pl(n,e,t,r,s);case"pin":return fl(n,e,t,r,s);case"passkeys":return ml(n,e,t,r,s);case"recovery-keys":return hl(n,e,t,r,s);default:return new Response("Not Found",{status:404})}}pe();async function yl(n,e,t,r,s){let o=new oe(e.DB),i=n.headers.get("CF-Connecting-IP")||"127.0.0.1",c=n.headers.get("User-Agent"),a=r[2];if(a==="activity"&&n.method==="GET"){let l=new URL(n.url).searchParams,d=Math.min(parseInt(l.get("limit")||"20",10),50),u=l.get("before")?parseInt(l.get("before"),10):void 0,f=await o.listByUser(t.id,d,u);return new Response(JSON.stringify(f),{headers:{"Content-Type":"application/json"}})}if(a==="sessions"){if(!r[3]&&n.method==="GET"){let{SessionModel:l}=await Promise.resolve().then(()=>(Be(),pr)),u=await new l(e.DB).getSessionsByUser(t.id),f=n.headers.get("Cookie")||"",p=Le(f),m=p&&ve(p)?.sid||null,h=await Promise.all(u.map(async g=>({...g,id:await re(g.id,t.id),is_current:g.id===m})));return new Response(JSON.stringify(h),{headers:{"Content-Type":"application/json"}})}if(r[3]==="others"&&n.method==="DELETE"){let{SessionModel:l}=await Promise.resolve().then(()=>(Be(),pr)),d=new l(e.DB),u=Le(n.headers.get("Cookie")||""),f=u&&ve(u)?.sid||null;if(!f)return new Response("Current session not found",{status:400});let m=(await d.getSessionsByUser(t.id)).filter(w=>w.id!==f),{invalidateSession:h}=await Promise.resolve().then(()=>(pe(),yr)),{invalidateAuthUserCache:g}=await Promise.resolve().then(()=>(Hr(),Hc));for(let w of m)await h(e,w.id),g(w.id);let b=await re(f,t.id);return await o.record(t.id,"session_revoked",i,c,{reason:"user_revoke_others",count:m.length},b),new Response(JSON.stringify({success:!0,revoked_count:m.length}),{headers:{"Content-Type":"application/json"}})}if(r[3]&&n.method==="DELETE"){let l=r[3],{SessionModel:d}=await Promise.resolve().then(()=>(Be(),pr)),f=await new d(e.DB).getSessionsByUser(t.id),p=null;for(let w of f)if(await re(w.id,t.id)===l){p=w.id;break}if(!p)return new Response("Not Found",{status:404});let{invalidateSession:m}=await Promise.resolve().then(()=>(pe(),yr));await m(e,p);let h=Le(n.headers.get("Cookie")||""),g=h&&ve(h)?.sid||null,b=g?await re(g,t.id):null;return await o.record(t.id,"session_revoked",i,c,{reason:"user_revoke",target_session:l},l),p===g?new Response(JSON.stringify({success:!0,is_current:!0}),{headers:{"Set-Cookie":Qe(),"Content-Type":"application/json"}}):new Response(JSON.stringify({success:!0,is_current:!1}),{headers:{"Content-Type":"application/json"}})}}return new Response("Not Found",{status:404})}async function wl(n,e,t,r,s){let o=new ae(e.DB),i=new q(e.DB),c=r[3];if(c==="status"&&n.method==="GET"){let[a,l]=await Promise.all([i.listByOwner(t.id),o.listByUser(t.id)]),d=null,u=[],f=null;if(a.length>0){let[p,m,h]=await Promise.all([e.DB.prepare("SELECT public_key, created_at, is_active FROM user_log_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?) LIMIT 1").bind(t.id).first(),e.DB.prepare(`
433
433
  SELECT upwk.passkey_id
434
434
  FROM user_passkey_wrapped_keys upwk
435
435
  JOIN passkeys pk ON upwk.passkey_id = pk.id
@@ -440,7 +440,7 @@ echo "Please change your system DNS to 127.0.0.1"
440
440
  JOIN passkeys pk ON upwk.passkey_id = pk.id
441
441
  WHERE pk.user_id = ?
442
442
  LIMIT 1
443
- `).bind(t.id).first()),d?Response.json({encryptedSk:d.encrypted_sk,iv:d.iv}):new Response("Wrapped key not found for this passkey",{status:404})}if(c==="recovery-wrapped-key"&&n.method==="GET"){let a=await e.DB.prepare("SELECT encrypted_sk, iv, salt FROM user_recovery_wrapped_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?) LIMIT 1").bind(t.id).first();return a?Response.json({encryptedSk:a.encrypted_sk,iv:a.iv,salt:a.salt}):new Response("Recovery wrapped key not found",{status:404})}if(c==="init"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{publicKey:l,passkeyId:d,encryptedSk:u,iv:f,recovery:p}=a;if(!l||!d&&!p)return new Response("Missing required fields (publicKey and either passkey or recovery)",{status:400});let m=null;if(d&&(m=await o.getByIdOrCredentialId(d,t.id),!m)){let E=await o.listByUser(t.id);E.length>0&&(m=E[0])}let h=await i.listByOwner(t.id),g=Math.floor(Date.now()/1e3),b=typeof l=="string"?l:JSON.stringify(l),w=[],y=h.length>0?h[0].id:null;if(y){m&&u&&f&&w.push(e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(m.id,y,u,f,g));for(let E of h)w.push(e.DB.prepare("INSERT INTO user_log_keys (profile_id, public_key, created_at, is_active) VALUES (?, ?, ?, 1) ON CONFLICT(profile_id) DO UPDATE SET public_key = excluded.public_key, created_at = excluded.created_at, is_active = 1").bind(E.id,b,g)),p&&p.encryptedSk&&p.iv&&p.salt&&w.push(e.DB.prepare("INSERT INTO user_recovery_wrapped_keys (profile_id, encrypted_sk, iv, salt, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, salt = excluded.salt, created_at = excluded.created_at").bind(E.id,p.encryptedSk,p.iv,p.salt,g))}w.length>0&&await e.DB.batch(w);for(let E of h)en(E.id);return Response.json({success:!0})}if(c==="wrap"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{passkeyId:l,encryptedSk:d,iv:u}=a;if(!l||!d||!u)return new Response("Missing required fields (passkeyId, encryptedSk, iv)",{status:400});let f=await o.getByIdOrCredentialId(l,t.id);if(!f){let g=await o.listByUser(t.id);g.length>0&&(f=g[0])}if(!f)return new Response("Passkey not found or unauthorized",{status:403});let p=await i.listByOwner(t.id),m=p.length>0?p[0].id:null;if(!m)return new Response("No profiles found for user",{status:400});let h=Math.floor(Date.now()/1e3);return await e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(f.id,m,d,u,h).run(),Response.json({success:!0})}if(c==="wrap-recovery"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{encryptedSk:l,iv:d,salt:u}=a;if(!l||!d||!u)return new Response("Missing required fields (encryptedSk, iv, salt)",{status:400});let f=await i.listByOwner(t.id),p=Math.floor(Date.now()/1e3),m=[];for(let h of f)m.push(e.DB.prepare("INSERT INTO user_recovery_wrapped_keys (profile_id, encrypted_sk, iv, salt, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, salt = excluded.salt, created_at = excluded.created_at").bind(h.id,l,d,u,p));return m.length>0&&await e.DB.batch(m),Response.json({success:!0})}if(c==="enable"&&n.method==="POST"){let a=await i.listByOwner(t.id);await e.DB.prepare("UPDATE user_log_keys SET is_active = 1 WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id).run();for(let l of a)en(l.id);return Response.json({success:!0,enabled:!0})}if(n.method==="DELETE"&&(!c||c==="keys")){let l=new URL(n.url).searchParams.get("purge")==="true",d=await i.listByOwner(t.id);l?await e.DB.batch([e.DB.prepare("DELETE FROM user_log_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id),e.DB.prepare("DELETE FROM user_passkey_wrapped_keys WHERE passkey_id IN (SELECT id FROM passkeys WHERE user_id = ?)").bind(t.id),e.DB.prepare("DELETE FROM user_recovery_wrapped_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id)]):await e.DB.prepare("UPDATE user_log_keys SET is_active = 0 WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id).run();for(let u of d)en(u.id);return Response.json({success:!0,purged:l,enabled:!1})}return new Response("Not Found",{status:404})}async function bl(n,e,t,r,s){let o=r[2];return o==="me"||o==="delete"||o==="logs"||o==="db-size"?dl(n,e,t,r,s):o==="password"||o==="totp"||o==="passkeys"||o==="mfa"||o==="migrate-password"||o==="pin"||o==="recovery-keys"?gl(n,e,t,r,s):o==="activity"||o==="sessions"?yl(n,e,t,r,s):o==="e2ee"?wl(n,e,t,r,s):new Response("Not Found",{status:404})}pe();Te();async function El(n,e,t,r,s){let o=new Y(e.DB,e),i=new q(e.DB);if(r[2]==="users"){if(n.method==="GET"){let c=await o.listAll();return new Response(JSON.stringify(c),{headers:{"Content-Type":"application/json"}})}if(n.method==="POST"){let{username:c,password:a,role:l}=await n.json();if(!c||!Rt.test(c))return new Response("Invalid username format",{status:400});if(!a||!St.test(a))return new Response("Password format error",{status:400});let d=await ot(a,2),u=j(15);try{return await o.create({id:u,username:c,passwordHash:d,role:l||"user",passwordVersion:2}),new Response(JSON.stringify({id:u}),{status:201})}catch(f){return new Response(f.message,{status:400})}}if(n.method==="DELETE"&&r[3]){let c=r[3];return c===t.id?new Response("Cannot delete yourself",{status:400}):(await i.deleteByOwner(c),await o.delete(c),new Response(null,{status:204}))}}if(r[2]==="settings"){let c=new he(e.DB);if(n.method==="GET"){let a=await c.getAll();return delete a.jwt_secret,new Response(JSON.stringify(a),{headers:{"Content-Type":"application/json"}})}if(n.method==="PATCH"){let a=await n.json();return delete a.jwt_secret,await c.setMany(a),new Response(JSON.stringify({success:!0}))}}return new Response("Not Found",{status:404})}async function _l(n,e,t,r){let o=new URL(n.url).pathname.split("/").filter(Boolean);return o[1]==="account"?bl(n,e,t,o,r):o[1]==="admin"?Yn.isAdmin(t)?El(n,e,t,o,r):new Response("Forbidden",{status:403}):new Response("Not Found",{status:404})}ge();async function Sl(n,e){let t=new URL(n.url),r=typeof caches<"u"?caches.default:null;if(t.pathname==="/api/clientinfo"){let s=n.headers.get("CF-Connecting-IP")||"127.0.0.1",o=await P.get(r,`active_dns:${s}`),i=n.cf,c=i?.country||n.headers.get("CF-IPCountry")||"",a=c,l=i?.region||n.headers.get("CF-Region")||"",d=i?.city||n.headers.get("CF-IPCity")||"",u=i?.timezone||n.headers.get("CF-Timezone")||"",f=i?.asn?Number(i.asn):n.headers.get("CF-ASN")?Number(n.headers.get("CF-ASN")):0,p=i?.asOrganization||n.headers.get("CF-AS-Org")||"";if(!c||c==="UNKNOWN"||c==="UN"||c==="XX")if(!qe(s))c="Private Network",a="LAN",l="LAN",d="Local",u="UNKNOWN",p="Private Network";else try{let h=await Er(s);if(h&&(c=h.country||h.country_code||"UNKNOWN",a=h.country_code||"UNKNOWN",l=h.region||"UNKNOWN",d=h.city||"UNKNOWN",u=h.timezone||"UNKNOWN",p=h.org||h.isp||"UNKNOWN",!f&&h.as)){let g=h.as.match(/^AS(\d+)/i);g&&(f=Number(g[1]))}}catch(h){console.warn(`[ClientInfo] Failed resolving geoip for ${s}:`,h)}let m=e.SERVERFULL_DEFAULT_PROFILE_KEY!==void 0||e.SERVERFULL_HOST!==void 0||e.SERVERFULL_DOT_DOMAIN!==void 0||e.SERVERFULL_HTTP_PORT!==void 0||e.SERVERFULL_HTTPS_PORT!==void 0;return new Response(JSON.stringify({ip:s,country:c||"UNKNOWN",countryCode:a||void 0,region:l||"UNKNOWN",city:d||"UNKNOWN",timezone:u||"UNKNOWN",asn:f||0,asOrganization:p||"UNKNOWN",connectedProfileId:o||null,substituteDomain:e.SUBSTITUTE_DOMAIN||tr,dotDomain:e.SERVERFULL_DOT_DOMAIN||e.DOT_DOMAIN||null,isServerfull:m,mode:m?"serverfull":"cloudflare"}),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/regions"){let s={};for(let[o,i]of Object.entries(e))if(o.startsWith("IP_REGION_")&&typeof i=="string")try{let c=o.replace("IP_REGION_",""),a=i.trim();a=a.replace(/^"""|"""$/g,"").replace(/^"|"$/g,"").replace(/^'|'$/g,"").trim(),s[c]=JSON.parse(a)}catch{}return Object.keys(s).length===0&&(s.CN=Xr),new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/substitute"){let s=e.SUBSTITUTE_DOMAIN||tr,o=null,i=null,c=async a=>{let l=["https://cloudflare-dns.com/dns-query","https://1.1.1.1/dns-query"];for(let d of l)try{let u=await fetch(`${d}?name=${s}&type=${a}`,{headers:{Accept:"application/dns-json"},signal:AbortSignal.timeout(3e3)});if(u.ok){let f=await u.json();if(f?.Answer?.length>0){let p=f.Answer.find(m=>m.type===(a==="A"?1:28));if(p?.data)return p.data}}}catch(u){console.error(`[Substitute Resolve] Failed resolving ${a} via ${d}:`,u)}return null};try{let[a,l]=await Promise.all([c("A"),c("AAAA")]);o=a,i=l}catch(a){console.error("[Substitute API] Error resolving substitute domain:",a)}return new Response(JSON.stringify({ip:o,ipv6:i}),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/presets/upstreams"){let s=Yr;if(e.PRESET_UPSTREAMS)try{let o=JSON.parse(e.PRESET_UPSTREAMS);Array.isArray(o)&&o.length>0&&(s=o)}catch(o){console.warn("[System API] Failed to parse PRESET_UPSTREAMS from env:",o)}return new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/presets/filters"){let s=zr;if(e.PRESET_EXTERNAL_FILTERS)try{let o=JSON.parse(e.PRESET_EXTERNAL_FILTERS);Array.isArray(o)&&o.length>0&&(s=o)}catch(o){console.warn("[System API] Failed to parse PRESET_EXTERNAL_FILTERS from env:",o)}return new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/presets/ech-fronting-domains"){let s=Ji(e);return new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname.startsWith("/api/icon/")){let s=t.pathname.replace("/api/icon/","");if(!s||!/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(s)||s.includes("..")||s.includes("/")||s.includes("\\"))return new Response("Invalid domain format",{status:400});let o=`https://icons.duckduckgo.com/ip3/${s}`;try{let i=await fetch(o),c=new Headers(i.headers);return c.set("Cache-Control","public, max-age=2592000"),c.delete("Access-Control-Allow-Origin"),new Response(i.body,{status:i.status,statusText:i.statusText,headers:c})}catch{return new Response("Error fetching icon",{status:500})}}if(t.pathname==="/api/geoip"){let s=t.searchParams.get("ip"),o=n.headers.get("CF-Connecting-IP"),i=(s||o||"").trim();if(i==="127.0.0.1"||i==="localhost"||i==="::1"||i.startsWith("192.168.")||i.startsWith("10.")||/^172\.(1[6-9]|2\d|3[01])\./.test(i))return new Response(JSON.stringify({success:!0,ip:i,city:"Local",country:"Private Network",flag:{emoji:"\u{1F3E0}"}}),{headers:{"Content-Type":"application/json"}});try{let c=i?`https://ipwho.is/${encodeURIComponent(i)}`:"https://ipwho.is/",a=await fetch(c,{signal:AbortSignal.timeout(3500)});if(a.ok){let l=await a.json();return new Response(JSON.stringify(l),{headers:{"Content-Type":"application/json"}})}}catch(c){console.warn(`[System API] Failed resolving geoip for ${i||"server"}:`,c)}return new Response(JSON.stringify({success:!1,ip:i||"unknown"}),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/resolve"){let s=t.searchParams.get("name")||t.searchParams.get("domain");if(!s)return new Response(JSON.stringify({error:"Missing domain parameter"}),{status:400,headers:{"Content-Type":"application/json"}});if(/^(\d{1,3}\.){3}\d{1,3}$/.test(s))return new Response(JSON.stringify({ipv4:[s],ipv6:[]}),{headers:{"Content-Type":"application/json"}});if(/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(s))return new Response(JSON.stringify({ipv4:[],ipv6:[s]}),{headers:{"Content-Type":"application/json"}});let o=async i=>{let c=["https://cloudflare-dns.com/dns-query","https://1.1.1.1/dns-query"];for(let a of c)try{let l=await fetch(`${a}?name=${encodeURIComponent(s)}&type=${i}`,{headers:{Accept:"application/dns-json"},signal:AbortSignal.timeout(3e3)});if(l.ok){let d=await l.json();if(Array.isArray(d?.Answer)&&d.Answer.length>0){let u=i==="A"?1:28;return d.Answer.filter(f=>f.type===u&&typeof f.data=="string").map(f=>f.data)}}}catch{}return[]};try{let[i,c]=await Promise.all([o("A"),o("AAAA")]);if(i.length===0&&c.length===0&&typeof process<"u"&&process.versions?.node)try{let a=await import("node:dns/promises"),[l,d]=await Promise.allSettled([a.resolve4(s),a.resolve6(s)]);l.status==="fulfilled"&&Array.isArray(l.value)&&(i=l.value),d.status==="fulfilled"&&Array.isArray(d.value)&&(c=d.value)}catch{}return new Response(JSON.stringify({ipv4:i,ipv6:c}),{headers:{"Content-Type":"application/json"}})}catch(i){return console.error("DNS resolve failed:",i),new Response(JSON.stringify({ipv4:[],ipv6:[],error:"Internal server error"}),{status:500,headers:{"Content-Type":"application/json"}})}}return new Response("Not Found",{status:404})}ge();async function Rl(n,e,t){try{let r=caches.default,s=Math.floor(Date.now()/1e3),o="cron:last_cleanup_timestamp",i=await P.get(r,o);if(!i||s-i>=3600){try{let c=new Y(e.DB,e),{clearedProfiles:a,deletedUsers:l}=await c.applyInactivityPolicy(s);(a>0||l>0)&&console.log(`[Cron] Inactivity cleanup: cleared ${a} profile(s), deleted ${l} user(s).`)}catch(c){console.error("[Cron] Inactivity policy execution failed:",c)}try{let c=new Ie(e.DB),a=e.MAX_LOG_RETENTION_DAYS!==void 0&&e.MAX_LOG_RETENTION_DAYS!==""?Number(e.MAX_LOG_RETENTION_DAYS):30,l=e.LOG_CLEANUP_BATCH_LIMIT!==void 0&&e.LOG_CLEANUP_BATCH_LIMIT!==""?Number(e.LOG_CLEANUP_BATCH_LIMIT):1e3,d=e.LOG_CLEANUP_DAILY_BUDGET!==void 0&&e.LOG_CLEANUP_DAILY_BUDGET!==""?Number(e.LOG_CLEANUP_DAILY_BUDGET):2e4;await c.cleanupGlobal(a,l,d)}catch(c){console.error("[Cron] Global log cleanup failed:",c)}try{let{SessionModel:c}=await Promise.resolve().then(()=>(Be(),pr)),a=new c(e.DB),{deletedSessions:l,deletedPending:d}=await a.deleteExpiredSessions(s);(l>0||d>0)&&console.log(`[Cron] Purged ${l} expired session(s) and ${d} pending token(s).`)}catch(c){console.error("[Cron] Session cleanup failed:",c)}await P.set(r,o,s,3600),console.log("[Cron] Cleanup phase completed at",new Date().toISOString())}try{await Bo(e.DB)}catch(c){console.error("[Cron] Cloudflare IP range sync failed:",c)}try{let c="cron:last_idle_sync_check",l=await P.get(r,c);if(!!!(l&&s-l<600)){let u=Number(e.SYNC_PROFILE_INTERVAL_SEC)||86400,f=s-u,p=new q(e.DB),m=Number(e.SYNC_BATCH_SIZE)||1,h=await p.getSyncTargets(f,m);if(h.length>0){await P.delete(r,c);for(let g of h)try{await zn(g.id,e,t)}catch(b){let w=b instanceof Error?b.message:String(b);console.error(`[Cron] Sync failed for profile ${g.id}:`,w)}console.log(`[Cron] Sync: processed ${h.length} profile(s).`)}else await P.set(r,c,s,600)}}catch(c){let a=c instanceof Error?c.message:String(c);console.error("[Cron] Sync phase failed:",a)}}catch(r){let s=r instanceof Error?r.message:String(r);console.error("[Cron] Critical Failure:",s)}}async function Tl(n,e,t){let r=new Request(new URL("https://obex.local/world-110m-v2.json"),n),s=caches.default,o=await s.match(r);if(!o)try{let c=await fetch("https://openlayers.org/en/latest/examples/data/topojson/world-110m.json",{headers:{"User-Agent":"DNS Worker/1.0 (Cloudflare Worker Map Proxy)"}});if(!c.ok)throw new Error(`Failed to fetch map data from openlayers.org: ${c.status}`);let a=await c.json();if(a&&a.objects){let l={};a.objects.countries&&(l.countries=a.objects.countries),a.objects.land&&(l.land=a.objects.land),a.objects=l}a&&a.objects&&a.objects.countries&&Array.isArray(a.objects.countries.geometries)&&a.objects.countries.geometries.forEach(l=>{l.id!==void 0&&l.id!==null&&(l.id=String(l.id).padStart(3,"0"))}),o=new Response(JSON.stringify(a),{headers:{"Content-Type":"application/json; charset=utf-8","Cache-Control":"public, max-age=2592000","Access-Control-Allow-Origin":"*"}}),t.waitUntil(s.put(r,o.clone()))}catch(i){return console.error("[MapData API Proxy] Error fetching or transforming TopoJSON:",i),new Response(JSON.stringify({error:"Failed to fetch map data"}),{status:502,headers:{"Content-Type":"application/json"}})}return o}Fe();var Eo={async fetch(n,e,t){let r=new Uint8Array(16);crypto.getRandomValues(r);let s=Array.from(r).map(l=>l.toString(16).padStart(2,"0")).join(""),o=null,c=await(async()=>{let l=new URL(n.url);if(l.pathname==="/world-110m.json")return Tl(n,e,t);if(l.pathname.startsWith("/api/")){let p=!1;try{p=!e.DB}catch{p=!0}let m=!Oe(e.JWT_SECRET);if(p||m)return new Response(JSON.stringify({error:"configuration_error",isDbMissing:p,isJwtSecretMissing:m,message:"DNS Worker is not fully configured. Please configure the D1 database binding and JWT_SECRET secret variable."}),{status:503,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}});o=await Li(n,e,t);let h=["/api/auth/login","/api/auth/signup","/api/auth/prelogin","/api/auth/check-username","/api/auth/config","/api/auth/refresh","/api/auth/logout","/api/auth/unlock-session","/api/clientinfo","/api/regions","/api/substitute","/api/resolve","/api/geoip"].includes(l.pathname)||l.pathname.startsWith("/api/icon/")||l.pathname.startsWith("/api/presets/")||l.pathname.startsWith("/api/auth/forgot-password");return!o&&!h?new Response("Unauthorized",{status:401}):o&&o.isPaused&&!["/api/auth/unlock-session","/api/auth/logout","/api/auth/refresh"].includes(l.pathname)?new Response("session_paused",{status:403}):o&&["POST","PUT","PATCH","DELETE"].includes(n.method)&&!h&&!Oi(n)?new Response("CSRF validation failed",{status:403}):l.pathname.startsWith("/api/auth/")?Zc(n,e):l.pathname==="/api/clientinfo"||l.pathname==="/api/regions"||l.pathname==="/api/substitute"||l.pathname==="/api/resolve"||l.pathname==="/api/geoip"||l.pathname.startsWith("/api/presets/")||l.pathname.startsWith("/api/icon/")?Sl(n,e):l.pathname.startsWith("/api/profiles")?cl(n,e,o,t):l.pathname.startsWith("/api/account")||l.pathname.startsWith("/api/admin")?_l(n,e,o,t):new Response("API Not Found",{status:404})}if(l.pathname==="/setup.sh"){let p=l.searchParams.get("key");if(!p||!Wt.test(p))return new Response("Missing or invalid key parameter",{status:400});let m=Fc(l.origin,p);return new Response(m,{headers:{"Content-Type":"text/plain; charset=utf-8","Cache-Control":"no-cache"}})}let d=l.pathname.slice(1),u=Wt.test(d),f=n.method==="POST"||l.searchParams.has("dns")||n.headers.get("accept")?.includes("dns-message");if(u&&(f||n.method==="OPTIONS"))return n.method==="OPTIONS"?new Response(null,{status:204,headers:{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Accept","Access-Control-Max-Age":"86400"}}):Uc(n,e,t,d);try{let p=await e.ASSETS.fetch(n);if(p.status===404&&!l.pathname.startsWith("/assets/")&&(n.method==="GET"||n.method==="HEAD")&&(p=await e.ASSETS.fetch(new Request(l.origin+"/",{method:n.method,headers:n.headers}))),l.pathname.endsWith(".wasm")){let h=new Headers(p.headers);return h.set("Content-Type","application/wasm"),h.set("Cross-Origin-Resource-Policy","same-origin"),new Response(p.body,{status:p.status,statusText:p.statusText,headers:h})}if((p.headers.get("Content-Type")||"").includes("text/html")){let h=!1;try{h=!e.DB}catch{h=!0}let g=!Oe(e.JWT_SECRET),b=e.SERVERFULL_DEFAULT_PROFILE_KEY!==void 0||e.SERVERFULL_HOST!==void 0||e.SERVERFULL_DOT_DOMAIN!==void 0||e.SERVERFULL_HTTP_PORT!==void 0||e.SERVERFULL_HTTPS_PORT!==void 0,w="{}";try{w=JSON.stringify({nonce:s,isDbMissing:h,isJwtSecretMissing:g,isServerfull:b,mode:b?"serverfull":"cloudflare"})}catch{}return new HTMLRewriter().on("head",{element(y){y.prepend(`<script nonce="${s}">window.DNS_WORKER_CONFIG = ${w};</script>`,{html:!0})}}).transform(p)}return p}catch{return new Response("Asset Fetch Error",{status:500})}})(),a=o;if(a&&!_n(n.headers.get("Cookie"))){let l=a.sessionId?.startsWith("k_");try{let d=j(32);c.headers.append("Set-Cookie",it(d,e,l))}catch{let u=new Headers(c.headers),f=j(32);u.append("Set-Cookie",it(f,e,l)),c=new Response(c.body,{status:c.status,statusText:c.statusText,headers:u})}}if(c.headers.get("Content-Type")?.includes("application/dns-message")){let l=new Headers(c.headers);return l.set("Access-Control-Allow-Origin","*"),l.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),l.set("Access-Control-Allow-Headers","Content-Type, Accept"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:l})}return ki(c,s)},async scheduled(n,e,t){await Rl(n,e,t)}};var Vu={".html":"text/html; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".webp":"image/webp",".png":"image/png",".jpg":"image/jpeg",".svg":"image/svg+xml",".ico":"image/x-icon",".webmanifest":"application/manifest+json",".wasm":"application/wasm"};function Al(n,e){if(n.ASSETS)return;let t=e||_o.join(Mt(),"static");n.ASSETS={fetch:async r=>{try{let s=new URL(r.url),o=decodeURIComponent(s.pathname);(o==="/"||o==="")&&(o="/index.html");let i=_o.normalize(o).replace(/^(\.\.[\/\\])+/,""),c=_o.join(t,i);if(jr.existsSync(c)&&jr.statSync(c).isFile()){let a=_o.extname(c).toLowerCase(),l=Vu[a]||"application/octet-stream",d=jr.readFileSync(c);return new Response(d,{status:200,headers:{"Content-Type":l,"Cache-Control":a===".html"?"no-cache":"public, max-age=31536000, immutable","Cross-Origin-Opener-Policy":"same-origin","Cross-Origin-Embedder-Policy":"credentialless","Cross-Origin-Resource-Policy":"same-origin"}})}return new Response("Not Found",{status:404})}catch{return new Response("Asset Error",{status:500})}}}}function So(n){if(!n||typeof n!="string")return null;let e=n.trim();if(!e)return null;if(e.startsWith("[")){let t=e.indexOf("]");t>1&&(e=e.substring(1,t))}else if(e.includes(":")&&e.indexOf(":")===e.lastIndexOf(":")){let[t,r]=e.split(":");r&&/^\d+$/.test(r)&&st(t)&&(e=t)}return e.startsWith("::ffff:")&&(e=e.substring(7)),st(e)||Ft(e)?e:null}function Gu(n,e){return e?(Array.isArray(e)?e:e.split(",").map(r=>r.trim()).filter(Boolean)).includes(n):!1}function Ju(n,e,t){let r=So(e);if(r&&qe(r)&&!Gu(r,t))return r;let s=So(n.get("CF-Connecting-IP"));if(s)return s;let o=So(n.get("X-Real-IP"));if(o)return o;let i=n.get("X-Forwarded-For");if(i){let c=i.split(",");for(let a of c){let l=So(a);if(l)return l}}return r||"127.0.0.1"}function vl(n,e,t,r){return async(s,o)=>{try{let i=s.headers["x-forwarded-proto"]||e,c=s.headers.host||`${t}:${r}`,a=new URL(s.url||"/",`${i}://${c}`),l=new Headers;for(let[w,y]of Object.entries(s.headers))y&&(Array.isArray(y)?y.forEach(E=>l.append(w,E)):l.set(w,String(y)));let d=Ju(l,s.socket.remoteAddress,n.TRUSTED_PROXIES);l.set("CF-Connecting-IP",d);let u=l.get("CF-IPLatitude");(!u||u.trim()==="")&&l.set("CF-IPLatitude","0.0");let f=l.get("CF-IPLongitude");(!f||f.trim()==="")&&l.set("CF-IPLongitude","0.0");let p=null;if(s.method!=="GET"&&s.method!=="HEAD"){let w=[];for await(let y of s)w.push(typeof y=="string"?xl.from(y):y);p=xl.concat(w)}let m=new Request(a.toString(),{method:s.method,headers:l,body:p,duplex:"half"}),h={waitUntil(w){w.catch(y=>console.error(`[${e.toUpperCase()} Background Task Error]:`,y))},passThroughOnException(){}},g=await Eo.fetch(m,n,h);o.statusCode=g.status,o.statusMessage=g.statusText;let b=typeof g.headers.getSetCookie=="function"?g.headers.getSetCookie():[];if(b.length===0&&g.headers.has("set-cookie")){let w=g.headers.get("set-cookie");w&&(b=[w])}if(g.headers.forEach((w,y)=>{let E=y.toLowerCase();E!=="set-cookie"&&(e==="https"&&E==="alt-svc"?o.setHeader("Alt-Svc",`h3=":${r}"; ma=86400, h3-29=":${r}"; ma=86400`):o.setHeader(y,w))}),b.length>0){let y=i==="http"?b.map(E=>E.replace(/;\s*Secure\b/gi,"")):b;o.setHeader("Set-Cookie",y)}if(e==="https"&&!o.hasHeader("Alt-Svc")&&o.setHeader("Alt-Svc",`h3=":${r}"; ma=86400, h3-29=":${r}"; ma=86400`),g.body){let w=g.body.getReader();for(;;){let{done:y,value:E}=await w.read();if(y)break;o.write(E)}}o.end()}catch(i){console.error(`[${e.toUpperCase()} Server] Request processing failed:`,i),o.headersSent||(o.statusCode=500,o.setHeader("Content-Type","application/json"),o.end(JSON.stringify({error:"Internal Server Error"})))}}}var Ro=class{constructor(e){this.options=e;Al(this.options.env,this.options.staticDir)}options;server=null;isRunning=!1;start(){return new Promise((e,t)=>{let{port:r,host:s,env:o}=this.options,i=vl(o,"http",s,r);this.server=Wu.createServer(i),this.server.keepAliveTimeout=65e3,this.server.headersTimeout=66e3,this.server.on("connection",a=>{a.setNoDelay(!0)});let c=a=>{this.server=null,console.error(sn({serviceName:"Web Dashboard & DoH (HTTP)",protocol:"HTTP",port:r,err:a,alternateOption:`--http-port <port> (e.g. --http-port ${r+1})`})),t(a)};this.server.once("error",c),this.server.listen(r,s,()=>{this.server?.removeListener("error",c),this.server?.on("error",a=>{console.error("[HTTP Server] Runtime error:",a)}),this.isRunning=!0,console.log(`[HTTP Server] Web Dashboard & DoH listening on http://${s}:${r}`),e()})})}stop(){return new Promise(e=>{this.server&&this.isRunning?this.server.close(()=>{this.isRunning=!1,console.log("[HTTP Server] Service stopped."),e()}):e()})}},To=class{constructor(e){this.options=e;Al(this.options.env,this.options.staticDir)}options;server=null;isRunning=!1;start(){return new Promise((e,t)=>{let{port:r,host:s,certPath:o,keyPath:i,env:c}=this.options;try{let a=jr.readFileSync(o),l=jr.readFileSync(i),d=vl(c,"https",s,r);this.server=ju.createServer({cert:a,key:l},d),this.server.keepAliveTimeout=65e3,this.server.headersTimeout=66e3,this.server.on("connection",f=>{f.setNoDelay(!0)});let u=f=>{this.server=null,console.error(sn({serviceName:"Web Dashboard & DoH (HTTPS)",protocol:"HTTPS",port:r,err:f,alternateOption:`--https-port <port> (e.g. --https-port ${r+1})`})),t(f)};this.server.once("error",u),this.server.listen(r,s,()=>{this.server?.removeListener("error",u),this.server?.on("error",f=>{console.error("[HTTPS Server] Runtime error:",f)}),this.isRunning=!0,console.log(`[HTTPS Server] Web Dashboard & DoH listening on https://${s}:${r}`),e()})}catch(a){t(a)}})}stop(){return new Promise(e=>{this.server&&this.isRunning?this.server.close(()=>{this.isRunning=!1,console.log("[HTTPS Server] Service stopped."),e()}):e()})}};Fe();import Pl from"node:fs";import Yu from"node:net";import zu from"node:dgram";import{DatabaseSync as Xu}from"node:sqlite";Fe();function Fi(n,e,t=300){return new Promise(r=>{let s=new Yu.Socket,o=!1,i=a=>{if(!o){o=!0;try{s.destroy()}catch{}r(a)}};s.setTimeout(t),s.once("connect",()=>i("active")),s.once("timeout",()=>i("free")),s.once("error",()=>i("free"));let c=e==="0.0.0.0"||e==="::"?"127.0.0.1":e;try{s.connect(n,c)}catch{i("free")}})}function Qu(n,e){return new Promise(t=>{let r=zu.createSocket("udp4"),s=!1,o=i=>{if(!s){s=!0;try{r.close()}catch{}t(i)}};r.once("error",i=>{let c=i?.code;o(c==="EADDRINUSE"?"active":"free")});try{r.bind(n,e,()=>{o("free")})}catch{o("active")}})}function Zu(n){if(!Pl.existsSync(n))return{exists:!1,statusText:"Not Initialized (File does not exist yet; created on first start)"};try{let e=Pl.statSync(n),t=e.size>1024*1024?`${(e.size/(1024*1024)).toFixed(2)} MB`:`${(e.size/1024).toFixed(1)} KB`,r=0,s=0,o=0;try{let c=new Xu(n,{readOnly:!0}),a=c.prepare("SELECT count(*) as cnt FROM sqlite_master WHERE type='table' AND name='d1_migrations'").get();a&&a.cnt>0&&(r=c.prepare("SELECT count(*) as count FROM d1_migrations").get()?.count||0);let l=c.prepare("SELECT count(*) as cnt FROM sqlite_master WHERE type='table' AND name='users'").get();l&&l.cnt>0&&(s=c.prepare("SELECT count(*) as count FROM users").get()?.count||0);let d=c.prepare("SELECT count(*) as cnt FROM sqlite_master WHERE type='table' AND name='profiles'").get();d&&d.cnt>0&&(o=c.prepare("SELECT count(*) as count FROM profiles").get()?.count||0),c.close()}catch{}let i=[t,r>0?`${r} migrations`:null,s>0?`${s} user(s)`:null,o>0?`${o} profile(s)`:null].filter(Boolean).join(", ");return{exists:!0,sizeFormatted:t,migrationsCount:r,usersCount:s,profilesCount:o,statusText:`Ready (${i})`}}catch(e){return{exists:!0,statusText:`Exists (Read warning: ${e instanceof Error?e.message:String(e)})`}}}async function Cl(n,e){let t=jn(n.tlsCertPath,n.tlsKeyPath),r=!n.disableHttps&&t.configured&&t.filesExist,[s,o,i,c]=await Promise.all([Fi(n.httpPort,n.host),r?Fi(n.httpsPort,n.host):Promise.resolve("disabled"),n.disableUdp?Promise.resolve("disabled"):Qu(n.udpPort,n.host),n.disableDot?Promise.resolve("disabled"):Fi(n.dotPort,n.host)]),a=Zu(n.dbPath),l="Default (Insecure fallback, please configure JWT_SECRET)";Oe(e)&&(l=bt(e)?"Configured (Strong, >= 32 characters)":"Configured (Warning: Shorter than 32 characters)");let d=s==="active"?nn(n.httpPort,"TCP"):null,u=d?`: ${rn(d)}`:"",f=s==="active"?`http://${n.host}:${n.httpPort} [Active / Listening${u}]`:`http://${n.host}:${n.httpPort} [Stopped / Port Available]`,p="";if(n.disableHttps)p="Disabled (--disable-https)";else if(!t.configured)p=`https://${n.host}:${n.httpsPort} [Disabled - TLS Certificates Not Configured]`;else if(!t.filesExist)p=`https://${n.host}:${n.httpsPort} [Warning - TLS Certificate Files Missing]`;else if(o==="active"){let ie=nn(n.httpsPort,"TCP"),k=ie?`: ${rn(ie)}`:"";p=`https://${n.host}:${n.httpsPort} [Active / Listening${k}]`}else p=`https://${n.host}:${n.httpsPort} [Stopped / Port Available]`;let m=!n.disableUdp&&i==="active"?nn(n.udpPort,"UDP"):null,h=m?`: ${rn(m)}`:"",g=n.disableUdp?"Disabled (--disable-udp)":i==="active"?`udp://${n.host}:${n.udpPort} [Active / In Use${h}]`:`udp://${n.host}:${n.udpPort} [Stopped / Port Available]`,b="",w=n.dotDomain?` (${n.dotDomain})`:"";if(n.disableDot)b="Disabled (--disable-dot)";else if(!t.configured)b=`tls://${n.host}:${n.dotPort}${w} [Disabled - TLS Certificates Not Configured]`;else if(!t.filesExist)b=`tls://${n.host}:${n.dotPort}${w} [Warning - TLS Certificate Files Missing]`;else if(!t.isWildcard)b=`tls://${n.host}:${n.dotPort}${w} [Paused - Requires Wildcard Certificate (*.domain)]`;else if(c==="active"){let ie=nn(n.dotPort,"TCP"),k=ie?`: ${rn(ie)}`:"";b=`tls://${n.host}:${n.dotPort}${w} [Active / Listening${k}]`}else b=`tls://${n.host}:${n.dotPort}${w} [Stopped / Port Available]`;let y=n.defaultProfileKey?`Explicit (${n.defaultProfileKey})`:"Auto (Database default profile)",E=Ce({title:"DNS Worker Runtime Status (Serverfull Mode)",borderChar:"=",bullet:"\u2022 "}),_=nt({title:`
443
+ `).bind(t.id).first()),d?Response.json({encryptedSk:d.encrypted_sk,iv:d.iv}):new Response("Wrapped key not found for this passkey",{status:404})}if(c==="recovery-wrapped-key"&&n.method==="GET"){let a=await e.DB.prepare("SELECT encrypted_sk, iv, salt FROM user_recovery_wrapped_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?) LIMIT 1").bind(t.id).first();return a?Response.json({encryptedSk:a.encrypted_sk,iv:a.iv,salt:a.salt}):new Response("Recovery wrapped key not found",{status:404})}if(c==="init"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{publicKey:l,passkeyId:d,encryptedSk:u,iv:f,recovery:p}=a;if(!l||!d&&!p)return new Response("Missing required fields (publicKey and either passkey or recovery)",{status:400});let m=null;if(d&&(m=await o.getByIdOrCredentialId(d,t.id),!m)){let E=await o.listByUser(t.id);E.length>0&&(m=E[0])}let h=await i.listByOwner(t.id),g=Math.floor(Date.now()/1e3),b=typeof l=="string"?l:JSON.stringify(l),w=[],y=h.length>0?h[0].id:null;if(y){m&&u&&f&&w.push(e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(m.id,y,u,f,g));for(let E of h)w.push(e.DB.prepare("INSERT INTO user_log_keys (profile_id, public_key, created_at, is_active) VALUES (?, ?, ?, 1) ON CONFLICT(profile_id) DO UPDATE SET public_key = excluded.public_key, created_at = excluded.created_at, is_active = 1").bind(E.id,b,g)),p&&p.encryptedSk&&p.iv&&p.salt&&w.push(e.DB.prepare("INSERT INTO user_recovery_wrapped_keys (profile_id, encrypted_sk, iv, salt, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, salt = excluded.salt, created_at = excluded.created_at").bind(E.id,p.encryptedSk,p.iv,p.salt,g))}w.length>0&&await e.DB.batch(w);for(let E of h)en(E.id);return Response.json({success:!0})}if(c==="wrap"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{passkeyId:l,encryptedSk:d,iv:u}=a;if(!l||!d||!u)return new Response("Missing required fields (passkeyId, encryptedSk, iv)",{status:400});let f=await o.getByIdOrCredentialId(l,t.id);if(!f){let g=await o.listByUser(t.id);g.length>0&&(f=g[0])}if(!f)return new Response("Passkey not found or unauthorized",{status:403});let p=await i.listByOwner(t.id),m=p.length>0?p[0].id:null;if(!m)return new Response("No profiles found for user",{status:400});let h=Math.floor(Date.now()/1e3);return await e.DB.prepare("INSERT INTO user_passkey_wrapped_keys (passkey_id, profile_id, encrypted_sk, iv, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(passkey_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, created_at = excluded.created_at").bind(f.id,m,d,u,h).run(),Response.json({success:!0})}if(c==="wrap-recovery"&&n.method==="POST"){let a;try{a=await n.json()}catch{return new Response("Invalid JSON body",{status:400})}let{encryptedSk:l,iv:d,salt:u}=a;if(!l||!d||!u)return new Response("Missing required fields (encryptedSk, iv, salt)",{status:400});let f=await i.listByOwner(t.id),p=Math.floor(Date.now()/1e3),m=[];for(let h of f)m.push(e.DB.prepare("INSERT INTO user_recovery_wrapped_keys (profile_id, encrypted_sk, iv, salt, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(profile_id) DO UPDATE SET encrypted_sk = excluded.encrypted_sk, iv = excluded.iv, salt = excluded.salt, created_at = excluded.created_at").bind(h.id,l,d,u,p));return m.length>0&&await e.DB.batch(m),Response.json({success:!0})}if(c==="enable"&&n.method==="POST"){let a=await i.listByOwner(t.id);await e.DB.prepare("UPDATE user_log_keys SET is_active = 1 WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id).run();for(let l of a)en(l.id);return Response.json({success:!0,enabled:!0})}if(n.method==="DELETE"&&(!c||c==="keys")){let l=new URL(n.url).searchParams.get("purge")==="true",d=await i.listByOwner(t.id);l?await e.DB.batch([e.DB.prepare("DELETE FROM user_log_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id),e.DB.prepare("DELETE FROM user_passkey_wrapped_keys WHERE passkey_id IN (SELECT id FROM passkeys WHERE user_id = ?)").bind(t.id),e.DB.prepare("DELETE FROM user_recovery_wrapped_keys WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id)]):await e.DB.prepare("UPDATE user_log_keys SET is_active = 0 WHERE profile_id IN (SELECT id FROM profiles WHERE owner_id = ?)").bind(t.id).run();for(let u of d)en(u.id);return Response.json({success:!0,purged:l,enabled:!1})}return new Response("Not Found",{status:404})}async function bl(n,e,t,r,s){let o=r[2];return o==="me"||o==="delete"||o==="logs"||o==="db-size"?dl(n,e,t,r,s):o==="password"||o==="totp"||o==="passkeys"||o==="mfa"||o==="migrate-password"||o==="pin"||o==="recovery-keys"?gl(n,e,t,r,s):o==="activity"||o==="sessions"?yl(n,e,t,r,s):o==="e2ee"?wl(n,e,t,r,s):new Response("Not Found",{status:404})}pe();Te();async function El(n,e,t,r,s){let o=new Y(e.DB,e),i=new q(e.DB);if(r[2]==="users"){if(n.method==="GET"){let c=await o.listAll();return new Response(JSON.stringify(c),{headers:{"Content-Type":"application/json"}})}if(n.method==="POST"){let{username:c,password:a,role:l}=await n.json();if(!c||!Rt.test(c))return new Response("Invalid username format",{status:400});if(!a||!St.test(a))return new Response("Password format error",{status:400});let d=await ot(a,2),u=j(15);try{return await o.create({id:u,username:c,passwordHash:d,role:l||"user",passwordVersion:2}),new Response(JSON.stringify({id:u}),{status:201})}catch(f){return new Response(f.message,{status:400})}}if(n.method==="DELETE"&&r[3]){let c=r[3];return c===t.id?new Response("Cannot delete yourself",{status:400}):(await i.deleteByOwner(c),await o.delete(c),new Response(null,{status:204}))}}if(r[2]==="settings"){let c=new he(e.DB);if(n.method==="GET"){let a=await c.getAll();return delete a.jwt_secret,new Response(JSON.stringify(a),{headers:{"Content-Type":"application/json"}})}if(n.method==="PATCH"){let a=await n.json();return delete a.jwt_secret,await c.setMany(a),new Response(JSON.stringify({success:!0}))}}return new Response("Not Found",{status:404})}async function _l(n,e,t,r){let o=new URL(n.url).pathname.split("/").filter(Boolean);return o[1]==="account"?bl(n,e,t,o,r):o[1]==="admin"?Yn.isAdmin(t)?El(n,e,t,o,r):new Response("Forbidden",{status:403}):new Response("Not Found",{status:404})}ge();async function Sl(n,e){let t=new URL(n.url),r=typeof caches<"u"?caches.default:null;if(t.pathname==="/api/clientinfo"){let s=n.headers.get("CF-Connecting-IP")||"127.0.0.1",o=await P.get(r,`active_dns:${s}`),i=n.cf,c=i?.country||n.headers.get("CF-IPCountry")||"",a=c,l=i?.region||n.headers.get("CF-Region")||"",d=i?.city||n.headers.get("CF-IPCity")||"",u=i?.timezone||n.headers.get("CF-Timezone")||"",f=i?.asn?Number(i.asn):n.headers.get("CF-ASN")?Number(n.headers.get("CF-ASN")):0,p=i?.asOrganization||n.headers.get("CF-AS-Org")||"";if(!c||c==="UNKNOWN"||c==="UN"||c==="XX")if(!qe(s))c="Private Network",a="LAN",l="LAN",d="Local",u="UNKNOWN",p="Private Network";else try{let h=await Er(s);if(h&&(c=h.country||h.country_code||"UNKNOWN",a=h.country_code||"UNKNOWN",l=h.region||"UNKNOWN",d=h.city||"UNKNOWN",u=h.timezone||"UNKNOWN",p=h.org||h.isp||"UNKNOWN",!f&&h.as)){let g=h.as.match(/^AS(\d+)/i);g&&(f=Number(g[1]))}}catch(h){console.warn(`[ClientInfo] Failed resolving geoip for ${s}:`,h)}let m=e.SERVERFULL_DEFAULT_PROFILE_KEY!==void 0||e.SERVERFULL_HOST!==void 0||e.SERVERFULL_DOT_DOMAIN!==void 0||e.SERVERFULL_HTTP_PORT!==void 0||e.SERVERFULL_HTTPS_PORT!==void 0;return new Response(JSON.stringify({ip:s,country:c||"UNKNOWN",countryCode:a||void 0,region:l||"UNKNOWN",city:d||"UNKNOWN",timezone:u||"UNKNOWN",asn:f||0,asOrganization:p||"UNKNOWN",connectedProfileId:o||null,substituteDomain:e.SUBSTITUTE_DOMAIN||tr,dotDomain:e.SERVERFULL_DOT_DOMAIN||e.DOT_DOMAIN||null,isServerfull:m,mode:m?"serverfull":"cloudflare"}),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/regions"){let s={};for(let[o,i]of Object.entries(e))if(o.startsWith("IP_REGION_")&&typeof i=="string")try{let c=o.replace("IP_REGION_",""),a=i.trim();a=a.replace(/^"""|"""$/g,"").replace(/^"|"$/g,"").replace(/^'|'$/g,"").trim(),s[c]=JSON.parse(a)}catch{}return Object.keys(s).length===0&&(s.CN=Xr),new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/substitute"){let s=e.SUBSTITUTE_DOMAIN||tr,o=null,i=null,c=async a=>{let l=["https://cloudflare-dns.com/dns-query","https://1.1.1.1/dns-query"];for(let d of l)try{let u=await fetch(`${d}?name=${s}&type=${a}`,{headers:{Accept:"application/dns-json"},signal:AbortSignal.timeout(3e3)});if(u.ok){let f=await u.json();if(f?.Answer?.length>0){let p=f.Answer.find(m=>m.type===(a==="A"?1:28));if(p?.data)return p.data}}}catch(u){console.error(`[Substitute Resolve] Failed resolving ${a} via ${d}:`,u)}return null};try{let[a,l]=await Promise.all([c("A"),c("AAAA")]);o=a,i=l}catch(a){console.error("[Substitute API] Error resolving substitute domain:",a)}return new Response(JSON.stringify({ip:o,ipv6:i}),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/presets/upstreams"){let s=Yr;if(e.PRESET_UPSTREAMS)try{let o=JSON.parse(e.PRESET_UPSTREAMS);Array.isArray(o)&&o.length>0&&(s=o)}catch(o){console.warn("[System API] Failed to parse PRESET_UPSTREAMS from env:",o)}return new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/presets/filters"){let s=zr;if(e.PRESET_EXTERNAL_FILTERS)try{let o=JSON.parse(e.PRESET_EXTERNAL_FILTERS);Array.isArray(o)&&o.length>0&&(s=o)}catch(o){console.warn("[System API] Failed to parse PRESET_EXTERNAL_FILTERS from env:",o)}return new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/presets/ech-fronting-domains"){let s=Ji(e);return new Response(JSON.stringify(s),{headers:{"Content-Type":"application/json"}})}if(t.pathname.startsWith("/api/icon/")){let s=t.pathname.replace("/api/icon/","");if(!s||!/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(s)||s.includes("..")||s.includes("/")||s.includes("\\"))return new Response("Invalid domain format",{status:400});let o=`https://icons.duckduckgo.com/ip3/${s}`;try{let i=await fetch(o),c=new Headers(i.headers);return c.set("Cache-Control","public, max-age=2592000"),c.delete("Access-Control-Allow-Origin"),new Response(i.body,{status:i.status,statusText:i.statusText,headers:c})}catch{return new Response("Error fetching icon",{status:500})}}if(t.pathname==="/api/geoip"){let s=t.searchParams.get("ip"),o=n.headers.get("CF-Connecting-IP"),i=(s||o||"").trim();if(i==="127.0.0.1"||i==="localhost"||i==="::1"||i.startsWith("192.168.")||i.startsWith("10.")||/^172\.(1[6-9]|2\d|3[01])\./.test(i))return new Response(JSON.stringify({success:!0,ip:i,city:"Local",country:"Private Network",flag:{emoji:"\u{1F6DC}"}}),{headers:{"Content-Type":"application/json"}});try{let c=i?`https://ipwho.is/${encodeURIComponent(i)}`:"https://ipwho.is/",a=await fetch(c,{signal:AbortSignal.timeout(3500)});if(a.ok){let l=await a.json();return new Response(JSON.stringify(l),{headers:{"Content-Type":"application/json"}})}}catch(c){console.warn(`[System API] Failed resolving geoip for ${i||"server"}:`,c)}return new Response(JSON.stringify({success:!1,ip:i||"unknown"}),{headers:{"Content-Type":"application/json"}})}if(t.pathname==="/api/resolve"){let s=t.searchParams.get("name")||t.searchParams.get("domain");if(!s)return new Response(JSON.stringify({error:"Missing domain parameter"}),{status:400,headers:{"Content-Type":"application/json"}});if(/^(\d{1,3}\.){3}\d{1,3}$/.test(s))return new Response(JSON.stringify({ipv4:[s],ipv6:[]}),{headers:{"Content-Type":"application/json"}});if(/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/.test(s))return new Response(JSON.stringify({ipv4:[],ipv6:[s]}),{headers:{"Content-Type":"application/json"}});let o=async i=>{let c=["https://cloudflare-dns.com/dns-query","https://1.1.1.1/dns-query"];for(let a of c)try{let l=await fetch(`${a}?name=${encodeURIComponent(s)}&type=${i}`,{headers:{Accept:"application/dns-json"},signal:AbortSignal.timeout(3e3)});if(l.ok){let d=await l.json();if(Array.isArray(d?.Answer)&&d.Answer.length>0){let u=i==="A"?1:28;return d.Answer.filter(f=>f.type===u&&typeof f.data=="string").map(f=>f.data)}}}catch{}return[]};try{let[i,c]=await Promise.all([o("A"),o("AAAA")]);if(i.length===0&&c.length===0&&typeof process<"u"&&process.versions?.node)try{let a=await import("node:dns/promises"),[l,d]=await Promise.allSettled([a.resolve4(s),a.resolve6(s)]);l.status==="fulfilled"&&Array.isArray(l.value)&&(i=l.value),d.status==="fulfilled"&&Array.isArray(d.value)&&(c=d.value)}catch{}return new Response(JSON.stringify({ipv4:i,ipv6:c}),{headers:{"Content-Type":"application/json"}})}catch(i){return console.error("DNS resolve failed:",i),new Response(JSON.stringify({ipv4:[],ipv6:[],error:"Internal server error"}),{status:500,headers:{"Content-Type":"application/json"}})}}return new Response("Not Found",{status:404})}ge();async function Rl(n,e,t){try{let r=caches.default,s=Math.floor(Date.now()/1e3),o="cron:last_cleanup_timestamp",i=await P.get(r,o);if(!i||s-i>=3600){try{let c=new Y(e.DB,e),{clearedProfiles:a,deletedUsers:l}=await c.applyInactivityPolicy(s);(a>0||l>0)&&console.log(`[Cron] Inactivity cleanup: cleared ${a} profile(s), deleted ${l} user(s).`)}catch(c){console.error("[Cron] Inactivity policy execution failed:",c)}try{let c=new Ie(e.DB),a=e.MAX_LOG_RETENTION_DAYS!==void 0&&e.MAX_LOG_RETENTION_DAYS!==""?Number(e.MAX_LOG_RETENTION_DAYS):30,l=e.LOG_CLEANUP_BATCH_LIMIT!==void 0&&e.LOG_CLEANUP_BATCH_LIMIT!==""?Number(e.LOG_CLEANUP_BATCH_LIMIT):1e3,d=e.LOG_CLEANUP_DAILY_BUDGET!==void 0&&e.LOG_CLEANUP_DAILY_BUDGET!==""?Number(e.LOG_CLEANUP_DAILY_BUDGET):2e4;await c.cleanupGlobal(a,l,d)}catch(c){console.error("[Cron] Global log cleanup failed:",c)}try{let{SessionModel:c}=await Promise.resolve().then(()=>(Be(),pr)),a=new c(e.DB),{deletedSessions:l,deletedPending:d}=await a.deleteExpiredSessions(s);(l>0||d>0)&&console.log(`[Cron] Purged ${l} expired session(s) and ${d} pending token(s).`)}catch(c){console.error("[Cron] Session cleanup failed:",c)}await P.set(r,o,s,3600),console.log("[Cron] Cleanup phase completed at",new Date().toISOString())}try{await Bo(e.DB)}catch(c){console.error("[Cron] Cloudflare IP range sync failed:",c)}try{let c="cron:last_idle_sync_check",l=await P.get(r,c);if(!!!(l&&s-l<600)){let u=Number(e.SYNC_PROFILE_INTERVAL_SEC)||86400,f=s-u,p=new q(e.DB),m=Number(e.SYNC_BATCH_SIZE)||1,h=await p.getSyncTargets(f,m);if(h.length>0){await P.delete(r,c);for(let g of h)try{await zn(g.id,e,t)}catch(b){let w=b instanceof Error?b.message:String(b);console.error(`[Cron] Sync failed for profile ${g.id}:`,w)}console.log(`[Cron] Sync: processed ${h.length} profile(s).`)}else await P.set(r,c,s,600)}}catch(c){let a=c instanceof Error?c.message:String(c);console.error("[Cron] Sync phase failed:",a)}}catch(r){let s=r instanceof Error?r.message:String(r);console.error("[Cron] Critical Failure:",s)}}async function Tl(n,e,t){let r=new Request(new URL("https://obex.local/world-110m-v2.json"),n),s=caches.default,o=await s.match(r);if(!o)try{let c=await fetch("https://openlayers.org/en/latest/examples/data/topojson/world-110m.json",{headers:{"User-Agent":"DNS Worker/1.0 (Cloudflare Worker Map Proxy)"}});if(!c.ok)throw new Error(`Failed to fetch map data from openlayers.org: ${c.status}`);let a=await c.json();if(a&&a.objects){let l={};a.objects.countries&&(l.countries=a.objects.countries),a.objects.land&&(l.land=a.objects.land),a.objects=l}a&&a.objects&&a.objects.countries&&Array.isArray(a.objects.countries.geometries)&&a.objects.countries.geometries.forEach(l=>{l.id!==void 0&&l.id!==null&&(l.id=String(l.id).padStart(3,"0"))}),o=new Response(JSON.stringify(a),{headers:{"Content-Type":"application/json; charset=utf-8","Cache-Control":"public, max-age=2592000","Access-Control-Allow-Origin":"*"}}),t.waitUntil(s.put(r,o.clone()))}catch(i){return console.error("[MapData API Proxy] Error fetching or transforming TopoJSON:",i),new Response(JSON.stringify({error:"Failed to fetch map data"}),{status:502,headers:{"Content-Type":"application/json"}})}return o}Fe();var Eo={async fetch(n,e,t){let r=new Uint8Array(16);crypto.getRandomValues(r);let s=Array.from(r).map(l=>l.toString(16).padStart(2,"0")).join(""),o=null,c=await(async()=>{let l=new URL(n.url);if(l.pathname==="/world-110m.json")return Tl(n,e,t);if(l.pathname.startsWith("/api/")){let p=!1;try{p=!e.DB}catch{p=!0}let m=!Oe(e.JWT_SECRET);if(p||m)return new Response(JSON.stringify({error:"configuration_error",isDbMissing:p,isJwtSecretMissing:m,message:"DNS Worker is not fully configured. Please configure the D1 database binding and JWT_SECRET secret variable."}),{status:503,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}});o=await Li(n,e,t);let h=["/api/auth/login","/api/auth/signup","/api/auth/prelogin","/api/auth/check-username","/api/auth/config","/api/auth/refresh","/api/auth/logout","/api/auth/unlock-session","/api/clientinfo","/api/regions","/api/substitute","/api/resolve","/api/geoip"].includes(l.pathname)||l.pathname.startsWith("/api/icon/")||l.pathname.startsWith("/api/presets/")||l.pathname.startsWith("/api/auth/forgot-password");return!o&&!h?new Response("Unauthorized",{status:401}):o&&o.isPaused&&!["/api/auth/unlock-session","/api/auth/logout","/api/auth/refresh"].includes(l.pathname)?new Response("session_paused",{status:403}):o&&["POST","PUT","PATCH","DELETE"].includes(n.method)&&!h&&!Oi(n)?new Response("CSRF validation failed",{status:403}):l.pathname.startsWith("/api/auth/")?Zc(n,e):l.pathname==="/api/clientinfo"||l.pathname==="/api/regions"||l.pathname==="/api/substitute"||l.pathname==="/api/resolve"||l.pathname==="/api/geoip"||l.pathname.startsWith("/api/presets/")||l.pathname.startsWith("/api/icon/")?Sl(n,e):l.pathname.startsWith("/api/profiles")?cl(n,e,o,t):l.pathname.startsWith("/api/account")||l.pathname.startsWith("/api/admin")?_l(n,e,o,t):new Response("API Not Found",{status:404})}if(l.pathname==="/setup.sh"){let p=l.searchParams.get("key");if(!p||!Wt.test(p))return new Response("Missing or invalid key parameter",{status:400});let m=Fc(l.origin,p);return new Response(m,{headers:{"Content-Type":"text/plain; charset=utf-8","Cache-Control":"no-cache"}})}let d=l.pathname.slice(1),u=Wt.test(d),f=n.method==="POST"||l.searchParams.has("dns")||n.headers.get("accept")?.includes("dns-message");if(u&&(f||n.method==="OPTIONS"))return n.method==="OPTIONS"?new Response(null,{status:204,headers:{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Accept","Access-Control-Max-Age":"86400"}}):Uc(n,e,t,d);try{let p=await e.ASSETS.fetch(n);if(p.status===404&&!l.pathname.startsWith("/assets/")&&(n.method==="GET"||n.method==="HEAD")&&(p=await e.ASSETS.fetch(new Request(l.origin+"/",{method:n.method,headers:n.headers}))),l.pathname.endsWith(".wasm")){let h=new Headers(p.headers);return h.set("Content-Type","application/wasm"),h.set("Cross-Origin-Resource-Policy","same-origin"),new Response(p.body,{status:p.status,statusText:p.statusText,headers:h})}if((p.headers.get("Content-Type")||"").includes("text/html")){let h=!1;try{h=!e.DB}catch{h=!0}let g=!Oe(e.JWT_SECRET),b=e.SERVERFULL_DEFAULT_PROFILE_KEY!==void 0||e.SERVERFULL_HOST!==void 0||e.SERVERFULL_DOT_DOMAIN!==void 0||e.SERVERFULL_HTTP_PORT!==void 0||e.SERVERFULL_HTTPS_PORT!==void 0,w="{}";try{w=JSON.stringify({nonce:s,isDbMissing:h,isJwtSecretMissing:g,isServerfull:b,mode:b?"serverfull":"cloudflare"})}catch{}return new HTMLRewriter().on("head",{element(y){y.prepend(`<script nonce="${s}">window.DNS_WORKER_CONFIG = ${w};</script>`,{html:!0})}}).transform(p)}return p}catch{return new Response("Asset Fetch Error",{status:500})}})(),a=o;if(a&&!_n(n.headers.get("Cookie"))){let l=a.sessionId?.startsWith("k_");try{let d=j(32);c.headers.append("Set-Cookie",it(d,e,l))}catch{let u=new Headers(c.headers),f=j(32);u.append("Set-Cookie",it(f,e,l)),c=new Response(c.body,{status:c.status,statusText:c.statusText,headers:u})}}if(c.headers.get("Content-Type")?.includes("application/dns-message")){let l=new Headers(c.headers);return l.set("Access-Control-Allow-Origin","*"),l.set("Access-Control-Allow-Methods","GET, POST, OPTIONS"),l.set("Access-Control-Allow-Headers","Content-Type, Accept"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:l})}return ki(c,s)},async scheduled(n,e,t){await Rl(n,e,t)}};var Vu={".html":"text/html; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".webp":"image/webp",".png":"image/png",".jpg":"image/jpeg",".svg":"image/svg+xml",".ico":"image/x-icon",".webmanifest":"application/manifest+json",".wasm":"application/wasm"};function Al(n,e){if(n.ASSETS)return;let t=e||_o.join(Mt(),"static");n.ASSETS={fetch:async r=>{try{let s=new URL(r.url),o=decodeURIComponent(s.pathname);(o==="/"||o==="")&&(o="/index.html");let i=_o.normalize(o).replace(/^(\.\.[\/\\])+/,""),c=_o.join(t,i);if(jr.existsSync(c)&&jr.statSync(c).isFile()){let a=_o.extname(c).toLowerCase(),l=Vu[a]||"application/octet-stream",d=jr.readFileSync(c);return new Response(d,{status:200,headers:{"Content-Type":l,"Cache-Control":a===".html"?"no-cache":"public, max-age=31536000, immutable","Cross-Origin-Opener-Policy":"same-origin","Cross-Origin-Embedder-Policy":"credentialless","Cross-Origin-Resource-Policy":"same-origin"}})}return new Response("Not Found",{status:404})}catch{return new Response("Asset Error",{status:500})}}}}function So(n){if(!n||typeof n!="string")return null;let e=n.trim();if(!e)return null;if(e.startsWith("[")){let t=e.indexOf("]");t>1&&(e=e.substring(1,t))}else if(e.includes(":")&&e.indexOf(":")===e.lastIndexOf(":")){let[t,r]=e.split(":");r&&/^\d+$/.test(r)&&st(t)&&(e=t)}return e.startsWith("::ffff:")&&(e=e.substring(7)),st(e)||Ft(e)?e:null}function Gu(n,e){return e?(Array.isArray(e)?e:e.split(",").map(r=>r.trim()).filter(Boolean)).includes(n):!1}function Ju(n,e,t){let r=So(e);if(r&&qe(r)&&!Gu(r,t))return r;let s=So(n.get("CF-Connecting-IP"));if(s)return s;let o=So(n.get("X-Real-IP"));if(o)return o;let i=n.get("X-Forwarded-For");if(i){let c=i.split(",");for(let a of c){let l=So(a);if(l)return l}}return r||"127.0.0.1"}function vl(n,e,t,r){return async(s,o)=>{try{let i=s.headers["x-forwarded-proto"]||e,c=s.headers.host||`${t}:${r}`,a=new URL(s.url||"/",`${i}://${c}`),l=new Headers;for(let[w,y]of Object.entries(s.headers))y&&(Array.isArray(y)?y.forEach(E=>l.append(w,E)):l.set(w,String(y)));let d=Ju(l,s.socket.remoteAddress,n.TRUSTED_PROXIES);l.set("CF-Connecting-IP",d);let u=l.get("CF-IPLatitude");(!u||u.trim()==="")&&l.set("CF-IPLatitude","0.0");let f=l.get("CF-IPLongitude");(!f||f.trim()==="")&&l.set("CF-IPLongitude","0.0");let p=null;if(s.method!=="GET"&&s.method!=="HEAD"){let w=[];for await(let y of s)w.push(typeof y=="string"?xl.from(y):y);p=xl.concat(w)}let m=new Request(a.toString(),{method:s.method,headers:l,body:p,duplex:"half"}),h={waitUntil(w){w.catch(y=>console.error(`[${e.toUpperCase()} Background Task Error]:`,y))},passThroughOnException(){}},g=await Eo.fetch(m,n,h);o.statusCode=g.status,o.statusMessage=g.statusText;let b=typeof g.headers.getSetCookie=="function"?g.headers.getSetCookie():[];if(b.length===0&&g.headers.has("set-cookie")){let w=g.headers.get("set-cookie");w&&(b=[w])}if(g.headers.forEach((w,y)=>{let E=y.toLowerCase();E!=="set-cookie"&&(e==="https"&&E==="alt-svc"?o.setHeader("Alt-Svc",`h3=":${r}"; ma=86400, h3-29=":${r}"; ma=86400`):o.setHeader(y,w))}),b.length>0){let y=i==="http"?b.map(E=>E.replace(/;\s*Secure\b/gi,"")):b;o.setHeader("Set-Cookie",y)}if(e==="https"&&!o.hasHeader("Alt-Svc")&&o.setHeader("Alt-Svc",`h3=":${r}"; ma=86400, h3-29=":${r}"; ma=86400`),g.body){let w=g.body.getReader();for(;;){let{done:y,value:E}=await w.read();if(y)break;o.write(E)}}o.end()}catch(i){console.error(`[${e.toUpperCase()} Server] Request processing failed:`,i),o.headersSent||(o.statusCode=500,o.setHeader("Content-Type","application/json"),o.end(JSON.stringify({error:"Internal Server Error"})))}}}var Ro=class{constructor(e){this.options=e;Al(this.options.env,this.options.staticDir)}options;server=null;isRunning=!1;start(){return new Promise((e,t)=>{let{port:r,host:s,env:o}=this.options,i=vl(o,"http",s,r);this.server=Wu.createServer(i),this.server.keepAliveTimeout=65e3,this.server.headersTimeout=66e3,this.server.on("connection",a=>{a.setNoDelay(!0)});let c=a=>{this.server=null,console.error(sn({serviceName:"Web Dashboard & DoH (HTTP)",protocol:"HTTP",port:r,err:a,alternateOption:`--http-port <port> (e.g. --http-port ${r+1})`})),t(a)};this.server.once("error",c),this.server.listen(r,s,()=>{this.server?.removeListener("error",c),this.server?.on("error",a=>{console.error("[HTTP Server] Runtime error:",a)}),this.isRunning=!0,console.log(`[HTTP Server] Web Dashboard & DoH listening on http://${s}:${r}`),e()})})}stop(){return new Promise(e=>{this.server&&this.isRunning?this.server.close(()=>{this.isRunning=!1,console.log("[HTTP Server] Service stopped."),e()}):e()})}},To=class{constructor(e){this.options=e;Al(this.options.env,this.options.staticDir)}options;server=null;isRunning=!1;start(){return new Promise((e,t)=>{let{port:r,host:s,certPath:o,keyPath:i,env:c}=this.options;try{let a=jr.readFileSync(o),l=jr.readFileSync(i),d=vl(c,"https",s,r);this.server=ju.createServer({cert:a,key:l},d),this.server.keepAliveTimeout=65e3,this.server.headersTimeout=66e3,this.server.on("connection",f=>{f.setNoDelay(!0)});let u=f=>{this.server=null,console.error(sn({serviceName:"Web Dashboard & DoH (HTTPS)",protocol:"HTTPS",port:r,err:f,alternateOption:`--https-port <port> (e.g. --https-port ${r+1})`})),t(f)};this.server.once("error",u),this.server.listen(r,s,()=>{this.server?.removeListener("error",u),this.server?.on("error",f=>{console.error("[HTTPS Server] Runtime error:",f)}),this.isRunning=!0,console.log(`[HTTPS Server] Web Dashboard & DoH listening on https://${s}:${r}`),e()})}catch(a){t(a)}})}stop(){return new Promise(e=>{this.server&&this.isRunning?this.server.close(()=>{this.isRunning=!1,console.log("[HTTPS Server] Service stopped."),e()}):e()})}};Fe();import Pl from"node:fs";import Yu from"node:net";import zu from"node:dgram";import{DatabaseSync as Xu}from"node:sqlite";Fe();function Fi(n,e,t=300){return new Promise(r=>{let s=new Yu.Socket,o=!1,i=a=>{if(!o){o=!0;try{s.destroy()}catch{}r(a)}};s.setTimeout(t),s.once("connect",()=>i("active")),s.once("timeout",()=>i("free")),s.once("error",()=>i("free"));let c=e==="0.0.0.0"||e==="::"?"127.0.0.1":e;try{s.connect(n,c)}catch{i("free")}})}function Qu(n,e){return new Promise(t=>{let r=zu.createSocket("udp4"),s=!1,o=i=>{if(!s){s=!0;try{r.close()}catch{}t(i)}};r.once("error",i=>{let c=i?.code;o(c==="EADDRINUSE"?"active":"free")});try{r.bind(n,e,()=>{o("free")})}catch{o("active")}})}function Zu(n){if(!Pl.existsSync(n))return{exists:!1,statusText:"Not Initialized (File does not exist yet; created on first start)"};try{let e=Pl.statSync(n),t=e.size>1024*1024?`${(e.size/(1024*1024)).toFixed(2)} MB`:`${(e.size/1024).toFixed(1)} KB`,r=0,s=0,o=0;try{let c=new Xu(n,{readOnly:!0}),a=c.prepare("SELECT count(*) as cnt FROM sqlite_master WHERE type='table' AND name='d1_migrations'").get();a&&a.cnt>0&&(r=c.prepare("SELECT count(*) as count FROM d1_migrations").get()?.count||0);let l=c.prepare("SELECT count(*) as cnt FROM sqlite_master WHERE type='table' AND name='users'").get();l&&l.cnt>0&&(s=c.prepare("SELECT count(*) as count FROM users").get()?.count||0);let d=c.prepare("SELECT count(*) as cnt FROM sqlite_master WHERE type='table' AND name='profiles'").get();d&&d.cnt>0&&(o=c.prepare("SELECT count(*) as count FROM profiles").get()?.count||0),c.close()}catch{}let i=[t,r>0?`${r} migrations`:null,s>0?`${s} user(s)`:null,o>0?`${o} profile(s)`:null].filter(Boolean).join(", ");return{exists:!0,sizeFormatted:t,migrationsCount:r,usersCount:s,profilesCount:o,statusText:`Ready (${i})`}}catch(e){return{exists:!0,statusText:`Exists (Read warning: ${e instanceof Error?e.message:String(e)})`}}}async function Cl(n,e){let t=jn(n.tlsCertPath,n.tlsKeyPath),r=!n.disableHttps&&t.configured&&t.filesExist,[s,o,i,c]=await Promise.all([Fi(n.httpPort,n.host),r?Fi(n.httpsPort,n.host):Promise.resolve("disabled"),n.disableUdp?Promise.resolve("disabled"):Qu(n.udpPort,n.host),n.disableDot?Promise.resolve("disabled"):Fi(n.dotPort,n.host)]),a=Zu(n.dbPath),l="Default (Insecure fallback, please configure JWT_SECRET)";Oe(e)&&(l=bt(e)?"Configured (Strong, >= 32 characters)":"Configured (Warning: Shorter than 32 characters)");let d=s==="active"?nn(n.httpPort,"TCP"):null,u=d?`: ${rn(d)}`:"",f=s==="active"?`http://${n.host}:${n.httpPort} [Active / Listening${u}]`:`http://${n.host}:${n.httpPort} [Stopped / Port Available]`,p="";if(n.disableHttps)p="Disabled (--disable-https)";else if(!t.configured)p=`https://${n.host}:${n.httpsPort} [Disabled - TLS Certificates Not Configured]`;else if(!t.filesExist)p=`https://${n.host}:${n.httpsPort} [Warning - TLS Certificate Files Missing]`;else if(o==="active"){let ie=nn(n.httpsPort,"TCP"),k=ie?`: ${rn(ie)}`:"";p=`https://${n.host}:${n.httpsPort} [Active / Listening${k}]`}else p=`https://${n.host}:${n.httpsPort} [Stopped / Port Available]`;let m=!n.disableUdp&&i==="active"?nn(n.udpPort,"UDP"):null,h=m?`: ${rn(m)}`:"",g=n.disableUdp?"Disabled (--disable-udp)":i==="active"?`udp://${n.host}:${n.udpPort} [Active / In Use${h}]`:`udp://${n.host}:${n.udpPort} [Stopped / Port Available]`,b="",w=n.dotDomain?` (${n.dotDomain})`:"";if(n.disableDot)b="Disabled (--disable-dot)";else if(!t.configured)b=`tls://${n.host}:${n.dotPort}${w} [Disabled - TLS Certificates Not Configured]`;else if(!t.filesExist)b=`tls://${n.host}:${n.dotPort}${w} [Warning - TLS Certificate Files Missing]`;else if(!t.isWildcard)b=`tls://${n.host}:${n.dotPort}${w} [Paused - Requires Wildcard Certificate (*.domain)]`;else if(c==="active"){let ie=nn(n.dotPort,"TCP"),k=ie?`: ${rn(ie)}`:"";b=`tls://${n.host}:${n.dotPort}${w} [Active / Listening${k}]`}else b=`tls://${n.host}:${n.dotPort}${w} [Stopped / Port Available]`;let y=n.defaultProfileKey?`Explicit (${n.defaultProfileKey})`:"Auto (Database default profile)",E=Ce({title:"DNS Worker Runtime Status (Serverfull Mode)",borderChar:"=",bullet:"\u2022 "}),_=nt({title:`
444
444
  Service Transports:`,items:[{label:"Web Dashboard (HTTP)",value:f},{label:"Web Dashboard (HTTPS)",value:p},{label:"Classic UDP DNS",value:g},{label:"DNS over TLS (DoT)",value:b}]}),S=ts(),x=S.length>0?`${S.join(", ")} [Loaded]`:`None found (Using built-in presets & defaults; default path: ${Xe()})`,T=nt({title:`
445
445
  Database & Storage:`,items:[{label:"SQLite Path",value:n.dbPath},{label:"Database Status",value:a.statusText},{label:"Persistent Data Dir",value:ht()}]}),v="Not Configured (HTTP only, HTTPS & DoT disabled)";if(t.configured)if(!t.filesExist)v="Configured [Files Missing]";else if(t.error)v=`Configured [Invalid Certificate: ${t.error}]`;else{let ie=t.isWildcard?"Wildcard (*.domain)":"Single Domain",k=t.expiresAt?t.expiresAt.toISOString().split("T")[0]:"Unknown";v=`Valid (${ie}, Expires: ${k})`}let C=n.dotDomain?`${n.dotDomain} (Wildcard / Single-domain compatible)`:"Not Configured (Auto SNI prefix matching)",L=!n.disableHttps&&t.configured&&t.filesExist?`Enabled via Alt-Svc (UDP :${n.httpsPort}, RFC 9114)`:"Disabled (Requires active HTTPS with TLS certificate)",H=t.configured&&t.filesExist?`Compatible (RFC 9250 / RFC 9460 DDR, UDP :${n.dotPort})`:"Disabled (Requires TLS certificate)",V=n.echEnabled?`Enabled (RFC 9460, Outer SNI: ${n.echFrontingDomain||"cloudflare-ech.com"})`:"Disabled",Q=nt({title:`
446
446
  Environment & Security:`,items:[{label:"Node.js Runtime",value:`v${process.versions.node} (${process.platform} ${process.arch})`},{label:"Config Directory",value:mn()},{label:"Config Source",value:x},{label:"TLS Certificate",value:v},{label:"JWT Secret",value:l},{label:"DoT Base Domain",value:C},{label:"HTTP/3 (h3)",value:L},{label:"DNS over QUIC (DoQ)",value:H},{label:"ECH (Encrypted Client Hello)",value:V},{label:"Default Profile Key",value:y}]}),Z=[];if(s==="free"&&(i==="free"||n.disableUdp)){let ie=process.platform==="win32"?"dns-worker service install (in Administrator terminal)":"sudo dns-worker service install";Z.push("Services are currently stopped."),Z.push(`To run continuously as a persistent background daemon: ${ie}`)}let _e=Fr(n.dotDomain);!t.configured||!t.filesExist?(Z.push(`To enable secure HTTPS (port ${n.httpsPort}) and DoT (port ${n.dotPort}):`),Z.push(` ${_e}`)):t.isWildcard||(Z.push("Current certificate is single-domain. To support per-profile DoT (<profile>.domain):"),Z.push(` ${_e}`)),Z.push("Run 'dns-worker ech' to view active ECH configuration, outer SNI, and RFC 9460 DNS records.");let K=Z.length>0?Wn(Z):"",Se=[E,_,T,Q,K].filter(Boolean).join(`