@auth-craft/aws-cf-stack 1.6.4 → 1.7.1
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/.env.example +15 -2
- package/assets/admin/cli.mjs +1 -1
- package/assets/gateway/worker.js +1 -1
- package/assets/lambda/index.mjs +15 -15
- package/cdk/lib/lambda-stack.ts +11 -0
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -34,8 +34,11 @@ LAMBDA_JWT_PRIVATE_KEY= # required — Ed25519 private key (hex or app's f
|
|
|
34
34
|
LAMBDA_JWT_PUBLIC_KEY= # required — Ed25519 public key (same pair); fed to workers too
|
|
35
35
|
LAMBDA_JWT_ALGORITHM=EdDSA # optional, default EdDSA
|
|
36
36
|
LAMBDA_JWT_ISSUER= # optional — overrides AUTH_SYSTEM_NAME as iss (must match worker verify)
|
|
37
|
-
LAMBDA_JWT_ACCESS_TOKEN_AUDIENCE=access # optional, default access
|
|
38
|
-
LAMBDA_JWT_REFRESH_TOKEN_AUDIENCE=refresh # optional, default refresh
|
|
37
|
+
LAMBDA_JWT_ACCESS_TOKEN_AUDIENCE=access # optional, default access — legacy single-aud fallback
|
|
38
|
+
LAMBDA_JWT_REFRESH_TOKEN_AUDIENCE=refresh # optional, default refresh — legacy single-aud fallback
|
|
39
|
+
# NOTE: tokens now carry a CONTEXT-DEPENDENT aud (customer/tenant/system); see
|
|
40
|
+
# LAMBDA_TOKEN_AUD_* below. Verification accepts all three, so the two values
|
|
41
|
+
# above are only the fallback when the per-scope audiences are unset.
|
|
39
42
|
|
|
40
43
|
# ─── Gateway secret (Worker → Lambda; the backend's only direct-access guard) ─
|
|
41
44
|
LAMBDA_GATEWAY_SECRET= # required for staging/prod (flag --gateway-secret)
|
|
@@ -132,6 +135,16 @@ LAMBDA_LOCKOUT_DURATION_MINUTES=30
|
|
|
132
135
|
LAMBDA_REQUIRE_VERIFIED_IDENTITY=false
|
|
133
136
|
LAMBDA_VERIFY_SESSION_ON_AUTH=false
|
|
134
137
|
LAMBDA_SYSTEM_TENANT_ID=__system__
|
|
138
|
+
# Context-dependent JWT audience. Values MUST equal the gateway AUTH_SCOPE names
|
|
139
|
+
# (customer/tenant/system) so the gateway can enforce scope-binding (token aud must
|
|
140
|
+
# match the route's scope). Change these only if you also change AUTH_SCOPE.
|
|
141
|
+
LAMBDA_TOKEN_AUD_CUSTOMER=customer
|
|
142
|
+
LAMBDA_TOKEN_AUD_TENANT=tenant
|
|
143
|
+
LAMBDA_TOKEN_AUD_SYSTEM=system
|
|
144
|
+
# Per-scope refresh-token cookie names (browser isolation across scopes).
|
|
145
|
+
LAMBDA_REFRESH_COOKIE_CUSTOMER=refresh_token
|
|
146
|
+
LAMBDA_REFRESH_COOKIE_TENANT=refresh_token_tenant
|
|
147
|
+
LAMBDA_REFRESH_COOKIE_SYSTEM=refresh_token_system
|
|
135
148
|
LAMBDA_AUDIT_ENABLED=false
|
|
136
149
|
LAMBDA_DEVICE_TRACKING=true
|
|
137
150
|
LAMBDA_APP_URL=
|
package/assets/admin/cli.mjs
CHANGED
|
@@ -189,7 +189,7 @@ Expecting one of '${n.join("', '")}'`);let i=`${e}Help`;return this.on(i,s=>{let
|
|
|
189
189
|
`)+`
|
|
190
190
|
-----END PRIVATE KEY-----`;return fm(s,"EdDSA")}async function rye(t){let e=t.replace(/^0x/,""),r=j5(e),n=iye(r);return In({kty:"OKP",crv:"Ed25519",x:n},"EdDSA")}async function Kv(t,e){if(e==="EdDSA"){let r=t.trim();if(r.startsWith("{"))return In(JSON.parse(r),e);if(!r.includes(`
|
|
191
191
|
`)&&!r.startsWith("-----")){try{let n=atob(r);if(n.trim().startsWith("{"))return In(JSON.parse(n),e)}catch{}if(/^[0-9a-fA-F]{64}$/.test(r))return tye(r)}if(r.startsWith("-----"))return fm(r,e)}throw new Error(`Unsupported key format or algorithm: ${e}`)}async function W5(t,e){if(e==="EdDSA"){let r=t.trim();if(r.startsWith("{"))return In(JSON.parse(r),e);if(!r.includes(`
|
|
192
|
-
`)&&!r.startsWith("-----")){try{let n=atob(r);if(n.trim().startsWith("{"))return In(JSON.parse(n),e)}catch{}if(/^[0-9a-fA-F]{64}$/.test(r))return rye(r)}if(r.startsWith("-----")){let{importSPKI:n}=await Promise.resolve().then(()=>(Bv(),V5));return n(r,e)}}throw new Error(`Unsupported key format or algorithm: ${e}`)}async function nye(t){return new TextEncoder().encode(t)}function j5(t){let e=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)e[r/2]=parseInt(t.slice(r,r+2),16);return e}function iye(t){return btoa(String.fromCharCode(...t)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}var TA=class{name="jwt-token-plugin";version="1.0.0";config;keys=null;keysPromise=null;constructor(e){this.config={...e}}async ensureKeys(){return this.keys?this.keys:this.keysPromise?this.keysPromise:(this.keysPromise=this.resolveKeys(),this.keys=await this.keysPromise,this.keys)}async resolveKeys(){let e=this.config.algorithm;if(e==="HS256"||e==="HS384"||e==="HS512"){if(!this.config.secret)throw new Error("Secret is required for HMAC algorithms");let a=await nye(this.config.secret);return{privateKey:a,publicKey:a,servicePrivateKey:a,servicePublicKey:a}}if(!this.config.privateKey)throw new Error("Private key is required for EdDSA algorithm");if(!this.config.publicKey)throw new Error("Public key is required for EdDSA algorithm (cannot be derived via Web Crypto)");let n=await Kv(this.config.privateKey,e),i=await W5(this.config.publicKey,e),s,o;return this.config.servicePrivateKey&&this.config.servicePublicKey?(s=await Kv(this.config.servicePrivateKey,e),o=await W5(this.config.servicePublicKey,e)):this.config.servicePrivateKey?(s=await Kv(this.config.servicePrivateKey,e),o=i):(s=n,o=i),{privateKey:n,publicKey:i,servicePrivateKey:s,servicePublicKey:o}}buildBasePayload(e,r,n){let i=Math.floor(Date.now()/1e3),s=i+r*60,o=e.aud??(n?this.config.refreshTokenAudience??this.config.audience:this.config.accessTokenAudience??this.config.audience),a={sub:e.sub,aud:o??"",iss:this.config.issuer??"",jti:e.jti,sid:e.sid,typ:n?H5:Fv,iat:i,exp:s};return e.tid!==void 0&&(a.tid=e.tid),e.tty!==void 0&&(a.tty=e.tty),a}async signJwt(e,r){let n=this.config.algorithm;return new Ul(e).setProtectedHeader({alg:n,typ:"JWT"}).sign(r)}async verifyJwt(e,r,n){try{let i=this.config.algorithm,s=this.config.clockTolerance??0,o={algorithms:[i],clockTolerance:s};this.config.issuer&&(o.issuer=this.config.issuer),n&&(o.audience=n);let{payload:a}=await gA(e,r,o);return a}catch{return null}}async sign(e,r){let n=await this.ensureKeys(),i=r??this.config.accessTokenExpiresIn,s=this.buildBasePayload(e,i,!1);return this.signJwt(s,n.privateKey)}async verify(e){let r=await this.ensureKeys(),n=
|
|
192
|
+
`)&&!r.startsWith("-----")){try{let n=atob(r);if(n.trim().startsWith("{"))return In(JSON.parse(n),e)}catch{}if(/^[0-9a-fA-F]{64}$/.test(r))return rye(r)}if(r.startsWith("-----")){let{importSPKI:n}=await Promise.resolve().then(()=>(Bv(),V5));return n(r,e)}}throw new Error(`Unsupported key format or algorithm: ${e}`)}async function nye(t){return new TextEncoder().encode(t)}function j5(t){let e=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)e[r/2]=parseInt(t.slice(r,r+2),16);return e}function iye(t){return btoa(String.fromCharCode(...t)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}var TA=class{name="jwt-token-plugin";version="1.0.0";config;keys=null;keysPromise=null;constructor(e){this.config={...e}}async ensureKeys(){return this.keys?this.keys:this.keysPromise?this.keysPromise:(this.keysPromise=this.resolveKeys(),this.keys=await this.keysPromise,this.keys)}async resolveKeys(){let e=this.config.algorithm;if(e==="HS256"||e==="HS384"||e==="HS512"){if(!this.config.secret)throw new Error("Secret is required for HMAC algorithms");let a=await nye(this.config.secret);return{privateKey:a,publicKey:a,servicePrivateKey:a,servicePublicKey:a}}if(!this.config.privateKey)throw new Error("Private key is required for EdDSA algorithm");if(!this.config.publicKey)throw new Error("Public key is required for EdDSA algorithm (cannot be derived via Web Crypto)");let n=await Kv(this.config.privateKey,e),i=await W5(this.config.publicKey,e),s,o;return this.config.servicePrivateKey&&this.config.servicePublicKey?(s=await Kv(this.config.servicePrivateKey,e),o=await W5(this.config.servicePublicKey,e)):this.config.servicePrivateKey?(s=await Kv(this.config.servicePrivateKey,e),o=i):(s=n,o=i),{privateKey:n,publicKey:i,servicePrivateKey:s,servicePublicKey:o}}buildBasePayload(e,r,n){let i=Math.floor(Date.now()/1e3),s=i+r*60,o=e.aud??(n?this.config.refreshTokenAudience??this.config.audience:this.config.accessTokenAudience??this.config.audience),a={sub:e.sub,aud:o??"",iss:this.config.issuer??"",jti:e.jti,sid:e.sid,typ:n?H5:Fv,iat:i,exp:s};return e.tid!==void 0&&(a.tid=e.tid),e.tty!==void 0&&(a.tty=e.tty),a}async signJwt(e,r){let n=this.config.algorithm;return new Ul(e).setProtectedHeader({alg:n,typ:"JWT"}).sign(r)}async verifyJwt(e,r,n){try{let i=this.config.algorithm,s=this.config.clockTolerance??0,o={algorithms:[i],clockTolerance:s};this.config.issuer&&(o.issuer=this.config.issuer),n&&(typeof n=="string"||n.length>0)&&(o.audience=n);let{payload:a}=await gA(e,r,o);return a}catch{return null}}async sign(e,r){let n=await this.ensureKeys(),i=r??this.config.accessTokenExpiresIn,s=this.buildBasePayload(e,i,!1);return this.signJwt(s,n.privateKey)}accessVerifyAudience(){return this.config.accessTokenAudiences&&this.config.accessTokenAudiences.length>0?this.config.accessTokenAudiences:this.config.accessTokenAudience??this.config.audience}refreshVerifyAudience(){return this.config.refreshTokenAudiences&&this.config.refreshTokenAudiences.length>0?this.config.refreshTokenAudiences:this.config.refreshTokenAudience??this.config.audience}async verify(e){let r=await this.ensureKeys(),n=await this.verifyJwt(e,r.publicKey,this.accessVerifyAudience());return!n||n.typ!==Fv?null:n}async signAccessToken(e,r){let n=await this.ensureKeys(),i=r??this.config.accessTokenExpiresIn,s=this.buildBasePayload(e,i,!1),o={...s};return e.roles!==void 0&&(o.roles=e.roles),e.perms!==void 0&&(o.perms=e.perms),{token:await this.signJwt(o,n.privateKey),expiresAt:s.exp}}async verifyAccessToken(e){let r=await this.ensureKeys(),n=await this.verifyJwt(e,r.publicKey,this.accessVerifyAudience());return!n||n.typ!==Fv||!("jti"in n)?null:n}async signRefreshToken(e,r){let n=await this.ensureKeys(),i=r??this.config.refreshTokenExpiresIn,s=this.buildBasePayload(e,i,!0);return{token:await this.signJwt(s,n.privateKey),expiresAt:s.exp}}async verifyRefreshToken(e){let r=await this.ensureKeys(),n=await this.verifyJwt(e,r.publicKey,this.refreshVerifyAudience());return!n||n.typ!==H5||!("sid"in n)||!("jti"in n)?null:n}async signServiceToken(e){if(this.config.algorithm!=="EdDSA")throw new Error("signServiceToken requires EdDSA algorithm");let r=await this.ensureKeys(),n=Math.floor(Date.now()/1e3),i=this.config.serviceTokenExpiresIn??10,s=e.aud??this.config.serviceTokenAudience??q5,o={sub:e.sub,aud:s,iss:this.config.issuer??"",jti:e.jti,typ:G5,iat:n,exp:n+i*60};return e.roles!==void 0&&(o.roles=e.roles),this.signJwt(o,r.servicePrivateKey)}async verifyServiceToken(e){if(this.config.algorithm!=="EdDSA")return null;let r=await this.ensureKeys(),n=this.config.serviceTokenAudience??q5,i=await this.verifyJwt(e,r.servicePublicKey,n);return!i||i.typ!==G5?null:i}async initialize(){}async cleanup(){}};function Vv(t){return new TA(t)}var rr={INVALID_EMAIL:{code:"INVALID_EMAIL",message:"Invalid email address format",status:400},WEAK_PASSWORD:{code:"WEAK_PASSWORD",message:"Password must be at least 8 characters",status:400},SUPER_ADMIN_EXISTS:{code:"SUPER_ADMIN_EXISTS",message:"Super admin already exists in the system tenant. Use --force to override (not recommended).",status:409},EXISTENCE_CHECK_FAILED:t=>({code:"EXISTENCE_CHECK_FAILED",message:t?`Could not verify whether a super admin already exists: ${t}. Aborting to avoid creating a duplicate.`:"Could not verify whether a super admin already exists. Aborting to avoid creating a duplicate.",status:503}),MISSING_DATABASE_CONFIG:t=>({code:"MISSING_DATABASE_CONFIG",message:`Missing required configuration for ${t} database`,status:400}),REGISTRATION_FAILED:t=>({code:"REGISTRATION_FAILED",message:t?`Registration failed: ${t}`:"Failed to register user",status:500}),VERIFICATION_FAILED:{code:"VERIFICATION_FAILED",message:"Failed to mark email as verified",status:500},TENANT_CREATE_FAILED:{code:"TENANT_CREATE_FAILED",message:"Failed to create system tenant",status:500},TENANT_MEMBER_FAILED:{code:"TENANT_MEMBER_FAILED",message:"Failed to create tenant member",status:500},TENANT_PROFILE_FAILED:{code:"TENANT_PROFILE_FAILED",message:"Failed to create tenant profile",status:500},SDK_INIT_FAILED:t=>({code:"SDK_INIT_FAILED",message:t?`SDK initialization failed: ${t}`:"Failed to initialize SDK",status:500})},z5=["user_profiles","user_auths","identities","credentials","tenants","tenant_members","user_tenant_profiles"],sye={user_profiles:["id","contact_email","display_name","status","created_at","updated_at"],user_auths:["user_id","user_status","mfa_required","created_at","updated_at"],identities:["type","value","user_id","login_enabled","verified_at","guards","created_at"],credentials:["id","user_id","type","value","data","created_at"],tenants:["id","type","owner_id","created_at"],tenant_members:["tenant_id","user_id","status","roles","auth_perm_mask","is_owner","created_at"],user_tenant_profiles:["tenant_id","user_id","display_name","display_name_source","contact_email","created_at"]};function oye(t){try{let e=new URL(t);return e.password&&(e.password="****"),e.toString()}catch{return t.replace(/:([^@/]+)@/,":****@")}}async function aye(t){console.log("[DB Diagnostics] Starting database health checks..."),console.log(`[DB Diagnostics] URL: ${oye(t)}`);let e=new mh({connectionString:t,max:1,connectionTimeoutMillis:5e3});try{console.log("[DB Diagnostics] Testing connection...");let r=await e.connect(),i=(await r.query("SELECT version()")).rows[0];console.log(`[DB Diagnostics] Connected! ${i?.version?.split(",")[0]??"unknown"}`);let o=(await r.query("SELECT current_database(), current_schema()")).rows[0];console.log(`[DB Diagnostics] Database: ${o?.current_database??"unknown"}, Schema: ${o?.current_schema??"unknown"}`),console.log("[DB Diagnostics] Checking required tables...");let a=await r.query(`SELECT table_name FROM information_schema.tables
|
|
193
193
|
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
|
194
194
|
ORDER BY table_name`),c=new Set(a.rows.map(p=>p.table_name)),u=!0;for(let p of z5)c.has(p)?console.log(`[DB Diagnostics] \u2713 ${p}`):(console.error(`[DB Diagnostics] \u2717 ${p} \u2014 MISSING`),u=!1);if(!u)return console.error("[DB Diagnostics] Some tables are missing! Run schema.sql first."),r.release(),!1;console.log("[DB Diagnostics] Checking table columns...");for(let[p,d]of Object.entries(sye)){let h=await r.query(`SELECT column_name FROM information_schema.columns
|
|
195
195
|
WHERE table_schema = 'public' AND table_name = $1
|
package/assets/gateway/worker.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var Et=typeof process<"u"&&!1;function y(t){return Et?Object.freeze(t):t}function m(t,e){if(arguments.length===0)return y({ok:!0,data:void 0});let n={ok:!0,data:t};return e!==void 0&&(n.meta=e),y(n)}function a(t,e){let r={ok:!1,errors:y(Array.isArray(t)?[...t]:[t])};return e!==void 0&&(r.meta=e),y(r)}function E(t,e){return n=>{let r={code:t,message:n?.message??e};return n?.field&&(r.field=n.field),r}}function N(t){return t.ok?t.data===void 0?t.meta?{ok:!0,meta:t.meta}:{ok:!0}:t.meta?{ok:!0,data:t.data,meta:t.meta}:{ok:!0,data:t.data}:t.meta?{ok:!1,errors:t.errors,meta:t.meta}:{ok:!1,errors:t.errors}}function v(t){let e=["BACKEND_URL","BACKEND_GATEWAY_SECRET","JWT_PUBLIC_KEY","SERVICE_JWT_PUBLIC_KEY"];U(t)||e.push("EDGE_GATE_SECRET");let n=e.filter(r=>!t[r]);if(n.length>0)throw new Error(`Missing required environment variables: ${n.join(", ")}`)}function U(t){return t.SKIP_EDGE_GATE==="true"}function pt(t){return t.ENVIRONMENT||"development"}function S(t){return pt(t)==="development"}function W(t){return t.ALLOWED_ORIGINS?t.ALLOWED_ORIGINS.split(",").map(e=>e.trim()).filter(Boolean):["*"]}function gt(t){return t.ALLOWED_ORIGIN_SUFFIXES?t.ALLOWED_ORIGIN_SUFFIXES.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[]}function P(t){return`origin:${t}`}async function H(t,e){if(!t)return!1;if(W(e).includes(t))return!0;let r=gt(e);if(r.length>0){let o;try{o=new URL(t).host.toLowerCase()}catch{return!1}if(r.some(i=>o===i.replace(/^\./,"")||o.endsWith(i)))return!0}return!!(e.CORS_KV&&await e.CORS_KV.get(P(t))!==null)}function O(t){let e=t.ROUTE_PREFIX||"";if(!e)return"";let n=e.startsWith("/")?e:`/${e}`;return n.endsWith("/")?n.slice(0,-1):n}function C(t,e){let n=O(e);return n?t===n?"/":t.startsWith(`${n}/`)?t.slice(n.length):t:t}var At=new Set(["system","tenant","customer"]);function L(t){return t&&At.has(t)?t:void 0}function F(t,e){let n=L(t.AUTH_SCOPE);if(n)return{scope:n,forwardPath:e};let r=L(O(t).replace(/^\//,""));if(r)return{scope:r,forwardPath:e};let o=e.split("/").filter(Boolean),i=L(o[0]);if(i){let s=e.slice(e.indexOf(o[0])+o[0].length);return{scope:i,forwardPath:s||"/"}}return{scope:void 0,forwardPath:e}}function X(t,e){let n=O(e);return n?t===n||t.startsWith(`${n}/`):!0}function $(t){if(!t)return new Set(["EdDSA"]);let e=t.split(",").map(n=>n.trim()).filter(Boolean);return e.length>0?new Set(e):new Set(["EdDSA"])}function Y(t){return $(t.JWT_ALLOWED_ALGORITHMS)}function j(t){return $(t.SERVICE_JWT_ALLOWED_ALGORITHMS)}function ht(t){let e=t.SERVICE_ROUTE_PREFIX||"/internal",n=e.startsWith("/")?e:`/${e}`;return n.endsWith("/")?n.slice(0,-1):n}function z(t){return t.SERVICE_JWT_AUDIENCE||"auth-craft:internal"}function Q(t){return t.JWT_ISSUER||void 0}function Z(t){return t.SERVICE_JWT_ISSUER||void 0}function q(t,e){let n=t.split("?")[0].replace(/\/$/,"")||"/",r=ht(e);return n===r||n.startsWith(`${r}/`)}var mt=new Set(["authenticate","login","register","refresh","logout","complete-challenge","complete-mfa-challenge","issue-mfa-challenge","forgot-password","reset-password","verify-email","resend-verification"]),M=new Set(["/verification/resend","/verification/verify","/password/forgot","/password/reset"]);function tt(t){let e=t.split("?")[0].replace(/\/$/,"")||"/";if(e==="/"||e.startsWith("/oauth/")||e==="/oauth"||M.has(e))return!0;let n=e.match(/^\/[^/]+(\/(?:verification|password)\/[^/]+)$/);if(n?.[1]&&M.has(n[1]))return!0;let r=e.match(/^(?:\/[^/]+)?\/auth\/([^/]+)/);if(r){let o=r[1];if(o&&mt.has(o)||o?.startsWith("oauth"))return!0}return!1}function et(t){return t.GATEWAY_JWT_PRIVATE_KEY||void 0}function rt(t){return t.TID_VALUE||void 0}function nt(t){let e=t.split("?")[0].replace(/\/$/,"");return e.endsWith("/auth/login")||e.endsWith("/auth/authenticate")}function Rt(){let t=Date.now(),e=new Uint8Array(16);e[0]=t/1099511627776&255,e[1]=t/4294967296&255,e[2]=t/16777216&255,e[3]=t/65536&255,e[4]=t/256&255,e[5]=t&255;let n=new Uint8Array(10);crypto.getRandomValues(n),e.set(n,6),e[6]=e[6]&15|112,e[8]=e[8]&63|128;let r="0123456789ABCDEFGHJKMNPQRSTVWXYZ",o="",i=0,s=0;for(let c=0;c<e.length;c++)for(s=s<<8|e[c],i+=8;i>=5;)o+=r[s>>>i-5&31],i-=5;return i>0&&(o+=r[s<<5-i&31]),o}var T={BAD_REQUEST:E("BAD_REQUEST","Bad request"),UNAUTHORIZED:E("UNAUTHORIZED","Authentication required"),FORBIDDEN:E("FORBIDDEN","Access denied"),NOT_FOUND:E("NOT_FOUND","Resource not found"),RATE_LIMIT_EXCEEDED:E("RATE_LIMIT_EXCEEDED","Too many requests"),INTERNAL_ERROR:E("INTERNAL_ERROR","Internal server error"),BAD_GATEWAY:E("BAD_GATEWAY","Gateway error"),SERVICE_UNAVAILABLE:E("SERVICE_UNAVAILABLE","Service temporarily unavailable"),GATEWAY_TIMEOUT:E("GATEWAY_TIMEOUT","Gateway timeout")},d={INVALID_FORMAT:E("JWT_INVALID_FORMAT","Invalid JWT format"),UNSUPPORTED_ALGORITHM:E("JWT_UNSUPPORTED_ALGORITHM","Unsupported JWT algorithm"),INVALID_TOKEN_TYPE:E("JWT_INVALID_TOKEN_TYPE","Invalid token type: expected access token"),TOKEN_EXPIRED:E("JWT_TOKEN_EXPIRED","JWT token expired"),SIGNATURE_INVALID:E("JWT_SIGNATURE_INVALID","JWT signature verification failed"),MISSING_TOKEN:E("JWT_MISSING_TOKEN","Missing access token"),INVALID_ISSUER:E("JWT_INVALID_ISSUER","Invalid JWT issuer")},ot={BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,RATE_LIMIT_EXCEEDED:429,INTERNAL_ERROR:500,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504,JWT_INVALID_FORMAT:401,JWT_UNSUPPORTED_ALGORITHM:401,JWT_INVALID_TOKEN_TYPE:401,JWT_TOKEN_EXPIRED:401,JWT_SIGNATURE_INVALID:401,JWT_MISSING_TOKEN:401,JWT_INVALID_ISSUER:401};function St(t){if(t.ok)return t.data===void 0?204:200;let e=t.errors?.[0];return e?.code&&e.code in ot?ot[e.code]:500}function f(t,e){let n=new Date().toISOString(),r=e||Rt(),o={...t.meta,timestamp:n,traceId:r},i=t.ok?m(t.data,o):a(t.errors,o),s=St(i),c=N(i);return new Response(JSON.stringify(c),{status:s,headers:{"Content-Type":"application/json"}})}var wt=new Set(["EdDSA","ES256","RS256","PS256"]);function _t(t){return wt.has(t)}function yt(t){switch(t){case"EdDSA":return{name:"Ed25519"};case"ES256":return{name:"ECDSA",namedCurve:"P-256"};case"RS256":return{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"};case"PS256":return{name:"RSA-PSS",hash:"SHA-256"}}}function It(t){switch(t){case"EdDSA":return"Ed25519";case"ES256":return{name:"ECDSA",hash:"SHA-256"};case"RS256":return{name:"RSASSA-PKCS1-v1_5"};case"PS256":return{name:"RSA-PSS",saltLength:32}}}function x(t){let e=t.replace(/-/g,"+").replace(/_/g,"/"),n=e+"==".slice(0,(4-e.length%4)%4),r=atob(n),o=new Uint8Array(r.length);for(let i=0;i<r.length;i++)o[i]=r.charCodeAt(i);return o}function it(t){return atob(t)}var Ot=/^[0-9a-fA-F]{64}$/;async function Dt(t,e){if(Ot.test(t)){if(e!=="EdDSA")throw new Error(`Hex key format is only supported for EdDSA, got ${e}`);let r=new Uint8Array(32);for(let o=0;o<32;o++)r[o]=parseInt(t.slice(o*2,o*2+2),16);return crypto.subtle.importKey("raw",r,{name:"Ed25519"},!1,["verify"])}let n;try{n=JSON.parse(it(t))}catch{throw new Error(`Invalid JWK base64 for algorithm ${e}`)}return await crypto.subtle.importKey("jwk",n,yt(e),!1,["verify"])}function st(t){let e=t.split(".");if(e.length!==3)return null;let n=e[1];if(!n)return null;try{let r=x(n);return JSON.parse(new TextDecoder().decode(r))}catch{return null}}function Pt(t){let n=t.split(".")[0];if(!n)return a(d.INVALID_FORMAT());try{let r=x(n);return m(JSON.parse(new TextDecoder().decode(r)))}catch{return a(d.INVALID_FORMAT())}}function Ct(t){let n=t.split(".")[1];if(!n)return a(d.INVALID_FORMAT());try{let r=x(n);return m(JSON.parse(new TextDecoder().decode(r)))}catch{return a(d.INVALID_FORMAT())}}async function xt(t,e,n){let r=t.split("."),o=r[0],i=r[1],s=r[2];if(!o||!i||!s)return!1;let c=new TextEncoder().encode(`${o}.${i}`),u=x(s);try{return await crypto.subtle.verify(It(n),e,u,c)}catch{return!1}}async function b(t,e,n=1,r=new Set(["EdDSA"]),o){let i=Pt(t);if(!i.ok)return a(i.errors,i.meta);let s=i.data.alg;if(!r.has(s))return a(d.UNSUPPORTED_ALGORITHM());if(!_t(s))return a(d.UNSUPPORTED_ALGORITHM());let c=Ct(t);if(!c.ok)return a(c.errors,c.meta);let u=c.data;if(u.typ!==n)return a(d.INVALID_TOKEN_TYPE());if(o!==void 0&&u.iss!==o)return a(d.INVALID_ISSUER());if(u.exp&&Math.floor(Date.now()/1e3)>=u.exp)return a(d.TOKEN_EXPIRED());let l;try{l=await Dt(e,s)}catch{return a(d.INVALID_FORMAT())}return await xt(t,l,s)?m(u):a(d.SIGNATURE_INVALID())}var J=null;async function Nt(t){let e;try{e=JSON.parse(it(t))}catch{throw new Error("Invalid Gateway JWT private key: expected base64-encoded JWK")}return await crypto.subtle.importKey("jwk",e,{name:"Ed25519"},!1,["sign"])}function k(t){let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function at(t,e,n,r,o=30){J||(J=Nt(t));let i=await J,s=Math.floor(Date.now()/1e3),c={alg:"EdDSA",typ:"JWT"},u={iss:e,aud:n,sub:r,iat:s,exp:s+o,jti:crypto.randomUUID()},l=k(new TextEncoder().encode(JSON.stringify(c))),A=k(new TextEncoder().encode(JSON.stringify(u))),R=new TextEncoder().encode(`${l}.${A}`),h=await crypto.subtle.sign("Ed25519",i,R),w=k(new Uint8Array(h));return`${l}.${A}.${w}`}var Lt=["content-type","accept","accept-encoding","accept-language","user-agent","cookie"];function Ut(t){return t.getSetCookie?.()??[]}async function Wt(t,e){if(!(t.headers.get("content-type")||"").includes("application/json"))return null;let r;try{r=await t.clone().json()}catch{return null}return delete r.tid,r.tid=e,{body:JSON.stringify(r),contentType:"application/json"}}function Jt(t,e,n,r,o,i){let s=new Headers;for(let R of Lt){let h=t.headers.get(R);h&&s.set(R,h)}o&&s.set("content-type",o);let c=t.headers.get("Authorization");c&&s.set("Authorization",c),r&&s.set("X-Auth-Context",r),s.set("X-Gateway-Secret",e),i&&s.set("x-gateway-auth",`Bearer ${i}`),s.set("X-Request-Id",n);let u=t.headers.get("cf-connecting-ip");u&&(s.set("X-Forwarded-For",u),s.set("X-Real-IP",u)),s.set("X-Forwarded-Proto","https");let l=new URL(t.url);s.set("X-Forwarded-Host",l.hostname);let A=t.headers.get("cf-ipcountry");return A&&s.set("X-Cloudflare-Country",A),s}function kt(t,e,n){let r=new URL(t.url),o=e.BACKEND_URL.endsWith("/")?e.BACKEND_URL.slice(0,-1):e.BACKEND_URL;S(e)&&console.log("[Gateway] Path transform:",JSON.stringify({original:r.pathname,forwarded:n}));let i=new URL(`${o}${n}`);return i.search=r.search,i.toString()}function bt(t,e,n){let r=new Headers(t.headers);r.set("X-Request-Id",e);let o=Ut(t.headers);if(o.length>0){r.delete("Set-Cookie");for(let i of o)r.append("Set-Cookie",i);S(n)&&console.log("[Gateway] Forwarding Set-Cookie:",o.length,"cookie(s)")}return new Response(t.body,{status:t.status,statusText:t.statusText,headers:r})}async function G(t,e){let n=crypto.randomUUID();try{let r=new URL(t.url),o=C(r.pathname,e),{scope:i,forwardPath:s}=F(e,o),c=kt(t,e,s),u,l,A=rt(e);if(A&&nt(s)&&t.method==="POST"){let I=await Wt(t,A);I&&(u=I.body,l=I.contentType,S(e)&&console.log("[Gateway] Injected tid:",A,"into",s))}let R,h=et(e);h&&(R=await at(h,"bff-worker","auth-craft:backend","cloudflare-auth-gateway"));let w=Jt(t,e.BACKEND_GATEWAY_SECRET,n,i,l,R),V=u!==void 0?u:t.body?await t.clone().arrayBuffer():void 0,_=await fetch(c,{method:t.method,headers:w,body:V});return bt(_,n,e)}catch(r){console.error("[Gateway] Proxy error:",r);let o=r instanceof Error?r.message:"Unknown error";return f(a(E("BAD_GATEWAY",`Failed to reach backend: ${o}`)()))}}var D=new Map,ct=0,Gt=[{match:t=>t==="/auth/authenticate",limit:15,windowMs:6e4,prefix:"login"},{match:t=>t==="/auth/register",limit:8,windowMs:6e4,prefix:"register"},{match:t=>t==="/password/forgot",limit:8,windowMs:3e5,prefix:"forgot"},{match:t=>t.startsWith("/oauth/"),limit:30,windowMs:6e4,prefix:"oauth"},{match:t=>t.startsWith("/auth/complete-"),limit:30,windowMs:6e4,prefix:"complete"},{match:t=>t==="/auth/issue-mfa-challenge",limit:15,windowMs:6e4,prefix:"issue-mfa"}];function Kt(t){let e=Date.now();if(e-ct<3e4)return;ct=e;let n=e-t;for(let[r,o]of D){let i=o.hits.filter(s=>s>n);i.length===0?D.delete(r):o.hits=i}}function ut(t,e,n){if(n!=="POST")return{allowed:!0,retryAfterSec:0};let r=Gt.find(l=>l.match(e));if(!r)return{allowed:!0,retryAfterSec:0};Kt(3e5);let o=`${r.prefix}:${t}`,i=Date.now(),s=i-r.windowMs,c=D.get(o)??{hits:[]},u=c.hits.filter(l=>l>s);if(u.length>=r.limit){let l=u[0]??i;return c.hits=u,D.set(o,c),{allowed:!1,retryAfterSec:Math.max(1,Math.ceil((l+r.windowMs-i)/1e3))}}return u.push(i),c.hits=u,D.set(o,c),{allowed:!0,retryAfterSec:0}}function ft(t,e){if(t.length!==e.length)return!1;let n=0;for(let r=0;r<t.length;r++)n|=t.charCodeAt(r)^e.charCodeAt(r);return n===0}async function lt(t,e){return t&&await H(t,e)?{"Access-Control-Allow-Origin":t,"Access-Control-Allow-Credentials":"true",Vary:"Origin"}:W(e).includes("*")?{"Access-Control-Allow-Origin":"*"}:{Vary:"Origin"}}async function Vt(t,e){let n=t.headers.get("Origin")??"*",r=t.headers.get("Access-Control-Request-Headers")??"";return new Response(null,{status:204,headers:{...await lt(n,e),"Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS, PATCH","Access-Control-Allow-Headers":r||"Content-Type, Authorization, X-Requested-With, X-Edge-Gate","Access-Control-Max-Age":"86400"}})}async function p(t,e,n){let r=e.headers.get("Origin")??"*",o=new Response(t.body,t),i=t.headers.getSetCookie?.()??[];if(i.length>0){o.headers.delete("Set-Cookie");for(let c of i)o.headers.append("Set-Cookie",c)}let s=await lt(r,n);for(let[c,u]of Object.entries(s))o.headers.set(c,u);return o.headers.set("Access-Control-Expose-Headers","Content-Length, X-Request-Id, Set-Cookie"),o}var Bt="/internal/cors-origins";function Mt(t){return t.split("?")[0].replace(/\/$/,"")===Bt}async function vt(t,e){if(!e.CORS_KV)return K();if(t.method==="GET"){let r=(await e.CORS_KV.list({prefix:"origin:"})).keys.map(o=>o.name.slice(7));return f(m({origins:r}))}if(t.method==="PUT"||t.method==="DELETE"){let n;try{n=(await t.json()).origin}catch{return f(a(T.BAD_REQUEST()))}if(typeof n!="string"||!n)return f(a(T.BAD_REQUEST()));let r;try{if(r=new URL(n).origin,r==="null"||r!==n.replace(/\/$/,""))return f(a(T.BAD_REQUEST()))}catch{return f(a(T.BAD_REQUEST()))}return t.method==="PUT"?await e.CORS_KV.put(P(r),"1"):await e.CORS_KV.delete(P(r)),f(m({origin:r}))}return f(a(T.BAD_REQUEST()))}function K(){return new Response(null,{status:404})}var Pe={async fetch(t,e,n){try{v(e);let r=new URL(t.url);if(!X(r.pathname,e))return K();if(t.method==="OPTIONS")return await Vt(t,e);if(!U(e)){let g=t.headers.get("X-Edge-Gate");if(!g||!e.EDGE_GATE_SECRET||!ft(g,e.EDGE_GATE_SECRET))return S(e)&&console.warn("[Gateway] X-Edge-Gate missing or invalid"),K()}let o=O(e),i=o?`${o}/health`:"/health";if(r.pathname===i)return await p(f(m({status:"healthy",timestamp:new Date().toISOString()})),t,e);let s=C(r.pathname,e);S(e)&&console.log("[Gateway] Request:",t.method,r.pathname,"\u2192",s);let c=t.headers.get("cf-connecting-ip")??"unknown",u=ut(c,s,t.method);if(!u.allowed){let g=f(a(T.RATE_LIMIT_EXCEEDED()));return g.headers.set("Retry-After",String(u.retryAfterSec)),await p(g,t,e)}let l=q(s,e);if(!l&&tt(s))return await p(await G(t,e),t,e);let A=t.headers.get("Authorization");if(!A)return await p(f(a(d.MISSING_TOKEN())),t,e);let R=A.match(/^Bearer\s+(.+)$/i);if(!R)return await p(f(a(d.INVALID_FORMAT())),t,e);let h=R[1];if(h.length>4096)return await p(f(a(d.INVALID_FORMAT())),t,e);let w=h.split(".");if(w.length!==3)return await p(f(a(d.INVALID_FORMAT())),t,e);if(w[1].length>2048)return await p(f(a(d.INVALID_FORMAT())),t,e);let _=st(h);if(!_)return await p(f(a(d.INVALID_FORMAT())),t,e);let I=_.typ,dt=l?3:1;if(I!==dt)return await p(f(a(d.INVALID_TOKEN_TYPE())),t,e);if(l&&_.aud!==z(e))return await p(f(a(d.INVALID_TOKEN_TYPE())),t,e);let B=_.exp;if(typeof B=="number"&&Math.floor(Date.now()/1e3)>=B)return await p(f(a(d.TOKEN_EXPIRED())),t,e);if(l){if(!e.SERVICE_JWT_PUBLIC_KEY)return await p(f(a(T.INTERNAL_ERROR())),t,e);let g=await b(h,e.SERVICE_JWT_PUBLIC_KEY,3,j(e),Z(e));if(!g.ok)return S(e)&&console.error("[Gateway] Service JWT failed:",g.errors),await p(f(a(g.errors,g.meta)),t,e)}else{let g=await b(h,e.JWT_PUBLIC_KEY,1,Y(e),Q(e));if(!g.ok)return S(e)&&console.error("[Gateway] User JWT failed:",g.errors),await p(f(a(g.errors,g.meta)),t,e)}return l&&Mt(s)?await p(await vt(t,e),t,e):await p(await G(t,e),t,e)}catch(r){return S(e)&&console.error("[Gateway] Unhandled error:",r),f(a(T.INTERNAL_ERROR()))}}};export{Pe as default};
|
|
1
|
+
var gt=typeof process<"u"&&!1;function y(t){return gt?Object.freeze(t):t}function m(t,e){if(arguments.length===0)return y({ok:!0,data:void 0});let r={ok:!0,data:t};return e!==void 0&&(r.meta=e),y(r)}function a(t,e){let n={ok:!1,errors:y(Array.isArray(t)?[...t]:[t])};return e!==void 0&&(n.meta=e),y(n)}function E(t,e){return r=>{let n={code:t,message:r?.message??e};return r?.field&&(n.field=r.field),n}}function U(t){return t.ok?t.data===void 0?t.meta?{ok:!0,meta:t.meta}:{ok:!0}:t.meta?{ok:!0,data:t.data,meta:t.meta}:{ok:!0,data:t.data}:t.meta?{ok:!1,errors:t.errors,meta:t.meta}:{ok:!1,errors:t.errors}}function F(t){let e=["BACKEND_URL","BACKEND_GATEWAY_SECRET","JWT_PUBLIC_KEY","SERVICE_JWT_PUBLIC_KEY"];J(t)||e.push("EDGE_GATE_SECRET");let r=e.filter(n=>!t[n]);if(r.length>0)throw new Error(`Missing required environment variables: ${r.join(", ")}`)}function J(t){return t.SKIP_EDGE_GATE==="true"}function At(t){return t.ENVIRONMENT||"development"}function S(t){return At(t)==="development"}function k(t){return t.ALLOWED_ORIGINS?t.ALLOWED_ORIGINS.split(",").map(e=>e.trim()).filter(Boolean):["*"]}function ht(t){return t.ALLOWED_ORIGIN_SUFFIXES?t.ALLOWED_ORIGIN_SUFFIXES.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[]}function C(t){return`origin:${t}`}async function $(t,e){if(!t)return!1;if(k(e).includes(t))return!0;let n=ht(e);if(n.length>0){let o;try{o=new URL(t).host.toLowerCase()}catch{return!1}if(n.some(i=>o===i.replace(/^\./,"")||o.endsWith(i)))return!0}return!!(e.CORS_KV&&await e.CORS_KV.get(C(t))!==null)}function O(t){let e=t.ROUTE_PREFIX||"";if(!e)return"";let r=e.startsWith("/")?e:`/${e}`;return r.endsWith("/")?r.slice(0,-1):r}function D(t,e){let r=O(e);return r?t===r?"/":t.startsWith(`${r}/`)?t.slice(r.length):t:t}var mt=new Set(["system","tenant","customer"]);function W(t){return t&&mt.has(t)?t:void 0}function x(t,e){let r=W(t.AUTH_SCOPE);if(r)return{scope:r,forwardPath:e};let n=W(O(t).replace(/^\//,""));if(n)return{scope:n,forwardPath:e};let o=e.split("/").filter(Boolean),i=W(o[0]);if(i){let s=e.slice(e.indexOf(o[0])+o[0].length);return{scope:i,forwardPath:s||"/"}}return{scope:void 0,forwardPath:e}}function X(t,e){let r=O(e);return r?t===r||t.startsWith(`${r}/`):!0}function Y(t){if(!t)return new Set(["EdDSA"]);let e=t.split(",").map(r=>r.trim()).filter(Boolean);return e.length>0?new Set(e):new Set(["EdDSA"])}function j(t){return Y(t.JWT_ALLOWED_ALGORITHMS)}function z(t){return Y(t.SERVICE_JWT_ALLOWED_ALGORITHMS)}function St(t){let e=t.SERVICE_ROUTE_PREFIX||"/internal",r=e.startsWith("/")?e:`/${e}`;return r.endsWith("/")?r.slice(0,-1):r}function Q(t){return t.SERVICE_JWT_AUDIENCE||"auth-craft:internal"}function Z(t){return t.JWT_ISSUER||void 0}function q(t){return t.SERVICE_JWT_ISSUER||void 0}function tt(t,e){let r=t.split("?")[0].replace(/\/$/,"")||"/",n=St(e);return r===n||r.startsWith(`${n}/`)}var Rt=new Set(["authenticate","login","register","refresh","logout","complete-challenge","complete-mfa-challenge","issue-mfa-challenge","forgot-password","reset-password","verify-email","resend-verification"]),v=new Set(["/verification/resend","/verification/verify","/password/forgot","/password/reset"]),Tt=new Set(["select-tenant"]);function et(t){let r=(t.split("?")[0].replace(/\/$/,"")||"/").match(/^(?:\/[^/]+)?\/auth\/([^/]+)/);return!!r?.[1]&&Tt.has(r[1])}function rt(t){let e=t.split("?")[0].replace(/\/$/,"")||"/";if(e==="/"||e.startsWith("/oauth/")||e==="/oauth"||v.has(e))return!0;let r=e.match(/^\/[^/]+(\/(?:verification|password)\/[^/]+)$/);if(r?.[1]&&v.has(r[1]))return!0;let n=e.match(/^(?:\/[^/]+)?\/auth\/([^/]+)/);if(n){let o=n[1];if(o&&Rt.has(o)||o?.startsWith("oauth"))return!0}return!1}function nt(t){return t.GATEWAY_JWT_PRIVATE_KEY||void 0}function ot(t){return t.TID_VALUE||void 0}function it(t){let e=t.split("?")[0].replace(/\/$/,"");return e.endsWith("/auth/login")||e.endsWith("/auth/authenticate")}function _t(){let t=Date.now(),e=new Uint8Array(16);e[0]=t/1099511627776&255,e[1]=t/4294967296&255,e[2]=t/16777216&255,e[3]=t/65536&255,e[4]=t/256&255,e[5]=t&255;let r=new Uint8Array(10);crypto.getRandomValues(r),e.set(r,6),e[6]=e[6]&15|112,e[8]=e[8]&63|128;let n="0123456789ABCDEFGHJKMNPQRSTVWXYZ",o="",i=0,s=0;for(let c=0;c<e.length;c++)for(s=s<<8|e[c],i+=8;i>=5;)o+=n[s>>>i-5&31],i-=5;return i>0&&(o+=n[s<<5-i&31]),o}var T={BAD_REQUEST:E("BAD_REQUEST","Bad request"),UNAUTHORIZED:E("UNAUTHORIZED","Authentication required"),FORBIDDEN:E("FORBIDDEN","Access denied"),NOT_FOUND:E("NOT_FOUND","Resource not found"),RATE_LIMIT_EXCEEDED:E("RATE_LIMIT_EXCEEDED","Too many requests"),INTERNAL_ERROR:E("INTERNAL_ERROR","Internal server error"),BAD_GATEWAY:E("BAD_GATEWAY","Gateway error"),SERVICE_UNAVAILABLE:E("SERVICE_UNAVAILABLE","Service temporarily unavailable"),GATEWAY_TIMEOUT:E("GATEWAY_TIMEOUT","Gateway timeout")},f={INVALID_FORMAT:E("JWT_INVALID_FORMAT","Invalid JWT format"),UNSUPPORTED_ALGORITHM:E("JWT_UNSUPPORTED_ALGORITHM","Unsupported JWT algorithm"),INVALID_TOKEN_TYPE:E("JWT_INVALID_TOKEN_TYPE","Invalid token type: expected access token"),TOKEN_EXPIRED:E("JWT_TOKEN_EXPIRED","JWT token expired"),SIGNATURE_INVALID:E("JWT_SIGNATURE_INVALID","JWT signature verification failed"),MISSING_TOKEN:E("JWT_MISSING_TOKEN","Missing access token"),INVALID_ISSUER:E("JWT_INVALID_ISSUER","Invalid JWT issuer"),SCOPE_MISMATCH:E("JWT_SCOPE_MISMATCH","Token not valid for this scope")},st={BAD_REQUEST:400,UNAUTHORIZED:401,FORBIDDEN:403,NOT_FOUND:404,RATE_LIMIT_EXCEEDED:429,INTERNAL_ERROR:500,BAD_GATEWAY:502,SERVICE_UNAVAILABLE:503,GATEWAY_TIMEOUT:504,JWT_INVALID_FORMAT:401,JWT_UNSUPPORTED_ALGORITHM:401,JWT_INVALID_TOKEN_TYPE:401,JWT_TOKEN_EXPIRED:401,JWT_SIGNATURE_INVALID:401,JWT_MISSING_TOKEN:401,JWT_INVALID_ISSUER:401,JWT_SCOPE_MISMATCH:403};function wt(t){if(t.ok)return t.data===void 0?204:200;let e=t.errors?.[0];return e?.code&&e.code in st?st[e.code]:500}function l(t,e){let r=new Date().toISOString(),n=e||_t(),o={...t.meta,timestamp:r,traceId:n},i=t.ok?m(t.data,o):a(t.errors,o),s=wt(i),c=U(i);return new Response(JSON.stringify(c),{status:s,headers:{"Content-Type":"application/json"}})}var It=new Set(["EdDSA","ES256","RS256","PS256"]);function Ot(t){return It.has(t)}function Pt(t){switch(t){case"EdDSA":return{name:"Ed25519"};case"ES256":return{name:"ECDSA",namedCurve:"P-256"};case"RS256":return{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"};case"PS256":return{name:"RSA-PSS",hash:"SHA-256"}}}function Ct(t){switch(t){case"EdDSA":return"Ed25519";case"ES256":return{name:"ECDSA",hash:"SHA-256"};case"RS256":return{name:"RSASSA-PKCS1-v1_5"};case"PS256":return{name:"RSA-PSS",saltLength:32}}}function N(t){let e=t.replace(/-/g,"+").replace(/_/g,"/"),r=e+"==".slice(0,(4-e.length%4)%4),n=atob(r),o=new Uint8Array(n.length);for(let i=0;i<n.length;i++)o[i]=n.charCodeAt(i);return o}function at(t){return atob(t)}var Dt=/^[0-9a-fA-F]{64}$/;async function xt(t,e){if(Dt.test(t)){if(e!=="EdDSA")throw new Error(`Hex key format is only supported for EdDSA, got ${e}`);let n=new Uint8Array(32);for(let o=0;o<32;o++)n[o]=parseInt(t.slice(o*2,o*2+2),16);return crypto.subtle.importKey("raw",n,{name:"Ed25519"},!1,["verify"])}let r;try{r=JSON.parse(at(t))}catch{throw new Error(`Invalid JWK base64 for algorithm ${e}`)}return await crypto.subtle.importKey("jwk",r,Pt(e),!1,["verify"])}function ct(t){let e=t.split(".");if(e.length!==3)return null;let r=e[1];if(!r)return null;try{let n=N(r);return JSON.parse(new TextDecoder().decode(n))}catch{return null}}function Nt(t){let r=t.split(".")[0];if(!r)return a(f.INVALID_FORMAT());try{let n=N(r);return m(JSON.parse(new TextDecoder().decode(n)))}catch{return a(f.INVALID_FORMAT())}}function Lt(t){let r=t.split(".")[1];if(!r)return a(f.INVALID_FORMAT());try{let n=N(r);return m(JSON.parse(new TextDecoder().decode(n)))}catch{return a(f.INVALID_FORMAT())}}async function Ut(t,e,r){let n=t.split("."),o=n[0],i=n[1],s=n[2];if(!o||!i||!s)return!1;let c=new TextEncoder().encode(`${o}.${i}`),u=N(s);try{return await crypto.subtle.verify(Ct(r),e,u,c)}catch{return!1}}async function K(t,e,r=1,n=new Set(["EdDSA"]),o){let i=Nt(t);if(!i.ok)return a(i.errors,i.meta);let s=i.data.alg;if(!n.has(s))return a(f.UNSUPPORTED_ALGORITHM());if(!Ot(s))return a(f.UNSUPPORTED_ALGORITHM());let c=Lt(t);if(!c.ok)return a(c.errors,c.meta);let u=c.data;if(u.typ!==r)return a(f.INVALID_TOKEN_TYPE());if(o!==void 0&&u.iss!==o)return a(f.INVALID_ISSUER());if(u.exp&&Math.floor(Date.now()/1e3)>=u.exp)return a(f.TOKEN_EXPIRED());let d;try{d=await xt(e,s)}catch{return a(f.INVALID_FORMAT())}return await Ut(t,d,s)?m(u):a(f.SIGNATURE_INVALID())}var b=null;async function Wt(t){let e;try{e=JSON.parse(at(t))}catch{throw new Error("Invalid Gateway JWT private key: expected base64-encoded JWK")}return await crypto.subtle.importKey("jwk",e,{name:"Ed25519"},!1,["sign"])}function G(t){let e="";for(let r=0;r<t.length;r++)e+=String.fromCharCode(t[r]);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function ut(t,e,r,n,o=30){b||(b=Wt(t));let i=await b,s=Math.floor(Date.now()/1e3),c={alg:"EdDSA",typ:"JWT"},u={iss:e,aud:r,sub:n,iat:s,exp:s+o,jti:crypto.randomUUID()},d=G(new TextEncoder().encode(JSON.stringify(c))),A=G(new TextEncoder().encode(JSON.stringify(u))),R=new TextEncoder().encode(`${d}.${A}`),h=await crypto.subtle.sign("Ed25519",i,R),_=G(new Uint8Array(h));return`${d}.${A}.${_}`}var Jt=["content-type","accept","accept-encoding","accept-language","user-agent","cookie"];function kt(t){return t.getSetCookie?.()??[]}async function bt(t,e){if(!(t.headers.get("content-type")||"").includes("application/json"))return null;let n;try{n=await t.clone().json()}catch{return null}return delete n.tid,n.tid=e,{body:JSON.stringify(n),contentType:"application/json"}}function Gt(t,e,r,n,o,i){let s=new Headers;for(let R of Jt){let h=t.headers.get(R);h&&s.set(R,h)}o&&s.set("content-type",o);let c=t.headers.get("Authorization");c&&s.set("Authorization",c),n&&s.set("X-Auth-Context",n),s.set("X-Gateway-Secret",e),i&&s.set("x-gateway-auth",`Bearer ${i}`),s.set("X-Request-Id",r);let u=t.headers.get("cf-connecting-ip");u&&(s.set("X-Forwarded-For",u),s.set("X-Real-IP",u)),s.set("X-Forwarded-Proto","https");let d=new URL(t.url);s.set("X-Forwarded-Host",d.hostname);let A=t.headers.get("cf-ipcountry");return A&&s.set("X-Cloudflare-Country",A),s}function Kt(t,e,r){let n=new URL(t.url),o=e.BACKEND_URL.endsWith("/")?e.BACKEND_URL.slice(0,-1):e.BACKEND_URL;S(e)&&console.log("[Gateway] Path transform:",JSON.stringify({original:n.pathname,forwarded:r}));let i=new URL(`${o}${r}`);return i.search=n.search,i.toString()}function Vt(t,e,r){let n=new Headers(t.headers);n.set("X-Request-Id",e);let o=kt(t.headers);if(o.length>0){n.delete("Set-Cookie");for(let i of o)n.append("Set-Cookie",i);S(r)&&console.log("[Gateway] Forwarding Set-Cookie:",o.length,"cookie(s)")}return new Response(t.body,{status:t.status,statusText:t.statusText,headers:n})}async function V(t,e){let r=crypto.randomUUID();try{let n=new URL(t.url),o=D(n.pathname,e),{scope:i,forwardPath:s}=x(e,o),c=Kt(t,e,s),u,d,A=ot(e);if(A&&it(s)&&t.method==="POST"){let I=await bt(t,A);I&&(u=I.body,d=I.contentType,S(e)&&console.log("[Gateway] Injected tid:",A,"into",s))}let R,h=nt(e);h&&(R=await ut(h,"bff-worker","auth-craft:backend","cloudflare-auth-gateway"));let _=Gt(t,e.BACKEND_GATEWAY_SECRET,r,i,d,R),B=u!==void 0?u:t.body?await t.clone().arrayBuffer():void 0,w=await fetch(c,{method:t.method,headers:_,body:B});return Vt(w,r,e)}catch(n){console.error("[Gateway] Proxy error:",n);let o=n instanceof Error?n.message:"Unknown error";return l(a(E("BAD_GATEWAY",`Failed to reach backend: ${o}`)()))}}var P=new Map,lt=0,Mt=[{match:t=>t==="/auth/authenticate",limit:15,windowMs:6e4,prefix:"login"},{match:t=>t==="/auth/register",limit:8,windowMs:6e4,prefix:"register"},{match:t=>t==="/password/forgot",limit:8,windowMs:3e5,prefix:"forgot"},{match:t=>t.startsWith("/oauth/"),limit:30,windowMs:6e4,prefix:"oauth"},{match:t=>t.startsWith("/auth/complete-"),limit:30,windowMs:6e4,prefix:"complete"},{match:t=>t==="/auth/issue-mfa-challenge",limit:15,windowMs:6e4,prefix:"issue-mfa"}];function Bt(t){let e=Date.now();if(e-lt<3e4)return;lt=e;let r=e-t;for(let[n,o]of P){let i=o.hits.filter(s=>s>r);i.length===0?P.delete(n):o.hits=i}}function ft(t,e,r){if(r!=="POST")return{allowed:!0,retryAfterSec:0};let n=Mt.find(d=>d.match(e));if(!n)return{allowed:!0,retryAfterSec:0};Bt(3e5);let o=`${n.prefix}:${t}`,i=Date.now(),s=i-n.windowMs,c=P.get(o)??{hits:[]},u=c.hits.filter(d=>d>s);if(u.length>=n.limit){let d=u[0]??i;return c.hits=u,P.set(o,c),{allowed:!1,retryAfterSec:Math.max(1,Math.ceil((d+n.windowMs-i)/1e3))}}return u.push(i),c.hits=u,P.set(o,c),{allowed:!0,retryAfterSec:0}}function dt(t,e){if(t.length!==e.length)return!1;let r=0;for(let n=0;n<t.length;n++)r|=t.charCodeAt(n)^e.charCodeAt(n);return r===0}async function Et(t,e){return t&&await $(t,e)?{"Access-Control-Allow-Origin":t,"Access-Control-Allow-Credentials":"true",Vary:"Origin"}:k(e).includes("*")?{"Access-Control-Allow-Origin":"*"}:{Vary:"Origin"}}async function Ht(t,e){let r=t.headers.get("Origin")??"*",n=t.headers.get("Access-Control-Request-Headers")??"";return new Response(null,{status:204,headers:{...await Et(r,e),"Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS, PATCH","Access-Control-Allow-Headers":n||"Content-Type, Authorization, X-Requested-With, X-Edge-Gate","Access-Control-Max-Age":"86400"}})}async function p(t,e,r){let n=e.headers.get("Origin")??"*",o=new Response(t.body,t),i=t.headers.getSetCookie?.()??[];if(i.length>0){o.headers.delete("Set-Cookie");for(let c of i)o.headers.append("Set-Cookie",c)}let s=await Et(n,r);for(let[c,u]of Object.entries(s))o.headers.set(c,u);return o.headers.set("Access-Control-Expose-Headers","Content-Length, X-Request-Id, Set-Cookie"),o}var vt="/internal/cors-origins";function Ft(t){return t.split("?")[0].replace(/\/$/,"")===vt}async function $t(t,e){if(!e.CORS_KV)return M();if(t.method==="GET"){let n=(await e.CORS_KV.list({prefix:"origin:"})).keys.map(o=>o.name.slice(7));return l(m({origins:n}))}if(t.method==="PUT"||t.method==="DELETE"){let r;try{r=(await t.json()).origin}catch{return l(a(T.BAD_REQUEST()))}if(typeof r!="string"||!r)return l(a(T.BAD_REQUEST()));let n;try{if(n=new URL(r).origin,n==="null"||n!==r.replace(/\/$/,""))return l(a(T.BAD_REQUEST()))}catch{return l(a(T.BAD_REQUEST()))}return t.method==="PUT"?await e.CORS_KV.put(C(n),"1"):await e.CORS_KV.delete(C(n)),l(m({origin:n}))}return l(a(T.BAD_REQUEST()))}function M(){return new Response(null,{status:404})}var Ne={async fetch(t,e,r){try{F(e);let n=new URL(t.url);if(!X(n.pathname,e))return M();if(t.method==="OPTIONS")return await Ht(t,e);if(!J(e)){let g=t.headers.get("X-Edge-Gate");if(!g||!e.EDGE_GATE_SECRET||!dt(g,e.EDGE_GATE_SECRET))return S(e)&&console.warn("[Gateway] X-Edge-Gate missing or invalid"),M()}let o=O(e),i=o?`${o}/health`:"/health";if(n.pathname===i)return await p(l(m({status:"healthy",timestamp:new Date().toISOString()})),t,e);let s=D(n.pathname,e);S(e)&&console.log("[Gateway] Request:",t.method,n.pathname,"\u2192",s);let c=t.headers.get("cf-connecting-ip")??"unknown",u=ft(c,s,t.method);if(!u.allowed){let g=l(a(T.RATE_LIMIT_EXCEEDED()));return g.headers.set("Retry-After",String(u.retryAfterSec)),await p(g,t,e)}let d=tt(s,e);if(!d&&rt(s))return await p(await V(t,e),t,e);let A=t.headers.get("Authorization");if(!A)return await p(l(a(f.MISSING_TOKEN())),t,e);let R=A.match(/^Bearer\s+(.+)$/i);if(!R)return await p(l(a(f.INVALID_FORMAT())),t,e);let h=R[1];if(h.length>4096)return await p(l(a(f.INVALID_FORMAT())),t,e);let _=h.split(".");if(_.length!==3)return await p(l(a(f.INVALID_FORMAT())),t,e);if(_[1].length>2048)return await p(l(a(f.INVALID_FORMAT())),t,e);let w=ct(h);if(!w)return await p(l(a(f.INVALID_FORMAT())),t,e);let I=w.typ,pt=d?3:1;if(I!==pt)return await p(l(a(f.INVALID_TOKEN_TYPE())),t,e);if(d&&w.aud!==Q(e))return await p(l(a(f.INVALID_TOKEN_TYPE())),t,e);let H=w.exp;if(typeof H=="number"&&Math.floor(Date.now()/1e3)>=H)return await p(l(a(f.TOKEN_EXPIRED())),t,e);if(d){if(!e.SERVICE_JWT_PUBLIC_KEY)return await p(l(a(T.INTERNAL_ERROR())),t,e);let g=await K(h,e.SERVICE_JWT_PUBLIC_KEY,3,z(e),q(e));if(!g.ok)return S(e)&&console.error("[Gateway] Service JWT failed:",g.errors),await p(l(a(g.errors,g.meta)),t,e)}else{let g=await K(h,e.JWT_PUBLIC_KEY,1,j(e),Z(e));if(!g.ok)return S(e)&&console.error("[Gateway] User JWT failed:",g.errors),await p(l(a(g.errors,g.meta)),t,e);let{scope:L}=x(e,s);if(L&&!et(s)&&g.data.aud!==L)return S(e)&&console.error(`[Gateway] Scope mismatch: token aud="${g.data.aud}" scope="${L}"`),await p(l(a(f.SCOPE_MISMATCH())),t,e)}return d&&Ft(s)?await p(await $t(t,e),t,e):await p(await V(t,e),t,e)}catch(n){return S(e)&&console.error("[Gateway] Unhandled error:",n),l(a(T.INTERNAL_ERROR()))}}};export{Ne as default};
|