@authrim/server 0.1.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/README.md +610 -0
- package/dist/adapters/express.cjs +3 -0
- package/dist/adapters/express.cjs.map +1 -0
- package/dist/adapters/express.d.cts +75 -0
- package/dist/adapters/express.d.ts +75 -0
- package/dist/adapters/express.js +3 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.cjs +3 -0
- package/dist/adapters/fastify.cjs.map +1 -0
- package/dist/adapters/fastify.d.cts +101 -0
- package/dist/adapters/fastify.d.ts +101 -0
- package/dist/adapters/fastify.js +3 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/hono.cjs +2 -0
- package/dist/adapters/hono.cjs.map +1 -0
- package/dist/adapters/hono.d.cts +85 -0
- package/dist/adapters/hono.d.ts +85 -0
- package/dist/adapters/hono.js +2 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/koa.cjs +3 -0
- package/dist/adapters/koa.cjs.map +1 -0
- package/dist/adapters/koa.d.cts +75 -0
- package/dist/adapters/koa.d.ts +75 -0
- package/dist/adapters/koa.js +3 -0
- package/dist/adapters/koa.js.map +1 -0
- package/dist/adapters/nestjs.cjs +3 -0
- package/dist/adapters/nestjs.cjs.map +1 -0
- package/dist/adapters/nestjs.d.cts +126 -0
- package/dist/adapters/nestjs.d.ts +126 -0
- package/dist/adapters/nestjs.js +3 -0
- package/dist/adapters/nestjs.js.map +1 -0
- package/dist/chunk-7POGA5LZ.cjs +3 -0
- package/dist/chunk-7POGA5LZ.cjs.map +1 -0
- package/dist/chunk-N3ONRO35.js +2 -0
- package/dist/chunk-N3ONRO35.js.map +1 -0
- package/dist/chunk-O2ALCNXB.cjs +2 -0
- package/dist/chunk-O2ALCNXB.cjs.map +1 -0
- package/dist/chunk-OS567YCE.js +3 -0
- package/dist/chunk-OS567YCE.js.map +1 -0
- package/dist/chunk-TPROSFE7.cjs +2 -0
- package/dist/chunk-TPROSFE7.cjs.map +1 -0
- package/dist/chunk-XOFM2JHF.js +2 -0
- package/dist/chunk-XOFM2JHF.js.map +1 -0
- package/dist/config-I0GIVJA_.d.cts +364 -0
- package/dist/config-I0GIVJA_.d.ts +364 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +791 -0
- package/dist/index.d.ts +791 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.cjs +2 -0
- package/dist/providers/index.cjs.map +1 -0
- package/dist/providers/index.d.cts +79 -0
- package/dist/providers/index.d.ts +79 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/types-CzpMdWFR.d.cts +435 -0
- package/dist/types-D7gjcvs9.d.ts +435 -0
- package/package.json +119 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import {e,c,b as b$1,h as h$1,g,f,a as a$1}from'./chunk-OS567YCE.js';export{c as base64UrlDecode,e as base64UrlDecodeString,b as base64UrlEncode,d as base64UrlEncodeString}from'./chunk-OS567YCE.js';export{c as buildErrorHeaders,a as buildErrorResponse,b as buildWwwAuthenticateHeader}from'./chunk-XOFM2JHF.js';import {a}from'./chunk-N3ONRO35.js';export{a as AuthrimServerError,c as authenticateRequest,b as getServerErrorMeta}from'./chunk-N3ONRO35.js';function q(i){return i.use===void 0||i.use==="sig"}function B(i,e){return i.alg===void 0?true:i.alg===e}function W(i,e,r){let o=i.find(t=>t.jwk.kid===e&&q(t.jwk)&&B(t.jwk,r));return o?{key:o,error:null,needsRefresh:false}:{key:null,error:new a("jwks_key_not_found",`No key found with kid: ${e}`),needsRefresh:true}}function N(i,e){let r=i.filter(o=>q(o.jwk)&&B(o.jwk,e));return r.length===1?{key:r[0],error:null,needsRefresh:false}:r.length===0?{key:null,error:new a("jwks_key_ambiguous",`No keys found matching algorithm: ${e}`),needsRefresh:false}:{key:null,error:new a("jwks_key_ambiguous",`Multiple keys (${r.length}) match algorithm: ${e}. Token must include 'kid' header.`),needsRefresh:false}}function S(i,e){return e.kid?W(i,e.kid,e.alg):N(i,e.alg)}var x=class x{constructor(e){this.inFlight=null;this.config=e,this.cacheKey=`jwks:${e.jwksUri}`;}async getKey(e){let r=await this.getKeys(),o=S(r,e);return o.error&&o.needsRefresh&&(r=await this.fetchAndCacheKeys(),o=S(r,e)),o}async getKeys(){let e=this.config.cache.get(this.cacheKey);return e||this.fetchAndCacheKeys()}async fetchAndCacheKeys(){if(this.inFlight&&this.config.clock.nowMs()-this.inFlight.startedAt<3e4)return this.inFlight.promise;let e=this.doFetchAndCache();this.inFlight={promise:e,startedAt:this.config.clock.nowMs()};try{return await e}finally{this.inFlight=null;}}async doFetchAndCache(){try{let e=await this.config.http.fetch(this.config.jwksUri,{headers:{Accept:"application/json"}});if(!this.config.allowCrossOriginRedirect&&e.url){let n=new URL(this.config.jwksUri),a$1=new URL(e.url);if(n.host!==a$1.host)throw new a("jwks_fetch_error",`JWKS fetch was redirected to a different host: ${a$1.host} (from ${n.host}). This is blocked for security. Set allowCrossOriginRedirect: true if this is intentional.`)}if(!e.ok)throw await e.text().catch(()=>{}),new a("jwks_fetch_error",`Failed to fetch JWKS: ${e.status} ${e.statusText}`);let r=await e.json();if(!r.keys||!Array.isArray(r.keys))throw new a("jwks_fetch_error","Invalid JWKS response: missing keys array");let o=[];for(let n of r.keys)try{let a=this.determineAlgorithm(n);if(!a){this.config.onKeyImportWarning?.({kid:n.kid,kty:n.kty,alg:n.alg,reason:n.kty==="RSA"||n.kty==="EC"||n.kty==="OKP"?"unsupported_algorithm":"unknown_key_type",message:`Cannot determine algorithm for key: kty=${n.kty}, alg=${n.alg??"none"}`});continue}let s=await this.config.crypto.importJwk(n,a);o.push({jwk:n,cryptoKey:s});}catch(a){this.config.onKeyImportWarning?.({kid:n.kid,kty:n.kty,alg:n.alg,reason:"import_failed",message:`Failed to import key: ${a instanceof Error?a.message:"Unknown error"}`});continue}let t=this.parseCacheControl(e.headers.get("Cache-Control"));return this.config.cache.set(this.cacheKey,o,t),o}catch(e){throw e instanceof a?e:new a("jwks_fetch_error",`Failed to fetch JWKS: ${e instanceof Error?e.message:"Unknown error"}`,{cause:e instanceof Error?e:void 0})}}determineAlgorithm(e){return e.alg?e.alg:e.kty==="RSA"?"RS256":e.kty==="EC"?{"P-256":"ES256","P-384":"ES384","P-521":"ES512"}[e.crv]??null:e.kty==="OKP"&&e.crv==="Ed25519"?"EdDSA":null}parseCacheControl(e){if(!e)return this.config.cacheTtlMs;let r=e.match(/max-age=(\d+)/);if(r&&r[1]){let o=parseInt(r[1],10);if(!isNaN(o)&&o>0)return Math.min(o*1e3,x.MAX_CACHE_TTL_MS)}return this.config.cacheTtlMs}invalidate(){this.config.cache.delete(this.cacheKey);}};x.MAX_CACHE_TTL_MS=1440*60*1e3;var v=x;var F=8192,ie=new Set(["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA"]),h=class extends Error{constructor(){super('Algorithm "none" is not allowed');this.code="insecure_algorithm";this.name="InsecureAlgorithmError";}};function m(i){if(i.length>F)throw new Error(`JWT exceeds maximum size of ${F} bytes`);let e$1=i.split(".");if(e$1.length!==3)throw new Error("Invalid JWT format: expected 3 parts");let[r,o,t]=e$1;if(!r||!o||!t)throw new Error("Invalid JWT format: empty parts");try{let n=JSON.parse(e(r)),a=JSON.parse(e(o));if(!n||typeof n!="object")throw new Error("Invalid JWT header: must be an object");if(typeof n.alg!="string")throw new Error("Invalid JWT header: missing or invalid alg claim");if(n.alg.toLowerCase()==="none")throw new h;return {header:n,payload:a,signature:t}}catch(n){throw n instanceof h?n:new Error("Invalid JWT format")}}async function I(i,e,r){let o=m(i);if(!ie.has(o.header.alg))return null;let t=i.lastIndexOf("."),n=i.substring(0,t),a=c(o.signature),s=new TextEncoder().encode(n);return await r.verifySignature(o.header.alg,e,a,s)?o:null}function p(i,e){if(typeof globalThis.crypto<"u"&&"timingSafeEqual"in globalThis.crypto){let s=new TextEncoder,l=s.encode(i),d=s.encode(e);return l.length!==d.length?(globalThis.crypto.timingSafeEqual(l,l),false):globalThis.crypto.timingSafeEqual(l,d)}let r=new TextEncoder,o=r.encode(i),t=r.encode(e),n=Math.max(o.length,t.length),a=o.length===t.length?0:1;for(let s=0;s<n;s++){let l=o[s]??0,d=t[s]??0;a|=l^d;}return a===0}function J(i,e){let{issuer:r,audience:o,clockToleranceSeconds:t,now:n,requireExp:a,requireIat:s}=e,l=oe(i.iss,r);if(!l.valid)return l;let d=ne(i.aud,o);if(!d.valid)return d;if(i.exp!==void 0){let u=ce(i.exp,n,t);if(!u.valid)return u}else if(a)return {valid:false,error:{code:"missing_exp",message:"Missing required exp claim"}};if(i.nbf!==void 0){let u=le(i.nbf,n,t);if(!u.valid)return u}if(i.iat!==void 0){let u=de(i.iat,n,t);if(!u.valid)return u}else if(s)return {valid:false,error:{code:"missing_iat",message:"Missing required iat claim"}};return {valid:true}}function oe(i,e){if(i===void 0||i==="")return {valid:false,error:{code:"invalid_issuer",message:"Missing or empty issuer claim"}};let o=(Array.isArray(e)?e:[e]).filter(n=>n!=="");return o.length===0?{valid:false,error:{code:"invalid_issuer",message:"No valid expected issuers configured"}}:o.some(n=>p(i,n))?{valid:true}:{valid:false,error:{code:"invalid_issuer",message:`Invalid issuer: ${i}`}}}function ne(i,e){if(i===void 0)return {valid:false,error:{code:"invalid_audience",message:"Missing audience claim"}};let r=Array.isArray(e)?e:[e],o=Array.isArray(i)?i:[i],t=r.filter(s=>s!==""),n=o.filter(s=>s!=="");return t.length===0?{valid:false,error:{code:"invalid_audience",message:"No valid expected audiences configured"}}:n.length===0?{valid:false,error:{code:"invalid_audience",message:"Token has no valid audience claims"}}:n.some(s=>t.some(l=>p(s,l)))?{valid:true}:{valid:false,error:{code:"invalid_audience",message:`Invalid audience: ${n.join(", ")}`}}}var se=3250368e4,ae=0;function M(i){return typeof i!="number"||!Number.isFinite(i)?false:i>=ae&&i<=se}function ce(i,e,r){return M(i)?i+r<e?{valid:false,error:{code:"token_expired",message:`Token expired at ${new Date(i*1e3).toISOString()}`}}:{valid:true}:{valid:false,error:{code:"invalid_exp",message:"Invalid exp claim: must be a number"}}}function le(i,e,r){return M(i)?i-r>e?{valid:false,error:{code:"token_not_yet_valid",message:`Token not valid until ${new Date(i*1e3).toISOString()}`}}:{valid:true}:{valid:false,error:{code:"invalid_nbf",message:"Invalid nbf claim: must be a number"}}}function de(i,e,r){return M(i)?i-r>e?{valid:false,error:{code:"iat_in_future",message:`Token issued in the future: ${new Date(i*1e3).toISOString()}`}}:{valid:true}:{valid:false,error:{code:"invalid_iat",message:"Invalid iat claim: must be a number"}}}function V(i,e){if(i!==void 0)return Math.max(0,i-e)}var k=class{constructor(e){this.config=e;}async validate(e){try{let r;try{r=m(e);}catch(d){return {data:null,error:{code:"token_malformed",message:d instanceof Error?d.message:"Invalid JWT format"}}}let o=await this.config.jwksManager.getKey(r.header);if(o.error)return {data:null,error:{code:o.error.code,message:o.error.message}};if(!o.key)return {data:null,error:{code:"jwks_key_not_found",message:"No suitable key found in JWKS"}};let t=await I(e,o.key.cryptoKey,this.config.crypto);if(!t)return {data:null,error:{code:"signature_invalid",message:"JWT signature verification failed"}};let n=this.config.clock.nowSeconds(),a=J(t.payload,{issuer:this.config.options.issuer,audience:this.config.options.audience,clockToleranceSeconds:this.config.options.clockToleranceSeconds??60,now:n});if(!a.valid&&a.error)return {data:null,error:a.error};if(this.config.options.requiredScopes?.length){let d=this.validateScopes(t.payload.scope,this.config.options.requiredScopes);if(!d.valid&&d.error)return {data:null,error:d.error}}let s=t.payload.cnf?.jkt?"DPoP":"Bearer";return {data:{claims:t.payload,token:e,tokenType:s,expiresIn:V(t.payload.exp,n)},error:null}}catch(r){return r instanceof a?{data:null,error:{code:r.code,message:r.message}}:{data:null,error:{code:"invalid_token",message:r instanceof Error?r.message:"Token validation failed"}}}}validateScopes(e,r){if(!e)return {valid:false,error:{code:"insufficient_scope",message:`Missing required scopes: ${r.join(" ")}`}};let o=e.split(" "),t=r.filter(n=>!o.includes(n));return t.length>0?{valid:false,error:{code:"insufficient_scope",message:`Missing required scopes: ${t.join(" ")}`}}:{valid:true}}};function b(i,e){let r=encodeURIComponent(i),o=encodeURIComponent(e),t=`${r}:${o}`,a=new TextEncoder().encode(t),s="";for(let l of a)s+=String.fromCharCode(l);return btoa(s)}var w=class{constructor(e){this.config=e;}async introspect(e){let r=new URLSearchParams;r.set("token",e.token),e.token_type_hint&&r.set("token_type_hint",e.token_type_hint);let o=b(this.config.clientId,this.config.clientSecret);try{let t=await this.config.http.fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:`Basic ${o}`,Accept:"application/json"},body:r.toString()});if(!t.ok)throw await t.text().catch(()=>{}),new a("introspection_error",`Introspection request failed: ${t.status} ${t.statusText}`);return await t.json()}catch(t){throw t instanceof a?t:new a("introspection_error",`Introspection request failed: ${t instanceof Error?t.message:"Unknown error"}`,{cause:t instanceof Error?t:void 0})}}};var C=class{constructor(e){this.config=e;}async revoke(e){let r=new URLSearchParams;r.set("token",e.token),e.token_type_hint&&r.set("token_type_hint",e.token_type_hint);let o=b(this.config.clientId,this.config.clientSecret);try{let t=await this.config.http.fetch(this.config.endpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Authorization:`Basic ${o}`},body:r.toString()});if(!t.ok)throw await t.text().catch(()=>{}),new a("revocation_error",`Revocation request failed: ${t.status} ${t.statusText}`)}catch(t){throw t instanceof a?t:new a("revocation_error",`Revocation request failed: ${t instanceof Error?t.message:"Unknown error"}`,{cause:t instanceof Error?t:void 0})}}};async function A(i,e){return e.calculateThumbprint(i)}async function ue(i,e,r){return await A(i,r)===e}var pe=new Set(["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA"]),fe=new Set(["d","p","q","dp","dq","qi","k"]);function me(i){let e$1=i.split(".");if(e$1.length!==3)throw new Error("Invalid DPoP proof format: expected 3 parts");let[r,o,t]=e$1;if(!r||!o||!t)throw new Error("Invalid DPoP proof format: empty parts");let n=JSON.parse(e(r)),a=JSON.parse(e(o));return {header:n,payload:a,signature:t}}function z(i){try{let e=new URL(i);return `${e.protocol}//${e.host}${e.pathname}`}catch{return null}}var P=class{constructor(e,r){this.crypto=e;this.clock=r;}async validate(e,r){try{let{header:o,payload:t}=me(e),n=this.validateHeader(o);if(!n.valid)return n;let a=this.validatePayloadStructure(t);if(!a.valid)return a;let s=await this.verifySignature(e,o);if(!s.valid)return s;if(t.htm.toUpperCase()!==r.method.toUpperCase())return {valid:!1,errorCode:"dpop_method_mismatch",errorMessage:`Method mismatch: expected ${r.method}, got ${t.htm}`};let l=z(r.uri),d=z(t.htu);if(l===null)return {valid:!1,errorCode:"dpop_proof_invalid",errorMessage:`Invalid expected URI: ${r.uri}`};if(d===null)return {valid:!1,errorCode:"dpop_uri_mismatch",errorMessage:`Invalid htu claim: ${t.htu}`};if(l!==d)return {valid:!1,errorCode:"dpop_uri_mismatch",errorMessage:`URI mismatch: expected ${l}, got ${d}`};let u=r.maxAge??60,f=r.clockTolerance??60,U=this.clock.nowSeconds(),G=U-u-f,X=U+f;if(t.iat<G||t.iat>X)return {valid:!1,errorCode:"dpop_iat_expired",errorMessage:"Proof iat is outside acceptable window"};if(r.expectedNonce!==void 0&&t.nonce!==r.expectedNonce)return {valid:!1,errorCode:"dpop_nonce_required",errorMessage:"Invalid or missing DPoP nonce"};let K=await A(o.jwk,this.crypto);if(r.accessToken){let D=await this.validateAccessTokenHash(t.ath,r.accessToken);if(!D.valid)return D}return r.expectedThumbprint&&!p(K,r.expectedThumbprint)?{valid:!1,errorCode:"dpop_binding_mismatch",errorMessage:"DPoP proof key does not match token binding"}:{valid:!0,thumbprint:K}}catch(o){return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:o instanceof Error?o.message:"Invalid DPoP proof"}}}validateHeader(e){if(e.typ!=="dpop+jwt")return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Invalid DPoP proof type: must be dpop+jwt"};if(!pe.has(e.alg))return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:`Unsupported algorithm: ${e.alg}`};if(!e.jwk)return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing JWK in DPoP proof header"};let r=e.jwk;for(let o of fe)if(o in r&&r[o]!==void 0)return {valid:false,errorCode:"dpop_proof_invalid",errorMessage:"DPoP proof JWK contains private key parameters"};return {valid:true}}validatePayloadStructure(e){return e.jti?e.htm?e.htu?typeof e.iat!="number"?{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing or invalid iat claim"}:{valid:true}:{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing htu claim"}:{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing htm claim"}:{valid:false,errorCode:"dpop_proof_invalid",errorMessage:"Missing jti claim"}}async verifySignature(e,r){try{let o=await this.crypto.importJwk(r.jwk,r.alg),t=e.lastIndexOf("."),n=e.substring(0,t),a=e.substring(t+1),s=new TextEncoder().encode(n),l=c(a);return await this.crypto.verifySignature(r.alg,o,l,s)?{valid:!0}:{valid:!1,errorCode:"dpop_proof_signature_invalid",errorMessage:"DPoP proof signature verification failed"}}catch(o){return {valid:false,errorCode:"dpop_proof_signature_invalid",errorMessage:`Failed to verify DPoP proof: ${o instanceof Error?o.message:"Unknown error"}`}}}async validateAccessTokenHash(e,r){if(!e)return {valid:false,errorCode:"dpop_ath_missing",errorMessage:"Missing ath claim when access token is present"};let o=new TextEncoder().encode(r),t=await this.crypto.sha256(o),n=b$1(t);return p(e,n)?{valid:true}:{valid:false,errorCode:"dpop_ath_mismatch",errorMessage:"Access token hash mismatch"}}};function _(i,e,r){if(!(!r||!i))try{if(new URL(i).protocol!=="https:")throw new a("configuration_error",`${e} must use HTTPS: ${i}. Set requireHttps: false to allow HTTP in development.`)}catch(o){throw o instanceof a?o:new a("configuration_error",`Invalid ${e} URL: ${i}`)}}function ge(i){let e=Array.isArray(i.issuer)?i.issuer:[i.issuer],r=Array.isArray(i.audience)?i.audience:[i.audience],o=i.requireHttps??true;for(let t of e)_(t,"issuer",o);return _(i.jwksUri,"jwksUri",o),_(i.introspectionEndpoint,"introspectionEndpoint",o),_(i.revocationEndpoint,"revocationEndpoint",o),{issuer:e,audience:r,jwksUri:i.jwksUri,clockToleranceSeconds:i.clockToleranceSeconds??60,jwksRefreshIntervalMs:i.jwksRefreshIntervalMs??36e5,introspectionEndpoint:i.introspectionEndpoint,revocationEndpoint:i.revocationEndpoint,clientCredentials:i.clientCredentials,http:i.http??a$1(),crypto:i.crypto??f(),clock:i.clock??g(),jwksCache:i.jwksCache??h$1({ttlMs:i.jwksRefreshIntervalMs??36e5}),requireHttps:o}}var T=class{constructor(e){this.jwksManager=null;this.tokenValidator=null;this.dpopValidator=null;this.introspectionClient=null;this.revocationClient=null;this.initPromise=null;this.initialized=false;this.config=ge(e);}async init(){if(!this.initialized){if(this.initPromise)return this.initPromise;this.initPromise=this.doInit();try{await this.initPromise,this.initialized=!0;}catch(e){throw this.initPromise=null,e}}}async doInit(){let e=this.config.jwksUri;e||(e=await this.discoverJwksUri()),this.jwksManager=new v({jwksUri:e,cacheTtlMs:this.config.jwksRefreshIntervalMs,http:this.config.http,crypto:this.config.crypto,clock:this.config.clock,cache:this.config.jwksCache}),this.tokenValidator=new k({jwksManager:this.jwksManager,crypto:this.config.crypto,clock:this.config.clock,options:{issuer:this.config.issuer,audience:this.config.audience,clockToleranceSeconds:this.config.clockToleranceSeconds}}),this.dpopValidator=new P(this.config.crypto,this.config.clock),this.config.introspectionEndpoint&&this.config.clientCredentials&&(this.introspectionClient=new w({endpoint:this.config.introspectionEndpoint,clientId:this.config.clientCredentials.clientId,clientSecret:this.config.clientCredentials.clientSecret,http:this.config.http})),this.config.revocationEndpoint&&this.config.clientCredentials&&(this.revocationClient=new C({endpoint:this.config.revocationEndpoint,clientId:this.config.clientCredentials.clientId,clientSecret:this.config.clientCredentials.clientSecret,http:this.config.http}));}async discoverJwksUri(){let e=this.config.issuer[0];if(!e)throw new a("configuration_error","No issuer configured");let r=`${e.replace(/\/$/,"")}/.well-known/openid-configuration`;try{let o=await this.config.http.fetch(r,{headers:{Accept:"application/json"}});if(!o.ok)throw await o.text().catch(()=>{}),new a("configuration_error",`Failed to fetch OpenID Configuration: ${o.status}`);let t=await o.json();if(!t.jwks_uri)throw new a("configuration_error","OpenID Configuration missing jwks_uri");return _(t.jwks_uri,"discovered jwks_uri",this.config.requireHttps),t.jwks_uri}catch(o){throw o instanceof a?o:new a("configuration_error",`Failed to discover JWKS URI: ${o instanceof Error?o.message:"Unknown error"}`,{cause:o instanceof Error?o:void 0})}}async validateToken(e){return await this.init(),this.tokenValidator?this.tokenValidator.validate(e):{data:null,error:{code:"configuration_error",message:"Token validator not initialized"}}}async validateDPoP(e,r){return await this.init(),this.dpopValidator?this.dpopValidator.validate(e,r):{valid:false,errorCode:"configuration_error",errorMessage:"DPoP validator not initialized"}}async introspect(e,r){if(await this.init(),!this.introspectionClient)throw new a("configuration_error","Introspection endpoint not configured");return this.introspectionClient.introspect({token:e,token_type_hint:r})}async revoke(e,r){if(await this.init(),!this.revocationClient)throw new a("configuration_error","Revocation endpoint not configured");return this.revocationClient.revoke({token:e,token_type_hint:r})}getConfig(){return this.config}invalidateJwksCache(){this.jwksManager?.invalidate();}};function he(i){return new T(i)}var R="http://schemas.openid.net/event/backchannel-logout",E=class{validate(e,r){let o,t;try{let f=m(e);o=f.header,t=f.payload;}catch(f){return f instanceof h?{valid:false,error:'Algorithm "none" is not allowed for logout tokens',errorCode:"insecure_algorithm"}:{valid:false,error:"Invalid JWT format",errorCode:"invalid_format"}}if(!t.iss||!p(t.iss,r.issuer))return {valid:false,error:"Issuer validation failed",errorCode:"invalid_issuer"};if(!(Array.isArray(t.aud)?t.aud:[t.aud]).some(f=>p(f,r.audience)))return {valid:false,error:"Audience validation failed",errorCode:"invalid_audience"};if(!t.events||typeof t.events!="object")return {valid:false,error:"Missing or invalid events claim",errorCode:"invalid_events"};let s=t.events[R];if(s===void 0)return {valid:false,error:`Missing ${R} in events claim`,errorCode:"invalid_events"};if(typeof s!="object"||s===null||Array.isArray(s)||Object.keys(s).length!==0)return {valid:false,error:"Back-channel logout event must be an empty object",errorCode:"invalid_events"};if(!t.jti)return {valid:false,error:"Missing jti claim",errorCode:"missing_jti"};if(!t.sub&&!t.sid)return {valid:false,error:"Logout token must contain either sub or sid (or both)",errorCode:"missing_sub_and_sid"};if("nonce"in t&&t.nonce!==void 0)return {valid:false,error:"Logout token must not contain nonce claim",errorCode:"nonce_present"};let l=Math.floor(Date.now()/1e3),d=r.maxAge??60,u=r.clockSkew??30;return t.exp?l>t.exp+u?{valid:false,error:`Token expired at ${t.exp}`,errorCode:"token_expired"}:t.iat&&l-t.iat>d+u?{valid:false,error:`Token too old: iat ${t.iat} is more than ${d} seconds ago`,errorCode:"iat_too_old"}:t.iat&&t.iat>l+u?{valid:false,error:`Token iat is in the future: ${t.iat}`,errorCode:"not_yet_valid"}:r.expectedSub&&t.sub&&!p(t.sub,r.expectedSub)?{valid:false,error:"Subject validation failed",errorCode:"sub_mismatch"}:r.expectedSid&&t.sid&&!p(t.sid,r.expectedSid)?{valid:false,error:"Session ID validation failed",errorCode:"sid_mismatch"}:{valid:true,claims:t,header:o}:{valid:false,error:"Missing exp claim",errorCode:"token_expired"}}extractClaims(e){try{return m(e).payload}catch{return null}}extractHeader(e){try{return m(e).header}catch{return null}}};
|
|
2
|
+
export{T as AuthrimServer,R as BACKCHANNEL_LOGOUT_EVENT,E as BackChannelLogoutValidator,P as DPoPValidator,w as IntrospectionClient,v as JwksManager,C as RevocationClient,k as TokenValidator,A as calculateJwkThumbprint,he as createAuthrimServer,V as getExpiresIn,m as parseJwt,S as selectKey,N as selectKeyByAlgorithm,W as selectKeyByKid,J as validateClaims,ue as verifyJwkThumbprint,I as verifyJwtSignature};//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/jwks/key-selector.ts","../src/jwks/manager.ts","../src/token/verify-jwt.ts","../src/utils/timing-safe.ts","../src/token/validate-claims.ts","../src/token/validator.ts","../src/utils/auth.ts","../src/token/introspection.ts","../src/token/revocation.ts","../src/dpop/thumbprint.ts","../src/dpop/validator.ts","../src/core/client.ts","../src/session/back-channel-logout.ts"],"names":["isSigningKey","jwk","algorithmMatches","tokenAlg","selectKeyByKid","keys","kid","alg","matchingKey","k","AuthrimServerError","selectKeyByAlgorithm","matchingKeys","selectKey","header","_JwksManager","config","result","cached","promise","response","requestedUrl","responseUrl","jwks","cachedKeys","cryptoKey","error","cacheTtl","maxAgeMatch","maxAge","JwksManager","MAX_JWT_SIZE","SUPPORTED_ALGORITHMS","InsecureAlgorithmError","parseJwt","token","parts","headerPart","payloadPart","signaturePart","headerRaw","base64UrlDecodeString","payload","verifyJwtSignature","key","crypto","parsed","dotIndex","signingInput","signatureBytes","base64UrlDecode","data","timingSafeEqual","a","b","encoder","bufA","bufB","maxLen","i","byteA","byteB","validateClaims","options","issuer","audience","clockToleranceSeconds","now","requireExp","requireIat","issuerResult","validateIssuer","audienceResult","validateAudience","expResult","validateExpiration","nbfResult","validateNotBefore","iatResult","validateIssuedAt","iss","expectedIssuers","validIssuers","expectedIss","aud","expectedAudiences","audiences","tokenAudiences","validAudiences","validTokenAudiences","tokenAud","expectedAud","MAX_REASONABLE_TIMESTAMP","MIN_REASONABLE_TIMESTAMP","isValidNumericClaim","value","exp","tolerance","nbf","iat","getExpiresIn","TokenValidator","keyResult","verified","claimsResult","scopeResult","tokenType","tokenScope","requiredScopes","tokenScopes","missingScopes","s","encodeBasicCredentials","clientId","clientSecret","encodedId","encodedSecret","credentials","credentialsBytes","binary","byte","IntrospectionClient","request","body","RevocationClient","calculateJwkThumbprint","verifyJwkThumbprint","expectedThumbprint","PRIVATE_KEY_PARAMS","parseDPoPProof","proof","normalizeUri","uri","url","DPoPValidator","clock","headerResult","payloadResult","signatureResult","expectedUri","actualUri","iatMin","iatMax","thumbprint","athResult","jwkObj","param","signatureB64","signature","ath","accessToken","tokenBytes","hash","expectedAth","base64UrlEncode","validateHttps","name","requireHttps","resolveConfig","fetchHttpProvider","webCryptoProvider","systemClock","memoryCache","AuthrimServer","jwksUri","configUrl","tokenTypeHint","createAuthrimServer","BACKCHANNEL_LOGOUT_EVENT","BackChannelLogoutValidator","logoutToken","claims","decoded","logoutEvent","clockSkew"],"mappings":"ocA+CA,SAASA,CAAAA,CAAaC,CAAAA,CAAyB,CAE7C,OAAOA,CAAAA,CAAI,MAAQ,MAAA,EAAaA,CAAAA,CAAI,GAAA,GAAQ,KAC9C,CASA,SAASC,EAAiBD,CAAAA,CAAgBE,CAAAA,CAA2B,CAEnE,OAAIF,CAAAA,CAAI,GAAA,GAAQ,MAAA,CACP,IAAA,CAGFA,CAAAA,CAAI,GAAA,GAAQE,CACrB,CAUO,SAASC,CAAAA,CACdC,EACAC,CAAAA,CACAC,CAAAA,CACoB,CAEpB,IAAMC,CAAAA,CAAcH,CAAAA,CAAK,IAAA,CACtBI,CAAAA,EAAMA,CAAAA,CAAE,GAAA,CAAI,GAAA,GAAQH,CAAAA,EAAON,CAAAA,CAAaS,CAAAA,CAAE,GAAG,CAAA,EAAKP,CAAAA,CAAiBO,CAAAA,CAAE,GAAA,CAAKF,CAAG,CAChF,CAAA,CAEA,OAAIC,CAAAA,CACK,CAAE,GAAA,CAAKA,CAAAA,CAAa,KAAA,CAAO,IAAA,CAAM,aAAc,KAAM,CAAA,CAIvD,CACL,GAAA,CAAK,IAAA,CACL,KAAA,CAAO,IAAIE,CAAAA,CACT,oBAAA,CACA,CAAA,uBAAA,EAA0BJ,CAAG,CAAA,CAC/B,CAAA,CACA,YAAA,CAAc,IAChB,CACF,CASO,SAASK,CAAAA,CACdN,CAAAA,CACAE,CAAAA,CACoB,CAEpB,IAAMK,CAAAA,CAAeP,CAAAA,CAAK,MAAA,CACvBI,CAAAA,EAAMT,CAAAA,CAAaS,CAAAA,CAAE,GAAG,CAAA,EAAKP,CAAAA,CAAiBO,CAAAA,CAAE,GAAA,CAAKF,CAAG,CAC3D,CAAA,CAEA,OAAIK,CAAAA,CAAa,MAAA,GAAW,CAAA,CACnB,CAAE,GAAA,CAAKA,CAAAA,CAAa,CAAC,CAAA,CAAI,KAAA,CAAO,IAAA,CAAM,YAAA,CAAc,KAAM,CAAA,CAG/DA,CAAAA,CAAa,MAAA,GAAW,CAAA,CACnB,CACL,GAAA,CAAK,IAAA,CACL,KAAA,CAAO,IAAIF,EACT,oBAAA,CACA,CAAA,kCAAA,EAAqCH,CAAG,CAAA,CAC1C,CAAA,CACA,YAAA,CAAc,KAChB,CAAA,CAIK,CACL,GAAA,CAAK,IAAA,CACL,KAAA,CAAO,IAAIG,EACT,oBAAA,CACA,CAAA,eAAA,EAAkBE,CAAAA,CAAa,MAAM,CAAA,mBAAA,EAAsBL,CAAG,CAAA,kCAAA,CAChE,CAAA,CACA,YAAA,CAAc,KAChB,CACF,CASO,SAASM,CAAAA,CACdR,EACAS,CAAAA,CACoB,CACpB,OAAIA,CAAAA,CAAO,GAAA,CACFV,CAAAA,CAAeC,CAAAA,CAAMS,CAAAA,CAAO,GAAA,CAAKA,CAAAA,CAAO,GAAG,CAAA,CAG7CH,CAAAA,CAAqBN,CAAAA,CAAMS,EAAO,GAAG,CAC9C,CChFO,IAAMC,CAAAA,CAAN,MAAMA,CAAY,CAKvB,WAAA,CAAYC,CAAAA,CAA2B,CAFvC,IAAA,CAAQ,QAAA,CAAmC,IAAA,CAGzC,KAAK,MAAA,CAASA,CAAAA,CACd,IAAA,CAAK,QAAA,CAAW,CAAA,KAAA,EAAQA,CAAAA,CAAO,OAAO,CAAA,EACxC,CAUA,MAAM,MAAA,CAAOF,CAAAA,CAAgD,CAE3D,IAAIT,EAAO,MAAM,IAAA,CAAK,OAAA,EAAQ,CAG1BY,CAAAA,CAASJ,CAAAA,CAAUR,CAAAA,CAAMS,CAAM,CAAA,CAGnC,OAAIG,CAAAA,CAAO,KAAA,EAASA,CAAAA,CAAO,YAAA,GAEzBZ,EAAO,MAAM,IAAA,CAAK,iBAAA,EAAkB,CACpCY,CAAAA,CAASJ,CAAAA,CAAUR,CAAAA,CAAMS,CAAM,CAAA,CAAA,CAG1BG,CACT,CAKA,MAAc,OAAA,EAAgC,CAE5C,IAAMC,CAAAA,CAAS,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,IAAA,CAAK,QAAQ,CAAA,CAClD,OAAIA,CAAAA,EAKG,IAAA,CAAK,iBAAA,EACd,CAOA,MAAc,iBAAA,EAA0C,CAEtD,GAAI,IAAA,CAAK,QAAA,EACK,IAAA,CAAK,OAAO,KAAA,CAAM,KAAA,EAAM,CAAI,IAAA,CAAK,QAAA,CAAS,SAAA,CAE5C,IACR,OAAO,IAAA,CAAK,QAAA,CAAS,OAAA,CAMzB,IAAMC,CAAAA,CAAU,IAAA,CAAK,eAAA,EAAgB,CACrC,IAAA,CAAK,QAAA,CAAW,CACd,OAAA,CAAAA,CAAAA,CACA,UAAW,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,KAAA,EAC/B,CAAA,CAEA,GAAI,CAEF,OADa,MAAMA,CAErB,CAAA,OAAE,CACA,IAAA,CAAK,SAAW,KAClB,CACF,CAKA,MAAc,eAAA,EAAwC,CACpD,GAAI,CACF,IAAMC,CAAAA,CAAW,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAS,CACjE,OAAA,CAAS,CACP,MAAA,CAAQ,kBACV,CACF,CAAC,CAAA,CAID,GAAI,CAAC,IAAA,CAAK,OAAO,wBAAA,EAA4BA,CAAAA,CAAS,GAAA,CAAK,CACzD,IAAMC,CAAAA,CAAe,IAAI,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,CAC1CC,GAAAA,CAAc,IAAI,IAAIF,CAAAA,CAAS,GAAG,CAAA,CAExC,GAAIC,CAAAA,CAAa,IAAA,GAASC,GAAAA,CAAY,IAAA,CACpC,MAAM,IAAIZ,CAAAA,CACR,kBAAA,CACA,CAAA,+CAAA,EAAkDY,GAAAA,CAAY,IAAI,CAAA,OAAA,EAAUD,CAAAA,CAAa,IAAI,CAAA,2FAAA,CAE/F,CAEJ,CAEA,GAAI,CAACD,CAAAA,CAAS,EAAA,CAEZ,MAAA,MAAMA,CAAAA,CAAS,IAAA,EAAK,CAAE,MAAM,IAAM,CAAC,CAAC,CAAA,CAC9B,IAAIV,CAAAA,CACR,mBACA,CAAA,sBAAA,EAAyBU,CAAAA,CAAS,MAAM,CAAA,CAAA,EAAIA,CAAAA,CAAS,UAAU,EACjE,CAAA,CAGF,IAAMG,CAAAA,CAAQ,MAAMH,CAAAA,CAAS,IAAA,EAAK,CAElC,GAAI,CAACG,CAAAA,CAAK,IAAA,EAAQ,CAAC,KAAA,CAAM,OAAA,CAAQA,EAAK,IAAI,CAAA,CACxC,MAAM,IAAIb,CAAAA,CACR,kBAAA,CACA,2CACF,CAAA,CAIF,IAAMc,CAAAA,CAA0B,EAAC,CACjC,IAAA,IAAWvB,CAAAA,IAAOsB,EAAK,IAAA,CACrB,GAAI,CAEF,IAAMhB,CAAAA,CAAM,IAAA,CAAK,kBAAA,CAAmBN,CAAG,CAAA,CACvC,GAAI,CAACM,CAAAA,CAAK,CAER,IAAA,CAAK,OAAO,kBAAA,GAAqB,CAC/B,GAAA,CAAKN,CAAAA,CAAI,GAAA,CACT,GAAA,CAAKA,CAAAA,CAAI,GAAA,CACT,GAAA,CAAKA,CAAAA,CAAI,GAAA,CACT,MAAA,CAAQA,CAAAA,CAAI,GAAA,GAAQ,OAASA,CAAAA,CAAI,GAAA,GAAQ,IAAA,EAAQA,CAAAA,CAAI,GAAA,GAAQ,KAAA,CACzD,uBAAA,CACA,kBAAA,CACJ,OAAA,CAAS,CAAA,wCAAA,EAA2CA,CAAAA,CAAI,GAAG,CAAA,MAAA,EAASA,CAAAA,CAAI,KAAO,MAAM,CAAA,CACvF,CAAC,CAAA,CACD,QACF,CAEA,IAAMwB,CAAAA,CAAY,MAAM,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,SAAA,CAAUxB,CAAAA,CAAmBM,CAAG,CAAA,CAC3EiB,CAAAA,CAAW,IAAA,CAAK,CAAE,GAAA,CAAAvB,CAAAA,CAAK,SAAA,CAAAwB,CAAU,CAAC,EACpC,CAAA,MAASC,CAAAA,CAAO,CAEd,IAAA,CAAK,OAAO,kBAAA,GAAqB,CAC/B,GAAA,CAAKzB,CAAAA,CAAI,GAAA,CACT,GAAA,CAAKA,EAAI,GAAA,CACT,GAAA,CAAKA,CAAAA,CAAI,GAAA,CACT,MAAA,CAAQ,eAAA,CACR,QAAS,CAAA,sBAAA,EAAyByB,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,eAAe,CAAA,CAC5F,CAAC,CAAA,CACD,QACF,CAIF,IAAMC,CAAAA,CAAW,IAAA,CAAK,kBAAkBP,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,eAAe,CAAC,CAAA,CAG7E,OAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,IAAA,CAAK,QAAA,CAAUI,CAAAA,CAAYG,CAAQ,EAElDH,CACT,CAAA,MAASE,CAAAA,CAAO,CACd,MAAIA,CAAAA,YAAiBhB,CAAAA,CACbgB,CAAAA,CAEF,IAAIhB,CAAAA,CACR,kBAAA,CACA,CAAA,sBAAA,EAAyBgB,CAAAA,YAAiB,KAAA,CAAQA,EAAM,OAAA,CAAU,eAAe,CAAA,CAAA,CACjF,CAAE,KAAA,CAAOA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAQ,MAAU,CACtD,CACF,CACF,CAKQ,kBAAA,CAAmBzB,EAA+B,CAExD,OAAIA,CAAAA,CAAI,GAAA,CACCA,CAAAA,CAAI,GAAA,CAITA,CAAAA,CAAI,GAAA,GAAQ,KAAA,CACP,OAAA,CAGLA,CAAAA,CAAI,GAAA,GAAQ,IAAA,CAC8B,CAC1C,QAAS,OAAA,CACT,OAAA,CAAS,OAAA,CACT,OAAA,CAAS,OACX,CAAA,CACmBA,CAAAA,CAAI,GAAG,CAAA,EAAK,IAAA,CAG7BA,CAAAA,CAAI,GAAA,GAAQ,KAAA,EAASA,CAAAA,CAAI,MAAQ,SAAA,CAC5B,OAAA,CAGF,IACT,CAaQ,iBAAA,CAAkBa,CAAAA,CAA+B,CACvD,GAAI,CAACA,CAAAA,CACH,OAAO,IAAA,CAAK,MAAA,CAAO,UAAA,CAIrB,IAAMc,CAAAA,CAAcd,CAAAA,CAAO,KAAA,CAAM,eAAe,CAAA,CAChD,GAAIc,GAAeA,CAAAA,CAAY,CAAC,CAAA,CAAG,CACjC,IAAMC,CAAAA,CAAS,SAASD,CAAAA,CAAY,CAAC,CAAA,CAAG,EAAE,CAAA,CAC1C,GAAI,CAAC,KAAA,CAAMC,CAAM,CAAA,EAAKA,CAAAA,CAAS,CAAA,CAG7B,OADiB,IAAA,CAAK,IAAIA,CAAAA,CAAS,GAAA,CAAMd,CAAAA,CAAY,gBAAgB,CAGzE,CAEA,OAAO,IAAA,CAAK,MAAA,CAAO,UACrB,CAKA,UAAA,EAAmB,CACjB,IAAA,CAAK,OAAO,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,QAAQ,EACxC,CACF,CAAA,CApPaA,CAAAA,CAmNa,gBAAA,CAAmB,IAAA,CAAU,EAAA,CAAK,GAAA,CAnNrD,IAAMe,CAAAA,CAANf,EC9DP,IAAMgB,CAAAA,CAAe,IAAA,CAKfC,EAAAA,CAAuB,IAAI,GAAA,CAAI,CACnC,OAAA,CAAS,OAAA,CAAS,OAAA,CAClB,OAAA,CAAS,OAAA,CAAS,OAAA,CAClB,OAAA,CAAS,QAAS,OAAA,CAClB,OACF,CAAC,CAAA,CAQYC,CAAAA,CAAN,cAAqC,KAAM,CAGhD,WAAA,EAAc,CACZ,KAAA,CAAM,iCAAiC,CAAA,CAHzC,IAAA,CAAS,KAAO,oBAAA,CAId,IAAA,CAAK,IAAA,CAAO,yBACd,CACF,CAAA,CAUO,SAASC,CAAAA,CAAsCC,CAAAA,CAA6B,CAEjF,GAAIA,CAAAA,CAAM,MAAA,CAASJ,CAAAA,CACjB,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+BA,CAAY,CAAA,MAAA,CAAQ,CAAA,CAGrE,IAAMK,GAAAA,CAAQD,CAAAA,CAAM,KAAA,CAAM,GAAG,CAAA,CAE7B,GAAIC,GAAAA,CAAM,SAAW,CAAA,CACnB,MAAM,IAAI,KAAA,CAAM,sCAAsC,CAAA,CAGxD,GAAM,CAACC,CAAAA,CAAYC,CAAAA,CAAaC,CAAa,CAAA,CAAIH,GAAAA,CAEjD,GAAI,CAACC,CAAAA,EAAc,CAACC,CAAAA,EAAe,CAACC,CAAAA,CAClC,MAAM,IAAI,KAAA,CAAM,iCAAiC,CAAA,CAGnD,GAAI,CACF,IAAMC,EAAY,IAAA,CAAK,KAAA,CAAMC,CAAAA,CAAsBJ,CAAU,CAAC,CAAA,CACxDK,CAAAA,CAAU,IAAA,CAAK,KAAA,CAAMD,CAAAA,CAAsBH,CAAW,CAAC,CAAA,CAG7D,GAAI,CAACE,CAAAA,EAAa,OAAOA,CAAAA,EAAc,QAAA,CACrC,MAAM,IAAI,KAAA,CAAM,uCAAuC,CAAA,CAIzD,GAAI,OAAOA,CAAAA,CAAU,GAAA,EAAQ,QAAA,CAC3B,MAAM,IAAI,KAAA,CAAM,kDAAkD,CAAA,CAKpE,GAAIA,CAAAA,CAAU,GAAA,CAAI,WAAA,EAAY,GAAM,MAAA,CAClC,MAAM,IAAIP,CAAAA,CAKZ,OAAO,CACL,MAAA,CAHaO,CAAAA,CAIb,OAAA,CAAAE,CAAAA,CACA,SAAA,CAAWH,CACb,CACF,CAAA,MAASb,CAAAA,CAAO,CAEd,MAAIA,CAAAA,YAAiBO,CAAAA,CACbP,CAAAA,CAGF,IAAI,KAAA,CAAM,oBAAoB,CACtC,CACF,CAUA,eAAsBiB,CAAAA,CACpBR,CAAAA,CACAS,CAAAA,CACAC,CAAAA,CAC8B,CAE9B,IAAMC,CAAAA,CAASZ,CAAAA,CAAYC,CAAK,CAAA,CAGhC,GAAI,CAACH,EAAAA,CAAqB,GAAA,CAAIc,CAAAA,CAAO,MAAA,CAAO,GAAG,CAAA,CAC7C,OAAO,IAAA,CAIT,IAAMC,CAAAA,CAAWZ,CAAAA,CAAM,YAAY,GAAG,CAAA,CAChCa,CAAAA,CAAeb,CAAAA,CAAM,SAAA,CAAU,CAAA,CAAGY,CAAQ,CAAA,CAC1CE,CAAAA,CAAiBC,CAAAA,CAAgBJ,CAAAA,CAAO,SAAS,CAAA,CAGjDK,CAAAA,CAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAOH,CAAY,CAAA,CAQlD,OAPc,MAAMH,CAAAA,CAAO,eAAA,CACzBC,CAAAA,CAAO,MAAA,CAAO,GAAA,CACdF,CAAAA,CACAK,CAAAA,CACAE,CACF,CAAA,CAEeL,CAAAA,CAAS,IAC1B,CChHO,SAASM,CAAAA,CAAgBC,CAAAA,CAAWC,CAAAA,CAAoB,CAE7D,GAAI,OAAO,UAAA,CAAW,MAAA,CAAW,GAAA,EAAe,oBAAqB,UAAA,CAAW,MAAA,CAAQ,CACtF,IAAMC,CAAAA,CAAU,IAAI,WAAA,CACdC,CAAAA,CAAOD,CAAAA,CAAQ,MAAA,CAAOF,CAAC,CAAA,CACvBI,CAAAA,CAAOF,CAAAA,CAAQ,OAAOD,CAAC,CAAA,CAI7B,OAAIE,CAAAA,CAAK,MAAA,GAAWC,CAAAA,CAAK,MAAA,EAGtB,UAAA,CAAW,MAAA,CACT,eAAA,CAAgBD,CAAAA,CAAMA,CAAI,CAAA,CACtB,KAAA,EAGD,WAAW,MAAA,CAChB,eAAA,CAAgBA,CAAAA,CAAMC,CAAI,CAC/B,CAGA,IAAMF,CAAAA,CAAU,IAAI,WAAA,CACdC,CAAAA,CAAOD,CAAAA,CAAQ,MAAA,CAAOF,CAAC,EACvBI,CAAAA,CAAOF,CAAAA,CAAQ,MAAA,CAAOD,CAAC,CAAA,CAIvBI,CAAAA,CAAS,IAAA,CAAK,GAAA,CAAIF,CAAAA,CAAK,MAAA,CAAQC,CAAAA,CAAK,MAAM,CAAA,CAC5CxC,CAAAA,CAASuC,EAAK,MAAA,GAAWC,CAAAA,CAAK,MAAA,CAAS,CAAA,CAAI,CAAA,CAE/C,IAAA,IAASE,CAAAA,CAAI,CAAA,CAAGA,CAAAA,CAAID,CAAAA,CAAQC,CAAAA,EAAAA,CAAK,CAC/B,IAAMC,CAAAA,CAAQJ,EAAKG,CAAC,CAAA,EAAK,CAAA,CACnBE,CAAAA,CAAQJ,CAAAA,CAAKE,CAAC,GAAK,CAAA,CACzB1C,CAAAA,EAAU2C,CAAAA,CAAQC,EACpB,CAEA,OAAO5C,IAAW,CACpB,CCtCO,SAAS6C,CAAAA,CACdpB,CAAAA,CACAqB,CAAAA,CACwB,CACxB,GAAM,CAAE,MAAA,CAAAC,CAAAA,CAAQ,QAAA,CAAAC,CAAAA,CAAU,qBAAA,CAAAC,EAAuB,GAAA,CAAAC,CAAAA,CAAK,UAAA,CAAAC,CAAAA,CAAY,UAAA,CAAAC,CAAW,CAAA,CAAIN,CAAAA,CAG3EO,CAAAA,CAAeC,EAAAA,CAAe7B,CAAAA,CAAQ,GAAA,CAAKsB,CAAM,CAAA,CACvD,GAAI,CAACM,CAAAA,CAAa,KAAA,CAChB,OAAOA,CAAAA,CAIT,IAAME,CAAAA,CAAiBC,EAAAA,CAAiB/B,CAAAA,CAAQ,GAAA,CAAKuB,CAAQ,CAAA,CAC7D,GAAI,CAACO,EAAe,KAAA,CAClB,OAAOA,CAAAA,CAKT,GAAI9B,CAAAA,CAAQ,GAAA,GAAQ,MAAA,CAAW,CAC7B,IAAMgC,CAAAA,CAAYC,EAAAA,CAAmBjC,CAAAA,CAAQ,GAAA,CAAKyB,CAAAA,CAAKD,CAAqB,CAAA,CAC5E,GAAI,CAACQ,CAAAA,CAAU,KAAA,CACb,OAAOA,CAEX,CAAA,KAAA,GAAWN,CAAAA,CACT,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,aAAA,CAAe,OAAA,CAAS,4BAA6B,CACtE,CAAA,CAIF,GAAI1B,CAAAA,CAAQ,GAAA,GAAQ,MAAA,CAAW,CAC7B,IAAMkC,CAAAA,CAAYC,EAAAA,CAAkBnC,EAAQ,GAAA,CAAKyB,CAAAA,CAAKD,CAAqB,CAAA,CAC3E,GAAI,CAACU,CAAAA,CAAU,KAAA,CACb,OAAOA,CAEX,CAIA,GAAIlC,CAAAA,CAAQ,GAAA,GAAQ,OAAW,CAC7B,IAAMoC,CAAAA,CAAYC,EAAAA,CAAiBrC,CAAAA,CAAQ,GAAA,CAAKyB,EAAKD,CAAqB,CAAA,CAC1E,GAAI,CAACY,CAAAA,CAAU,KAAA,CACb,OAAOA,CAEX,CAAA,KAAA,GAAWT,CAAAA,CACT,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,aAAA,CAAe,OAAA,CAAS,4BAA6B,CACtE,EAGF,OAAO,CAAE,KAAA,CAAO,IAAK,CACvB,CAKA,SAASE,EAAAA,CACPS,CAAAA,CACAC,CAAAA,CACwB,CAExB,GAAID,CAAAA,GAAQ,MAAA,EAAaA,IAAQ,EAAA,CAC/B,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,gBAAA,CAAkB,OAAA,CAAS,+BAAgC,CAC5E,CAAA,CAMF,IAAME,GAHU,KAAA,CAAM,OAAA,CAAQD,CAAe,CAAA,CAAIA,CAAAA,CAAkB,CAACA,CAAe,CAAA,EAGtD,MAAA,CAAQtB,CAAAA,EAAMA,CAAAA,GAAM,EAAE,CAAA,CACnD,OAAIuB,EAAa,MAAA,GAAW,CAAA,CACnB,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,gBAAA,CAAkB,OAAA,CAAS,sCAAuC,CACnF,CAAA,CAIkBA,CAAAA,CAAa,KAAMC,CAAAA,EAAgB/B,CAAAA,CAAgB4B,CAAAA,CAAKG,CAAW,CAAC,CAAA,CAQjF,CAAE,KAAA,CAAO,IAAK,CAAA,CANZ,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,gBAAA,CAAkB,OAAA,CAAS,CAAA,gBAAA,EAAmBH,CAAG,CAAA,CAAG,CACrE,CAIJ,CAKA,SAASP,EAAAA,CACPW,CAAAA,CACAC,CAAAA,CACwB,CACxB,GAAID,CAAAA,GAAQ,MAAA,CACV,OAAO,CACL,KAAA,CAAO,KAAA,CACP,MAAO,CAAE,IAAA,CAAM,kBAAA,CAAoB,OAAA,CAAS,wBAAyB,CACvE,EAGF,IAAME,CAAAA,CAAY,KAAA,CAAM,OAAA,CAAQD,CAAiB,CAAA,CAAIA,CAAAA,CAAoB,CAACA,CAAiB,CAAA,CACrFE,CAAAA,CAAiB,KAAA,CAAM,OAAA,CAAQH,CAAG,EAAIA,CAAAA,CAAM,CAACA,CAAG,CAAA,CAGhDI,CAAAA,CAAiBF,CAAAA,CAAU,MAAA,CAAQjC,CAAAA,EAAMA,CAAAA,GAAM,EAAE,CAAA,CACjDoC,CAAAA,CAAsBF,CAAAA,CAAe,MAAA,CAAQlC,GAAMA,CAAAA,GAAM,EAAE,CAAA,CAEjE,OAAImC,CAAAA,CAAe,MAAA,GAAW,CAAA,CACrB,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,kBAAA,CAAoB,QAAS,wCAAyC,CACvF,CAAA,CAGEC,CAAAA,CAAoB,MAAA,GAAW,CAAA,CAC1B,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAE,IAAA,CAAM,kBAAA,CAAoB,OAAA,CAAS,oCAAqC,CACnF,CAAA,CAKuBA,CAAAA,CAAoB,IAAA,CAAMC,CAAAA,EACjDF,CAAAA,CAAe,IAAA,CAAMG,CAAAA,EAAgBvC,CAAAA,CAAgBsC,CAAAA,CAAUC,CAAW,CAAC,CAC7E,CAAA,CAYO,CAAE,KAAA,CAAO,IAAK,CAAA,CATZ,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,kBAAA,CACN,OAAA,CAAS,CAAA,kBAAA,EAAqBF,CAAAA,CAAoB,IAAA,CAAK,IAAI,CAAC,CAAA,CAC9D,CACF,CAIJ,CAMA,IAAMG,EAAAA,CAA2B,SAAA,CAK3BC,EAAAA,CAA2B,CAAA,CASjC,SAASC,CAAAA,CAAoBC,CAAAA,CAAiC,CAC5D,OAAI,OAAOA,CAAAA,EAAU,QAAA,EAAY,CAAC,MAAA,CAAO,QAAA,CAASA,CAAK,CAAA,CAC9C,KAAA,CAGFA,CAAAA,EAASF,EAAAA,EAA4BE,CAAAA,EAASH,EACvD,CAKA,SAASjB,EAAAA,CACPqB,CAAAA,CACA7B,CAAAA,CACA8B,CAAAA,CACwB,CAExB,OAAKH,CAAAA,CAAoBE,CAAG,CAAA,CAWxBA,CAAAA,CAAMC,CAAAA,CAAY9B,CAAAA,CACb,CACL,MAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,eAAA,CACN,OAAA,CAAS,CAAA,iBAAA,EAAoB,IAAI,IAAA,CAAK6B,CAAAA,CAAM,GAAI,CAAA,CAAE,WAAA,EAAa,EACjE,CACF,CAAA,CAGK,CAAE,KAAA,CAAO,IAAK,CAAA,CApBZ,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,aAAA,CACN,OAAA,CAAS,qCACX,CACF,CAeJ,CAKA,SAASnB,EAAAA,CACPqB,CAAAA,CACA/B,CAAAA,CACA8B,CAAAA,CACwB,CAExB,OAAKH,CAAAA,CAAoBI,CAAG,CAAA,CAWxBA,CAAAA,CAAMD,EAAY9B,CAAAA,CACb,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,qBAAA,CACN,OAAA,CAAS,CAAA,sBAAA,EAAyB,IAAI,IAAA,CAAK+B,CAAAA,CAAM,GAAI,EAAE,WAAA,EAAa,CAAA,CACtE,CACF,CAAA,CAGK,CAAE,KAAA,CAAO,IAAK,CAAA,CApBZ,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CACL,KAAM,aAAA,CACN,OAAA,CAAS,qCACX,CACF,CAeJ,CASA,SAASnB,EAAAA,CACPoB,CAAAA,CACAhC,CAAAA,CACA8B,CAAAA,CACwB,CAExB,OAAKH,CAAAA,CAAoBK,CAAG,CAAA,CAWxBA,CAAAA,CAAMF,CAAAA,CAAY9B,CAAAA,CACb,CACL,KAAA,CAAO,MACP,KAAA,CAAO,CACL,IAAA,CAAM,eAAA,CACN,OAAA,CAAS,CAAA,4BAAA,EAA+B,IAAI,IAAA,CAAKgC,CAAAA,CAAM,GAAI,CAAA,CAAE,WAAA,EAAa,CAAA,CAC5E,CACF,CAAA,CAGK,CAAE,KAAA,CAAO,IAAK,CAAA,CApBZ,CACL,MAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,aAAA,CACN,OAAA,CAAS,qCACX,CACF,CAeJ,CASO,SAASC,CAAAA,CAAaJ,CAAAA,CAAyB7B,CAAAA,CAAiC,CACrF,GAAI6B,CAAAA,GAAQ,MAAA,CAGZ,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,CAAGA,CAAAA,CAAM7B,CAAG,CAC9B,CC/RO,IAAMkC,CAAAA,CAAN,KAAqB,CAG1B,WAAA,CAAYrF,CAAAA,CAA8B,CACxC,IAAA,CAAK,MAAA,CAASA,EAChB,CAcA,MAAM,QAAA,CAASmB,CAAAA,CAA+C,CAC5D,GAAI,CAEF,IAAIW,EACJ,GAAI,CACFA,CAAAA,CAASZ,CAAAA,CAA4BC,CAAK,EAC5C,CAAA,MAAST,CAAAA,CAAO,CACd,OAAO,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,CACL,IAAA,CAAM,iBAAA,CACN,OAAA,CAASA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,oBACpD,CACF,CACF,CAGA,IAAM4E,CAAAA,CAAY,MAAM,KAAK,MAAA,CAAO,WAAA,CAAY,MAAA,CAAOxD,CAAAA,CAAO,MAAM,CAAA,CACpE,GAAIwD,CAAAA,CAAU,KAAA,CACZ,OAAO,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,CACL,IAAA,CAAMA,CAAAA,CAAU,KAAA,CAAM,IAAA,CACtB,OAAA,CAASA,CAAAA,CAAU,MAAM,OAC3B,CACF,CAAA,CAGF,GAAI,CAACA,CAAAA,CAAU,IACb,OAAO,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,CACL,IAAA,CAAM,oBAAA,CACN,OAAA,CAAS,+BACX,CACF,CAAA,CAIF,IAAMC,CAAAA,CAAW,MAAM5D,CAAAA,CACrBR,CAAAA,CACAmE,CAAAA,CAAU,GAAA,CAAI,SAAA,CACd,IAAA,CAAK,MAAA,CAAO,MACd,CAAA,CAEA,GAAI,CAACC,CAAAA,CACH,OAAO,CACL,KAAM,IAAA,CACN,KAAA,CAAO,CACL,IAAA,CAAM,mBAAA,CACN,OAAA,CAAS,mCACX,CACF,CAAA,CAIF,IAAMpC,CAAAA,CAAM,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,YAAW,CACnCqC,CAAAA,CAAe1C,CAAAA,CAAeyC,CAAAA,CAAS,OAAA,CAAS,CACpD,MAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,MAAA,CAC5B,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,QAAQ,QAAA,CAC9B,qBAAA,CAAuB,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,qBAAA,EAAyB,EAAA,CACpE,GAAA,CAAApC,CACF,CAAC,CAAA,CAED,GAAI,CAACqC,CAAAA,CAAa,OAASA,CAAAA,CAAa,KAAA,CACtC,OAAO,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAOA,CAAAA,CAAa,KACtB,CAAA,CAIF,GAAI,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,MAAA,CAAQ,CAC9C,IAAMC,CAAAA,CAAc,IAAA,CAAK,cAAA,CACvBF,CAAAA,CAAS,OAAA,CAAQ,KAAA,CACjB,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,cACtB,CAAA,CACA,GAAI,CAACE,CAAAA,CAAY,KAAA,EAASA,CAAAA,CAAY,KAAA,CACpC,OAAO,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAOA,CAAAA,CAAY,KACrB,CAEJ,CAGA,IAAMC,CAAAA,CAAYH,CAAAA,CAAS,OAAA,CAAQ,GAAA,EAAK,GAAA,CAAM,MAAA,CAAS,QAAA,CAUvD,OAAO,CACL,IAAA,CARqC,CACrC,MAAA,CAAQA,CAAAA,CAAS,QACjB,KAAA,CAAApE,CAAAA,CACA,SAAA,CAAAuE,CAAAA,CACA,SAAA,CAAWN,CAAAA,CAAaG,CAAAA,CAAS,OAAA,CAAQ,GAAA,CAAKpC,CAAG,CACnD,CAAA,CAIE,KAAA,CAAO,IACT,CACF,CAAA,MAASzC,CAAAA,CAAO,CACd,OAAIA,CAAAA,YAAiBhB,CAAAA,CACZ,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,CACL,IAAA,CAAMgB,CAAAA,CAAM,IAAA,CACZ,QAASA,CAAAA,CAAM,OACjB,CACF,CAAA,CAGK,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,CACL,IAAA,CAAM,eAAA,CACN,OAAA,CAASA,CAAAA,YAAiB,KAAA,CAAQA,EAAM,OAAA,CAAU,yBACpD,CACF,CACF,CACF,CAKQ,cAAA,CACNiF,CAAAA,CACAC,CAAAA,CAC+D,CAC/D,GAAI,CAACD,CAAAA,CACH,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,oBAAA,CACN,OAAA,CAAS,CAAA,yBAAA,EAA4BC,CAAAA,CAAe,IAAA,CAAK,GAAG,CAAC,CAAA,CAC/D,CACF,EAGF,IAAMC,CAAAA,CAAcF,CAAAA,CAAW,KAAA,CAAM,GAAG,CAAA,CAClCG,CAAAA,CAAgBF,CAAAA,CAAe,MAAA,CAAQG,CAAAA,EAAM,CAACF,CAAAA,CAAY,QAAA,CAASE,CAAC,CAAC,CAAA,CAE3E,OAAID,CAAAA,CAAc,MAAA,CAAS,CAAA,CAClB,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CACL,IAAA,CAAM,oBAAA,CACN,OAAA,CAAS,CAAA,yBAAA,EAA4BA,EAAc,IAAA,CAAK,GAAG,CAAC,CAAA,CAC9D,CACF,CAAA,CAGK,CAAE,KAAA,CAAO,IAAK,CACvB,CACF,EC3LO,SAASE,CAAAA,CAAuBC,EAAkBC,CAAAA,CAA8B,CAGrF,IAAMC,CAAAA,CAAY,kBAAA,CAAmBF,CAAQ,CAAA,CACvCG,CAAAA,CAAgB,kBAAA,CAAmBF,CAAY,CAAA,CAG/CG,CAAAA,CAAc,CAAA,EAAGF,CAAS,IAAIC,CAAa,CAAA,CAAA,CAI3CE,CAAAA,CADU,IAAI,WAAA,EAAY,CACC,MAAA,CAAOD,CAAW,CAAA,CAG/CE,CAAAA,CAAS,EAAA,CACb,IAAA,IAAWC,CAAAA,IAAQF,CAAAA,CACjBC,GAAU,MAAA,CAAO,YAAA,CAAaC,CAAI,CAAA,CAEpC,OAAO,IAAA,CAAKD,CAAM,CACpB,CCRO,IAAME,CAAAA,CAAN,KAA0B,CAG/B,WAAA,CAAYzG,EAAmC,CAC7C,IAAA,CAAK,MAAA,CAASA,EAChB,CAQA,MAAM,UAAA,CAAW0G,CAAAA,CAA+D,CAC9E,IAAMC,CAAAA,CAAO,IAAI,eAAA,CACjBA,CAAAA,CAAK,IAAI,OAAA,CAASD,CAAAA,CAAQ,KAAK,CAAA,CAE3BA,CAAAA,CAAQ,eAAA,EACVC,CAAAA,CAAK,GAAA,CAAI,iBAAA,CAAmBD,CAAAA,CAAQ,eAAe,CAAA,CAIrD,IAAML,CAAAA,CAAcL,EAAuB,IAAA,CAAK,MAAA,CAAO,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,YAAY,CAAA,CAEzF,GAAI,CACF,IAAM5F,CAAAA,CAAW,MAAM,IAAA,CAAK,MAAA,CAAO,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAU,CAClE,MAAA,CAAQ,OACR,OAAA,CAAS,CACP,cAAA,CAAgB,mCAAA,CAChB,aAAA,CAAiB,CAAA,MAAA,EAASiG,CAAW,CAAA,CAAA,CACrC,MAAA,CAAU,kBACZ,CAAA,CACA,IAAA,CAAMM,CAAAA,CAAK,QAAA,EACb,CAAC,CAAA,CAED,GAAI,CAACvG,CAAAA,CAAS,EAAA,CAEZ,YAAMA,CAAAA,CAAS,IAAA,EAAK,CAAE,KAAA,CAAM,IAAM,CAAC,CAAC,CAAA,CAC9B,IAAIV,CAAAA,CACR,qBAAA,CACA,CAAA,8BAAA,EAAiCU,CAAAA,CAAS,MAAM,IAAIA,CAAAA,CAAS,UAAU,CAAA,CACzE,CAAA,CAIF,OADe,MAAMA,CAAAA,CAAS,IAAA,EAEhC,CAAA,MAASM,CAAAA,CAAO,CACd,MAAIA,CAAAA,YAAiBhB,EACbgB,CAAAA,CAGF,IAAIhB,CAAAA,CACR,qBAAA,CACA,CAAA,8BAAA,EAAiCgB,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,eAAe,CAAA,CAAA,CACzF,CAAE,KAAA,CAAOA,CAAAA,YAAiB,MAAQA,CAAAA,CAAQ,MAAU,CACtD,CACF,CACF,CACF,EC1DO,IAAMkG,CAAAA,CAAN,KAAuB,CAG5B,WAAA,CAAY5G,CAAAA,CAAgC,CAC1C,KAAK,MAAA,CAASA,EAChB,CAUA,MAAM,MAAA,CAAO0G,CAAAA,CAA2C,CACtD,IAAMC,CAAAA,CAAO,IAAI,eAAA,CACjBA,CAAAA,CAAK,GAAA,CAAI,OAAA,CAASD,EAAQ,KAAK,CAAA,CAE3BA,CAAAA,CAAQ,eAAA,EACVC,CAAAA,CAAK,GAAA,CAAI,iBAAA,CAAmBD,CAAAA,CAAQ,eAAe,CAAA,CAIrD,IAAML,CAAAA,CAAcL,CAAAA,CAAuB,IAAA,CAAK,OAAO,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,YAAY,CAAA,CAEzF,GAAI,CACF,IAAM5F,CAAAA,CAAW,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,MAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAU,CAClE,MAAA,CAAQ,MAAA,CACR,OAAA,CAAS,CACP,cAAA,CAAgB,mCAAA,CAChB,aAAA,CAAiB,CAAA,MAAA,EAASiG,CAAW,CAAA,CACvC,EACA,IAAA,CAAMM,CAAAA,CAAK,QAAA,EACb,CAAC,CAAA,CAGD,GAAI,CAACvG,CAAAA,CAAS,EAAA,CAEZ,MAAA,MAAMA,CAAAA,CAAS,IAAA,EAAK,CAAE,MAAM,IAAM,CAAC,CAAC,CAAA,CAC9B,IAAIV,CAAAA,CACR,kBAAA,CACA,CAAA,2BAAA,EAA8BU,CAAAA,CAAS,MAAM,CAAA,CAAA,EAAIA,CAAAA,CAAS,UAAU,CAAA,CACtE,CAEJ,CAAA,MAASM,CAAAA,CAAO,CACd,MAAIA,CAAAA,YAAiBhB,CAAAA,CACbgB,CAAAA,CAGF,IAAIhB,CAAAA,CACR,kBAAA,CACA,CAAA,2BAAA,EAA8BgB,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,QAAU,eAAe,CAAA,CAAA,CACtF,CAAE,KAAA,CAAOA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAQ,MAAU,CACtD,CACF,CACF,CACF,ECnEA,eAAsBmG,EACpB5H,CAAAA,CACA4C,CAAAA,CACiB,CACjB,OAAOA,CAAAA,CAAO,mBAAA,CAAoB5C,CAAG,CACvC,CAUA,eAAsB6H,EAAAA,CACpB7H,CAAAA,CACA8H,CAAAA,CACAlF,CAAAA,CACkB,CAElB,OADyB,MAAMgF,CAAAA,CAAuB5H,CAAAA,CAAK4C,CAAM,CAAA,GACrCkF,CAC9B,CCTA,IAAM/F,EAAAA,CAAuB,IAAI,GAAA,CAAI,CACnC,OAAA,CAAS,QAAS,OAAA,CAClB,OAAA,CAAS,OAAA,CAAS,OAAA,CAClB,OAAA,CAAS,OAAA,CAAS,QAClB,OACF,CAAC,CAAA,CAMKgG,EAAAA,CAAqB,IAAI,GAAA,CAAI,CACjC,GAAA,CACA,GAAA,CACA,GAAA,CACA,IAAA,CACA,IAAA,CACA,IAAA,CACA,GACF,CAAC,CAAA,CAKD,SAASC,EAAAA,CAAeC,CAAAA,CAA0F,CAChH,IAAM9F,IAAQ8F,CAAAA,CAAM,KAAA,CAAM,GAAG,CAAA,CAE7B,GAAI9F,GAAAA,CAAM,MAAA,GAAW,CAAA,CACnB,MAAM,IAAI,KAAA,CAAM,6CAA6C,CAAA,CAG/D,GAAM,CAACC,CAAAA,CAAYC,CAAAA,CAAaC,CAAa,CAAA,CAAIH,GAAAA,CAEjD,GAAI,CAACC,CAAAA,EAAc,CAACC,CAAAA,EAAe,CAACC,CAAAA,CAClC,MAAM,IAAI,MAAM,wCAAwC,CAAA,CAG1D,IAAMzB,CAAAA,CAAS,IAAA,CAAK,KAAA,CAAM2B,CAAAA,CAAsBJ,CAAU,CAAC,CAAA,CACrDK,CAAAA,CAAU,IAAA,CAAK,KAAA,CAAMD,CAAAA,CAAsBH,CAAW,CAAC,CAAA,CAE7D,OAAO,CAAE,MAAA,CAAAxB,CAAAA,CAAQ,OAAA,CAAA4B,CAAAA,CAAS,SAAA,CAAWH,CAAc,CACrD,CAUA,SAAS4F,CAAAA,CAAaC,EAA4B,CAChD,GAAI,CACF,IAAMC,CAAAA,CAAM,IAAI,GAAA,CAAID,CAAG,CAAA,CAEvB,OAAO,CAAA,EAAGC,CAAAA,CAAI,QAAQ,CAAA,EAAA,EAAKA,EAAI,IAAI,CAAA,EAAGA,CAAAA,CAAI,QAAQ,CAAA,CACpD,CAAA,KAAQ,CAEN,OAAO,IACT,CACF,CAKO,IAAMC,CAAAA,CAAN,KAAoB,CACzB,WAAA,CACmBzF,CAAAA,CACA0F,CAAAA,CACjB,CAFiB,IAAA,CAAA,MAAA,CAAA1F,CAAAA,CACA,WAAA0F,EAChB,CASH,MAAM,QAAA,CACJL,CAAAA,CACAnE,CAAAA,CAC+B,CAC/B,GAAI,CAEF,GAAM,CAAE,MAAA,CAAAjD,CAAAA,CAAQ,OAAA,CAAA4B,CAAQ,CAAA,CAAIuF,EAAAA,CAAeC,CAAK,CAAA,CAG1CM,CAAAA,CAAe,IAAA,CAAK,eAAe1H,CAAM,CAAA,CAC/C,GAAI,CAAC0H,CAAAA,CAAa,KAAA,CAChB,OAAOA,CAAAA,CAIT,IAAMC,CAAAA,CAAgB,IAAA,CAAK,wBAAA,CAAyB/F,CAAO,CAAA,CAC3D,GAAI,CAAC+F,CAAAA,CAAc,KAAA,CACjB,OAAOA,CAAAA,CAIT,IAAMC,CAAAA,CAAkB,MAAM,IAAA,CAAK,eAAA,CAAgBR,CAAAA,CAAOpH,CAAM,CAAA,CAChE,GAAI,CAAC4H,CAAAA,CAAgB,KAAA,CACnB,OAAOA,CAAAA,CAIT,GAAIhG,CAAAA,CAAQ,GAAA,CAAI,WAAA,EAAY,GAAMqB,CAAAA,CAAQ,MAAA,CAAO,WAAA,EAAY,CAC3D,OAAO,CACL,KAAA,CAAO,CAAA,CAAA,CACP,SAAA,CAAW,sBAAA,CACX,YAAA,CAAc,CAAA,0BAAA,EAA6BA,CAAAA,CAAQ,MAAM,CAAA,MAAA,EAASrB,CAAAA,CAAQ,GAAG,CAAA,CAC/E,CAAA,CAIF,IAAMiG,EAAcR,CAAAA,CAAapE,CAAAA,CAAQ,GAAG,CAAA,CACtC6E,CAAAA,CAAYT,CAAAA,CAAazF,CAAAA,CAAQ,GAAG,CAAA,CAG1C,GAAIiG,CAAAA,GAAgB,IAAA,CAClB,OAAO,CACL,MAAO,CAAA,CAAA,CACP,SAAA,CAAW,oBAAA,CACX,YAAA,CAAc,CAAA,sBAAA,EAAyB5E,CAAAA,CAAQ,GAAG,CAAA,CACpD,CAAA,CAEF,GAAI6E,CAAAA,GAAc,IAAA,CAChB,OAAO,CACL,MAAO,CAAA,CAAA,CACP,SAAA,CAAW,mBAAA,CACX,YAAA,CAAc,CAAA,mBAAA,EAAsBlG,CAAAA,CAAQ,GAAG,CAAA,CACjD,CAAA,CAGF,GAAIiG,CAAAA,GAAgBC,CAAAA,CAClB,OAAO,CACL,KAAA,CAAO,CAAA,CAAA,CACP,SAAA,CAAW,mBAAA,CACX,YAAA,CAAc,CAAA,uBAAA,EAA0BD,CAAW,CAAA,MAAA,EAASC,CAAS,CAAA,CACvE,CAAA,CAIF,IAAM/G,CAAAA,CAASkC,CAAAA,CAAQ,QAAU,EAAA,CAC3BkC,CAAAA,CAAYlC,CAAAA,CAAQ,cAAA,EAAkB,EAAA,CACtCI,CAAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAA,EAAW,CAC5B0E,CAAAA,CAAS1E,CAAAA,CAAMtC,CAAAA,CAASoE,CAAAA,CACxB6C,EAAS3E,CAAAA,CAAM8B,CAAAA,CAErB,GAAIvD,CAAAA,CAAQ,GAAA,CAAMmG,CAAAA,EAAUnG,CAAAA,CAAQ,GAAA,CAAMoG,CAAAA,CACxC,OAAO,CACL,KAAA,CAAO,CAAA,CAAA,CACP,SAAA,CAAW,mBACX,YAAA,CAAc,wCAChB,CAAA,CAIF,GAAI/E,CAAAA,CAAQ,aAAA,GAAkB,KAAA,CAAA,EACxBrB,CAAAA,CAAQ,KAAA,GAAUqB,CAAAA,CAAQ,aAAA,CAC5B,OAAO,CACL,KAAA,CAAO,GACP,SAAA,CAAW,qBAAA,CACX,YAAA,CAAc,+BAChB,CAAA,CAKJ,IAAMgF,CAAAA,CAAa,MAAMlB,CAAAA,CAAuB/G,CAAAA,CAAO,GAAA,CAAmB,IAAA,CAAK,MAAM,CAAA,CAGrF,GAAIiD,CAAAA,CAAQ,WAAA,CAAa,CACvB,IAAMiF,CAAAA,CAAY,MAAM,IAAA,CAAK,uBAAA,CAC3BtG,CAAAA,CAAQ,GAAA,CACRqB,CAAAA,CAAQ,WACV,CAAA,CACA,GAAI,CAACiF,CAAAA,CAAU,KAAA,CACb,OAAOA,CAEX,CAIA,OAAIjF,CAAAA,CAAQ,kBAAA,EACN,CAACX,CAAAA,CAAgB2F,CAAAA,CAAYhF,CAAAA,CAAQ,kBAAkB,CAAA,CAClD,CACL,KAAA,CAAO,CAAA,CAAA,CACP,SAAA,CAAW,uBAAA,CACX,YAAA,CAAc,6CAChB,EAIG,CACL,KAAA,CAAO,CAAA,CAAA,CACP,UAAA,CAAAgF,CACF,CACF,OAASrH,CAAAA,CAAO,CACd,OAAO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,oBAAA,CACX,YAAA,CAAcA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,oBACzD,CACF,CACF,CAKQ,cAAA,CAAeZ,CAAAA,CAA+C,CAEpE,GAAIA,CAAAA,CAAO,GAAA,GAAQ,UAAA,CACjB,OAAO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,qBACX,YAAA,CAAc,2CAChB,CAAA,CAIF,GAAI,CAACkB,EAAAA,CAAqB,GAAA,CAAIlB,CAAAA,CAAO,GAAG,CAAA,CACtC,OAAO,CACL,KAAA,CAAO,KAAA,CACP,UAAW,oBAAA,CACX,YAAA,CAAc,CAAA,uBAAA,EAA0BA,CAAAA,CAAO,GAAG,CAAA,CACpD,CAAA,CAIF,GAAI,CAACA,CAAAA,CAAO,GAAA,CACV,OAAO,CACL,KAAA,CAAO,MACP,SAAA,CAAW,oBAAA,CACX,YAAA,CAAc,kCAChB,CAAA,CAKF,IAAMmI,CAAAA,CAASnI,CAAAA,CAAO,GAAA,CACtB,IAAA,IAAWoI,CAAAA,IAASlB,EAAAA,CAClB,GAAIkB,CAAAA,IAASD,GAAUA,CAAAA,CAAOC,CAAK,CAAA,GAAM,MAAA,CACvC,OAAO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,oBAAA,CACX,YAAA,CAAc,gDAChB,CAAA,CAIJ,OAAO,CAAE,KAAA,CAAO,IAAK,CACvB,CAKQ,wBAAA,CAAyBxG,CAAAA,CAAiD,CAChF,OAAKA,CAAAA,CAAQ,GAAA,CAQRA,CAAAA,CAAQ,GAAA,CAQRA,CAAAA,CAAQ,GAAA,CAQT,OAAOA,CAAAA,CAAQ,GAAA,EAAQ,QAAA,CAClB,CACL,KAAA,CAAO,KAAA,CACP,UAAW,oBAAA,CACX,YAAA,CAAc,8BAChB,CAAA,CAGK,CAAE,KAAA,CAAO,IAAK,CAAA,CAfZ,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,oBAAA,CACX,YAAA,CAAc,mBAChB,CAAA,CAZO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,oBAAA,CACX,aAAc,mBAChB,CAAA,CAZO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,oBAAA,CACX,YAAA,CAAc,mBAChB,CA4BJ,CAKA,MAAc,eAAA,CACZwF,CAAAA,CACApH,EAC+B,CAC/B,GAAI,CAEF,IAAMW,CAAAA,CAAY,MAAM,IAAA,CAAK,MAAA,CAAO,SAAA,CAClCX,CAAAA,CAAO,GAAA,CACPA,CAAAA,CAAO,GACT,CAAA,CAGMiC,EAAWmF,CAAAA,CAAM,WAAA,CAAY,GAAG,CAAA,CAChClF,CAAAA,CAAekF,CAAAA,CAAM,SAAA,CAAU,CAAA,CAAGnF,CAAQ,CAAA,CAC1CoG,CAAAA,CAAejB,CAAAA,CAAM,SAAA,CAAUnF,CAAAA,CAAW,CAAC,CAAA,CAE3CI,CAAAA,CAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAOH,CAAY,CAAA,CAC5CoG,CAAAA,CAAYlG,CAAAA,CAAgBiG,CAAY,CAAA,CAU9C,OAPc,MAAM,KAAK,MAAA,CAAO,eAAA,CAC9BrI,CAAAA,CAAO,GAAA,CACPW,CAAAA,CACA2H,CAAAA,CACAjG,CACF,CAAA,CAUO,CAAE,KAAA,CAAO,CAAA,CAAK,CAAA,CAPZ,CACL,KAAA,CAAO,GACP,SAAA,CAAW,8BAAA,CACX,YAAA,CAAc,0CAChB,CAIJ,CAAA,MAASzB,CAAAA,CAAO,CACd,OAAO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,8BAAA,CACX,aAAc,CAAA,6BAAA,EAAgCA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,eAAe,CAAA,CACxG,CACF,CACF,CASA,MAAc,uBAAA,CACZ2H,CAAAA,CACAC,CAAAA,CAC+B,CAE/B,GAAI,CAACD,CAAAA,CACH,OAAO,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,kBAAA,CACX,YAAA,CAAc,gDAChB,CAAA,CAIF,IAAME,CAAAA,CAAa,IAAI,WAAA,EAAY,CAAE,MAAA,CAAOD,CAAW,CAAA,CACjDE,CAAAA,CAAO,MAAM,IAAA,CAAK,MAAA,CAAO,MAAA,CAAOD,CAAU,CAAA,CAC1CE,CAAAA,CAAcC,GAAAA,CAAgBF,CAAI,CAAA,CAGxC,OAAKpG,CAAAA,CAAgBiG,CAAAA,CAAKI,CAAW,CAAA,CAQ9B,CAAE,KAAA,CAAO,IAAK,CAAA,CAPZ,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,oBACX,YAAA,CAAc,4BAChB,CAIJ,CACF,EChYA,SAASE,CAAAA,CAActB,CAAAA,CAAyBuB,CAAAA,CAAcC,CAAAA,CAA6B,CACzF,GAAI,EAAA,CAACA,CAAAA,EAAgB,CAACxB,CAAAA,CAAAA,CAItB,GAAI,CAEF,GADe,IAAI,GAAA,CAAIA,CAAG,CAAA,CACf,QAAA,GAAa,QAAA,CACtB,MAAM,IAAI3H,CAAAA,CACR,qBAAA,CACA,GAAGkJ,CAAI,CAAA,iBAAA,EAAoBvB,CAAG,CAAA,uDAAA,CAChC,CAEJ,CAAA,MAAS3G,CAAAA,CAAO,CACd,MAAIA,CAAAA,YAAiBhB,CAAAA,CACbgB,CAAAA,CAEF,IAAIhB,CAAAA,CACR,sBACA,CAAA,QAAA,EAAWkJ,CAAI,CAAA,MAAA,EAASvB,CAAG,CAAA,CAC7B,CACF,CACF,CAKA,SAASyB,EAAAA,CAAc9I,CAAAA,CAA0D,CAC/E,IAAMgD,CAAAA,CAAS,MAAM,OAAA,CAAQhD,CAAAA,CAAO,MAAM,CAAA,CAAIA,CAAAA,CAAO,MAAA,CAAS,CAACA,CAAAA,CAAO,MAAM,CAAA,CACtEiD,CAAAA,CAAW,KAAA,CAAM,OAAA,CAAQjD,EAAO,QAAQ,CAAA,CAAIA,CAAAA,CAAO,QAAA,CAAW,CAACA,CAAAA,CAAO,QAAQ,CAAA,CAC9E6I,CAAAA,CAAe7I,CAAAA,CAAO,YAAA,EAAgB,IAAA,CAG5C,IAAA,IAAWgE,CAAAA,IAAOhB,EAChB2F,CAAAA,CAAc3E,CAAAA,CAAK,QAAA,CAAU6E,CAAY,CAAA,CAE3C,OAAAF,CAAAA,CAAc3I,CAAAA,CAAO,OAAA,CAAS,SAAA,CAAW6I,CAAY,CAAA,CACrDF,CAAAA,CAAc3I,CAAAA,CAAO,sBAAuB,uBAAA,CAAyB6I,CAAY,CAAA,CACjFF,CAAAA,CAAc3I,CAAAA,CAAO,kBAAA,CAAoB,oBAAA,CAAsB6I,CAAY,CAAA,CAEpE,CACL,MAAA,CAAA7F,CAAAA,CACA,QAAA,CAAAC,CAAAA,CACA,QAASjD,CAAAA,CAAO,OAAA,CAChB,qBAAA,CAAuBA,CAAAA,CAAO,qBAAA,EAAyB,EAAA,CACvD,qBAAA,CAAuBA,CAAAA,CAAO,qBAAA,EAAyB,IAAA,CACvD,qBAAA,CAAuBA,CAAAA,CAAO,qBAAA,CAC9B,kBAAA,CAAoBA,EAAO,kBAAA,CAC3B,iBAAA,CAAmBA,CAAAA,CAAO,iBAAA,CAC1B,IAAA,CAAMA,CAAAA,CAAO,IAAA,EAAQ+I,GAAAA,EAAkB,CACvC,MAAA,CAAQ/I,CAAAA,CAAO,MAAA,EAAUgJ,CAAAA,EAAkB,CAC3C,MAAOhJ,CAAAA,CAAO,KAAA,EAASiJ,CAAAA,EAAY,CACnC,SAAA,CAAWjJ,CAAAA,CAAO,SAAA,EAAakJ,GAAAA,CAAyB,CAAE,KAAA,CAAOlJ,CAAAA,CAAO,qBAAA,EAAyB,IAAS,CAAC,EAC3G,YAAA,CAAA6I,CACF,CACF,CAOO,IAAMM,CAAAA,CAAN,KAAoB,CAUzB,WAAA,CAAYnJ,CAAAA,CAA6B,CARzC,IAAA,CAAQ,WAAA,CAAkC,IAAA,CAC1C,KAAQ,cAAA,CAAwC,IAAA,CAChD,IAAA,CAAQ,aAAA,CAAsC,IAAA,CAC9C,IAAA,CAAQ,oBAAkD,IAAA,CAC1D,IAAA,CAAQ,gBAAA,CAA4C,IAAA,CACpD,IAAA,CAAQ,WAAA,CAAoC,KAC5C,IAAA,CAAQ,WAAA,CAAc,KAAA,CAGpB,IAAA,CAAK,MAAA,CAAS8I,EAAAA,CAAc9I,CAAM,EACpC,CAQA,MAAM,IAAA,EAAsB,CAE1B,GAAI,CAAA,IAAA,CAAK,YAKT,CAAA,GAAI,IAAA,CAAK,WAAA,CACP,OAAO,IAAA,CAAK,WAAA,CAId,IAAA,CAAK,WAAA,CAAc,IAAA,CAAK,MAAA,EAAO,CAE/B,GAAI,CACF,MAAM,KAAK,WAAA,CACX,IAAA,CAAK,WAAA,CAAc,CAAA,EACrB,CAAA,MAASU,CAAAA,CAAO,CAEd,MAAA,IAAA,CAAK,WAAA,CAAc,IAAA,CACbA,CACR,CAAA,CACF,CAEA,MAAc,QAAwB,CAEpC,IAAI0I,CAAAA,CAAU,IAAA,CAAK,MAAA,CAAO,OAAA,CAErBA,CAAAA,GACHA,CAAAA,CAAU,MAAM,IAAA,CAAK,eAAA,EAAgB,CAAA,CAIvC,IAAA,CAAK,WAAA,CAAc,IAAItI,CAAAA,CAAY,CACjC,OAAA,CAAAsI,CAAAA,CACA,UAAA,CAAY,IAAA,CAAK,MAAA,CAAO,qBAAA,CACxB,IAAA,CAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAClB,MAAA,CAAQ,IAAA,CAAK,OAAO,MAAA,CACpB,KAAA,CAAO,IAAA,CAAK,MAAA,CAAO,KAAA,CACnB,KAAA,CAAO,IAAA,CAAK,MAAA,CAAO,SACrB,CAAC,CAAA,CAGD,IAAA,CAAK,cAAA,CAAiB,IAAI/D,EAAe,CACvC,WAAA,CAAa,IAAA,CAAK,WAAA,CAClB,MAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,MAAA,CACpB,KAAA,CAAO,IAAA,CAAK,MAAA,CAAO,KAAA,CACnB,OAAA,CAAS,CACP,OAAQ,IAAA,CAAK,MAAA,CAAO,MAAA,CACpB,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,SACtB,qBAAA,CAAuB,IAAA,CAAK,MAAA,CAAO,qBACrC,CACF,CAAC,EAGD,IAAA,CAAK,aAAA,CAAgB,IAAIiC,CAAAA,CAAc,IAAA,CAAK,MAAA,CAAO,MAAA,CAAQ,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,CAGxE,IAAA,CAAK,MAAA,CAAO,qBAAA,EAAyB,KAAK,MAAA,CAAO,iBAAA,GACnD,IAAA,CAAK,mBAAA,CAAsB,IAAIb,CAAAA,CAAoB,CACjD,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,qBAAA,CACtB,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,kBAAkB,QAAA,CACxC,YAAA,CAAc,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,YAAA,CAC5C,IAAA,CAAM,IAAA,CAAK,MAAA,CAAO,IACpB,CAAC,CAAA,CAAA,CAIC,IAAA,CAAK,MAAA,CAAO,oBAAsB,IAAA,CAAK,MAAA,CAAO,iBAAA,GAChD,IAAA,CAAK,gBAAA,CAAmB,IAAIG,CAAAA,CAAiB,CAC3C,QAAA,CAAU,IAAA,CAAK,MAAA,CAAO,kBAAA,CACtB,QAAA,CAAU,IAAA,CAAK,OAAO,iBAAA,CAAkB,QAAA,CACxC,YAAA,CAAc,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,YAAA,CAC5C,IAAA,CAAM,IAAA,CAAK,MAAA,CAAO,IACpB,CAAC,CAAA,EAEL,CAKA,MAAc,eAAA,EAAmC,CAC/C,IAAM5D,CAAAA,CAAS,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,CACnC,GAAI,CAACA,CAAAA,CACH,MAAM,IAAItD,EACR,qBAAA,CACA,sBACF,CAAA,CAGF,IAAM2J,CAAAA,CAAY,CAAA,EAAGrG,CAAAA,CAAO,OAAA,CAAQ,KAAA,CAAO,EAAE,CAAC,CAAA,iCAAA,CAAA,CAE9C,GAAI,CACF,IAAM5C,CAAAA,CAAW,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,KAAA,CAAMiJ,CAAAA,CAAW,CACvD,OAAA,CAAS,CAAE,MAAA,CAAQ,kBAAmB,CACxC,CAAC,EAED,GAAI,CAACjJ,CAAAA,CAAS,EAAA,CAEZ,MAAA,MAAMA,CAAAA,CAAS,IAAA,EAAK,CAAE,KAAA,CAAM,IAAM,CAAC,CAAC,CAAA,CAC9B,IAAIV,EACR,qBAAA,CACA,CAAA,sCAAA,EAAyCU,CAAAA,CAAS,MAAM,CAAA,CAC1D,CAAA,CAGF,IAAMJ,CAAAA,CAAS,MAAMI,CAAAA,CAAS,IAAA,EAAK,CAEnC,GAAI,CAACJ,EAAO,QAAA,CACV,MAAM,IAAIN,CAAAA,CACR,qBAAA,CACA,uCACF,CAAA,CAIF,OAAAiJ,CAAAA,CAAc3I,CAAAA,CAAO,QAAA,CAAU,qBAAA,CAAuB,IAAA,CAAK,MAAA,CAAO,YAAY,CAAA,CAEvEA,CAAAA,CAAO,QAChB,CAAA,MAASU,CAAAA,CAAO,CACd,MAAIA,CAAAA,YAAiBhB,CAAAA,CACbgB,CAAAA,CAEF,IAAIhB,CAAAA,CACR,qBAAA,CACA,CAAA,6BAAA,EAAgCgB,aAAiB,KAAA,CAAQA,CAAAA,CAAM,OAAA,CAAU,eAAe,CAAA,CAAA,CACxF,CAAE,KAAA,CAAOA,CAAAA,YAAiB,KAAA,CAAQA,CAAAA,CAAQ,MAAU,CACtD,CACF,CACF,CAQA,MAAM,aAAA,CAAcS,CAAAA,CAA+C,CAGjE,OAFA,MAAM,IAAA,CAAK,IAAA,EAAK,CAEX,IAAA,CAAK,cAAA,CAOH,IAAA,CAAK,cAAA,CAAe,QAAA,CAASA,CAAK,CAAA,CANhC,CACL,IAAA,CAAM,IAAA,CACN,KAAA,CAAO,CAAE,IAAA,CAAM,qBAAA,CAAuB,OAAA,CAAS,iCAAkC,CACnF,CAIJ,CASA,MAAM,aACJ+F,CAAAA,CACAnE,CAAAA,CAC+B,CAG/B,OAFA,MAAM,IAAA,CAAK,MAAK,CAEX,IAAA,CAAK,aAAA,CAQH,IAAA,CAAK,aAAA,CAAc,QAAA,CAASmE,EAAOnE,CAAO,CAAA,CAPxC,CACL,KAAA,CAAO,KAAA,CACP,SAAA,CAAW,qBAAA,CACX,YAAA,CAAc,gCAChB,CAIJ,CASA,MAAM,UAAA,CACJ5B,CAAAA,CACAmI,EACgC,CAGhC,GAFA,MAAM,IAAA,CAAK,IAAA,EAAK,CAEZ,CAAC,IAAA,CAAK,mBAAA,CACR,MAAM,IAAI5J,CAAAA,CACR,qBAAA,CACA,uCACF,EAGF,OAAO,IAAA,CAAK,mBAAA,CAAoB,UAAA,CAAW,CACzC,KAAA,CAAAyB,CAAAA,CACA,eAAA,CAAiBmI,CACnB,CAAC,CACH,CAQA,MAAM,MAAA,CACJnI,EACAmI,CAAAA,CACe,CAGf,GAFA,MAAM,IAAA,CAAK,IAAA,EAAK,CAEZ,CAAC,IAAA,CAAK,gBAAA,CACR,MAAM,IAAI5J,CAAAA,CACR,qBAAA,CACA,oCACF,CAAA,CAGF,OAAO,IAAA,CAAK,gBAAA,CAAiB,MAAA,CAAO,CAClC,KAAA,CAAAyB,CAAAA,CACA,eAAA,CAAiBmI,CACnB,CAAC,CACH,CAKA,SAAA,EAAyC,CACvC,OAAO,IAAA,CAAK,MACd,CAKA,mBAAA,EAA4B,CAC1B,IAAA,CAAK,WAAA,EAAa,UAAA,GACpB,CACF,EAQO,SAASC,EAAAA,CAAoBvJ,EAA4C,CAC9E,OAAO,IAAImJ,CAAAA,CAAcnJ,CAAM,CACjC,CC9UO,IAAMwJ,CAAAA,CAA2B,oDAAA,CA6G3BC,CAAAA,CAAN,KAAiC,CAgBtC,QAAA,CACEC,EACA3G,CAAAA,CACmC,CAEnC,IAAIjD,CAAAA,CACA6J,CAAAA,CAEJ,GAAI,CACF,IAAMC,CAAAA,CAAU1I,CAAAA,CAA4BwI,CAAW,CAAA,CACvD5J,CAAAA,CAAS8J,EAAQ,MAAA,CACjBD,CAAAA,CAASC,CAAAA,CAAQ,QACnB,CAAA,MAASlJ,CAAAA,CAAO,CAGd,OAAIA,CAAAA,YAAiBO,CAAAA,CACZ,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,oDACP,SAAA,CAAW,oBACb,CAAA,CAEK,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,oBAAA,CACP,SAAA,CAAW,gBACb,CACF,CAGA,GAAI,CAAC0I,EAAO,GAAA,EAAO,CAACvH,CAAAA,CAAgBuH,CAAAA,CAAO,GAAA,CAAK5G,CAAAA,CAAQ,MAAM,CAAA,CAC5D,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,0BAAA,CACP,UAAW,gBACb,CAAA,CAMF,GAAI,CAAA,CAFc,KAAA,CAAM,OAAA,CAAQ4G,CAAAA,CAAO,GAAG,CAAA,CAAIA,CAAAA,CAAO,GAAA,CAAM,CAACA,CAAAA,CAAO,GAAG,GACtC,IAAA,CAAMvF,CAAAA,EAAQhC,CAAAA,CAAgBgC,CAAAA,CAAKrB,CAAAA,CAAQ,QAAQ,CAAC,CAAA,CAElF,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,4BAAA,CACP,UAAW,kBACb,CAAA,CAKF,GAAI,CAAC4G,CAAAA,CAAO,MAAA,EAAU,OAAOA,CAAAA,CAAO,MAAA,EAAW,QAAA,CAC7C,OAAO,CACL,KAAA,CAAO,KAAA,CACP,MAAO,iCAAA,CACP,SAAA,CAAW,gBACb,CAAA,CAGF,IAAME,CAAAA,CAAcF,CAAAA,CAAO,MAAA,CAAOH,CAAwB,CAAA,CAC1D,GAAIK,CAAAA,GAAgB,MAAA,CAClB,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAA,QAAA,EAAWL,CAAwB,CAAA,gBAAA,CAAA,CAC1C,UAAW,gBACb,CAAA,CAKF,GACE,OAAOK,CAAAA,EAAgB,QAAA,EACvBA,IAAgB,IAAA,EAChB,KAAA,CAAM,OAAA,CAAQA,CAAW,CAAA,EACzB,MAAA,CAAO,IAAA,CAAKA,CAAW,CAAA,CAAE,MAAA,GAAW,CAAA,CAEpC,OAAO,CACL,KAAA,CAAO,MACP,KAAA,CAAO,mDAAA,CACP,SAAA,CAAW,gBACb,CAAA,CAIF,GAAI,CAACF,CAAAA,CAAO,GAAA,CACV,OAAO,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,oBACP,SAAA,CAAW,aACb,CAAA,CAIF,GAAI,CAACA,CAAAA,CAAO,GAAA,EAAO,CAACA,CAAAA,CAAO,GAAA,CACzB,OAAO,CACL,KAAA,CAAO,KAAA,CACP,MAAO,uDAAA,CACP,SAAA,CAAW,qBACb,CAAA,CAKF,GAAI,OAAA,GAAWA,CAAAA,EAAWA,CAAAA,CAAe,KAAA,GAAU,MAAA,CACjD,OAAO,CACL,KAAA,CAAO,KAAA,CACP,MAAO,2CAAA,CACP,SAAA,CAAW,eACb,CAAA,CAIF,IAAMxG,CAAAA,CAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,EAAI,CAAI,GAAI,CAAA,CAClCtC,CAAAA,CAASkC,EAAQ,MAAA,EAAU,EAAA,CAC3B+G,CAAAA,CAAY/G,CAAAA,CAAQ,SAAA,EAAa,EAAA,CAIvC,OAAK4G,CAAAA,CAAO,GAAA,CASRxG,CAAAA,CAAMwG,CAAAA,CAAO,GAAA,CAAMG,CAAAA,CACd,CACL,MAAO,KAAA,CACP,KAAA,CAAO,CAAA,iBAAA,EAAoBH,CAAAA,CAAO,GAAG,CAAA,CAAA,CACrC,SAAA,CAAW,eACb,CAAA,CAIEA,CAAAA,CAAO,GAAA,EAAOxG,CAAAA,CAAMwG,CAAAA,CAAO,GAAA,CAAM9I,EAASiJ,CAAAA,CACrC,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAA,mBAAA,EAAsBH,CAAAA,CAAO,GAAG,CAAA,cAAA,EAAiB9I,CAAM,CAAA,YAAA,CAAA,CAC9D,SAAA,CAAW,aACb,CAAA,CAIE8I,EAAO,GAAA,EAAOA,CAAAA,CAAO,GAAA,CAAMxG,CAAAA,CAAM2G,CAAAA,CAC5B,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,CAAA,4BAAA,EAA+BH,CAAAA,CAAO,GAAG,CAAA,CAAA,CAChD,SAAA,CAAW,eACb,CAAA,CAKE5G,CAAAA,CAAQ,WAAA,EAAe4G,CAAAA,CAAO,GAAA,EAAO,CAACvH,CAAAA,CAAgBuH,CAAAA,CAAO,GAAA,CAAK5G,CAAAA,CAAQ,WAAW,CAAA,CAChF,CACL,KAAA,CAAO,MACP,KAAA,CAAO,2BAAA,CACP,SAAA,CAAW,cACb,CAAA,CAKEA,CAAAA,CAAQ,WAAA,EAAe4G,CAAAA,CAAO,GAAA,EAAO,CAACvH,CAAAA,CAAgBuH,CAAAA,CAAO,GAAA,CAAK5G,CAAAA,CAAQ,WAAW,CAAA,CAChF,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,8BAAA,CACP,SAAA,CAAW,cACb,CAAA,CAGK,CACL,KAAA,CAAO,IAAA,CACP,MAAA,CAAA4G,CAAAA,CACA,OAAA7J,CACF,CAAA,CA1DS,CACL,KAAA,CAAO,KAAA,CACP,KAAA,CAAO,mBAAA,CACP,SAAA,CAAW,eACb,CAuDJ,CAUA,aAAA,CAAc4J,CAAAA,CAA+C,CAC3D,GAAI,CAEF,OADgBxI,CAAAA,CAA4BwI,CAAW,CAAA,CACxC,OACjB,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CAUA,aAAA,CAAcA,CAAAA,CAAuC,CACnD,GAAI,CAEF,OADgBxI,CAAAA,CAA4BwI,CAAW,CAAA,CACxC,MACjB,CAAA,KAAQ,CACN,OAAO,IACT,CACF,CACF","file":"index.js","sourcesContent":["/**\r\n * JWKS Key Selection Algorithm\r\n *\r\n * This algorithm MUST be implemented identically across all language SDKs.\r\n *\r\n * 1. If token header contains `kid`:\r\n * a. Find key where key.kid === header.kid AND key.use === 'sig' (or undefined)\r\n * b. If not found AND cache is stale, refresh JWKS and retry ONCE\r\n * c. If still not found, return error: jwks_key_not_found\r\n *\r\n * 2. If no `kid` in token header:\r\n * a. Filter keys by: key.alg === header.alg AND key.use === 'sig' (or undefined)\r\n * b. If exactly ONE key matches, use it\r\n * c. If ZERO or MORE THAN ONE keys match, return error: jwks_key_ambiguous\r\n *\r\n * 3. Algorithm mismatch:\r\n * - If key.alg is present AND key.alg !== header.alg, skip the key\r\n *\r\n * 4. Cache behavior:\r\n * - Default TTL: 1 hour\r\n * - Stale-while-revalidate: NOT implemented (fail-closed)\r\n * - Thundering herd: Use single-flight pattern (one concurrent fetch per issuer)\r\n *\r\n * 5. Cache-Control header:\r\n * - SHOULD be respected if present\r\n * - max-age takes precedence over default TTL\r\n */\r\n\r\nimport type { PublicJwk, CachedJwk } from '../types/jwk.js';\r\nimport type { JwtHeader } from '../types/claims.js';\r\nimport { AuthrimServerError } from '../types/errors.js';\r\n\r\n/**\r\n * Key selection result\r\n */\r\nexport interface KeySelectionResult {\r\n key: CachedJwk | null;\r\n error: AuthrimServerError | null;\r\n needsRefresh: boolean;\r\n}\r\n\r\n/**\r\n * Check if a JWK is suitable for signature verification\r\n *\r\n * @param jwk - JWK to check\r\n * @returns true if the key can be used for signature verification\r\n */\r\nfunction isSigningKey(jwk: PublicJwk): boolean {\r\n // Key must not be explicitly for encryption\r\n return jwk.use === undefined || jwk.use === 'sig';\r\n}\r\n\r\n/**\r\n * Check if a JWK's algorithm matches the token's algorithm\r\n *\r\n * @param jwk - JWK to check\r\n * @param tokenAlg - Algorithm from token header\r\n * @returns true if algorithms match (or key has no explicit algorithm)\r\n */\r\nfunction algorithmMatches(jwk: PublicJwk, tokenAlg: string): boolean {\r\n // If key has no explicit algorithm, it can be used with any compatible algorithm\r\n if (jwk.alg === undefined) {\r\n return true;\r\n }\r\n // If key has explicit algorithm, it must match\r\n return jwk.alg === tokenAlg;\r\n}\r\n\r\n/**\r\n * Select a key by kid (Key ID)\r\n *\r\n * @param keys - Available keys\r\n * @param kid - Key ID from token header\r\n * @param alg - Algorithm from token header\r\n * @returns KeySelectionResult\r\n */\r\nexport function selectKeyByKid(\r\n keys: CachedJwk[],\r\n kid: string,\r\n alg: string\r\n): KeySelectionResult {\r\n // Find key with matching kid\r\n const matchingKey = keys.find(\r\n (k) => k.jwk.kid === kid && isSigningKey(k.jwk) && algorithmMatches(k.jwk, alg)\r\n );\r\n\r\n if (matchingKey) {\r\n return { key: matchingKey, error: null, needsRefresh: false };\r\n }\r\n\r\n // Key not found - may need to refresh JWKS\r\n return {\r\n key: null,\r\n error: new AuthrimServerError(\r\n 'jwks_key_not_found',\r\n `No key found with kid: ${kid}`\r\n ),\r\n needsRefresh: true,\r\n };\r\n}\r\n\r\n/**\r\n * Select a key by algorithm (when no kid is present)\r\n *\r\n * @param keys - Available keys\r\n * @param alg - Algorithm from token header\r\n * @returns KeySelectionResult\r\n */\r\nexport function selectKeyByAlgorithm(\r\n keys: CachedJwk[],\r\n alg: string\r\n): KeySelectionResult {\r\n // Filter keys that match the algorithm\r\n const matchingKeys = keys.filter(\r\n (k) => isSigningKey(k.jwk) && algorithmMatches(k.jwk, alg)\r\n );\r\n\r\n if (matchingKeys.length === 1) {\r\n return { key: matchingKeys[0]!, error: null, needsRefresh: false };\r\n }\r\n\r\n if (matchingKeys.length === 0) {\r\n return {\r\n key: null,\r\n error: new AuthrimServerError(\r\n 'jwks_key_ambiguous',\r\n `No keys found matching algorithm: ${alg}`\r\n ),\r\n needsRefresh: false,\r\n };\r\n }\r\n\r\n // Multiple keys match - ambiguous\r\n return {\r\n key: null,\r\n error: new AuthrimServerError(\r\n 'jwks_key_ambiguous',\r\n `Multiple keys (${matchingKeys.length}) match algorithm: ${alg}. Token must include 'kid' header.`\r\n ),\r\n needsRefresh: false,\r\n };\r\n}\r\n\r\n/**\r\n * Select a key from JWKS based on token header\r\n *\r\n * @param keys - Available keys\r\n * @param header - JWT header\r\n * @returns KeySelectionResult\r\n */\r\nexport function selectKey(\r\n keys: CachedJwk[],\r\n header: JwtHeader\r\n): KeySelectionResult {\r\n if (header.kid) {\r\n return selectKeyByKid(keys, header.kid, header.alg);\r\n }\r\n\r\n return selectKeyByAlgorithm(keys, header.alg);\r\n}\r\n","/**\r\n * JWKS Manager\r\n *\r\n * Handles JWKS fetching, caching, and key rotation.\r\n */\r\n\r\nimport type { HttpProvider } from '../providers/http.js';\r\nimport type { CryptoProvider } from '../providers/crypto.js';\r\nimport type { ClockProvider } from '../providers/clock.js';\r\nimport type { CacheProvider } from '../providers/cache.js';\r\nimport type { PublicJwk, JwkSet, CachedJwk } from '../types/jwk.js';\r\nimport type { JwtHeader } from '../types/claims.js';\r\nimport { AuthrimServerError } from '../types/errors.js';\r\nimport { selectKey, type KeySelectionResult } from './key-selector.js';\r\n\r\n/**\r\n * JWKS Manager configuration\r\n */\r\nexport interface JwksManagerConfig {\r\n /** JWKS endpoint URL */\r\n jwksUri: string;\r\n /** Cache TTL in milliseconds */\r\n cacheTtlMs: number;\r\n /** HTTP provider */\r\n http: HttpProvider;\r\n /** Crypto provider */\r\n crypto: CryptoProvider;\r\n /** Clock provider */\r\n clock: ClockProvider;\r\n /** Cache provider */\r\n cache: CacheProvider<CachedJwk[]>;\r\n /**\r\n * Optional callback for key import warnings\r\n * Called when a key fails to import (may be encryption key, unsupported algorithm, etc.)\r\n */\r\n onKeyImportWarning?: (warning: KeyImportWarning) => void;\r\n /**\r\n * Allow redirects to different hosts (default: false)\r\n *\r\n * SECURITY: By default, JWKS fetches will fail if redirected to a different host\r\n * to prevent SSRF attacks. Only enable this if you explicitly trust the issuer\r\n * to redirect to arbitrary hosts.\r\n */\r\n allowCrossOriginRedirect?: boolean;\r\n}\r\n\r\n/**\r\n * Key import warning information\r\n */\r\nexport interface KeyImportWarning {\r\n /** Key ID (if present) */\r\n kid?: string;\r\n /** Key type */\r\n kty: string;\r\n /** Algorithm (if present) */\r\n alg?: string;\r\n /** Reason for failure */\r\n reason: 'unsupported_algorithm' | 'import_failed' | 'unknown_key_type';\r\n /** Error message */\r\n message: string;\r\n}\r\n\r\n/**\r\n * Single-flight state for preventing thundering herd\r\n */\r\ninterface InFlightRequest {\r\n promise: Promise<CachedJwk[]>;\r\n startedAt: number;\r\n}\r\n\r\n/**\r\n * JWKS Manager\r\n *\r\n * Features:\r\n * - Automatic JWKS fetching and caching\r\n * - Key rotation handling (retry on kid not found)\r\n * - Single-flight pattern (one concurrent fetch per issuer)\r\n * - Cache-Control header support\r\n */\r\nexport class JwksManager {\r\n private readonly config: JwksManagerConfig;\r\n private readonly cacheKey: string;\r\n private inFlight: InFlightRequest | null = null;\r\n\r\n constructor(config: JwksManagerConfig) {\r\n this.config = config;\r\n this.cacheKey = `jwks:${config.jwksUri}`;\r\n }\r\n\r\n /**\r\n * Get a key for verifying a JWT\r\n *\r\n * Implements the key selection algorithm with automatic refresh on key not found.\r\n *\r\n * @param header - JWT header\r\n * @returns Selected key or error\r\n */\r\n async getKey(header: JwtHeader): Promise<KeySelectionResult> {\r\n // Try to get keys from cache\r\n let keys = await this.getKeys();\r\n\r\n // First attempt at key selection\r\n let result = selectKey(keys, header);\r\n\r\n // If key not found and we might need to refresh, try once more\r\n if (result.error && result.needsRefresh) {\r\n // Force refresh\r\n keys = await this.fetchAndCacheKeys();\r\n result = selectKey(keys, header);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Get cached keys or fetch if not available\r\n */\r\n private async getKeys(): Promise<CachedJwk[]> {\r\n // Check cache first\r\n const cached = this.config.cache.get(this.cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Fetch and cache\r\n return this.fetchAndCacheKeys();\r\n }\r\n\r\n /**\r\n * Fetch JWKS and cache keys\r\n *\r\n * Uses single-flight pattern to prevent thundering herd.\r\n */\r\n private async fetchAndCacheKeys(): Promise<CachedJwk[]> {\r\n // Check for in-flight request (single-flight pattern)\r\n if (this.inFlight) {\r\n const age = this.config.clock.nowMs() - this.inFlight.startedAt;\r\n // If request is still recent (< 30s), wait for it\r\n if (age < 30_000) {\r\n return this.inFlight.promise;\r\n }\r\n // Otherwise, start a new request\r\n }\r\n\r\n // Start new request\r\n const promise = this.doFetchAndCache();\r\n this.inFlight = {\r\n promise,\r\n startedAt: this.config.clock.nowMs(),\r\n };\r\n\r\n try {\r\n const keys = await promise;\r\n return keys;\r\n } finally {\r\n this.inFlight = null;\r\n }\r\n }\r\n\r\n /**\r\n * Actually fetch and cache keys\r\n */\r\n private async doFetchAndCache(): Promise<CachedJwk[]> {\r\n try {\r\n const response = await this.config.http.fetch(this.config.jwksUri, {\r\n headers: {\r\n Accept: 'application/json',\r\n },\r\n });\r\n\r\n // Security: Check for cross-origin redirects to prevent SSRF\r\n // If the response URL differs from the request URL, validate the redirect\r\n if (!this.config.allowCrossOriginRedirect && response.url) {\r\n const requestedUrl = new URL(this.config.jwksUri);\r\n const responseUrl = new URL(response.url);\r\n\r\n if (requestedUrl.host !== responseUrl.host) {\r\n throw new AuthrimServerError(\r\n 'jwks_fetch_error',\r\n `JWKS fetch was redirected to a different host: ${responseUrl.host} (from ${requestedUrl.host}). ` +\r\n 'This is blocked for security. Set allowCrossOriginRedirect: true if this is intentional.'\r\n );\r\n }\r\n }\r\n\r\n if (!response.ok) {\r\n // Consume response body to release the connection\r\n await response.text().catch(() => {});\r\n throw new AuthrimServerError(\r\n 'jwks_fetch_error',\r\n `Failed to fetch JWKS: ${response.status} ${response.statusText}`\r\n );\r\n }\r\n\r\n const jwks = (await response.json()) as JwkSet;\r\n\r\n if (!jwks.keys || !Array.isArray(jwks.keys)) {\r\n throw new AuthrimServerError(\r\n 'jwks_fetch_error',\r\n 'Invalid JWKS response: missing keys array'\r\n );\r\n }\r\n\r\n // Import keys\r\n const cachedKeys: CachedJwk[] = [];\r\n for (const jwk of jwks.keys) {\r\n try {\r\n // Determine algorithm for import\r\n const alg = this.determineAlgorithm(jwk);\r\n if (!alg) {\r\n // Report warning for keys without determinable algorithm\r\n this.config.onKeyImportWarning?.({\r\n kid: jwk.kid,\r\n kty: jwk.kty,\r\n alg: jwk.alg,\r\n reason: jwk.kty === 'RSA' || jwk.kty === 'EC' || jwk.kty === 'OKP'\r\n ? 'unsupported_algorithm'\r\n : 'unknown_key_type',\r\n message: `Cannot determine algorithm for key: kty=${jwk.kty}, alg=${jwk.alg ?? 'none'}`,\r\n });\r\n continue;\r\n }\r\n\r\n const cryptoKey = await this.config.crypto.importJwk(jwk as JsonWebKey, alg);\r\n cachedKeys.push({ jwk, cryptoKey });\r\n } catch (error) {\r\n // Report warning for keys that fail to import\r\n this.config.onKeyImportWarning?.({\r\n kid: jwk.kid,\r\n kty: jwk.kty,\r\n alg: jwk.alg,\r\n reason: 'import_failed',\r\n message: `Failed to import key: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n });\r\n continue;\r\n }\r\n }\r\n\r\n // Determine cache TTL from Cache-Control header\r\n const cacheTtl = this.parseCacheControl(response.headers.get('Cache-Control'));\r\n\r\n // Cache keys\r\n this.config.cache.set(this.cacheKey, cachedKeys, cacheTtl);\r\n\r\n return cachedKeys;\r\n } catch (error) {\r\n if (error instanceof AuthrimServerError) {\r\n throw error;\r\n }\r\n throw new AuthrimServerError(\r\n 'jwks_fetch_error',\r\n `Failed to fetch JWKS: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n { cause: error instanceof Error ? error : undefined }\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Determine the algorithm for a JWK\r\n */\r\n private determineAlgorithm(jwk: PublicJwk): string | null {\r\n // Use explicit algorithm if present\r\n if (jwk.alg) {\r\n return jwk.alg;\r\n }\r\n\r\n // Infer from key type and curve\r\n if (jwk.kty === 'RSA') {\r\n return 'RS256'; // Default for RSA\r\n }\r\n\r\n if (jwk.kty === 'EC') {\r\n const curveAlgMap: Record<string, string> = {\r\n 'P-256': 'ES256',\r\n 'P-384': 'ES384',\r\n 'P-521': 'ES512',\r\n };\r\n return curveAlgMap[jwk.crv] ?? null;\r\n }\r\n\r\n if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') {\r\n return 'EdDSA';\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Maximum cache TTL (24 hours) to prevent overflow and unreasonable cache times\r\n */\r\n private static readonly MAX_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\r\n\r\n /**\r\n * Parse Cache-Control header for max-age\r\n *\r\n * @param header - Cache-Control header value\r\n * @returns TTL in milliseconds\r\n */\r\n private parseCacheControl(header: string | null): number {\r\n if (!header) {\r\n return this.config.cacheTtlMs;\r\n }\r\n\r\n // Look for max-age directive\r\n const maxAgeMatch = header.match(/max-age=(\\d+)/);\r\n if (maxAgeMatch && maxAgeMatch[1]) {\r\n const maxAge = parseInt(maxAgeMatch[1], 10);\r\n if (!isNaN(maxAge) && maxAge > 0) {\r\n // Cap at MAX_CACHE_TTL_MS to prevent overflow and excessive caching\r\n const maxAgeMs = Math.min(maxAge * 1000, JwksManager.MAX_CACHE_TTL_MS);\r\n return maxAgeMs;\r\n }\r\n }\r\n\r\n return this.config.cacheTtlMs;\r\n }\r\n\r\n /**\r\n * Invalidate cached keys\r\n */\r\n invalidate(): void {\r\n this.config.cache.delete(this.cacheKey);\r\n }\r\n}\r\n","/**\r\n * JWT Signature Verification\r\n *\r\n * RFC 7519 - JSON Web Token (JWT)\r\n *\r\n * This module handles ONLY cryptographic signature verification.\r\n * Claims validation is handled separately in validate-claims.ts.\r\n */\r\n\r\nimport type { CryptoProvider } from '../providers/crypto.js';\r\nimport type { JwtHeader, ParsedJwt } from '../types/claims.js';\r\nimport { base64UrlDecode, base64UrlDecodeString } from '../utils/base64url.js';\r\n\r\n/**\r\n * Maximum JWT size in bytes (8KB)\r\n * This prevents DoS attacks using extremely large tokens\r\n */\r\nconst MAX_JWT_SIZE = 8192;\r\n\r\n/**\r\n * Supported signing algorithms\r\n */\r\nconst SUPPORTED_ALGORITHMS = new Set([\r\n 'RS256', 'RS384', 'RS512', // RSASSA-PKCS1-v1_5\r\n 'PS256', 'PS384', 'PS512', // RSASSA-PSS\r\n 'ES256', 'ES384', 'ES512', // ECDSA\r\n 'EdDSA', // Edwards-curve\r\n]);\r\n\r\n/**\r\n * Error class for insecure algorithm (alg: none) rejection\r\n *\r\n * Thrown when a JWT uses alg: none, which is a critical security issue.\r\n * This allows callers to distinguish this specific error for logging purposes.\r\n */\r\nexport class InsecureAlgorithmError extends Error {\r\n readonly code = 'insecure_algorithm' as const;\r\n\r\n constructor() {\r\n super('Algorithm \"none\" is not allowed');\r\n this.name = 'InsecureAlgorithmError';\r\n }\r\n}\r\n\r\n/**\r\n * Parse a JWT without verification\r\n *\r\n * @param token - JWT string\r\n * @returns Parsed JWT structure\r\n * @throws Error if JWT format is invalid\r\n * @throws InsecureAlgorithmError if JWT uses alg: none\r\n */\r\nexport function parseJwt<T = Record<string, unknown>>(token: string): ParsedJwt<T> {\r\n // Check size to prevent DoS attacks with huge payloads\r\n if (token.length > MAX_JWT_SIZE) {\r\n throw new Error(`JWT exceeds maximum size of ${MAX_JWT_SIZE} bytes`);\r\n }\r\n\r\n const parts = token.split('.');\r\n\r\n if (parts.length !== 3) {\r\n throw new Error('Invalid JWT format: expected 3 parts');\r\n }\r\n\r\n const [headerPart, payloadPart, signaturePart] = parts;\r\n\r\n if (!headerPart || !payloadPart || !signaturePart) {\r\n throw new Error('Invalid JWT format: empty parts');\r\n }\r\n\r\n try {\r\n const headerRaw = JSON.parse(base64UrlDecodeString(headerPart));\r\n const payload = JSON.parse(base64UrlDecodeString(payloadPart)) as T;\r\n\r\n // Validate header structure\r\n if (!headerRaw || typeof headerRaw !== 'object') {\r\n throw new Error('Invalid JWT header: must be an object');\r\n }\r\n\r\n // Validate alg claim exists and is a string\r\n if (typeof headerRaw.alg !== 'string') {\r\n throw new Error('Invalid JWT header: missing or invalid alg claim');\r\n }\r\n\r\n // Explicitly reject \"none\" algorithm (and case variations) - critical security check\r\n // This prevents algorithm substitution attacks\r\n if (headerRaw.alg.toLowerCase() === 'none') {\r\n throw new InsecureAlgorithmError();\r\n }\r\n\r\n const header = headerRaw as JwtHeader;\r\n\r\n return {\r\n header,\r\n payload,\r\n signature: signaturePart,\r\n };\r\n } catch (error) {\r\n // Re-throw InsecureAlgorithmError as-is for security logging\r\n if (error instanceof InsecureAlgorithmError) {\r\n throw error;\r\n }\r\n // Return generic error message to avoid information leakage for other errors\r\n throw new Error('Invalid JWT format');\r\n }\r\n}\r\n\r\n/**\r\n * Verify JWT signature\r\n *\r\n * @param token - JWT string\r\n * @param key - CryptoKey for verification\r\n * @param crypto - Crypto provider\r\n * @returns Parsed JWT if signature is valid, null otherwise\r\n */\r\nexport async function verifyJwtSignature<T = Record<string, unknown>>(\r\n token: string,\r\n key: CryptoKey,\r\n crypto: CryptoProvider\r\n): Promise<ParsedJwt<T> | null> {\r\n // Parse JWT\r\n const parsed = parseJwt<T>(token);\r\n\r\n // Validate algorithm\r\n if (!SUPPORTED_ALGORITHMS.has(parsed.header.alg)) {\r\n return null;\r\n }\r\n\r\n // Extract signing input and signature\r\n const dotIndex = token.lastIndexOf('.');\r\n const signingInput = token.substring(0, dotIndex);\r\n const signatureBytes = base64UrlDecode(parsed.signature);\r\n\r\n // Verify signature\r\n const data = new TextEncoder().encode(signingInput);\r\n const valid = await crypto.verifySignature(\r\n parsed.header.alg,\r\n key,\r\n signatureBytes,\r\n data\r\n );\r\n\r\n return valid ? parsed : null;\r\n}\r\n\r\n/**\r\n * Get signing input from JWT (header.payload)\r\n *\r\n * @param token - JWT string\r\n * @returns Signing input as bytes\r\n */\r\nexport function getSigningInput(token: string): Uint8Array {\r\n const dotIndex = token.lastIndexOf('.');\r\n const signingInput = token.substring(0, dotIndex);\r\n return new TextEncoder().encode(signingInput);\r\n}\r\n\r\n/**\r\n * Get signature from JWT\r\n *\r\n * @param token - JWT string\r\n * @returns Signature as bytes\r\n */\r\nexport function getSignature(token: string): Uint8Array {\r\n const dotIndex = token.lastIndexOf('.');\r\n const signatureB64 = token.substring(dotIndex + 1);\r\n return base64UrlDecode(signatureB64);\r\n}\r\n","/**\r\n * Timing-Safe Comparison Utilities\r\n *\r\n * Prevents timing attacks when comparing secrets.\r\n *\r\n * SECURITY NOTE:\r\n * These functions protect against timing attacks that could reveal the\r\n * expected value by measuring comparison time. However, the length of\r\n * the inputs may still be leaked through timing differences. This is\r\n * generally acceptable because:\r\n * 1. Input lengths are often known or easily guessable\r\n * 2. True constant-time for arbitrary lengths is expensive\r\n * 3. The main attack vector (early return on mismatch) is prevented\r\n *\r\n * For maximum security with unknown-length secrets, consider using\r\n * fixed-length representations (e.g., hashes) for comparison.\r\n */\r\n\r\n/**\r\n * Compare two strings in constant time\r\n *\r\n * This function compares strings without short-circuiting, preventing\r\n * timing attacks that could reveal information about the expected value.\r\n *\r\n * Note: The function does leak whether the lengths are equal, but the\r\n * actual comparison is constant-time regardless of where mismatches occur.\r\n *\r\n * @param a - First string\r\n * @param b - Second string\r\n * @returns true if strings are equal\r\n */\r\nexport function timingSafeEqual(a: string, b: string): boolean {\r\n // Use crypto.timingSafeEqual if available (Node.js)\r\n if (typeof globalThis.crypto !== 'undefined' && 'timingSafeEqual' in globalThis.crypto) {\r\n const encoder = new TextEncoder();\r\n const bufA = encoder.encode(a);\r\n const bufB = encoder.encode(b);\r\n\r\n // Different lengths: perform a dummy comparison to maintain similar timing\r\n // Note: Length difference is still detectable, but comparison time is consistent\r\n if (bufA.length !== bufB.length) {\r\n // Compare bufA with itself to maintain constant time behavior\r\n // This ensures we still perform cryptographic comparison operation\r\n (globalThis.crypto as { timingSafeEqual: (a: Uint8Array, b: Uint8Array) => boolean })\r\n .timingSafeEqual(bufA, bufA);\r\n return false;\r\n }\r\n\r\n return (globalThis.crypto as { timingSafeEqual: (a: Uint8Array, b: Uint8Array) => boolean })\r\n .timingSafeEqual(bufA, bufB);\r\n }\r\n\r\n // Fallback: manual constant-time comparison\r\n const encoder = new TextEncoder();\r\n const bufA = encoder.encode(a);\r\n const bufB = encoder.encode(b);\r\n\r\n // XOR comparison - continue even if lengths differ\r\n // This ensures all bytes are compared regardless of early mismatches\r\n const maxLen = Math.max(bufA.length, bufB.length);\r\n let result = bufA.length === bufB.length ? 0 : 1;\r\n\r\n for (let i = 0; i < maxLen; i++) {\r\n const byteA = bufA[i] ?? 0;\r\n const byteB = bufB[i] ?? 0;\r\n result |= byteA ^ byteB;\r\n }\r\n\r\n return result === 0;\r\n}\r\n\r\n/**\r\n * Compare two byte arrays in constant time\r\n *\r\n * @param a - First byte array\r\n * @param b - Second byte array\r\n * @returns true if arrays are equal\r\n */\r\nexport function timingSafeEqualBytes(a: Uint8Array, b: Uint8Array): boolean {\r\n // Use crypto.timingSafeEqual if available (Node.js)\r\n if (typeof globalThis.crypto !== 'undefined' && 'timingSafeEqual' in globalThis.crypto) {\r\n if (a.length !== b.length) {\r\n // Compare with itself to maintain constant time\r\n (globalThis.crypto as { timingSafeEqual: (a: Uint8Array, b: Uint8Array) => boolean })\r\n .timingSafeEqual(a, a);\r\n return false;\r\n }\r\n\r\n return (globalThis.crypto as { timingSafeEqual: (a: Uint8Array, b: Uint8Array) => boolean })\r\n .timingSafeEqual(a, b);\r\n }\r\n\r\n // Fallback: manual constant-time comparison\r\n const maxLen = Math.max(a.length, b.length);\r\n let result = a.length === b.length ? 0 : 1;\r\n\r\n for (let i = 0; i < maxLen; i++) {\r\n const byteA = a[i] ?? 0;\r\n const byteB = b[i] ?? 0;\r\n result |= byteA ^ byteB;\r\n }\r\n\r\n return result === 0;\r\n}\r\n","/**\r\n * JWT Claims Validation\r\n *\r\n * RFC 7519 - JSON Web Token (JWT)\r\n *\r\n * This module handles claims validation (iss, aud, exp, nbf, iat).\r\n * Signature verification is handled separately in verify-jwt.ts.\r\n */\r\n\r\nimport type { StandardClaims } from '../types/claims.js';\r\nimport type { ClaimsValidationOptions, ClaimsValidationResult } from '../types/token.js';\r\nimport { timingSafeEqual } from '../utils/timing-safe.js';\r\n\r\n/**\r\n * Validate JWT claims\r\n *\r\n * Validates:\r\n * - iss (issuer) - must match expected issuer\r\n * - aud (audience) - must include expected audience\r\n * - exp (expiration) - must not be expired (with tolerance)\r\n * - nbf (not before) - must be past nbf (with tolerance)\r\n * - iat (issued at) - must not be in the future (with tolerance)\r\n *\r\n * For ID Token validation (OIDC Core 1.0 Section 3.1.3.7), set:\r\n * - requireExp: true\r\n * - requireIat: true\r\n *\r\n * @param payload - JWT payload containing claims\r\n * @param options - Validation options\r\n * @returns Validation result\r\n */\r\nexport function validateClaims(\r\n payload: StandardClaims,\r\n options: ClaimsValidationOptions\r\n): ClaimsValidationResult {\r\n const { issuer, audience, clockToleranceSeconds, now, requireExp, requireIat } = options;\r\n\r\n // Validate issuer\r\n const issuerResult = validateIssuer(payload.iss, issuer);\r\n if (!issuerResult.valid) {\r\n return issuerResult;\r\n }\r\n\r\n // Validate audience\r\n const audienceResult = validateAudience(payload.aud, audience);\r\n if (!audienceResult.valid) {\r\n return audienceResult;\r\n }\r\n\r\n // Validate expiration\r\n // Per OIDC Core 1.0 Section 3.1.3.7 Step 9: exp is REQUIRED for ID Tokens\r\n if (payload.exp !== undefined) {\r\n const expResult = validateExpiration(payload.exp, now, clockToleranceSeconds);\r\n if (!expResult.valid) {\r\n return expResult;\r\n }\r\n } else if (requireExp) {\r\n return {\r\n valid: false,\r\n error: { code: 'missing_exp', message: 'Missing required exp claim' },\r\n };\r\n }\r\n\r\n // Validate not before\r\n if (payload.nbf !== undefined) {\r\n const nbfResult = validateNotBefore(payload.nbf, now, clockToleranceSeconds);\r\n if (!nbfResult.valid) {\r\n return nbfResult;\r\n }\r\n }\r\n\r\n // Validate issued at (sanity check)\r\n // Per OIDC Core 1.0 Section 3.1.3.7 Step 10: iat is REQUIRED for ID Tokens\r\n if (payload.iat !== undefined) {\r\n const iatResult = validateIssuedAt(payload.iat, now, clockToleranceSeconds);\r\n if (!iatResult.valid) {\r\n return iatResult;\r\n }\r\n } else if (requireIat) {\r\n return {\r\n valid: false,\r\n error: { code: 'missing_iat', message: 'Missing required iat claim' },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n/**\r\n * Validate issuer claim\r\n */\r\nfunction validateIssuer(\r\n iss: string | undefined,\r\n expectedIssuers: string | string[]\r\n): ClaimsValidationResult {\r\n // Check for missing or empty issuer\r\n if (iss === undefined || iss === '') {\r\n return {\r\n valid: false,\r\n error: { code: 'invalid_issuer', message: 'Missing or empty issuer claim' },\r\n };\r\n }\r\n\r\n const issuers = Array.isArray(expectedIssuers) ? expectedIssuers : [expectedIssuers];\r\n\r\n // Filter out empty strings from expected issuers\r\n const validIssuers = issuers.filter((i) => i !== '');\r\n if (validIssuers.length === 0) {\r\n return {\r\n valid: false,\r\n error: { code: 'invalid_issuer', message: 'No valid expected issuers configured' },\r\n };\r\n }\r\n\r\n // Use timing-safe comparison to prevent timing attacks\r\n const issuerValid = validIssuers.some((expectedIss) => timingSafeEqual(iss, expectedIss));\r\n if (!issuerValid) {\r\n return {\r\n valid: false,\r\n error: { code: 'invalid_issuer', message: `Invalid issuer: ${iss}` },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n/**\r\n * Validate audience claim\r\n */\r\nfunction validateAudience(\r\n aud: string | string[] | undefined,\r\n expectedAudiences: string | string[]\r\n): ClaimsValidationResult {\r\n if (aud === undefined) {\r\n return {\r\n valid: false,\r\n error: { code: 'invalid_audience', message: 'Missing audience claim' },\r\n };\r\n }\r\n\r\n const audiences = Array.isArray(expectedAudiences) ? expectedAudiences : [expectedAudiences];\r\n const tokenAudiences = Array.isArray(aud) ? aud : [aud];\r\n\r\n // Filter out empty strings\r\n const validAudiences = audiences.filter((a) => a !== '');\r\n const validTokenAudiences = tokenAudiences.filter((a) => a !== '');\r\n\r\n if (validAudiences.length === 0) {\r\n return {\r\n valid: false,\r\n error: { code: 'invalid_audience', message: 'No valid expected audiences configured' },\r\n };\r\n }\r\n\r\n if (validTokenAudiences.length === 0) {\r\n return {\r\n valid: false,\r\n error: { code: 'invalid_audience', message: 'Token has no valid audience claims' },\r\n };\r\n }\r\n\r\n // Check if any expected audience is in token audiences\r\n // Use timing-safe comparison to prevent timing attacks\r\n const hasValidAudience = validTokenAudiences.some((tokenAud) =>\r\n validAudiences.some((expectedAud) => timingSafeEqual(tokenAud, expectedAud))\r\n );\r\n\r\n if (!hasValidAudience) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'invalid_audience',\r\n message: `Invalid audience: ${validTokenAudiences.join(', ')}`,\r\n },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n/**\r\n * Maximum reasonable Unix timestamp (year 3000)\r\n * Prevents Date overflow and unrealistic token lifetimes\r\n */\r\nconst MAX_REASONABLE_TIMESTAMP = 32503680000; // 3000-01-01 00:00:00 UTC\r\n\r\n/**\r\n * Minimum reasonable Unix timestamp (year 1970)\r\n */\r\nconst MIN_REASONABLE_TIMESTAMP = 0;\r\n\r\n/**\r\n * Validate that a claim value is a finite number within reasonable bounds\r\n *\r\n * Per RFC 7519 Section 2, NumericDate values are numbers representing\r\n * seconds since Unix epoch. Non-numeric values must be rejected.\r\n * Additionally, we validate bounds to prevent Date overflow issues.\r\n */\r\nfunction isValidNumericClaim(value: unknown): value is number {\r\n if (typeof value !== 'number' || !Number.isFinite(value)) {\r\n return false;\r\n }\r\n // Validate bounds to prevent Date overflow and unrealistic values\r\n return value >= MIN_REASONABLE_TIMESTAMP && value <= MAX_REASONABLE_TIMESTAMP;\r\n}\r\n\r\n/**\r\n * Validate expiration claim\r\n */\r\nfunction validateExpiration(\r\n exp: unknown,\r\n now: number,\r\n tolerance: number\r\n): ClaimsValidationResult {\r\n // Validate type first - must be a finite number per RFC 7519\r\n if (!isValidNumericClaim(exp)) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'invalid_exp',\r\n message: 'Invalid exp claim: must be a number',\r\n },\r\n };\r\n }\r\n\r\n // Token is expired if exp + tolerance < now\r\n if (exp + tolerance < now) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'token_expired',\r\n message: `Token expired at ${new Date(exp * 1000).toISOString()}`,\r\n },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n/**\r\n * Validate not before claim\r\n */\r\nfunction validateNotBefore(\r\n nbf: unknown,\r\n now: number,\r\n tolerance: number\r\n): ClaimsValidationResult {\r\n // Validate type first - must be a finite number per RFC 7519\r\n if (!isValidNumericClaim(nbf)) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'invalid_nbf',\r\n message: 'Invalid nbf claim: must be a number',\r\n },\r\n };\r\n }\r\n\r\n // Token is not yet valid if nbf - tolerance > now\r\n if (nbf - tolerance > now) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'token_not_yet_valid',\r\n message: `Token not valid until ${new Date(nbf * 1000).toISOString()}`,\r\n },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n/**\r\n * Validate issued at claim (sanity check)\r\n *\r\n * Per RFC 7519 Section 4.1.6, the iat claim identifies the time at which\r\n * the JWT was issued. A token with iat significantly in the future\r\n * indicates either clock skew or a potentially malicious token.\r\n */\r\nfunction validateIssuedAt(\r\n iat: unknown,\r\n now: number,\r\n tolerance: number\r\n): ClaimsValidationResult {\r\n // Validate type first - must be a finite number per RFC 7519\r\n if (!isValidNumericClaim(iat)) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'invalid_iat',\r\n message: 'Invalid iat claim: must be a number',\r\n },\r\n };\r\n }\r\n\r\n // Token was issued in the future (with tolerance)\r\n if (iat - tolerance > now) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'iat_in_future',\r\n message: `Token issued in the future: ${new Date(iat * 1000).toISOString()}`,\r\n },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n/**\r\n * Calculate time remaining until token expiration\r\n *\r\n * @param exp - Expiration timestamp (Unix seconds)\r\n * @param now - Current timestamp (Unix seconds)\r\n * @returns Seconds until expiration, or undefined if no exp claim\r\n */\r\nexport function getExpiresIn(exp: number | undefined, now: number): number | undefined {\r\n if (exp === undefined) {\r\n return undefined;\r\n }\r\n return Math.max(0, exp - now);\r\n}\r\n","/**\r\n * Token Validator\r\n *\r\n * Orchestrates JWT signature verification and claims validation.\r\n */\r\n\r\nimport type { CryptoProvider } from '../providers/crypto.js';\r\nimport type { ClockProvider } from '../providers/clock.js';\r\nimport type { AccessTokenClaims, ValidatedToken } from '../types/claims.js';\r\nimport type { TokenValidationOptions, TokenValidationResult } from '../types/token.js';\r\nimport { AuthrimServerError } from '../types/errors.js';\r\nimport { JwksManager } from '../jwks/manager.js';\r\nimport { parseJwt, verifyJwtSignature } from './verify-jwt.js';\r\nimport { validateClaims, getExpiresIn } from './validate-claims.js';\r\n\r\n/**\r\n * Token Validator configuration\r\n */\r\nexport interface TokenValidatorConfig {\r\n /** JWKS Manager */\r\n jwksManager: JwksManager;\r\n /** Crypto provider */\r\n crypto: CryptoProvider;\r\n /** Clock provider */\r\n clock: ClockProvider;\r\n /** Validation options */\r\n options: TokenValidationOptions;\r\n}\r\n\r\n/**\r\n * Token Validator\r\n *\r\n * Combines JWKS key retrieval, signature verification, and claims validation.\r\n */\r\nexport class TokenValidator {\r\n private readonly config: TokenValidatorConfig;\r\n\r\n constructor(config: TokenValidatorConfig) {\r\n this.config = config;\r\n }\r\n\r\n /**\r\n * Validate a JWT access token\r\n *\r\n * Steps:\r\n * 1. Parse JWT structure\r\n * 2. Get signing key from JWKS\r\n * 3. Verify signature\r\n * 4. Validate claims (iss, aud, exp, nbf, iat)\r\n *\r\n * @param token - JWT string\r\n * @returns Validation result\r\n */\r\n async validate(token: string): Promise<TokenValidationResult> {\r\n try {\r\n // Parse JWT to get header\r\n let parsed;\r\n try {\r\n parsed = parseJwt<AccessTokenClaims>(token);\r\n } catch (error) {\r\n return {\r\n data: null,\r\n error: {\r\n code: 'token_malformed',\r\n message: error instanceof Error ? error.message : 'Invalid JWT format',\r\n },\r\n };\r\n }\r\n\r\n // Get signing key from JWKS\r\n const keyResult = await this.config.jwksManager.getKey(parsed.header);\r\n if (keyResult.error) {\r\n return {\r\n data: null,\r\n error: {\r\n code: keyResult.error.code,\r\n message: keyResult.error.message,\r\n },\r\n };\r\n }\r\n\r\n if (!keyResult.key) {\r\n return {\r\n data: null,\r\n error: {\r\n code: 'jwks_key_not_found',\r\n message: 'No suitable key found in JWKS',\r\n },\r\n };\r\n }\r\n\r\n // Verify signature\r\n const verified = await verifyJwtSignature<AccessTokenClaims>(\r\n token,\r\n keyResult.key.cryptoKey,\r\n this.config.crypto\r\n );\r\n\r\n if (!verified) {\r\n return {\r\n data: null,\r\n error: {\r\n code: 'signature_invalid',\r\n message: 'JWT signature verification failed',\r\n },\r\n };\r\n }\r\n\r\n // Validate claims\r\n const now = this.config.clock.nowSeconds();\r\n const claimsResult = validateClaims(verified.payload, {\r\n issuer: this.config.options.issuer,\r\n audience: this.config.options.audience,\r\n clockToleranceSeconds: this.config.options.clockToleranceSeconds ?? 60,\r\n now,\r\n });\r\n\r\n if (!claimsResult.valid && claimsResult.error) {\r\n return {\r\n data: null,\r\n error: claimsResult.error,\r\n };\r\n }\r\n\r\n // Validate required scopes if specified\r\n if (this.config.options.requiredScopes?.length) {\r\n const scopeResult = this.validateScopes(\r\n verified.payload.scope,\r\n this.config.options.requiredScopes\r\n );\r\n if (!scopeResult.valid && scopeResult.error) {\r\n return {\r\n data: null,\r\n error: scopeResult.error,\r\n };\r\n }\r\n }\r\n\r\n // Determine token type\r\n const tokenType = verified.payload.cnf?.jkt ? 'DPoP' : 'Bearer';\r\n\r\n // Build validated token\r\n const validatedToken: ValidatedToken = {\r\n claims: verified.payload,\r\n token,\r\n tokenType,\r\n expiresIn: getExpiresIn(verified.payload.exp, now),\r\n };\r\n\r\n return {\r\n data: validatedToken,\r\n error: null,\r\n };\r\n } catch (error) {\r\n if (error instanceof AuthrimServerError) {\r\n return {\r\n data: null,\r\n error: {\r\n code: error.code,\r\n message: error.message,\r\n },\r\n };\r\n }\r\n\r\n return {\r\n data: null,\r\n error: {\r\n code: 'invalid_token',\r\n message: error instanceof Error ? error.message : 'Token validation failed',\r\n },\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Validate required scopes\r\n */\r\n private validateScopes(\r\n tokenScope: string | undefined,\r\n requiredScopes: string[]\r\n ): { valid: boolean; error?: { code: string; message: string } } {\r\n if (!tokenScope) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'insufficient_scope',\r\n message: `Missing required scopes: ${requiredScopes.join(' ')}`,\r\n },\r\n };\r\n }\r\n\r\n const tokenScopes = tokenScope.split(' ');\r\n const missingScopes = requiredScopes.filter((s) => !tokenScopes.includes(s));\r\n\r\n if (missingScopes.length > 0) {\r\n return {\r\n valid: false,\r\n error: {\r\n code: 'insufficient_scope',\r\n message: `Missing required scopes: ${missingScopes.join(' ')}`,\r\n },\r\n };\r\n }\r\n\r\n return { valid: true };\r\n }\r\n}\r\n","/**\r\n * Authentication Utilities\r\n *\r\n * Common authentication-related helper functions.\r\n */\r\n\r\n/**\r\n * Encode client credentials for HTTP Basic authentication\r\n *\r\n * Per RFC 7617 Section 2.1:\r\n * 1. The user-id and password are percent-encoded (RFC 3986)\r\n * 2. Combined with a single colon (:)\r\n * 3. Encoded using UTF-8\r\n * 4. Base64 encoded\r\n *\r\n * @param clientId - Client identifier\r\n * @param clientSecret - Client secret\r\n * @returns Base64-encoded credentials for Authorization header\r\n */\r\nexport function encodeBasicCredentials(clientId: string, clientSecret: string): string {\r\n // Step 1: URL encode (percent-encode) the credentials per RFC 7617\r\n // This handles special characters including colons in the client ID/secret\r\n const encodedId = encodeURIComponent(clientId);\r\n const encodedSecret = encodeURIComponent(clientSecret);\r\n\r\n // Step 2: Combine with colon\r\n const credentials = `${encodedId}:${encodedSecret}`;\r\n\r\n // Step 3 & 4: UTF-8 encode then Base64 encode\r\n const encoder = new TextEncoder();\r\n const credentialsBytes = encoder.encode(credentials);\r\n\r\n // Convert Uint8Array to base64\r\n let binary = '';\r\n for (const byte of credentialsBytes) {\r\n binary += String.fromCharCode(byte);\r\n }\r\n return btoa(binary);\r\n}\r\n","/**\r\n * Token Introspection (RFC 7662)\r\n *\r\n * OAuth 2.0 Token Introspection\r\n */\r\n\r\nimport type { HttpProvider } from '../providers/http.js';\r\nimport type { IntrospectionRequest, IntrospectionResponse } from '../types/token.js';\r\nimport { AuthrimServerError } from '../types/errors.js';\r\nimport { encodeBasicCredentials } from '../utils/auth.js';\r\n\r\n/**\r\n * Introspection client configuration\r\n */\r\nexport interface IntrospectionClientConfig {\r\n /** Introspection endpoint URL */\r\n endpoint: string;\r\n /** Client ID */\r\n clientId: string;\r\n /** Client secret */\r\n clientSecret: string;\r\n /** HTTP provider */\r\n http: HttpProvider;\r\n}\r\n\r\n/**\r\n * Token Introspection Client\r\n *\r\n * Calls the authorization server's introspection endpoint to check token validity.\r\n */\r\nexport class IntrospectionClient {\r\n private readonly config: IntrospectionClientConfig;\r\n\r\n constructor(config: IntrospectionClientConfig) {\r\n this.config = config;\r\n }\r\n\r\n /**\r\n * Introspect a token\r\n *\r\n * @param request - Introspection request\r\n * @returns Introspection response\r\n */\r\n async introspect(request: IntrospectionRequest): Promise<IntrospectionResponse> {\r\n const body = new URLSearchParams();\r\n body.set('token', request.token);\r\n\r\n if (request.token_type_hint) {\r\n body.set('token_type_hint', request.token_type_hint);\r\n }\r\n\r\n // Build Basic auth header with proper encoding per RFC 7617\r\n const credentials = encodeBasicCredentials(this.config.clientId, this.config.clientSecret);\r\n\r\n try {\r\n const response = await this.config.http.fetch(this.config.endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n 'Authorization': `Basic ${credentials}`,\r\n 'Accept': 'application/json',\r\n },\r\n body: body.toString(),\r\n });\r\n\r\n if (!response.ok) {\r\n // Consume response body to release the connection\r\n await response.text().catch(() => {});\r\n throw new AuthrimServerError(\r\n 'introspection_error',\r\n `Introspection request failed: ${response.status} ${response.statusText}`\r\n );\r\n }\r\n\r\n const result = await response.json() as IntrospectionResponse;\r\n return result;\r\n } catch (error) {\r\n if (error instanceof AuthrimServerError) {\r\n throw error;\r\n }\r\n\r\n throw new AuthrimServerError(\r\n 'introspection_error',\r\n `Introspection request failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n { cause: error instanceof Error ? error : undefined }\r\n );\r\n }\r\n }\r\n}\r\n","/**\r\n * Token Revocation (RFC 7009)\r\n *\r\n * OAuth 2.0 Token Revocation\r\n */\r\n\r\nimport type { HttpProvider } from '../providers/http.js';\r\nimport type { RevocationRequest } from '../types/token.js';\r\nimport { AuthrimServerError } from '../types/errors.js';\r\nimport { encodeBasicCredentials } from '../utils/auth.js';\r\n\r\n/**\r\n * Revocation client configuration\r\n */\r\nexport interface RevocationClientConfig {\r\n /** Revocation endpoint URL */\r\n endpoint: string;\r\n /** Client ID */\r\n clientId: string;\r\n /** Client secret */\r\n clientSecret: string;\r\n /** HTTP provider */\r\n http: HttpProvider;\r\n}\r\n\r\n/**\r\n * Token Revocation Client\r\n *\r\n * Calls the authorization server's revocation endpoint to invalidate tokens.\r\n */\r\nexport class RevocationClient {\r\n private readonly config: RevocationClientConfig;\r\n\r\n constructor(config: RevocationClientConfig) {\r\n this.config = config;\r\n }\r\n\r\n /**\r\n * Revoke a token\r\n *\r\n * Note: Per RFC 7009, a successful response (200) does not guarantee\r\n * the token was revoked. The server may silently ignore invalid tokens.\r\n *\r\n * @param request - Revocation request\r\n */\r\n async revoke(request: RevocationRequest): Promise<void> {\r\n const body = new URLSearchParams();\r\n body.set('token', request.token);\r\n\r\n if (request.token_type_hint) {\r\n body.set('token_type_hint', request.token_type_hint);\r\n }\r\n\r\n // Build Basic auth header with proper encoding per RFC 7617\r\n const credentials = encodeBasicCredentials(this.config.clientId, this.config.clientSecret);\r\n\r\n try {\r\n const response = await this.config.http.fetch(this.config.endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded',\r\n 'Authorization': `Basic ${credentials}`,\r\n },\r\n body: body.toString(),\r\n });\r\n\r\n // Per RFC 7009, 200 is success even if token was already revoked or invalid\r\n if (!response.ok) {\r\n // Consume response body to release the connection\r\n await response.text().catch(() => {});\r\n throw new AuthrimServerError(\r\n 'revocation_error',\r\n `Revocation request failed: ${response.status} ${response.statusText}`\r\n );\r\n }\r\n } catch (error) {\r\n if (error instanceof AuthrimServerError) {\r\n throw error;\r\n }\r\n\r\n throw new AuthrimServerError(\r\n 'revocation_error',\r\n `Revocation request failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n { cause: error instanceof Error ? error : undefined }\r\n );\r\n }\r\n }\r\n}\r\n","/**\r\n * JWK Thumbprint (RFC 7638)\r\n *\r\n * Computes a hash of a JWK for use as a key identifier.\r\n */\r\n\r\nimport type { CryptoProvider } from '../providers/crypto.js';\r\n\r\n/**\r\n * Calculate JWK thumbprint\r\n *\r\n * Per RFC 7638, the thumbprint is computed as:\r\n * 1. Construct a JSON object with only the required members, in lexicographic order\r\n * 2. Compute SHA-256 hash of the UTF-8 encoding of the JSON\r\n * 3. Base64url encode the hash\r\n *\r\n * @param jwk - JSON Web Key\r\n * @param crypto - Crypto provider\r\n * @returns Base64url-encoded thumbprint\r\n */\r\nexport async function calculateJwkThumbprint(\r\n jwk: JsonWebKey,\r\n crypto: CryptoProvider\r\n): Promise<string> {\r\n return crypto.calculateThumbprint(jwk);\r\n}\r\n\r\n/**\r\n * Verify that a JWK thumbprint matches the expected value\r\n *\r\n * @param jwk - JSON Web Key to verify\r\n * @param expectedThumbprint - Expected thumbprint value\r\n * @param crypto - Crypto provider\r\n * @returns true if thumbprints match\r\n */\r\nexport async function verifyJwkThumbprint(\r\n jwk: JsonWebKey,\r\n expectedThumbprint: string,\r\n crypto: CryptoProvider\r\n): Promise<boolean> {\r\n const actualThumbprint = await calculateJwkThumbprint(jwk, crypto);\r\n return actualThumbprint === expectedThumbprint;\r\n}\r\n","/**\r\n * DPoP Proof Validator (RFC 9449)\r\n *\r\n * IMPORTANT: This SDK validates DPoP proofs STATELESSLY.\r\n *\r\n * What this SDK validates:\r\n * - Proof signature (embedded JWK in header)\r\n * - htm (HTTP method) matches request\r\n * - htu (HTTP URI) matches request\r\n * - ath (access token hash) if present\r\n * - cnf.jkt binding (thumbprint matches)\r\n * - iat is within acceptable window\r\n *\r\n * What this SDK does NOT validate:\r\n * - jti uniqueness (replay detection)\r\n * → MUST be implemented by the resource server if required\r\n * → Recommended: Store jti with TTL = proof lifetime + clock tolerance\r\n *\r\n * Nonce handling:\r\n * - If server returns use_dpop_nonce error, SDK returns dpop_nonce_required\r\n * - Application is responsible for retry with nonce\r\n */\r\n\r\nimport type { CryptoProvider } from '../providers/crypto.js';\r\nimport type { ClockProvider } from '../providers/clock.js';\r\nimport type { DPoPProofHeader, DPoPProofPayload, DPoPValidationOptions, DPoPValidationResult } from '../types/dpop.js';\r\nimport { base64UrlDecode, base64UrlDecodeString, base64UrlEncode } from '../utils/base64url.js';\r\nimport { timingSafeEqual } from '../utils/timing-safe.js';\r\nimport { calculateJwkThumbprint } from './thumbprint.js';\r\n\r\n/**\r\n * Supported algorithms for DPoP proofs\r\n */\r\nconst SUPPORTED_ALGORITHMS = new Set([\r\n 'RS256', 'RS384', 'RS512',\r\n 'PS256', 'PS384', 'PS512',\r\n 'ES256', 'ES384', 'ES512',\r\n 'EdDSA',\r\n]);\r\n\r\n/**\r\n * Private key parameters that MUST NOT be present in DPoP proof JWK\r\n * Per RFC 9449, the JWK in the header MUST be a public key\r\n */\r\nconst PRIVATE_KEY_PARAMS = new Set([\r\n 'd', // EC/OKP private key, RSA private exponent\r\n 'p', // RSA first prime factor\r\n 'q', // RSA second prime factor\r\n 'dp', // RSA first factor CRT exponent\r\n 'dq', // RSA second factor CRT exponent\r\n 'qi', // RSA first CRT coefficient\r\n 'k', // Symmetric key (should not appear in asymmetric JWKs anyway)\r\n]);\r\n\r\n/**\r\n * Parse a DPoP proof without verification\r\n */\r\nfunction parseDPoPProof(proof: string): { header: DPoPProofHeader; payload: DPoPProofPayload; signature: string } {\r\n const parts = proof.split('.');\r\n\r\n if (parts.length !== 3) {\r\n throw new Error('Invalid DPoP proof format: expected 3 parts');\r\n }\r\n\r\n const [headerPart, payloadPart, signaturePart] = parts;\r\n\r\n if (!headerPart || !payloadPart || !signaturePart) {\r\n throw new Error('Invalid DPoP proof format: empty parts');\r\n }\r\n\r\n const header = JSON.parse(base64UrlDecodeString(headerPart)) as DPoPProofHeader;\r\n const payload = JSON.parse(base64UrlDecodeString(payloadPart)) as DPoPProofPayload;\r\n\r\n return { header, payload, signature: signaturePart };\r\n}\r\n\r\n/**\r\n * Normalize URI for htu comparison\r\n *\r\n * Per RFC 9449, the htu claim contains the scheme, host, and path,\r\n * but NOT the query string or fragment.\r\n *\r\n * @returns Normalized URI or null if URI is invalid\r\n */\r\nfunction normalizeUri(uri: string): string | null {\r\n try {\r\n const url = new URL(uri);\r\n // Return scheme + host + path (no query or fragment)\r\n return `${url.protocol}//${url.host}${url.pathname}`;\r\n } catch {\r\n // Return null for invalid URIs instead of silently passing through\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * DPoP Proof Validator\r\n */\r\nexport class DPoPValidator {\r\n constructor(\r\n private readonly crypto: CryptoProvider,\r\n private readonly clock: ClockProvider\r\n ) {}\r\n\r\n /**\r\n * Validate a DPoP proof\r\n *\r\n * @param proof - DPoP proof JWT\r\n * @param options - Validation options\r\n * @returns Validation result\r\n */\r\n async validate(\r\n proof: string,\r\n options: DPoPValidationOptions\r\n ): Promise<DPoPValidationResult> {\r\n try {\r\n // Parse proof\r\n const { header, payload } = parseDPoPProof(proof);\r\n\r\n // Validate header\r\n const headerResult = this.validateHeader(header);\r\n if (!headerResult.valid) {\r\n return headerResult;\r\n }\r\n\r\n // Validate payload structure\r\n const payloadResult = this.validatePayloadStructure(payload);\r\n if (!payloadResult.valid) {\r\n return payloadResult;\r\n }\r\n\r\n // Verify signature\r\n const signatureResult = await this.verifySignature(proof, header);\r\n if (!signatureResult.valid) {\r\n return signatureResult;\r\n }\r\n\r\n // Validate htm (HTTP method)\r\n if (payload.htm.toUpperCase() !== options.method.toUpperCase()) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_method_mismatch',\r\n errorMessage: `Method mismatch: expected ${options.method}, got ${payload.htm}`,\r\n };\r\n }\r\n\r\n // Validate htu (HTTP URI)\r\n const expectedUri = normalizeUri(options.uri);\r\n const actualUri = normalizeUri(payload.htu);\r\n\r\n // Reject if either URI is invalid\r\n if (expectedUri === null) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: `Invalid expected URI: ${options.uri}`,\r\n };\r\n }\r\n if (actualUri === null) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_uri_mismatch',\r\n errorMessage: `Invalid htu claim: ${payload.htu}`,\r\n };\r\n }\r\n\r\n if (expectedUri !== actualUri) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_uri_mismatch',\r\n errorMessage: `URI mismatch: expected ${expectedUri}, got ${actualUri}`,\r\n };\r\n }\r\n\r\n // Validate iat (issued at)\r\n const maxAge = options.maxAge ?? 60;\r\n const tolerance = options.clockTolerance ?? 60;\r\n const now = this.clock.nowSeconds();\r\n const iatMin = now - maxAge - tolerance;\r\n const iatMax = now + tolerance;\r\n\r\n if (payload.iat < iatMin || payload.iat > iatMax) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_iat_expired',\r\n errorMessage: `Proof iat is outside acceptable window`,\r\n };\r\n }\r\n\r\n // Validate nonce if expected\r\n if (options.expectedNonce !== undefined) {\r\n if (payload.nonce !== options.expectedNonce) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_nonce_required',\r\n errorMessage: 'Invalid or missing DPoP nonce',\r\n };\r\n }\r\n }\r\n\r\n // Calculate thumbprint\r\n const thumbprint = await calculateJwkThumbprint(header.jwk as JsonWebKey, this.crypto);\r\n\r\n // Validate access token hash if provided\r\n if (options.accessToken) {\r\n const athResult = await this.validateAccessTokenHash(\r\n payload.ath,\r\n options.accessToken\r\n );\r\n if (!athResult.valid) {\r\n return athResult;\r\n }\r\n }\r\n\r\n // Validate thumbprint binding if expected\r\n // Use timing-safe comparison to prevent timing attacks\r\n if (options.expectedThumbprint) {\r\n if (!timingSafeEqual(thumbprint, options.expectedThumbprint)) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_binding_mismatch',\r\n errorMessage: 'DPoP proof key does not match token binding',\r\n };\r\n }\r\n }\r\n\r\n return {\r\n valid: true,\r\n thumbprint,\r\n };\r\n } catch (error) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: error instanceof Error ? error.message : 'Invalid DPoP proof',\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Validate DPoP header\r\n */\r\n private validateHeader(header: DPoPProofHeader): DPoPValidationResult {\r\n // Check typ\r\n if (header.typ !== 'dpop+jwt') {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'Invalid DPoP proof type: must be dpop+jwt',\r\n };\r\n }\r\n\r\n // Check alg\r\n if (!SUPPORTED_ALGORITHMS.has(header.alg)) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: `Unsupported algorithm: ${header.alg}`,\r\n };\r\n }\r\n\r\n // Check jwk presence\r\n if (!header.jwk) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'Missing JWK in DPoP proof header',\r\n };\r\n }\r\n\r\n // Check that JWK is a public key (no private key parameters)\r\n // Per RFC 9449 Section 4.2: The JWK MUST NOT contain a private key\r\n const jwkObj = header.jwk as unknown as Record<string, unknown>;\r\n for (const param of PRIVATE_KEY_PARAMS) {\r\n if (param in jwkObj && jwkObj[param] !== undefined) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'DPoP proof JWK contains private key parameters',\r\n };\r\n }\r\n }\r\n\r\n return { valid: true };\r\n }\r\n\r\n /**\r\n * Validate DPoP payload structure\r\n */\r\n private validatePayloadStructure(payload: DPoPProofPayload): DPoPValidationResult {\r\n if (!payload.jti) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'Missing jti claim',\r\n };\r\n }\r\n\r\n if (!payload.htm) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'Missing htm claim',\r\n };\r\n }\r\n\r\n if (!payload.htu) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'Missing htu claim',\r\n };\r\n }\r\n\r\n if (typeof payload.iat !== 'number') {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_invalid',\r\n errorMessage: 'Missing or invalid iat claim',\r\n };\r\n }\r\n\r\n return { valid: true };\r\n }\r\n\r\n /**\r\n * Verify DPoP proof signature\r\n */\r\n private async verifySignature(\r\n proof: string,\r\n header: DPoPProofHeader\r\n ): Promise<DPoPValidationResult> {\r\n try {\r\n // Import public key from header\r\n const cryptoKey = await this.crypto.importJwk(\r\n header.jwk as JsonWebKey,\r\n header.alg\r\n );\r\n\r\n // Get signing input and signature\r\n const dotIndex = proof.lastIndexOf('.');\r\n const signingInput = proof.substring(0, dotIndex);\r\n const signatureB64 = proof.substring(dotIndex + 1);\r\n\r\n const data = new TextEncoder().encode(signingInput);\r\n const signature = base64UrlDecode(signatureB64);\r\n\r\n // Verify\r\n const valid = await this.crypto.verifySignature(\r\n header.alg,\r\n cryptoKey,\r\n signature,\r\n data\r\n );\r\n\r\n if (!valid) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_signature_invalid',\r\n errorMessage: 'DPoP proof signature verification failed',\r\n };\r\n }\r\n\r\n return { valid: true };\r\n } catch (error) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_proof_signature_invalid',\r\n errorMessage: `Failed to verify DPoP proof: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Validate access token hash (ath claim)\r\n *\r\n * Per RFC 9449 Section 4.2:\r\n * When the DPoP proof is used with an access token, the ath claim MUST be present\r\n * and contain the base64url-encoded SHA-256 hash of the access token.\r\n */\r\n private async validateAccessTokenHash(\r\n ath: string | undefined,\r\n accessToken: string\r\n ): Promise<DPoPValidationResult> {\r\n // When an access token is provided, ath is REQUIRED\r\n if (!ath) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_ath_missing',\r\n errorMessage: 'Missing ath claim when access token is present',\r\n };\r\n }\r\n\r\n // Compute expected hash\r\n const tokenBytes = new TextEncoder().encode(accessToken);\r\n const hash = await this.crypto.sha256(tokenBytes);\r\n const expectedAth = base64UrlEncode(hash);\r\n\r\n // Use timing-safe comparison for defense in depth\r\n if (!timingSafeEqual(ath, expectedAth)) {\r\n return {\r\n valid: false,\r\n errorCode: 'dpop_ath_mismatch',\r\n errorMessage: 'Access token hash mismatch',\r\n };\r\n }\r\n\r\n return { valid: true };\r\n }\r\n}\r\n","/**\r\n * AuthrimServer - Main entry point for the server SDK\r\n */\r\n\r\nimport type {\r\n AuthrimServerConfig,\r\n ResolvedAuthrimServerConfig,\r\n} from '../types/config.js';\r\nimport type { TokenValidationResult, IntrospectionResponse } from '../types/token.js';\r\nimport type { DPoPValidationOptions, DPoPValidationResult } from '../types/dpop.js';\r\nimport type { CachedJwk } from '../types/jwk.js';\r\nimport { AuthrimServerError } from '../types/errors.js';\r\nimport { fetchHttpProvider } from '../providers-impl/fetch-http.js';\r\nimport { webCryptoProvider } from '../providers-impl/web-crypto.js';\r\nimport { systemClock } from '../providers-impl/system-clock.js';\r\nimport { memoryCache } from '../providers-impl/memory-cache.js';\r\nimport { JwksManager } from '../jwks/manager.js';\r\nimport { TokenValidator } from '../token/validator.js';\r\nimport { IntrospectionClient } from '../token/introspection.js';\r\nimport { RevocationClient } from '../token/revocation.js';\r\nimport { DPoPValidator } from '../dpop/validator.js';\r\n\r\n/**\r\n * Validate URL uses HTTPS\r\n */\r\nfunction validateHttps(url: string | undefined, name: string, requireHttps: boolean): void {\r\n if (!requireHttps || !url) {\r\n return;\r\n }\r\n\r\n try {\r\n const parsed = new URL(url);\r\n if (parsed.protocol !== 'https:') {\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n `${name} must use HTTPS: ${url}. Set requireHttps: false to allow HTTP in development.`\r\n );\r\n }\r\n } catch (error) {\r\n if (error instanceof AuthrimServerError) {\r\n throw error;\r\n }\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n `Invalid ${name} URL: ${url}`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Resolve configuration with defaults\r\n */\r\nfunction resolveConfig(config: AuthrimServerConfig): ResolvedAuthrimServerConfig {\r\n const issuer = Array.isArray(config.issuer) ? config.issuer : [config.issuer];\r\n const audience = Array.isArray(config.audience) ? config.audience : [config.audience];\r\n const requireHttps = config.requireHttps ?? true;\r\n\r\n // Validate HTTPS for security-critical URLs\r\n for (const iss of issuer) {\r\n validateHttps(iss, 'issuer', requireHttps);\r\n }\r\n validateHttps(config.jwksUri, 'jwksUri', requireHttps);\r\n validateHttps(config.introspectionEndpoint, 'introspectionEndpoint', requireHttps);\r\n validateHttps(config.revocationEndpoint, 'revocationEndpoint', requireHttps);\r\n\r\n return {\r\n issuer,\r\n audience,\r\n jwksUri: config.jwksUri,\r\n clockToleranceSeconds: config.clockToleranceSeconds ?? 60,\r\n jwksRefreshIntervalMs: config.jwksRefreshIntervalMs ?? 3600_000,\r\n introspectionEndpoint: config.introspectionEndpoint,\r\n revocationEndpoint: config.revocationEndpoint,\r\n clientCredentials: config.clientCredentials,\r\n http: config.http ?? fetchHttpProvider(),\r\n crypto: config.crypto ?? webCryptoProvider(),\r\n clock: config.clock ?? systemClock(),\r\n jwksCache: config.jwksCache ?? memoryCache<CachedJwk[]>({ ttlMs: config.jwksRefreshIntervalMs ?? 3600_000 }),\r\n requireHttps,\r\n };\r\n}\r\n\r\n/**\r\n * AuthrimServer\r\n *\r\n * Main class for server-side token validation and DPoP handling.\r\n */\r\nexport class AuthrimServer {\r\n private readonly config: ResolvedAuthrimServerConfig;\r\n private jwksManager: JwksManager | null = null;\r\n private tokenValidator: TokenValidator | null = null;\r\n private dpopValidator: DPoPValidator | null = null;\r\n private introspectionClient: IntrospectionClient | null = null;\r\n private revocationClient: RevocationClient | null = null;\r\n private initPromise: Promise<void> | null = null;\r\n private initialized = false;\r\n\r\n constructor(config: AuthrimServerConfig) {\r\n this.config = resolveConfig(config);\r\n }\r\n\r\n /**\r\n * Initialize the server (discovers JWKS endpoint if needed)\r\n *\r\n * This method is idempotent and thread-safe. Multiple concurrent calls\r\n * will wait for the same initialization to complete.\r\n */\r\n async init(): Promise<void> {\r\n // Fast path: already initialized\r\n if (this.initialized) {\r\n return;\r\n }\r\n\r\n // If an initialization is in progress, wait for it\r\n if (this.initPromise) {\r\n return this.initPromise;\r\n }\r\n\r\n // Start initialization\r\n this.initPromise = this.doInit();\r\n\r\n try {\r\n await this.initPromise;\r\n this.initialized = true;\r\n } catch (error) {\r\n // Reset promise on failure so subsequent calls can retry\r\n this.initPromise = null;\r\n throw error;\r\n }\r\n }\r\n\r\n private async doInit(): Promise<void> {\r\n // Discover JWKS URI if not provided\r\n let jwksUri = this.config.jwksUri;\r\n\r\n if (!jwksUri) {\r\n jwksUri = await this.discoverJwksUri();\r\n }\r\n\r\n // Initialize JWKS Manager\r\n this.jwksManager = new JwksManager({\r\n jwksUri,\r\n cacheTtlMs: this.config.jwksRefreshIntervalMs,\r\n http: this.config.http,\r\n crypto: this.config.crypto,\r\n clock: this.config.clock,\r\n cache: this.config.jwksCache,\r\n });\r\n\r\n // Initialize Token Validator\r\n this.tokenValidator = new TokenValidator({\r\n jwksManager: this.jwksManager,\r\n crypto: this.config.crypto,\r\n clock: this.config.clock,\r\n options: {\r\n issuer: this.config.issuer,\r\n audience: this.config.audience,\r\n clockToleranceSeconds: this.config.clockToleranceSeconds,\r\n },\r\n });\r\n\r\n // Initialize DPoP Validator\r\n this.dpopValidator = new DPoPValidator(this.config.crypto, this.config.clock);\r\n\r\n // Initialize Introspection Client if endpoint is provided\r\n if (this.config.introspectionEndpoint && this.config.clientCredentials) {\r\n this.introspectionClient = new IntrospectionClient({\r\n endpoint: this.config.introspectionEndpoint,\r\n clientId: this.config.clientCredentials.clientId,\r\n clientSecret: this.config.clientCredentials.clientSecret,\r\n http: this.config.http,\r\n });\r\n }\r\n\r\n // Initialize Revocation Client if endpoint is provided\r\n if (this.config.revocationEndpoint && this.config.clientCredentials) {\r\n this.revocationClient = new RevocationClient({\r\n endpoint: this.config.revocationEndpoint,\r\n clientId: this.config.clientCredentials.clientId,\r\n clientSecret: this.config.clientCredentials.clientSecret,\r\n http: this.config.http,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Discover JWKS URI from OpenID Configuration\r\n */\r\n private async discoverJwksUri(): Promise<string> {\r\n const issuer = this.config.issuer[0];\r\n if (!issuer) {\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n 'No issuer configured'\r\n );\r\n }\r\n\r\n const configUrl = `${issuer.replace(/\\/$/, '')}/.well-known/openid-configuration`;\r\n\r\n try {\r\n const response = await this.config.http.fetch(configUrl, {\r\n headers: { Accept: 'application/json' },\r\n });\r\n\r\n if (!response.ok) {\r\n // Consume response body to release the connection\r\n await response.text().catch(() => {});\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n `Failed to fetch OpenID Configuration: ${response.status}`\r\n );\r\n }\r\n\r\n const config = await response.json() as { jwks_uri?: string };\r\n\r\n if (!config.jwks_uri) {\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n 'OpenID Configuration missing jwks_uri'\r\n );\r\n }\r\n\r\n // Validate HTTPS for discovered JWKS URI\r\n validateHttps(config.jwks_uri, 'discovered jwks_uri', this.config.requireHttps);\r\n\r\n return config.jwks_uri;\r\n } catch (error) {\r\n if (error instanceof AuthrimServerError) {\r\n throw error;\r\n }\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n `Failed to discover JWKS URI: ${error instanceof Error ? error.message : 'Unknown error'}`,\r\n { cause: error instanceof Error ? error : undefined }\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Validate a JWT access token\r\n *\r\n * @param token - JWT string\r\n * @returns Validation result\r\n */\r\n async validateToken(token: string): Promise<TokenValidationResult> {\r\n await this.init();\r\n\r\n if (!this.tokenValidator) {\r\n return {\r\n data: null,\r\n error: { code: 'configuration_error', message: 'Token validator not initialized' },\r\n };\r\n }\r\n\r\n return this.tokenValidator.validate(token);\r\n }\r\n\r\n /**\r\n * Validate a DPoP proof\r\n *\r\n * @param proof - DPoP proof JWT\r\n * @param options - Validation options\r\n * @returns Validation result\r\n */\r\n async validateDPoP(\r\n proof: string,\r\n options: DPoPValidationOptions\r\n ): Promise<DPoPValidationResult> {\r\n await this.init();\r\n\r\n if (!this.dpopValidator) {\r\n return {\r\n valid: false,\r\n errorCode: 'configuration_error',\r\n errorMessage: 'DPoP validator not initialized',\r\n };\r\n }\r\n\r\n return this.dpopValidator.validate(proof, options);\r\n }\r\n\r\n /**\r\n * Introspect a token\r\n *\r\n * @param token - Token to introspect\r\n * @param tokenTypeHint - Optional token type hint\r\n * @returns Introspection response\r\n */\r\n async introspect(\r\n token: string,\r\n tokenTypeHint?: 'access_token' | 'refresh_token'\r\n ): Promise<IntrospectionResponse> {\r\n await this.init();\r\n\r\n if (!this.introspectionClient) {\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n 'Introspection endpoint not configured'\r\n );\r\n }\r\n\r\n return this.introspectionClient.introspect({\r\n token,\r\n token_type_hint: tokenTypeHint,\r\n });\r\n }\r\n\r\n /**\r\n * Revoke a token\r\n *\r\n * @param token - Token to revoke\r\n * @param tokenTypeHint - Optional token type hint\r\n */\r\n async revoke(\r\n token: string,\r\n tokenTypeHint?: 'access_token' | 'refresh_token'\r\n ): Promise<void> {\r\n await this.init();\r\n\r\n if (!this.revocationClient) {\r\n throw new AuthrimServerError(\r\n 'configuration_error',\r\n 'Revocation endpoint not configured'\r\n );\r\n }\r\n\r\n return this.revocationClient.revoke({\r\n token,\r\n token_type_hint: tokenTypeHint,\r\n });\r\n }\r\n\r\n /**\r\n * Get the resolved configuration\r\n */\r\n getConfig(): ResolvedAuthrimServerConfig {\r\n return this.config;\r\n }\r\n\r\n /**\r\n * Invalidate JWKS cache\r\n */\r\n invalidateJwksCache(): void {\r\n this.jwksManager?.invalidate();\r\n }\r\n}\r\n\r\n/**\r\n * Create an AuthrimServer instance\r\n *\r\n * @param config - Server configuration\r\n * @returns AuthrimServer instance\r\n */\r\nexport function createAuthrimServer(config: AuthrimServerConfig): AuthrimServer {\r\n return new AuthrimServer(config);\r\n}\r\n","/**\r\n * Back-Channel Logout Validator\r\n *\r\n * Implements OpenID Connect Back-Channel Logout 1.0\r\n * https://openid.net/specs/openid-connect-backchannel-1_0.html\r\n *\r\n * Back-channel logout allows the OP to notify RPs of logout events\r\n * via direct HTTP calls (server-to-server).\r\n *\r\n * NOTE: This validator performs claims validation only.\r\n * JWT signature verification MUST be performed separately using JWKS.\r\n */\r\n\r\nimport type { LogoutTokenClaims } from '../types/session.js';\r\nimport type { JwtHeader } from '../types/claims.js';\r\nimport { parseJwt, InsecureAlgorithmError } from '../token/verify-jwt.js';\r\nimport { timingSafeEqual } from '../utils/timing-safe.js';\r\n\r\n/**\r\n * Back-channel logout event URI\r\n */\r\nexport const BACKCHANNEL_LOGOUT_EVENT = 'http://schemas.openid.net/event/backchannel-logout';\r\n\r\n/**\r\n * Back-channel logout validation error codes\r\n */\r\nexport type BackChannelLogoutErrorCode =\r\n | 'invalid_format'\r\n | 'insecure_algorithm'\r\n | 'invalid_issuer'\r\n | 'invalid_audience'\r\n | 'invalid_events'\r\n | 'token_expired'\r\n | 'iat_too_old'\r\n | 'not_yet_valid'\r\n | 'missing_sub_and_sid'\r\n | 'sub_mismatch'\r\n | 'sid_mismatch'\r\n | 'missing_jti'\r\n | 'nonce_present';\r\n\r\n/**\r\n * Options for validating a logout token\r\n */\r\nexport interface BackChannelLogoutValidationOptions {\r\n /** Expected issuer */\r\n issuer: string;\r\n /** Expected audience (client_id) */\r\n audience: string;\r\n /** Maximum age in seconds (default: 60) */\r\n maxAge?: number;\r\n /** Clock skew tolerance in seconds (default: 30) */\r\n clockSkew?: number;\r\n /** Expected session ID (optional) */\r\n expectedSid?: string;\r\n /** Expected subject (optional) */\r\n expectedSub?: string;\r\n}\r\n\r\n/**\r\n * Result of back-channel logout token validation\r\n */\r\nexport interface BackChannelLogoutValidationResult {\r\n /** Whether the token is valid */\r\n valid: boolean;\r\n /** Validated claims (if valid) */\r\n claims?: LogoutTokenClaims;\r\n /** JWT header (if valid) */\r\n header?: JwtHeader;\r\n /** Error message (if invalid) */\r\n error?: string;\r\n /** Error code (if invalid) */\r\n errorCode?: BackChannelLogoutErrorCode;\r\n}\r\n\r\n/**\r\n * Back-Channel Logout Validator\r\n *\r\n * Validates logout_token JWTs per OIDC Back-Channel Logout 1.0.\r\n *\r\n * ## Security Notes\r\n *\r\n * 1. **JWT Signature Verification**: This class performs claims validation only.\r\n * JWT signature verification MUST be performed separately using JWKS\r\n * before calling validate(). The caller MUST reject tokens with alg: none.\r\n *\r\n * 2. **Algorithm Validation**: Per Section 2.6, \"alg with the value none MUST NOT\r\n * be used\". This validator explicitly rejects alg: none tokens.\r\n *\r\n * 3. **JTI Replay Protection**: This validator checks for jti presence only.\r\n * The application MUST implement jti replay protection by:\r\n * - Storing used jti values (at least until token expiry + clock skew)\r\n * - Rejecting tokens with previously-used jti values\r\n *\r\n * 4. **Expiration Validation**: Per Section 2.4, exp is REQUIRED. This validator\r\n * checks that the token has not expired (with clock skew tolerance).\r\n *\r\n * Usage (Node.js server receiving back-channel logout request):\r\n * ```typescript\r\n * // 1. Receive logout_token from POST body\r\n * const logoutToken = req.body.logout_token;\r\n *\r\n * // 2. Verify JWT signature using JWKS (MUST reject alg: none)\r\n * const jwks = await jwksManager.getJwks();\r\n * const verified = await verifyJwtSignature(logoutToken, key, crypto);\r\n * if (!verified) {\r\n * return res.status(400).send('Invalid signature');\r\n * }\r\n *\r\n * // 3. Validate claims (includes alg: none rejection)\r\n * const validator = new BackChannelLogoutValidator();\r\n * const result = validator.validate(logoutToken, {\r\n * issuer: 'https://op.example.com',\r\n * audience: 'my-client-id'\r\n * });\r\n *\r\n * if (!result.valid) {\r\n * return res.status(400).send('Invalid logout token');\r\n * }\r\n *\r\n * // 4. Check for jti replay (application responsibility)\r\n * if (await jtiStore.has(result.claims.jti)) {\r\n * return res.status(400).send('Token replay detected');\r\n * }\r\n * await jtiStore.set(result.claims.jti, true, result.claims.exp + clockSkew);\r\n *\r\n * // 5. Perform logout for sub and/or sid\r\n * await logoutUser(result.claims.sub, result.claims.sid);\r\n * ```\r\n */\r\nexport class BackChannelLogoutValidator {\r\n /**\r\n * Validate a logout_token\r\n *\r\n * Per OIDC Back-Channel Logout 1.0 Section 2.4 and 2.6, the logout_token MUST:\r\n * - Be a valid JWT with alg != \"none\"\r\n * - Contain iss, aud, iat, exp, jti claims\r\n * - Contain events claim with back-channel logout event\r\n * - Contain either sub or sid (or both)\r\n * - NOT contain a nonce claim\r\n * - NOT be expired (exp validation)\r\n *\r\n * @param logoutToken - JWT logout token to validate\r\n * @param options - Validation options\r\n * @returns Validation result\r\n */\r\n validate(\r\n logoutToken: string,\r\n options: BackChannelLogoutValidationOptions\r\n ): BackChannelLogoutValidationResult {\r\n // Decode JWT (without signature verification)\r\n let header: JwtHeader;\r\n let claims: LogoutTokenClaims;\r\n\r\n try {\r\n const decoded = parseJwt<LogoutTokenClaims>(logoutToken);\r\n header = decoded.header;\r\n claims = decoded.payload;\r\n } catch (error) {\r\n // Per Section 2.6: \"alg with the value none MUST NOT be used\"\r\n // parseJwt throws InsecureAlgorithmError for alg: none\r\n if (error instanceof InsecureAlgorithmError) {\r\n return {\r\n valid: false,\r\n error: 'Algorithm \"none\" is not allowed for logout tokens',\r\n errorCode: 'insecure_algorithm',\r\n };\r\n }\r\n return {\r\n valid: false,\r\n error: 'Invalid JWT format',\r\n errorCode: 'invalid_format',\r\n };\r\n }\r\n\r\n // Validate issuer using constant-time comparison to prevent timing attacks\r\n if (!claims.iss || !timingSafeEqual(claims.iss, options.issuer)) {\r\n return {\r\n valid: false,\r\n error: 'Issuer validation failed',\r\n errorCode: 'invalid_issuer',\r\n };\r\n }\r\n\r\n // Validate audience using constant-time comparison\r\n const audiences = Array.isArray(claims.aud) ? claims.aud : [claims.aud];\r\n const audienceValid = audiences.some((aud) => timingSafeEqual(aud, options.audience));\r\n if (!audienceValid) {\r\n return {\r\n valid: false,\r\n error: 'Audience validation failed',\r\n errorCode: 'invalid_audience',\r\n };\r\n }\r\n\r\n // Validate events claim\r\n // Must contain the back-channel logout event with an empty object value\r\n if (!claims.events || typeof claims.events !== 'object') {\r\n return {\r\n valid: false,\r\n error: 'Missing or invalid events claim',\r\n errorCode: 'invalid_events',\r\n };\r\n }\r\n\r\n const logoutEvent = claims.events[BACKCHANNEL_LOGOUT_EVENT];\r\n if (logoutEvent === undefined) {\r\n return {\r\n valid: false,\r\n error: `Missing ${BACKCHANNEL_LOGOUT_EVENT} in events claim`,\r\n errorCode: 'invalid_events',\r\n };\r\n }\r\n\r\n // The event value MUST be an empty object {}\r\n // Check: must be object, not null, not array, and have no keys\r\n if (\r\n typeof logoutEvent !== 'object' ||\r\n logoutEvent === null ||\r\n Array.isArray(logoutEvent) ||\r\n Object.keys(logoutEvent).length !== 0\r\n ) {\r\n return {\r\n valid: false,\r\n error: 'Back-channel logout event must be an empty object',\r\n errorCode: 'invalid_events',\r\n };\r\n }\r\n\r\n // Validate jti (required for replay protection)\r\n if (!claims.jti) {\r\n return {\r\n valid: false,\r\n error: 'Missing jti claim',\r\n errorCode: 'missing_jti',\r\n };\r\n }\r\n\r\n // Validate that either sub or sid is present\r\n if (!claims.sub && !claims.sid) {\r\n return {\r\n valid: false,\r\n error: 'Logout token must contain either sub or sid (or both)',\r\n errorCode: 'missing_sub_and_sid',\r\n };\r\n }\r\n\r\n // Validate nonce is NOT present (per spec, logout tokens MUST NOT contain nonce)\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n if ('nonce' in claims && (claims as any).nonce !== undefined) {\r\n return {\r\n valid: false,\r\n error: 'Logout token must not contain nonce claim',\r\n errorCode: 'nonce_present',\r\n };\r\n }\r\n\r\n // Validate timing\r\n const now = Math.floor(Date.now() / 1000);\r\n const maxAge = options.maxAge ?? 60;\r\n const clockSkew = options.clockSkew ?? 30;\r\n\r\n // Per Section 2.4: exp is REQUIRED\r\n // Per Section 2.6 Step 3: Validate exp claim\r\n if (!claims.exp) {\r\n return {\r\n valid: false,\r\n error: 'Missing exp claim',\r\n errorCode: 'token_expired',\r\n };\r\n }\r\n\r\n // Check token is not expired (with clock skew tolerance)\r\n if (now > claims.exp + clockSkew) {\r\n return {\r\n valid: false,\r\n error: `Token expired at ${claims.exp}`,\r\n errorCode: 'token_expired',\r\n };\r\n }\r\n\r\n // Check iat is not too old (additional security measure)\r\n if (claims.iat && now - claims.iat > maxAge + clockSkew) {\r\n return {\r\n valid: false,\r\n error: `Token too old: iat ${claims.iat} is more than ${maxAge} seconds ago`,\r\n errorCode: 'iat_too_old',\r\n };\r\n }\r\n\r\n // Check iat is not in the future (with clock skew tolerance)\r\n if (claims.iat && claims.iat > now + clockSkew) {\r\n return {\r\n valid: false,\r\n error: `Token iat is in the future: ${claims.iat}`,\r\n errorCode: 'not_yet_valid',\r\n };\r\n }\r\n\r\n // Validate expected sub if provided\r\n // Use constant-time comparison to prevent timing attacks\r\n if (options.expectedSub && claims.sub && !timingSafeEqual(claims.sub, options.expectedSub)) {\r\n return {\r\n valid: false,\r\n error: 'Subject validation failed',\r\n errorCode: 'sub_mismatch',\r\n };\r\n }\r\n\r\n // Validate expected sid if provided\r\n // Use constant-time comparison to prevent timing attacks\r\n if (options.expectedSid && claims.sid && !timingSafeEqual(claims.sid, options.expectedSid)) {\r\n return {\r\n valid: false,\r\n error: 'Session ID validation failed',\r\n errorCode: 'sid_mismatch',\r\n };\r\n }\r\n\r\n return {\r\n valid: true,\r\n claims,\r\n header,\r\n };\r\n }\r\n\r\n /**\r\n * Extract claims from a logout token without validation\r\n *\r\n * Useful for inspecting the token before/during validation.\r\n *\r\n * @param logoutToken - JWT logout token\r\n * @returns Claims or null if invalid format\r\n */\r\n extractClaims(logoutToken: string): LogoutTokenClaims | null {\r\n try {\r\n const decoded = parseJwt<LogoutTokenClaims>(logoutToken);\r\n return decoded.payload;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Extract header from a logout token without validation\r\n *\r\n * Useful for getting kid to select the correct JWKS key.\r\n *\r\n * @param logoutToken - JWT logout token\r\n * @returns Header or null if invalid format\r\n */\r\n extractHeader(logoutToken: string): JwtHeader | null {\r\n try {\r\n const decoded = parseJwt<LogoutTokenClaims>(logoutToken);\r\n return decoded.header;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunk7POGA5LZ_cjs=require('../chunk-7POGA5LZ.cjs');Object.defineProperty(exports,"fetchHttpProvider",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.a}});Object.defineProperty(exports,"memoryCache",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.h}});Object.defineProperty(exports,"systemClock",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.g}});Object.defineProperty(exports,"webCryptoProvider",{enumerable:true,get:function(){return chunk7POGA5LZ_cjs.f}});//# sourceMappingURL=index.cjs.map
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { H as HttpProvider, a as CryptoProvider, b as ClockProvider, M as MemoryCacheOptions, c as CacheProvider } from '../config-I0GIVJA_.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetch-based HTTP Provider Implementation
|
|
5
|
+
*
|
|
6
|
+
* Uses globalThis.fetch for maximum runtime compatibility.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a fetch-based HTTP provider
|
|
11
|
+
*
|
|
12
|
+
* This implementation uses the global fetch API which is available in:
|
|
13
|
+
* - Node.js 18+
|
|
14
|
+
* - Bun
|
|
15
|
+
* - Deno
|
|
16
|
+
* - Cloudflare Workers
|
|
17
|
+
* - Vercel Edge Functions
|
|
18
|
+
* - All modern browsers
|
|
19
|
+
*
|
|
20
|
+
* @returns HttpProvider implementation
|
|
21
|
+
*/
|
|
22
|
+
declare function fetchHttpProvider(): HttpProvider;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Web Crypto API Provider Implementation
|
|
26
|
+
*
|
|
27
|
+
* Uses crypto.subtle for cryptographic operations.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a Web Crypto API-based crypto provider
|
|
32
|
+
*
|
|
33
|
+
* This implementation uses crypto.subtle which is available in:
|
|
34
|
+
* - Node.js 18+
|
|
35
|
+
* - Bun
|
|
36
|
+
* - Deno
|
|
37
|
+
* - Cloudflare Workers
|
|
38
|
+
* - Vercel Edge Functions
|
|
39
|
+
* - All modern browsers
|
|
40
|
+
*
|
|
41
|
+
* @returns CryptoProvider implementation
|
|
42
|
+
*/
|
|
43
|
+
declare function webCryptoProvider(): CryptoProvider;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* System Clock Provider Implementation
|
|
47
|
+
*
|
|
48
|
+
* Uses Date.now() for time operations.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a system clock provider
|
|
53
|
+
*
|
|
54
|
+
* Uses Date.now() which is available in all JavaScript runtimes.
|
|
55
|
+
*
|
|
56
|
+
* @returns ClockProvider implementation
|
|
57
|
+
*/
|
|
58
|
+
declare function systemClock(): ClockProvider;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* In-Memory Cache Provider Implementation
|
|
62
|
+
*
|
|
63
|
+
* Simple TTL-based cache with optional size limits.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create an in-memory cache provider
|
|
68
|
+
*
|
|
69
|
+
* Features:
|
|
70
|
+
* - TTL-based expiration
|
|
71
|
+
* - Optional size limits with LRU eviction
|
|
72
|
+
* - Lazy cleanup on access
|
|
73
|
+
*
|
|
74
|
+
* @param options - Cache configuration options
|
|
75
|
+
* @returns CacheProvider implementation
|
|
76
|
+
*/
|
|
77
|
+
declare function memoryCache<T>(options?: MemoryCacheOptions): CacheProvider<T>;
|
|
78
|
+
|
|
79
|
+
export { CacheProvider, ClockProvider, CryptoProvider, HttpProvider, fetchHttpProvider, memoryCache, systemClock, webCryptoProvider };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { H as HttpProvider, a as CryptoProvider, b as ClockProvider, M as MemoryCacheOptions, c as CacheProvider } from '../config-I0GIVJA_.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetch-based HTTP Provider Implementation
|
|
5
|
+
*
|
|
6
|
+
* Uses globalThis.fetch for maximum runtime compatibility.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a fetch-based HTTP provider
|
|
11
|
+
*
|
|
12
|
+
* This implementation uses the global fetch API which is available in:
|
|
13
|
+
* - Node.js 18+
|
|
14
|
+
* - Bun
|
|
15
|
+
* - Deno
|
|
16
|
+
* - Cloudflare Workers
|
|
17
|
+
* - Vercel Edge Functions
|
|
18
|
+
* - All modern browsers
|
|
19
|
+
*
|
|
20
|
+
* @returns HttpProvider implementation
|
|
21
|
+
*/
|
|
22
|
+
declare function fetchHttpProvider(): HttpProvider;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Web Crypto API Provider Implementation
|
|
26
|
+
*
|
|
27
|
+
* Uses crypto.subtle for cryptographic operations.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a Web Crypto API-based crypto provider
|
|
32
|
+
*
|
|
33
|
+
* This implementation uses crypto.subtle which is available in:
|
|
34
|
+
* - Node.js 18+
|
|
35
|
+
* - Bun
|
|
36
|
+
* - Deno
|
|
37
|
+
* - Cloudflare Workers
|
|
38
|
+
* - Vercel Edge Functions
|
|
39
|
+
* - All modern browsers
|
|
40
|
+
*
|
|
41
|
+
* @returns CryptoProvider implementation
|
|
42
|
+
*/
|
|
43
|
+
declare function webCryptoProvider(): CryptoProvider;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* System Clock Provider Implementation
|
|
47
|
+
*
|
|
48
|
+
* Uses Date.now() for time operations.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a system clock provider
|
|
53
|
+
*
|
|
54
|
+
* Uses Date.now() which is available in all JavaScript runtimes.
|
|
55
|
+
*
|
|
56
|
+
* @returns ClockProvider implementation
|
|
57
|
+
*/
|
|
58
|
+
declare function systemClock(): ClockProvider;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* In-Memory Cache Provider Implementation
|
|
62
|
+
*
|
|
63
|
+
* Simple TTL-based cache with optional size limits.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create an in-memory cache provider
|
|
68
|
+
*
|
|
69
|
+
* Features:
|
|
70
|
+
* - TTL-based expiration
|
|
71
|
+
* - Optional size limits with LRU eviction
|
|
72
|
+
* - Lazy cleanup on access
|
|
73
|
+
*
|
|
74
|
+
* @param options - Cache configuration options
|
|
75
|
+
* @returns CacheProvider implementation
|
|
76
|
+
*/
|
|
77
|
+
declare function memoryCache<T>(options?: MemoryCacheOptions): CacheProvider<T>;
|
|
78
|
+
|
|
79
|
+
export { CacheProvider, ClockProvider, CryptoProvider, HttpProvider, fetchHttpProvider, memoryCache, systemClock, webCryptoProvider };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|