mcp-trellis 0.2.1 → 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 (293) hide show
  1. package/README.md +70 -620
  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 +16 -24
  7. package/dist/adapters/node.d.ts.map +1 -1
  8. package/dist/adapters/node.js +69 -88
  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 -66
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +150 -57
  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 +25 -5
  29. package/dist/auth/bearer.js.map +1 -1
  30. package/dist/clients.d.ts.map +1 -1
  31. package/dist/clients.js +3 -6
  32. package/dist/clients.js.map +1 -1
  33. package/dist/deprecated.d.ts +42 -0
  34. package/dist/deprecated.d.ts.map +1 -0
  35. package/dist/deprecated.js +35 -0
  36. package/dist/deprecated.js.map +1 -0
  37. package/dist/http/body.d.ts +69 -0
  38. package/dist/http/body.d.ts.map +1 -0
  39. package/dist/http/body.js +165 -0
  40. package/dist/http/body.js.map +1 -0
  41. package/dist/http/http.d.ts +42 -0
  42. package/dist/http/http.d.ts.map +1 -0
  43. package/dist/http/http.js +100 -0
  44. package/dist/http/http.js.map +1 -0
  45. package/dist/http/origins.d.ts +37 -0
  46. package/dist/http/origins.d.ts.map +1 -0
  47. package/dist/http/origins.js +87 -0
  48. package/dist/http/origins.js.map +1 -0
  49. package/dist/mcp/dispatch.d.ts +7 -0
  50. package/dist/mcp/dispatch.d.ts.map +1 -0
  51. package/dist/mcp/dispatch.js +99 -0
  52. package/dist/mcp/dispatch.js.map +1 -0
  53. package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +10 -0
  54. package/dist/mcp/jsonrpc.d.ts.map +1 -0
  55. package/dist/mcp/jsonrpc.js +88 -0
  56. package/dist/mcp/jsonrpc.js.map +1 -0
  57. package/dist/mcp/methods.d.ts +133 -0
  58. package/dist/mcp/methods.d.ts.map +1 -0
  59. package/dist/mcp/methods.js +84 -0
  60. package/dist/mcp/methods.js.map +1 -0
  61. package/dist/mcp/protocol.d.ts +16 -0
  62. package/dist/mcp/protocol.d.ts.map +1 -0
  63. package/dist/mcp/protocol.js +22 -0
  64. package/dist/mcp/protocol.js.map +1 -0
  65. package/dist/mcp/registry.d.ts +58 -0
  66. package/dist/mcp/registry.d.ts.map +1 -0
  67. package/dist/mcp/registry.js +81 -0
  68. package/dist/mcp/registry.js.map +1 -0
  69. package/dist/mcp/rpc-handlers.d.ts +11 -0
  70. package/dist/mcp/rpc-handlers.d.ts.map +1 -0
  71. package/dist/mcp/rpc-handlers.js +133 -0
  72. package/dist/mcp/rpc-handlers.js.map +1 -0
  73. package/dist/{tools.d.ts → mcp/tools.d.ts} +30 -2
  74. package/dist/mcp/tools.d.ts.map +1 -0
  75. package/dist/mcp/tools.js +166 -0
  76. package/dist/mcp/tools.js.map +1 -0
  77. package/dist/{validate.d.ts → mcp/validate.d.ts} +24 -12
  78. package/dist/mcp/validate.d.ts.map +1 -0
  79. package/dist/mcp/validate.js +186 -0
  80. package/dist/mcp/validate.js.map +1 -0
  81. package/dist/oauth/audit.d.ts +20 -0
  82. package/dist/oauth/audit.d.ts.map +1 -0
  83. package/dist/oauth/audit.js +29 -0
  84. package/dist/oauth/audit.js.map +1 -0
  85. package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
  86. package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
  87. package/dist/oauth/cimd/cimd-parse.js +70 -0
  88. package/dist/oauth/cimd/cimd-parse.js.map +1 -0
  89. package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
  90. package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
  91. package/dist/oauth/cimd/cimd-ssrf.js +193 -0
  92. package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
  93. package/dist/oauth/cimd/cimd.d.ts +36 -0
  94. package/dist/oauth/cimd/cimd.d.ts.map +1 -0
  95. package/dist/oauth/cimd/cimd.js +132 -0
  96. package/dist/oauth/cimd/cimd.js.map +1 -0
  97. package/dist/oauth/cimd/types.d.ts +12 -0
  98. package/dist/oauth/cimd/types.d.ts.map +1 -0
  99. package/dist/oauth/cimd/types.js +3 -0
  100. package/dist/oauth/cimd/types.js.map +1 -0
  101. package/dist/oauth/config.d.ts +51 -0
  102. package/dist/oauth/config.d.ts.map +1 -0
  103. package/dist/oauth/config.js +108 -0
  104. package/dist/oauth/config.js.map +1 -0
  105. package/dist/oauth/constants.d.ts +15 -0
  106. package/dist/oauth/constants.d.ts.map +1 -1
  107. package/dist/oauth/constants.js +15 -0
  108. package/dist/oauth/constants.js.map +1 -1
  109. package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +1 -2
  110. package/dist/oauth/crypto/base64url.d.ts.map +1 -0
  111. package/dist/oauth/{base64url.js → crypto/base64url.js} +1 -5
  112. package/dist/oauth/crypto/base64url.js.map +1 -0
  113. package/dist/oauth/crypto/clientauth.d.ts +13 -0
  114. package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
  115. package/dist/oauth/crypto/clientauth.js +195 -0
  116. package/dist/oauth/crypto/clientauth.js.map +1 -0
  117. package/dist/oauth/crypto/codes.d.ts +65 -0
  118. package/dist/oauth/crypto/codes.d.ts.map +1 -0
  119. package/dist/oauth/crypto/codes.js +121 -0
  120. package/dist/oauth/crypto/codes.js.map +1 -0
  121. package/dist/oauth/crypto/hkdf.d.ts +13 -0
  122. package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
  123. package/dist/oauth/crypto/hkdf.js +30 -0
  124. package/dist/oauth/crypto/hkdf.js.map +1 -0
  125. package/dist/oauth/crypto/keycache.d.ts +19 -0
  126. package/dist/oauth/crypto/keycache.d.ts.map +1 -0
  127. package/dist/oauth/crypto/keycache.js +44 -0
  128. package/dist/oauth/crypto/keycache.js.map +1 -0
  129. package/dist/oauth/crypto/pkce.d.ts.map +1 -0
  130. package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
  131. package/dist/oauth/crypto/pkce.js.map +1 -0
  132. package/dist/oauth/crypto/sealed.d.ts +26 -0
  133. package/dist/oauth/crypto/sealed.d.ts.map +1 -0
  134. package/dist/oauth/crypto/sealed.js +65 -0
  135. package/dist/oauth/crypto/sealed.js.map +1 -0
  136. package/dist/oauth/crypto/secrethash.d.ts +58 -0
  137. package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
  138. package/dist/oauth/crypto/secrethash.js +82 -0
  139. package/dist/oauth/crypto/secrethash.js.map +1 -0
  140. package/dist/oauth/deprecated.d.ts +52 -0
  141. package/dist/oauth/deprecated.d.ts.map +1 -0
  142. package/dist/oauth/deprecated.js +49 -0
  143. package/dist/oauth/deprecated.js.map +1 -0
  144. package/dist/oauth/endpoints/authorize.d.ts +3 -0
  145. package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
  146. package/dist/oauth/endpoints/authorize.js +209 -0
  147. package/dist/oauth/endpoints/authorize.js.map +1 -0
  148. package/dist/oauth/endpoints/callback.d.ts +20 -0
  149. package/dist/oauth/endpoints/callback.d.ts.map +1 -0
  150. package/dist/oauth/endpoints/callback.js +31 -0
  151. package/dist/oauth/endpoints/callback.js.map +1 -0
  152. package/dist/oauth/endpoints/consent-page.d.ts +5 -0
  153. package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
  154. package/dist/oauth/endpoints/consent-page.js +63 -0
  155. package/dist/oauth/endpoints/consent-page.js.map +1 -0
  156. package/dist/oauth/endpoints/consent.d.ts +29 -0
  157. package/dist/oauth/endpoints/consent.d.ts.map +1 -0
  158. package/dist/oauth/endpoints/consent.js +98 -0
  159. package/dist/oauth/endpoints/consent.js.map +1 -0
  160. package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +14 -8
  161. package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
  162. package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
  163. package/dist/oauth/endpoints/metadata.js.map +1 -0
  164. package/dist/oauth/endpoints/register.d.ts +10 -0
  165. package/dist/oauth/endpoints/register.d.ts.map +1 -0
  166. package/dist/oauth/endpoints/register.js +96 -0
  167. package/dist/oauth/endpoints/register.js.map +1 -0
  168. package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
  169. package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
  170. package/dist/oauth/{revoke.js → endpoints/revoke.js} +11 -9
  171. package/dist/oauth/endpoints/revoke.js.map +1 -0
  172. package/dist/oauth/endpoints/token.d.ts +3 -0
  173. package/dist/oauth/endpoints/token.d.ts.map +1 -0
  174. package/dist/oauth/{token.js → endpoints/token.js} +33 -47
  175. package/dist/oauth/endpoints/token.js.map +1 -0
  176. package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
  177. package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
  178. package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -7
  179. package/dist/oauth/endpoints/wellknown.js.map +1 -0
  180. package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +6 -1
  181. package/dist/oauth/policy/redirect.d.ts.map +1 -0
  182. package/dist/oauth/policy/redirect.js +39 -0
  183. package/dist/oauth/policy/redirect.js.map +1 -0
  184. package/dist/oauth/policy/reqbody.d.ts +18 -0
  185. package/dist/oauth/policy/reqbody.d.ts.map +1 -0
  186. package/dist/oauth/policy/reqbody.js +68 -0
  187. package/dist/oauth/policy/reqbody.js.map +1 -0
  188. package/dist/oauth/policy/resource.d.ts +37 -0
  189. package/dist/oauth/policy/resource.d.ts.map +1 -0
  190. package/dist/oauth/policy/resource.js +74 -0
  191. package/dist/oauth/policy/resource.js.map +1 -0
  192. package/dist/oauth/policy/scope.d.ts +20 -0
  193. package/dist/oauth/policy/scope.d.ts.map +1 -0
  194. package/dist/oauth/policy/scope.js +35 -0
  195. package/dist/oauth/policy/scope.js.map +1 -0
  196. package/dist/oauth/router.d.ts +12 -12
  197. package/dist/oauth/router.d.ts.map +1 -1
  198. package/dist/oauth/router.js +105 -87
  199. package/dist/oauth/router.js.map +1 -1
  200. package/dist/oauth/types.d.ts +177 -26
  201. package/dist/oauth/types.d.ts.map +1 -1
  202. package/dist/oauth/types.js +1 -27
  203. package/dist/oauth/types.js.map +1 -1
  204. package/dist/server.d.ts +14 -13
  205. package/dist/server.d.ts.map +1 -1
  206. package/dist/server.js +10 -9
  207. package/dist/server.js.map +1 -1
  208. package/dist/util/defined.d.ts +11 -0
  209. package/dist/util/defined.d.ts.map +1 -0
  210. package/dist/util/defined.js +15 -0
  211. package/dist/util/defined.js.map +1 -0
  212. package/dist/util/json.d.ts +3 -0
  213. package/dist/util/json.d.ts.map +1 -0
  214. package/dist/util/json.js +3 -0
  215. package/dist/util/json.js.map +1 -0
  216. package/docs/ROADMAP.md +283 -0
  217. package/docs/compatibility.md +84 -0
  218. package/docs/diagrams/mcp-trellis-architecture.html +305 -0
  219. package/docs/guide.md +441 -0
  220. package/docs/reference.md +210 -0
  221. package/docs/security.md +112 -0
  222. package/docs/troubleshooting.md +19 -0
  223. package/package.json +24 -8
  224. package/dist/adapters/origins.d.ts +0 -19
  225. package/dist/adapters/origins.d.ts.map +0 -1
  226. package/dist/adapters/origins.js +0 -55
  227. package/dist/adapters/origins.js.map +0 -1
  228. package/dist/dispatch.d.ts +0 -7
  229. package/dist/dispatch.d.ts.map +0 -1
  230. package/dist/dispatch.js +0 -79
  231. package/dist/dispatch.js.map +0 -1
  232. package/dist/http.d.ts +0 -17
  233. package/dist/http.d.ts.map +0 -1
  234. package/dist/http.js +0 -38
  235. package/dist/http.js.map +0 -1
  236. package/dist/jsonrpc.d.ts.map +0 -1
  237. package/dist/jsonrpc.js +0 -17
  238. package/dist/jsonrpc.js.map +0 -1
  239. package/dist/methods.d.ts +0 -61
  240. package/dist/methods.d.ts.map +0 -1
  241. package/dist/methods.js +0 -114
  242. package/dist/methods.js.map +0 -1
  243. package/dist/oauth/authorize.d.ts +0 -3
  244. package/dist/oauth/authorize.d.ts.map +0 -1
  245. package/dist/oauth/authorize.js +0 -109
  246. package/dist/oauth/authorize.js.map +0 -1
  247. package/dist/oauth/base64url.d.ts.map +0 -1
  248. package/dist/oauth/base64url.js.map +0 -1
  249. package/dist/oauth/clientauth.d.ts +0 -19
  250. package/dist/oauth/clientauth.d.ts.map +0 -1
  251. package/dist/oauth/clientauth.js +0 -76
  252. package/dist/oauth/clientauth.js.map +0 -1
  253. package/dist/oauth/codes.d.ts +0 -30
  254. package/dist/oauth/codes.d.ts.map +0 -1
  255. package/dist/oauth/codes.js +0 -93
  256. package/dist/oauth/codes.js.map +0 -1
  257. package/dist/oauth/metadata.d.ts.map +0 -1
  258. package/dist/oauth/metadata.js.map +0 -1
  259. package/dist/oauth/pkce.d.ts.map +0 -1
  260. package/dist/oauth/pkce.js.map +0 -1
  261. package/dist/oauth/redirect.d.ts.map +0 -1
  262. package/dist/oauth/redirect.js +0 -26
  263. package/dist/oauth/redirect.js.map +0 -1
  264. package/dist/oauth/resource.d.ts +0 -7
  265. package/dist/oauth/resource.d.ts.map +0 -1
  266. package/dist/oauth/resource.js +0 -44
  267. package/dist/oauth/resource.js.map +0 -1
  268. package/dist/oauth/revoke.d.ts.map +0 -1
  269. package/dist/oauth/revoke.js.map +0 -1
  270. package/dist/oauth/scope.d.ts +0 -11
  271. package/dist/oauth/scope.d.ts.map +0 -1
  272. package/dist/oauth/scope.js +0 -24
  273. package/dist/oauth/scope.js.map +0 -1
  274. package/dist/oauth/token.d.ts +0 -3
  275. package/dist/oauth/token.d.ts.map +0 -1
  276. package/dist/oauth/token.js.map +0 -1
  277. package/dist/oauth/wellknown.d.ts.map +0 -1
  278. package/dist/oauth/wellknown.js.map +0 -1
  279. package/dist/protocol.d.ts +0 -4
  280. package/dist/protocol.d.ts.map +0 -1
  281. package/dist/protocol.js +0 -14
  282. package/dist/protocol.js.map +0 -1
  283. package/dist/registry.d.ts +0 -37
  284. package/dist/registry.d.ts.map +0 -1
  285. package/dist/registry.js +0 -69
  286. package/dist/registry.js.map +0 -1
  287. package/dist/tools.d.ts.map +0 -1
  288. package/dist/tools.js +0 -97
  289. package/dist/tools.js.map +0 -1
  290. package/dist/validate.d.ts.map +0 -1
  291. package/dist/validate.js +0 -130
  292. package/dist/validate.js.map +0 -1
  293. /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
@@ -0,0 +1,5 @@
1
+ /** The built-in consent interstitial, used when the host supplies no `consent.render`. */
2
+ import type { ConsentRequest } from "../types.js";
3
+ /** Built-in hardened interstitial — used whenever the host doesn't supply `consent.render`. */
4
+ export declare const renderBuiltInConsent: (input: ConsentRequest) => Response;
5
+ //# sourceMappingURL=consent-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent-page.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent-page.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BlD,+FAA+F;AAC/F,eAAO,MAAM,oBAAoB,GAAI,OAAO,cAAc,KAAG,QAmC5D,CAAC"}
@@ -0,0 +1,63 @@
1
+ /** The built-in consent interstitial, used when the host supplies no `consent.render`. */
2
+ const ESCAPE_HTML = {
3
+ "&": "&",
4
+ "<": "&lt;",
5
+ ">": "&gt;",
6
+ '"': "&quot;",
7
+ "'": "&#39;",
8
+ };
9
+ const escapeHtml = (value) => value.replace(/[&<>"']/g, (ch) => ESCAPE_HTML[ch] ?? ch);
10
+ /**
11
+ * `form-action` governs where a form (and the redirect chain it triggers)
12
+ * may end up — not just the immediate POST target. Chromium enforces it on
13
+ * the 302 this page's own submission produces, so `'self'` alone blocks the
14
+ * approve/deny POST to `${oauthPath}/consent` from ever reaching a
15
+ * `redirectUri` on another origin (the common case — Claude, Gemini,
16
+ * loopback clients). `redirectUri` is already validated by `authorize.ts`
17
+ * before this renders, so its origin is safe to add here.
18
+ */
19
+ const consentSecurityHeaders = (redirectUri) => ({
20
+ "Content-Security-Policy": `default-src 'none'; style-src 'unsafe-inline'; ` +
21
+ `form-action 'self' ${new URL(redirectUri).origin}; frame-ancestors 'none'`,
22
+ "X-Frame-Options": "DENY",
23
+ "Referrer-Policy": "no-referrer",
24
+ "Cache-Control": "no-store",
25
+ });
26
+ /** Built-in hardened interstitial — used whenever the host doesn't supply `consent.render`. */
27
+ export const renderBuiltInConsent = (input) => {
28
+ const html = `<!doctype html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="utf-8">
32
+ <title>Authorize access</title>
33
+ <style>
34
+ body { font: 16px system-ui, sans-serif; max-width: 32rem; margin: 3rem auto; padding: 0 1rem; }
35
+ dl { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; }
36
+ dt { font-weight: 600; }
37
+ button { font-size: 1rem; padding: 0.5rem 1.25rem; margin-right: 0.5rem; }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <h1>Authorize access</h1>
42
+ <p><strong>${escapeHtml(input.clientId)}</strong> is requesting access to your account.</p>
43
+ <dl>
44
+ <dt>Redirect URI</dt><dd>${escapeHtml(input.redirectUri)}</dd>
45
+ <dt>Scope</dt><dd>${escapeHtml(input.scope.join(" ") || "(none)")}</dd>
46
+ <dt>Resource</dt><dd>${escapeHtml(input.resource)}</dd>
47
+ </dl>
48
+ <form method="POST" action="${escapeHtml(input.oauthPath)}/consent">
49
+ <input type="hidden" name="consent_ticket" value="${escapeHtml(input.ticket)}">
50
+ <button type="submit" name="approved" value="true">Allow</button>
51
+ <button type="submit" name="approved" value="false">Deny</button>
52
+ </form>
53
+ </body>
54
+ </html>`;
55
+ return new Response(html, {
56
+ status: 200,
57
+ headers: {
58
+ "Content-Type": "text/html; charset=utf-8",
59
+ ...consentSecurityHeaders(input.redirectUri),
60
+ },
61
+ });
62
+ };
63
+ //# sourceMappingURL=consent-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent-page.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent-page.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAI1F,MAAM,WAAW,GAA2B;IAC1C,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAC3C,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG,CAAC,WAAmB,EAA0B,EAAE,CAAC,CAAC;IAC/E,yBAAyB,EACvB,iDAAiD;QACjD,sBAAsB,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,0BAA0B;IAC7E,iBAAiB,EAAE,MAAM;IACzB,iBAAiB,EAAE,aAAa;IAChC,eAAe,EAAE,UAAU;CAC5B,CAAC,CAAC;AAEH,+FAA+F;AAC/F,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAqB,EAAY,EAAE;IACtE,MAAM,IAAI,GAAG;;;;;;;;;;;;;;aAcF,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;;6BAEV,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC;sBACpC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;yBAC1C,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;;8BAErB,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC;sDACH,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;;;;;QAKtE,CAAC;IACP,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,GAAG;QACX,OAAO,EAAE;YACP,cAAc,EAAE,0BAA0B;YAC1C,GAAG,sBAAsB,CAAC,KAAK,CAAC,WAAW,CAAC;SAC7C;KACF,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The consent step between a resolved user and code issuance (RFC 6749
3
+ * §4.1.1 "the resource owner grants or denies the ... request").
4
+ *
5
+ * `/authorize` seals everything the eventual `/consent` POST needs into an
6
+ * opaque, single-use ticket — the ticket carries `clientId`, `redirectUri`,
7
+ * `codeChallenge`, `resource`, and `scope` from the *validated* request, so
8
+ * `handleConsent` trusts nothing from the POST body except which ticket and
9
+ * whether the user approved it. Nothing else in that body is read.
10
+ */
11
+ import type { ConsentRequest, OAuthRouterOptions, RegisteredClient, ResolvedOAuthRouterOptions } from "../types.js";
12
+ type ConsentTicketPayload = {
13
+ clientId: string;
14
+ redirectUri: string;
15
+ codeChallenge: string;
16
+ userId: string;
17
+ resource: string;
18
+ scope: string;
19
+ state: string;
20
+ exp: number;
21
+ jti: string;
22
+ };
23
+ export declare const issueConsentTicket: (secret: string, payload: Omit<ConsentTicketPayload, "exp" | "jti">) => Promise<string>;
24
+ export declare const renderConsent: (options: OAuthRouterOptions, input: ConsentRequest) => Promise<Response>;
25
+ /** Only honored when `clientStore` actually resolves the id — never a self-invented one. */
26
+ export declare const isPreApproved: (options: OAuthRouterOptions, clientId: string, registered: RegisteredClient | null) => boolean;
27
+ export declare const handleConsent: (request: Request, options: ResolvedOAuthRouterOptions) => Promise<Response>;
28
+ export {};
29
+ //# sourceMappingURL=consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAElB,gBAAgB,EAChB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AAMrB,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,SAAS,IAAI,CAAC,oBAAoB,EAAE,KAAK,GAAG,KAAK,CAAC,KACjD,OAAO,CAAC,MAAM,CAKkB,CAAC;AAEpC,eAAO,MAAM,aAAa,GACxB,SAAS,kBAAkB,EAC3B,OAAO,cAAc,KACpB,OAAO,CAAC,QAAQ,CAA6D,CAAC;AAEjF,4FAA4F;AAC5F,eAAO,MAAM,aAAa,GACxB,SAAS,kBAAkB,EAC3B,UAAU,MAAM,EAChB,YAAY,gBAAgB,GAAG,IAAI,KAClC,OAC0F,CAAC;AAyD9F,eAAO,MAAM,aAAa,GACxB,SAAS,OAAO,EAChB,SAAS,0BAA0B,KAClC,OAAO,CAAC,QAAQ,CAmClB,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The consent step between a resolved user and code issuance (RFC 6749
3
+ * §4.1.1 "the resource owner grants or denies the ... request").
4
+ *
5
+ * `/authorize` seals everything the eventual `/consent` POST needs into an
6
+ * opaque, single-use ticket — the ticket carries `clientId`, `redirectUri`,
7
+ * `codeChallenge`, `resource`, and `scope` from the *validated* request, so
8
+ * `handleConsent` trusts nothing from the POST body except which ticket and
9
+ * whether the user approved it. Nothing else in that body is read.
10
+ */
11
+ import { NO_CORS, POST_ONLY, requireHttpMethod } from "../../http/http.js";
12
+ import { auditLegacyKeyUsage } from "../audit.js";
13
+ import { oauthError, resolveSecrets } from "../config.js";
14
+ import { OAUTH_ERRORS } from "../constants.js";
15
+ import { issueAuthCode } from "../crypto/codes.js";
16
+ import { seal, unsealAny } from "../crypto/sealed.js";
17
+ import { readBodyOrError, readOAuthBody } from "../policy/reqbody.js";
18
+ import { codeRedirect, errorRedirect } from "./callback.js";
19
+ import { renderBuiltInConsent } from "./consent-page.js";
20
+ const CONSENT_TICKET_TTL_MS = 300_000;
21
+ export const issueConsentTicket = (secret, payload) => seal(secret, "consent", {
22
+ ...payload,
23
+ exp: Date.now() + CONSENT_TICKET_TTL_MS,
24
+ jti: crypto.randomUUID(),
25
+ });
26
+ export const renderConsent = async (options, input) => (options.consent?.render ?? renderBuiltInConsent)(input);
27
+ /** Only honored when `clientStore` actually resolves the id — never a self-invented one. */
28
+ export const isPreApproved = (options, clientId, registered) => Boolean(registered) && (options.consent?.preApprovedClientIds?.includes(clientId) ?? false);
29
+ /** Unseals `ticket`, checks its expiry/jti shape, and audits a legacy-key unseal. */
30
+ const readValidTicket = async (request, options, ticket) => {
31
+ const secrets = await resolveSecrets(options.ports, request);
32
+ const unsealed = await unsealAny(secrets, "consent", ticket);
33
+ const payload = unsealed?.value ?? null;
34
+ if (!payload ||
35
+ typeof payload.exp !== "number" ||
36
+ payload.exp < Date.now() ||
37
+ typeof payload.jti !== "string" ||
38
+ !payload.jti) {
39
+ return oauthError(OAUTH_ERRORS.invalidGrant, 400, "invalid or expired consent_ticket");
40
+ }
41
+ await auditLegacyKeyUsage(options, {
42
+ clientId: payload.clientId,
43
+ keyIndex: unsealed?.keyIndex,
44
+ source: "consent ticket unsealed",
45
+ });
46
+ return { payload, secrets };
47
+ };
48
+ /**
49
+ * The resolved user, once the ticket is confirmed as theirs and burned.
50
+ * Consumed only after the user check — a POST from the wrong session must
51
+ * not burn the rightful user's ticket — but before either outcome (code or
52
+ * denial), so single-use holds. Auth codes share this store; the `ct:`
53
+ * prefix keeps a ticket's jti apart from a code's.
54
+ */
55
+ const consumeTicketForUser = async (request, options, payload) => {
56
+ const user = await options.ports.resolveUser(request);
57
+ if (!user || user.id !== payload.userId) {
58
+ return oauthError(OAUTH_ERRORS.invalidGrant, 400, "consent_ticket does not match the current user");
59
+ }
60
+ const fresh = await options.ports.codeStore.consume(`ct:${payload.jti}`, payload.exp);
61
+ return fresh ? user : oauthError(OAUTH_ERRORS.invalidGrant, 400, "consent_ticket already used");
62
+ };
63
+ export const handleConsent = async (request, options) => {
64
+ const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
65
+ if (methodError)
66
+ return methodError;
67
+ const body = await readBodyOrError(() => readOAuthBody(request));
68
+ if (body instanceof Response)
69
+ return body;
70
+ const ticket = body.consent_ticket ?? "";
71
+ if (!ticket) {
72
+ return oauthError(OAUTH_ERRORS.invalidRequest, 400, "consent_ticket required");
73
+ }
74
+ const resolved = await readValidTicket(request, options, ticket);
75
+ if (resolved instanceof Response)
76
+ return resolved;
77
+ const { payload, secrets } = resolved;
78
+ const user = await consumeTicketForUser(request, options, payload);
79
+ if (user instanceof Response)
80
+ return user;
81
+ const callback = {
82
+ redirectUri: payload.redirectUri,
83
+ state: payload.state,
84
+ issuer: new URL(request.url).origin,
85
+ };
86
+ if (body.approved !== "true")
87
+ return errorRedirect(callback, OAUTH_ERRORS.accessDenied);
88
+ const code = await issueAuthCode(secrets[0], {
89
+ clientId: payload.clientId,
90
+ redirectUri: payload.redirectUri,
91
+ codeChallenge: payload.codeChallenge,
92
+ userId: user.id,
93
+ resource: payload.resource,
94
+ scope: payload.scope,
95
+ });
96
+ return codeRedirect(callback, code);
97
+ };
98
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { type GrantType, type TokenEndpointAuthMethod } from "./constants.js";
2
- export type { GrantType } from "./constants.js";
1
+ import { type GrantType, type TokenEndpointAuthMethod } from "../constants.js";
2
+ export type { GrantType } from "../constants.js";
3
3
  export type ProtectedResourceMetadata = {
4
4
  resource: string;
5
5
  authorization_servers: string[];
@@ -22,22 +22,28 @@ export type AuthorizationServerMetadata = {
22
22
  /** Omitted when `revokeToken` is not configured. */
23
23
  revocation_endpoint?: string;
24
24
  revocation_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
25
+ /** RFC 9207 — authorize redirect includes `iss`. */
26
+ authorization_response_iss_parameter_supported?: boolean;
27
+ /** Client ID Metadata Documents (MCP 2026-07-28). */
28
+ client_id_metadata_document_supported?: boolean;
25
29
  };
26
30
  export type MetadataOptions = {
27
31
  origin: string;
28
32
  /** MCP resource path, e.g. "/mcp". */
29
- resourcePath?: string;
33
+ resourcePath?: string | undefined;
30
34
  /** OAuth path prefix, e.g. "/mcp/oauth". */
31
- oauthPath?: string;
35
+ oauthPath?: string | undefined;
32
36
  /** Only advertise grants that are actually handled. */
33
37
  grantTypes: GrantType[];
34
- scopes?: string[];
38
+ scopes?: string[] | undefined;
35
39
  /** Only advertise auth methods the configured clients actually use. */
36
- tokenEndpointAuthMethods?: TokenEndpointAuthMethod[];
40
+ tokenEndpointAuthMethods?: TokenEndpointAuthMethod[] | undefined;
37
41
  /** Advertise `registration_endpoint`. Default true. */
38
- dcrEnabled?: boolean;
42
+ dcrEnabled?: boolean | undefined;
39
43
  /** Advertise `revocation_endpoint`. Default false. */
40
- revocationEnabled?: boolean;
44
+ revocationEnabled?: boolean | undefined;
45
+ /** Advertise CIMD support. Default false. */
46
+ cimdSupported?: boolean | undefined;
41
47
  };
42
48
  export declare const protectedResourceMetadata: (options: MetadataOptions) => ProtectedResourceMetadata;
43
49
  export declare const authorizationServerMetadata: (options: MetadataOptions) => AuthorizationServerMetadata;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/metadata.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,uBAAuB,EAC7B,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gCAAgC,EAAE,MAAM,EAAE,CAAC;IAC3C,qCAAqC,EAAE,uBAAuB,EAAE,CAAC;IACjE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,kEAAkE;IAClE,4BAA4B,EAAE,OAAO,CAAC;IACtC,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0CAA0C,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACvE,oDAAoD;IACpD,8CAA8C,CAAC,EAAE,OAAO,CAAC;IACzD,qDAAqD;IACrD,qCAAqC,CAAC,EAAE,OAAO,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,uDAAuD;IACvD,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,uEAAuE;IACvE,wBAAwB,CAAC,EAAE,uBAAuB,EAAE,GAAG,SAAS,CAAC;IACjE,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,6CAA6C;IAC7C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,SAAS,eAAe,KAAG,yBAKnE,CAAC;AAEH,eAAO,MAAM,2BAA2B,GACtC,SAAS,eAAe,KACvB,2BA2BF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,qBAAoC,EACpC,cAAa,KACZ,MAIC,CAAC"}
@@ -1,5 +1,6 @@
1
- import { DEFAULT_SCOPE, TOKEN_ENDPOINT_AUTH_METHODS, } from "./constants.js";
2
- import { canonicalResource, DEFAULT_RESOURCE_PATH } from "./resource.js";
1
+ import { wwwAuthenticateHeader } from "../../auth/bearer.js";
2
+ import { DEFAULT_OAUTH_PATH, DEFAULT_SCOPE, TOKEN_ENDPOINT_AUTH_METHODS, } from "../constants.js";
3
+ import { canonicalResource, DEFAULT_RESOURCE_PATH } from "../policy/resource.js";
3
4
  export const protectedResourceMetadata = (options) => ({
4
5
  resource: canonicalResource(options.origin, options.resourcePath),
5
6
  authorization_servers: [options.origin],
@@ -7,7 +8,7 @@ export const protectedResourceMetadata = (options) => ({
7
8
  scopes_supported: options.scopes ?? [DEFAULT_SCOPE],
8
9
  });
9
10
  export const authorizationServerMetadata = (options) => {
10
- const oauthPath = options.oauthPath ?? "/mcp/oauth";
11
+ const oauthPath = options.oauthPath ?? DEFAULT_OAUTH_PATH;
11
12
  const tokenEndpointAuthMethods = options.tokenEndpointAuthMethods ?? [
12
13
  TOKEN_ENDPOINT_AUTH_METHODS.none,
13
14
  ];
@@ -30,7 +31,12 @@ export const authorizationServerMetadata = (options) => {
30
31
  token_endpoint_auth_methods_supported: tokenEndpointAuthMethods,
31
32
  scopes_supported: options.scopes ?? [DEFAULT_SCOPE],
32
33
  resource_parameter_supported: true,
34
+ authorization_response_iss_parameter_supported: true,
35
+ ...(options.cimdSupported ? { client_id_metadata_document_supported: true } : {}),
33
36
  };
34
37
  };
35
- export const mcpWwwAuthenticate = (origin, resourcePath = DEFAULT_RESOURCE_PATH, realm = "mcp") => `Bearer realm="${realm}", resource_metadata="${origin}/.well-known/oauth-protected-resource${resourcePath}"`;
38
+ export const mcpWwwAuthenticate = (origin, resourcePath = DEFAULT_RESOURCE_PATH, realm = "mcp") => wwwAuthenticateHeader({
39
+ realm,
40
+ resourceMetadataUrl: `${origin}/.well-known/oauth-protected-resource${resourcePath}`,
41
+ });
36
42
  //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,kBAAkB,EAClB,aAAa,EAEb,2BAA2B,GAE5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAoDjF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,OAAwB,EAA6B,EAAE,CAAC,CAAC;IACjG,QAAQ,EAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC;IACjE,qBAAqB,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IACvC,wBAAwB,EAAE,CAAC,QAAQ,CAAC;IACpC,gBAAgB,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC;CACpD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAAwB,EACK,EAAE;IAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,IAAI;QACnE,2BAA2B,CAAC,IAAI;KACjC,CAAC;IACF,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,sBAAsB,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,YAAY;QACjE,cAAc,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,QAAQ;QACrD,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,KAAK;YAC9B,CAAC,CAAC,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,WAAW,EAAE;YACrE,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,CAAC,iBAAiB;YAC3B,CAAC,CAAC;gBACE,mBAAmB,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,SAAS;gBAC3D,0CAA0C,EAAE,wBAAwB;aACrE;YACH,CAAC,CAAC,EAAE,CAAC;QACP,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,qBAAqB,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;QAC9C,gCAAgC,EAAE,CAAC,MAAM,CAAC;QAC1C,qCAAqC,EAAE,wBAAwB;QAC/D,gBAAgB,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC;QACnD,4BAA4B,EAAE,IAAI;QAClC,8CAA8C,EAAE,IAAI;QACpD,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,qCAAqC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,MAAc,EACd,YAAY,GAAG,qBAAqB,EACpC,KAAK,GAAG,KAAK,EACL,EAAE,CACV,qBAAqB,CAAC;IACpB,KAAK;IACL,mBAAmB,EAAE,GAAG,MAAM,wCAAwC,YAAY,EAAE;CACrF,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * RFC 7591 Dynamic Client Registration — `POST ${oauthPath}/register`.
3
+ *
4
+ * Zero storage: the issued `client_id` is a sealed assertion of the validated
5
+ * `redirect_uris` (see `sealed.ts`). Unauthenticated callers can still mint
6
+ * their own id; consent + `requireRegisteredClients` address impersonation.
7
+ */
8
+ import type { OAuthRouterOptions } from "../types.js";
9
+ export declare const handleRegister: (request: Request, options: OAuthRouterOptions) => Promise<Response>;
10
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/register.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAsFvE,eAAO,MAAM,cAAc,GACzB,SAAS,OAAO,EAChB,SAAS,kBAAkB,KAC1B,OAAO,CAAC,QAAQ,CAgBlB,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * RFC 7591 Dynamic Client Registration — `POST ${oauthPath}/register`.
3
+ *
4
+ * Zero storage: the issued `client_id` is a sealed assertion of the validated
5
+ * `redirect_uris` (see `sealed.ts`). Unauthenticated callers can still mint
6
+ * their own id; consent + `requireRegisteredClients` address impersonation.
7
+ */
8
+ import { DEFAULT_OAUTH_BODY_LIMIT, readBoundedText } from "../../http/body.js";
9
+ import { jsonResponse, POST_ONLY, requireHttpMethod } from "../../http/http.js";
10
+ import { isJsonObject } from "../../util/json.js";
11
+ import { oauthError, resolveSecrets } from "../config.js";
12
+ import { GRANT_TYPES, MAX_CLIENT_REDIRECT_URIS, OAUTH_ERRORS } from "../constants.js";
13
+ import { seal } from "../crypto/sealed.js";
14
+ import { CLAUDE_CALLBACK, isAllowedRedirectUri } from "../policy/redirect.js";
15
+ import { readBodyOrError } from "../policy/reqbody.js";
16
+ /**
17
+ * Malformed JSON or a non-object body is an error, never "no redirect_uris
18
+ * supplied" — that would silently register the Claude-callback default for
19
+ * a request that was never understood.
20
+ */
21
+ const parseRegisterBody = (text) => {
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(text);
25
+ }
26
+ catch {
27
+ parsed = undefined;
28
+ }
29
+ return isJsonObject(parsed)
30
+ ? parsed
31
+ : oauthError(OAUTH_ERRORS.invalidClientMetadata, 400, "request body must be a JSON object");
32
+ };
33
+ /** The allowed, non-empty `redirect_uris` to seal, or the error explaining why there are none. */
34
+ const resolveRegisterRedirectUris = (body, options) => {
35
+ const raw = body.redirect_uris;
36
+ if (raw === undefined) {
37
+ // Fall back to the Claude callback only if this server's own allowlist
38
+ // accepts it — never advertise a redirect_uri `/authorize` would reject
39
+ // (e.g. `clients` without `"claude"`, where `allowClaude` is false).
40
+ return isAllowedRedirectUri(CLAUDE_CALLBACK, options.redirect)
41
+ ? [CLAUDE_CALLBACK]
42
+ : oauthError(OAUTH_ERRORS.invalidRedirectUri, 400, "redirect_uris required — this server has no default redirect_uri");
43
+ }
44
+ if (!Array.isArray(raw)) {
45
+ return oauthError(OAUTH_ERRORS.invalidClientMetadata, 400, "redirect_uris must be an array");
46
+ }
47
+ if (raw.length > MAX_CLIENT_REDIRECT_URIS) {
48
+ return oauthError(OAUTH_ERRORS.invalidClientMetadata, 400, `redirect_uris must have at most ${MAX_CLIENT_REDIRECT_URIS} entries`);
49
+ }
50
+ const allowed = raw.map(String).filter((uri) => isAllowedRedirectUri(uri, options.redirect));
51
+ return allowed.length > 0
52
+ ? allowed
53
+ : oauthError(OAUTH_ERRORS.invalidRedirectUri, 400, "no allowed redirect_uris");
54
+ };
55
+ /**
56
+ * Zero storage: the client_id *is* the registration record — a sealed,
57
+ * self-verifying assertion of the redirect_uris just validated, which
58
+ * `/authorize` binds the client to. Always sealed with the primary
59
+ * `codeSecret`, so new ids verify with the fewest keys going forward.
60
+ */
61
+ const issueRegistration = async (request, options, redirectUris) => {
62
+ const secrets = await resolveSecrets(options.ports, request);
63
+ const issuedAt = Math.floor(Date.now() / 1000);
64
+ const clientId = await seal(secrets[0], "client", {
65
+ redirectUris,
66
+ iat: issuedAt,
67
+ });
68
+ // RFC 7591 §3.2.1: a successful registration is 201 Created.
69
+ return jsonResponse({
70
+ status: 201,
71
+ data: {
72
+ client_id: clientId,
73
+ client_id_issued_at: issuedAt,
74
+ token_endpoint_auth_method: "none",
75
+ redirect_uris: redirectUris,
76
+ grant_types: [GRANT_TYPES.authorizationCode],
77
+ response_types: ["code"],
78
+ },
79
+ });
80
+ };
81
+ export const handleRegister = async (request, options) => {
82
+ const methodError = requireHttpMethod(request, POST_ONLY);
83
+ if (methodError)
84
+ return methodError;
85
+ const text = await readBodyOrError(() => readBoundedText(request, DEFAULT_OAUTH_BODY_LIMIT), OAUTH_ERRORS.invalidClientMetadata);
86
+ if (text instanceof Response)
87
+ return text;
88
+ const body = parseRegisterBody(text);
89
+ if (body instanceof Response)
90
+ return body;
91
+ const redirectUris = resolveRegisterRedirectUris(body, options);
92
+ if (redirectUris instanceof Response)
93
+ return redirectUris;
94
+ return issueRegistration(request, options, redirectUris);
95
+ };
96
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/register.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  /** RFC 7009 token revocation. */
2
- import { type OAuthRouterOptions } from "./types.js";
2
+ import type { OAuthRouterOptions } from "../types.js";
3
3
  export declare const handleRevoke: (request: Request, options: OAuthRouterOptions) => Promise<Response>;
4
4
  //# sourceMappingURL=revoke.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAOjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKtD,eAAO,MAAM,YAAY,GACvB,SAAS,OAAO,EAChB,SAAS,kBAAkB,KAC1B,OAAO,CAAC,QAAQ,CAuBlB,CAAC"}
@@ -1,29 +1,31 @@
1
1
  /** RFC 7009 token revocation. */
2
- import { requireHttpMethod } from "../http.js";
3
- import { firstClientAuthError, readClientAuth } from "./clientauth.js";
4
- import { OAUTH_ERRORS } from "./constants.js";
5
- import { oauthError } from "./types.js";
6
- const POST_ONLY = new Set(["POST"]);
7
- const NO_CORS = { cors: false };
2
+ import { NO_CORS, POST_ONLY, requireHttpMethod } from "../../http/http.js";
3
+ import { oauthError } from "../config.js";
4
+ import { OAUTH_ERRORS } from "../constants.js";
5
+ import { firstClientAuthError, readClientAuth } from "../crypto/clientauth.js";
6
+ import { readBodyOrError, readOAuthBody } from "../policy/reqbody.js";
8
7
  const tokenTypeHintOf = (hint) => hint === "access_token" || hint === "refresh_token" ? hint : undefined;
9
8
  export const handleRevoke = async (request, options) => {
10
9
  const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
11
10
  if (methodError)
12
11
  return methodError;
13
- const body = Object.fromEntries(new URLSearchParams(await request.text()).entries());
12
+ const body = await readBodyOrError(() => readOAuthBody(request));
13
+ if (body instanceof Response)
14
+ return body;
14
15
  const token = body.token ?? "";
15
16
  if (!token)
16
17
  return oauthError(OAUTH_ERRORS.invalidRequest, 400, "token required");
17
18
  const auth = readClientAuth(request, body);
18
19
  if (!auth)
19
20
  return oauthError(OAUTH_ERRORS.invalidRequest, 400, "client_id required");
20
- const clientAuthError = await firstClientAuthError(auth, options);
21
+ const clientAuthError = await firstClientAuthError(auth, options, request);
21
22
  if (clientAuthError)
22
23
  return clientAuthError;
24
+ const hint = tokenTypeHintOf(body.token_type_hint);
23
25
  await options.ports.revokeToken?.({
24
26
  token,
25
27
  clientId: auth.clientId,
26
- tokenTypeHint: tokenTypeHintOf(body.token_type_hint),
28
+ ...(hint !== undefined ? { tokenTypeHint: hint } : {}),
27
29
  });
28
30
  return new Response(null, { status: 200, headers: { "Cache-Control": "no-store" } });
29
31
  };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGtE,MAAM,eAAe,GAAG,CAAC,IAAwB,EAAgD,EAAE,CACjG,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAEzE,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,OAAgB,EAChB,OAA2B,EACR,EAAE;IACrB,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACnE,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACjE,IAAI,IAAI,YAAY,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,UAAU,CAAC,YAAY,CAAC,cAAc,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAElF,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,UAAU,CAAC,YAAY,CAAC,cAAc,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAErF,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3E,IAAI,eAAe;QAAE,OAAO,eAAe,CAAC;IAE5C,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACnD,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAChC,KAAK;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC,CAAC;IAEH,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;AACvF,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ResolvedOAuthRouterOptions } from "../types.js";
2
+ export declare const handleToken: (request: Request, options: ResolvedOAuthRouterOptions) => Promise<Response>;
3
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/token.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AA+G9D,eAAO,MAAM,WAAW,GACtB,SAAS,OAAO,EAChB,SAAS,0BAA0B,KAClC,OAAO,CAAC,QAAQ,CAgClB,CAAC"}
@@ -1,35 +1,14 @@
1
- import { requireHttpMethod } from "../http.js";
2
- import { firstClientAuthError, readClientAuth } from "./clientauth.js";
3
- import { consumeAuthCode } from "./codes.js";
4
- import { GRANT_TYPES, OAUTH_ERRORS } from "./constants.js";
5
- import { verifyPkceS256 } from "./pkce.js";
6
- import { canonicalResource, firstResourceError, resourcesEqual, } from "./resource.js";
7
- import { oauthError, resolveSecret, tokenResponse, } from "./types.js";
8
- const POST_ONLY = new Set(["POST"]);
9
- const NO_CORS = { cors: false };
10
- const readJsonBody = async (request) => {
11
- const body = (await request.json());
12
- if (!body || typeof body !== "object" || Array.isArray(body)) {
13
- throw new Error("malformed request body");
14
- }
15
- return Object.fromEntries(Object.entries(body)
16
- .filter(([, value]) => value != null)
17
- .map(([key, value]) => [key, String(value)]));
18
- };
19
- const readFormBody = async (request) => {
20
- const params = new URLSearchParams(await request.text());
21
- return Object.fromEntries([...params.entries()]);
22
- };
23
- const BODY_READERS = [
24
- { match: (ctype) => ctype.includes("application/json"), read: readJsonBody },
25
- { match: () => true, read: readFormBody },
26
- ];
27
- const readTokenBody = async (request) => {
28
- const ctype = request.headers.get("Content-Type") ?? "";
29
- const reader = BODY_READERS.find((entry) => entry.match(ctype))?.read ?? readFormBody;
30
- return reader(request);
31
- };
32
- const handleRefresh = async ({ body, options, expectedResource, clientId, }) => {
1
+ import { NO_CORS, POST_ONLY, requireHttpMethod } from "../../http/http.js";
2
+ import { auditLegacyKeyUsage } from "../audit.js";
3
+ import { advertisedScopes, oauthError, resolveSecrets, tokenResponse } from "../config.js";
4
+ import { GRANT_TYPES, OAUTH_ERRORS } from "../constants.js";
5
+ import { firstClientAuthError, readClientAuth } from "../crypto/clientauth.js";
6
+ import { consumeAuthCodeDetailed } from "../crypto/codes.js";
7
+ import { verifyPkceS256 } from "../crypto/pkce.js";
8
+ import { readBodyOrError, readOAuthBody } from "../policy/reqbody.js";
9
+ import { canonicalResource, firstResourceError, resourcesEqual } from "../policy/resource.js";
10
+ import { firstScopeError, formatScope, requestedScopes } from "../policy/scope.js";
11
+ const handleRefresh = async ({ body, options, expectedResource, clientId }) => {
33
12
  if (!options.ports.refreshAccessToken) {
34
13
  return oauthError(OAUTH_ERRORS.unsupportedGrantType, 400, "refresh_token not enabled");
35
14
  }
@@ -40,15 +19,26 @@ const handleRefresh = async ({ body, options, expectedResource, clientId, }) =>
40
19
  const resourceError = firstResourceError(body.resource ?? "", expectedResource);
41
20
  if (resourceError)
42
21
  return resourceError;
43
- // ponytail: AS cannot verify the refresh token's original audience; the port owns that check.
22
+ // RFC 6749 §6: client MAY request a reduced scope; omit → host keeps original.
23
+ let requestedScope;
24
+ if (body.scope !== undefined && body.scope !== "") {
25
+ const requested = requestedScopes(body.scope, []);
26
+ const scopeError = firstScopeError(requested, advertisedScopes(options));
27
+ if (scopeError)
28
+ return scopeError;
29
+ requestedScope = formatScope(requested);
30
+ }
31
+ // ponytail: AS cannot verify the refresh token's original audience/grant; the port owns those checks.
44
32
  const minted = await options.ports.refreshAccessToken({
45
33
  refreshToken,
46
34
  clientId,
47
35
  resource: expectedResource,
36
+ ...(requestedScope !== undefined ? { scope: requestedScope } : {}),
48
37
  });
49
38
  if (!minted) {
50
39
  return oauthError(OAUTH_ERRORS.invalidGrant, 400, "invalid refresh_token");
51
40
  }
41
+ // Scope in the response must come from the port — not the client's request.
52
42
  return tokenResponse(minted);
53
43
  };
54
44
  const firstAuthCodeMismatch = (record, body, clientId) => {
@@ -62,21 +52,21 @@ const firstAuthCodeMismatch = (record, body, clientId) => {
62
52
  description: "client_id mismatch",
63
53
  },
64
54
  ].find((entry) => !entry.ok);
65
- return rule
66
- ? oauthError(OAUTH_ERRORS.invalidGrant, 400, rule.description)
67
- : null;
55
+ return rule ? oauthError(OAUTH_ERRORS.invalidGrant, 400, rule.description) : null;
68
56
  };
69
57
  const handleAuthCode = async ({ request, body, options, expectedResource, clientId, }) => {
70
58
  const resourceError = firstResourceError(body.resource ?? "", expectedResource);
71
59
  if (resourceError)
72
60
  return resourceError;
73
- const secret = await resolveSecret(options.ports, request);
74
- const record = await consumeAuthCode(secret, body.code ?? "", {
61
+ const secrets = await resolveSecrets(options.ports, request);
62
+ const consumed = await consumeAuthCodeDetailed(secrets, body.code ?? "", {
75
63
  codeStore: options.ports.codeStore,
76
64
  });
77
- if (!record) {
65
+ if (!consumed) {
78
66
  return oauthError(OAUTH_ERRORS.invalidGrant, 400, "invalid or expired code");
79
67
  }
68
+ const { record, keyIndex } = consumed;
69
+ await auditLegacyKeyUsage(options, { clientId, keyIndex, source: "auth code redeemed" });
80
70
  const mismatch = firstAuthCodeMismatch(record, body, clientId);
81
71
  if (mismatch)
82
72
  return mismatch;
@@ -102,18 +92,14 @@ export const handleToken = async (request, options) => {
102
92
  const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
103
93
  if (methodError)
104
94
  return methodError;
105
- let body;
106
- try {
107
- body = await readTokenBody(request);
108
- }
109
- catch {
110
- return oauthError(OAUTH_ERRORS.invalidRequest, 400, "malformed request body");
111
- }
95
+ const body = await readBodyOrError(() => readOAuthBody(request));
96
+ if (body instanceof Response)
97
+ return body;
112
98
  const auth = readClientAuth(request, body);
113
99
  if (!auth) {
114
100
  return oauthError(OAUTH_ERRORS.invalidRequest, 400, "client_id required");
115
101
  }
116
- const clientAuthError = await firstClientAuthError(auth, options);
102
+ const clientAuthError = await firstClientAuthError(auth, options, request);
117
103
  if (clientAuthError)
118
104
  return clientAuthError;
119
105
  const url = new URL(request.url);