@stacknet/userutils 0.6.6 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.cjs +2 -2
- package/dist/components/index.d.cts +59 -1
- package/dist/components/index.d.ts +59 -1
- package/dist/components/index.js +2 -2
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.d.cts +6 -2
- package/dist/server/index.d.ts +6 -2
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {createHmac,timingSafeEqual,randomBytes,createHash}from'crypto';function Z(e){return Buffer.from(e).toString("base64url")}function se(e){return Buffer.from(e,"base64url").toString()}function N(e){try{let r=e.split(".");return r.length!==3?null:JSON.parse(se(r[1]))}catch{return null}}function j(e,r){let t=Z(JSON.stringify({alg:"HS256",typ:"JWT"})),n=Z(JSON.stringify(e)),s=createHmac("sha256",r).update(`${t}.${n}`).digest("base64url");return `${t}.${n}.${s}`}function X(e,r){try{let t=e.split(".");if(t.length!==3)return !1;let[n,s,i]=t,c=createHmac("sha256",r).update(`${n}.${s}`).digest("base64url"),w=Buffer.from(i),m=Buffer.from(c);return w.length!==m.length?!1:timingSafeEqual(w,m)}catch{return false}}function C(e,r){if(!X(e,r))return null;let t=N(e);return !t||t.exp&&t.exp<Math.floor(Date.now()/1e3)?null:t}function E(e,r,t=900,n=300){let s=C(e,r);return !s?.exp||s.exp*1e3-Date.now()>n*1e3?null:j({...s,exp:Math.floor(Date.now()/1e3)+t},r)}function F(e=32){return randomBytes(e).toString("hex")}function T(e,r={}){let t=r.trustedProxyCount??1,n=r.trustRealIpHeader===true;if(r.customExtractor){let s=r.customExtractor(e);if(s)return s}if(t>0){let s=e.headers.get("x-forwarded-for");if(s){let i=s.split(",").map(w=>w.trim()).filter(Boolean),c=i.length-t;if(c>=0&&c<i.length)return i[c]}}if(n){let s=e.headers.get("x-real-ip");if(s)return s.trim()}return "unknown"}var ne="__csrf",ae="x-csrf-token",ie=/^[A-Za-z_$][A-Za-z0-9_$-]{0,63}$/,ce=/^[A-Za-z][A-Za-z0-9-]{0,63}$/;function b(e={}){let r=e.cookieName||ne,t=e.headerName||ae,n=e.tokenLength||32,s=e.secure!==false;if(!ie.test(r))throw new Error(`createCSRFProtection: invalid cookieName "${r}"`);if(!ce.test(t))throw new Error(`createCSRFProtection: invalid headerName "${t}"`);if(n<16||n>128)throw new Error("createCSRFProtection: tokenLength must be between 16 and 128 bytes");return {generateToken(i){let c=F(n),w=[`${r}=${c}`,"Path=/","SameSite=Lax"];return s&&w.push("Secure"),i.append("Set-Cookie",w.join("; ")),c},validateRequest(i){let c=i.headers.get("cookie");if(!c)return {valid:false,error:"No cookies present"};let w=c.split(";").map(g=>g.trim()).find(g=>g.startsWith(`${r}=`))?.slice(r.length+1);if(!w)return {valid:false,error:"CSRF cookie missing"};let m=i.headers.get(t);if(!m)return {valid:false,error:"CSRF header missing"};try{let g=Buffer.from(w),u=Buffer.from(m);return g.length!==u.length?{valid:!1,error:"CSRF token mismatch"}:timingSafeEqual(g,u)?{valid:!0}:{valid:!1,error:"CSRF token mismatch"}}catch{return {valid:false,error:"CSRF validation failed"}}},cookieName:r,headerName:t}}function v(e){let r=new Map,t=setInterval(()=>{let n=Date.now();for(let[s,i]of r)n>=i.resetAt&&r.delete(s);},6e4);return typeof t=="object"&&"unref"in t&&t.unref(),{async check(n){let s=Date.now(),i=r.get(n);return (!i||s>=i.resetAt)&&(i={count:0,resetAt:s+e.windowMs},r.set(n,i)),i.count++,i.count>e.maxRequests?{allowed:false,remaining:0,retryAfter:Math.ceil((i.resetAt-s)/1e3)}:{allowed:true,remaining:e.maxRequests-i.count}}}}function ue(){let e=new Map,r=setInterval(()=>{let t=Date.now();for(let[n,s]of e)t>=s&&e.delete(n);},6e4);return typeof r=="object"&&"unref"in r&&r.unref(),{async has(t){let n=e.get(t);return n?Date.now()>=n?(e.delete(t),false):true:false},async set(t,n){e.set(t,Date.now()+n*1e3);}}}function pe(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=e.stacknetJwtSecret||e.authSecret;return async function(m){let g=T(m,e.ipConfig),u=await t.check(`auth:${g}`);if(!u.allowed)return Response.json({error:"Too many login attempts. Please wait."},{status:429,headers:{"Retry-After":String(u.retryAfter||60)}});let d;try{d=await m.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{chain:l,message:f,signature:o,publicKey:a,otp:p,code:h,redirectUrl:S,stackId:k}=d,y=k||e.stackId,x;if(l&&f&&o){let ee={"Content-Type":"application/json"},J=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(y)}/auth/web3/verify`,{method:"POST",headers:ee,body:JSON.stringify({chain:l,message:f,signature:o,public_key:a}),signal:AbortSignal.timeout(1e4)});if(!J.ok){let B=await J.json().catch(()=>({})),W=B?.error?.message||B?.message||B?.error||`StackNet returned ${J.status}`;return console.error(`[auth-callback] Verify failed: ${J.status}`,W),Response.json({error:"Wallet verification failed",detail:typeof W=="string"?W:void 0},{status:401})}let U=await J.json();x=U.data?.session||U.session||U.data||U,console.log(`[auth-callback] Verify OK, sessionData keys: ${Object.keys(x||{}).join(", ")}`);}else return p||h?Response.json({error:"Use /api/auth/otp for OTP verification"},{status:400}):Response.json({error:"Provide wallet signature or OTP code"},{status:400});if(!x?.jwt)return Response.json({error:"Authentication failed \u2014 no session returned"},{status:401});let R=C(x.jwt,c);if(!R)return Response.json({error:"Upstream session JWT failed verification"},{status:502});let P=Math.floor(Date.now()/1e3),I={...R,exp:P+s,iat:P},A=j(I,e.authSecret),O={userId:R.sub||R.user_id||R.session_id||R.global_id||"",address:x.address||R.address,chain:x.chain||l,expiresAt:Date.now()+i*1e3,authMethod:l?`web3:${l}`:"otp"},$=new Headers({"Content-Type":"application/json"}),H=e.secureCookies!==false?"; Secure":"",_=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";$.append("Set-Cookie",`stackauth_jwt=${A}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${H}${_}`);let q=Buffer.from(JSON.stringify(O)).toString("base64url");return $.append("Set-Cookie",`stackauth_session=${q}; Path=/; SameSite=Lax; Max-Age=${i}${H}${_}`),n.generateToken($),new Response(JSON.stringify({user:O}),{status:200,headers:$})}}function Q(e,r){if(!r)return null;try{let t=N(e);if(!t||t.exp&&t.exp<Math.floor(Date.now()/1e3))return null;let n=Buffer.from(JSON.stringify({alg:"HS256",typ:"JWT"})).toString("base64url"),s=Buffer.from(JSON.stringify(t)).toString("base64url"),i=createHmac("sha256",r).update(`${n}.${s}`).digest("base64url");return `${n}.${s}.${i}`}catch{return null}}var le=/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;function Y(e){return typeof e!="string"||e.length===0||e.length>8192?null:le.test(e)?e:null}function D(e,r){let t=Q(e,r),n=t&&Y(t);if(n)return {Cookie:`stackauth_jwt=${n}`};let s=Y(e);return s?{Cookie:`stackauth_jwt=${s}`}:{}}function M(e){let r=e.headers.get("cookie");if(r){let n=r.split(";").map(s=>s.trim()).find(s=>s.startsWith("stackauth_jwt="));if(n)return n.slice(14)}let t=e.headers.get("authorization");return t?.startsWith("Bearer ")?t.slice(7):null}function fe(e){return !e.authSecret&&typeof console<"u"&&console.warn("[userutils] createLogoutHandler called without authSecret \u2014 upstream session revocation is disabled. Pass authSecret to enable it safely."),async function(t){let n=M(t);if(n&&e.authSecret){let w=C(n,e.authSecret),m=w?.session_id||w?.sub;if(m&&typeof m=="string")try{await fetch(`${e.stacknetUrl}/api/v2/sessions/${encodeURIComponent(m)}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});}catch{}}let s=e.secureCookies!==false?"; Secure":"",i=e.cookieDomain?`; Domain=${e.cookieDomain}`:"",c=new Headers({"Content-Type":"application/json"});return c.append("Set-Cookie",`stackauth_jwt=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${s}${i}`),c.append("Set-Cookie",`stackauth_session=; Path=/; SameSite=Lax; Max-Age=0${s}${i}`),c.append("Set-Cookie",`__csrf=; Path=/; SameSite=Lax; Max-Age=0${s}${i}`),new Response(JSON.stringify({success:true}),{status:200,headers:c})}}function me(e){let r=e.jwtExpiry||900,t=e.sessionMaxAge||604800;return async function(s){let i=M(s);if(!i)return Response.json({session:null},{status:200});let c=C(i,e.authSecret);if(!c)return Response.json({session:null},{status:200});let m={userId:c.sub||c.user_id||c.session_id||c.global_id||"",address:c.address,chain:c.chain,expiresAt:c.session_expires_at||(c.exp?c.exp*1e3:Date.now()+t*1e3),planId:c.plan_id,authMethod:c.auth_method},g=new Headers({"Content-Type":"application/json"}),u=E(i,e.authSecret,r,300);if(u){let d=e.secureCookies!==false?"; Secure":"",l=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";g.append("Set-Cookie",`stackauth_jwt=${u}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${t}${d}${l}`);}return new Response(JSON.stringify({session:m}),{status:200,headers:g})}}function ye(e,r){if(e.length!==r.length)return false;try{return timingSafeEqual(Buffer.from(e),Buffer.from(r))}catch{return false}}function we(e){let r=e.rateLimiter||v({maxRequests:5,windowMs:3e5}),t=b({secure:e.secureCookies!==false}),n=e.jwtExpiry||900,s=e.sessionMaxAge||604800;return async function(c){let w=T(c,e.ipConfig),m=await r.check(`otp:${w}`);if(!m.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(m.retryAfter||300)}});let g;try{g=await c.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{code:u}=g;if(!u||typeof u!="string"||u.length!==6)return Response.json({error:"Invalid code format"},{status:400});if(!ye(u,e.otpSecret))return Response.json({error:"Invalid code"},{status:401});let d=Math.floor(Date.now()/1e3),f={sub:`preview:otp:${createHash("sha256").update(`otp:${u}:${Date.now()}`).digest("hex").slice(0,32)}`,scope:"preview",auth_method:"otp",credentials:["otp"],iat:d,exp:d+n},o=j(f,e.authSecret),a={userId:f.sub,expiresAt:Date.now()+s*1e3,authMethod:"otp",scope:"preview"},p=new Headers({"Content-Type":"application/json"}),h=e.secureCookies!==false?"; Secure":"",S=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";p.append("Set-Cookie",`stackauth_jwt=${o}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${s}${h}${S}`);let k=Buffer.from(JSON.stringify(a)).toString("base64url");return p.append("Set-Cookie",`stackauth_session=${k}; Path=/; SameSite=Lax; Max-Age=${s}${h}${S}`),t.generateToken(p),new Response(JSON.stringify({success:true,data:{user:a}}),{status:200,headers:p})}}function Se(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=e.stacknetJwtSecret||e.authSecret;async function w(g){let u=new URL(g.url),d=u.searchParams.get("provider"),l=u.searchParams.get("redirectUri")||u.searchParams.get("redirect_uri"),f=u.searchParams.get("stackId")||e.stackId;if(!d)return Response.json({error:"Missing provider parameter"},{status:400});if(!l)return Response.json({error:"Missing redirectUri parameter"},{status:400});if(!/^[a-z][a-z0-9_-]{0,32}$/.test(d))return Response.json({error:"Invalid provider name"},{status:400});if(!f||!/^[a-zA-Z0-9_-]{1,64}$/.test(f))return Response.json({error:"Invalid stackId"},{status:400});try{let o=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(f)}/auth/oauth/${encodeURIComponent(d)}/initiate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({redirectUrl:l}),signal:AbortSignal.timeout(1e4)});if(!o.ok){let h=await o.json().catch(()=>({}));return Response.json({error:h.error?.message||`Failed to start OAuth flow: ${o.statusText}`},{status:o.status})}let a=await o.json(),p=a.data||a;return Response.json({redirect_url:p.url,state:p.state})}catch(o){return Response.json({error:o.message||"Failed to start OAuth flow"},{status:500})}}async function m(g){let u=T(g,e.ipConfig),d=await t.check(`oauth:${u}`);if(!d.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(d.retryAfter||60)}});let l;try{l=await g.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{provider:f,code:o,state:a,stackId:p}=l,h=p||e.stackId;if(!f||!o||!a)return Response.json({error:"Missing provider, code, or state"},{status:400});if(!/^[a-z][a-z0-9_-]{0,32}$/.test(f))return Response.json({error:"Invalid provider name"},{status:400});if(!h||!/^[a-zA-Z0-9_-]{1,64}$/.test(h))return Response.json({error:"Invalid stackId"},{status:400});try{let S=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(h)}/auth/oauth/${encodeURIComponent(f)}/callback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:o,state:a}),signal:AbortSignal.timeout(1e4)});if(!S.ok){let _=await S.json().catch(()=>({}));return Response.json({error:_.error?.message||`OAuth verification failed: ${S.statusText}`},{status:401})}let k=await S.json(),y=k.data?.session||k.session||k.data||k;if(!y?.jwt)return Response.json({error:"OAuth authentication failed \u2014 no session returned"},{status:401});let x=C(y.jwt,c);if(!x)return Response.json({error:"Upstream session JWT failed verification"},{status:502});let R=Math.floor(Date.now()/1e3),P=j({...x,exp:R+s,iat:R},e.authSecret),A={userId:x.sub||x.user_id||x.session_id||x.global_id||"",address:y.address||x.address,chain:void 0,expiresAt:Date.now()+i*1e3,authMethod:`oauth:${f}`},L=new Headers({"Content-Type":"application/json"}),O=e.secureCookies!==!1?"; Secure":"",$=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";L.append("Set-Cookie",`stackauth_jwt=${P}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${O}${$}`);let H=Buffer.from(JSON.stringify(A)).toString("base64url");return L.append("Set-Cookie",`stackauth_session=${H}; Path=/; SameSite=Lax; Max-Age=${i}${O}${$}`),n.generateToken(L),new Response(JSON.stringify({user:A}),{status:200,headers:L})}catch(S){return Response.json({error:S.message||"OAuth callback failed"},{status:500})}}return {startFlow:w,handleCallback:m}}function ke(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=e.stacknetJwtSecret||e.authSecret;return async function(m){let g=T(m,e.ipConfig),u=await t.check(`google-onetap:${g}`);if(!u.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(u.retryAfter||60)}});let d;try{d=await m.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{credential:l,stackId:f}=d,o=f||e.stackId;if(!l)return Response.json({error:"Missing credential"},{status:400});if(l.split(".").length!==3)return Response.json({error:"Invalid credential format"},{status:400});let a;try{let y=await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${encodeURIComponent(l)}`,{signal:AbortSignal.timeout(1e4)});if(!y.ok)return Response.json({error:"Google credential verification failed"},{status:401});a=await y.json();}catch{return Response.json({error:"Failed to verify Google credential"},{status:500})}if(!a.sub||!a.email)return Response.json({error:"Invalid Google token \u2014 missing user info"},{status:401});if(a.iss!=="https://accounts.google.com"&&a.iss!=="accounts.google.com")return Response.json({error:"Invalid Google token issuer"},{status:401});let p=typeof a.exp=="string"?parseInt(a.exp,10):Number(a.exp);if(!Number.isFinite(p)||p<Math.floor(Date.now()/1e3))return Response.json({error:"Google token expired"},{status:401});let h=e.googleClientIds||(e.googleClientId?[e.googleClientId]:[]);if(h.length===0)return Response.json({error:"Google One Tap not configured \u2014 set ServerConfig.googleClientId(s)"},{status:500});if(!a.aud||!h.includes(a.aud))return Response.json({error:"Invalid Google token audience"},{status:401});if(!(a.email_verified===true||a.email_verified==="true"))return Response.json({error:"Google email is not verified"},{status:401});let k={sub:a.sub,email:a.email,name:a.name,picture:a.picture};try{let y=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(o)}/auth/oauth/google/callback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:l,google_id:k.sub,email:k.email,name:k.name,picture:k.picture,one_tap:!0}),signal:AbortSignal.timeout(1e4)});if(!y.ok){let P=await y.json().catch(()=>({}));return Response.json({error:P?.error?.message||"Google sign-in failed"},{status:y.status})}let x=await y.json(),R=x.data?.session||x.session||x.data||x;if(R?.jwt){let P=C(R.jwt,c);if(!P)return Response.json({error:"Upstream session JWT failed verification"},{status:502});let I=Math.floor(Date.now()/1e3),A=j({...P,exp:I+s,iat:I},e.authSecret),O={userId:P.sub||P.user_id||k.sub,address:k.email||R.address,chain:void 0,expiresAt:Date.now()+i*1e3,authMethod:"oauth:google"},$=new Headers({"Content-Type":"application/json"}),H=e.secureCookies!==!1?"; Secure":"",_=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";$.append("Set-Cookie",`stackauth_jwt=${A}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${H}${_}`);let q=Buffer.from(JSON.stringify(O)).toString("base64url");return $.append("Set-Cookie",`stackauth_session=${q}; Path=/; SameSite=Lax; Max-Age=${i}${H}${_}`),n.generateToken($),new Response(JSON.stringify({user:O}),{status:200,headers:$})}return Response.json({error:"No session returned"},{status:401})}catch(y){return Response.json({error:y.message||"Google One Tap authentication failed"},{status:500})}}}function xe(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=r?.sessionMaxAge??3600;return async function(c){let w=T(c,e.ipConfig),m=await t.check(`preview-code:${w}`);if(!m.allowed)return Response.json({error:"Too many preview-code attempts. Please wait."},{status:429,headers:{"Retry-After":String(m.retryAfter||60)}});let g;try{g=await c.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let u=typeof g?.code=="string"?g.code.trim():"";if(!/^\d{6}$/.test(u))return Response.json({error:"Code must be 6 digits"},{status:400});let d;try{let y=await fetch(`${e.stacknetUrl.replace(/\/$/,"")}/v1/preview-codes/${encodeURIComponent(u)}`,{method:"GET",headers:{Accept:"application/json"},signal:AbortSignal.timeout(5e3)});if(y.status===404)return Response.json({error:"Preview code not found"},{status:404});if(!y.ok)return Response.json({error:`Upstream returned ${y.status}`},{status:502});d=await y.json();}catch{return Response.json({error:"Upstream validation failed"},{status:502})}if(!d||d.code!==u)return Response.json({error:"Preview code not found"},{status:404});if(d.revoked===true)return Response.json({error:"Preview code has been revoked"},{status:401});if(typeof d.expiresAt=="number"&&d.expiresAt<=Date.now())return Response.json({error:"Preview code has expired"},{status:401});if(typeof d.tokensRemaining=="number"&&d.tokensRemaining<=0)return Response.json({error:"Preview code is exhausted"},{status:402});let l=Math.floor(Date.now()/1e3),f=`preview_${u}`,o=j({sub:f,global_id:f,auth_method:"preview_code",preview_code:u,exp:l+s,iat:l},e.authSecret),a={userId:f,expiresAt:Date.now()+s*1e3,authMethod:"preview_code"},p=new Headers({"Content-Type":"application/json"}),h=e.secureCookies!==false?"; Secure":"",S=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";p.append("Set-Cookie",`stackauth_jwt=${o}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${s}${h}${S}`);let k=Buffer.from(JSON.stringify(a)).toString("base64url");return p.append("Set-Cookie",`stackauth_session=${k}; Path=/; SameSite=Lax; Max-Age=${s}${h}${S}`),n.generateToken(p),new Response(JSON.stringify({user:a,name:d.name??null}),{status:200,headers:p})}}function Re(e){let r=b({secure:e.secureCookies!==false}),t=e.rateLimiter||v({maxRequests:20,windowMs:6e4}),n=e.stacknetJwtSecret||e.authSecret,s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=null;if(e.canonicalOrigin){let o;try{o=new URL(e.canonicalOrigin);}catch{throw new Error(`createBillingProxy: canonicalOrigin "${e.canonicalOrigin}" is not a valid URL`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`createBillingProxy: canonicalOrigin must be http or https (got "${o.protocol}")`);if(o.pathname!=="/"&&o.pathname!=="")throw new Error(`createBillingProxy: canonicalOrigin must have no path (got "${o.pathname}")`);c=o.origin;}let w=false;function m(o){if(c)return c;w||(w=true,console.warn("[userutils] createBillingProxy: canonicalOrigin not set \u2014 falling back to request origin. Set canonicalOrigin to the public URL of this app to prevent Host-header spoofing of Stripe success URLs."));try{let a=new URL(o.url);return a.protocol!=="http:"&&a.protocol!=="https:"?null:a.origin}catch{return null}}function g(o){let a=M(o);if(!a)return null;let p=C(a,e.authSecret);return p?{jwt:a,payload:p}:null}function u(o,a){let p=E(o,e.authSecret,s,300);if(p){let h=e.secureCookies!==false?"; Secure":"",S=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";a.append("Set-Cookie",`stackauth_jwt=${p}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${h}${S}`);}}async function d(o,a){let p=g(o);if(!p)return Response.json({error:"Unauthorized"},{status:401});let h=D(p.jwt,n),S=await fetch(`${e.stacknetUrl}${a}`,{headers:h,signal:AbortSignal.timeout(15e3)}),k=await S.json().catch(()=>({})),y=new Headers({"Content-Type":"application/json"});return u(p.jwt,y),new Response(JSON.stringify(k),{status:S.status,headers:y})}async function l(o,a,p){let h=g(o);if(!h)return Response.json({error:"Unauthorized"},{status:401});let S=r.validateRequest(o);if(!S.valid)return Response.json({error:S.error||"CSRF validation failed"},{status:403});let k=h.payload.sub||h.payload.user_id||"unknown";if(!(await t.check(`billing:${k}`)).allowed)return Response.json({error:"Too many requests"},{status:429});let x=await o.json().catch(()=>({})),R=D(h.jwt,n);R["Content-Type"]="application/json";let P=await fetch(`${e.stacknetUrl}${a}`,{method:"POST",headers:R,body:JSON.stringify({...x,...p}),signal:AbortSignal.timeout(15e3)}),I=await P.json().catch(()=>({})),A=new Headers({"Content-Type":"application/json"});return u(h.jwt,A),new Response(JSON.stringify(I),{status:P.status,headers:A})}let f=`/api/v2/stacks/${encodeURIComponent(e.stackId)}`;return {plans:{GET:async o=>{let a=await fetch(`${e.stacknetUrl}${f}/plans`,{signal:AbortSignal.timeout(1e4)}),p=await a.json().catch(()=>({}));return Response.json(p,{status:a.status})}},subscription:{GET:(o=>d(o,`${f}/subscription`))},subscribe:{POST:(o=>{let a=m(o);return a?l(o,`${f}/subscribe`,{successUrl:`${a}/billing/success?session_id={CHECKOUT_SESSION_ID}`,cancelUrl:`${a}/pricing`}):Promise.resolve(Response.json({error:"Invalid request origin for checkout"},{status:400}))})},cancel:{POST:(o=>l(o,`${f}/cancel-subscription`))},usage:{GET:(o=>d(o,"/v1/account/usage"))},history:{GET:(o=>d(o,`${f}/billing`))},prepaid:{POST:(o=>{let a=m(o);return a?l(o,`${f}/prepaid`,{successUrl:`${a}/pricing/prepaid/success?session_id={CHECKOUT_SESSION_ID}`,cancelUrl:`${a}/pricing/prepaid`}):Promise.resolve(Response.json({error:"Invalid request origin for checkout"},{status:400}))})},verifyPrepaid:{POST:(o=>l(o,`${f}/verify-prepaid`))},verifySession:{POST:(o=>l(o,`${f}/verify-session`))},subscribeSol:{POST:(o=>l(o,`${f}/subscribe-sol`))},prepaidSol:{POST:(o=>l(o,`${f}/prepaid-sol`))},topup:{POST:(o=>l(o,"/v1/account/topup"))}}}function Pe(e){return async function(t){let n=t.headers.get("stripe-signature");if(!n)return Response.json({error:"Missing Stripe signature"},{status:400});try{let s=await t.text(),i=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(e.stackId)}/webhook/stripe`,{method:"POST",headers:{"Content-Type":"application/json","stripe-signature":n},body:s,signal:AbortSignal.timeout(1e4)}),c=await i.json().catch(()=>({received:!0}));return Response.json(c,{status:i.status})}catch{return Response.json({error:"Webhook processing failed"},{status:502})}}}function G(){return {"Strict-Transport-Security":"max-age=63072000; includeSubDomains; preload","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","X-XSS-Protection":"0","Referrer-Policy":"strict-origin-when-cross-origin","Permissions-Policy":"camera=(), microphone=(), geolocation=()"}}function Ce(e){return async r=>{let t=await e(r),n=G(),s=new Headers(t.headers);for(let[i,c]of Object.entries(n))s.set(i,c);return new Response(t.body,{status:t.status,statusText:t.statusText,headers:s})}}function be(){return Object.entries(G()).map(([e,r])=>({key:e,value:r}))}function z(e){return {"Content-Type":"application/json",...D(e.jwt,e.stacknetJwtSecret)}}async function ve(e,r){let t=await fetch(`${e.stacknetBaseUrl}/v1/preview-codes`,{method:"POST",headers:z(e),body:JSON.stringify({token_budget:r.tokenBudget,code:r.code,expires_at:r.expiresAt,name:r.name})});return t.ok?await t.json():{error:await t.text().catch(()=>"")||`HTTP ${t.status}`,status:t.status}}async function $e(e){let r=await fetch(`${e.stacknetBaseUrl}/v1/preview-codes`,{method:"GET",headers:z(e)});return r.ok?(await r.json()).codes:{error:await r.text().catch(()=>"")||`HTTP ${r.status}`,status:r.status}}async function je(e,r){let t=await fetch(`${e}/v1/preview-codes/${encodeURIComponent(r)}`,{method:"GET"});return t.status===404||!t.ok?null:await t.json()}async function Te(e,r){let t=await fetch(`${e.stacknetBaseUrl}/v1/preview-codes/${encodeURIComponent(r)}`,{method:"DELETE",headers:z(e)});return t.ok?await t.json():{error:await t.text().catch(()=>"")||`HTTP ${t.status}`,status:t.status}}async function Ae(e,r,t){let n=await fetch(`${e}/v1/preview-codes/${encodeURIComponent(r)}/redeem`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tokens:t})});return n.ok?await n.json():{error:await n.text().catch(()=>"")||`HTTP ${n.status}`,status:n.status}}var V=["127924fc17182f69cb463d9977348c482d2e784dfdd16f9e6eecc4db07fb04c3","HxLBLBjKbSrJSFCE7LGs9FxMA6M5bztmpNoLuB43HTFs"];function Oe(e){return e?V.includes(e):false}var Ie=V[0];
|
|
1
|
+
import {createHmac,timingSafeEqual,randomBytes,createHash}from'crypto';function Z(e){return Buffer.from(e).toString("base64url")}function se(e){return Buffer.from(e,"base64url").toString()}function N(e){try{let r=e.split(".");return r.length!==3?null:JSON.parse(se(r[1]))}catch{return null}}function j(e,r){let t=Z(JSON.stringify({alg:"HS256",typ:"JWT"})),n=Z(JSON.stringify(e)),s=createHmac("sha256",r).update(`${t}.${n}`).digest("base64url");return `${t}.${n}.${s}`}function X(e,r){try{let t=e.split(".");if(t.length!==3)return !1;let[n,s,i]=t,c=createHmac("sha256",r).update(`${n}.${s}`).digest("base64url"),w=Buffer.from(i),m=Buffer.from(c);return w.length!==m.length?!1:timingSafeEqual(w,m)}catch{return false}}function C(e,r){if(!X(e,r))return null;let t=N(e);return !t||t.exp&&t.exp<Math.floor(Date.now()/1e3)?null:t}function E(e,r,t=900,n=300){let s=C(e,r);return !s?.exp||s.exp*1e3-Date.now()>n*1e3?null:j({...s,exp:Math.floor(Date.now()/1e3)+t},r)}function F(e=32){return randomBytes(e).toString("hex")}function T(e,r={}){let t=r.trustedProxyCount??1,n=r.trustRealIpHeader===true;if(r.customExtractor){let s=r.customExtractor(e);if(s)return s}if(t>0){let s=e.headers.get("x-forwarded-for");if(s){let i=s.split(",").map(w=>w.trim()).filter(Boolean),c=i.length-t;if(c>=0&&c<i.length)return i[c]}}if(n){let s=e.headers.get("x-real-ip");if(s)return s.trim()}return "unknown"}var ne="__csrf",ae="x-csrf-token",ie=/^[A-Za-z_$][A-Za-z0-9_$-]{0,63}$/,ce=/^[A-Za-z][A-Za-z0-9-]{0,63}$/;function b(e={}){let r=e.cookieName||ne,t=e.headerName||ae,n=e.tokenLength||32,s=e.secure!==false;if(!ie.test(r))throw new Error(`createCSRFProtection: invalid cookieName "${r}"`);if(!ce.test(t))throw new Error(`createCSRFProtection: invalid headerName "${t}"`);if(n<16||n>128)throw new Error("createCSRFProtection: tokenLength must be between 16 and 128 bytes");return {generateToken(i){let c=F(n),w=[`${r}=${c}`,"Path=/","SameSite=Lax"];return s&&w.push("Secure"),i.append("Set-Cookie",w.join("; ")),c},validateRequest(i){let c=i.headers.get("cookie");if(!c)return {valid:false,error:"No cookies present"};let w=c.split(";").map(g=>g.trim()).find(g=>g.startsWith(`${r}=`))?.slice(r.length+1);if(!w)return {valid:false,error:"CSRF cookie missing"};let m=i.headers.get(t);if(!m)return {valid:false,error:"CSRF header missing"};try{let g=Buffer.from(w),u=Buffer.from(m);return g.length!==u.length?{valid:!1,error:"CSRF token mismatch"}:timingSafeEqual(g,u)?{valid:!0}:{valid:!1,error:"CSRF token mismatch"}}catch{return {valid:false,error:"CSRF validation failed"}}},cookieName:r,headerName:t}}function v(e){let r=new Map,t=setInterval(()=>{let n=Date.now();for(let[s,i]of r)n>=i.resetAt&&r.delete(s);},6e4);return typeof t=="object"&&"unref"in t&&t.unref(),{async check(n){let s=Date.now(),i=r.get(n);return (!i||s>=i.resetAt)&&(i={count:0,resetAt:s+e.windowMs},r.set(n,i)),i.count++,i.count>e.maxRequests?{allowed:false,remaining:0,retryAfter:Math.ceil((i.resetAt-s)/1e3)}:{allowed:true,remaining:e.maxRequests-i.count}}}}function ue(){let e=new Map,r=setInterval(()=>{let t=Date.now();for(let[n,s]of e)t>=s&&e.delete(n);},6e4);return typeof r=="object"&&"unref"in r&&r.unref(),{async has(t){let n=e.get(t);return n?Date.now()>=n?(e.delete(t),false):true:false},async set(t,n){e.set(t,Date.now()+n*1e3);}}}function pe(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=e.stacknetJwtSecret||e.authSecret;return async function(m){let g=T(m,e.ipConfig),u=await t.check(`auth:${g}`);if(!u.allowed)return Response.json({error:"Too many login attempts. Please wait."},{status:429,headers:{"Retry-After":String(u.retryAfter||60)}});let d;try{d=await m.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{chain:l,message:f,signature:o,publicKey:a,otp:p,code:h,redirectUrl:S,stackId:k}=d,y=k||e.stackId,x;if(l&&f&&o){let ee={"Content-Type":"application/json"},J=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(y)}/auth/web3/verify`,{method:"POST",headers:ee,body:JSON.stringify({chain:l,message:f,signature:o,public_key:a}),signal:AbortSignal.timeout(1e4)});if(!J.ok){let B=await J.json().catch(()=>({})),W=B?.error?.message||B?.message||B?.error||`StackNet returned ${J.status}`;return console.error(`[auth-callback] Verify failed: ${J.status}`,W),Response.json({error:"Wallet verification failed",detail:typeof W=="string"?W:void 0},{status:401})}let U=await J.json();x=U.data?.session||U.session||U.data||U,console.log(`[auth-callback] Verify OK, sessionData keys: ${Object.keys(x||{}).join(", ")}`);}else return p||h?Response.json({error:"Use /api/auth/otp for OTP verification"},{status:400}):Response.json({error:"Provide wallet signature or OTP code"},{status:400});if(!x?.jwt)return Response.json({error:"Authentication failed \u2014 no session returned"},{status:401});let R=C(x.jwt,c);if(!R)return Response.json({error:"Upstream session JWT failed verification"},{status:502});let P=Math.floor(Date.now()/1e3),I={...R,exp:P+s,iat:P},A=j(I,e.authSecret),O={userId:R.sub||R.user_id||R.session_id||R.global_id||"",address:x.address||R.address,chain:x.chain||l,expiresAt:Date.now()+i*1e3,authMethod:l?`web3:${l}`:"otp"},$=new Headers({"Content-Type":"application/json"}),H=e.secureCookies!==false?"; Secure":"",_=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";$.append("Set-Cookie",`stackauth_jwt=${A}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${H}${_}`);let q=Buffer.from(JSON.stringify(O)).toString("base64url");return $.append("Set-Cookie",`stackauth_session=${q}; Path=/; SameSite=Lax; Max-Age=${i}${H}${_}`),n.generateToken($),new Response(JSON.stringify({user:O}),{status:200,headers:$})}}function Q(e,r){if(!r)return null;try{let t=N(e);if(!t||t.exp&&t.exp<Math.floor(Date.now()/1e3))return null;let n=Buffer.from(JSON.stringify({alg:"HS256",typ:"JWT"})).toString("base64url"),s=Buffer.from(JSON.stringify(t)).toString("base64url"),i=createHmac("sha256",r).update(`${n}.${s}`).digest("base64url");return `${n}.${s}.${i}`}catch{return null}}var le=/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;function Y(e){return typeof e!="string"||e.length===0||e.length>8192?null:le.test(e)?e:null}function D(e,r){let t=Q(e,r),n=t&&Y(t);if(n)return {Cookie:`stackauth_jwt=${n}`};let s=Y(e);return s?{Cookie:`stackauth_jwt=${s}`}:{}}function M(e){let r=e.headers.get("cookie");if(r){let n=r.split(";").map(s=>s.trim()).find(s=>s.startsWith("stackauth_jwt="));if(n)return n.slice(14)}let t=e.headers.get("authorization");return t?.startsWith("Bearer ")?t.slice(7):null}function fe(e){return !e.authSecret&&typeof console<"u"&&console.warn("[userutils] createLogoutHandler called without authSecret \u2014 upstream session revocation is disabled. Pass authSecret to enable it safely."),async function(t){let n=M(t);if(n&&e.authSecret){let w=C(n,e.authSecret),m=w?.session_id||w?.sub;if(m&&typeof m=="string")try{await fetch(`${e.stacknetUrl}/api/v2/sessions/${encodeURIComponent(m)}`,{method:"DELETE",signal:AbortSignal.timeout(5e3)});}catch{}}let s=e.secureCookies!==false?"; Secure":"",i=e.cookieDomain?`; Domain=${e.cookieDomain}`:"",c=new Headers({"Content-Type":"application/json"});return c.append("Set-Cookie",`stackauth_jwt=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${s}${i}`),c.append("Set-Cookie",`stackauth_session=; Path=/; SameSite=Lax; Max-Age=0${s}${i}`),c.append("Set-Cookie",`__csrf=; Path=/; SameSite=Lax; Max-Age=0${s}${i}`),new Response(JSON.stringify({success:true}),{status:200,headers:c})}}function me(e){let r=e.jwtExpiry||900,t=e.sessionMaxAge||604800;return async function(s){let i=M(s);if(!i)return Response.json({session:null},{status:200});let c=C(i,e.authSecret);if(!c)return Response.json({session:null},{status:200});let m={userId:c.sub||c.user_id||c.session_id||c.global_id||"",address:c.address,chain:c.chain,expiresAt:c.session_expires_at||(c.exp?c.exp*1e3:Date.now()+t*1e3),planId:c.plan_id,authMethod:c.auth_method},g=new Headers({"Content-Type":"application/json"}),u=E(i,e.authSecret,r,300);if(u){let d=e.secureCookies!==false?"; Secure":"",l=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";g.append("Set-Cookie",`stackauth_jwt=${u}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${t}${d}${l}`);}return new Response(JSON.stringify({session:m}),{status:200,headers:g})}}function ye(e,r){if(e.length!==r.length)return false;try{return timingSafeEqual(Buffer.from(e),Buffer.from(r))}catch{return false}}function we(e){let r=e.rateLimiter||v({maxRequests:3,windowMs:3e5}),t=b({secure:e.secureCookies!==false}),n=e.jwtExpiry||900,s=e.sessionMaxAge||604800;return async function(c){let w=T(c,e.ipConfig),m=await r.check(`otp:${w}`);if(!m.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(m.retryAfter||300)}});let g;try{g=await c.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{code:u}=g;if(!u||typeof u!="string"||u.length!==6)return Response.json({error:"Invalid code format"},{status:400});if(!ye(u,e.otpSecret))return Response.json({error:"Invalid code"},{status:401});let d=Math.floor(Date.now()/1e3),f={sub:`preview:otp:${createHash("sha256").update(`otp:${u}:${Date.now()}`).digest("hex").slice(0,32)}`,scope:"preview",auth_method:"otp",credentials:["otp"],iat:d,exp:d+n},o=j(f,e.authSecret),a={userId:f.sub,expiresAt:Date.now()+s*1e3,authMethod:"otp",scope:"preview"},p=new Headers({"Content-Type":"application/json"}),h=e.secureCookies!==false?"; Secure":"",S=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";p.append("Set-Cookie",`stackauth_jwt=${o}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${s}${h}${S}`);let k=Buffer.from(JSON.stringify(a)).toString("base64url");return p.append("Set-Cookie",`stackauth_session=${k}; Path=/; SameSite=Lax; Max-Age=${s}${h}${S}`),t.generateToken(p),new Response(JSON.stringify({success:true,data:{user:a}}),{status:200,headers:p})}}function Se(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=e.stacknetJwtSecret||e.authSecret;async function w(g){let u=new URL(g.url),d=u.searchParams.get("provider"),l=u.searchParams.get("redirectUri")||u.searchParams.get("redirect_uri"),f=u.searchParams.get("stackId")||e.stackId;if(!d)return Response.json({error:"Missing provider parameter"},{status:400});if(!l)return Response.json({error:"Missing redirectUri parameter"},{status:400});if(!/^[a-z][a-z0-9_-]{0,32}$/.test(d))return Response.json({error:"Invalid provider name"},{status:400});if(!f||!/^[a-zA-Z0-9_-]{1,64}$/.test(f))return Response.json({error:"Invalid stackId"},{status:400});try{let o=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(f)}/auth/oauth/${encodeURIComponent(d)}/initiate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({redirectUrl:l}),signal:AbortSignal.timeout(1e4)});if(!o.ok){let h=await o.json().catch(()=>({}));return Response.json({error:h.error?.message||`Failed to start OAuth flow: ${o.statusText}`},{status:o.status})}let a=await o.json(),p=a.data||a;return Response.json({redirect_url:p.url,state:p.state})}catch(o){return Response.json({error:o.message||"Failed to start OAuth flow"},{status:500})}}async function m(g){let u=T(g,e.ipConfig),d=await t.check(`oauth:${u}`);if(!d.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(d.retryAfter||60)}});let l;try{l=await g.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{provider:f,code:o,state:a,stackId:p}=l,h=p||e.stackId;if(!f||!o||!a)return Response.json({error:"Missing provider, code, or state"},{status:400});if(!/^[a-z][a-z0-9_-]{0,32}$/.test(f))return Response.json({error:"Invalid provider name"},{status:400});if(!h||!/^[a-zA-Z0-9_-]{1,64}$/.test(h))return Response.json({error:"Invalid stackId"},{status:400});try{let S=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(h)}/auth/oauth/${encodeURIComponent(f)}/callback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:o,state:a}),signal:AbortSignal.timeout(1e4)});if(!S.ok){let _=await S.json().catch(()=>({}));return Response.json({error:_.error?.message||`OAuth verification failed: ${S.statusText}`},{status:401})}let k=await S.json(),y=k.data?.session||k.session||k.data||k;if(!y?.jwt)return Response.json({error:"OAuth authentication failed \u2014 no session returned"},{status:401});let x=C(y.jwt,c);if(!x)return Response.json({error:"Upstream session JWT failed verification"},{status:502});let R=Math.floor(Date.now()/1e3),P=j({...x,exp:R+s,iat:R},e.authSecret),A={userId:x.sub||x.user_id||x.session_id||x.global_id||"",address:y.address||x.address,chain:void 0,expiresAt:Date.now()+i*1e3,authMethod:`oauth:${f}`},L=new Headers({"Content-Type":"application/json"}),O=e.secureCookies!==!1?"; Secure":"",$=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";L.append("Set-Cookie",`stackauth_jwt=${P}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${O}${$}`);let H=Buffer.from(JSON.stringify(A)).toString("base64url");return L.append("Set-Cookie",`stackauth_session=${H}; Path=/; SameSite=Lax; Max-Age=${i}${O}${$}`),n.generateToken(L),new Response(JSON.stringify({user:A}),{status:200,headers:L})}catch(S){return Response.json({error:S.message||"OAuth callback failed"},{status:500})}}return {startFlow:w,handleCallback:m}}function ke(e,r){let t=r?.rateLimiter||v({maxRequests:10,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=e.stacknetJwtSecret||e.authSecret;return async function(m){let g=T(m,e.ipConfig),u=await t.check(`google-onetap:${g}`);if(!u.allowed)return Response.json({error:"Too many attempts. Please wait."},{status:429,headers:{"Retry-After":String(u.retryAfter||60)}});let d;try{d=await m.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let{credential:l,stackId:f}=d,o=f||e.stackId;if(!l)return Response.json({error:"Missing credential"},{status:400});if(l.split(".").length!==3)return Response.json({error:"Invalid credential format"},{status:400});let a;try{let y=await fetch(`https://oauth2.googleapis.com/tokeninfo?id_token=${encodeURIComponent(l)}`,{signal:AbortSignal.timeout(1e4)});if(!y.ok)return Response.json({error:"Google credential verification failed"},{status:401});a=await y.json();}catch{return Response.json({error:"Failed to verify Google credential"},{status:500})}if(!a.sub||!a.email)return Response.json({error:"Invalid Google token \u2014 missing user info"},{status:401});if(a.iss!=="https://accounts.google.com"&&a.iss!=="accounts.google.com")return Response.json({error:"Invalid Google token issuer"},{status:401});let p=typeof a.exp=="string"?parseInt(a.exp,10):Number(a.exp);if(!Number.isFinite(p)||p<Math.floor(Date.now()/1e3))return Response.json({error:"Google token expired"},{status:401});let h=e.googleClientIds||(e.googleClientId?[e.googleClientId]:[]);if(h.length===0)return Response.json({error:"Google One Tap not configured \u2014 set ServerConfig.googleClientId(s)"},{status:500});if(!a.aud||!h.includes(a.aud))return Response.json({error:"Invalid Google token audience"},{status:401});if(!(a.email_verified===true||a.email_verified==="true"))return Response.json({error:"Google email is not verified"},{status:401});let k={sub:a.sub,email:a.email,name:a.name,picture:a.picture};try{let y=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(o)}/auth/oauth/google/callback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:l,google_id:k.sub,email:k.email,name:k.name,picture:k.picture,one_tap:!0}),signal:AbortSignal.timeout(1e4)});if(!y.ok){let P=await y.json().catch(()=>({}));return Response.json({error:P?.error?.message||"Google sign-in failed"},{status:y.status})}let x=await y.json(),R=x.data?.session||x.session||x.data||x;if(R?.jwt){let P=C(R.jwt,c);if(!P)return Response.json({error:"Upstream session JWT failed verification"},{status:502});let I=Math.floor(Date.now()/1e3),A=j({...P,exp:I+s,iat:I},e.authSecret),O={userId:P.sub||P.user_id||k.sub,address:k.email||R.address,chain:void 0,expiresAt:Date.now()+i*1e3,authMethod:"oauth:google"},$=new Headers({"Content-Type":"application/json"}),H=e.secureCookies!==!1?"; Secure":"",_=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";$.append("Set-Cookie",`stackauth_jwt=${A}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${H}${_}`);let q=Buffer.from(JSON.stringify(O)).toString("base64url");return $.append("Set-Cookie",`stackauth_session=${q}; Path=/; SameSite=Lax; Max-Age=${i}${H}${_}`),n.generateToken($),new Response(JSON.stringify({user:O}),{status:200,headers:$})}return Response.json({error:"No session returned"},{status:401})}catch(y){return Response.json({error:y.message||"Google One Tap authentication failed"},{status:500})}}}function xe(e,r){let t=r?.rateLimiter||v({maxRequests:3,windowMs:6e4}),n=b({secure:e.secureCookies!==false}),s=r?.sessionMaxAge??3600;return async function(c){let w=T(c,e.ipConfig),m=await t.check(`preview-code:${w}`);if(!m.allowed)return Response.json({error:"Too many preview-code attempts. Please wait."},{status:429,headers:{"Retry-After":String(m.retryAfter||60)}});let g;try{g=await c.json();}catch{return Response.json({error:"Invalid request body"},{status:400})}let u=typeof g?.code=="string"?g.code.trim():"";if(!/^\d{6}$/.test(u))return Response.json({error:"Code must be 6 digits"},{status:400});let d;try{let y=await fetch(`${e.stacknetUrl.replace(/\/$/,"")}/v1/preview-codes/${encodeURIComponent(u)}`,{method:"GET",headers:{Accept:"application/json"},signal:AbortSignal.timeout(5e3)});if(y.status===404)return Response.json({error:"Preview code not found"},{status:404});if(!y.ok)return Response.json({error:`Upstream returned ${y.status}`},{status:502});d=await y.json();}catch{return Response.json({error:"Upstream validation failed"},{status:502})}if(!d||d.code!==u)return Response.json({error:"Preview code not found"},{status:404});if(d.revoked===true)return Response.json({error:"Preview code has been revoked"},{status:401});if(typeof d.expiresAt=="number"&&d.expiresAt<=Date.now())return Response.json({error:"Preview code has expired"},{status:401});if(typeof d.tokensRemaining=="number"&&d.tokensRemaining<=0)return Response.json({error:"Preview code is exhausted"},{status:402});let l=Math.floor(Date.now()/1e3),f=`preview_${u}`,o=j({sub:f,global_id:f,auth_method:"preview_code",preview_code:u,exp:l+s,iat:l},e.authSecret),a={userId:f,expiresAt:Date.now()+s*1e3,authMethod:"preview_code"},p=new Headers({"Content-Type":"application/json"}),h=e.secureCookies!==false?"; Secure":"",S=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";p.append("Set-Cookie",`stackauth_jwt=${o}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${s}${h}${S}`);let k=Buffer.from(JSON.stringify(a)).toString("base64url");return p.append("Set-Cookie",`stackauth_session=${k}; Path=/; SameSite=Lax; Max-Age=${s}${h}${S}`),n.generateToken(p),new Response(JSON.stringify({user:a,name:d.name??null}),{status:200,headers:p})}}function Re(e){let r=b({secure:e.secureCookies!==false}),t=e.rateLimiter||v({maxRequests:20,windowMs:6e4}),n=e.stacknetJwtSecret||e.authSecret,s=e.jwtExpiry||900,i=e.sessionMaxAge||604800,c=null;if(e.canonicalOrigin){let o;try{o=new URL(e.canonicalOrigin);}catch{throw new Error(`createBillingProxy: canonicalOrigin "${e.canonicalOrigin}" is not a valid URL`)}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new Error(`createBillingProxy: canonicalOrigin must be http or https (got "${o.protocol}")`);if(o.pathname!=="/"&&o.pathname!=="")throw new Error(`createBillingProxy: canonicalOrigin must have no path (got "${o.pathname}")`);c=o.origin;}let w=false;function m(o){if(c)return c;w||(w=true,console.warn("[userutils] createBillingProxy: canonicalOrigin not set \u2014 falling back to request origin. Set canonicalOrigin to the public URL of this app to prevent Host-header spoofing of Stripe success URLs."));try{let a=new URL(o.url);return a.protocol!=="http:"&&a.protocol!=="https:"?null:a.origin}catch{return null}}function g(o){let a=M(o);if(!a)return null;let p=C(a,e.authSecret);return p?{jwt:a,payload:p}:null}function u(o,a){let p=E(o,e.authSecret,s,300);if(p){let h=e.secureCookies!==false?"; Secure":"",S=e.cookieDomain?`; Domain=${e.cookieDomain}`:"";a.append("Set-Cookie",`stackauth_jwt=${p}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${i}${h}${S}`);}}async function d(o,a){let p=g(o);if(!p)return Response.json({error:"Unauthorized"},{status:401});let h=D(p.jwt,n),S=await fetch(`${e.stacknetUrl}${a}`,{headers:h,signal:AbortSignal.timeout(15e3)}),k=await S.json().catch(()=>({})),y=new Headers({"Content-Type":"application/json"});return u(p.jwt,y),new Response(JSON.stringify(k),{status:S.status,headers:y})}async function l(o,a,p){let h=g(o);if(!h)return Response.json({error:"Unauthorized"},{status:401});let S=r.validateRequest(o);if(!S.valid)return Response.json({error:S.error||"CSRF validation failed"},{status:403});let k=h.payload.sub||h.payload.user_id||"unknown";if(!(await t.check(`billing:${k}`)).allowed)return Response.json({error:"Too many requests"},{status:429});let x=await o.json().catch(()=>({})),R=D(h.jwt,n);R["Content-Type"]="application/json";let P=await fetch(`${e.stacknetUrl}${a}`,{method:"POST",headers:R,body:JSON.stringify({...x,...p}),signal:AbortSignal.timeout(15e3)}),I=await P.json().catch(()=>({})),A=new Headers({"Content-Type":"application/json"});return u(h.jwt,A),new Response(JSON.stringify(I),{status:P.status,headers:A})}let f=`/api/v2/stacks/${encodeURIComponent(e.stackId)}`;return {plans:{GET:async o=>{let a=await fetch(`${e.stacknetUrl}${f}/plans`,{signal:AbortSignal.timeout(1e4)}),p=await a.json().catch(()=>({}));return Response.json(p,{status:a.status})}},subscription:{GET:(o=>d(o,`${f}/subscription`))},subscribe:{POST:(o=>{let a=m(o);return a?l(o,`${f}/subscribe`,{successUrl:`${a}/billing/success?session_id={CHECKOUT_SESSION_ID}`,cancelUrl:`${a}/pricing`}):Promise.resolve(Response.json({error:"Invalid request origin for checkout"},{status:400}))})},cancel:{POST:(o=>l(o,`${f}/cancel-subscription`))},usage:{GET:(o=>d(o,"/v1/account/usage"))},history:{GET:(o=>d(o,`${f}/billing`))},prepaid:{POST:(o=>{let a=m(o);return a?l(o,`${f}/prepaid`,{successUrl:`${a}/pricing/prepaid/success?session_id={CHECKOUT_SESSION_ID}`,cancelUrl:`${a}/pricing/prepaid`}):Promise.resolve(Response.json({error:"Invalid request origin for checkout"},{status:400}))})},verifyPrepaid:{POST:(o=>l(o,`${f}/verify-prepaid`))},verifySession:{POST:(o=>l(o,`${f}/verify-session`))},subscribeSol:{POST:(o=>l(o,`${f}/subscribe-sol`))},prepaidSol:{POST:(o=>l(o,`${f}/prepaid-sol`))},topup:{POST:(o=>l(o,"/v1/account/topup"))}}}function Pe(e){return async function(t){let n=t.headers.get("stripe-signature");if(!n)return Response.json({error:"Missing Stripe signature"},{status:400});try{let s=await t.text(),i=await fetch(`${e.stacknetUrl}/api/v2/stacks/${encodeURIComponent(e.stackId)}/webhook/stripe`,{method:"POST",headers:{"Content-Type":"application/json","stripe-signature":n},body:s,signal:AbortSignal.timeout(1e4)}),c=await i.json().catch(()=>({received:!0}));return Response.json(c,{status:i.status})}catch{return Response.json({error:"Webhook processing failed"},{status:502})}}}function G(){return {"Strict-Transport-Security":"max-age=63072000; includeSubDomains; preload","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","X-XSS-Protection":"0","Referrer-Policy":"strict-origin-when-cross-origin","Permissions-Policy":"camera=(), microphone=(), geolocation=()"}}function Ce(e){return async r=>{let t=await e(r),n=G(),s=new Headers(t.headers);for(let[i,c]of Object.entries(n))s.set(i,c);return new Response(t.body,{status:t.status,statusText:t.statusText,headers:s})}}function be(){return Object.entries(G()).map(([e,r])=>({key:e,value:r}))}function z(e){return {"Content-Type":"application/json",...D(e.jwt,e.stacknetJwtSecret)}}async function ve(e,r){let t=await fetch(`${e.stacknetBaseUrl}/v1/preview-codes`,{method:"POST",headers:z(e),body:JSON.stringify({token_budget:r.tokenBudget,code:r.code,expires_at:r.expiresAt,name:r.name})});return t.ok?await t.json():{error:await t.text().catch(()=>"")||`HTTP ${t.status}`,status:t.status}}async function $e(e){let r=await fetch(`${e.stacknetBaseUrl}/v1/preview-codes`,{method:"GET",headers:z(e)});return r.ok?(await r.json()).codes:{error:await r.text().catch(()=>"")||`HTTP ${r.status}`,status:r.status}}async function je(e,r){let t=await fetch(`${e}/v1/preview-codes/${encodeURIComponent(r)}`,{method:"GET"});return t.status===404||!t.ok?null:await t.json()}async function Te(e,r){let t=await fetch(`${e.stacknetBaseUrl}/v1/preview-codes/${encodeURIComponent(r)}`,{method:"DELETE",headers:z(e)});return t.ok?await t.json():{error:await t.text().catch(()=>"")||`HTTP ${t.status}`,status:t.status}}async function Ae(e,r,t){let n=await fetch(`${e}/v1/preview-codes/${encodeURIComponent(r)}/redeem`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tokens:t})});return n.ok?await n.json():{error:await n.text().catch(()=>"")||`HTTP ${n.status}`,status:n.status}}var V=["127924fc17182f69cb463d9977348c482d2e784dfdd16f9e6eecc4db07fb04c3","HxLBLBjKbSrJSFCE7LGs9FxMA6M5bztmpNoLuB43HTFs"];function Oe(e){return e?V.includes(e):false}var Ie=V[0];
|
|
2
2
|
export{Ie as PREVIEW_CODE_ADMIN_GLOBAL_ID,V as PREVIEW_CODE_ADMIN_GLOBAL_IDS,D as buildStackNetHeaders,pe as createAuthCallback,Re as createBillingProxy,b as createCSRFProtection,ke as createGoogleOneTapHandler,v as createInMemoryRateLimiter,ue as createInMemoryReplayStore,fe as createLogoutHandler,Se as createOAuthHandlers,we as createOTPHandler,xe as createPreviewCodeHandler,me as createSessionHandler,Pe as createWebhookHandler,N as decodeJWTPayload,T as extractIP,M as extractJwt,F as generateToken,je as getPreviewCode,Oe as isPreviewCodeAdmin,$e as listPreviewCodes,E as maybeRefreshJWT,ve as mintPreviewCode,be as nextSecurityHeaders,Ae as redeemPreviewCode,Q as resignForStackNet,Te as revokePreviewCode,G as securityHeaders,j as signJWT,C as verifyJWT,X as verifyJWTSignature,Ce as withSecurityHeaders};
|