{"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/chunk-SSVFQCSN.cjs","../src/auth/helpers.ts","../src/auth/OAuthProxy.ts","../src/auth/types.ts","../src/auth/utils/claimsExtractor.ts","../src/auth/utils/consent.ts","../src/auth/utils/jwtIssuer.ts","../src/auth/utils/pkce.ts","../src/auth/utils/tokenStore.ts","../src/auth/providers/AuthProvider.ts","../src/auth/providers/AzureProvider.ts","../src/auth/providers/GitHubProvider.ts","../src/auth/providers/GoogleProvider.ts","../src/auth/providers/OAuthProvider.ts","../src/auth/utils/diskStore.ts","../src/auth/utils/jwks.ts"],"names":["randomBytes"],"mappings":"AAAA;ACaO,SAAS,cAAA,CACd,OAAA,EACG;AACH,EAAA,GAAA,CAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,KAAA,CAAM,8BAA8B,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,OAAA;AACT;AAKO,SAAS,UAAA,CAAA,GACX,MAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GACN,MAAA,CAAO,KAAA;AAAA,IAAM,CAAC,KAAA,EAAA,GACZ,OAAO,MAAA,IAAU,WAAA,EAAa,KAAA,CAAM,IAAI,EAAA,EAAI;AAAA,EAC9C,CAAA;AACJ;AAKO,SAAS,UAAA,CAAA,GACX,MAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GACN,MAAA,CAAO,IAAA,CAAK,CAAC,KAAA,EAAA,GAAW,OAAO,MAAA,IAAU,WAAA,EAAa,KAAA,CAAM,IAAI,EAAA,EAAI,KAAM,CAAA;AAC9E;AAKO,SAAS,WAAA,CAAmC,IAAA,EAAkB;AACnE,EAAA,OAAO,KAAA,IAAS,KAAA,EAAA,GAAa,KAAA,IAAS,IAAA;AACxC;AAKO,SAAS,WAAA,CAAA,GACX,YAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GAAqB;AAC3B,IAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,KAAA;AAClB,IAAA,MAAM,KAAA,EAAQ,IAAA,CAAiC,IAAA;AAC/C,IAAA,OAAO,OAAO,KAAA,IAAS,SAAA,GAAY,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA;AAAA,EAC/D,CAAA;AACF;AAKO,SAAS,aAAA,CAAA,GACX,cAAA,EACmB;AACtB,EAAA,OAAO,CAAC,IAAA,EAAA,GAAqB;AAC3B,IAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,KAAA;AAClB,IAAA,MAAM,WAAA,EAAc,IAAA,CAAiC,MAAA;AACrD,IAAA,GAAA,CAAI,CAAC,UAAA,EAAY,OAAO,KAAA;AAExB,IAAA,MAAM,SAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,UAAU,EAAA,EACrC,IAAI,GAAA,CAAI,UAAU,EAAA,EAClB,WAAA,WAAsB,IAAA,EACpB,WAAA,kBACA,IAAI,GAAA,CAAI,CAAA;AAEd,IAAA,OAAO,cAAA,CAAe,KAAA,CAAM,CAAC,KAAA,EAAA,GAAU,QAAA,CAAS,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA,EAC5D,CAAA;AACF;ADjDA;AACA;AE9BA,gCAA4B;AAC5B,0BAAkB;AFgClB;AACA;AG/BO,IAAM,yBAAA,EAA2B,IAAA;AACjC,IAAM,oCAAA,EAAsC,OAAA;AAC5C,IAAM,0BAAA,EAA4B,MAAA;AAClC,IAAM,+BAAA,EAAiC,GAAA;AACvC,IAAM,wBAAA,EAA0B,GAAA;AHiCvC;AACA;AIvCO,IAAM,gBAAA,YAAN,MAAsB;AAAA,EACnB;AAAA;AAAA,iBAGS,iBAAA,kBAAmB,IAAI,GAAA,CAAI;AAAA,IAC1C,KAAA;AAAA,IACA,WAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,EACF,CAAC,EAAA;AAAA,EAED,WAAA,CAAY,MAAA,EAAiD;AAE3D,IAAA,GAAA,CAAI,OAAO,OAAA,IAAW,SAAA,EAAW;AAC/B,MAAA,OAAA,EAAS,OAAA,EAAS,CAAC,EAAA,EAAI,EAAE,eAAA,EAAiB,KAAA,EAAO,WAAA,EAAa,MAAM,CAAA;AAAA,IACtE;AAGA,IAAA,IAAA,CAAK,OAAA,EAAS;AAAA,MACZ,kBAAA,EAAoB,MAAA,CAAO,mBAAA,GAAsB,KAAA;AAAA,MACjD,aAAA,EAAe,MAAA,CAAO,aAAA;AAAA,MACtB,aAAA,EAAe,MAAA,CAAO,cAAA,GAAiB,CAAC,CAAA;AAAA,MACxC,WAAA,EACE,MAAA,CAAO,YAAA,IAAgB,KAAA,EAAA,EAAY,MAAA,CAAO,YAAA,EAAc,KAAA;AAAA;AAAA,MAC1D,eAAA,EAAiB,MAAA,CAAO,gBAAA,IAAoB,KAAA;AAAA;AAAA,MAC5C,WAAA,EAAa,MAAA,CAAO,YAAA,IAAgB,KAAA;AAAA;AAAA,MACpC,iBAAA,EAAmB,MAAA,CAAO,kBAAA,GAAqB;AAAA,IACjD,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAA,CACJ,KAAA,EACA,SAAA,EACyC;AAEzC,IAAA,GAAA,CAAI,UAAA,IAAc,SAAA,GAAY,CAAC,IAAA,CAAK,MAAA,CAAO,eAAA,EAAiB;AAC1D,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,GAAA,CAAI,UAAA,IAAc,KAAA,GAAQ,CAAC,IAAA,CAAK,MAAA,CAAO,WAAA,EAAa;AAClD,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,GAAA,CAAI,CAAC,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA,EAAG;AAEtB,MAAA,OAAO,IAAA;AAAA,IACT;AAIA,IAAA,MAAM,QAAA,EAAU,IAAA,CAAK,gBAAA,CAAiB,KAAK,CAAA;AAC3C,IAAA,GAAA,CAAI,CAAC,OAAA,EAAS;AACZ,MAAA,OAAO,IAAA;AAAA,IACT;AAGA,IAAA,MAAM,SAAA,EAAW,IAAA,CAAK,YAAA,CAAa,OAAO,CAAA;AAG1C,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,QAAQ,CAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAA,CACN,MAAA,EACyB;AACzB,IAAA,MAAM,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,WAAA;AAG3B,IAAA,GAAA,CAAI,OAAA,IAAW,MAAA,GAAS,OAAA,IAAW,GAAA,GAAM,OAAA,IAAW,KAAA,CAAA,EAAW;AAC7D,MAAA,OAAO,MAAA;AAAA,IACT;AAGA,IAAA,MAAM,OAAA,EAAkC,CAAC,CAAA;AACzC,IAAA,IAAA,CAAA,MAAW,CAAC,GAAA,EAAK,KAAK,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,MAAA,MAAA,CAAO,CAAA,EAAA;AACT,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACF,IAAA;AACI,MAAA;AACF,MAAA;AACF,QAAA;AACF,MAAA;AAGM,MAAA;AACC,MAAA;AACA,IAAA;AAEC,MAAA;AACD,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAGA,IAAA;AAEN,IAAA;AAEM,MAAA;AACF,QAAA;AACF,MAAA;AAGI,MAAA;AACF,QAAA;AACF,MAAA;AAIE,MAAA;AAGA,QAAA;AACF,MAAA;AAGK,MAAA;AACH,QAAA;AACA,QAAA;AACF,MAAA;AAEO,MAAA;AACT,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AACL,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACF,IAAA;AACK,MAAA;AACT,IAAA;AAEM,IAAA;AAGF,IAAA;AACI,MAAA;AACE,MAAA;AACV,IAAA;AAEI,IAAA;AACK,MAAA;AACT,IAAA;AAGU,IAAA;AAEH,MAAA;AACH,QAAA;AACF,MAAA;AAGI,MAAA;AACI,QAAA;AACA,QAAA;AACN,QAAA;AACM,MAAA;AAEN,QAAA;AACF,MAAA;AACF,IAAA;AAGO,IAAA;AACT,EAAA;AACF;AJnBc;AACA;AKtLL;AAOI;AACH,EAAA;AAEI,EAAA;AACL,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAIQ,IAAA;AACJ,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AAEC,IAAA;AACL,MAAA;AACE,QAAA;AACF,MAAA;AACQ,MAAA;AACT,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACU,IAAA;AAED,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAA;AAgKmC;AAAA;AAAA;AAAA;AAAA,uCAAA;AAK0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAA;AAOW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8DAAA;AAaL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBnF,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACQ,IAAA;AACA,IAAA;AAEI,IAAA;AACZ,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACM,IAAA;AACK,MAAA;AAEF,MAAA;AACH,QAAA;AACF,MAAA;AAEM,MAAA;AACA,MAAA;AAEF,MAAA;AACF,QAAA;AACF,MAAA;AAEM,MAAA;AAGA,MAAA;AACF,MAAA;AACF,QAAA;AACF,MAAA;AAEO,MAAA;AACD,IAAA;AACC,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKmB,EAAA;AACX,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACP,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKoB,EAAA;AAEZ,IAAA;AACG,MAAA;AACC,MAAA;AACR,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKa,EAAA;AACJ,IAAA;AACT,EAAA;AACF;ALwJc;AACA;AM1eL;AACA;AAOH;AAgEO;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAEI,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAAA;AAMA,EAAA;AAIQ,IAAA;AACA,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAMQ,IAAA;AACA,IAAA;AAEA,IAAA;AACC,MAAA;AACL,MAAA;AACK,MAAA;AACA,MAAA;AACA,MAAA;AACL,MAAA;AACA,MAAA;AAAA;AAEI,MAAA;AACN,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAMQ,IAAA;AACA,IAAA;AAEA,IAAA;AACC,MAAA;AACL,MAAA;AACK,MAAA;AACA,MAAA;AACA,MAAA;AACL,MAAA;AACA,MAAA;AAAA;AAEI,MAAA;AACN,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACA,IAAA;AACI,MAAA;AACF,MAAA;AACF,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAEO,MAAA;AAGD,MAAA;AACF,MAAA;AACF,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAGM,MAAA;AACJ,QAAA;AACF,MAAA;AAGM,MAAA;AAEF,MAAA;AACF,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAEI,MAAA;AACF,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAEI,MAAA;AACF,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAEO,MAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACO,IAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKmC,EAAA;AAC3B,IAAA;AACD,IAAA;AACE,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKkB,EAAA;AACV,IAAA;AACC,MAAA;AACA,MAAA;AACP,IAAA;AAEM,IAAA;AACA,IAAA;AACJ,MAAA;AACF,IAAA;AAEM,IAAA;AAEI,IAAA;AACZ,EAAA;AACF;ANgYc;AACA;AOjoBL;AAOI;AAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAOd,EAAA;AAID,IAAA;AACK,MAAA;AACT,IAAA;AAEI,IAAA;AACI,MAAA;AACD,MAAA;AACE,MAAA;AACT,IAAA;AAEU,IAAA;AACZ,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,EAAA;AACC,IAAA;AACA,IAAA;AAEC,IAAA;AACL,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,EAAA;AACD,IAAA;AACI,MAAA;AACR,IAAA;AAIM,IAAA;AACA,IAAA;AAEC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASO,EAAA;AAKA,IAAA;AACI,MAAA;AACT,IAAA;AAEI,IAAA;AACK,MAAA;AACT,IAAA;AAEI,IAAA;AACI,MAAA;AACC,MAAA;AACT,IAAA;AAGO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOe,EAAA;AACN,IAAA;AAKT,EAAA;AACF;APmmBc;AACA;AQ7sBd;AACE;AACA;AACA;AACA;AACK;AAaM;AACS,kBAAA;AACZ,EAAA;AACA,EAAA;AAEI,EAAA;AACL,IAAA;AAEC,IAAA;AACD,IAAA;AACP,EAAA;AAEM,EAAA;AACE,IAAA;AACR,EAAA;AAEM,EAAA;AACE,IAAA;AACR,EAAA;AAEU,EAAA;AACF,IAAA;AAED,IAAA;AACI,MAAA;AACT,IAAA;AAEI,IAAA;AACI,MAAA;AACJ,QAAA;AACK,QAAA;AACP,MAAA;AACO,MAAA;AACA,IAAA;AACC,MAAA;AACD,MAAA;AACT,IAAA;AACF,EAAA;AAEW,EAAA;AACH,IAAA;AACC,MAAA;AACA,MAAA;AACP,IAAA;AACM,IAAA;AACR,EAAA;AAEc,EAAA;AACN,IAAA;AACI,IAAA;AACF,MAAA;AACR,IAAA;AAEO,IAAA;AACD,IAAA;AACA,IAAA;AAEA,IAAA;AAEwD,IAAA;AAC5D,MAAA;AACF,IAAA;AAEI,IAAA;AACJ,IAAA;AAEO,IAAA;AACT,EAAA;AAEc,EAAA;AACN,IAAA;AACA,IAAA;AAEF,IAAA;AACJ,IAAA;AAGM,IAAA;AAKI,IAAA;AACZ,EAAA;AACF;AAKa;AACH,kBAAA;AACmC,kBAAA;AAE/B,EAAA;AAEL,IAAA;AACG,MAAA;AACN,MAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AACE,IAAA;AACA,IAAA;AAEN,IAAA;AACM,MAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACO,MAAA;AACP,IAAA;AACF,EAAA;AAEM,EAAA;AACC,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAKgB,EAAA;AACL,IAAA;AACP,MAAA;AACK,MAAA;AACP,IAAA;AACK,IAAA;AACP,EAAA;AAEU,EAAA;AACF,IAAA;AAED,IAAA;AACI,MAAA;AACT,IAAA;AAEU,IAAA;AACH,MAAA;AACE,MAAA;AACT,IAAA;AAEO,IAAA;AACT,EAAA;AAEW,EAAA;AACH,IAAA;AAED,IAAA;AACH,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKe,EAAA;AACN,IAAA;AACT,EAAA;AACF;AR2pBc;AACA;AExyBD;AACH,kBAAA;AACA,kBAAA;AACA,kBAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,kBAAA;AACA,EAAA;AACA,kBAAA;AAEI,EAAA;AACL,IAAA;AAAS;AAAA;AAAA;AAAA;AAKZ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAiB;AACjB,MAAA;AACA,MAAA;AACA,MAAA;AACG,MAAA;AACL,IAAA;AAGI,IAAA;AAIE,IAAA;AAGD,IAAA;AAEG,MAAA;AAKN,MAAA;AACF,IAAA;AAEK,IAAA;AACA,IAAA;AACI,MAAA;AACT,IAAA;AAGS,IAAA;AAED,MAAA;AAED,MAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AAGM,IAAA;AAKF,IAAA;AACG,MAAA;AACP,IAAA;AAGK,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AAEC,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEI,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAII,IAAA;AACI,MAAA;AACR,IAAA;AAMU,IAAA;AACF,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGI,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGM,IAAA;AAGG,IAAA;AACA,MAAA;AACL,QAAA;AACK,QAAA;AACP,MAAA;AACF,IAAA;AAGO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKgB,EAAA;AACL,IAAA;AACP,MAAA;AACK,MAAA;AACP,IAAA;AAEK,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AAGA,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAKI,IAAA;AACI,MAAA;AACR,IAAA;AAEM,IAAA;AACD,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGI,IAAA;AACI,MAAA;AACR,IAAA;AAGI,IAAA;AACG,MAAA;AACG,QAAA;AACJ,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAEM,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AAEK,MAAA;AACG,QAAA;AACR,MAAA;AACF,IAAA;AAGI,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGA,IAAA;AACK,IAAA;AAGI,IAAA;AAEA,MAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACK,IAAA;AAEC,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AAEI,MAAA;AACF,QAAA;AACF,MAAA;AAEI,MAAA;AACF,QAAA;AACF,MAAA;AAEI,MAAA;AACF,QAAA;AACF,MAAA;AAEO,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACA,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGS,IAAA;AACA,MAAA;AACT,IAAA;AAGO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAqBS,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACQ,MAAA;AACR,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AAGF,IAAA;AACI,MAAA;AACA,MAAA;AACR,IAAA;AAEK,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGM,IAAA;AACD,IAAA;AACG,MAAA;AACR,IAAA;AAKU,IAAA;AACH,MAAA;AACC,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGM,IAAA;AAGA,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AAGK,IAAA;AAGC,IAAA;AACN,IAAA;AACA,IAAA;AAEO,IAAA;AACL,MAAA;AACE,QAAA;AACF,MAAA;AACQ,MAAA;AACT,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAED,IAAA;AACG,MAAA;AACR,IAAA;AAEM,IAAA;AACD,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEI,IAAA;AAEG,MAAA;AAEA,MAAA;AACG,QAAA;AACJ,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AAEO,MAAA;AACL,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AAGA,IAAA;AACK,IAAA;AAEE,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AAGM,IAAA;AACD,MAAA;AACT,IAAA;AAGM,IAAA;AACD,IAAA;AACI,MAAA;AACT,IAAA;AAGM,IAAA;AACJ,MAAA;AACF,IAAA;AAIK,IAAA;AACI,MAAA;AACT,IAAA;AAGM,IAAA;AACJ,MAAA;AACF,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AAEC,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAGA,IAAA;AACO,MAAA;AACG,QAAA;AACJ,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAGM,IAAA;AACA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACM,QAAA;AACN,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAKA,IAAA;AACO,MAAA;AACP,IAAA;AAGM,IAAA;AACJ,MAAA;AACA,MAAA;AAAiD;AAEjD,MAAA;AACA,MAAA;AACA,MAAA;AAA0B;AAC1B,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACM,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AAEA,MAAA;AACF,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACF,IAAA;AACK,MAAA;AACT,IAAA;AACS,MAAA;AACT,IAAA;AACS,MAAA;AACF,IAAA;AACE,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKwB,EAAA;AAChB,IAAA;AAGN,IAAA;AACM,MAAA;AACG,QAAA;AACP,MAAA;AACF,IAAA;AAGA,IAAA;AACM,MAAA;AACG,QAAA;AACP,MAAA;AACF,IAAA;AAGU,IAAA;AACZ,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AAGN,IAAA;AACA,IAAA;AAEA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,QAAA;AACP,MAAA;AACI,MAAA;AACJ,MAAA;AACA,MAAA;AACO,MAAA;AACA,MAAA;AACT,IAAA;AAEK,IAAA;AAEE,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AAIN,IAAA;AAGA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGK,IAAA;AACH,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAGI,IAAA;AACM,MAAA;AACV,IAAA;AAEM,IAAA;AACE,MAAA;AACN,MAAA;AACQ,MAAA;AACT,IAAA;AAEI,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACI,QAAA;AAIN,QAAA;AACA,QAAA;AACM,MAAA;AACN,QAAA;AACF,MAAA;AACM,MAAA;AACR,IAAA;AAEM,IAAA;AAEC,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AACF,IAAA;AACF,MAAA;AACR,IAAA;AAEM,IAAA;AACD,IAAA;AACG,MAAA;AACR,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMc,EAAA;AAGF,IAAA;AACD,MAAA;AACT,IAAA;AAEM,IAAA;AAGA,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AACI,IAAA;AACK,MAAA;AACT,IAAA;AAGI,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACI,MAAA;AAEF,QAAA;AACM,UAAA;AACF,YAAA;AACF,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AAIA,IAAA;AAEA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,QAAA;AACP,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEK,IAAA;AAEE,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACCA,IAAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACCA,IAAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMQ,EAAA;AACA,IAAA;AACA,IAAA;AACC,MAAA;AACP,IAAA;AACO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACA,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AAGN,IAAA;AAGA,IAAA;AACJ,MAAA;AACA,MAAA;AACI,MAAA;AACN,IAAA;AAGK,IAAA;AACH,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAGI,IAAA;AACM,MAAA;AACV,IAAA;AAGM,IAAA;AACE,MAAA;AACN,MAAA;AACQ,MAAA;AACT,IAAA;AAEI,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACI,QAAA;AAIN,QAAA;AACA,QAAA;AACM,MAAA;AACN,QAAA;AACF,MAAA;AACM,MAAA;AACR,IAAA;AAEM,IAAA;AAEC,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AAGF,IAAA;AACF,MAAA;AACR,IAAA;AAEM,IAAA;AACD,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACI,IAAA;AACF,MAAA;AACR,IAAA;AAEM,IAAA;AAMD,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAEK,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEK,IAAA;AACG,MAAA;AACJ,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACQ,MAAA;AACV,IAAA;AAEI,IAAA;AACF,MAAA;AACF,IAAA;AAEM,IAAA;AAIA,IAAA;AACJ,MAAA;AACF,IAAA;AACM,IAAA;AAEA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEO,IAAA;AACG,MAAA;AACR,MAAA;AACQ,MAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMc,EAAA;AAIF,IAAA;AACF,MAAA;AACR,IAAA;AAGM,IAAA;AAGF,IAAA;AACA,IAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACK,IAAA;AACL,MAAA;AACF,IAAA;AAIM,IAAA;AAOA,IAAA;AACA,IAAA;AACA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGM,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAGM,IAAA;AAGA,IAAA;AACJ,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACK,QAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACF,IAAA;AAGI,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACM,MAAA;AAGA,MAAA;AACJ,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AAMF,IAAA;AACF,MAAA;AACR,IAAA;AAEM,IAAA;AAEA,IAAA;AAEA,IAAA;AACA,IAAA;AAMA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACA,IAAA;AACJ,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACK,QAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACF,IAAA;AAEI,IAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACM,MAAA;AAEA,MAAA;AACJ,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACA,IAAA;AACE,MAAA;AACR,IAAA;AACO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMc,EAAA;AASN,IAAA;AAKA,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACD,IAAA;AAGG,IAAA;AACI,MAAA;AACA,MAAA;AAEA,MAAA;AACJ,QAAA;AACA,QAAA;AAGA,QAAA;AACA,QAAA;AAGA,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AAEO,MAAA;AACT,IAAA;AAGM,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACA,IAAA;AAEE,IAAA;AACA,IAAA;AACN,MAAA;AACQ,MAAA;AACV,IAAA;AACQ,IAAA;AACA,IAAA;AAEJ,IAAA;AACM,MAAA;AACV,IAAA;AAGU,IAAA;AACA,MAAA;AACN,QAAA;AACA,QAAA;AACF,MAAA;AACQ,MAAA;AACV,IAAA;AAEO,IAAA;AACL,MAAA;AACE,QAAA;AACF,MAAA;AACQ,MAAA;AACT,IAAA;AACH,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AAIN,IAAA;AAGA,IAAA;AACJ,MAAA;AACA,MAAA;AACI,MAAA;AACN,IAAA;AAGK,IAAA;AACH,MAAA;AACA,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACF,IAAA;AAGI,IAAA;AACM,MAAA;AACV,IAAA;AAGM,IAAA;AACE,MAAA;AACN,MAAA;AACQ,MAAA;AACT,IAAA;AAEI,IAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACI,QAAA;AAIN,QAAA;AACA,QAAA;AACM,MAAA;AACN,QAAA;AACF,MAAA;AACM,MAAA;AACR,IAAA;AAEM,IAAA;AAIC,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACO,MAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKQ,EAAA;AACD,IAAA;AACE,MAAA;AACC,IAAA;AACV,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcQ,EAAA;AACF,IAAA;AACM,MAAA;AACF,IAAA;AACC,MAAA;AACT,IAAA;AAEM,IAAA;AACF,IAAA;AACK,MAAA;AACT,IAAA;AAEO,IAAA;AACT,EAAA;AACF;AAKa;AAEF,EAAA;AAIG,IAAA;AAJH,IAAA;AACA,IAAA;AACA,IAAA;AAGF,IAAA;AACP,EAAA;AAEqB,EAAA;AACZ,IAAA;AACE,MAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACS,IAAA;AACL,MAAA;AACQ,MAAA;AACT,IAAA;AACH,EAAA;AACF;AFicc;AACA;AShvDQ;AAGV,EAAA;AAAA;AAAA;AAAA;AAIwB,EAAA;AACtB,IAAA;AACH,MAAA;AACP,IAAA;AACO,IAAA;AACT,EAAA;AAEQ,EAAA;AAEI,EAAA;AACL,IAAA;AAEP,EAAA;AAAA;AAAA;AAAA;AAAA;AAMM,EAAA;AAGC,IAAA;AAEI,MAAA;AACT,IAAA;AAEM,IAAA;AACD,IAAA;AACI,MAAA;AACT,IAAA;AAEM,IAAA;AACA,IAAA;AAED,IAAA;AACI,MAAA;AACT,IAAA;AAEO,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AAYS,IAAA;AACL,MAAA;AACA,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACO,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKuB,EAAA;AACd,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AASU,EAAA;AACD,IAAA;AACL,MAAA;AACA,MAAA;AAGA,MAAA;AACA,MAAA;AACQ,MAAA;AACV,IAAA;AACF,EAAA;AAUF;ATysDc;AACA;AU12DD;AACH,EAAA;AAEI,EAAA;AACJ,IAAA;AACD,IAAA;AACP,EAAA;AAEU,EAAA;AACD,IAAA;AAAe;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACQ,MAAA;AACR,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AAEU,EAAA;AACA,IAAA;AACV,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AACF;AVu2Dc;AACA;AWt5DD;AACC,EAAA;AACJ,IAAA;AACR,EAAA;AAEU,EAAA;AACD,IAAA;AAAe;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACQ,MAAA;AACR,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AAEU,EAAA;AACA,IAAA;AACV,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AACF;AXo5Dc;AACA;AYx7DD;AACC,EAAA;AACJ,IAAA;AACR,EAAA;AAEU,EAAA;AACD,IAAA;AAAe;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACQ,MAAA;AACR,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AAEU,EAAA;AACA,IAAA;AACV,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AACF;AZs7Dc;AACA;Aaj+DD;AAGD,EAAA;AAEE,EAAA;AACJ,IAAA;AACD,IAAA;AACP,EAAA;AAEU,EAAA;AACD,IAAA;AAAe;AAAA;AAAA;AAIpB,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACQ,MAAA;AACR,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAED,IAAA;AACH,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AAEU,EAAA;AACA,IAAA;AACV,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AACF;Ab29Dc;AACA;AcjhEL;AACA;AAgCI;AACH,mBAAA;AACA,EAAA;AACA,EAAA;AAEI,EAAA;AACL,IAAA;AACA,IAAA;AAGK,IAAA;AAGJ,IAAA;AACD,IAAA;AACE,MAAA;AACJ,IAAA;AACL,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACA,IAAA;AACI,MAAA;AACA,MAAA;AACA,MAAA;AAEN,MAAA;AACO,QAAA;AACH,UAAA;AACF,QAAA;AAEI,QAAA;AACF,UAAA;AACA,UAAA;AACA,UAAA;AAEI,UAAA;AACF,YAAA;AACF,UAAA;AACF,QAAA;AAEE,UAAA;AACI,UAAA;AACF,YAAA;AACF,UAAA;AAEA,UAAA;AACF,QAAA;AACF,MAAA;AACO,IAAA;AACC,MAAA;AACV,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACE,IAAA;AACF,IAAA;AACI,MAAA;AACC,IAAA;AAEF,MAAA;AACH,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKgB,EAAA;AACL,IAAA;AACP,MAAA;AACK,MAAA;AACP,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKU,EAAA;AACF,IAAA;AAEF,IAAA;AACI,MAAA;AACA,MAAA;AAGF,MAAA;AACI,QAAA;AACN,QAAA;AACF,MAAA;AAEO,MAAA;AACA,IAAA;AAEF,MAAA;AACH,QAAA;AACF,MAAA;AACQ,MAAA;AACD,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKW,EAAA;AACH,IAAA;AAEA,IAAA;AACA,IAAA;AAEA,IAAA;AACJ,MAAA;AACA,MAAA;AACF,IAAA;AAEI,IAAA;AACI,MAAA;AACC,IAAA;AACC,MAAA;AACF,MAAA;AACR,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKM,EAAA;AACA,IAAA;AACI,MAAA;AACA,MAAA;AACC,MAAA;AACD,IAAA;AACC,MAAA;AACT,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKc,EAAA;AACR,IAAA;AACI,MAAA;AACD,MAAA;AACG,QAAA;AACR,MAAA;AACO,IAAA;AACF,MAAA;AACG,QAAA;AACD,MAAA;AACC,QAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKoB,EAAA;AAEZ,IAAA;AACC,IAAA;AACT,EAAA;AACF;Adw9Dc;AACA;Ae5lED;AACH,EAAA;AACA,EAAA;AACA,mBAAA;AACA,EAAA;AAEI,EAAA;AACL,IAAA;AACH,MAAA;AAAe;AACf,MAAA;AAAkB;AACf,MAAA;AACH,MAAA;AACQ,MAAA;AACV,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAKA,EAAA;AACS,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMM,EAAA;AACE,IAAA;AAGD,IAAA;AACK,MAAA;AACR,MAAA;AACE,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBM,EAAA;AACA,IAAA;AAEI,MAAA;AAGA,MAAA;AAEF,MAAA;AACF,QAAA;AACF,MAAA;AAEI,MAAA;AACF,QAAA;AACF,MAAA;AAEQ,MAAA;AACN,QAAA;AACK,QAAA;AACL,QAAA;AACF,MAAA;AAIM,MAAA;AACC,QAAA;AACL,QAAA;AACK,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACL,QAAA;AACG,QAAA;AAAA;AACL,MAAA;AAEO,MAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACO,IAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMc,EAAA;AACH,IAAA;AACP,MAAA;AACF,IAAA;AAEI,IAAA;AACG,MAAA;AACA,MAAA;AAGA,MAAA;AACC,QAAA;AACJ,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACO,IAAA;AACD,MAAA;AACJ,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAA;AAIF,MAAA;AACF,IAAA;AACF,EAAA;AAAA;AAAA;AAAA;AAAA;AAMmB,EAAA;AACZ,IAAA;AACK,MAAA;AACV,IAAA;AAEI,IAAA;AACK,MAAA;AACT,IAAA;AAEU,IAAA;AACD,MAAA;AACT,IAAA;AAEQ,IAAA;AACV,EAAA;AACF;Af2kEc;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/fastmcp/fastmcp/dist/chunk-SSVFQCSN.cjs","sourcesContent":[null,"/**\n * Authentication Helper Functions\n * Utility functions for use with canAccess on tools, resources, and prompts\n */\n\nimport type { OAuthSession } from \"./providers/AuthProvider.js\";\n\ntype SessionAuth = Record<string, unknown> | undefined;\n\n/**\n * Extract and type-cast OAuth session from canAccess context.\n * Throws if session is undefined (use with canAccess: requireAuth).\n */\nexport function getAuthSession<T extends OAuthSession = OAuthSession>(\n  session: SessionAuth,\n): T {\n  if (!session) {\n    throw new Error(\"Session is not authenticated\");\n  }\n  return session as T;\n}\n\n/**\n * Combines multiple canAccess checks with AND logic.\n */\nexport function requireAll<T extends SessionAuth>(\n  ...checks: Array<((auth: T) => boolean) | boolean>\n): (auth: T) => boolean {\n  return (auth: T): boolean =>\n    checks.every((check) =>\n      typeof check === \"function\" ? check(auth) : check,\n    );\n}\n\n/**\n * Combines multiple canAccess checks with OR logic.\n */\nexport function requireAny<T extends SessionAuth>(\n  ...checks: Array<((auth: T) => boolean) | boolean>\n): (auth: T) => boolean {\n  return (auth: T): boolean =>\n    checks.some((check) => (typeof check === \"function\" ? check(auth) : check));\n}\n\n/**\n * Requires any authenticated session.\n */\nexport function requireAuth<T extends SessionAuth>(auth: T): boolean {\n  return auth !== undefined && auth !== null;\n}\n\n/**\n * Requires session to have a specific role (OR logic for multiple roles).\n */\nexport function requireRole<T extends SessionAuth>(\n  ...allowedRoles: string[]\n): (auth: T) => boolean {\n  return (auth: T): boolean => {\n    if (!auth) return false;\n    const role = (auth as Record<string, unknown>).role;\n    return typeof role === \"string\" && allowedRoles.includes(role);\n  };\n}\n\n/**\n * Requires session to have specific scopes.\n */\nexport function requireScopes<T extends SessionAuth>(\n  ...requiredScopes: string[]\n): (auth: T) => boolean {\n  return (auth: T): boolean => {\n    if (!auth) return false;\n    const authScopes = (auth as Record<string, unknown>).scopes;\n    if (!authScopes) return false;\n\n    const scopeSet = Array.isArray(authScopes)\n      ? new Set(authScopes)\n      : authScopes instanceof Set\n        ? authScopes\n        : new Set();\n\n    return requiredScopes.every((scope) => scopeSet.has(scope));\n  };\n}\n","/**\n * OAuth 2.1 Proxy Implementation\n * Provides DCR-compatible interface for non-DCR OAuth providers\n */\n\nimport { randomBytes } from \"crypto\";\nimport { z } from \"zod\";\n\nimport type {\n  AuthorizationParams,\n  ClientCode,\n  DCRRequest,\n  DCRResponse,\n  OAuthError,\n  OAuthProxyConfig,\n  OAuthTransaction,\n  ProxyDCRClient,\n  RefreshRequest,\n  TokenRequest,\n  TokenResponse,\n  TokenStorage,\n  UpstreamTokenSet,\n} from \"./types.js\";\n\nimport {\n  DEFAULT_ACCESS_TOKEN_TTL,\n  DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH,\n  DEFAULT_AUTHORIZATION_CODE_TTL,\n  DEFAULT_REFRESH_TOKEN_TTL,\n  DEFAULT_TRANSACTION_TTL,\n} from \"./types.js\";\nimport { ClaimsExtractor } from \"./utils/claimsExtractor.js\";\nimport { ConsentManager } from \"./utils/consent.js\";\nimport { JWTIssuer } from \"./utils/jwtIssuer.js\";\nimport { PKCEUtils } from \"./utils/pkce.js\";\nimport {\n  EncryptedTokenStorage,\n  MemoryTokenStorage,\n} from \"./utils/tokenStore.js\";\n\n/**\n * OAuth 2.1 Proxy\n * Acts as transparent intermediary between MCP clients and upstream OAuth providers\n */\nexport class OAuthProxy {\n  private claimsExtractor: ClaimsExtractor | null = null;\n  private cleanupInterval: NodeJS.Timeout | null = null;\n  private clientCodes: Map<string, ClientCode> = new Map();\n  private config: OAuthProxyConfig;\n  private consentManager: ConsentManager;\n  private jwtIssuer?: JWTIssuer;\n  private registeredClients: Map<string, ProxyDCRClient> = new Map();\n  private tokenStorage: TokenStorage;\n  private transactions: Map<string, OAuthTransaction> = new Map();\n\n  constructor(config: OAuthProxyConfig) {\n    this.config = {\n      // Empty by default. Framework users must explicitly configure the URIs they\n      // trust, per RFC 6819 §4.1.5. The previous default (`[\"https://*\", \"http://localhost:*\"]`)\n      // allowed open DCR registration of any https URL, enabling CWE-601 open-redirect\n      // attacks against /oauth/authorize.\n      allowedRedirectUriPatterns: [],\n      authorizationCodeTtl: DEFAULT_AUTHORIZATION_CODE_TTL,\n      consentRequired: true,\n      enableTokenSwap: true, // Enabled by default for security\n      redirectPath: \"/oauth/callback\",\n      transactionTtl: DEFAULT_TRANSACTION_TTL,\n      upstreamTokenEndpointAuthMethod: \"client_secret_basic\",\n      ...config,\n    };\n\n    // Set up token storage with encryption by default (matches Python's secure defaults)\n    let storage = config.tokenStorage || new MemoryTokenStorage();\n\n    // Wrap storage with encryption if not already encrypted\n    // Check if it's already an EncryptedTokenStorage instance\n    const isAlreadyEncrypted =\n      storage.constructor.name === \"EncryptedTokenStorage\";\n\n    if (!isAlreadyEncrypted && config.encryptionKey !== false) {\n      // Auto-generate encryption key if not provided\n      const encryptionKey =\n        typeof config.encryptionKey === \"string\"\n          ? config.encryptionKey\n          : this.generateSigningKey();\n\n      storage = new EncryptedTokenStorage(storage, encryptionKey);\n    }\n\n    this.tokenStorage = storage;\n    this.consentManager = new ConsentManager(\n      config.consentSigningKey || this.generateSigningKey(),\n    );\n\n    // Initialize JWT issuer if token swap is enabled\n    if (this.config.enableTokenSwap) {\n      // Auto-generate signing key if not provided\n      const signingKey = this.config.jwtSigningKey || this.generateSigningKey();\n\n      this.jwtIssuer = new JWTIssuer({\n        audience: this.config.baseUrl,\n        issuer: this.config.baseUrl,\n        signingKey: signingKey,\n      });\n    }\n\n    // Initialize claims extractor (enabled by default)\n    const claimsConfig =\n      config.customClaimsPassthrough !== undefined\n        ? config.customClaimsPassthrough\n        : true; // Default: enabled\n\n    if (claimsConfig !== false) {\n      this.claimsExtractor = new ClaimsExtractor(claimsConfig);\n    }\n\n    // Start periodic cleanup\n    this.startCleanup();\n  }\n\n  /**\n   * OAuth authorization endpoint\n   */\n  async authorize(params: AuthorizationParams): Promise<Response> {\n    // Validate parameters\n    if (!params.client_id || !params.redirect_uri || !params.response_type) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Missing required parameters\",\n      );\n    }\n\n    if (params.response_type !== \"code\") {\n      throw new OAuthProxyError(\n        \"unsupported_response_type\",\n        \"Only 'code' response type is supported\",\n      );\n    }\n\n    // RFC 6749 §5.2 - reject unknown clients with invalid_client.\n    // The proxy exposes a single upstream identity; any other client_id is rejected.\n    if (params.client_id !== this.config.upstreamClientId) {\n      throw new OAuthProxyError(\"invalid_client\", \"Unknown client_id\");\n    }\n\n    // RFC 6749 §3.1.2.3 / RFC 6819 §4.1.5 - the redirect_uri MUST match one\n    // previously registered by the client (exact string comparison). Skipping\n    // this check is CWE-601: an attacker can steal an authorization code by\n    // passing their own URL as redirect_uri.\n    if (!this.registeredClients.has(params.redirect_uri)) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"redirect_uri is not registered for this client\",\n      );\n    }\n\n    // Validate PKCE if provided\n    if (params.code_challenge && !params.code_challenge_method) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"code_challenge_method required when code_challenge is present\",\n      );\n    }\n\n    // Create transaction\n    const transaction = await this.createTransaction(params);\n\n    // If consent required, show consent screen\n    if (this.config.consentRequired && !transaction.consentGiven) {\n      return this.consentManager.createConsentResponse(\n        transaction,\n        this.getProviderName(),\n      );\n    }\n\n    // Redirect to upstream provider\n    return this.redirectToUpstream(transaction);\n  }\n\n  /**\n   * Stop cleanup interval and destroy resources\n   */\n  destroy(): void {\n    if (this.cleanupInterval) {\n      clearInterval(this.cleanupInterval);\n      this.cleanupInterval = null;\n    }\n\n    this.transactions.clear();\n    this.clientCodes.clear();\n    this.registeredClients.clear();\n  }\n\n  /**\n   * Token endpoint - exchange authorization code for tokens\n   */\n  async exchangeAuthorizationCode(\n    request: TokenRequest,\n  ): Promise<TokenResponse> {\n    if (request.grant_type !== \"authorization_code\") {\n      throw new OAuthProxyError(\n        \"unsupported_grant_type\",\n        \"Only authorization_code grant type is supported\",\n      );\n    }\n\n    // RFC 6749 §5.2 - reject unknown clients. The proxy exposes a single\n    // upstream identity; any other client_id is rejected here as well as at\n    // authorize(), so stolen codes cannot be exchanged by arbitrary callers.\n    if (request.client_id !== this.config.upstreamClientId) {\n      throw new OAuthProxyError(\"invalid_client\", \"Unknown client_id\");\n    }\n\n    const clientCode = this.clientCodes.get(request.code);\n    if (!clientCode) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Invalid or expired authorization code\",\n      );\n    }\n\n    // Validate client\n    if (clientCode.clientId !== request.client_id) {\n      throw new OAuthProxyError(\"invalid_client\", \"Client ID mismatch\");\n    }\n\n    // Validate PKCE if used\n    if (clientCode.codeChallenge) {\n      if (!request.code_verifier) {\n        throw new OAuthProxyError(\n          \"invalid_request\",\n          \"code_verifier required for PKCE\",\n        );\n      }\n\n      const valid = PKCEUtils.validateChallenge(\n        request.code_verifier,\n        clientCode.codeChallenge,\n        clientCode.codeChallengeMethod,\n      );\n\n      if (!valid) {\n        throw new OAuthProxyError(\"invalid_grant\", \"Invalid PKCE verifier\");\n      }\n    }\n\n    // Check if code was already used\n    if (clientCode.used) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Authorization code already used\",\n      );\n    }\n\n    // Mark code as used\n    clientCode.used = true;\n    this.clientCodes.set(request.code, clientCode);\n\n    // Return tokens based on token swap setting\n    if (this.config.enableTokenSwap && this.jwtIssuer) {\n      // Token swap pattern: issue short-lived JWTs and store upstream tokens\n      return await this.issueSwappedTokens(\n        clientCode.clientId,\n        clientCode.upstreamTokens,\n      );\n    } else {\n      // Pass-through pattern: return upstream tokens directly\n      const response: TokenResponse = {\n        access_token: clientCode.upstreamTokens.accessToken,\n        expires_in: clientCode.upstreamTokens.expiresIn,\n        token_type: clientCode.upstreamTokens.tokenType,\n      };\n\n      if (clientCode.upstreamTokens.refreshToken) {\n        response.refresh_token = clientCode.upstreamTokens.refreshToken;\n      }\n\n      if (clientCode.upstreamTokens.idToken) {\n        response.id_token = clientCode.upstreamTokens.idToken;\n      }\n\n      if (clientCode.upstreamTokens.scope.length > 0) {\n        response.scope = clientCode.upstreamTokens.scope.join(\" \");\n      }\n\n      return response;\n    }\n  }\n\n  /**\n   * Token endpoint - refresh access token\n   */\n  async exchangeRefreshToken(request: RefreshRequest): Promise<TokenResponse> {\n    if (request.grant_type !== \"refresh_token\") {\n      throw new OAuthProxyError(\n        \"unsupported_grant_type\",\n        \"Only refresh_token grant type is supported\",\n      );\n    }\n\n    // Check for swap mode\n    if (this.config.enableTokenSwap && this.jwtIssuer) {\n      return await this.handleSwapModeRefresh(request);\n    }\n\n    // Passthrough mode: forward refresh token directly to upstream\n    return await this.handlePassthroughRefresh(request);\n  }\n\n  /**\n   * Get OAuth discovery metadata\n   */\n  getAuthorizationServerMetadata(): {\n    authorizationEndpoint: string;\n    codeChallengeMethodsSupported?: string[];\n    dpopSigningAlgValuesSupported?: string[];\n    grantTypesSupported?: string[];\n    introspectionEndpoint?: string;\n    issuer: string;\n    jwksUri?: string;\n    opPolicyUri?: string;\n    opTosUri?: string;\n    registrationEndpoint?: string;\n    responseModesSupported?: string[];\n    responseTypesSupported: string[];\n    revocationEndpoint?: string;\n    scopesSupported?: string[];\n    serviceDocumentation?: string;\n    tokenEndpoint: string;\n    tokenEndpointAuthMethodsSupported?: string[];\n    tokenEndpointAuthSigningAlgValuesSupported?: string[];\n    uiLocalesSupported?: string[];\n  } {\n    return {\n      authorizationEndpoint: `${this.config.baseUrl}/oauth/authorize`,\n      codeChallengeMethodsSupported: [\"S256\", \"plain\"],\n      grantTypesSupported: [\"authorization_code\", \"refresh_token\"],\n      issuer: this.config.baseUrl,\n      registrationEndpoint: `${this.config.baseUrl}/oauth/register`,\n      responseTypesSupported: [\"code\"],\n      scopesSupported: this.config.scopes || [],\n      tokenEndpoint: `${this.config.baseUrl}/oauth/token`,\n      tokenEndpointAuthMethodsSupported: [\n        \"client_secret_basic\",\n        \"client_secret_post\",\n      ],\n    };\n  }\n\n  /**\n   * Handle OAuth callback from upstream provider\n   */\n  async handleCallback(request: Request): Promise<Response> {\n    const url = new URL(request.url);\n    const code = url.searchParams.get(\"code\");\n    const state = url.searchParams.get(\"state\");\n    const error = url.searchParams.get(\"error\");\n\n    // Check for errors from upstream\n    if (error) {\n      const errorDescription = url.searchParams.get(\"error_description\");\n      throw new OAuthProxyError(error, errorDescription || undefined);\n    }\n\n    if (!code || !state) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Missing code or state parameter\",\n      );\n    }\n\n    // Retrieve transaction\n    const transaction = this.transactions.get(state);\n    if (!transaction) {\n      throw new OAuthProxyError(\"invalid_request\", \"Invalid or expired state\");\n    }\n\n    // Defense-in-depth: the transaction's stored callback URL must still be\n    // registered. Guards against any code path that could persist an\n    // unvalidated URI, and against registration being revoked mid-flow.\n    if (!this.registeredClients.has(transaction.clientCallbackUrl)) {\n      this.transactions.delete(state);\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Transaction callback URL is not registered\",\n      );\n    }\n\n    // Exchange code with upstream provider\n    const upstreamTokens = await this.exchangeUpstreamCode(code, transaction);\n\n    // Generate authorization code for client\n    const clientCode = this.generateAuthorizationCode(\n      transaction,\n      upstreamTokens,\n    );\n\n    // Clean up transaction\n    this.transactions.delete(state);\n\n    // Redirect to client callback with code\n    const redirectUrl = new URL(transaction.clientCallbackUrl);\n    redirectUrl.searchParams.set(\"code\", clientCode);\n    redirectUrl.searchParams.set(\"state\", transaction.state);\n\n    return new Response(null, {\n      headers: {\n        Location: redirectUrl.toString(),\n      },\n      status: 302,\n    });\n  }\n\n  /**\n   * Handle consent form submission\n   */\n  async handleConsent(request: Request): Promise<Response> {\n    const formData = await request.formData();\n    const transactionId = formData.get(\"transaction_id\") as string;\n    const action = formData.get(\"action\") as string;\n\n    if (!transactionId) {\n      throw new OAuthProxyError(\"invalid_request\", \"Missing transaction_id\");\n    }\n\n    const transaction = this.transactions.get(transactionId);\n    if (!transaction) {\n      throw new OAuthProxyError(\n        \"invalid_request\",\n        \"Invalid or expired transaction\",\n      );\n    }\n\n    if (action === \"deny\") {\n      // User denied consent\n      this.transactions.delete(transactionId);\n      // Defense-in-depth: never redirect to an unregistered URI.\n      if (!this.registeredClients.has(transaction.clientCallbackUrl)) {\n        throw new OAuthProxyError(\n          \"invalid_request\",\n          \"Transaction callback URL is not registered\",\n        );\n      }\n      const redirectUrl = new URL(transaction.clientCallbackUrl);\n      redirectUrl.searchParams.set(\"error\", \"access_denied\");\n      redirectUrl.searchParams.set(\n        \"error_description\",\n        \"User denied authorization\",\n      );\n      redirectUrl.searchParams.set(\"state\", transaction.state);\n\n      return new Response(null, {\n        headers: {\n          Location: redirectUrl.toString(),\n        },\n        status: 302,\n      });\n    }\n\n    // User approved, mark consent and redirect to upstream\n    transaction.consentGiven = true;\n    this.transactions.set(transactionId, transaction);\n\n    return this.redirectToUpstream(transaction);\n  }\n\n  /**\n   * Load upstream tokens from a FastMCP JWT\n   */\n  async loadUpstreamTokens(\n    fastmcpToken: string,\n  ): Promise<null | UpstreamTokenSet> {\n    if (!this.jwtIssuer) {\n      return null;\n    }\n\n    // Verify FastMCP JWT\n    const result = await this.jwtIssuer.verify(fastmcpToken);\n    if (!result.valid || !result.claims?.jti) {\n      return null;\n    }\n\n    // Look up token mapping\n    const mapping = (await this.tokenStorage.get(\n      `mapping:${result.claims.jti}`,\n    )) as {\n      upstreamTokenKey: string;\n    } | null;\n\n    if (!mapping) {\n      return null;\n    }\n\n    // Retrieve upstream tokens\n    const upstreamTokens = (await this.tokenStorage.get(\n      `upstream:${mapping.upstreamTokenKey}`,\n    )) as null | UpstreamTokenSet;\n\n    return upstreamTokens;\n  }\n\n  /**\n   * RFC 7591 Dynamic Client Registration\n   */\n  async registerClient(request: DCRRequest): Promise<DCRResponse> {\n    // Validate required fields\n    if (!request.redirect_uris || request.redirect_uris.length === 0) {\n      throw new OAuthProxyError(\n        \"invalid_client_metadata\",\n        \"redirect_uris is required\",\n      );\n    }\n\n    // Validate redirect URIs\n    for (const uri of request.redirect_uris) {\n      if (!this.validateRedirectUri(uri)) {\n        throw new OAuthProxyError(\n          \"invalid_redirect_uri\",\n          `Invalid redirect URI: ${uri}`,\n        );\n      }\n    }\n\n    // Store client registration (indexed by primary redirect URI)\n    const clientId = this.config.upstreamClientId;\n    const client: ProxyDCRClient = {\n      callbackUrl: request.redirect_uris[0],\n      clientId,\n      clientSecret: this.config.upstreamClientSecret,\n      metadata: {\n        client_name: request.client_name,\n        client_uri: request.client_uri,\n        contacts: request.contacts,\n        jwks: request.jwks,\n        jwks_uri: request.jwks_uri,\n        logo_uri: request.logo_uri,\n        policy_uri: request.policy_uri,\n        scope: request.scope,\n        software_id: request.software_id,\n        software_version: request.software_version,\n        tos_uri: request.tos_uri,\n      },\n      registeredAt: new Date(),\n    };\n\n    // Store registration under every presented redirect_uri so that authorize()\n    // can validate each one exactly (RFC 6749 §3.1.2.3). The previous code only\n    // stored the first URI, silently dropping the rest.\n    for (const uri of request.redirect_uris) {\n      this.registeredClients.set(uri, client);\n    }\n\n    // Return RFC 7591 compliant response\n    const response: DCRResponse = {\n      client_id: clientId,\n      client_id_issued_at: Math.floor(Date.now() / 1000),\n      // Echo back optional metadata\n      client_name: request.client_name,\n      client_secret: this.config.upstreamClientSecret,\n      client_secret_expires_at: 0, // Never expires\n      client_uri: request.client_uri,\n      contacts: request.contacts,\n      grant_types: request.grant_types || [\n        \"authorization_code\",\n        \"refresh_token\",\n      ],\n      jwks: request.jwks,\n      jwks_uri: request.jwks_uri,\n      logo_uri: request.logo_uri,\n      policy_uri: request.policy_uri,\n      redirect_uris: request.redirect_uris,\n      response_types: request.response_types || [\"code\"],\n      scope: request.scope,\n      software_id: request.software_id,\n      software_version: request.software_version,\n      token_endpoint_auth_method:\n        request.token_endpoint_auth_method || \"client_secret_basic\",\n      tos_uri: request.tos_uri,\n    };\n\n    return response;\n  }\n\n  /**\n   * Calculate access token TTL from upstream tokens\n   */\n  private calculateAccessTokenTtl(upstreamTokens: UpstreamTokenSet): number {\n    if (upstreamTokens.expiresIn > 0) {\n      return upstreamTokens.expiresIn;\n    } else if (this.config.accessTokenTtl) {\n      return this.config.accessTokenTtl;\n    } else if (upstreamTokens.refreshToken) {\n      return DEFAULT_ACCESS_TOKEN_TTL;\n    } else {\n      return DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH;\n    }\n  }\n\n  /**\n   * Clean up expired transactions and codes\n   */\n  private cleanup(): void {\n    const now = Date.now();\n\n    // Clean up expired transactions\n    for (const [id, transaction] of this.transactions.entries()) {\n      if (transaction.expiresAt.getTime() < now) {\n        this.transactions.delete(id);\n      }\n    }\n\n    // Clean up expired codes\n    for (const [code, clientCode] of this.clientCodes.entries()) {\n      if (clientCode.expiresAt.getTime() < now) {\n        this.clientCodes.delete(code);\n      }\n    }\n\n    // Clean up token storage\n    void this.tokenStorage.cleanup();\n  }\n\n  /**\n   * Create a new OAuth transaction\n   */\n  private async createTransaction(\n    params: AuthorizationParams,\n  ): Promise<OAuthTransaction> {\n    const transactionId = this.generateId();\n    const proxyPkce = PKCEUtils.generatePair(\"S256\");\n\n    const transaction: OAuthTransaction = {\n      clientCallbackUrl: params.redirect_uri,\n      clientCodeChallenge: params.code_challenge || \"\",\n      clientCodeChallengeMethod: params.code_challenge_method || \"plain\",\n      clientId: params.client_id,\n      createdAt: new Date(),\n      expiresAt: new Date(\n        Date.now() + (this.config.transactionTtl || 600) * 1000,\n      ),\n      id: transactionId,\n      proxyCodeChallenge: proxyPkce.challenge,\n      proxyCodeVerifier: proxyPkce.verifier,\n      scope: params.scope ? params.scope.split(\" \") : this.config.scopes || [],\n      state: params.state || this.generateId(),\n    };\n\n    this.transactions.set(transactionId, transaction);\n\n    return transaction;\n  }\n\n  /**\n   * Exchange authorization code with upstream provider\n   */\n  private async exchangeUpstreamCode(\n    code: string,\n    transaction: OAuthTransaction,\n  ): Promise<UpstreamTokenSet> {\n    const useBasicAuth =\n      this.config.upstreamTokenEndpointAuthMethod === \"client_secret_basic\";\n\n    const bodyParams: Record<string, string> = {\n      code,\n      code_verifier: transaction.proxyCodeVerifier,\n      grant_type: \"authorization_code\",\n      redirect_uri: `${this.config.baseUrl}${this.config.redirectPath}`,\n    };\n\n    // Include client credentials in body only for client_secret_post\n    if (!useBasicAuth) {\n      bodyParams.client_id = this.config.upstreamClientId;\n      bodyParams.client_secret = this.config.upstreamClientSecret;\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    };\n\n    // Add Basic Auth header for client_secret_basic\n    if (useBasicAuth) {\n      headers[\"Authorization\"] = this.getBasicAuthHeader();\n    }\n\n    const tokenResponse = await fetch(this.config.upstreamTokenEndpoint, {\n      body: new URLSearchParams(bodyParams),\n      headers,\n      method: \"POST\",\n    });\n\n    if (!tokenResponse.ok) {\n      let errorCode = \"server_error\";\n      let errorDescription: string | undefined;\n      try {\n        const error = (await tokenResponse.json()) as {\n          error?: string;\n          error_description?: string;\n        };\n        errorCode = error.error || \"server_error\";\n        errorDescription = error.error_description;\n      } catch {\n        errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;\n      }\n      throw new OAuthProxyError(errorCode, errorDescription);\n    }\n\n    const tokens = await this.parseTokenResponse(tokenResponse);\n\n    return {\n      accessToken: tokens.access_token,\n      expiresIn: tokens.expires_in || 3600,\n      idToken: tokens.id_token,\n      issuedAt: new Date(),\n      refreshExpiresIn: tokens.refresh_expires_in,\n      refreshToken: tokens.refresh_token,\n      scope: tokens.scope ? tokens.scope.split(\" \") : transaction.scope,\n      tokenType: tokens.token_type || \"Bearer\",\n    };\n  }\n\n  /**\n   * Extract JTI from a JWT token\n   */\n  private async extractJti(token: string): Promise<string> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    const result = await this.jwtIssuer.verify(token);\n    if (!result.valid || !result.claims?.jti) {\n      throw new Error(\"Failed to extract JTI from token\");\n    }\n\n    return result.claims.jti;\n  }\n\n  /**\n   * Extract custom claims from upstream tokens\n   * Combines claims from access token and ID token (if present)\n   */\n  private async extractUpstreamClaims(\n    upstreamTokens: UpstreamTokenSet,\n  ): Promise<null | Record<string, unknown>> {\n    if (!this.claimsExtractor) {\n      return null;\n    }\n\n    const allClaims: Record<string, unknown> = {};\n\n    // Extract from access token (if JWT format)\n    const accessClaims = await this.claimsExtractor.extract(\n      upstreamTokens.accessToken,\n      \"access\",\n    );\n    if (accessClaims) {\n      Object.assign(allClaims, accessClaims);\n    }\n\n    // Extract from ID token (if present and JWT format)\n    if (upstreamTokens.idToken) {\n      const idClaims = await this.claimsExtractor.extract(\n        upstreamTokens.idToken,\n        \"id\",\n      );\n      if (idClaims) {\n        // Access token claims take precedence over ID token claims\n        for (const [key, value] of Object.entries(idClaims)) {\n          if (!(key in allClaims)) {\n            allClaims[key] = value;\n          }\n        }\n      }\n    }\n\n    return Object.keys(allClaims).length > 0 ? allClaims : null;\n  }\n\n  /**\n   * Generate authorization code for client\n   */\n  private generateAuthorizationCode(\n    transaction: OAuthTransaction,\n    upstreamTokens: UpstreamTokenSet,\n  ): string {\n    const code = this.generateId();\n\n    const clientCode: ClientCode = {\n      clientId: transaction.clientId,\n      code,\n      codeChallenge: transaction.clientCodeChallenge,\n      codeChallengeMethod: transaction.clientCodeChallengeMethod,\n      createdAt: new Date(),\n      expiresAt: new Date(\n        Date.now() + (this.config.authorizationCodeTtl || 300) * 1000,\n      ),\n      transactionId: transaction.id,\n      upstreamTokens,\n    };\n\n    this.clientCodes.set(code, clientCode);\n\n    return code;\n  }\n\n  /**\n   * Generate secure random ID\n   */\n  private generateId(): string {\n    return randomBytes(32).toString(\"base64url\");\n  }\n\n  /**\n   * Generate signing key for consent cookies\n   */\n  private generateSigningKey(): string {\n    return randomBytes(32).toString(\"hex\");\n  }\n\n  /**\n   * Generate Basic auth header value for upstream token endpoint\n   * Per RFC 6749 Section 2.3.1, credentials must be URL-encoded before base64 encoding\n   */\n  private getBasicAuthHeader(): string {\n    const encodedClientId = encodeURIComponent(this.config.upstreamClientId);\n    const encodedClientSecret = encodeURIComponent(\n      this.config.upstreamClientSecret,\n    );\n    return `Basic ${Buffer.from(`${encodedClientId}:${encodedClientSecret}`).toString(\"base64\")}`;\n  }\n\n  /**\n   * Get provider name for display\n   */\n  private getProviderName(): string {\n    const url = new URL(this.config.upstreamAuthorizationEndpoint);\n    return url.hostname;\n  }\n\n  /**\n   * Handle passthrough mode refresh - forward refresh token directly to upstream\n   */\n  private async handlePassthroughRefresh(\n    request: RefreshRequest,\n  ): Promise<TokenResponse> {\n    const useBasicAuth =\n      this.config.upstreamTokenEndpointAuthMethod === \"client_secret_basic\";\n\n    const bodyParams: Record<string, string> = {\n      grant_type: \"refresh_token\",\n      refresh_token: request.refresh_token,\n      ...(request.scope && { scope: request.scope }),\n    };\n\n    // Include client credentials in body only for client_secret_post\n    if (!useBasicAuth) {\n      bodyParams.client_id = this.config.upstreamClientId;\n      bodyParams.client_secret = this.config.upstreamClientSecret;\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    };\n\n    // Add Basic Auth header for client_secret_basic\n    if (useBasicAuth) {\n      headers[\"Authorization\"] = this.getBasicAuthHeader();\n    }\n\n    // Exchange refresh token with upstream provider\n    const tokenResponse = await fetch(this.config.upstreamTokenEndpoint, {\n      body: new URLSearchParams(bodyParams),\n      headers,\n      method: \"POST\",\n    });\n\n    if (!tokenResponse.ok) {\n      let errorCode = \"invalid_grant\";\n      let errorDescription: string | undefined;\n      try {\n        const error = (await tokenResponse.json()) as {\n          error?: string;\n          error_description?: string;\n        };\n        errorCode = error.error || \"invalid_grant\";\n        errorDescription = error.error_description;\n      } catch {\n        errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;\n      }\n      throw new OAuthProxyError(errorCode, errorDescription);\n    }\n\n    const tokens = await this.parseTokenResponse(tokenResponse);\n\n    return {\n      access_token: tokens.access_token,\n      expires_in: tokens.expires_in || 3600,\n      id_token: tokens.id_token,\n      refresh_token: tokens.refresh_token,\n      scope: tokens.scope,\n      token_type: tokens.token_type || \"Bearer\",\n    };\n  }\n\n  /**\n   * Handle swap mode refresh - verify FastMCP JWT and issue new tokens\n   */\n  private async handleSwapModeRefresh(\n    request: RefreshRequest,\n  ): Promise<TokenResponse> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    const verifyResult = await this.jwtIssuer.verify(request.refresh_token);\n    if (!verifyResult.valid) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Invalid or expired refresh token\",\n      );\n    }\n\n    const jti = verifyResult.claims?.jti;\n    if (!jti) {\n      throw new OAuthProxyError(\"invalid_grant\", \"Refresh token missing JTI\");\n    }\n\n    const mapping = (await this.tokenStorage.get(`mapping:${jti}`)) as {\n      clientId: string;\n      scope: string[];\n      upstreamTokenKey: string;\n    } | null;\n\n    if (!mapping) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Refresh token already used or expired\",\n      );\n    }\n\n    const upstreamTokens = (await this.tokenStorage.get(\n      `upstream:${mapping.upstreamTokenKey}`,\n    )) as null | UpstreamTokenSet;\n\n    if (!upstreamTokens) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"Upstream tokens not found or expired\",\n      );\n    }\n\n    if (!upstreamTokens.refreshToken) {\n      throw new OAuthProxyError(\n        \"invalid_grant\",\n        \"No upstream refresh token available\",\n      );\n    }\n\n    const refreshedUpstreamTokens = await this.refreshUpstreamTokens(\n      upstreamTokens.refreshToken,\n      request.scope,\n    );\n\n    if (refreshedUpstreamTokens.scope.length === 0) {\n      refreshedUpstreamTokens.scope = upstreamTokens.scope;\n    }\n\n    const refreshTokenTtl =\n      refreshedUpstreamTokens.refreshExpiresIn ??\n      this.config.refreshTokenTtl ??\n      DEFAULT_REFRESH_TOKEN_TTL;\n    const accessTokenTtl = this.calculateAccessTokenTtl(\n      refreshedUpstreamTokens,\n    );\n    const upstreamStorageTtl = Math.max(accessTokenTtl, refreshTokenTtl, 1);\n\n    await this.tokenStorage.save(\n      `upstream:${mapping.upstreamTokenKey}`,\n      refreshedUpstreamTokens,\n      upstreamStorageTtl,\n    );\n\n    return await this.issueSwappedTokensForRefresh(\n      mapping.clientId,\n      refreshedUpstreamTokens,\n      mapping.upstreamTokenKey,\n      jti,\n    );\n  }\n\n  /**\n   * Issue swapped tokens (JWT pattern)\n   * Issues short-lived FastMCP JWTs and stores upstream tokens securely\n   */\n  private async issueSwappedTokens(\n    clientId: string,\n    upstreamTokens: UpstreamTokenSet,\n  ): Promise<TokenResponse> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    // Extract custom claims from upstream tokens\n    const customClaims = await this.extractUpstreamClaims(upstreamTokens);\n\n    // Determine access token TTL (hierarchical: upstream → config → default)\n    let accessTokenTtl: number;\n    if (upstreamTokens.expiresIn > 0) {\n      accessTokenTtl = upstreamTokens.expiresIn;\n    } else if (this.config.accessTokenTtl) {\n      accessTokenTtl = this.config.accessTokenTtl;\n    } else if (upstreamTokens.refreshToken) {\n      accessTokenTtl = DEFAULT_ACCESS_TOKEN_TTL;\n    } else {\n      accessTokenTtl = DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH;\n    }\n\n    // Determine refresh token TTL early (needed for upstream storage TTL)\n    // Use upstream's refresh_expires_in if provided, otherwise fall back to config/default\n    const refreshTokenTtl = upstreamTokens.refreshToken\n      ? (upstreamTokens.refreshExpiresIn ??\n        this.config.refreshTokenTtl ??\n        DEFAULT_REFRESH_TOKEN_TTL)\n      : 0;\n\n    // Store upstream tokens with longest-lived token TTL (min 1s for safety)\n    const upstreamStorageTtl = Math.max(accessTokenTtl, refreshTokenTtl, 1);\n    const upstreamTokenKey = this.generateId();\n    await this.tokenStorage.save(\n      `upstream:${upstreamTokenKey}`,\n      upstreamTokens,\n      upstreamStorageTtl,\n    );\n\n    // Issue FastMCP access token with custom claims\n    const accessToken = this.jwtIssuer.issueAccessToken(\n      clientId,\n      upstreamTokens.scope,\n      customClaims || undefined,\n      accessTokenTtl,\n    );\n\n    // Decode JWT to get JTI\n    const accessJti = await this.extractJti(accessToken);\n\n    // Store token mapping\n    await this.tokenStorage.save(\n      `mapping:${accessJti}`,\n      {\n        clientId,\n        createdAt: new Date(),\n        expiresAt: new Date(Date.now() + accessTokenTtl * 1000),\n        jti: accessJti,\n        scope: upstreamTokens.scope,\n        upstreamTokenKey,\n      },\n      accessTokenTtl,\n    );\n\n    const response: TokenResponse = {\n      access_token: accessToken,\n      expires_in: accessTokenTtl,\n      scope: upstreamTokens.scope.join(\" \"),\n      token_type: \"Bearer\",\n    };\n\n    // Issue refresh token if upstream provided one\n    if (upstreamTokens.refreshToken) {\n      const refreshToken = this.jwtIssuer.issueRefreshToken(\n        clientId,\n        upstreamTokens.scope,\n        customClaims || undefined,\n        refreshTokenTtl,\n      );\n      const refreshJti = await this.extractJti(refreshToken);\n\n      // Store refresh token mapping\n      await this.tokenStorage.save(\n        `mapping:${refreshJti}`,\n        {\n          clientId,\n          createdAt: new Date(),\n          expiresAt: new Date(Date.now() + refreshTokenTtl * 1000),\n          jti: refreshJti,\n          scope: upstreamTokens.scope,\n          upstreamTokenKey,\n        },\n        refreshTokenTtl,\n      );\n\n      response.refresh_token = refreshToken;\n    }\n\n    return response;\n  }\n\n  /**\n   * Issue swapped tokens for refresh flow\n   */\n  private async issueSwappedTokensForRefresh(\n    clientId: string,\n    upstreamTokens: UpstreamTokenSet,\n    upstreamTokenKey: string,\n    oldJti: string,\n  ): Promise<TokenResponse> {\n    if (!this.jwtIssuer) {\n      throw new Error(\"JWT issuer not initialized\");\n    }\n\n    await this.tokenStorage.delete(`mapping:${oldJti}`);\n\n    const customClaims = await this.extractUpstreamClaims(upstreamTokens);\n\n    const accessTokenTtl = this.calculateAccessTokenTtl(upstreamTokens);\n    const refreshTokenTtl = upstreamTokens.refreshToken\n      ? (upstreamTokens.refreshExpiresIn ??\n        this.config.refreshTokenTtl ??\n        DEFAULT_REFRESH_TOKEN_TTL)\n      : 0;\n\n    const accessToken = this.jwtIssuer.issueAccessToken(\n      clientId,\n      upstreamTokens.scope,\n      customClaims || undefined,\n      accessTokenTtl,\n    );\n\n    const accessJti = await this.extractJti(accessToken);\n    await this.tokenStorage.save(\n      `mapping:${accessJti}`,\n      {\n        clientId,\n        createdAt: new Date(),\n        expiresAt: new Date(Date.now() + accessTokenTtl * 1000),\n        jti: accessJti,\n        scope: upstreamTokens.scope,\n        upstreamTokenKey,\n      },\n      accessTokenTtl,\n    );\n\n    const response: TokenResponse = {\n      access_token: accessToken,\n      expires_in: accessTokenTtl,\n      scope: upstreamTokens.scope.join(\" \"),\n      token_type: \"Bearer\",\n    };\n\n    if (upstreamTokens.refreshToken) {\n      const refreshToken = this.jwtIssuer.issueRefreshToken(\n        clientId,\n        upstreamTokens.scope,\n        customClaims || undefined,\n        refreshTokenTtl,\n      );\n      const refreshJti = await this.extractJti(refreshToken);\n\n      await this.tokenStorage.save(\n        `mapping:${refreshJti}`,\n        {\n          clientId,\n          createdAt: new Date(),\n          expiresAt: new Date(Date.now() + refreshTokenTtl * 1000),\n          jti: refreshJti,\n          scope: upstreamTokens.scope,\n          upstreamTokenKey,\n        },\n        refreshTokenTtl,\n      );\n\n      response.refresh_token = refreshToken;\n    }\n\n    return response;\n  }\n\n  /**\n   * Match URI against pattern (supports wildcards)\n   */\n  private matchesPattern(uri: string, pattern: string): boolean {\n    const regex = new RegExp(\n      \"^\" + pattern.replace(/\\*/g, \".*\").replace(/\\?/g, \".\") + \"$\",\n    );\n    return regex.test(uri);\n  }\n\n  /**\n   * Parse token response that can be either JSON or URL-encoded\n   * GitHub Apps return URL-encoded format, most providers return JSON\n   */\n  private async parseTokenResponse(response: Response): Promise<{\n    access_token: string;\n    expires_in?: number;\n    id_token?: string;\n    refresh_expires_in?: number;\n    refresh_token?: string;\n    scope?: string;\n    token_type?: string;\n  }> {\n    const contentType = (\n      response.headers.get(\"content-type\") || \"\"\n    ).toLowerCase();\n\n    // Define Zod schema for token response validation\n    const tokenResponseSchema = z.object({\n      access_token: z.string().min(1, \"access_token cannot be empty\"),\n      expires_in: z.coerce.number().int().positive().optional(),\n      id_token: z.string().optional(),\n      refresh_expires_in: z.coerce.number().int().positive().optional(),\n      refresh_token: z.string().optional(),\n      scope: z.string().optional(),\n      token_type: z.string().optional(),\n    });\n\n    // Check if response is URL-encoded (e.g., GitHub Apps)\n    if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n      const text = await response.text();\n      const params = new URLSearchParams(text);\n\n      const rawData = {\n        access_token: params.get(\"access_token\") || \"\",\n        expires_in: params.get(\"expires_in\")\n          ? parseInt(params.get(\"expires_in\")!)\n          : undefined,\n        id_token: params.get(\"id_token\") || undefined,\n        refresh_expires_in: params.get(\"refresh_expires_in\")\n          ? parseInt(params.get(\"refresh_expires_in\")!)\n          : undefined,\n        refresh_token: params.get(\"refresh_token\") || undefined,\n        scope: params.get(\"scope\") || undefined,\n        token_type: params.get(\"token_type\") || undefined,\n      };\n\n      return tokenResponseSchema.parse(rawData);\n    }\n\n    // Default to JSON parsing\n    const rawJson = await response.json();\n    return tokenResponseSchema.parse(rawJson);\n  }\n\n  /**\n   * Redirect to upstream OAuth provider\n   */\n  private redirectToUpstream(transaction: OAuthTransaction): Response {\n    const authUrl = new URL(this.config.upstreamAuthorizationEndpoint);\n\n    authUrl.searchParams.set(\"client_id\", this.config.upstreamClientId);\n    authUrl.searchParams.set(\n      \"redirect_uri\",\n      `${this.config.baseUrl}${this.config.redirectPath}`,\n    );\n    authUrl.searchParams.set(\"response_type\", \"code\");\n    authUrl.searchParams.set(\"state\", transaction.id);\n\n    if (transaction.scope.length > 0) {\n      authUrl.searchParams.set(\"scope\", transaction.scope.join(\" \"));\n    }\n\n    // Add PKCE if not forwarding client PKCE\n    if (!this.config.forwardPkce) {\n      authUrl.searchParams.set(\n        \"code_challenge\",\n        transaction.proxyCodeChallenge,\n      );\n      authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n    }\n\n    return new Response(null, {\n      headers: {\n        Location: authUrl.toString(),\n      },\n      status: 302,\n    });\n  }\n\n  /**\n   * Refresh upstream tokens with provider\n   */\n  private async refreshUpstreamTokens(\n    upstreamRefreshToken: string,\n    requestedScope?: string,\n  ): Promise<UpstreamTokenSet> {\n    const useBasicAuth =\n      this.config.upstreamTokenEndpointAuthMethod === \"client_secret_basic\";\n\n    const bodyParams: Record<string, string> = {\n      grant_type: \"refresh_token\",\n      refresh_token: upstreamRefreshToken,\n      ...(requestedScope && { scope: requestedScope }),\n    };\n\n    // Include client credentials in body only for client_secret_post\n    if (!useBasicAuth) {\n      bodyParams.client_id = this.config.upstreamClientId;\n      bodyParams.client_secret = this.config.upstreamClientSecret;\n    }\n\n    const headers: Record<string, string> = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    };\n\n    // Add Basic Auth header for client_secret_basic\n    if (useBasicAuth) {\n      headers[\"Authorization\"] = this.getBasicAuthHeader();\n    }\n\n    // Exchange refresh token with upstream provider\n    const tokenResponse = await fetch(this.config.upstreamTokenEndpoint, {\n      body: new URLSearchParams(bodyParams),\n      headers,\n      method: \"POST\",\n    });\n\n    if (!tokenResponse.ok) {\n      let errorCode = \"invalid_grant\";\n      let errorDescription: string | undefined = \"Upstream refresh failed\";\n      try {\n        const error = (await tokenResponse.json()) as {\n          error?: string;\n          error_description?: string;\n        };\n        errorCode = error.error || \"invalid_grant\";\n        errorDescription = error.error_description || \"Upstream refresh failed\";\n      } catch {\n        errorDescription = `Upstream returned HTTP ${tokenResponse.status} ${tokenResponse.statusText}`;\n      }\n      throw new OAuthProxyError(errorCode, errorDescription);\n    }\n\n    const tokens = await this.parseTokenResponse(tokenResponse);\n\n    // Handle token rotation: if upstream doesn't return new refresh token,\n    // preserve the original one\n    return {\n      accessToken: tokens.access_token,\n      expiresIn: tokens.expires_in || 3600,\n      idToken: tokens.id_token,\n      issuedAt: new Date(),\n      refreshExpiresIn: tokens.refresh_expires_in,\n      refreshToken: tokens.refresh_token || upstreamRefreshToken,\n      scope: tokens.scope ? tokens.scope.split(\" \") : [],\n      tokenType: tokens.token_type || \"Bearer\",\n    };\n  }\n\n  /**\n   * Start periodic cleanup of expired transactions and codes\n   */\n  private startCleanup(): void {\n    this.cleanupInterval = setInterval(() => {\n      this.cleanup();\n    }, 60000); // Run every minute\n  }\n\n  /**\n   * Validate a redirect URI against the configured allow-list.\n   *\n   * Returns `true` only if the URI is syntactically valid AND matches one of\n   * the explicitly configured `allowedRedirectUriPatterns`. An empty or unset\n   * pattern list means DCR will reject every URI — framework users must\n   * opt-in by listing the exact URIs (or wildcards) they trust.\n   *\n   * Prior versions also fell back to allowing any https URL or localhost,\n   * which enabled attackers to DCR an arbitrary URL and then abuse it via\n   * /oauth/authorize (CWE-601). Do not re-introduce that fallback.\n   */\n  private validateRedirectUri(uri: string): boolean {\n    try {\n      new URL(uri); // syntactic check only — throws on malformed input\n    } catch {\n      return false;\n    }\n\n    const patterns = this.config.allowedRedirectUriPatterns || [];\n    if (patterns.length === 0) {\n      return false;\n    }\n\n    return patterns.some((pattern) => this.matchesPattern(uri, pattern));\n  }\n}\n\n/**\n * OAuth Proxy Error\n */\nexport class OAuthProxyError extends Error {\n  constructor(\n    public code: string,\n    public description?: string,\n    public statusCode: number = 400,\n  ) {\n    super(code);\n    this.name = \"OAuthProxyError\";\n  }\n\n  toJSON(): OAuthError {\n    return {\n      error: this.code,\n      error_description: this.description,\n    };\n  }\n\n  toResponse(): Response {\n    return new Response(JSON.stringify(this.toJSON()), {\n      headers: { \"Content-Type\": \"application/json\" },\n      status: this.statusCode,\n    });\n  }\n}\n","/**\n * OAuth Proxy Types\n * Type definitions for the OAuth 2.1 Proxy implementation\n */\n\n/**\n * Default TTL values for token expiration (in seconds)\n */\nexport const DEFAULT_ACCESS_TOKEN_TTL = 3600; // 1 hour\nexport const DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536000; // 1 year\nexport const DEFAULT_REFRESH_TOKEN_TTL = 2592000; // 30 days\nexport const DEFAULT_AUTHORIZATION_CODE_TTL = 300; // 5 minutes\nexport const DEFAULT_TRANSACTION_TTL = 600; // 10 minutes\n\n/**\n * OAuth authorization request parameters\n */\nexport interface AuthorizationParams {\n  [key: string]: unknown;\n  client_id: string;\n  code_challenge?: string;\n  code_challenge_method?: string;\n  redirect_uri: string;\n  response_type: string;\n  scope?: string;\n  state?: string;\n}\n\n/**\n * Authorization code storage with PKCE validation\n */\nexport interface ClientCode {\n  /** Client ID that owns this code */\n  clientId: string;\n  /** Authorization code */\n  code: string;\n  /** PKCE code challenge for validation */\n  codeChallenge: string;\n  /** PKCE code challenge method */\n  codeChallengeMethod: string;\n  /** Code creation timestamp */\n  createdAt: Date;\n  /** Code expiration timestamp */\n  expiresAt: Date;\n  /** Associated transaction ID */\n  transactionId: string;\n  /** Upstream tokens obtained from provider */\n  upstreamTokens: UpstreamTokenSet;\n  /** Whether code has been used */\n  used?: boolean;\n}\n\n/**\n * Consent data for user approval\n */\nexport interface ConsentData {\n  clientName: string;\n  provider: string;\n  scope: string[];\n  timestamp: number;\n  transactionId: string;\n}\n\n/**\n * Custom claims passthrough configuration\n */\nexport interface CustomClaimsPassthroughConfig {\n  /** Allow nested objects/arrays in claims. Default: false (only primitives) */\n  allowComplexClaims?: boolean;\n\n  /** Only passthrough these specific claims (allowlist). Default: undefined (allow all non-protected) */\n  allowedClaims?: string[];\n\n  /** Never passthrough these claims (blocklist, in addition to protected claims). Default: [] */\n  blockedClaims?: string[];\n\n  /** Prefix upstream claims to prevent collisions. Default: false (no prefix) */\n  claimPrefix?: false | string;\n\n  /** Enable passthrough from upstream access token (if JWT format). Default: true */\n  fromAccessToken?: boolean;\n\n  /** Enable passthrough from upstream ID token. Default: true */\n  fromIdToken?: boolean;\n\n  /** Maximum length for claim values. Default: 2000 */\n  maxClaimValueSize?: number;\n}\n\n/**\n * Client metadata for storage\n */\nexport interface DCRClientMetadata {\n  client_name?: string;\n  client_uri?: string;\n  contacts?: string[];\n  jwks?: Record<string, unknown>;\n  jwks_uri?: string;\n  logo_uri?: string;\n  policy_uri?: string;\n  scope?: string;\n  software_id?: string;\n  software_version?: string;\n  tos_uri?: string;\n}\n\n/**\n * RFC 7591 Dynamic Client Registration Request\n */\nexport interface DCRRequest {\n  /** Client name */\n  client_name?: string;\n  /** Client homepage URL */\n  client_uri?: string;\n  /** Contact email addresses */\n  contacts?: string[];\n  /** Allowed grant types */\n  grant_types?: string[];\n  /** JWKS object */\n  jwks?: Record<string, unknown>;\n  /** JWKS URI */\n  jwks_uri?: string;\n  /** Client logo URL */\n  logo_uri?: string;\n  /** Privacy policy URL */\n  policy_uri?: string;\n  /** REQUIRED: Array of redirect URIs */\n  redirect_uris: string[];\n  /** Allowed response types */\n  response_types?: string[];\n  /** Requested scope */\n  scope?: string;\n  /** Software identifier */\n  software_id?: string;\n  /** Software version */\n  software_version?: string;\n  /** Token endpoint authentication method */\n  token_endpoint_auth_method?: string;\n  /** Terms of service URL */\n  tos_uri?: string;\n}\n\n/**\n * RFC 7591 Dynamic Client Registration Response\n */\nexport interface DCRResponse {\n  /** REQUIRED: Client identifier */\n  client_id: string;\n  /** Client ID issued timestamp */\n  client_id_issued_at?: number;\n  client_name?: string;\n  /** Client secret */\n  client_secret?: string;\n  /** Client secret expiration (0 = never) */\n  client_secret_expires_at?: number;\n  client_uri?: string;\n  contacts?: string[];\n  grant_types?: string[];\n  jwks?: Record<string, unknown>;\n  jwks_uri?: string;\n  logo_uri?: string;\n  policy_uri?: string;\n  /** Echo back all registered metadata */\n  redirect_uris: string[];\n  /** Registration access token */\n  registration_access_token?: string;\n  /** Registration client URI */\n  registration_client_uri?: string;\n  response_types?: string[];\n  scope?: string;\n  software_id?: string;\n  software_version?: string;\n  token_endpoint_auth_method?: string;\n  tos_uri?: string;\n}\n\n/**\n * OAuth error response\n */\nexport interface OAuthError {\n  error: string;\n  error_description?: string;\n  error_uri?: string;\n}\n\n/**\n * OAuth Proxy provider for pre-configured providers\n */\nexport interface OAuthProviderConfig {\n  baseUrl: string;\n  clientId: string;\n  clientSecret: string;\n  consentRequired?: boolean;\n  scopes?: string[];\n}\n\n/**\n * Configuration for the OAuth Proxy\n */\nexport interface OAuthProxyConfig {\n  /** Access token TTL in seconds (default: 3600) */\n  accessTokenTtl?: number;\n  /**\n   * Allow-list of redirect URI patterns accepted by Dynamic Client Registration.\n   *\n   * A client calling POST /oauth/register must present a `redirect_uri` that\n   * matches one of these patterns (exact string or glob with `*` / `?`);\n   * otherwise the registration is rejected with `invalid_redirect_uri`. Once\n   * registered, the same exact URI must be echoed back at /oauth/authorize —\n   * the proxy performs exact string comparison per RFC 6749 §3.1.2.3.\n   *\n   * Default: `[]` (DCR rejects everything — explicit opt-in required).\n   *\n   * Prior versions defaulted to `[\"https://*\", \"http://localhost:*\"]` with an\n   * implicit fallback that allowed any https URL. This enabled CWE-601\n   * open-redirect / authorization-code theft: an attacker could DCR their own\n   * URL and then steal victim codes via /oauth/authorize. Do not loosen this\n   * default without understanding that threat model.\n   */\n  allowedRedirectUriPatterns?: string[];\n  /** Authorization code TTL in seconds (default: 300) */\n  authorizationCodeTtl?: number;\n  /** Base URL of this proxy server */\n  baseUrl: string;\n  /** Require user consent (default: true) */\n  consentRequired?: boolean;\n  /** Secret key for signing consent cookies */\n  consentSigningKey?: string;\n  /**\n   * Custom claims passthrough configuration.\n   * When enabled (default), extracts custom claims from upstream access token and ID token\n   * and includes them in the proxy's issued JWT tokens.\n   * This enables authorization based on upstream roles, permissions, etc.\n   * Set to false to disable claims passthrough entirely.\n   * Default: true (enabled with default settings)\n   */\n  customClaimsPassthrough?: boolean | CustomClaimsPassthroughConfig;\n  /** Enable token swap pattern (default: true) - issues short-lived JWTs instead of passing through upstream tokens */\n  enableTokenSwap?: boolean;\n  /** Encryption key for token storage (default: auto-generated). Set to false to disable encryption. */\n  encryptionKey?: false | string;\n  /** Forward client's PKCE to upstream (default: false) */\n  forwardPkce?: boolean;\n  /** Secret key for signing JWTs when token swap is enabled */\n  jwtSigningKey?: string;\n  /** OAuth callback path (default: /oauth/callback) */\n  redirectPath?: string;\n  /** Refresh token TTL in seconds (default: 2592000) */\n  refreshTokenTtl?: number;\n  /** Scopes to request from upstream provider */\n  scopes?: string[];\n  /** Custom token storage backend */\n  tokenStorage?: TokenStorage;\n  /** Custom token verifier for validating upstream tokens */\n  tokenVerifier?: TokenVerifier;\n  /** Transaction TTL in seconds (default: 600) */\n  transactionTtl?: number;\n  /** Upstream provider's authorization endpoint URL */\n  upstreamAuthorizationEndpoint: string;\n  /** Pre-registered client ID with upstream provider */\n  upstreamClientId: string;\n  /** Pre-registered client secret with upstream provider */\n  upstreamClientSecret: string;\n  /** Upstream provider's token endpoint URL */\n  upstreamTokenEndpoint: string;\n  /** Upstream token endpoint authentication method (default: \"client_secret_basic\") */\n  upstreamTokenEndpointAuthMethod?:\n    | \"client_secret_basic\"\n    | \"client_secret_post\";\n}\n\n/**\n * OAuth transaction tracking active authorization flows\n */\nexport interface OAuthTransaction {\n  /** Client's callback URL */\n  clientCallbackUrl: string;\n  /** Client's PKCE code challenge */\n  clientCodeChallenge: string;\n  /** Client's PKCE code challenge method (S256 or plain) */\n  clientCodeChallengeMethod: string;\n  /** Client ID from registration */\n  clientId: string;\n  /** Whether user consent was given */\n  consentGiven?: boolean;\n  /** Transaction creation timestamp */\n  createdAt: Date;\n  /** Transaction expiration timestamp */\n  expiresAt: Date;\n  /** Unique transaction ID */\n  id: string;\n  /** Additional state data */\n  metadata?: Record<string, unknown>;\n  /** Proxy-generated PKCE challenge for upstream */\n  proxyCodeChallenge: string;\n  /** Proxy-generated PKCE verifier for upstream */\n  proxyCodeVerifier: string;\n  /** Requested scopes */\n  scope: string[];\n  /** OAuth state parameter */\n  state: string;\n}\n\n/**\n * PKCE pair\n */\nexport interface PKCEPair {\n  challenge: string;\n  verifier: string;\n}\n\n/**\n * Dynamic client registration data\n */\nexport interface ProxyDCRClient {\n  /** Registered callback URL */\n  callbackUrl: string;\n  /** Generated or assigned client ID */\n  clientId: string;\n  /** Client secret (optional) */\n  clientSecret?: string;\n  /** Client metadata from registration request */\n  metadata?: DCRClientMetadata;\n  /** Client registration timestamp */\n  registeredAt: Date;\n}\n\n/**\n * OAuth refresh token request\n */\nexport interface RefreshRequest {\n  client_id: string;\n  client_secret?: string;\n  grant_type: \"refresh_token\";\n  refresh_token: string;\n  scope?: string;\n}\n\n/**\n * Token mapping for JWT swap pattern\n * Maps JTI to upstream token reference\n */\nexport interface TokenMapping {\n  /** Client ID */\n  clientId: string;\n  /** Creation timestamp */\n  createdAt: Date;\n  /** Expiration timestamp */\n  expiresAt: Date;\n  /** JTI from FastMCP JWT */\n  jti: string;\n  /** Scopes */\n  scope: string[];\n  /** Reference to upstream token set */\n  upstreamTokenKey: string;\n}\n\n/**\n * OAuth token request\n */\nexport interface TokenRequest {\n  client_id: string;\n  client_secret?: string;\n  code: string;\n  code_verifier?: string;\n  grant_type: \"authorization_code\";\n  redirect_uri: string;\n}\n\n/**\n * OAuth token response\n */\nexport interface TokenResponse {\n  access_token: string;\n  expires_in: number;\n  id_token?: string;\n  refresh_token?: string;\n  scope?: string;\n  token_type: string;\n}\n\n/**\n * Token storage interface\n */\nexport interface TokenStorage {\n  /** Clean up expired entries */\n  cleanup(): Promise<void>;\n  /** Delete a value */\n  delete(key: string): Promise<void>;\n  /** Retrieve a value */\n  get(key: string): Promise<null | unknown>;\n  /** Save a value with optional TTL */\n  save(key: string, value: unknown, ttl?: number): Promise<void>;\n}\n\n/**\n * Token verification result\n */\nexport interface TokenVerificationResult {\n  claims?: Record<string, unknown>;\n  error?: string;\n  valid: boolean;\n}\n\n/**\n * Token verifier for validating upstream tokens\n */\nexport interface TokenVerifier {\n  verify(token: string): Promise<TokenVerificationResult>;\n}\n\n/**\n * Token set from upstream OAuth provider\n */\nexport interface UpstreamTokenSet {\n  /** Access token */\n  accessToken: string;\n  /** Token expiration in seconds */\n  expiresIn: number;\n  /** ID token (for OIDC) */\n  idToken?: string;\n  /** Token issuance timestamp */\n  issuedAt: Date;\n  /** Refresh token expiration in seconds (if provided by upstream) */\n  refreshExpiresIn?: number;\n  /** Refresh token (if provided) */\n  refreshToken?: string;\n  /** Granted scopes */\n  scope: string[];\n  /** Token type (usually \"Bearer\") */\n  tokenType: string;\n}\n","/**\n * ClaimsExtractor\n * Securely extracts and filters custom claims from upstream OAuth tokens\n */\n\nimport type { CustomClaimsPassthroughConfig } from \"../types.js\";\n\nexport class ClaimsExtractor {\n  private config: CustomClaimsPassthroughConfig;\n\n  // Claims that MUST NOT be copied from upstream (protect proxy's JWT integrity)\n  private readonly PROTECTED_CLAIMS = new Set([\n    \"aud\",\n    \"client_id\",\n    \"exp\",\n    \"iat\",\n    \"iss\",\n    \"jti\",\n    \"nbf\",\n  ]);\n\n  constructor(config: boolean | CustomClaimsPassthroughConfig) {\n    // Handle boolean shorthand: true = default config, false = disabled\n    if (typeof config === \"boolean\") {\n      config = config ? {} : { fromAccessToken: false, fromIdToken: false };\n    }\n\n    // Apply defaults\n    this.config = {\n      allowComplexClaims: config.allowComplexClaims || false,\n      allowedClaims: config.allowedClaims,\n      blockedClaims: config.blockedClaims || [],\n      claimPrefix:\n        config.claimPrefix !== undefined ? config.claimPrefix : false, // Default: no prefix\n      fromAccessToken: config.fromAccessToken !== false, // Default: true\n      fromIdToken: config.fromIdToken !== false, // Default: true\n      maxClaimValueSize: config.maxClaimValueSize || 2000,\n    };\n  }\n\n  /**\n   * Extract claims from a token (access token or ID token)\n   */\n  async extract(\n    token: string,\n    tokenType: \"access\" | \"id\",\n  ): Promise<null | Record<string, unknown>> {\n    // Check if this token type is enabled\n    if (tokenType === \"access\" && !this.config.fromAccessToken) {\n      return null;\n    }\n    if (tokenType === \"id\" && !this.config.fromIdToken) {\n      return null;\n    }\n\n    // Detect if token is JWT format (3 parts separated by dots)\n    if (!this.isJWT(token)) {\n      // Opaque token - no claims to extract\n      return null;\n    }\n\n    // Decode JWT payload (base64url decode only, no signature verification)\n    // We trust the token because it came from upstream via server-to-server exchange\n    const payload = this.decodeJWTPayload(token);\n    if (!payload) {\n      return null;\n    }\n\n    // Filter and validate claims\n    const filtered = this.filterClaims(payload);\n\n    // Apply prefix if configured\n    return this.applyPrefix(filtered);\n  }\n\n  /**\n   * Apply prefix to claim names (if configured)\n   */\n  private applyPrefix(\n    claims: Record<string, unknown>,\n  ): Record<string, unknown> {\n    const prefix = this.config.claimPrefix;\n\n    // No prefix configured or explicitly disabled\n    if (prefix === false || prefix === \"\" || prefix === undefined) {\n      return claims;\n    }\n\n    // Apply prefix to all claim names\n    const result: Record<string, unknown> = {};\n    for (const [key, value] of Object.entries(claims)) {\n      result[`${prefix}${key}`] = value;\n    }\n\n    return result;\n  }\n\n  /**\n   * Decode JWT payload without signature verification\n   * Safe because token came from trusted upstream via server-to-server exchange\n   */\n  private decodeJWTPayload(token: string): null | Record<string, unknown> {\n    try {\n      const parts = token.split(\".\");\n      if (parts.length !== 3) {\n        return null;\n      }\n\n      // Decode the payload (middle part)\n      const payload = Buffer.from(parts[1], \"base64url\").toString(\"utf-8\");\n      return JSON.parse(payload) as Record<string, unknown>;\n    } catch (error) {\n      // Invalid JWT format or JSON\n      console.warn(`Failed to decode JWT payload: ${error}`);\n      return null;\n    }\n  }\n\n  /**\n   * Filter claims based on security rules\n   */\n  private filterClaims(\n    claims: Record<string, unknown>,\n  ): Record<string, unknown> {\n    const result: Record<string, unknown> = {};\n\n    for (const [key, value] of Object.entries(claims)) {\n      // RULE 1: Skip protected claims (ALWAYS enforced)\n      if (this.PROTECTED_CLAIMS.has(key)) {\n        continue;\n      }\n\n      // RULE 2: Skip blocked claims\n      if (this.config.blockedClaims?.includes(key)) {\n        continue;\n      }\n\n      // RULE 3: If allowlist exists, only include allowed claims\n      if (\n        this.config.allowedClaims &&\n        !this.config.allowedClaims.includes(key)\n      ) {\n        continue;\n      }\n\n      // RULE 4: Validate claim value\n      if (!this.isValidClaimValue(value)) {\n        console.warn(`Skipping claim '${key}' due to invalid value`);\n        continue;\n      }\n\n      result[key] = value;\n    }\n\n    return result;\n  }\n\n  /**\n   * Check if a token is in JWT format\n   */\n  private isJWT(token: string): boolean {\n    return token.split(\".\").length === 3;\n  }\n\n  /**\n   * Validate a claim value (type and size checks)\n   */\n  private isValidClaimValue(value: unknown): boolean {\n    if (value === null || value === undefined) {\n      return false;\n    }\n\n    const type = typeof value;\n\n    // Primitive types (string, number, boolean) are always allowed\n    if (type === \"string\") {\n      const maxSize = this.config.maxClaimValueSize ?? 2000;\n      return (value as string).length <= maxSize;\n    }\n\n    if (type === \"number\" || type === \"boolean\") {\n      return true;\n    }\n\n    // Arrays and objects only if explicitly allowed\n    if (Array.isArray(value) || type === \"object\") {\n      // Complex types not allowed by default (security)\n      if (!this.config.allowComplexClaims) {\n        return false;\n      }\n\n      // Check serialized size\n      try {\n        const stringified = JSON.stringify(value);\n        const maxSize = this.config.maxClaimValueSize ?? 2000;\n        return stringified.length <= maxSize;\n      } catch {\n        // Can't serialize - reject\n        return false;\n      }\n    }\n\n    // Unknown type - reject\n    return false;\n  }\n}\n","/**\n * Consent Management\n * Handles user consent flow for OAuth authorization\n */\n\nimport { createHmac } from \"crypto\";\n\nimport type { ConsentData, OAuthTransaction } from \"../types.js\";\n\n/**\n * Manages consent screens and cookie signing\n */\nexport class ConsentManager {\n  private signingKey: string;\n\n  constructor(signingKey: string) {\n    this.signingKey = signingKey || this.generateDefaultKey();\n  }\n\n  /**\n   * Create HTTP response with consent screen\n   */\n  createConsentResponse(\n    transaction: OAuthTransaction,\n    provider: string,\n  ): Response {\n    const consentData: ConsentData = {\n      clientName: \"MCP Client\",\n      provider,\n      scope: transaction.scope,\n      timestamp: Date.now(),\n      transactionId: transaction.id,\n    };\n\n    const html = this.generateConsentScreen(consentData);\n\n    return new Response(html, {\n      headers: {\n        \"Content-Type\": \"text/html; charset=utf-8\",\n      },\n      status: 200,\n    });\n  }\n\n  /**\n   * Generate HTML for consent screen\n   */\n  generateConsentScreen(data: ConsentData): string {\n    const { clientName, provider, scope, transactionId } = data;\n\n    return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Authorization Request</title>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            min-height: 100vh;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            padding: 20px;\n        }\n\n        .consent-container {\n            background: white;\n            border-radius: 12px;\n            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n            max-width: 480px;\n            width: 100%;\n            padding: 40px;\n        }\n\n        .header {\n            text-align: center;\n            margin-bottom: 30px;\n        }\n\n        .header h1 {\n            color: #1a202c;\n            font-size: 24px;\n            margin-bottom: 8px;\n        }\n\n        .header p {\n            color: #718096;\n            font-size: 14px;\n        }\n\n        .app-info {\n            background: #f7fafc;\n            border-radius: 8px;\n            padding: 20px;\n            margin-bottom: 24px;\n        }\n\n        .app-info h2 {\n            color: #2d3748;\n            font-size: 18px;\n            margin-bottom: 12px;\n        }\n\n        .app-name {\n            color: #667eea;\n            font-weight: 600;\n        }\n\n        .permissions {\n            margin-top: 16px;\n        }\n\n        .permissions h3 {\n            color: #4a5568;\n            font-size: 14px;\n            margin-bottom: 8px;\n            font-weight: 600;\n        }\n\n        .permissions ul {\n            list-style: none;\n        }\n\n        .permissions li {\n            color: #718096;\n            font-size: 14px;\n            padding: 6px 0;\n            padding-left: 24px;\n            position: relative;\n        }\n\n        .permissions li:before {\n            content: \"✓\";\n            position: absolute;\n            left: 0;\n            color: #48bb78;\n            font-weight: bold;\n        }\n\n        .warning {\n            background: #fffaf0;\n            border-left: 4px solid #ed8936;\n            padding: 12px 16px;\n            margin-bottom: 24px;\n            border-radius: 4px;\n        }\n\n        .warning p {\n            color: #744210;\n            font-size: 13px;\n            line-height: 1.5;\n        }\n\n        .actions {\n            display: flex;\n            gap: 12px;\n        }\n\n        button {\n            flex: 1;\n            padding: 14px 24px;\n            border: none;\n            border-radius: 6px;\n            font-size: 16px;\n            font-weight: 600;\n            cursor: pointer;\n            transition: all 0.2s;\n        }\n\n        .approve {\n            background: #667eea;\n            color: white;\n        }\n\n        .approve:hover {\n            background: #5a67d8;\n            transform: translateY(-1px);\n            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);\n        }\n\n        .deny {\n            background: #e2e8f0;\n            color: #4a5568;\n        }\n\n        .deny:hover {\n            background: #cbd5e0;\n        }\n\n        .footer {\n            margin-top: 24px;\n            text-align: center;\n            color: #a0aec0;\n            font-size: 12px;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"consent-container\">\n        <div class=\"header\">\n            <h1>🔐 Authorization Request</h1>\n            <p>via ${this.escapeHtml(provider)}</p>\n        </div>\n\n        <div class=\"app-info\">\n            <h2>\n                <span class=\"app-name\">${this.escapeHtml(clientName || \"An application\")}</span>\n                requests access\n            </h2>\n\n            <div class=\"permissions\">\n                <h3>This will allow the app to:</h3>\n                <ul>\n                    ${scope.map((s) => `<li>${this.escapeHtml(this.formatScope(s))}</li>`).join(\"\")}\n                </ul>\n            </div>\n        </div>\n\n        <div class=\"warning\">\n            <p>\n                <strong>⚠️ Important:</strong> Only approve if you trust this application.\n                By approving, you authorize it to access your account information.\n            </p>\n        </div>\n\n        <form method=\"POST\" action=\"/oauth/consent\">\n            <input type=\"hidden\" name=\"transaction_id\" value=\"${this.escapeHtml(transactionId)}\">\n            <div class=\"actions\">\n                <button type=\"submit\" name=\"action\" value=\"deny\" class=\"deny\">\n                    Deny\n                </button>\n                <button type=\"submit\" name=\"action\" value=\"approve\" class=\"approve\">\n                    Approve\n                </button>\n            </div>\n        </form>\n\n        <div class=\"footer\">\n            <p>This consent is required to prevent unauthorized access.</p>\n        </div>\n    </div>\n</body>\n</html>\n    `.trim();\n  }\n\n  /**\n   * Sign consent data for cookie\n   */\n  signConsentCookie(data: ConsentData): string {\n    const payload = JSON.stringify(data);\n    const signature = this.sign(payload);\n\n    return `${Buffer.from(payload).toString(\"base64\")}.${signature}`;\n  }\n\n  /**\n   * Validate and parse consent cookie\n   */\n  validateConsentCookie(cookie: string): ConsentData | null {\n    try {\n      const [payloadB64, signature] = cookie.split(\".\");\n\n      if (!payloadB64 || !signature) {\n        return null;\n      }\n\n      const payload = Buffer.from(payloadB64, \"base64\").toString(\"utf8\");\n      const expectedSignature = this.sign(payload);\n\n      if (signature !== expectedSignature) {\n        return null;\n      }\n\n      const data = JSON.parse(payload) as ConsentData;\n\n      // Check if consent is still valid (5 minutes)\n      const age = Date.now() - data.timestamp;\n      if (age > 5 * 60 * 1000) {\n        return null;\n      }\n\n      return data;\n    } catch {\n      return null;\n    }\n  }\n\n  /**\n   * Escape HTML to prevent XSS\n   */\n  private escapeHtml(text: string): string {\n    const map: Record<string, string> = {\n      \"'\": \"&#x27;\",\n      '\"': \"&quot;\",\n      \"/\": \"&#x2F;\",\n      \"&\": \"&amp;\",\n      \"<\": \"&lt;\",\n      \">\": \"&gt;\",\n    };\n\n    return text.replace(/[&<>\"'/]/g, (char) => map[char] || char);\n  }\n\n  /**\n   * Format scope for display\n   */\n  private formatScope(scope: string): string {\n    // Convert scope names to readable format\n    const scopeMap: Record<string, string> = {\n      email: \"Access your email address\",\n      openid: \"Verify your identity\",\n      profile: \"View your basic profile information\",\n      \"read:user\": \"Read your user information\",\n      \"write:user\": \"Modify your user information\",\n    };\n\n    return scopeMap[scope] || scope.replace(/_/g, \" \").replace(/:/g, \" - \");\n  }\n\n  /**\n   * Generate default signing key if none provided\n   */\n  private generateDefaultKey(): string {\n    return `fastmcp-consent-${Date.now()}-${Math.random()}`;\n  }\n\n  /**\n   * Sign a payload using HMAC-SHA256\n   */\n  private sign(payload: string): string {\n    return createHmac(\"sha256\", this.signingKey).update(payload).digest(\"hex\");\n  }\n}\n","/**\n * JWT Issuer for OAuth Proxy\n * Issues and validates short-lived JWTs that reference upstream provider tokens\n */\n\nimport { createHmac, pbkdf2, randomBytes } from \"crypto\";\nimport { promisify } from \"util\";\n\nimport {\n  DEFAULT_ACCESS_TOKEN_TTL,\n  DEFAULT_REFRESH_TOKEN_TTL,\n} from \"../types.js\";\n\nconst pbkdf2Async = promisify(pbkdf2);\n\n/**\n * JWT Claims for FastMCP tokens\n */\nexport interface JWTClaims {\n  /** Additional custom claims from upstream tokens */\n  [key: string]: unknown;\n  /** Audience */\n  aud: string;\n  /** Client ID */\n  client_id: string;\n  /** Expiration time (seconds since epoch) */\n  exp: number;\n  /** Issued at time (seconds since epoch) */\n  iat: number;\n  /** Issuer */\n  iss: string;\n  /** JWT ID (unique identifier) */\n  jti: string;\n  /** Scopes */\n  scope: string[];\n}\n\n/**\n * JWT Issuer configuration\n */\nexport interface JWTIssuerConfig {\n  /** Token expiration in seconds (default: 3600 = 1 hour) */\n  accessTokenTtl?: number;\n  /** Audience for issued tokens */\n  audience: string;\n  /** Issuer identifier */\n  issuer: string;\n  /** Refresh token expiration in seconds (default: 2592000 = 30 days) */\n  refreshTokenTtl?: number;\n  /** Secret key for signing tokens */\n  signingKey: string;\n}\n\n/**\n * Token validation result\n */\nexport interface TokenValidationResult {\n  /** Decoded claims if valid */\n  claims?: JWTClaims;\n  /** Error message if invalid */\n  error?: string;\n  /** Whether token is valid */\n  valid: boolean;\n}\n\n/**\n * JWT Header\n */\ninterface JWTHeader {\n  alg: string;\n  typ: string;\n}\n\n/**\n * JWT Issuer\n * Issues and validates HS256-signed JWTs for the OAuth proxy\n */\nexport class JWTIssuer {\n  private accessTokenTtl: number;\n  private audience: string;\n  private issuer: string;\n  private refreshTokenTtl: number;\n  private signingKey: Buffer;\n\n  constructor(config: JWTIssuerConfig) {\n    this.issuer = config.issuer;\n    this.audience = config.audience;\n    this.accessTokenTtl = config.accessTokenTtl || DEFAULT_ACCESS_TOKEN_TTL;\n    this.refreshTokenTtl = config.refreshTokenTtl || DEFAULT_REFRESH_TOKEN_TTL;\n    this.signingKey = Buffer.from(config.signingKey);\n  }\n\n  /**\n   * Derive a signing key from a secret\n   * Uses PBKDF2 for key derivation\n   */\n  static async deriveKey(\n    secret: string,\n    iterations: number = 100000,\n  ): Promise<string> {\n    const salt = Buffer.from(\"fastmcp-oauth-proxy\");\n    const key = await pbkdf2Async(secret, salt, iterations, 32, \"sha256\");\n    return key.toString(\"base64\");\n  }\n\n  /**\n   * Issue an access token\n   */\n  issueAccessToken(\n    clientId: string,\n    scope: string[],\n    additionalClaims?: Record<string, unknown>,\n    expiresIn?: number,\n  ): string {\n    const now = Math.floor(Date.now() / 1000);\n    const jti = this.generateJti();\n\n    const claims: JWTClaims = {\n      aud: this.audience,\n      client_id: clientId,\n      exp: now + (expiresIn ?? this.accessTokenTtl),\n      iat: now,\n      iss: this.issuer,\n      jti,\n      scope,\n      // Merge additional claims (custom claims from upstream)\n      ...(additionalClaims || {}),\n    };\n\n    return this.signToken(claims);\n  }\n\n  /**\n   * Issue a refresh token\n   */\n  issueRefreshToken(\n    clientId: string,\n    scope: string[],\n    additionalClaims?: Record<string, unknown>,\n    expiresIn?: number,\n  ): string {\n    const now = Math.floor(Date.now() / 1000);\n    const jti = this.generateJti();\n\n    const claims: JWTClaims = {\n      aud: this.audience,\n      client_id: clientId,\n      exp: now + (expiresIn ?? this.refreshTokenTtl),\n      iat: now,\n      iss: this.issuer,\n      jti,\n      scope,\n      // Merge additional claims (custom claims from upstream)\n      ...(additionalClaims || {}),\n    };\n\n    return this.signToken(claims);\n  }\n\n  /**\n   * Validate a JWT token\n   */\n  async verify(token: string): Promise<TokenValidationResult> {\n    try {\n      const parts = token.split(\".\");\n      if (parts.length !== 3) {\n        return {\n          error: \"Invalid token format\",\n          valid: false,\n        };\n      }\n\n      const [headerB64, payloadB64, signatureB64] = parts;\n\n      // Verify signature\n      const expectedSignature = this.sign(`${headerB64}.${payloadB64}`);\n      if (signatureB64 !== expectedSignature) {\n        return {\n          error: \"Invalid signature\",\n          valid: false,\n        };\n      }\n\n      // Decode claims\n      const claims: JWTClaims = JSON.parse(\n        Buffer.from(payloadB64, \"base64url\").toString(\"utf-8\"),\n      );\n\n      // Validate claims\n      const now = Math.floor(Date.now() / 1000);\n\n      if (claims.exp <= now) {\n        return {\n          claims,\n          error: \"Token expired\",\n          valid: false,\n        };\n      }\n\n      if (claims.iss !== this.issuer) {\n        return {\n          claims,\n          error: \"Invalid issuer\",\n          valid: false,\n        };\n      }\n\n      if (claims.aud !== this.audience) {\n        return {\n          claims,\n          error: \"Invalid audience\",\n          valid: false,\n        };\n      }\n\n      return {\n        claims,\n        valid: true,\n      };\n    } catch (error) {\n      return {\n        error: error instanceof Error ? error.message : \"Validation failed\",\n        valid: false,\n      };\n    }\n  }\n\n  /**\n   * Generate unique JWT ID\n   */\n  private generateJti(): string {\n    return randomBytes(16).toString(\"base64url\");\n  }\n\n  /**\n   * Sign data with HMAC-SHA256\n   */\n  private sign(data: string): string {\n    const hmac = createHmac(\"sha256\", this.signingKey);\n    hmac.update(data);\n    return hmac.digest(\"base64url\");\n  }\n\n  /**\n   * Sign a JWT token\n   */\n  private signToken(claims: JWTClaims): string {\n    const header: JWTHeader = {\n      alg: \"HS256\",\n      typ: \"JWT\",\n    };\n\n    const headerB64 = Buffer.from(JSON.stringify(header)).toString(\"base64url\");\n    const payloadB64 = Buffer.from(JSON.stringify(claims)).toString(\n      \"base64url\",\n    );\n\n    const signature = this.sign(`${headerB64}.${payloadB64}`);\n\n    return `${headerB64}.${payloadB64}.${signature}`;\n  }\n}\n","/**\n * PKCE (Proof Key for Code Exchange) Utilities\n * Implements RFC 7636 for OAuth 2.0 public clients\n */\n\nimport { createHash, randomBytes } from \"crypto\";\n\nimport type { PKCEPair } from \"../types.js\";\n\n/**\n * PKCE utility class for generating and validating code challenges\n */\nexport class PKCEUtils {\n  /**\n   * Generate a code challenge from a verifier\n   * @param verifier The code verifier\n   * @param method Challenge method: 'S256' or 'plain' (default: 'S256')\n   * @returns Base64URL-encoded challenge string\n   */\n  static generateChallenge(\n    verifier: string,\n    method: \"plain\" | \"S256\" = \"S256\",\n  ): string {\n    if (method === \"plain\") {\n      return verifier;\n    }\n\n    if (method === \"S256\") {\n      const hash = createHash(\"sha256\");\n      hash.update(verifier);\n      return PKCEUtils.base64URLEncode(hash.digest());\n    }\n\n    throw new Error(`Unsupported challenge method: ${method}`);\n  }\n\n  /**\n   * Generate a complete PKCE pair (verifier + challenge)\n   * @param method Challenge method: 'S256' or 'plain' (default: 'S256')\n   * @returns Object containing verifier and challenge\n   */\n  static generatePair(method: \"plain\" | \"S256\" = \"S256\"): PKCEPair {\n    const verifier = PKCEUtils.generateVerifier();\n    const challenge = PKCEUtils.generateChallenge(verifier, method);\n\n    return {\n      challenge,\n      verifier,\n    };\n  }\n\n  /**\n   * Generate a cryptographically secure code verifier\n   * @param length Length of verifier (43-128 characters, default: 128)\n   * @returns Base64URL-encoded verifier string\n   */\n  static generateVerifier(length: number = 128): string {\n    if (length < 43 || length > 128) {\n      throw new Error(\"PKCE verifier length must be between 43 and 128\");\n    }\n\n    // Generate random bytes and encode as base64url\n    // Need more bytes because base64 encoding expands data\n    const byteLength = Math.ceil((length * 3) / 4);\n    const randomBytesBuffer = randomBytes(byteLength);\n\n    return PKCEUtils.base64URLEncode(randomBytesBuffer).slice(0, length);\n  }\n\n  /**\n   * Validate a code verifier against a challenge\n   * @param verifier The code verifier to validate\n   * @param challenge The expected challenge\n   * @param method The challenge method used\n   * @returns True if verifier matches challenge\n   */\n  static validateChallenge(\n    verifier: string,\n    challenge: string,\n    method: string,\n  ): boolean {\n    if (!verifier || !challenge) {\n      return false;\n    }\n\n    if (method === \"plain\") {\n      return verifier === challenge;\n    }\n\n    if (method === \"S256\") {\n      const computedChallenge = PKCEUtils.generateChallenge(verifier, \"S256\");\n      return computedChallenge === challenge;\n    }\n\n    // Unknown method\n    return false;\n  }\n\n  /**\n   * Encode a buffer as base64url (RFC 4648)\n   * @param buffer Buffer to encode\n   * @returns Base64URL-encoded string\n   */\n  private static base64URLEncode(buffer: Buffer): string {\n    return buffer\n      .toString(\"base64\")\n      .replace(/\\+/g, \"-\")\n      .replace(/\\//g, \"_\")\n      .replace(/=/g, \"\");\n  }\n}\n","/**\n * Token Storage Implementations\n * Secure storage for OAuth tokens and transaction state\n */\n\nimport {\n  createCipheriv,\n  createDecipheriv,\n  randomBytes,\n  scryptSync,\n} from \"crypto\";\n\nimport type { TokenStorage } from \"../types.js\";\n\ninterface StorageEntry {\n  expiresAt: number;\n  value: unknown;\n}\n\n/**\n * Encrypted token storage wrapper\n * Encrypts values using AES-256-GCM before storing\n */\nexport class EncryptedTokenStorage implements TokenStorage {\n  private algorithm = \"aes-256-gcm\";\n  private backend: TokenStorage;\n  private encryptionKey: Buffer;\n\n  constructor(backend: TokenStorage, encryptionKey: string) {\n    this.backend = backend;\n    // Synchronously derive key using scrypt\n    const salt = Buffer.from(\"fastmcp-oauth-proxy-salt\");\n    this.encryptionKey = scryptSync(encryptionKey, salt, 32);\n  }\n\n  async cleanup(): Promise<void> {\n    await this.backend.cleanup();\n  }\n\n  async delete(key: string): Promise<void> {\n    await this.backend.delete(key);\n  }\n\n  async get(key: string): Promise<null | unknown> {\n    const encrypted = await this.backend.get(key);\n\n    if (!encrypted) {\n      return null;\n    }\n\n    try {\n      const decrypted = await this.decrypt(\n        encrypted as string,\n        this.encryptionKey,\n      );\n      return JSON.parse(decrypted);\n    } catch (error) {\n      console.error(\"Failed to decrypt value:\", error);\n      return null;\n    }\n  }\n\n  async save(key: string, value: unknown, ttl?: number): Promise<void> {\n    const encrypted = await this.encrypt(\n      JSON.stringify(value),\n      this.encryptionKey,\n    );\n    await this.backend.save(key, encrypted, ttl);\n  }\n\n  private async decrypt(ciphertext: string, key: Buffer): Promise<string> {\n    const parts = ciphertext.split(\":\");\n    if (parts.length !== 3) {\n      throw new Error(\"Invalid encrypted data format\");\n    }\n\n    const [ivHex, authTagHex, encrypted] = parts;\n    const iv = Buffer.from(ivHex, \"hex\");\n    const authTag = Buffer.from(authTagHex, \"hex\");\n\n    const decipher = createDecipheriv(this.algorithm, key, iv);\n    // Use type assertion for GCM-specific method\n    (decipher as unknown as { setAuthTag(buffer: Buffer): void }).setAuthTag(\n      authTag,\n    );\n\n    let decrypted = decipher.update(encrypted, \"hex\", \"utf8\");\n    decrypted += decipher.final(\"utf8\");\n\n    return decrypted;\n  }\n\n  private async encrypt(plaintext: string, key: Buffer): Promise<string> {\n    const iv = randomBytes(16);\n    const cipher = createCipheriv(this.algorithm, key, iv);\n\n    let encrypted = cipher.update(plaintext, \"utf8\", \"hex\");\n    encrypted += cipher.final(\"hex\");\n\n    // Use type assertion for GCM-specific method\n    const authTag = (\n      cipher as unknown as { getAuthTag(): Buffer }\n    ).getAuthTag();\n\n    // Return format: iv:authTag:encrypted\n    return `${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted}`;\n  }\n}\n\n/**\n * In-memory token storage with TTL support\n */\nexport class MemoryTokenStorage implements TokenStorage {\n  private cleanupInterval: NodeJS.Timeout | null = null;\n  private store: Map<string, StorageEntry> = new Map();\n\n  constructor(cleanupIntervalMs: number = 60000) {\n    // Run cleanup every minute by default\n    this.cleanupInterval = setInterval(\n      () => void this.cleanup(),\n      cleanupIntervalMs,\n    );\n  }\n\n  async cleanup(): Promise<void> {\n    const now = Date.now();\n    const keysToDelete: string[] = [];\n\n    for (const [key, entry] of this.store.entries()) {\n      if (entry.expiresAt < now) {\n        keysToDelete.push(key);\n      }\n    }\n\n    for (const key of keysToDelete) {\n      this.store.delete(key);\n    }\n  }\n\n  async delete(key: string): Promise<void> {\n    this.store.delete(key);\n  }\n\n  /**\n   * Destroy the storage and clear cleanup interval\n   */\n  destroy(): void {\n    if (this.cleanupInterval) {\n      clearInterval(this.cleanupInterval);\n      this.cleanupInterval = null;\n    }\n    this.store.clear();\n  }\n\n  async get(key: string): Promise<null | unknown> {\n    const entry = this.store.get(key);\n\n    if (!entry) {\n      return null;\n    }\n\n    if (entry.expiresAt < Date.now()) {\n      this.store.delete(key);\n      return null;\n    }\n\n    return entry.value;\n  }\n\n  async save(key: string, value: unknown, ttl?: number): Promise<void> {\n    const expiresAt = ttl ? Date.now() + ttl * 1000 : Number.MAX_SAFE_INTEGER;\n\n    this.store.set(key, {\n      expiresAt,\n      value,\n    });\n  }\n\n  /**\n   * Get the number of stored items\n   */\n  size(): number {\n    return this.store.size;\n  }\n}\n","/**\n * AuthProvider Base Class\n * High-level abstraction for OAuth authentication that simplifies configuration\n */\n\nimport type { IncomingMessage } from \"node:http\";\n\nimport type { TokenStorage, UpstreamTokenSet } from \"../types.js\";\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\n\n/**\n * Configuration common to all OAuth providers.\n */\nexport interface AuthProviderConfig {\n  /**\n   * Allow-list of redirect URI patterns accepted by Dynamic Client\n   * Registration. Required for any deployment that exposes /oauth/register\n   * or /oauth/authorize — an empty/unset list rejects every URI.\n   *\n   * Example: `[\"https://yourapp.example.com/*\"]`\n   *\n   * Prior versions defaulted to `[\"http://localhost:*\", \"https://*\"]`, which\n   * enabled CWE-601 open-redirect / authorization-code theft. See the\n   * SECURITY advisory before loosening this.\n   */\n  allowedRedirectUriPatterns?: string[];\n  /** Base URL where the MCP server is accessible */\n  baseUrl: string;\n  /** OAuth client ID */\n  clientId: string;\n  /** OAuth client secret */\n  clientSecret: string;\n  /** Require user consent screen (default: true) */\n  consentRequired?: boolean;\n  /** Encryption key for token storage (auto-generated if not provided, set to false to disable) */\n  encryptionKey?: false | string;\n  /** JWT signing key (auto-generated if not provided) */\n  jwtSigningKey?: string;\n  /** Scopes to request (defaults vary by provider) */\n  scopes?: string[];\n  /** Token storage backend (default: MemoryTokenStorage) */\n  tokenStorage?: TokenStorage;\n}\n\n/**\n * Configuration for generic OAuth provider (user-specified endpoints).\n */\nexport interface GenericOAuthProviderConfig extends AuthProviderConfig {\n  /** OAuth authorization endpoint URL */\n  authorizationEndpoint: string;\n  /** OAuth token endpoint URL */\n  tokenEndpoint: string;\n  /** Token endpoint auth method (default: \"client_secret_basic\") */\n  tokenEndpointAuthMethod?: \"client_secret_basic\" | \"client_secret_post\";\n}\n\n/**\n * Standard session type for OAuth providers.\n * Contains the upstream access token and optional metadata.\n */\nexport interface OAuthSession {\n  /** The upstream OAuth access token */\n  accessToken: string;\n  /** Additional claims extracted from the token (if customClaimsPassthrough enabled) */\n  claims?: Record<string, unknown>;\n  /** Token expiration time (Unix timestamp in seconds) */\n  expiresAt?: number;\n  /** ID token from OIDC providers */\n  idToken?: string;\n  /** Refresh token (if available) */\n  refreshToken?: string;\n  /** Scopes granted by the OAuth provider */\n  scopes?: string[];\n}\n\n/**\n * Abstract base class for OAuth providers.\n * Encapsulates OAuthProxy creation, authenticate function, and oauth config.\n *\n * Subclasses only need to implement the endpoint and default scope methods.\n */\nexport abstract class AuthProvider<\n  TSession extends OAuthSession = OAuthSession,\n> {\n  protected config: AuthProviderConfig;\n  /**\n   * Get the proxy, creating it lazily if needed.\n   */\n  protected get proxy(): OAuthProxy {\n    if (!this._proxy) {\n      this._proxy = this.createProxy();\n    }\n    return this._proxy;\n  }\n\n  private _proxy: OAuthProxy | undefined;\n\n  constructor(config: AuthProviderConfig) {\n    this.config = config;\n    // Note: proxy is created lazily to allow subclass constructors to run first\n  }\n\n  /**\n   * Authenticate function to be used by FastMCP.\n   * Extracts Bearer token, validates it, and returns session with upstream access token.\n   */\n  async authenticate(\n    request: IncomingMessage | undefined,\n  ): Promise<TSession | undefined> {\n    if (!request) {\n      // stdio transport - no HTTP authentication\n      return undefined;\n    }\n\n    const authHeader = request.headers?.authorization;\n    if (!authHeader || !authHeader.startsWith(\"Bearer \")) {\n      return undefined;\n    }\n\n    const token = authHeader.slice(7);\n    const upstreamTokens = await this.proxy.loadUpstreamTokens(token);\n\n    if (!upstreamTokens) {\n      return undefined;\n    }\n\n    return this.createSession(upstreamTokens);\n  }\n\n  /**\n   * Get the OAuth configuration object for FastMCP ServerOptions.\n   */\n  getOAuthConfig(): {\n    authorizationServer: ReturnType<\n      OAuthProxy[\"getAuthorizationServerMetadata\"]\n    >;\n    enabled: true;\n    protectedResource: {\n      authorizationServers: string[];\n      resource: string;\n      scopesSupported: string[];\n    };\n    proxy: OAuthProxy;\n  } {\n    return {\n      authorizationServer: this.proxy.getAuthorizationServerMetadata(),\n      enabled: true,\n      protectedResource: {\n        authorizationServers: [this.config.baseUrl],\n        resource: this.config.baseUrl,\n        scopesSupported: this.config.scopes ?? this.getDefaultScopes(),\n      },\n      proxy: this.proxy,\n    };\n  }\n\n  /**\n   * Get the OAuthProxy instance (for advanced use cases).\n   */\n  getProxy(): OAuthProxy {\n    return this.proxy;\n  }\n\n  /** Create the underlying OAuthProxy with provider-specific configuration */\n  protected abstract createProxy(): OAuthProxy;\n\n  /**\n   * Create a session object from upstream tokens.\n   * Override in subclasses to add provider-specific session data.\n   */\n  protected createSession(upstreamTokens: UpstreamTokenSet): TSession {\n    return {\n      accessToken: upstreamTokens.accessToken,\n      expiresAt: upstreamTokens.expiresIn\n        ? Math.floor(Date.now() / 1000) + upstreamTokens.expiresIn\n        : undefined,\n      idToken: upstreamTokens.idToken,\n      refreshToken: upstreamTokens.refreshToken,\n      scopes: upstreamTokens.scope,\n    } as TSession;\n  }\n\n  /** Get the authorization endpoint for this provider */\n  protected abstract getAuthorizationEndpoint(): string;\n\n  /** Default scopes for this provider */\n  protected abstract getDefaultScopes(): string[];\n\n  /** Get the token endpoint for this provider */\n  protected abstract getTokenEndpoint(): string;\n}\n","/**\n * Microsoft Azure/Entra ID OAuth Provider\n * Pre-configured OAuth provider for Microsoft Identity Platform\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type AuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * Azure-specific configuration\n */\nexport interface AzureProviderConfig extends AuthProviderConfig {\n  /** Tenant ID or 'common', 'organizations', 'consumers' (default: 'common') */\n  tenantId?: string;\n}\n\n/**\n * Azure-specific session with additional user info\n */\nexport interface AzureSession extends OAuthSession {\n  upn?: string;\n}\n\n/**\n * Microsoft Azure AD / Entra ID OAuth 2.0 Provider\n * Callback URL: {baseUrl}/oauth/callback\n */\nexport class AzureProvider extends AuthProvider<AzureSession> {\n  private tenantId: string;\n\n  constructor(config: AzureProviderConfig) {\n    super(config);\n    this.tenantId = config.tenantId ?? \"common\";\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/authorize`;\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"openid\", \"profile\", \"email\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return `https://login.microsoftonline.com/${this.tenantId}/oauth2/v2.0/token`;\n  }\n}\n","/**\n * GitHub OAuth Provider\n * Pre-configured OAuth provider for GitHub OAuth Apps\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type AuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * GitHub-specific session with additional user info\n */\nexport interface GitHubSession extends OAuthSession {\n  username?: string;\n}\n\n/**\n * GitHub OAuth 2.0 Provider\n * Callback URL: {baseUrl}/oauth/callback\n */\nexport class GitHubProvider extends AuthProvider<GitHubSession> {\n  constructor(config: AuthProviderConfig) {\n    super(config);\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return \"https://github.com/login/oauth/authorize\";\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"read:user\", \"user:email\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return \"https://github.com/login/oauth/access_token\";\n  }\n}\n","/**\n * Google OAuth Provider\n * Pre-configured OAuth provider for Google Identity Platform\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type AuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * Google-specific session with additional user info\n */\nexport interface GoogleSession extends OAuthSession {\n  email?: string;\n}\n\n/**\n * Google OAuth 2.0 Provider\n * Callback URL: {baseUrl}/oauth/callback\n */\nexport class GoogleProvider extends AuthProvider<GoogleSession> {\n  constructor(config: AuthProviderConfig) {\n    super(config);\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return \"https://accounts.google.com/o/oauth2/v2/auth\";\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"openid\", \"profile\", \"email\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return \"https://oauth2.googleapis.com/token\";\n  }\n}\n","/**\n * Generic OAuth Provider\n * For any OAuth 2.0 compliant authorization server\n */\n\nimport { OAuthProxy } from \"../OAuthProxy.js\";\nimport {\n  AuthProvider,\n  type GenericOAuthProviderConfig,\n  type OAuthSession,\n} from \"./AuthProvider.js\";\n\n/**\n * Generic OAuth provider for any OAuth 2.0 compliant authorization server.\n * Use when there's no built-in provider for your identity provider.\n */\nexport class OAuthProvider<\n  TSession extends OAuthSession = OAuthSession,\n> extends AuthProvider<TSession> {\n  protected genericConfig: GenericOAuthProviderConfig;\n\n  constructor(config: GenericOAuthProviderConfig) {\n    super(config);\n    this.genericConfig = config;\n  }\n\n  protected createProxy(): OAuthProxy {\n    return new OAuthProxy({\n      // No fallback default: framework users must explicitly list the URIs\n      // they trust. A previous default of [\"http://localhost:*\", \"https://*\"]\n      // enabled CWE-601 open-redirect / code-theft via /oauth/authorize.\n      allowedRedirectUriPatterns: this.config.allowedRedirectUriPatterns,\n      baseUrl: this.config.baseUrl,\n      consentRequired: this.config.consentRequired ?? true,\n      encryptionKey: this.config.encryptionKey,\n      jwtSigningKey: this.config.jwtSigningKey,\n      scopes: this.config.scopes ?? this.getDefaultScopes(),\n      tokenStorage: this.config.tokenStorage,\n      upstreamAuthorizationEndpoint: this.getAuthorizationEndpoint(),\n      upstreamClientId: this.config.clientId,\n      upstreamClientSecret: this.config.clientSecret,\n      upstreamTokenEndpoint: this.getTokenEndpoint(),\n      upstreamTokenEndpointAuthMethod:\n        this.genericConfig.tokenEndpointAuthMethod ?? \"client_secret_basic\",\n    });\n  }\n\n  protected getAuthorizationEndpoint(): string {\n    return this.genericConfig.authorizationEndpoint;\n  }\n\n  protected getDefaultScopes(): string[] {\n    return [\"openid\"];\n  }\n\n  protected getTokenEndpoint(): string {\n    return this.genericConfig.tokenEndpoint;\n  }\n}\n","/**\n * Disk-based Token Storage Implementation\n * Provides persistent file-based storage for OAuth tokens and transaction state\n */\n\nimport { mkdir, readdir, readFile, rm, stat, writeFile } from \"fs/promises\";\nimport { join } from \"path\";\n\nimport type { TokenStorage } from \"../types.js\";\n\nexport interface DiskStoreOptions {\n  /**\n   * How often to run cleanup (in milliseconds)\n   * @default 60000 (1 minute)\n   */\n  cleanupIntervalMs?: number;\n\n  /**\n   * Directory path for storing data\n   */\n  directory: string;\n\n  /**\n   * File extension for stored files\n   * @default \".json\"\n   */\n  fileExtension?: string;\n}\n\ninterface StorageEntry {\n  expiresAt: number;\n  value: unknown;\n}\n\n/**\n * Disk-based token storage with TTL support\n * Persists tokens to filesystem for survival across server restarts\n */\nexport class DiskStore implements TokenStorage {\n  private cleanupInterval: NodeJS.Timeout | null = null;\n  private directory: string;\n  private fileExtension: string;\n\n  constructor(options: DiskStoreOptions) {\n    this.directory = options.directory;\n    this.fileExtension = options.fileExtension || \".json\";\n\n    // Ensure directory exists\n    void this.ensureDirectory();\n\n    // Start periodic cleanup\n    const cleanupIntervalMs = options.cleanupIntervalMs || 60000;\n    this.cleanupInterval = setInterval(() => {\n      void this.cleanup();\n    }, cleanupIntervalMs);\n  }\n\n  /**\n   * Clean up expired entries\n   */\n  async cleanup(): Promise<void> {\n    try {\n      await this.ensureDirectory();\n      const files = await readdir(this.directory);\n      const now = Date.now();\n\n      for (const file of files) {\n        if (!file.endsWith(this.fileExtension)) {\n          continue;\n        }\n\n        try {\n          const filePath = join(this.directory, file);\n          const content = await readFile(filePath, \"utf-8\");\n          const entry: StorageEntry = JSON.parse(content);\n\n          if (entry.expiresAt < now) {\n            await rm(filePath);\n          }\n        } catch (error) {\n          // If file is corrupted or can't be read, delete it\n          console.warn(`Failed to read/parse file ${file}, deleting:`, error);\n          try {\n            await rm(join(this.directory, file));\n          } catch {\n            // Ignore deletion errors\n          }\n        }\n      }\n    } catch (error) {\n      console.error(\"Cleanup failed:\", error);\n    }\n  }\n\n  /**\n   * Delete a value\n   */\n  async delete(key: string): Promise<void> {\n    const filePath = this.getFilePath(key);\n    try {\n      await rm(filePath);\n    } catch (error) {\n      // File might not exist, which is fine\n      if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n        console.error(`Failed to delete key ${key}:`, error);\n      }\n    }\n  }\n\n  /**\n   * Destroy the storage and clear cleanup interval\n   */\n  destroy(): void {\n    if (this.cleanupInterval) {\n      clearInterval(this.cleanupInterval);\n      this.cleanupInterval = null;\n    }\n  }\n\n  /**\n   * Retrieve a value\n   */\n  async get(key: string): Promise<null | unknown> {\n    const filePath = this.getFilePath(key);\n\n    try {\n      const content = await readFile(filePath, \"utf-8\");\n      const entry: StorageEntry = JSON.parse(content);\n\n      // Check if expired\n      if (entry.expiresAt < Date.now()) {\n        await rm(filePath);\n        return null;\n      }\n\n      return entry.value;\n    } catch (error) {\n      // File doesn't exist or is corrupted\n      if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n        return null;\n      }\n      console.error(`Failed to read key ${key}:`, error);\n      return null;\n    }\n  }\n\n  /**\n   * Save a value with optional TTL\n   */\n  async save(key: string, value: unknown, ttl?: number): Promise<void> {\n    await this.ensureDirectory();\n\n    const filePath = this.getFilePath(key);\n    const expiresAt = ttl ? Date.now() + ttl * 1000 : Number.MAX_SAFE_INTEGER;\n\n    const entry: StorageEntry = {\n      expiresAt,\n      value,\n    };\n\n    try {\n      await writeFile(filePath, JSON.stringify(entry, null, 2), \"utf-8\");\n    } catch (error) {\n      console.error(`Failed to save key ${key}:`, error);\n      throw error;\n    }\n  }\n\n  /**\n   * Get the number of stored items\n   */\n  async size(): Promise<number> {\n    try {\n      await this.ensureDirectory();\n      const files = await readdir(this.directory);\n      return files.filter((f) => f.endsWith(this.fileExtension)).length;\n    } catch {\n      return 0;\n    }\n  }\n\n  /**\n   * Ensure storage directory exists\n   */\n  private async ensureDirectory(): Promise<void> {\n    try {\n      const stats = await stat(this.directory);\n      if (!stats.isDirectory()) {\n        throw new Error(`Path ${this.directory} exists but is not a directory`);\n      }\n    } catch (error) {\n      if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n        await mkdir(this.directory, { recursive: true });\n      } else {\n        throw error;\n      }\n    }\n  }\n\n  /**\n   * Get file path for a key\n   */\n  private getFilePath(key: string): string {\n    // Sanitize key to prevent directory traversal\n    const sanitizedKey = key.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n    return join(this.directory, `${sanitizedKey}${this.fileExtension}`);\n  }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/**\n * JWKS (JSON Web Key Set) Verifier\n * Provides JWT verification using public keys from JWKS endpoints\n *\n * Requires the 'jose' package as an optional peer dependency.\n * Install with: npm install jose\n */\n\nimport type { TokenVerificationResult, TokenVerifier } from \"../types.js\";\nimport type { JWTClaims } from \"./jwtIssuer.js\";\n\n/**\n * Token verification result\n */\nexport interface JWKSVerificationResult {\n  claims?: JWTClaims;\n  error?: string;\n  valid: boolean;\n}\n\n/**\n * JWKS configuration options\n */\nexport interface JWKSVerifierConfig {\n  /**\n   * Expected token audience\n   */\n  audience?: string;\n\n  /**\n   * Cache duration for JWKS keys in milliseconds\n   * @default 3600000 (1 hour)\n   */\n  cacheDuration?: number;\n\n  /**\n   * Cooldown duration between JWKS refetches in milliseconds\n   * @default 30000 (30 seconds)\n   */\n  cooldownDuration?: number;\n\n  /**\n   * Expected token issuer\n   */\n  issuer?: string;\n\n  /**\n   * JWKS endpoint URL (e.g., https://provider.com/.well-known/jwks.json)\n   */\n  jwksUri: string;\n}\n\n/**\n * JWKS Verifier\n * Verifies JWTs using public keys from a JWKS endpoint\n *\n * This class requires the 'jose' package to be installed:\n * ```bash\n * npm install jose\n * ```\n *\n * @example\n * ```typescript\n * const verifier = new JWKSVerifier({\n *   jwksUri: 'https://accounts.google.com/.well-known/jwks.json',\n *   audience: 'your-client-id',\n *   issuer: 'https://accounts.google.com'\n * });\n *\n * const result = await verifier.verify(token);\n * if (result.valid) {\n *   console.log('Token claims:', result.claims);\n * }\n * ```\n */\nexport class JWKSVerifier implements TokenVerifier {\n  private config: Required<JWKSVerifierConfig>;\n  private jose: any;\n  private joseLoaded = false;\n  private jwksCache: any;\n\n  constructor(config: JWKSVerifierConfig) {\n    this.config = {\n      cacheDuration: 3600000, // 1 hour\n      cooldownDuration: 30000, // 30 seconds\n      ...config,\n      audience: config.audience || \"\",\n      issuer: config.issuer || \"\",\n    };\n  }\n\n  /**\n   * Get the JWKS URI being used\n   */\n  getJwksUri(): string {\n    return this.config.jwksUri;\n  }\n\n  /**\n   * Refresh the JWKS cache\n   * Useful if you need to force a key refresh\n   */\n  async refreshKeys(): Promise<void> {\n    await this.loadJose();\n\n    // Recreate the JWKS cache to force a refresh\n    this.jwksCache = this.jose.createRemoteJWKSet(\n      new URL(this.config.jwksUri),\n      {\n        cacheMaxAge: this.config.cacheDuration,\n        cooldownDuration: this.config.cooldownDuration,\n      },\n    );\n  }\n\n  /**\n   * Verify a JWT token using JWKS\n   *\n   * @param token - The JWT token to verify\n   * @returns Verification result with claims if valid\n   *\n   * @example\n   * ```typescript\n   * const result = await verifier.verify(token);\n   * if (result.valid) {\n   *   console.log('User:', result.claims?.client_id);\n   * } else {\n   *   console.error('Invalid token:', result.error);\n   * }\n   * ```\n   */\n  async verify(token: string): Promise<TokenVerificationResult> {\n    try {\n      // Ensure jose is loaded\n      await this.loadJose();\n\n      // Verify the token using JWKS\n      const verifyOptions: any = {};\n\n      if (this.config.audience) {\n        verifyOptions.audience = this.config.audience;\n      }\n\n      if (this.config.issuer) {\n        verifyOptions.issuer = this.config.issuer;\n      }\n\n      const { payload } = await this.jose.jwtVerify(\n        token,\n        this.jwksCache,\n        verifyOptions,\n      );\n\n      // Map jose claims to TokenVerificationResult format\n      // Store all claims as Record<string, unknown> for compatibility\n      const claims: Record<string, unknown> = {\n        aud: payload.aud,\n        client_id: payload.client_id || payload.sub,\n        exp: payload.exp,\n        iat: payload.iat,\n        iss: payload.iss,\n        jti: payload.jti || \"\",\n        scope: this.parseScope(payload.scope),\n        ...payload, // Include all other claims\n      };\n\n      return {\n        claims,\n        valid: true,\n      };\n    } catch (error: any) {\n      return {\n        error: error.message || \"Token verification failed\",\n        valid: false,\n      };\n    }\n  }\n\n  /**\n   * Lazy load the jose library\n   * Only loads when verification is first attempted\n   */\n  private async loadJose(): Promise<void> {\n    if (this.joseLoaded) {\n      return;\n    }\n\n    try {\n      this.jose = await import(\"jose\");\n      this.joseLoaded = true;\n\n      // Create the JWKS cache with the configured URI\n      this.jwksCache = this.jose.createRemoteJWKSet(\n        new URL(this.config.jwksUri),\n        {\n          cacheMaxAge: this.config.cacheDuration,\n          cooldownDuration: this.config.cooldownDuration,\n        },\n      );\n    } catch (error: any) {\n      throw new Error(\n        `JWKS verification requires the 'jose' package.\\n` +\n          `Install it with: npm install jose\\n\\n` +\n          `If you don't need JWKS support, use HS256 signing instead (default).\\n\\n` +\n          `Original error: ${error.message}`,\n      );\n    }\n  }\n\n  /**\n   * Parse scope from token payload\n   * Handles both string (space-separated) and array formats\n   */\n  private parseScope(scope: unknown): string[] {\n    if (!scope) {\n      return [];\n    }\n\n    if (typeof scope === \"string\") {\n      return scope.split(\" \").filter(Boolean);\n    }\n\n    if (Array.isArray(scope)) {\n      return scope;\n    }\n\n    return [];\n  }\n}\n"]}