@auth-craft/aws-cf-stack 1.6.3 → 1.7.0
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/wrangler/wrangler.toml +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 pt=typeof process<"u"&&!1;function y(t){return pt?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 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 n=e.filter(r=>!t[r]);if(n.length>0)throw new Error(`Missing required environment variables: ${n.join(", ")}`)}function J(t){return t.SKIP_EDGE_GATE==="true"}function gt(t){return t.ENVIRONMENT||"development"}function S(t){return gt(t)==="development"}function k(t){return t.ALLOWED_ORIGINS?t.ALLOWED_ORIGINS.split(",").map(e=>e.trim()).filter(Boolean):["*"]}function At(t){return t.ALLOWED_ORIGIN_SUFFIXES?t.ALLOWED_ORIGIN_SUFFIXES.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):[]}function D(t){return`origin:${t}`}async function $(t,e){if(!t)return!1;if(k(e).includes(t))return!0;let r=At(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(D(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 ht=new Set(["system","tenant","customer"]);function W(t){return t&&ht.has(t)?t:void 0}function x(t,e){let n=W(t.AUTH_SCOPE);if(n)return{scope:n,forwardPath:e};let r=W(O(t).replace(/^\//,""));if(r)return{scope:r,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 n=O(e);return n?t===n||t.startsWith(`${n}/`):!0}function Y(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 j(t){return Y(t.JWT_ALLOWED_ALGORITHMS)}function z(t){return Y(t.SERVICE_JWT_ALLOWED_ALGORITHMS)}function mt(t){let e=t.SERVICE_ROUTE_PREFIX||"/internal",n=e.startsWith("/")?e:`/${e}`;return n.endsWith("/")?n.slice(0,-1):n}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 n=t.split("?")[0].replace(/\/$/,"")||"/",r=mt(e);return n===r||n.startsWith(`${r}/`)}var St=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"]);function et(t){let e=t.split("?")[0].replace(/\/$/,"")||"/";if(e==="/"||e.startsWith("/oauth/")||e==="/oauth"||v.has(e))return!0;let n=e.match(/^\/[^/]+(\/(?:verification|password)\/[^/]+)$/);if(n?.[1]&&v.has(n[1]))return!0;let r=e.match(/^(?:\/[^/]+)?\/auth\/([^/]+)/);if(r){let o=r[1];if(o&&St.has(o)||o?.startsWith("oauth"))return!0}return!1}function rt(t){return t.GATEWAY_JWT_PRIVATE_KEY||void 0}function nt(t){return t.TID_VALUE||void 0}function ot(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")},l={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")},it={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 Tt(t){if(t.ok)return t.data===void 0?204:200;let e=t.errors?.[0];return e?.code&&e.code in it?it[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=Tt(i),c=U(i);return new Response(JSON.stringify(c),{status:s,headers:{"Content-Type":"application/json"}})}var wt=new Set(["EdDSA","ES256","RS256","PS256"]);function yt(t){return wt.has(t)}function It(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 Ot(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,"/"),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 st(t){return atob(t)}var Pt=/^[0-9a-fA-F]{64}$/;async function Dt(t,e){if(Pt.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(st(t))}catch{throw new Error(`Invalid JWK base64 for algorithm ${e}`)}return await crypto.subtle.importKey("jwk",n,It(e),!1,["verify"])}function at(t){let e=t.split(".");if(e.length!==3)return null;let n=e[1];if(!n)return null;try{let r=N(n);return JSON.parse(new TextDecoder().decode(r))}catch{return null}}function Ct(t){let n=t.split(".")[0];if(!n)return a(l.INVALID_FORMAT());try{let r=N(n);return m(JSON.parse(new TextDecoder().decode(r)))}catch{return a(l.INVALID_FORMAT())}}function xt(t){let n=t.split(".")[1];if(!n)return a(l.INVALID_FORMAT());try{let r=N(n);return m(JSON.parse(new TextDecoder().decode(r)))}catch{return a(l.INVALID_FORMAT())}}async function Nt(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=N(s);try{return await crypto.subtle.verify(Ot(n),e,u,c)}catch{return!1}}async function K(t,e,n=1,r=new Set(["EdDSA"]),o){let i=Ct(t);if(!i.ok)return a(i.errors,i.meta);let s=i.data.alg;if(!r.has(s))return a(l.UNSUPPORTED_ALGORITHM());if(!yt(s))return a(l.UNSUPPORTED_ALGORITHM());let c=xt(t);if(!c.ok)return a(c.errors,c.meta);let u=c.data;if(u.typ!==n)return a(l.INVALID_TOKEN_TYPE());if(o!==void 0&&u.iss!==o)return a(l.INVALID_ISSUER());if(u.exp&&Math.floor(Date.now()/1e3)>=u.exp)return a(l.TOKEN_EXPIRED());let d;try{d=await Dt(e,s)}catch{return a(l.INVALID_FORMAT())}return await Nt(t,d,s)?m(u):a(l.SIGNATURE_INVALID())}var b=null;async function Lt(t){let e;try{e=JSON.parse(st(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 n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function ct(t,e,n,r,o=30){b||(b=Lt(t));let i=await b,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()},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 Ut=["content-type","accept","accept-encoding","accept-language","user-agent","cookie"];function Wt(t){return t.getSetCookie?.()??[]}async function Jt(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 kt(t,e,n,r,o,i){let s=new Headers;for(let R of Ut){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 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 bt(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 Gt(t,e,n){let r=new Headers(t.headers);r.set("X-Request-Id",e);let o=Wt(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 V(t,e){let n=crypto.randomUUID();try{let r=new URL(t.url),o=C(r.pathname,e),{scope:i,forwardPath:s}=x(e,o),c=bt(t,e,s),u,d,A=nt(e);if(A&&ot(s)&&t.method==="POST"){let I=await Jt(t,A);I&&(u=I.body,d=I.contentType,S(e)&&console.log("[Gateway] Injected tid:",A,"into",s))}let R,h=rt(e);h&&(R=await ct(h,"bff-worker","auth-craft:backend","cloudflare-auth-gateway"));let _=kt(t,e.BACKEND_GATEWAY_SECRET,n,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 Gt(w,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 P=new Map,ut=0,Kt=[{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 Vt(t){let e=Date.now();if(e-ut<3e4)return;ut=e;let n=e-t;for(let[r,o]of P){let i=o.hits.filter(s=>s>n);i.length===0?P.delete(r):o.hits=i}}function ft(t,e,n){if(n!=="POST")return{allowed:!0,retryAfterSec:0};let r=Kt.find(d=>d.match(e));if(!r)return{allowed:!0,retryAfterSec:0};Vt(3e5);let o=`${r.prefix}:${t}`,i=Date.now(),s=i-r.windowMs,c=P.get(o)??{hits:[]},u=c.hits.filter(d=>d>s);if(u.length>=r.limit){let d=u[0]??i;return c.hits=u,P.set(o,c),{allowed:!1,retryAfterSec:Math.max(1,Math.ceil((d+r.windowMs-i)/1e3))}}return u.push(i),c.hits=u,P.set(o,c),{allowed:!0,retryAfterSec:0}}function lt(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 dt(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 Mt(t,e){let n=t.headers.get("Origin")??"*",r=t.headers.get("Access-Control-Request-Headers")??"";return new Response(null,{status:204,headers:{...await dt(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 dt(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 Ht(t){return t.split("?")[0].replace(/\/$/,"")===Bt}async function vt(t,e){if(!e.CORS_KV)return M();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(D(r),"1"):await e.CORS_KV.delete(D(r)),f(m({origin:r}))}return f(a(T.BAD_REQUEST()))}function M(){return new Response(null,{status:404})}var Ce={async fetch(t,e,n){try{F(e);let r=new URL(t.url);if(!X(r.pathname,e))return M();if(t.method==="OPTIONS")return await Mt(t,e);if(!J(e)){let g=t.headers.get("X-Edge-Gate");if(!g||!e.EDGE_GATE_SECRET||!lt(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(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=ft(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 d=tt(s,e);if(!d&&et(s))return await p(await V(t,e),t,e);let A=t.headers.get("Authorization");if(!A)return await p(f(a(l.MISSING_TOKEN())),t,e);let R=A.match(/^Bearer\s+(.+)$/i);if(!R)return await p(f(a(l.INVALID_FORMAT())),t,e);let h=R[1];if(h.length>4096)return await p(f(a(l.INVALID_FORMAT())),t,e);let _=h.split(".");if(_.length!==3)return await p(f(a(l.INVALID_FORMAT())),t,e);if(_[1].length>2048)return await p(f(a(l.INVALID_FORMAT())),t,e);let w=at(h);if(!w)return await p(f(a(l.INVALID_FORMAT())),t,e);let I=w.typ,Et=d?3:1;if(I!==Et)return await p(f(a(l.INVALID_TOKEN_TYPE())),t,e);if(d&&w.aud!==Q(e))return await p(f(a(l.INVALID_TOKEN_TYPE())),t,e);let H=w.exp;if(typeof H=="number"&&Math.floor(Date.now()/1e3)>=H)return await p(f(a(l.TOKEN_EXPIRED())),t,e);if(d){if(!e.SERVICE_JWT_PUBLIC_KEY)return await p(f(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(f(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(f(a(g.errors,g.meta)),t,e);let{scope:L}=x(e,s);if(L&&g.data.aud!==L)return S(e)&&console.error(`[Gateway] Scope mismatch: token aud="${g.data.aud}" scope="${L}"`),await p(f(a(l.SCOPE_MISMATCH())),t,e)}return d&&Ht(s)?await p(await vt(t,e),t,e):await p(await V(t,e),t,e)}catch(r){return S(e)&&console.error("[Gateway] Unhandled error:",r),f(a(T.INTERNAL_ERROR()))}}};export{Ce as default};
|