mcp-trellis 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +63 -93
  2. package/dist/adapters/node-request.d.ts +53 -0
  3. package/dist/adapters/node-request.d.ts.map +1 -0
  4. package/dist/adapters/node-request.js +165 -0
  5. package/dist/adapters/node-request.js.map +1 -0
  6. package/dist/adapters/node.d.ts +13 -30
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +55 -101
  9. package/dist/adapters/node.js.map +1 -1
  10. package/dist/advanced.d.ts +18 -0
  11. package/dist/advanced.d.ts.map +1 -0
  12. package/dist/advanced.js +18 -0
  13. package/dist/advanced.js.map +1 -0
  14. package/dist/app-options.d.ts +132 -0
  15. package/dist/app-options.d.ts.map +1 -0
  16. package/dist/app-options.js +2 -0
  17. package/dist/app-options.js.map +1 -0
  18. package/dist/app.d.ts +2 -89
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +143 -63
  21. package/dist/app.js.map +1 -1
  22. package/dist/audit.d.ts +5 -5
  23. package/dist/audit.d.ts.map +1 -1
  24. package/dist/audit.js +19 -8
  25. package/dist/audit.js.map +1 -1
  26. package/dist/auth/bearer.d.ts +4 -1
  27. package/dist/auth/bearer.d.ts.map +1 -1
  28. package/dist/auth/bearer.js +19 -4
  29. package/dist/auth/bearer.js.map +1 -1
  30. package/dist/clients.js +1 -1
  31. package/dist/clients.js.map +1 -1
  32. package/dist/deprecated.d.ts +42 -0
  33. package/dist/deprecated.d.ts.map +1 -0
  34. package/dist/deprecated.js +35 -0
  35. package/dist/deprecated.js.map +1 -0
  36. package/dist/{body.d.ts → http/body.d.ts} +27 -6
  37. package/dist/http/body.d.ts.map +1 -0
  38. package/dist/{body.js → http/body.js} +33 -13
  39. package/dist/http/body.js.map +1 -0
  40. package/dist/http/http.d.ts +42 -0
  41. package/dist/http/http.d.ts.map +1 -0
  42. package/dist/http/http.js +100 -0
  43. package/dist/http/http.js.map +1 -0
  44. package/dist/http/origins.d.ts +37 -0
  45. package/dist/http/origins.d.ts.map +1 -0
  46. package/dist/http/origins.js +87 -0
  47. package/dist/http/origins.js.map +1 -0
  48. package/dist/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
  49. package/dist/mcp/dispatch.d.ts.map +1 -0
  50. package/dist/mcp/dispatch.js +99 -0
  51. package/dist/mcp/dispatch.js.map +1 -0
  52. package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +9 -0
  53. package/dist/mcp/jsonrpc.d.ts.map +1 -0
  54. package/dist/mcp/jsonrpc.js +88 -0
  55. package/dist/mcp/jsonrpc.js.map +1 -0
  56. package/dist/{methods.d.ts → mcp/methods.d.ts} +60 -11
  57. package/dist/mcp/methods.d.ts.map +1 -0
  58. package/dist/mcp/methods.js +84 -0
  59. package/dist/mcp/methods.js.map +1 -0
  60. package/dist/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
  61. package/dist/mcp/protocol.d.ts.map +1 -0
  62. package/dist/{protocol.js → mcp/protocol.js} +1 -3
  63. package/dist/mcp/protocol.js.map +1 -0
  64. package/dist/mcp/registry.d.ts +58 -0
  65. package/dist/mcp/registry.d.ts.map +1 -0
  66. package/dist/mcp/registry.js +81 -0
  67. package/dist/mcp/registry.js.map +1 -0
  68. package/dist/mcp/rpc-handlers.d.ts +11 -0
  69. package/dist/mcp/rpc-handlers.d.ts.map +1 -0
  70. package/dist/mcp/rpc-handlers.js +133 -0
  71. package/dist/mcp/rpc-handlers.js.map +1 -0
  72. package/dist/{tools.d.ts → mcp/tools.d.ts} +20 -1
  73. package/dist/mcp/tools.d.ts.map +1 -0
  74. package/dist/mcp/tools.js +166 -0
  75. package/dist/mcp/tools.js.map +1 -0
  76. package/dist/{validate.d.ts → mcp/validate.d.ts} +18 -21
  77. package/dist/mcp/validate.d.ts.map +1 -0
  78. package/dist/{validate.js → mcp/validate.js} +59 -41
  79. package/dist/mcp/validate.js.map +1 -0
  80. package/dist/oauth/audit.d.ts +20 -0
  81. package/dist/oauth/audit.d.ts.map +1 -0
  82. package/dist/oauth/audit.js +29 -0
  83. package/dist/oauth/audit.js.map +1 -0
  84. package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
  85. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
  86. package/dist/oauth/cimd/cimd-parse.js +70 -0
  87. package/dist/oauth/cimd/cimd-parse.js.map +1 -0
  88. package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
  89. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
  90. package/dist/oauth/cimd/cimd-ssrf.js +193 -0
  91. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
  92. package/dist/oauth/cimd/cimd.d.ts +36 -0
  93. package/dist/oauth/cimd/cimd.d.ts.map +1 -0
  94. package/dist/oauth/cimd/cimd.js +132 -0
  95. package/dist/oauth/cimd/cimd.js.map +1 -0
  96. package/dist/oauth/cimd/types.d.ts +12 -0
  97. package/dist/oauth/cimd/types.d.ts.map +1 -0
  98. package/dist/oauth/cimd/types.js +3 -0
  99. package/dist/oauth/cimd/types.js.map +1 -0
  100. package/dist/oauth/config.d.ts +51 -0
  101. package/dist/oauth/config.d.ts.map +1 -0
  102. package/dist/oauth/config.js +108 -0
  103. package/dist/oauth/config.js.map +1 -0
  104. package/dist/oauth/constants.d.ts +11 -0
  105. package/dist/oauth/constants.d.ts.map +1 -1
  106. package/dist/oauth/constants.js +11 -0
  107. package/dist/oauth/constants.js.map +1 -1
  108. package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
  109. package/dist/oauth/crypto/base64url.d.ts.map +1 -0
  110. package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
  111. package/dist/oauth/crypto/base64url.js.map +1 -0
  112. package/dist/oauth/crypto/clientauth.d.ts +13 -0
  113. package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
  114. package/dist/oauth/crypto/clientauth.js +195 -0
  115. package/dist/oauth/crypto/clientauth.js.map +1 -0
  116. package/dist/oauth/crypto/codes.d.ts +65 -0
  117. package/dist/oauth/crypto/codes.d.ts.map +1 -0
  118. package/dist/oauth/crypto/codes.js +121 -0
  119. package/dist/oauth/crypto/codes.js.map +1 -0
  120. package/dist/oauth/crypto/hkdf.d.ts +13 -0
  121. package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
  122. package/dist/oauth/crypto/hkdf.js +30 -0
  123. package/dist/oauth/crypto/hkdf.js.map +1 -0
  124. package/dist/oauth/crypto/keycache.d.ts +19 -0
  125. package/dist/oauth/crypto/keycache.d.ts.map +1 -0
  126. package/dist/oauth/crypto/keycache.js +44 -0
  127. package/dist/oauth/crypto/keycache.js.map +1 -0
  128. package/dist/oauth/crypto/pkce.d.ts.map +1 -0
  129. package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
  130. package/dist/oauth/crypto/pkce.js.map +1 -0
  131. package/dist/oauth/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
  132. package/dist/oauth/crypto/sealed.d.ts.map +1 -0
  133. package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
  134. package/dist/oauth/crypto/sealed.js.map +1 -0
  135. package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
  136. package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
  137. package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
  138. package/dist/oauth/crypto/secrethash.js.map +1 -0
  139. package/dist/oauth/deprecated.d.ts +52 -0
  140. package/dist/oauth/deprecated.d.ts.map +1 -0
  141. package/dist/oauth/deprecated.js +49 -0
  142. package/dist/oauth/deprecated.js.map +1 -0
  143. package/dist/oauth/endpoints/authorize.d.ts +3 -0
  144. package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
  145. package/dist/oauth/endpoints/authorize.js +209 -0
  146. package/dist/oauth/endpoints/authorize.js.map +1 -0
  147. package/dist/oauth/endpoints/callback.d.ts +20 -0
  148. package/dist/oauth/endpoints/callback.d.ts.map +1 -0
  149. package/dist/oauth/endpoints/callback.js +31 -0
  150. package/dist/oauth/endpoints/callback.js.map +1 -0
  151. package/dist/oauth/endpoints/consent-page.d.ts +5 -0
  152. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
  153. package/dist/oauth/endpoints/consent-page.js +63 -0
  154. package/dist/oauth/endpoints/consent-page.js.map +1 -0
  155. package/dist/oauth/endpoints/consent.d.ts +29 -0
  156. package/dist/oauth/endpoints/consent.d.ts.map +1 -0
  157. package/dist/oauth/endpoints/consent.js +98 -0
  158. package/dist/oauth/endpoints/consent.js.map +1 -0
  159. package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +8 -2
  160. package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
  161. package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
  162. package/dist/oauth/endpoints/metadata.js.map +1 -0
  163. package/dist/oauth/endpoints/register.d.ts +10 -0
  164. package/dist/oauth/endpoints/register.d.ts.map +1 -0
  165. package/dist/oauth/endpoints/register.js +96 -0
  166. package/dist/oauth/endpoints/register.js.map +1 -0
  167. package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
  168. package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
  169. package/dist/oauth/{revoke.js → endpoints/revoke.js} +8 -18
  170. package/dist/oauth/endpoints/revoke.js.map +1 -0
  171. package/dist/oauth/endpoints/token.d.ts +3 -0
  172. package/dist/oauth/endpoints/token.d.ts.map +1 -0
  173. package/dist/oauth/{token.js → endpoints/token.js} +32 -25
  174. package/dist/oauth/endpoints/token.js.map +1 -0
  175. package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
  176. package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
  177. package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -9
  178. package/dist/oauth/endpoints/wellknown.js.map +1 -0
  179. package/dist/oauth/policy/redirect.d.ts.map +1 -0
  180. package/dist/oauth/policy/redirect.js.map +1 -0
  181. package/dist/oauth/policy/reqbody.d.ts +18 -0
  182. package/dist/oauth/policy/reqbody.d.ts.map +1 -0
  183. package/dist/oauth/policy/reqbody.js +68 -0
  184. package/dist/oauth/policy/reqbody.js.map +1 -0
  185. package/dist/oauth/{resource.d.ts → policy/resource.d.ts} +1 -1
  186. package/dist/oauth/policy/resource.d.ts.map +1 -0
  187. package/dist/oauth/{resource.js → policy/resource.js} +2 -2
  188. package/dist/oauth/policy/resource.js.map +1 -0
  189. package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
  190. package/dist/oauth/policy/scope.d.ts.map +1 -0
  191. package/dist/oauth/{scope.js → policy/scope.js} +3 -3
  192. package/dist/oauth/policy/scope.js.map +1 -0
  193. package/dist/oauth/router.d.ts +10 -13
  194. package/dist/oauth/router.d.ts.map +1 -1
  195. package/dist/oauth/router.js +93 -115
  196. package/dist/oauth/router.js.map +1 -1
  197. package/dist/oauth/types.d.ts +99 -45
  198. package/dist/oauth/types.d.ts.map +1 -1
  199. package/dist/oauth/types.js +1 -90
  200. package/dist/oauth/types.js.map +1 -1
  201. package/dist/server.d.ts +12 -12
  202. package/dist/server.d.ts.map +1 -1
  203. package/dist/server.js +9 -8
  204. package/dist/server.js.map +1 -1
  205. package/dist/util/defined.d.ts +11 -0
  206. package/dist/util/defined.d.ts.map +1 -0
  207. package/dist/util/defined.js +15 -0
  208. package/dist/util/defined.js.map +1 -0
  209. package/dist/util/json.d.ts +3 -0
  210. package/dist/util/json.d.ts.map +1 -0
  211. package/dist/util/json.js +3 -0
  212. package/dist/util/json.js.map +1 -0
  213. package/docs/ROADMAP.md +35 -47
  214. package/docs/compatibility.md +84 -0
  215. package/docs/diagrams/mcp-trellis-architecture.html +305 -0
  216. package/docs/guide.md +37 -13
  217. package/docs/reference.md +54 -22
  218. package/docs/security.md +58 -9
  219. package/docs/troubleshooting.md +19 -0
  220. package/package.json +23 -10
  221. package/dist/adapters/origins.d.ts +0 -19
  222. package/dist/adapters/origins.d.ts.map +0 -1
  223. package/dist/adapters/origins.js +0 -52
  224. package/dist/adapters/origins.js.map +0 -1
  225. package/dist/body.d.ts.map +0 -1
  226. package/dist/body.js.map +0 -1
  227. package/dist/dispatch.d.ts.map +0 -1
  228. package/dist/dispatch.js +0 -119
  229. package/dist/dispatch.js.map +0 -1
  230. package/dist/http.d.ts +0 -17
  231. package/dist/http.d.ts.map +0 -1
  232. package/dist/http.js +0 -38
  233. package/dist/http.js.map +0 -1
  234. package/dist/jsonrpc.d.ts.map +0 -1
  235. package/dist/jsonrpc.js +0 -18
  236. package/dist/jsonrpc.js.map +0 -1
  237. package/dist/methods.d.ts.map +0 -1
  238. package/dist/methods.js +0 -130
  239. package/dist/methods.js.map +0 -1
  240. package/dist/oauth/authorize.d.ts +0 -3
  241. package/dist/oauth/authorize.d.ts.map +0 -1
  242. package/dist/oauth/authorize.js +0 -141
  243. package/dist/oauth/authorize.js.map +0 -1
  244. package/dist/oauth/base64url.d.ts.map +0 -1
  245. package/dist/oauth/base64url.js.map +0 -1
  246. package/dist/oauth/clientauth.d.ts +0 -24
  247. package/dist/oauth/clientauth.d.ts.map +0 -1
  248. package/dist/oauth/clientauth.js +0 -119
  249. package/dist/oauth/clientauth.js.map +0 -1
  250. package/dist/oauth/codes.d.ts +0 -30
  251. package/dist/oauth/codes.d.ts.map +0 -1
  252. package/dist/oauth/codes.js +0 -85
  253. package/dist/oauth/codes.js.map +0 -1
  254. package/dist/oauth/consent.d.ts +0 -65
  255. package/dist/oauth/consent.d.ts.map +0 -1
  256. package/dist/oauth/consent.js +0 -173
  257. package/dist/oauth/consent.js.map +0 -1
  258. package/dist/oauth/metadata.d.ts.map +0 -1
  259. package/dist/oauth/metadata.js.map +0 -1
  260. package/dist/oauth/pkce.d.ts.map +0 -1
  261. package/dist/oauth/pkce.js.map +0 -1
  262. package/dist/oauth/redirect.d.ts.map +0 -1
  263. package/dist/oauth/redirect.js.map +0 -1
  264. package/dist/oauth/reqbody.d.ts +0 -10
  265. package/dist/oauth/reqbody.d.ts.map +0 -1
  266. package/dist/oauth/reqbody.js +0 -29
  267. package/dist/oauth/reqbody.js.map +0 -1
  268. package/dist/oauth/resource.d.ts.map +0 -1
  269. package/dist/oauth/resource.js.map +0 -1
  270. package/dist/oauth/revoke.d.ts.map +0 -1
  271. package/dist/oauth/revoke.js.map +0 -1
  272. package/dist/oauth/scope.d.ts.map +0 -1
  273. package/dist/oauth/scope.js.map +0 -1
  274. package/dist/oauth/sealed.d.ts.map +0 -1
  275. package/dist/oauth/sealed.js.map +0 -1
  276. package/dist/oauth/secrethash.d.ts.map +0 -1
  277. package/dist/oauth/secrethash.js.map +0 -1
  278. package/dist/oauth/token.d.ts +0 -3
  279. package/dist/oauth/token.d.ts.map +0 -1
  280. package/dist/oauth/token.js.map +0 -1
  281. package/dist/oauth/wellknown.d.ts.map +0 -1
  282. package/dist/oauth/wellknown.js.map +0 -1
  283. package/dist/protocol.d.ts.map +0 -1
  284. package/dist/protocol.js.map +0 -1
  285. package/dist/registry.d.ts +0 -37
  286. package/dist/registry.d.ts.map +0 -1
  287. package/dist/registry.js +0 -77
  288. package/dist/registry.js.map +0 -1
  289. package/dist/tools.d.ts.map +0 -1
  290. package/dist/tools.js +0 -98
  291. package/dist/tools.js.map +0 -1
  292. package/dist/validate.d.ts.map +0 -1
  293. package/dist/validate.js.map +0 -1
  294. /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
  295. /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
  296. /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/oauth/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iBAAiB,EAAE,oBAAoB;IACvC,YAAY,EAAE,eAAe;CACrB,CAAC;AAIX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc,EAAE,iBAAiB;IACjC,YAAY,EAAE,eAAe;IAC7B,oBAAoB,EAAE,wBAAwB;IAC9C,kBAAkB,EAAE,sBAAsB;IAC1C,+EAA+E;IAC/E,aAAa,EAAE,gBAAgB;IAC/B,uFAAuF;IACvF,kBAAkB,EAAE,qBAAqB;IACzC,kGAAkG;IAClG,uBAAuB,EAAE,2BAA2B;IACpD,2DAA2D;IAC3D,YAAY,EAAE,eAAe;IAC7B,uDAAuD;IACvD,aAAa,EAAE,gBAAgB;IAC/B,iFAAiF;IACjF,WAAW,EAAE,cAAc;IAC3B,6DAA6D;IAC7D,YAAY,EAAE,eAAe;CACrB,CAAC;AAEX,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,8CAA8C;IAC9C,IAAI,EAAE,MAAM;IACZ,+CAA+C;IAC/C,KAAK,EAAE,qBAAqB;IAC5B,uCAAuC;IACvC,IAAI,EAAE,oBAAoB;CAClB,CAAC;AAKX,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/oauth/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iBAAiB,EAAE,oBAAoB;IACvC,YAAY,EAAE,eAAe;CACrB,CAAC;AAIX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc,EAAE,iBAAiB;IACjC,YAAY,EAAE,eAAe;IAC7B,oBAAoB,EAAE,wBAAwB;IAC9C,kBAAkB,EAAE,sBAAsB;IAC1C,4FAA4F;IAC5F,qBAAqB,EAAE,yBAAyB;IAChD,+EAA+E;IAC/E,aAAa,EAAE,gBAAgB;IAC/B,uFAAuF;IACvF,kBAAkB,EAAE,qBAAqB;IACzC,kGAAkG;IAClG,uBAAuB,EAAE,2BAA2B;IACpD,2DAA2D;IAC3D,YAAY,EAAE,eAAe;IAC7B,uDAAuD;IACvD,aAAa,EAAE,gBAAgB;IAC/B,iFAAiF;IACjF,WAAW,EAAE,cAAc;IAC3B,6DAA6D;IAC7D,YAAY,EAAE,eAAe;CACrB,CAAC;AAEX,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,8CAA8C;IAC9C,IAAI,EAAE,MAAM;IACZ,+CAA+C;IAC/C,KAAK,EAAE,qBAAqB;IAC5B,uCAAuC;IACvC,IAAI,EAAE,oBAAoB;CAClB,CAAC;AAKX,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AAEnC,0GAA0G;AAC1G,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC"}
@@ -1,5 +1,4 @@
1
1
  /** Shared base64url helpers (WebCrypto / DOM — no Node Buffer). */
2
2
  export declare const bytesToBase64Url: (bytes: ArrayBuffer | Uint8Array) => string;
3
- export declare const textToBase64Url: (text: string) => string;
4
3
  export declare const fromBase64Url: (text: string) => Uint8Array<ArrayBuffer>;
5
4
  //# sourceMappingURL=base64url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/base64url.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,eAAO,MAAM,gBAAgB,GAAI,OAAO,WAAW,GAAG,UAAU,KAAG,MAIlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,UAAU,CAAC,WAAW,CAKlE,CAAC"}
@@ -4,7 +4,6 @@ export const bytesToBase64Url = (bytes) => {
4
4
  const binary = Array.from(u8, (byte) => String.fromCharCode(byte)).join("");
5
5
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6
6
  };
7
- export const textToBase64Url = (text) => bytesToBase64Url(new TextEncoder().encode(text));
8
7
  export const fromBase64Url = (text) => {
9
8
  const padded = text.replace(/-/g, "+").replace(/_/g, "/");
10
9
  const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - (padded.length % 4));
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64url.js","sourceRoot":"","sources":["../../../src/oauth/crypto/base64url.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAA+B,EAAU,EAAE;IAC1E,MAAM,EAAE,GAAG,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACjF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAA2B,EAAE;IACrE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /** Token-endpoint client authentication (RFC 6749 §2.3.1). */
2
+ import { type TokenEndpointAuthMethod } from "../constants.js";
3
+ import type { OAuthRouterOptions } from "../types.js";
4
+ export type ClientAuth = {
5
+ clientId: string;
6
+ secret: string | null;
7
+ method: TokenEndpointAuthMethod;
8
+ };
9
+ /** Credentials from the Authorization header, else from the body. */
10
+ export declare const readClientAuth: (request: Request, body: Record<string, string>) => ClientAuth | null;
11
+ /** Enforce the registered auth method for known clients — see the helpers above for each branch. */
12
+ export declare const firstClientAuthError: (auth: ClientAuth, options: OAuthRouterOptions, request: Request) => Promise<Response | null>;
13
+ //# sourceMappingURL=clientauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientauth.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/clientauth.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAU9D,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAGV,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAIrB,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AA6BF,qEAAqE;AACrE,eAAO,MAAM,cAAc,GACzB,SAAS,OAAO,EAChB,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC3B,UAAU,GAAG,IAaf,CAAC;AA2LF,oGAAoG;AACpG,eAAO,MAAM,oBAAoB,GAC/B,MAAM,UAAU,EAChB,SAAS,kBAAkB,EAC3B,SAAS,OAAO,KACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAMzB,CAAC"}
@@ -0,0 +1,195 @@
1
+ /** Token-endpoint client authentication (RFC 6749 §2.3.1). */
2
+ import { auditLegacyKeyUsage, safeOAuthAudit } from "../audit.js";
3
+ import { cimdFetchOpts, isCimdClientId, resolveCimdClient } from "../cimd/cimd.js";
4
+ import { oauthError, registeredClientsRequired, resolveSecrets, unregisteredClientsAllowed, } from "../config.js";
5
+ import { OAUTH_ERRORS, TOKEN_ENDPOINT_AUTH_METHODS, } from "../constants.js";
6
+ import { unsealAny } from "./sealed.js";
7
+ import { verifyClientSecretAny } from "./secrethash.js";
8
+ /** RFC 7235 §2.1 — the auth-scheme token is case-insensitive. */
9
+ const BASIC_SCHEME_RE = /^basic\s+/i;
10
+ /** RFC 6749 §2.3.1 — id and secret are form-urlencoded, so `+` means a space. */
11
+ const decodeFormComponent = (value) => decodeURIComponent(value.replace(/\+/g, " "));
12
+ /** `Authorization: Basic base64(urlencode(id):urlencode(secret))`. */
13
+ const parseBasicAuth = (header) => {
14
+ const scheme = header ? BASIC_SCHEME_RE.exec(header) : null;
15
+ if (!header || !scheme)
16
+ return null;
17
+ try {
18
+ const decoded = atob(header.slice(scheme[0].length).trim());
19
+ const separator = decoded.indexOf(":");
20
+ if (separator < 0)
21
+ return null;
22
+ const clientId = decodeFormComponent(decoded.slice(0, separator));
23
+ if (!clientId)
24
+ return null;
25
+ return {
26
+ clientId,
27
+ secret: decodeFormComponent(decoded.slice(separator + 1)),
28
+ method: TOKEN_ENDPOINT_AUTH_METHODS.basic,
29
+ };
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ };
35
+ /** Credentials from the Authorization header, else from the body. */
36
+ export const readClientAuth = (request, body) => {
37
+ const basic = parseBasicAuth(request.headers.get("Authorization"));
38
+ if (basic)
39
+ return basic;
40
+ const clientId = body.client_id ?? "";
41
+ if (!clientId)
42
+ return null;
43
+ const secret = body.client_secret ?? "";
44
+ return {
45
+ clientId,
46
+ secret: secret.length > 0 ? secret : null,
47
+ method: secret.length > 0 ? TOKEN_ENDPOINT_AUTH_METHODS.post : TOKEN_ENDPOINT_AUTH_METHODS.none,
48
+ };
49
+ };
50
+ /**
51
+ * Every failure below returns the same code, status, and description.
52
+ * Distinguishable messages here ("client requires client_secret_basic" vs.
53
+ * "unknown client_id" vs. "client_secret required" vs. …) let an
54
+ * unauthenticated caller enumerate which client_ids this server actually
55
+ * knows about and how each is configured, just by reading error text —
56
+ * a real information leak on the one endpoint whose entire job is to
57
+ * reject an untrusted caller. `/authorize`'s `unauthorized_client` stays
58
+ * descriptive on purpose: it's a human debugging an OAuth flow in a
59
+ * browser, not a bare API response, and a locked-down server already
60
+ * announces the same fact by omitting `registration_endpoint` from its
61
+ * metadata.
62
+ */
63
+ const CLIENT_AUTH_FAILED = "client authentication failed";
64
+ /** The generic response every failure below returns to the caller. */
65
+ const invalidClient = () => oauthError(OAUTH_ERRORS.invalidClient, 401, CLIENT_AUTH_FAILED);
66
+ /** The response above, plus the real reason to `ports.audit` — never to the caller. */
67
+ const invalidClientAudited = async ({ auth, options }, reason) => {
68
+ await safeOAuthAudit(options, { event: "client_auth_failed", clientId: auth.clientId, reason });
69
+ return invalidClient();
70
+ };
71
+ /**
72
+ * Never a real credential — exists only so the locked-down branch below
73
+ * does the same amount of comparison work whether or not a secret was
74
+ * actually presented, and whether or not a stored hash actually exists.
75
+ */
76
+ const DUMMY_SECRET = "dummy-secret-for-constant-time-padding";
77
+ const DUMMY_STORED_HASH = "hmac-sha256$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
78
+ /**
79
+ * Rejects with `reason`, but only after paying the same
80
+ * `verifyClientSecretAny` cost a real (wrong-secret) rejection would — a
81
+ * missing or empty secret is replaced by `DUMMY_SECRET`, so a caller who
82
+ * presented nothing can't be told apart, by timing, from a wrong secret.
83
+ */
84
+ const rejectWithDummyCompare = async (context, reason) => {
85
+ const codeSecretValues = await resolveSecrets(context.options.ports, context.request);
86
+ const presented = context.auth.secret || DUMMY_SECRET;
87
+ await verifyClientSecretAny(presented, DUMMY_STORED_HASH, codeSecretValues);
88
+ return invalidClientAudited(context, reason);
89
+ };
90
+ /**
91
+ * True when `clientId` is this server's own sealed `/register` assertion or,
92
+ * with CIMD enabled, a resolvable CIMD document. URL-shaped ids are always
93
+ * re-resolved, never accepted by shape alone.
94
+ */
95
+ const isSelfRegisteredOrCimdClient = async ({ auth, options, request, }) => {
96
+ const codeSecretValues = await resolveSecrets(options.ports, request);
97
+ const unsealed = await unsealAny(codeSecretValues, "client", auth.clientId);
98
+ if (unsealed) {
99
+ await auditLegacyKeyUsage(options, {
100
+ clientId: auth.clientId,
101
+ keyIndex: unsealed.keyIndex,
102
+ source: "client assertion unsealed",
103
+ });
104
+ return true;
105
+ }
106
+ if (options.cimd !== true || !isCimdClientId(auth.clientId))
107
+ return false;
108
+ if (await resolveCimdClient(auth.clientId, cimdFetchOpts(options)))
109
+ return true;
110
+ // Without `cimdCache` every refresh/revoke re-fetches — a flaky document
111
+ // host logs users out. Surface that distinctly.
112
+ await safeOAuthAudit(options, {
113
+ event: "cimd_resolve_failed",
114
+ clientId: auth.clientId,
115
+ reason: "CIMD document could not be resolved at token/revoke time",
116
+ });
117
+ return false;
118
+ };
119
+ /**
120
+ * An unknown client_id is treated as public — PKCE is its only proof —
121
+ * unless `allowUnregisteredClients` is false, in which case it is rejected.
122
+ * On that locked-down path a dummy secret comparison runs even for a
123
+ * client_id this server has never heard of, so timing can't distinguish
124
+ * "unknown" from "known, wrong secret". Public clients never pay that cost.
125
+ *
126
+ * Under `requireRegisteredClients`, a client_id resolved by neither
127
+ * `clientStore`, this server's own sealed `/register` assertion, nor CIMD is
128
+ * rejected — the same rule `/authorize` enforces. It lives in this shared
129
+ * gate so the `refresh_token` grant and `/revoke` cannot be reached with an
130
+ * invented id that never went through `/authorize`.
131
+ */
132
+ const unregisteredClientAuthError = async (context) => {
133
+ if (!unregisteredClientsAllowed(context.options)) {
134
+ return rejectWithDummyCompare(context, "unknown client_id");
135
+ }
136
+ if (!registeredClientsRequired(context.options))
137
+ return null;
138
+ if (await isSelfRegisteredOrCimdClient(context))
139
+ return null;
140
+ return invalidClientAudited(context, "client_id must come from clientStore or this server's own /register");
141
+ };
142
+ /** Compare against `ClientStore.secretHash` — the same cost whether a hash is on file or not. */
143
+ const secretHashAuthError = async (context, secret, secretHash) => {
144
+ const { auth, options, request } = context;
145
+ const codeSecretValues = await resolveSecrets(options.ports, request);
146
+ const stored = await secretHash(auth.clientId);
147
+ const { ok, keyIndex } = await verifyClientSecretAny(secret, stored ?? DUMMY_STORED_HASH, codeSecretValues);
148
+ if (ok && stored !== null) {
149
+ await auditLegacyKeyUsage(options, {
150
+ clientId: auth.clientId,
151
+ keyIndex,
152
+ source: "client_secret_hash verified",
153
+ });
154
+ return null;
155
+ }
156
+ return invalidClientAudited(context, stored === null ? "no secretHash on file for this client" : "secretHash mismatch");
157
+ };
158
+ /** Delegate the comparison to the host's `ClientStore.verifySecret`. */
159
+ const verifySecretAuthError = async (context, secret, verifySecret) => {
160
+ if (!verifySecret) {
161
+ return invalidClientAudited(context, "clientStore.verifySecret is not configured");
162
+ }
163
+ if (!(await verifySecret(context.auth.clientId, secret))) {
164
+ return invalidClientAudited(context, "verifySecret returned false");
165
+ }
166
+ return null;
167
+ };
168
+ /** Enforce a known client's own `tokenEndpointAuthMethod`. */
169
+ const registeredClientAuthError = async (context, registered) => {
170
+ const { auth, options } = context;
171
+ const expected = registered.tokenEndpointAuthMethod;
172
+ if (expected === TOKEN_ENDPOINT_AUTH_METHODS.none)
173
+ return null;
174
+ // A wrong method or a missing secret pays the same comparison cost as a
175
+ // wrong secret, so neither branch leaks "this client exists and needs a
176
+ // secret" through response timing.
177
+ if (auth.method !== expected) {
178
+ return rejectWithDummyCompare(context, `client requires ${expected}, got ${auth.method}`);
179
+ }
180
+ if (!auth.secret)
181
+ return rejectWithDummyCompare(context, "client_secret required");
182
+ const store = options.ports.clientStore;
183
+ return store?.secretHash
184
+ ? secretHashAuthError(context, auth.secret, store.secretHash)
185
+ : verifySecretAuthError(context, auth.secret, store?.verifySecret);
186
+ };
187
+ /** Enforce the registered auth method for known clients — see the helpers above for each branch. */
188
+ export const firstClientAuthError = async (auth, options, request) => {
189
+ const context = { auth, options, request };
190
+ const registered = (await options.ports.clientStore?.get(auth.clientId)) ?? null;
191
+ return registered
192
+ ? registeredClientAuthError(context, registered)
193
+ : unregisteredClientAuthError(context);
194
+ };
195
+ //# sourceMappingURL=clientauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientauth.js","sourceRoot":"","sources":["../../../src/oauth/crypto/clientauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ export type AuthCodeRecord = {
2
+ clientId: string;
3
+ redirectUri: string;
4
+ codeChallenge: string;
5
+ /** Consenting user id — required so minted tokens are bound. */
6
+ userId: string;
7
+ /** RFC 8707 resource indicator — audience for the eventual access token. */
8
+ resource: string;
9
+ /** Space-delimited scope granted at authorize time. */
10
+ scope: string;
11
+ exp: number;
12
+ };
13
+ export type CodeStore = {
14
+ /**
15
+ * Mark a code jti as consumed; return false if already used.
16
+ * `expMs` is the code expiry (ms since epoch) for TTL-backed stores.
17
+ */
18
+ consume: (jti: string, expMs: number) => Promise<boolean> | boolean;
19
+ };
20
+ /**
21
+ * Process-local jti → expMs map when no shared codeStore is provided.
22
+ * ponytail: single-process only; multi-instance → pass ports.codeStore (KV/Redis SET NX EX).
23
+ */
24
+ export declare const createMemoryCodeStore: () => CodeStore;
25
+ /**
26
+ * The one process-wide in-memory store. Shared rather than per router: an
27
+ * app built per request (`buildApp(env).fetch(req)`) would otherwise get a
28
+ * fresh map each time and lose single-use entirely. `createOAuthRouter`
29
+ * resolves it into `ports.codeStore` only when `allowInMemoryCodeStore` is
30
+ * set; endpoint handlers never fall back on their own.
31
+ */
32
+ export declare const processCodeStore: CodeStore;
33
+ /**
34
+ * Issue an AES-GCM sealed auth code (v2). Single-use via codeStore, or
35
+ * pruning in-memory Map when omitted.
36
+ */
37
+ export declare const issueAuthCode: (secret: string, record: Omit<AuthCodeRecord, "exp">, nowMs?: number) => Promise<string>;
38
+ /**
39
+ * Rotation-aware `consumeAuthCode`, also reporting which `secret` entry
40
+ * actually unsealed the code — used only by `token.ts` to audit
41
+ * `"legacy_code_secret_used"` without a second decrypt pass. Not part of
42
+ * the public API; `consumeAuthCode` below is the stable, exported surface.
43
+ */
44
+ export declare const consumeAuthCodeDetailed: (secret: string | readonly string[], code: string, options?: {
45
+ codeStore?: CodeStore | undefined;
46
+ nowMs?: number | undefined;
47
+ }) => Promise<{
48
+ record: AuthCodeRecord;
49
+ keyIndex: number;
50
+ } | null>;
51
+ /**
52
+ * `secret` may be a single key or a list — every entry is tried, so a code
53
+ * sealed under an older `codeSecret` keeps redeeming during rotation. See
54
+ * `resolveSecrets`/`OAuthPorts.codeSecret` for the rotation contract.
55
+ *
56
+ * Omitting `options.codeStore` is deprecated: it falls back to a
57
+ * process-local map that cannot enforce single use across instances, and
58
+ * `codeStore` becomes required in 3.0.
59
+ */
60
+ export declare const consumeAuthCode: (secret: string | readonly string[], code: string, options?: {
61
+ codeStore?: CodeStore | undefined;
62
+ nowMs?: number | undefined;
63
+ }) => Promise<AuthCodeRecord | null>;
64
+ export declare const newClientId: () => string;
65
+ //# sourceMappingURL=codes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codes.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/codes.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB;;;OAGG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACrE,CAAC;AAQF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,SAiBxC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAmC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,QAAQ,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EACnC,cAAkB,KACjB,OAAO,CAAC,MAAM,CAOhB,CAAC;AAmCF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,MAAM,EAAE,EAClC,MAAM,MAAM,EACZ,UAAS;IAAE,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,KAC9E,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqB7D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,GAAG,SAAS,MAAM,EAAE,EAClC,MAAM,MAAM,EACZ,UAAS;IAAE,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,KAC9E,OAAO,CAAC,cAAc,GAAG,IAAI,CAG/B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,MAA6B,CAAC"}
@@ -0,0 +1,121 @@
1
+ import { randomBase64Url } from "./pkce.js";
2
+ import { seal, unsealAny } from "./sealed.js";
3
+ const CODE_TTL_MS = 600_000;
4
+ /** AES-GCM sealed auth codes — authenticated and encrypted (see `sealed.ts`). */
5
+ const SEALED_PREFIX = "v2.";
6
+ /**
7
+ * Prune the in-memory store at most this often: a full `Map` scan on every
8
+ * `consume` would run for every code and consent-ticket redemption.
9
+ */
10
+ const PRUNE_INTERVAL_MS = 60_000;
11
+ /**
12
+ * Process-local jti → expMs map when no shared codeStore is provided.
13
+ * ponytail: single-process only; multi-instance → pass ports.codeStore (KV/Redis SET NX EX).
14
+ */
15
+ export const createMemoryCodeStore = () => {
16
+ const used = new Map();
17
+ let lastPrunedAtMs = 0;
18
+ return {
19
+ consume: (jti, expMs) => {
20
+ const nowMs = Date.now();
21
+ if (nowMs - lastPrunedAtMs >= PRUNE_INTERVAL_MS) {
22
+ lastPrunedAtMs = nowMs;
23
+ for (const [key, exp] of used) {
24
+ if (exp < nowMs)
25
+ used.delete(key);
26
+ }
27
+ }
28
+ if (used.has(jti))
29
+ return false;
30
+ used.set(jti, expMs);
31
+ return true;
32
+ },
33
+ };
34
+ };
35
+ /**
36
+ * The one process-wide in-memory store. Shared rather than per router: an
37
+ * app built per request (`buildApp(env).fetch(req)`) would otherwise get a
38
+ * fresh map each time and lose single-use entirely. `createOAuthRouter`
39
+ * resolves it into `ports.codeStore` only when `allowInMemoryCodeStore` is
40
+ * set; endpoint handlers never fall back on their own.
41
+ */
42
+ export const processCodeStore = createMemoryCodeStore();
43
+ /**
44
+ * Issue an AES-GCM sealed auth code (v2). Single-use via codeStore, or
45
+ * pruning in-memory Map when omitted.
46
+ */
47
+ export const issueAuthCode = async (secret, record, nowMs = Date.now()) => {
48
+ const body = {
49
+ ...record,
50
+ exp: nowMs + CODE_TTL_MS,
51
+ jti: randomBase64Url(12),
52
+ };
53
+ return `${SEALED_PREFIX}${await seal(secret, "code", body)}`;
54
+ };
55
+ const shapeCheckedRecord = (data, nowMs) => {
56
+ if (typeof data.clientId !== "string" ||
57
+ typeof data.redirectUri !== "string" ||
58
+ typeof data.codeChallenge !== "string" ||
59
+ typeof data.userId !== "string" ||
60
+ typeof data.resource !== "string" ||
61
+ data.resource.length === 0 ||
62
+ typeof data.scope !== "string" ||
63
+ typeof data.exp !== "number" ||
64
+ data.exp < nowMs ||
65
+ typeof data.jti !== "string" ||
66
+ data.jti.length === 0) {
67
+ return null;
68
+ }
69
+ return {
70
+ clientId: data.clientId,
71
+ redirectUri: data.redirectUri,
72
+ codeChallenge: data.codeChallenge,
73
+ userId: data.userId,
74
+ resource: data.resource,
75
+ scope: data.scope,
76
+ exp: data.exp,
77
+ jti: data.jti,
78
+ };
79
+ };
80
+ /**
81
+ * Rotation-aware `consumeAuthCode`, also reporting which `secret` entry
82
+ * actually unsealed the code — used only by `token.ts` to audit
83
+ * `"legacy_code_secret_used"` without a second decrypt pass. Not part of
84
+ * the public API; `consumeAuthCode` below is the stable, exported surface.
85
+ */
86
+ export const consumeAuthCodeDetailed = async (secret, code, options = {}) => {
87
+ const secrets = (Array.isArray(secret) ? secret : [secret]).filter((candidate) => Boolean(candidate));
88
+ if (!code || secrets.length === 0)
89
+ return null;
90
+ // Only AES-GCM sealed (`v2.`) codes redeem.
91
+ if (!code.startsWith(SEALED_PREFIX))
92
+ return null;
93
+ const nowMs = options.nowMs ?? Date.now();
94
+ const unsealed = await unsealAny(secrets, "code", code.slice(SEALED_PREFIX.length));
95
+ if (!unsealed)
96
+ return null;
97
+ const record = shapeCheckedRecord(unsealed.value, nowMs);
98
+ if (!record)
99
+ return null;
100
+ const store = options.codeStore ?? processCodeStore;
101
+ const fresh = await store.consume(record.jti, record.exp);
102
+ if (!fresh)
103
+ return null;
104
+ const { jti: _jti, ...rest } = record;
105
+ return { record: rest, keyIndex: unsealed.keyIndex };
106
+ };
107
+ /**
108
+ * `secret` may be a single key or a list — every entry is tried, so a code
109
+ * sealed under an older `codeSecret` keeps redeeming during rotation. See
110
+ * `resolveSecrets`/`OAuthPorts.codeSecret` for the rotation contract.
111
+ *
112
+ * Omitting `options.codeStore` is deprecated: it falls back to a
113
+ * process-local map that cannot enforce single use across instances, and
114
+ * `codeStore` becomes required in 3.0.
115
+ */
116
+ export const consumeAuthCode = async (secret, code, options = {}) => {
117
+ const result = await consumeAuthCodeDetailed(secret, code, options);
118
+ return result?.record ?? null;
119
+ };
120
+ export const newClientId = () => randomBase64Url(16);
121
+ //# sourceMappingURL=codes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codes.js","sourceRoot":"","sources":["../../../src/oauth/crypto/codes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Purpose-bound keys derived from `codeSecret` with HKDF-SHA256, cached so
3
+ * rotation (`unsealAny`, `verifyClientSecretAny`) doesn't re-run
4
+ * `importKey` + `deriveKey` for every entry on every request.
5
+ */
6
+ /** What a derived key is for. `info` is the domain separator: one value per purpose. */
7
+ export type HkdfKeyPurpose = {
8
+ info: string;
9
+ algorithm: AesKeyGenParams | HmacKeyGenParams;
10
+ usage: KeyUsage;
11
+ };
12
+ export declare const hkdfKey: (secret: string, purpose: HkdfKeyPurpose) => Promise<CryptoKey>;
13
+ //# sourceMappingURL=hkdf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hkdf.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/hkdf.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,wFAAwF;AACxF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,eAAe,GAAG,gBAAgB,CAAC;IAC9C,KAAK,EAAE,QAAQ,CAAC;CACjB,CAAC;AAgCF,eAAO,MAAM,OAAO,GAAU,QAAQ,MAAM,EAAE,SAAS,cAAc,KAAG,OAAO,CAAC,SAAS,CAGxF,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Purpose-bound keys derived from `codeSecret` with HKDF-SHA256, cached so
3
+ * rotation (`unsealAny`, `verifyClientSecretAny`) doesn't re-run
4
+ * `importKey` + `deriveKey` for every entry on every request.
5
+ */
6
+ import { bytesToBase64Url } from "./base64url.js";
7
+ import { createBoundedCache, settleCached } from "./keycache.js";
8
+ const deriveKey = async (secret, purpose) => {
9
+ const encoder = new TextEncoder();
10
+ const material = await crypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, [
11
+ "deriveKey",
12
+ ]);
13
+ return crypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: encoder.encode(purpose.info) }, material, purpose.algorithm, false, [purpose.usage]);
14
+ };
15
+ /**
16
+ * SHA-256 fingerprint of `secret` for the cache key — not a KDF. Keeps the
17
+ * plaintext secret out of Map key space (material still lives inside the
18
+ * derived CryptoKey).
19
+ */
20
+ const secretFingerprint = async (secret) => {
21
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`mcp-trellis:cache-key:${secret}`));
22
+ return bytesToBase64Url(digest);
23
+ };
24
+ const KEY_CACHE_LIMIT = 64;
25
+ const keyCache = createBoundedCache(KEY_CACHE_LIMIT);
26
+ export const hkdfKey = async (secret, purpose) => {
27
+ const cacheKey = `${purpose.info}:${purpose.usage}:${await secretFingerprint(secret)}`;
28
+ return settleCached(keyCache, cacheKey, () => deriveKey(secret, purpose));
29
+ };
30
+ //# sourceMappingURL=hkdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hkdf.js","sourceRoot":"","sources":["../../../src/oauth/crypto/hkdf.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AASjE,MAAM,SAAS,GAAG,KAAK,EAAE,MAAc,EAAE,OAAuB,EAAsB,EAAE;IACtF,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE;QAC3F,WAAW;KACZ,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAC5B,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAC9F,QAAQ,EACR,OAAO,CAAC,SAAS,EACjB,KAAK,EACL,CAAC,OAAO,CAAC,KAAK,CAAC,CAChB,CAAC;AACJ,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,KAAK,EAAE,MAAc,EAAmB,EAAE;IAClE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CACvC,SAAS,EACT,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yBAAyB,MAAM,EAAE,CAAC,CAC5D,CAAC;IACF,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,QAAQ,GAAG,kBAAkB,CAAqB,eAAe,CAAC,CAAC;AAEzE,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAAE,MAAc,EAAE,OAAuB,EAAsB,EAAE;IAC3F,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,IAAI,MAAM,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;IACvF,OAAO,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAC5E,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A small bounded cache, used by `hkdf.ts` for derived `CryptoKey`s.
3
+ *
4
+ * Insertion-order eviction, not true LRU — simpler, and the expected key
5
+ * count (a handful of rotation entries, rarely changing at runtime) stays
6
+ * well under the cap in ordinary use, so eviction order rarely matters in
7
+ * practice. The cap itself exists for the discouraged-but-possible case of
8
+ * a function-form `codeSecret` returning a different value on every call,
9
+ * which would otherwise grow this cache without bound.
10
+ */
11
+ export type BoundedCache<V> = {
12
+ get: (key: string) => V | undefined;
13
+ set: (key: string, value: V) => void;
14
+ delete: (key: string) => void;
15
+ };
16
+ export declare const createBoundedCache: <V>(limit: number) => BoundedCache<V>;
17
+ /** Cache a promise; drop the entry if it rejects so the slot is not poisoned. */
18
+ export declare const settleCached: <T>(cache: BoundedCache<Promise<T>>, key: string, create: () => Promise<T>) => Promise<T>;
19
+ //# sourceMappingURL=keycache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keycache.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/keycache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC;IACpC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACrC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAAE,OAAO,MAAM,KAAG,YAAY,CAAC,CAAC,CAenE,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,YAAY,GAAU,CAAC,EAClC,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC/B,KAAK,MAAM,EACX,QAAQ,MAAM,OAAO,CAAC,CAAC,CAAC,KACvB,OAAO,CAAC,CAAC,CAWX,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A small bounded cache, used by `hkdf.ts` for derived `CryptoKey`s.
3
+ *
4
+ * Insertion-order eviction, not true LRU — simpler, and the expected key
5
+ * count (a handful of rotation entries, rarely changing at runtime) stays
6
+ * well under the cap in ordinary use, so eviction order rarely matters in
7
+ * practice. The cap itself exists for the discouraged-but-possible case of
8
+ * a function-form `codeSecret` returning a different value on every call,
9
+ * which would otherwise grow this cache without bound.
10
+ */
11
+ export const createBoundedCache = (limit) => {
12
+ const map = new Map();
13
+ return {
14
+ get: (key) => map.get(key),
15
+ set: (key, value) => {
16
+ if (!map.has(key) && map.size >= limit) {
17
+ const oldest = map.keys().next().value;
18
+ if (oldest !== undefined)
19
+ map.delete(oldest);
20
+ }
21
+ map.set(key, value);
22
+ },
23
+ delete: (key) => {
24
+ map.delete(key);
25
+ },
26
+ };
27
+ };
28
+ /** Cache a promise; drop the entry if it rejects so the slot is not poisoned. */
29
+ export const settleCached = async (cache, key, create) => {
30
+ const cached = cache.get(key);
31
+ if (cached)
32
+ return cached;
33
+ const pending = create();
34
+ cache.set(key, pending);
35
+ try {
36
+ return await pending;
37
+ }
38
+ catch (caught) {
39
+ if (cache.get(key) === pending)
40
+ cache.delete(key);
41
+ throw caught;
42
+ }
43
+ };
44
+ //# sourceMappingURL=keycache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keycache.js","sourceRoot":"","sources":["../../../src/oauth/crypto/keycache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAI,KAAa,EAAmB,EAAE;IACtE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAa,CAAC;IACjC,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;QAC1B,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YAClB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC;gBACvC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;gBACvC,IAAI,MAAM,KAAK,SAAS;oBAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC/C,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;YACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,KAA+B,EAC/B,GAAW,EACX,MAAwB,EACZ,EAAE;IACd,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACxB,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC;IACvB,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;YAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,MAAM,CAAC;IACf,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/pkce.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,CAGnE,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,UAAU,MAAM,EAAE,WAAW,MAAM,KAAG,OAAO,CAAC,OAAO,CAIzF,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,KAAG,MAGjD,CAAC"}
@@ -1,4 +1,4 @@
1
- import { timingSafeEqual } from "../auth/bearer.js";
1
+ import { timingSafeEqual } from "../../auth/bearer.js";
2
2
  import { bytesToBase64Url } from "./base64url.js";
3
3
  export const sha256Base64Url = async (input) => {
4
4
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/oauth/crypto/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,KAAa,EAAmB,EAAE;IACtE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtF,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,QAAgB,EAAE,SAAiB,EAAoB,EAAE;IAC5F,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAe,EAAU,EAAE;IACzD,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9D,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC;AACjC,CAAC,CAAC"}
@@ -1,7 +1,6 @@
1
1
  /**
2
- * AES-GCM sealing for short-lived, self-describing tokens derived from the
3
- * same `codeSecret` as auth codes (HMAC today, AES-GCM from Phase 2 —
4
- * see `codes.ts`).
2
+ * AES-GCM sealing for short-lived, self-describing tokens (auth codes,
3
+ * consent tickets, DCR client assertions), all keyed from `codeSecret`.
5
4
  *
6
5
  * Domain separation is mandatory, not a nice-to-have: a consent ticket must
7
6
  * never unseal as a client-id assertion, and neither must ever unseal as an
@@ -14,4 +13,14 @@ export type SealedType = "consent" | "client" | "code";
14
13
  export declare const seal: <T>(secret: string, type: SealedType, payload: T) => Promise<string>;
15
14
  /** Unseals a string sealed with the same `secret` and `type`; `null` on any mismatch or tamper. */
16
15
  export declare const unseal: <T>(secret: string, type: SealedType, sealed: string) => Promise<T | null>;
16
+ /**
17
+ * Rotation-aware unseal: tries each `secrets` entry in order, returning the
18
+ * index of the one that actually worked so a caller can audit "sealed under
19
+ * a non-primary key" without a second decrypt pass. `secrets` is expected
20
+ * non-empty (callers resolve it via `resolveSecrets`, which enforces that).
21
+ */
22
+ export declare const unsealAny: <T>(secrets: readonly string[], type: SealedType, sealed: string) => Promise<{
23
+ value: T;
24
+ keyIndex: number;
25
+ } | null>;
17
26
  //# sourceMappingURL=sealed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sealed.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/sealed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAevD,uDAAuD;AACvD,eAAO,MAAM,IAAI,GAAU,CAAC,EAAE,QAAQ,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,CAAC,KAAG,OAAO,CAAC,MAAM,CAW1F,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,MAAM,GAAU,CAAC,EAC5B,QAAQ,MAAM,EACd,MAAM,UAAU,EAChB,QAAQ,MAAM,KACb,OAAO,CAAC,CAAC,GAAG,IAAI,CAsBlB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAU,CAAC,EAC/B,SAAS,SAAS,MAAM,EAAE,EAC1B,MAAM,UAAU,EAChB,QAAQ,MAAM,KACb,OAAO,CAAC;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM/C,CAAC"}