@redocly/redoc 0.139.4 → 0.140.0-next.10

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 (216) hide show
  1. package/CHANGELOG.md +401 -201
  2. package/dist/api-schemas/mcp-fetch-count.d.ts +2 -18
  3. package/dist/api-schemas/otel-traces.d.ts +7 -1345
  4. package/dist/api-schemas/otel-traces.js +1 -1
  5. package/dist/api-schemas/search-facets.d.ts +3 -31
  6. package/dist/api-schemas/search-facets.js +1 -1
  7. package/dist/api-schemas/search.d.ts +7 -52
  8. package/dist/api-schemas/search.js +1 -1
  9. package/dist/cli/eject/resolveEjectParams.js +1 -1
  10. package/dist/cli/generate/get-schema-template.js +1 -1
  11. package/dist/client/app/hooks/markdown/useMarkdocRenderer.js +1 -1
  12. package/dist/client/app/hooks/markdown/useMarkdownText.js +1 -1
  13. package/dist/client/app/markdoc/custom-components/openapi/openapi-code-sample.d.ts +2 -0
  14. package/dist/client/app/markdoc/custom-components/openapi/openapi-code-sample.js +1 -1
  15. package/dist/client/app/markdoc/custom-components/openapi/use-markdown-adapter.d.ts +1 -33
  16. package/dist/client/app/useGlobalData.d.ts +1 -1
  17. package/dist/client/providers/post-message/use-post-message.d.ts +1 -1
  18. package/dist/compiled/@redocly/pii/index.mjs +1 -0
  19. package/dist/compiled/@redocly/pii/package.json +1 -0
  20. package/dist/compiled/svgo/svgo-node.js +1 -1
  21. package/dist/constants/l10n/langs/ar.js +1 -1
  22. package/dist/constants/l10n/langs/de.js +1 -1
  23. package/dist/constants/l10n/langs/en.js +1 -1
  24. package/dist/constants/l10n/langs/es.js +1 -1
  25. package/dist/constants/l10n/langs/fr.js +1 -1
  26. package/dist/constants/l10n/langs/hi.js +1 -1
  27. package/dist/constants/l10n/langs/it.js +1 -1
  28. package/dist/constants/l10n/langs/ja.js +1 -1
  29. package/dist/constants/l10n/langs/ko.js +1 -1
  30. package/dist/constants/l10n/langs/pl.js +1 -1
  31. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  32. package/dist/constants/l10n/langs/pt.js +1 -1
  33. package/dist/constants/l10n/langs/ru.js +1 -1
  34. package/dist/constants/l10n/langs/uk.js +1 -1
  35. package/dist/constants/l10n/langs/zh.js +1 -1
  36. package/dist/markdoc/attributes/definition-path.d.ts +1 -1
  37. package/dist/markdoc/attributes/json-example-ref.d.ts +1 -1
  38. package/dist/markdoc/attributes/json-schema-ref.d.ts +1 -1
  39. package/dist/markdoc/attributes/parsed-yaml.d.ts +1 -1
  40. package/dist/markdoc/attributes/raw-content.d.ts +1 -1
  41. package/dist/markdoc/attributes/sample-from-json-schema.d.ts +1 -1
  42. package/dist/markdoc/helpers/extract-rbac-from-condition-node.d.ts +1 -1
  43. package/dist/markdoc/helpers/get-inner-text.d.ts +1 -1
  44. package/dist/markdoc/helpers/get-node-attribute.d.ts +1 -1
  45. package/dist/markdoc/helpers/get-variable.d.ts +1 -1
  46. package/dist/markdoc/helpers/guards/is-conditional-node.d.ts +1 -1
  47. package/dist/markdoc/helpers/guards/is-function.d.ts +1 -1
  48. package/dist/markdoc/helpers/guards/is-node.d.ts +1 -1
  49. package/dist/markdoc/helpers/guards/is-prompt-node.d.ts +1 -1
  50. package/dist/markdoc/helpers/guards/is-tag.d.ts +1 -1
  51. package/dist/markdoc/helpers/guards/is-variable.d.ts +1 -1
  52. package/dist/markdoc/helpers/parse-partials.d.ts +1 -1
  53. package/dist/markdoc/helpers/parse-partials.js +1 -1
  54. package/dist/markdoc/helpers/remove-markdoc-tags.js +1 -1
  55. package/dist/markdoc/helpers/set-node-attribute-value.d.ts +1 -1
  56. package/dist/markdoc/nodes/fence/index.d.ts +1 -1
  57. package/dist/markdoc/nodes/fence/index.js +1 -1
  58. package/dist/markdoc/nodes/gfm-list-item.d.ts +1 -1
  59. package/dist/markdoc/nodes/gfm-list-item.js +1 -1
  60. package/dist/markdoc/nodes/heading.d.ts +1 -1
  61. package/dist/markdoc/nodes/heading.js +1 -1
  62. package/dist/markdoc/nodes/index.d.ts +6 -165
  63. package/dist/markdoc/nodes/index.js +1 -1
  64. package/dist/markdoc/nodes/table.d.ts +1 -1
  65. package/dist/markdoc/nodes/table.js +1 -1
  66. package/dist/markdoc/nodes/text.d.ts +1 -1
  67. package/dist/markdoc/tags/json-example.js +1 -1
  68. package/dist/markdoc/tags/openapi-code-sample.js +1 -1
  69. package/dist/server/config/env-config.d.ts +26 -24
  70. package/dist/server/config/env-schema.d.ts +87 -326
  71. package/dist/server/config/env-schemas/api-urls.d.ts +2 -14
  72. package/dist/server/config/env-schemas/auth.d.ts +10 -32
  73. package/dist/server/config/env-schemas/catalog.d.ts +6 -9
  74. package/dist/server/config/env-schemas/database.d.ts +2 -8
  75. package/dist/server/config/env-schemas/environment-detection.d.ts +20 -18
  76. package/dist/server/config/env-schemas/feature-flags.d.ts +29 -33
  77. package/dist/server/config/env-schemas/feature-flags.js +1 -1
  78. package/dist/server/config/env-schemas/organization-project.d.ts +3 -21
  79. package/dist/server/config/env-schemas/scorecards.d.ts +2 -6
  80. package/dist/server/config/env-schemas/search.d.ts +9 -15
  81. package/dist/server/config/env-schemas/server-config.d.ts +6 -31
  82. package/dist/server/config/env-schemas/site.d.ts +1 -5
  83. package/dist/server/config/env-schemas/ssr.d.ts +7 -12
  84. package/dist/server/config/env-schemas/telemetry.d.ts +1 -7
  85. package/dist/server/constants/common.d.ts +3 -0
  86. package/dist/server/constants/common.js +1 -1
  87. package/dist/server/esbuild/plugins/dependency-resolver.js +1 -1
  88. package/dist/server/esbuild/plugins/themes-resolver.js +3 -3
  89. package/dist/server/external-plugins/resolve-external-plugins.js +1 -1
  90. package/dist/server/plugins/api-docs/definition-access.d.ts +22 -0
  91. package/dist/server/plugins/api-docs/definition-access.js +1 -0
  92. package/dist/server/plugins/api-docs/index.js +1 -1
  93. package/dist/server/plugins/api-docs/loader.d.ts +1 -1
  94. package/dist/server/plugins/api-docs/options.js +1 -1
  95. package/dist/server/plugins/api-docs/spec-download.api.js +1 -1
  96. package/dist/server/plugins/config-parser/index.js +1 -1
  97. package/dist/server/plugins/config-parser/loaders/utils/read-and-validate-config.js +1 -1
  98. package/dist/server/plugins/dev-onboarding/api/middlewares/auth.d.ts +2 -4
  99. package/dist/server/plugins/dev-onboarding/api/middlewares/integrations.d.ts +2 -4
  100. package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/filesets-resolver.d.ts +1 -1
  101. package/dist/server/plugins/markdown/attribute-resolvers/helpers/load-openapi-bundle.d.ts +1 -1
  102. package/dist/server/plugins/markdown/attribute-resolvers/index.d.ts +1 -1
  103. package/dist/server/plugins/markdown/attribute-resolvers/resolve-code-snippet-from-file.d.ts +1 -1
  104. package/dist/server/plugins/markdown/attribute-resolvers/resolve-diagram-from-file.d.ts +1 -1
  105. package/dist/server/plugins/markdown/attribute-resolvers/resolve-html-href.d.ts +1 -1
  106. package/dist/server/plugins/markdown/attribute-resolvers/resolve-html-source-attribute.d.ts +1 -1
  107. package/dist/server/plugins/markdown/attribute-resolvers/resolve-image-gallery.d.ts +1 -1
  108. package/dist/server/plugins/markdown/attribute-resolvers/resolve-image-src-set.d.ts +1 -1
  109. package/dist/server/plugins/markdown/attribute-resolvers/resolve-image-src.d.ts +1 -1
  110. package/dist/server/plugins/markdown/attribute-resolvers/resolve-json-example-ref.d.ts +1 -1
  111. package/dist/server/plugins/markdown/attribute-resolvers/resolve-json-schema-ref.d.ts +1 -1
  112. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.d.ts +1 -1
  113. package/dist/server/plugins/markdown/attribute-resolvers/resolve-native-md-link.d.ts +1 -1
  114. package/dist/server/plugins/markdown/attribute-resolvers/resolve-nav-links.d.ts +1 -1
  115. package/dist/server/plugins/markdown/attribute-resolvers/resolve-open-api-ref.d.ts +1 -1
  116. package/dist/server/plugins/markdown/attribute-resolvers/resolve-openapi-code-samples.d.ts +1 -1
  117. package/dist/server/plugins/markdown/attribute-resolvers/resolve-openapi-code-samples.js +1 -1
  118. package/dist/server/plugins/markdown/attribute-resolvers/resolve-openapi-response-samples.d.ts +1 -1
  119. package/dist/server/plugins/markdown/attribute-resolvers/resolve-parsed-yaml.d.ts +1 -1
  120. package/dist/server/plugins/markdown/attribute-resolvers/resolve-raw-content.d.ts +1 -1
  121. package/dist/server/plugins/markdown/attribute-resolvers/resolve-relative-or-cdn-icon.d.ts +1 -1
  122. package/dist/server/plugins/markdown/attribute-resolvers/resolve-relative-path.d.ts +1 -1
  123. package/dist/server/plugins/markdown/attribute-resolvers/resolve-sample-from-json-schema.d.ts +1 -1
  124. package/dist/server/plugins/markdown/attribute-resolvers/resolve-svg-content.d.ts +1 -1
  125. package/dist/server/plugins/markdown/attribute-resolvers/resolve-tag-raw-content.d.ts +1 -1
  126. package/dist/server/plugins/markdown/attribute-resolvers/resolve-tag-raw-content.js +2 -2
  127. package/dist/server/plugins/markdown/attribute-resolvers/utils.d.ts +1 -1
  128. package/dist/server/plugins/markdown/compiler.d.ts +1 -1
  129. package/dist/server/plugins/markdown/compiler.js +1 -1
  130. package/dist/server/plugins/markdown/errors.d.ts +1 -1
  131. package/dist/server/plugins/markdown/get-server-props.js +1 -1
  132. package/dist/server/plugins/markdown/html/normalize-html-headings.d.ts +1 -1
  133. package/dist/server/plugins/markdown/html/normalize-html-headings.js +1 -1
  134. package/dist/server/plugins/markdown/markdoc/functions/concat.d.ts +1 -1
  135. package/dist/server/plugins/markdown/markdoc/markdoc-options.d.ts +5 -165
  136. package/dist/server/plugins/markdown/markdoc/markdoc-options.js +1 -1
  137. package/dist/server/plugins/markdown/markdoc/partials.d.ts +1 -1
  138. package/dist/server/plugins/markdown/markdoc/plugins/headings.d.ts +1 -1
  139. package/dist/server/plugins/markdown/markdoc/plugins/utils.d.ts +1 -1
  140. package/dist/server/plugins/markdown/markdoc/reparse-example-fences.d.ts +1 -1
  141. package/dist/server/plugins/markdown/markdoc/reparse-example-fences.js +1 -1
  142. package/dist/server/plugins/markdown/runtime-transform.d.ts +2 -2
  143. package/dist/server/plugins/markdown/runtime-transform.js +1 -1
  144. package/dist/server/plugins/markdown/search/get-ai-search-documents.js +1 -1
  145. package/dist/server/plugins/markdown/search/nodes/section-node.d.ts +1 -1
  146. package/dist/server/plugins/markdown/search/search-resolver.js +1 -1
  147. package/dist/server/plugins/markdown/search/to-markdown.d.ts +1 -1
  148. package/dist/server/plugins/markdown/search/walk-sections.d.ts +1 -1
  149. package/dist/server/plugins/markdown/search/walk-sections.js +1 -1
  150. package/dist/server/plugins/markdown/utils/stringify-tag-children-objects.d.ts +1 -1
  151. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/get-graphql-type-list.js +1 -1
  152. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/list-graphql-apis.js +1 -1
  153. package/dist/server/plugins/mcp/docs-mcp/tools/graphql/types.d.ts +4 -4
  154. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/list-apis.js +1 -1
  155. package/dist/server/plugins/mcp/docs-mcp/tools/openapi/types.d.ts +2 -2
  156. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  157. package/dist/server/plugins/mcp/handlers/errors.d.ts +0 -5
  158. package/dist/server/plugins/mcp/handlers/errors.js +1 -1
  159. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
  160. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  161. package/dist/server/plugins/scorecard-classic/compute-scorecard.d.ts +2 -2
  162. package/dist/server/plugins/scorecard-classic/compute-scorecard.js +1 -1
  163. package/dist/server/plugins/scorecard-classic/index.js +1 -1
  164. package/dist/server/plugins/scorecard-classic/shared-utils.js +1 -1
  165. package/dist/server/plugins/scorecard-classic/template/Grid/GridHeader.js +3 -3
  166. package/dist/server/plugins/scorecard-classic/template/views.js +1 -1
  167. package/dist/server/plugins/search/engines/typesense/index.js +1 -1
  168. package/dist/server/store.d.ts +1 -1
  169. package/dist/server/store.js +1 -1
  170. package/dist/server/tools/notifiers/formatter.js +3 -3
  171. package/dist/server/tools/notifiers/helpers/colors.d.ts +1 -1
  172. package/dist/server/types/plugins/common.d.ts +1 -1
  173. package/dist/server/utils/codeframes/codeframes.d.ts +1 -1
  174. package/dist/server/utils/codeframes/yaml-ast.d.ts +1 -1
  175. package/dist/server/utils/decode-request-path.d.ts +2 -0
  176. package/dist/server/utils/decode-request-path.js +1 -0
  177. package/dist/server/utils/envs/env-boolean-string.d.ts +9 -3
  178. package/dist/server/utils/envs/load-env-variables.js +1 -1
  179. package/dist/server/utils/errors.d.ts +3 -0
  180. package/dist/server/utils/errors.js +1 -1
  181. package/dist/server/utils/llmstxt/resolve-llms-txt-md.js +1 -1
  182. package/dist/server/utils/rbac-expand.js +1 -1
  183. package/dist/server/utils/rbac.d.ts +5 -1
  184. package/dist/server/utils/rbac.js +1 -1
  185. package/dist/server/utils/report-all-errors.js +1 -1
  186. package/dist/server/web-server/auth.d.ts +2 -0
  187. package/dist/server/web-server/auth.js +2 -2
  188. package/dist/server/web-server/middleware/jsonValidatorMiddleware.d.ts +5 -325
  189. package/dist/server/web-server/routes/auth.js +1 -1
  190. package/dist/server/web-server/routes/dynamic-route.js +1 -1
  191. package/dist/server/web-server/routes/error.js +1 -1
  192. package/dist/server/web-server/routes/feedback.d.ts +2 -8
  193. package/dist/server/web-server/routes/helpers/process-redirects.js +1 -1
  194. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.d.ts +2 -2
  195. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.js +18 -18
  196. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  197. package/dist/server/web-server/utils/ssrf-safe-lookup.d.ts +2 -1
  198. package/dist/server/web-server/utils/ssrf-safe-lookup.js +1 -1
  199. package/dist/server/web-server/utils.d.ts +1 -3
  200. package/dist/types/errors.d.ts +1 -1
  201. package/dist/types/markdoc.d.ts +1 -1
  202. package/package.json +12 -12
  203. package/dist/server/plugins/markdown/html/html-jsx-attribute-mapping.d.ts +0 -6
  204. package/dist/server/plugins/markdown/html/html-jsx-attribute-mapping.js +0 -1
  205. package/dist/server/plugins/markdown/html/process-html-tokens.d.ts +0 -2
  206. package/dist/server/plugins/markdown/html/process-html-tokens.js +0 -1
  207. package/dist/server/plugins/markdown/source.d.ts +0 -4
  208. package/dist/server/plugins/markdown/source.js +0 -1
  209. package/dist/server/tools/notifiers/helpers/privacy/mask-email.d.ts +0 -25
  210. package/dist/server/tools/notifiers/helpers/privacy/mask-email.js +0 -1
  211. package/dist/server/tools/notifiers/helpers/privacy/mask-subject.d.ts +0 -13
  212. package/dist/server/tools/notifiers/helpers/privacy/mask-subject.js +0 -1
  213. package/dist/server/tools/notifiers/helpers/privacy/remove-pii-from-url.d.ts +0 -25
  214. package/dist/server/tools/notifiers/helpers/privacy/remove-pii-from-url.js +0 -1
  215. package/dist/server/tools/notifiers/helpers/privacy/remove-pii.d.ts +0 -36
  216. package/dist/server/tools/notifiers/helpers/privacy/remove-pii.js +0 -1
@@ -1,4 +1,4 @@
1
- import type { Context } from 'hono';
1
+ import type { Handler } from 'hono';
2
2
  import type { Store } from '../../store.js';
3
3
  import type { FEEDBACK_TYPES } from '../../../constants/common.js';
4
4
  type FeedbackData = {
@@ -32,12 +32,6 @@ export declare function normalizeFeedbackData({ component, path, location, lang,
32
32
  origin: string | undefined;
33
33
  metadata: Record<string, any> | undefined;
34
34
  };
35
- export declare function feedbackHandler(store: Store): (ctx: Context) => Promise<(Response & import("hono").TypedResponse<any, 200, "json">) | (Response & import("hono").TypedResponse<{
36
- errors: string[];
37
- }, 400, "json">) | (Response & import("hono").TypedResponse<{
38
- errors: string[];
39
- }, 404, "json">) | (Response & import("hono").TypedResponse<{
40
- errors: string[];
41
- }, 403, "json">) | (Response & import("hono").TypedResponse<never, 500, "json">)>;
35
+ export declare function feedbackHandler(store: Store): Handler;
42
36
  export {};
43
37
  //# sourceMappingURL=feedback.d.ts.map
@@ -1 +1 @@
1
- import{removeTrailingSlash as n}from"../../../../utils/url/remove-trailing-slash.js";import{isLocalLink as r}from"../../../../utils/path/is-local-link.js";import{withPathPrefix as m}from"@redocly/theme/core/utils";function s({redirect:o,reqUrlSearch:t=""}){const i=r(o.to)?m(o.to):o.to;return{location:encodeURI(n(i)+t),type:o?.type||301}}export{s as processRedirects};
1
+ import{removeTrailingSlash as i}from"../../../../utils/url/remove-trailing-slash.js";import{isLocalLink as e}from"../../../../utils/path/is-local-link.js";import{withPathPrefix as r}from"@redocly/theme/core/utils";function m({redirect:o,reqUrlSearch:t=""}){const n=e(o.to)?r(o.to):o.to;return{location:a(i(n))+t,type:o?.type||301}}function a(o){return encodeURI(o).replace(/%25([0-9A-Fa-f]{2})/g,"%$1")}export{m as processRedirects};
@@ -15,10 +15,10 @@ export type ConsentPageProps = {
15
15
  clientIdHost: string | undefined;
16
16
  redirectUrl: URL;
17
17
  scopes: readonly string[];
18
- accessLifetimeHours: number;
18
+ accessLifetimeDays: number;
19
19
  rememberDays: number | undefined;
20
20
  consentToken: string;
21
21
  };
22
- export declare function consentPage({ site, user, clientName, clientIdHost, redirectUrl, scopes, accessLifetimeHours, rememberDays, consentToken, }: ConsentPageProps): string;
22
+ export declare function consentPage({ site, user, clientName, clientIdHost, redirectUrl, scopes, accessLifetimeDays, rememberDays, consentToken, }: ConsentPageProps): string;
23
23
  export declare function consentInvalidPage(site: PageSite): string;
24
24
  //# sourceMappingURL=mcp-consent-page.d.ts.map
@@ -1,35 +1,35 @@
1
- import{ICONS as d,code as E,escapeHtml as a,htmlDocument as p,siteHeader as N}from"../../../plugins/mcp/handlers/credentials-page/layout.js";import{statusPage as L}from"../../../plugins/mcp/handlers/credentials-page/outcome-pages.js";const $=300,e={TITLE:"Allow MCP access",INTRO:"An MCP client asks to use this portal with your account.",WARNING:"Allow access only if you started this connection in your MCP client.",REDIRECT_LABEL:"Sends access to",REDIRECT_HINT:"The app receives access at this address.",USER_LABEL:"Signed in as",NOT_PROVIDED:"Not provided",CLIENT_LABEL:"App",CLIENT_UNKNOWN:"The app did not send a name. Continue only if you just started a connection yourself.",CLIENT_NAME_HINT:"The app chose this name itself. We do not check it.",CLIENT_ID_HOST:s=>`App details loaded from ${E(s)}`,PANEL_TITLE:"What the client gets",ACCOUNT_DETAILS:"Your account details, including your name and email address",ACT_AS_YOU:"Use of the MCP server of this portal as you",ACCESS_LIFETIME:s=>`Access that it can renew for up to ${s} hours`,REMEMBER:s=>`If you allow access, we do not ask again for this client for ${s} days.`,SCOPES_LABEL:"Scopes:",ALLOW:"Allow access",DENY:"Deny",INVALID_TITLE:"This request is no longer valid",INVALID_BODY:s=>`An access request works for ${s} minutes. This one has expired or can no longer be used.`,INVALID_NEXT:"Go back to your MCP client and start the connection again."},f={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; frame-ancestors 'none'; base-uri 'none'"};function _({protocol:s,host:n,pathname:t,search:o}){return`${s}${n?`//${n}`:""}${t}${o}`}function r(s,n,t){return`<section aria-labelledby="${s}">
2
- <h2 id="${s}">${a(n)}</h2>
1
+ import{ICONS as d,code as E,escapeHtml as a,htmlDocument as p,siteHeader as N}from"../../../plugins/mcp/handlers/credentials-page/layout.js";import{statusPage as L}from"../../../plugins/mcp/handlers/credentials-page/outcome-pages.js";const $=300,s={TITLE:"Allow MCP access",INTRO:"An MCP client asks to use this portal with your account.",WARNING:"Allow access only if you started this connection in your MCP client.",REDIRECT_LABEL:"Sends access to",REDIRECT_HINT:"The app receives access at this address.",USER_LABEL:"Signed in as",NOT_PROVIDED:"Not provided",CLIENT_LABEL:"App",CLIENT_UNKNOWN:"The app did not send a name. Continue only if you just started a connection yourself.",CLIENT_NAME_HINT:"The app chose this name itself. We do not check it.",CLIENT_ID_HOST:e=>`App details loaded from ${E(e)}`,PANEL_TITLE:"What the client gets",ACCOUNT_DETAILS:"Your account details, including your name and email address",ACT_AS_YOU:"Use of the MCP server of this portal as you",ACCESS_LIFETIME:e=>`Access that it can renew for up to ${e===1?"1 day":`${e} days`}`,REMEMBER:e=>`If you allow access, we do not ask again for this client for ${e} days.`,SCOPES_LABEL:"Scopes:",ALLOW:"Allow access",DENY:"Deny",INVALID_TITLE:"This request is no longer valid",INVALID_BODY:e=>`An access request works for ${e} minutes. This one has expired or can no longer be used.`,INVALID_NEXT:"Go back to your MCP client and start the connection again."},h={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; frame-ancestors 'none'; base-uri 'none'"};function _({protocol:e,host:n,pathname:t,search:o}){return`${e}${n?`//${n}`:""}${t}${o}`}function r(e,n,t){return`<section aria-labelledby="${e}">
2
+ <h2 id="${e}">${a(n)}</h2>
3
3
  ${t.filter(Boolean).join(`
4
4
  `)}
5
- </section>`}function m(s,n){return r("consent-client",e.CLIENT_LABEL,[s?`<p>${a(s)}</p><p class="muted small">${e.CLIENT_NAME_HINT}</p>`:`<p>${e.CLIENT_UNKNOWN}</p>`,n?`<p class="muted small">${e.CLIENT_ID_HOST(n)}</p>`:""])}function A({scopes:s,accessLifetimeHours:n,rememberDays:t}){const o=`${d.shield}${e.PANEL_TITLE}`,i=`<ol class="steps">
6
- <li>${e.ACCOUNT_DETAILS}</li>
7
- <li>${e.ACT_AS_YOU}</li>
8
- <li>${e.ACCESS_LIFETIME(n)}</li>
9
- </ol>`,c=t?`<p>${e.REMEMBER(t)}</p>`:"";return`<aside class="panel" aria-labelledby="panel-title">
5
+ </section>`}function m(e,n){return r("consent-client",s.CLIENT_LABEL,[e?`<p>${a(e)}</p><p class="muted small">${s.CLIENT_NAME_HINT}</p>`:`<p>${s.CLIENT_UNKNOWN}</p>`,n?`<p class="muted small">${s.CLIENT_ID_HOST(n)}</p>`:""])}function A({scopes:e,accessLifetimeDays:n,rememberDays:t}){const o=`${d.shield}${s.PANEL_TITLE}`,i=`<ol class="steps">
6
+ <li>${s.ACCOUNT_DETAILS}</li>
7
+ <li>${s.ACT_AS_YOU}</li>
8
+ <li>${s.ACCESS_LIFETIME(n)}</li>
9
+ </ol>`,c=t?`<p>${s.REMEMBER(t)}</p>`:"";return`<aside class="panel" aria-labelledby="panel-title">
10
10
  <div class="panel-steps"><h2 id="panel-title">${o}</h2>${i}</div>
11
11
  <details class="panel-steps-toggle"><summary><h2>${o}</h2></summary>${i}</details>
12
12
  <div class="link-facts">
13
13
  ${c}
14
- <div class="tags"><span>${e.SCOPES_LABEL}</span>${s.map(l=>`<span class="tag">${a(l)}</span>`).join("")}</div>
14
+ <div class="tags"><span>${s.SCOPES_LABEL}</span>${e.map(l=>`<span class="tag">${a(l)}</span>`).join("")}</div>
15
15
  </div>
16
- </aside>`}function S({site:s,user:n,clientName:t,clientIdHost:o,redirectUrl:i,scopes:c,accessLifetimeHours:l,rememberDays:T,consentToken:u}){const I=`<div class="page">
16
+ </aside>`}function S({site:e,user:n,clientName:t,clientIdHost:o,redirectUrl:i,scopes:c,accessLifetimeDays:l,rememberDays:T,consentToken:u}){const I=`<div class="page">
17
17
  <main class="card">
18
- ${N(s)}
19
- <h1>${e.TITLE}</h1>
20
- <p class="lede">${e.INTRO}</p>
21
- <div class="banner warning" role="note">${d.warning}<p>${e.WARNING}</p></div>
18
+ ${N(e)}
19
+ <h1>${s.TITLE}</h1>
20
+ <p class="lede">${s.INTRO}</p>
21
+ <div class="banner warning" role="note">${d.warning}<p>${s.WARNING}</p></div>
22
22
  <div class="info-card">
23
23
  ${m(t,o)}
24
- ${r("consent-user",e.USER_LABEL,[`<p>${a(n||e.NOT_PROVIDED)}</p>`])}
25
- ${r("consent-redirect",e.REDIRECT_LABEL,[`<p>${E(_(i))}</p>`,`<p class="muted small">${e.REDIRECT_HINT}</p>`])}
24
+ ${r("consent-user",s.USER_LABEL,[`<p>${a(n||s.NOT_PROVIDED)}</p>`])}
25
+ ${r("consent-redirect",s.REDIRECT_LABEL,[`<p>${E(_(i))}</p>`,`<p class="muted small">${s.REDIRECT_HINT}</p>`])}
26
26
  </div>
27
27
  <form class="credentials" method="post" autocomplete="off">
28
28
  <input type="hidden" name="consent" value="${a(u)}">
29
29
  <div class="actions">
30
- <div class="actions-row"><button class="secondary" type="submit" name="decision" value="deny">${e.DENY}</button><button class="primary" type="submit" name="decision" value="allow">${e.ALLOW}</button></div>
30
+ <div class="actions-row"><button class="secondary" type="submit" name="decision" value="deny">${s.DENY}</button><button class="primary" type="submit" name="decision" value="allow">${s.ALLOW}</button></div>
31
31
  </div>
32
32
  </form>
33
33
  </main>
34
- ${A({scopes:c,accessLifetimeHours:l,rememberDays:T})}
35
- </div>`;return p({title:e.TITLE,body:I},s.colorScheme)}function y(s){return p(L("warning",e.INVALID_TITLE,[e.INVALID_BODY($/60),e.INVALID_NEXT]),s.colorScheme)}export{f as CONSENT_PAGE_HEADERS,$ as MCP_CONSENT_TTL_SEC,y as consentInvalidPage,S as consentPage};
34
+ ${A({scopes:c,accessLifetimeDays:l,rememberDays:T})}
35
+ </div>`;return p({title:s.TITLE,body:I},e.colorScheme)}function y(e){return p(L("warning",s.INVALID_TITLE,[s.INVALID_BODY($/60),s.INVALID_NEXT]),e.colorScheme)}export{h as CONSENT_PAGE_HEADERS,$ as MCP_CONSENT_TTL_SEC,y as consentInvalidPage,S as consentPage};
@@ -1 +1 @@
1
- import{getCookie as H}from"hono/cookie";import{createHash as G,timingSafeEqual as oe}from"node:crypto";import{ulid as ie}from"ulid";import{isRedoclySso as se,isOidcProviderConfig as R,createMcpAuthorizationCode as ae,verifyMcpAuthorizationCode as de,createMcpRefreshToken as K,verifyMcpRefreshToken as ce,createMcpSessionResource as M,oidcRefreshTokenGrant as pe,getOidcMetadata as ue,getUserParamsFromCookies as le,idTokenIsForThisProject as _e}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{resolveSiteBranding as fe}from"../../../plugins/mcp/handlers/credentials-page/site-branding.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{AUTH_URL as he,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as B}from"../../../constants/common.js";import{ServerRoutes as w}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as y}from"../../../telemetry/index.js";import{envConfig as q}from"../../../config/env-config.js";import{hasProjectDatabase as j}from"../../../providers/database/has-project-database.js";import*as C from"../../jwt/jwt.js";import{InternalTokenTypes as O,McpAccessTokenMarker as U}from"../../jwt/token-types.js";import{AlgorithmTypes as A,JwtAlgorithmNotImplemented as me,JwtTokenExpired as ge,JwtTokenInvalid as Ce,JwtTokenNotBefore as Te,JwtTokenSignatureMismatched as ke}from"../../jwt/types.js";import{getRequestOrigin as L}from"../../utils/get-request-origin.js";import{encryptClaimValue as J}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as Se}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as V}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as we,isClientMetadataDocumentUrl as z,isLoopbackRedirect as W,isRedirectUriRegistered as Ie}from"./mcp-client-metadata.js";import{CONSENT_PAGE_HEADERS as Y,MCP_CONSENT_TTL_SEC as Me,consentInvalidPage as Ee,consentPage as ye}from"./mcp-consent-page.js";import{MCP_CONSENT_MEMORY_DAYS as Ae,hasConsent as Re,redeemAuthorizationCode as Pe,redeemRefreshToken as ve,saveConsent as Oe}from"./mcp-oauth-store.js";const s=(r,e,t=200,n)=>r.json(e,t,{"Content-Type":"application/json",...n??{}}),$=new Set(["S256"]),F=["authorization_code","refresh_token"];function Ue(r,e,t){if(!r||!$.has(e||""))return!1;const n=G("sha256").update(t).digest("base64url"),i=Buffer.from(n),a=Buffer.from(r);return i.length!==a.length?!1:oe(i,a)}async function Le(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONTEXT,...r,iat:e,exp:e+600},m,A.HS256)}async function Ne(r){await C.verify(r,m,A.HS256);const{payload:e}=C.decode(r);if(e.type!==O.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function fr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=L(e);return s(e,{resource:`${t}${I("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${w.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function hr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=L(e);return s(e,{issuer:t,authorization_endpoint:`${t}${I(w.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${I(w.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${I(w.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...F],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function mr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:q.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...F],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}const De=new Set(["http:","https:","ws:","wss:","ftp:","file:"]),He=new Set(["javascript:","data:","blob:","about:","vbscript:","intent:","chrome:","view-source:","ms-msdt:","ms-appx-web:"]),be=2048,qe=/[\s\p{Cc}]/u;function Z(r){if(!r||r.length>be||r.includes("#")||qe.test(r))return;const e=URL.parse(r);if(e){if(e.protocol==="https:"||W(e))return r.toLowerCase().startsWith(`${e.protocol}//`)?e:void 0;if(!(De.has(e.protocol)||He.has(e.protocol)))return e}}function X(r){const e=r||{},t=Object.keys(e),n=t.find(a=>R(e[a])&&se(e[a])),i=t.find(a=>R(e[a]));return n||i||t[0]}function gr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),a=n.get("client_id"),d=n.get("code_challenge"),o=n.get("code_challenge_method"),c=ie();if(!i||!Z(i))return s(e,{error:"invalid_request",error_description:"redirect_uri must be an https URL, a loopback http URL, or a private-use scheme"},400);if(!d)return s(e,{error:"invalid_request",error_description:"code_challenge is required"},400);if(!$.has(o||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...$].join(", ")}`},400);let p;if(z(a))try{const u=await we(a);if(p=u.client_name,!Ie(i,u.redirect_uris))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(u){const f=u instanceof Error?u.message:String(u);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${f}`},400)}y.sendMcpAuthorizationStartedMessage([{...M(c),redirect_uri:i||null}]);const l=L(e),k={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:a,mcpState:n.get("state"),mcpSessionId:c,mcpCodeChallenge:d,mcpCodeChallengeMethod:o,mcpClientName:p,timestamp:Date.now()};try{const u=await Le(k),f=new URL(I(w.IDP_LOGIN),l);f.searchParams.set("redirectTo",`${w.MCP_CALLBACK}/${u}`);const h=X(r.getConfig().ssoDirect);return h?(f.searchParams.set("idpId",h),e.redirect(f.toString())):s(e,{error:"not_found"},404)}catch(u){const f=u instanceof Error?u.message:String(u),h=u instanceof Error?u.stack:String(u);y.sendMcpAuthorizationFailedMessage([{...M(c),error:f,error_details:h}]);const S=new URL(I(`${he}/oauth2/auth`));return S.search=n.toString(),e.redirect(S.toString())}}}async function Q(r){const e=Math.floor(Date.now()/1e3),t=r.ttlSec?e+r.ttlSec:Math.min(typeof r.claims.exp=="number"?r.claims.exp:1/0,e+b);return{accessToken:await C.sign({...r.claims,[U.CLAIM]:U.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e}:{},exp:t},m,A.HS256,r.kid),expiresIn:Math.max(t-e,0)}}async function je(r,e,t){const n=T=>s(r,{error:"invalid_grant",error_description:T},400),i=r.get("logger"),a=t.get("refresh_token"),d=t.get("client_id")||void 0;if(!a)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let o;try{o=await ce(a)}catch(T){return i?.warn("MCP refresh token rejected: %s",T instanceof Error?T.message:String(T)),n("Invalid refresh token")}if(d&&d!==o.client_id)return n("Client mismatch");const c=o.idp_id?e.getConfig().ssoDirect?.[o.idp_id]:void 0;let p,l,k=o.idp_access_token_enc,u=o.idp_refresh_token_enc;if(o.idp_refresh_token_enc){let T;try{T=Se(o.idp_refresh_token_enc,m)}catch{return n("Invalid refresh token")}if(!o.idp_id||!R(c))return n("Unknown identity provider");let _;try{const g=await ue(o.idp_id,c);if(!g?.token_endpoint)return n("Identity provider has no token endpoint");_=await pe(g.token_endpoint,T,c)}catch(g){return i?.warn("MCP refresh failed to reach the IdP token endpoint: %s",g instanceof Error?g.message:String(g)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(_.error||!(_.id_token||_.access_token))return i?.warn("MCP refresh rejected by IdP: %s",_.error||"empty token response"),n("Refresh rejected by identity provider");if(_.refresh_token&&(u=V(_.refresh_token,m)),_.access_token&&(k=J(_.access_token,m)),_.id_token){try{const g=C.decode(_.id_token);p=g.payload,l=g.header.kid}catch{return n("Invalid token response from identity provider")}if(p.iss!==o.claims.iss||p.sub!==o.claims.sub)return i?.warn("MCP refresh rejected: the id_token names another issuer or subject"),n("Invalid refresh token")}}const f=Math.min(R(c)&&c.tokenExpirationTime||b,b),h=p??o.claims;if(!_e(h,c))return i?.warn("MCP refresh rejected: the id_token was issued for another project"),n("Invalid refresh token");if(j()&&!await ve(await e.getKv(),a,o.exp))return i?.warn("MCP refresh rejected: the refresh token was already used"),n("Refresh token already used");const S=await Q({claims:h,kid:l,idpId:o.idp_id,idpAccessTokenEnc:k,ttlSec:p?void 0:f}),D=await K({clientId:o.client_id,idpId:o.idp_id,claims:h,idpAccessTokenEnc:k,idpRefreshTokenEnc:u,sessionExp:o.session_exp});return s(r,{access_token:S.accessToken,token_type:"Bearer",expires_in:S.expiresIn,refresh_token:D,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Cr(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),a=t.get("redirect_uri")||void 0,d=t.get("code_verifier")||void 0;if(!F.includes(n))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(n==="refresh_token")return await je(e,r,t);if(!i)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const o=await de(i);if(a&&a!==o.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(!d)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!Ue(o.code_challenge,o.code_challenge_method,d))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400);if(q.OAUTH_CLIENT_ID&&o.client_id&&!z(o.client_id)&&o.client_id!==q.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const c=o.id_token;if(typeof c!="string"||c.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const p=await x(c,r.getConfig().ssoDirect);if(!p)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);if(j()&&!await Pe(await r.getKv(),i,o.exp))return s(e,{error:"invalid_grant",error_description:"Authorization code already used"},400);const l=o.idp_access_token_enc,{accessToken:k,expiresIn:u}=await Q({claims:p.payload,kid:p.kid,idpId:o.idp_id,idpAccessTokenEnc:l}),h=Math.floor(Date.now()/1e3)+B,S=await K({clientId:o.client_id,idpId:o.idp_id,claims:p.payload,idpAccessTokenEnc:l,idpRefreshTokenEnc:o.idp_refresh_token_enc,sessionExp:h}),D={access_token:k,token_type:"Bearer",expires_in:u,refresh_token:S,scope:E.join(" ")};return s(e,D,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(o){return e.get("logger")?.warn("MCP token exchange failed: %s",o instanceof Error?o.message:String(o)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const n=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}async function x(r,e){const t=await le(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:n,header:i}=C.decode(r);if(n[U.CLAIM]!==U.VALUE)return{payload:n,kid:i.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function N(r){return G("sha256").update(r).digest("base64url")}async function ze(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONSENT,session_hash:N(r.sessionToken),context_hash:N(r.contextToken),iat:e,exp:e+Me},m,A.HS256)}async function $e(r,e){try{await C.verify(r,m,A.HS256)}catch{return!1}const{payload:t}=C.decode(r);return t.type===O.MCP_CONSENT&&t.session_hash===N(e.sessionToken)&&t.context_hash===N(e.contextToken)}function ee(r,e){return async t=>{const n=new URL(t.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(I(`${w.MCP_CALLBACK}/`))){const d=n.pathname.split("/");i=d[d.length-1]}if(!i)return t.text("Missing context parameter",400);let a=null;try{const d=await Ne(i);a=d.mcpSessionId||null;const o=d.originalRedirectUri,c=Z(o);if(!d.isMcpFlow||!o||!c)throw new Error("Invalid MCP context");const p=H(t,v.AUTHORIZATION),l=p?await x(p,r.getConfig().ssoDirect):void 0;return!p||!l?(y.sendMcpAuthorizationFailedMessage([{...M(a),error:"Authentication required",error_details:null}]),t.text("Authentication required",401)):await e(t,{contextToken:i,mcpContext:d,redirectUri:o,redirectUrl:c,sessionToken:p,session:l})}catch(d){const o=Ke(d);return y.sendMcpAuthorizationFailedMessage([{...M(a),...o}]),t.text(`Invalid MCP callback: ${o.error}`,400)}}}async function re(r,e,{mcpContext:t,redirectUri:n,redirectUrl:i,sessionToken:a,session:d}){const o=H(r,v.IDP_ACCESS_TOKEN),c=H(r,v.IDP_REFRESH_TOKEN),p=await ae({idToken:a,idpId:d.idpId??X(e.getConfig().ssoDirect),idpAccessTokenEnc:o?J(o,m):void 0,idpRefreshTokenEnc:c?V(c,m):void 0,clientId:t.mcpClientId||"",redirectUri:n,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(i);return l.searchParams.set("code",p),t.mcpState&&l.searchParams.set("state",t.mcpState),y.sendMcpAuthorizationCompletedMessage([{...M(t.mcpSessionId||null),redirect_uri:n}]),r.redirect(l.toString())}function Fe(r,{mcpContext:e,redirectUrl:t}){const n=new URL(t);return n.searchParams.set("error","access_denied"),e.mcpState&&n.searchParams.set("state",e.mcpState),y.sendMcpAuthorizationFailedMessage([{...M(e.mcpSessionId||null),error:"access_denied",error_details:null}]),r.redirect(n.toString())}function Ge({payload:r}){return[r.email,r.name,r.sub].find(e=>typeof e=="string"&&e.length>0)}function te({mcpContext:r,redirectUri:e,session:t}){const{sub:n}=t.payload;if(!(!j()||typeof n!="string"||n.length===0))return{idpId:t.idpId,subject:n,clientId:r.mcpClientId||"",redirectUri:e}}function ne(r,e){const{seo:t,colorMode:n}=e.getConfig(),{logo:i}=e.getGlobalData();return{...fe({seo:t,logo:i,colorMode:n}),host:new URL(L(r)).host}}function Tr(r){return ee(r,async(e,t)=>{const n=te(t);if(W(t.redirectUrl)||n&&await Re(await r.getKv(),n))return re(e,r,t);const{mcpClientId:i,mcpClientName:a}=t.mcpContext,d=ye({site:ne(e,r),user:Ge(t.session),clientName:a||void 0,clientIdHost:z(i)?new URL(i).host:void 0,redirectUrl:t.redirectUrl,scopes:E,accessLifetimeHours:B/3600,rememberDays:n?Ae:void 0,consentToken:await ze(t)});return new Response(d,{status:200,headers:Y})})}function kr(r){return ee(r,async(e,t)=>{const n=await e.req.formData(),i=n.get("consent");if(typeof i!="string"||!await $e(i,t))return new Response(Ee(ne(e,r)),{status:403,headers:Y});if(n.get("decision")==="allow"){const a=te(t);return a&&await Oe(await r.getKv(),a),re(e,r,t)}return Fe(e,t)})}function Ke(r){return r instanceof Error&&[me,ge,Ce,Te,ke].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{F as SUPPORTED_MCP_GRANT_TYPES,Le as createMcpContextToken,gr as mcpAuthorizationHandler,Tr as mcpCallbackHandler,kr as mcpConsentHandler,mr as mcpDynamicClientRegistrationHandler,hr as mcpOAuthAuthorizationServerHandler,fr as mcpOAuthProtectedResourceHandler,Cr as mcpTokenPortalHandler,X as resolveMcpIdpId,Ne as verifyAndParseMcpContextToken,Ue as verifyPkce};
1
+ import{getCookie as v}from"hono/cookie";import{createHash as G,timingSafeEqual as se}from"node:crypto";import{ulid as ae}from"ulid";import{isRedoclySso as ce,isOidcProviderConfig as O,createMcpAuthorizationCode as de,verifyMcpAuthorizationCode as pe,createMcpRefreshToken as B,verifyMcpRefreshToken as ue,createMcpSessionResource as M,oidcRefreshTokenGrant as le,getOidcMetadata as _e,getUserParamsFromCookies as fe,idTokenIsForThisProject as he}from"../../auth.js";import{isMcpContentProtected as U}from"../../../plugins/mcp/auth/auth-handlers.js";import{resolveSiteBranding as me}from"../../../plugins/mcp/handlers/credentials-page/site-branding.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{AUTH_URL as ge,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_CLAIMS_ONLY_SESSION_LIFETIME_SEC as K,MCP_REFRESH_SESSION_LIFETIME_SEC as Ce}from"../../../constants/common.js";import{ServerRoutes as I}from"../../../../constants/api.js";import{AuthCookieNames as A}from"../../../../constants/common.js";import{withPathPrefix as w}from"@redocly/theme/core/utils";import{telemetry as y}from"../../../telemetry/index.js";import{envConfig as q}from"../../../config/env-config.js";import{hasProjectDatabase as j}from"../../../providers/database/has-project-database.js";import*as C from"../../jwt/jwt.js";import{InternalTokenTypes as L,McpAccessTokenMarker as N}from"../../jwt/token-types.js";import{AlgorithmTypes as R,JwtAlgorithmNotImplemented as Te,JwtTokenExpired as ke,JwtTokenInvalid as Se,JwtTokenNotBefore as Ie,JwtTokenSignatureMismatched as we}from"../../jwt/types.js";import{getRequestOrigin as D}from"../../utils/get-request-origin.js";import{encryptClaimValue as J}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as Me}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as V}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as Ee,isClientMetadataDocumentUrl as z,isLoopbackRedirect as W,isRedirectUriRegistered as ye}from"./mcp-client-metadata.js";import{CONSENT_PAGE_HEADERS as Y,MCP_CONSENT_TTL_SEC as Ae,consentInvalidPage as Re,consentPage as Pe}from"./mcp-consent-page.js";import{MCP_CONSENT_MEMORY_DAYS as ve,hasConsent as Oe,redeemAuthorizationCode as Ue,redeemRefreshToken as Le,saveConsent as Ne}from"./mcp-oauth-store.js";const s=(r,e,t=200,n)=>r.json(e,t,{"Content-Type":"application/json",...n??{}}),$=new Set(["S256"]),F=["authorization_code","refresh_token"];function De(r,e,t){if(!r||!$.has(e||""))return!1;const n=G("sha256").update(t).digest("base64url"),i=Buffer.from(n),a=Buffer.from(r);return i.length!==a.length?!1:se(i,a)}async function He(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:L.MCP_CONTEXT,...r,iat:e,exp:e+600},m,R.HS256)}async function be(r){await C.verify(r,m,R.HS256);const{payload:e}=C.decode(r);if(e.type!==L.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function gr(r){return async e=>{if(!U(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=D(e);return s(e,{resource:`${t}${w("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${I.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function Cr(r){return async e=>{if(!U(r))return s(e,{error:"not_found"},404);const t=D(e);return s(e,{issuer:t,authorization_endpoint:`${t}${w(I.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${w(I.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${w(I.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...F],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function Tr(r){return async e=>{if(!U(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:q.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...F],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}const qe=new Set(["http:","https:","ws:","wss:","ftp:","file:"]),je=new Set(["javascript:","data:","blob:","about:","vbscript:","intent:","chrome:","view-source:","ms-msdt:","ms-appx-web:"]),ze=2048,$e=/[\s\p{Cc}]/u;function Z(r){return r?Ce:K}function X(r){if(!r||r.length>ze||r.includes("#")||$e.test(r))return;const e=URL.parse(r);if(e){if(e.protocol==="https:"||W(e))return r.toLowerCase().startsWith(`${e.protocol}//`)?e:void 0;if(!(qe.has(e.protocol)||je.has(e.protocol)))return e}}function Q(r){const e=r||{},t=Object.keys(e),n=t.find(a=>O(e[a])&&ce(e[a])),i=t.find(a=>O(e[a]));return n||i||t[0]}function kr(r){return async e=>{if(!U(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),a=n.get("client_id"),c=n.get("code_challenge"),o=n.get("code_challenge_method"),p=ae();if(!i||!X(i))return s(e,{error:"invalid_request",error_description:"redirect_uri must be an https URL, a loopback http URL, or a private-use scheme"},400);if(!c)return s(e,{error:"invalid_request",error_description:"code_challenge is required"},400);if(!$.has(o||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...$].join(", ")}`},400);let d;if(z(a))try{const u=await Ee(a);if(d=u.client_name,!ye(i,u.redirect_uris))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(u){const h=u instanceof Error?u.message:String(u);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${h}`},400)}y.sendMcpAuthorizationStartedMessage([{...M(p),redirect_uri:i||null}]);const l=D(e),k={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:a,mcpState:n.get("state"),mcpSessionId:p,mcpCodeChallenge:c,mcpCodeChallengeMethod:o,mcpClientName:d,timestamp:Date.now()};try{const u=await He(k),h=new URL(w(I.IDP_LOGIN),l);h.searchParams.set("redirectTo",`${I.MCP_CALLBACK}/${u}`);const _=Q(r.getConfig().ssoDirect);return _?(h.searchParams.set("idpId",_),e.redirect(h.toString())):s(e,{error:"not_found"},404)}catch(u){const h=u instanceof Error?u.message:String(u),_=u instanceof Error?u.stack:String(u);y.sendMcpAuthorizationFailedMessage([{...M(p),error:h,error_details:_}]);const S=new URL(w(`${ge}/oauth2/auth`));return S.search=n.toString(),e.redirect(S.toString())}}}async function x(r){const e=Math.floor(Date.now()/1e3),t=r.ttlSec?e+r.ttlSec:Math.min(typeof r.claims.exp=="number"?r.claims.exp:1/0,e+b);return{accessToken:await C.sign({...r.claims,[N.CLAIM]:N.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e}:{},exp:t},m,R.HS256,r.kid),expiresIn:Math.max(t-e,0)}}async function Fe(r,e,t){const n=T=>s(r,{error:"invalid_grant",error_description:T},400),i=r.get("logger"),a=t.get("refresh_token"),c=t.get("client_id")||void 0;if(!a)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let o;try{o=await ue(a)}catch(T){return i?.warn("MCP refresh token rejected: %s",T instanceof Error?T.message:String(T)),n("Invalid refresh token")}if(c&&c!==o.client_id)return n("Client mismatch");const p=o.idp_id?e.getConfig().ssoDirect?.[o.idp_id]:void 0;let d,l,k=o.idp_access_token_enc,u=o.idp_refresh_token_enc;if(o.idp_refresh_token_enc){let T;try{T=Me(o.idp_refresh_token_enc,m)}catch{return n("Invalid refresh token")}if(!o.idp_id||!O(p))return n("Unknown identity provider");let f;try{const g=await _e(o.idp_id,p);if(!g?.token_endpoint)return n("Identity provider has no token endpoint");f=await le(g.token_endpoint,T,p)}catch(g){return i?.warn("MCP refresh failed to reach the IdP token endpoint: %s",g instanceof Error?g.message:String(g)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(f.error||!(f.id_token||f.access_token))return i?.warn("MCP refresh rejected by IdP: %s",f.error||"empty token response"),n("Refresh rejected by identity provider");if(f.refresh_token&&(u=V(f.refresh_token,m)),f.access_token&&(k=J(f.access_token,m)),f.id_token){try{const g=C.decode(f.id_token);d=g.payload,l=g.header.kid}catch{return n("Invalid token response from identity provider")}if(d.iss!==o.claims.iss||d.sub!==o.claims.sub)return i?.warn("MCP refresh rejected: the id_token names another issuer or subject"),n("Invalid refresh token")}}const h=Math.min(O(p)&&p.tokenExpirationTime||b,b),_=d??o.claims;if(!he(_,p))return i?.warn("MCP refresh rejected: the id_token was issued for another project"),n("Invalid refresh token");const S=d||typeof _.iat!="number"?o.session_exp:Math.min(o.session_exp,_.iat+K);if(S<=Math.floor(Date.now()/1e3))return i?.warn("MCP refresh rejected: the claims snapshot is older than the claims-only window"),n("Refresh token expired");if(j()&&!await Le(await e.getKv(),a,o.exp))return i?.warn("MCP refresh rejected: the refresh token was already used"),n("Refresh token already used");const P=await x({claims:_,kid:l,idpId:o.idp_id,idpAccessTokenEnc:k,ttlSec:d?void 0:h}),ie=await B({clientId:o.client_id,idpId:o.idp_id,claims:_,idpAccessTokenEnc:k,idpRefreshTokenEnc:u,sessionExp:S});return s(r,{access_token:P.accessToken,token_type:"Bearer",expires_in:P.expiresIn,refresh_token:ie,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Sr(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),a=t.get("redirect_uri")||void 0,c=t.get("code_verifier")||void 0;if(!F.includes(n))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(n==="refresh_token")return await Fe(e,r,t);if(!i)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const o=await pe(i);if(a&&a!==o.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(!c)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!De(o.code_challenge,o.code_challenge_method,c))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400);if(q.OAUTH_CLIENT_ID&&o.client_id&&!z(o.client_id)&&o.client_id!==q.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const p=o.id_token;if(typeof p!="string"||p.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const d=await ee(p,r.getConfig().ssoDirect);if(!d)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);if(j()&&!await Ue(await r.getKv(),i,o.exp))return s(e,{error:"invalid_grant",error_description:"Authorization code already used"},400);const l=o.idp_access_token_enc,{accessToken:k,expiresIn:u}=await x({claims:d.payload,kid:d.kid,idpId:o.idp_id,idpAccessTokenEnc:l}),_=Math.floor(Date.now()/1e3)+Z(!!o.idp_refresh_token_enc),S=await B({clientId:o.client_id,idpId:o.idp_id,claims:d.payload,idpAccessTokenEnc:l,idpRefreshTokenEnc:o.idp_refresh_token_enc,sessionExp:_}),P={access_token:k,token_type:"Bearer",expires_in:u,refresh_token:S,scope:E.join(" ")};return s(e,P,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(o){return e.get("logger")?.warn("MCP token exchange failed: %s",o instanceof Error?o.message:String(o)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const n=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}async function ee(r,e){const t=await fe(e||{},{[A.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:n,header:i}=C.decode(r);if(n[N.CLAIM]!==N.VALUE)return{payload:n,kid:i.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function H(r){return G("sha256").update(r).digest("base64url")}async function Ge(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:L.MCP_CONSENT,session_hash:H(r.sessionToken),context_hash:H(r.contextToken),iat:e,exp:e+Ae},m,R.HS256)}async function Be(r,e){try{await C.verify(r,m,R.HS256)}catch{return!1}const{payload:t}=C.decode(r);return t.type===L.MCP_CONSENT&&t.session_hash===H(e.sessionToken)&&t.context_hash===H(e.contextToken)}function re(r,e){return async t=>{const n=new URL(t.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(w(`${I.MCP_CALLBACK}/`))){const c=n.pathname.split("/");i=c[c.length-1]}if(!i)return t.text("Missing context parameter",400);let a=null;try{const c=await be(i);a=c.mcpSessionId||null;const o=c.originalRedirectUri,p=X(o);if(!c.isMcpFlow||!o||!p)throw new Error("Invalid MCP context");const d=v(t,A.AUTHORIZATION),l=d?await ee(d,r.getConfig().ssoDirect):void 0;return!d||!l?(y.sendMcpAuthorizationFailedMessage([{...M(a),error:"Authentication required",error_details:null}]),t.text("Authentication required",401)):await e(t,{contextToken:i,mcpContext:c,redirectUri:o,redirectUrl:p,sessionToken:d,session:l})}catch(c){const o=Ve(c);return y.sendMcpAuthorizationFailedMessage([{...M(a),...o}]),t.text(`Invalid MCP callback: ${o.error}`,400)}}}async function te(r,e,{mcpContext:t,redirectUri:n,redirectUrl:i,sessionToken:a,session:c}){const o=v(r,A.IDP_ACCESS_TOKEN),p=v(r,A.IDP_REFRESH_TOKEN),d=await de({idToken:a,idpId:c.idpId??Q(e.getConfig().ssoDirect),idpAccessTokenEnc:o?J(o,m):void 0,idpRefreshTokenEnc:p?V(p,m):void 0,clientId:t.mcpClientId||"",redirectUri:n,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(i);return l.searchParams.set("code",d),t.mcpState&&l.searchParams.set("state",t.mcpState),y.sendMcpAuthorizationCompletedMessage([{...M(t.mcpSessionId||null),redirect_uri:n}]),r.redirect(l.toString())}function Ke(r,{mcpContext:e,redirectUrl:t}){const n=new URL(t);return n.searchParams.set("error","access_denied"),e.mcpState&&n.searchParams.set("state",e.mcpState),y.sendMcpAuthorizationFailedMessage([{...M(e.mcpSessionId||null),error:"access_denied",error_details:null}]),r.redirect(n.toString())}function Je({payload:r}){return[r.email,r.name,r.sub].find(e=>typeof e=="string"&&e.length>0)}function ne({mcpContext:r,redirectUri:e,session:t}){const{sub:n}=t.payload;if(!(!j()||typeof n!="string"||n.length===0))return{idpId:t.idpId,subject:n,clientId:r.mcpClientId||"",redirectUri:e}}function oe(r,e){const{seo:t,colorMode:n}=e.getConfig(),{logo:i}=e.getGlobalData();return{...me({seo:t,logo:i,colorMode:n}),host:new URL(D(r)).host}}function Ir(r){return re(r,async(e,t)=>{const n=ne(t);if(W(t.redirectUrl)||n&&await Oe(await r.getKv(),n))return te(e,r,t);const{mcpClientId:i,mcpClientName:a}=t.mcpContext,c=Pe({site:oe(e,r),user:Je(t.session),clientName:a||void 0,clientIdHost:z(i)?new URL(i).host:void 0,redirectUrl:t.redirectUrl,scopes:E,accessLifetimeDays:Z(!!v(e,A.IDP_REFRESH_TOKEN))/(1440*60),rememberDays:n?ve:void 0,consentToken:await Ge(t)});return new Response(c,{status:200,headers:Y})})}function wr(r){return re(r,async(e,t)=>{const n=await e.req.formData(),i=n.get("consent");if(typeof i!="string"||!await Be(i,t))return new Response(Re(oe(e,r)),{status:403,headers:Y});if(n.get("decision")==="allow"){const a=ne(t);return a&&await Ne(await r.getKv(),a),te(e,r,t)}return Ke(e,t)})}function Ve(r){return r instanceof Error&&[Te,ke,Se,Ie,we].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{F as SUPPORTED_MCP_GRANT_TYPES,He as createMcpContextToken,kr as mcpAuthorizationHandler,Ir as mcpCallbackHandler,wr as mcpConsentHandler,Tr as mcpDynamicClientRegistrationHandler,Cr as mcpOAuthAuthorizationServerHandler,gr as mcpOAuthProtectedResourceHandler,Sr as mcpTokenPortalHandler,Q as resolveMcpIdpId,be as verifyAndParseMcpContextToken,De as verifyPkce};
@@ -1,7 +1,8 @@
1
1
  import { type LookupFunction } from 'node:net';
2
2
  /**
3
3
  * Returns a drop-in replacement for `dns.lookup` that errors with
4
- * {@link SsrfBlockedError} if any resolved address is in the blocklist.
4
+ * {@link SsrfBlockedError} if any resolved address is in the blocklist. The error message names
5
+ * the hostname only, never a resolved address, so callers may show it to the client.
5
6
  *
6
7
  * Install on an http/https agent (`connect.lookup` for undici, or `lookup` for a node:https Agent)
7
8
  * so that every socket connection — including those made while following redirects — is re-validated
@@ -1 +1 @@
1
- import{lookup as c}from"node:dns";import{isIP as k}from"node:net";import{isBlockedIp as v,isLoopback as w,SsrfBlockedError as n}from"./ssrf-blocklist.js";function L({allowLoopback:p=!1}={}){const f=o=>v(o)&&!(p&&w(o));return(o,m,r)=>{const s=m,l=t=>{if(s.all){r(null,t);return}const[e]=t;if(!e){r(new n(`Hostname "${o}" did not resolve`),"",0);return}r(null,e.address,e.family)},u=k(o);if(u!==0){if(f(o)){r(new n(`Hostname "${o}" is a blocked address`),"",0);return}l([{address:o,family:u}]);return}c(o,{...s,all:!0},(t,e)=>{if(t){r(t,"",0);return}const d=e.filter(({address:i})=>f(i));if(d.length>0){const i=d.map(({address:$})=>$).join(", ");r(new n(`Hostname "${o}" resolves to a blocked address: ${i}`),"",0);return}if(!e.length){r(new n(`Hostname "${o}" did not resolve`),"",0);return}l(e)})}}export{L as createSsrfSafeLookup};
1
+ import{lookup as m}from"node:dns";import{isIP as w}from"node:net";import{isBlockedIp as H,isLoopback as $,SsrfBlockedError as i}from"./ssrf-blocklist.js";function B({allowLoopback:l=!1}={}){const n=r=>H(r)&&!(l&&$(r));return(r,d,o)=>{const f=d,s=t=>{if(f.all){o(null,t);return}const[e]=t;if(!e){o(new i(`Hostname "${r}" did not resolve`),"",0);return}o(null,e.address,e.family)},u=w(r);if(u!==0){if(n(r)){o(new i(`Hostname "${r}" is a blocked address`),"",0);return}s([{address:r,family:u}]);return}m(r,{...f,all:!0},(t,e)=>{if(t){o(t,"",0);return}if(e.some(({address:p})=>n(p))){o(new i(`Hostname "${r}" resolves to a blocked address`),"",0);return}if(!e.length){o(new i(`Hostname "${r}" did not resolve`),"",0);return}s(e)})}}export{B as createSsrfSafeLookup};
@@ -13,9 +13,7 @@ export declare function handleErrorPageRender(ctx: Context, store: Store, option
13
13
  }, statusCode: ClientErrorStatusCode, templateId?: string): Promise<Response>;
14
14
  export declare function getLoginUrlWithRedirect(redirectTo: string): string;
15
15
  export declare function handleUnauthorizedAsset(ctx: Context): Promise<Response & import("hono").TypedResponse<string, -1 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511, "text">>;
16
- export declare function handleUnauthorizedApiRequest(ctx: Context): Response & import("hono").TypedResponse<{
17
- message: string;
18
- }, -1 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511, "json">;
16
+ export declare function handleUnauthorizedApiRequest(ctx: Context): Response;
19
17
  /**
20
18
  * Normalize the IP address to an IPv4 address.
21
19
  * @param address - The IP address to normalize.
@@ -1,4 +1,4 @@
1
- import type { LocationEdge } from '@markdoc/markdoc';
1
+ import type { LocationEdge } from '@redocly/markdoc';
2
2
  import type { MARKDOC_ERROR_TYPES } from '../constants/common.js';
3
3
  export type ErrorSeverity = 'PANIC' | 'ERROR';
4
4
  export type CommonError = {
@@ -1,4 +1,4 @@
1
- import type { Node, Variable, RenderableTreeNode, Function } from '@markdoc/markdoc';
1
+ import type { Node, Variable, RenderableTreeNode, Function } from '@redocly/markdoc';
2
2
  import type { RenderForLlmsRenderContext } from '@redocly/theme/markdoc/tags/types';
3
3
  export type RenderTagFn = (node: Node, context: RenderForLlmsRenderContext) => string | undefined;
4
4
  export type GetInnerContentFnOptions = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/redoc",
3
- "version": "0.139.4",
3
+ "version": "0.140.0-next.10",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,8 +20,8 @@
20
20
  "@hono/zod-validator": "0.8.0",
21
21
  "@jitl/quickjs-singlefile-mjs-release-sync": "0.32.0",
22
22
  "@libsql/client": "0.17.3",
23
- "@markdoc/markdoc": "0.5.2",
24
- "@modelcontextprotocol/server": "2.0.0",
23
+ "@markdoc/markdoc": "npm:@redocly/markdoc@0.7.0-beta.2",
24
+ "@modelcontextprotocol/server": "2.3.1",
25
25
  "@opentelemetry/api": "1.9.1",
26
26
  "@opentelemetry/context-zone": "2.0.1",
27
27
  "@opentelemetry/core": "2.8.0",
@@ -33,13 +33,14 @@
33
33
  "@opentelemetry/sdk-trace-web": "2.8.0",
34
34
  "@opentelemetry/semantic-conventions": "1.40.0",
35
35
  "@redocly/ajv": "8.18.0",
36
- "@redocly/api-docs": "0.4.2",
37
- "@redocly/config": "0.67.0",
38
- "@redocly/openapi-core": "2.66.0",
36
+ "@redocly/api-docs": "0.5.0-next.7",
37
+ "@redocly/config": "0.70.0",
38
+ "@redocly/markdoc": "0.7.0-beta.2",
39
+ "@redocly/openapi-core": "2.72.1",
39
40
  "@redocly/portal-legacy-ui": "0.19.0",
40
- "@redocly/portal-plugin-mock-server": "0.24.0",
41
+ "@redocly/portal-plugin-mock-server": "0.25.0-next.2",
41
42
  "@redocly/realm-asyncapi-sdk": "0.17.0",
42
- "@redocly/theme": "0.71.0",
43
+ "@redocly/theme": "0.72.0-next.6",
43
44
  "@shikijs/transformers": "3.21.0",
44
45
  "@tanstack/react-query": "5.62.3",
45
46
  "@tanstack/react-table": "8.21.3",
@@ -61,8 +62,7 @@
61
62
  "fflate": "0.7.5",
62
63
  "flexsearch": "0.7.43",
63
64
  "graphql": "16.14.2",
64
- "hono": "4.13.7",
65
- "htmlparser2": "8.0.2",
65
+ "hono": "4.13.13",
66
66
  "i18next": "22.4.15",
67
67
  "is-glob": "4.0.3",
68
68
  "js-yaml": "4.3.2",
@@ -91,14 +91,14 @@
91
91
  "tty-browserify": "0.0.1",
92
92
  "typesense": "2.1.0",
93
93
  "ulid": "^3.0.2",
94
- "undici": "^6.28.0",
94
+ "undici": "^6.29.1",
95
95
  "web-vitals": "3.3.1",
96
96
  "workerpool": "9.2.0",
97
97
  "ws": "^8.21.0",
98
98
  "xml-crypto": "6.1.0",
99
99
  "xpath": "0.0.34",
100
100
  "yaml-ast-parser": "0.0.43",
101
- "zod": "^3.25.76"
101
+ "zod": "^4.6.6"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "react": "^19.2.7",
@@ -1,6 +0,0 @@
1
- export declare function getJsxConvertedAttributes(attrs: {
2
- [key: string]: string;
3
- }, tagName?: string): {
4
- [key: string]: string | boolean | object;
5
- };
6
- //# sourceMappingURL=html-jsx-attribute-mapping.d.ts.map
@@ -1 +0,0 @@
1
- const a={for:"htmlFor",class:"className","accept-charset":"acceptCharset",accesskey:"accessKey",allowfullscreen:"allowFullScreen",allowtransparency:"allowTransparency",autocomplete:"autoComplete",autoplay:"autoPlay",cellpadding:"cellPadding",cellspacing:"cellSpacing",charset:"charSet",classid:"classID",colspan:"colSpan",contenteditable:"contentEditable",contextmenu:"contextMenu",crossorigin:"crossOrigin",datetime:"dateTime",enctype:"encType",formaction:"formAction",formenctype:"formEncType",formmethod:"formMethod",formnovalidate:"formNoValidate",formtarget:"formTarget",frameborder:"frameBorder",hreflang:"hrefLang","http-equiv":"httpEquiv",inputmode:"inputMode",keyparams:"keyParams",keytype:"keyType",marginheight:"marginHeight",marginwidth:"marginWidth",maxlength:"maxLength",mediagroup:"mediaGroup",minlength:"minLength",novalidate:"noValidate",playsinline:"playsInline",radiogroup:"radioGroup",readonly:"readOnly",referrerpolicy:"referrerPolicy",rowspan:"rowSpan",spellcheck:"spellCheck",srcdoc:"srcDoc",srclang:"srcLang",srcset:"srcSet",tabindex:"tabIndex",usemap:"useMap",autocapitalize:"autoCapitalize",autocorrect:"autoCorrect",autosave:"autoSave",itemprop:"itemProp",itemscope:"itemScope",itemtype:"itemType",itemid:"itemID",itemref:"itemRef","accent-height":"accentHeight","alignment-baseline":"alignmentBaseline","arabic-form":"arabicForm","baseline-shift":"baselineShift","cap-height":"capHeight","clip-path":"clipPath","clip-rule":"clipRule","color-interpolation":"colorInterpolation","color-interpolation-filters":"colorInterpolationFilters","color-profile":"colorProfile","color-rendering":"colorRendering","dominant-baseline":"dominantBaseline","enable-background":"enableBackground","fill-opacity":"fillOpacity","fill-rule":"fillRule","flood-color":"floodColor","flood-opacity":"floodOpacity","font-family":"fontFamily","font-size":"fontSize","font-size-adjust":"fontSizeAdjust","font-stretch":"fontStretch","font-style":"fontStyle","font-variant":"fontVariant","font-weight":"fontWeight","glyph-name":"glyphName","glyph-orientation-horizontal":"glyphOrientationHorizontal","glyph-orientation-vertical":"glyphOrientationVertical","horiz-adv-x":"horizAdvX","horiz-origin-x":"horizOriginX","image-rendering":"imageRendering","letter-spacing":"letterSpacing","lighting-color":"lightingColor","marker-end":"markerEnd","marker-mid":"markerMid","marker-start":"markerStart","overline-position":"overlinePosition","overline-thickness":"overlineThickness","paint-order":"paintOrder","panose-1":"panose1","pointer-events":"pointerEvents","rendering-intent":"renderingIntent","shape-rendering":"shapeRendering","stop-color":"stopColor","stop-opacity":"stopOpacity","strikethrough-position":"strikethroughPosition","strikethrough-thickness":"strikethroughThickness","stroke-dasharray":"strokeDasharray","stroke-dashoffset":"strokeDashoffset","stroke-linecap":"strokeLinecap","stroke-linejoin":"strokeLinejoin","stroke-miterlimit":"strokeMiterlimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-anchor":"textAnchor","text-decoration":"textDecoration","text-rendering":"textRendering","underline-position":"underlinePosition","underline-thickness":"underlineThickness","unicode-bidi":"unicodeBidi","unicode-range":"unicodeRange","units-per-em":"unitsPerEm","v-alphabetic":"vAlphabetic","v-hanging":"vHanging","v-ideographic":"vIdeographic","v-mathematical":"vMathematical","vector-effect":"vectorEffect","vert-adv-y":"vertAdvY","vert-origin-x":"vertOriginX","vert-origin-y":"vertOriginY","word-spacing":"wordSpacing","writing-mode":"writingMode","x-height":"xHeight","xlink:actuate":"xlinkActuate","xlink:arcrole":"xlinkArcrole","xlink:href":"xlinkHref","xlink:role":"xlinkRole","xlink:show":"xlinkShow","xlink:title":"xlinkTitle","xlink:type":"xlinkType","xml:base":"xmlBase","xmlns:xlink":"xmlnsXlink","xml:lang":"xmlLang","xml:space":"xmlSpace"};function s(r,t){const e={};for(const[i,n]of Object.entries(r))switch(i){case"value":t==="input"||t==="textarea"?e.defaultValue=n:e.value=n;break;case"checked":t==="input"?e.defaultChecked=!0:e.checked=n;break;case"style":e.style=c(n);break;default:e[a[i]||i]=n}return e}function l(r){return r.match(/^--/)?r:r.replace(/-[a-z]/g,t=>t[1].toUpperCase())}function c(r){return r.split(";").filter(t=>t.trim().length).reduce((t,e)=>{const i=e.indexOf(":"),n=i===-1?e:e.slice(0,i),o=i===-1?"":e.slice(i+1);return t[l(n.trim())]=o.trim(),t},{})}export{s as getJsxConvertedAttributes};
@@ -1,2 +0,0 @@
1
- export declare function processHtmlTokens(tokens: any): any[];
2
- //# sourceMappingURL=process-html-tokens.d.ts.map
@@ -1 +0,0 @@
1
- import{Parser as p}from"htmlparser2";import{getJsxConvertedAttributes as i}from"./html-jsx-attribute-mapping.js";function l(r){const e=[],n=[];let o=null;const s=new p({onopentag(t,a){n.push({name:t,token:o}),e.push({type:"tag_open",nesting:1,meta:{tag:"html",attributes:[{type:"attribute",name:"name",value:t},{type:"attribute",name:"attrs",value:i(a,t)}]}})},ontext(t){t?.trim().length>0&&e.push({type:"text",content:t})},onclosetag(){n.pop(),e.push({type:"tag_close",nesting:-1,meta:{tag:"html"}})}});for(const t of r){if(t.type.startsWith("html")){o=t,s.write(t.content);continue}t.type==="inline"&&(t.children=l(t.children)),e.push(t)}for(const t of n)e.push({type:"error",meta:{error:{message:`HTML tag <${t.name}> is not closed, opened at`,location:t?.token?.map?{start:{line:t.token.map[0]},end:{line:t.token.map[1]}}:void 0}}}),e.push({type:"tag_close",nesting:-1,meta:{tag:"html"}});return e}export{l as processHtmlTokens};
@@ -1,4 +0,0 @@
1
- import type { Node } from '@markdoc/markdoc';
2
- export declare function getSourceRange(node: Node, linesOfCode: string[]): any;
3
- export declare function hashSourceContent(content: string): string;
4
- //# sourceMappingURL=source.d.ts.map
@@ -1 +0,0 @@
1
- function s(e,t){if(e.lines.length===0)return"";let l="",i=[];if(e.location){const n=e.lines[0]+1;if(l+=`#L${n}`,n!==e.lines[e.lines.length-1]){const r=e.lines[e.lines.length-1];l+=`-L${e.lines[e.lines.length-1]}`,i=t.slice(n-1,r)}else i=[t[n-1]]}return{range:l,source:i}}function h(e){let t=5381,l=e.length;for(;l;)t=t*33^e.charCodeAt(--l);return(t>>>0).toString(16)}export{s as getSourceRange,h as hashSourceContent};
@@ -1,25 +0,0 @@
1
- /**
2
- * Masks the username part of an email address for privacy or anonymization.
3
- *
4
- * The function preserves the domain while replacing most characters in the username
5
- * with asterisks (`*`). It keeps the first two characters visible when the username
6
- * is longer than two characters. If the input is invalid or not a string containing
7
- * an `@` symbol, it returns the original value unchanged.
8
- *
9
- * @example
10
- * ```ts
11
- * maskEmail("john.doe@example.com");
12
- * // → "jo******@example.com"
13
- *
14
- * maskEmail("ab@example.com");
15
- * // → "ab@example.com"
16
- *
17
- * maskEmail("a@example.com");
18
- * // → "*@example.com"
19
- * ```
20
- *
21
- * @param email - The email address to mask.
22
- * @returns A masked version of the email, or the original value if invalid.
23
- */
24
- export declare const maskEmail: (email: string) => string;
25
- //# sourceMappingURL=mask-email.d.ts.map
@@ -1 +0,0 @@
1
- const r=e=>{if(!e||typeof e!="string"||!e.includes("@"))return e;const[n,t]=e.split("@");return`${n.length>2?n.slice(0,2)+"*".repeat(n.length-2):"*".repeat(n.length)}@${t}`};export{r as maskEmail};
@@ -1,13 +0,0 @@
1
- /**
2
- * Masks all email addresses found within a given subject string.
3
- *
4
- * This utility searches the input text for any email-like patterns and replaces
5
- * each one using the {@link maskEmail} function, preserving the rest of the string.
6
- * It is typically used to sanitize or anonymize email addresses before logging,
7
- * displaying, or transmitting text content.
8
- *
9
- * @param subject - The text that may contain one or more email addresses.
10
- * @returns A new string with all detected email addresses masked.
11
- */
12
- export declare const maskSubject: (subject: string) => string;
13
- //# sourceMappingURL=mask-subject.d.ts.map
@@ -1 +0,0 @@
1
- import{maskEmail as c}from"./mask-email.js";const r=/[a-zA-Z0-9._%+-]/,m=/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/,d=e=>{let o="",i=0,a=0;for(;a<e.length;){const n=e.indexOf("@",a);if(n===-1)break;let t=n;for(;t>i&&r.test(e.charAt(t-1));)t--;const s=t<n?m.exec(e.slice(n+1))?.[0]:void 0;if(!s){a=n+1;continue}const l=n+1+s.length;o+=e.slice(i,t)+c(e.slice(t,l)),i=l,a=l}return o+e.slice(i)};export{d as maskSubject};
@@ -1,25 +0,0 @@
1
- /**
2
- * Sanitizes personally identifiable information (PII) found within a URL or query string.
3
- *
4
- * This function scans all query parameter values and removes or masks any PII using
5
- * the {@link removePii} function. It gracefully handles both absolute and relative URLs.
6
- *
7
- * - If the input is not a string, it is returned unchanged.
8
- * - If parsing as a URL fails, the function falls back to applying {@link removePii}
9
- * directly on the raw string.
10
- * - The output preserves the original format (absolute or relative).
11
- *
12
- * @example
13
- * ```ts
14
- * removePiiFromUrl('https://example.com?email=john.doe@example.com&phone=555-123-4567');
15
- * // → 'https://example.com/?email=[email_removed]&phone=[phone_removed]'
16
- *
17
- * removePiiFromUrl('/profile?ssn=123-45-6789');
18
- * // → '/profile?ssn=[SSN_removed]'
19
- * ```
20
- *
21
- * @param url - The input URL string or relative path to sanitize.
22
- * @returns A sanitized URL string with PII masked, or the input value unchanged if not a string.
23
- */
24
- export declare function removePiiFromUrl(url: unknown): unknown;
25
- //# sourceMappingURL=remove-pii-from-url.d.ts.map
@@ -1 +0,0 @@
1
- import{isString as n}from"../../../../../utils/string/is-string.js";import{removePii as e}from"./remove-pii.js";function m(t){if(!n(t))return t;try{const r=new URL(t,"http://example.com");for(const[i,s]of r.searchParams.entries()){const a=String(e(s));r.searchParams.set(i,a)}return t.startsWith("http://")||t.startsWith("https://")?r.toString():r.pathname+r.search+r.hash}catch{return String(e(t))}}export{m as removePiiFromUrl};
@@ -1,36 +0,0 @@
1
- /**
2
- * Implementation of PII sanitization from @coffeeandfun/remove-pii (MIT licensed) without unnecessary methods
3
- */
4
- type PIIConfigOption = {
5
- enabled: false;
6
- replacement?: string;
7
- pattern?: RegExp;
8
- } | {
9
- enabled: true;
10
- replacement: string;
11
- pattern?: RegExp;
12
- };
13
- type PIIConfig = {
14
- [key: string]: PIIConfigOption;
15
- };
16
- /**
17
- * Removes or masks personally identifiable information (PII) from a given text string.
18
- *
19
- * This function identifies and replaces sensitive data such as email addresses,
20
- * phone numbers, credit card numbers, and other personal identifiers with
21
- * configurable placeholder strings. It’s a lightweight implementation inspired by
22
- * `@coffeeandfun/remove-pii` (MIT licensed), keeping only the essential sanitization logic.
23
- *
24
- * By default, all common PII patterns (email, phone, SSN, credit card, address,
25
- * passport number, driver’s license, ZIP code, bank account, and date of birth)
26
- * are detected and replaced. Custom configuration can override or disable any pattern.
27
- *
28
- * Non-string inputs are returned unchanged.
29
- *
30
- * @param text - The input text that may contain personally identifiable information.
31
- * @param options - Optional configuration to enable, disable, or customize PII replacements.
32
- * @returns A sanitized string with PII masked or removed; if the input is not a string, returns it unchanged.
33
- */
34
- export declare function removePii(text: unknown, options?: PIIConfig): unknown;
35
- export {};
36
- //# sourceMappingURL=remove-pii.d.ts.map
@@ -1 +0,0 @@
1
- import{isString as m}from"../../../../../utils/string/is-string.js";const s={email:{pattern:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,replacement:"[email_removed]"},phone:{pattern:/(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})(?:\s?(?:ext|x|extension)\.?\s?(\d+))?/g,replacement:"[phone_removed]"},ssn:{pattern:/\b(?!000|666|9\d{2})\d{3}-?(?!00)\d{2}-?(?!0000)\d{4}\b/g,replacement:"[SSN_removed]"},creditCard:{pattern:/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,replacement:"[credit_card_removed]"},address:{pattern:/\b\d+\s[A-Za-z0-9\s,.-]+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Court|Ct|Circle|Cir|Way|Place|Pl|Parkway|Pkwy|Terrace|Ter)\b/gi,replacement:"[address_removed]"},passport:{pattern:/\b[A-Z]{2}[0-9]{7}\b/g,replacement:"[passport_number_removed]"},driversLicense:{pattern:/\b[A-Z]{1,2}[0-9]{6,9}\b/g,replacement:"[driver_license_removed]"},zipCode:{pattern:/(?<!\d-)\b\d{5}(?:-\d{4})?\b(?!-\d)/g,replacement:"[zip_code_removed]"},bankAccount:{pattern:/\b\d{8,17}\b/g,replacement:"[bank_account_removed]"},dateOfBirth:{pattern:/\b(?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01])[-/](?:19|20)\d{2}\b/g,replacement:"[date_removed]"}};function l(n={}){const e={email:{enabled:!0,replacement:"[email_removed]"},phone:{enabled:!0,replacement:"[phone_removed]"},ssn:{enabled:!0,replacement:"[SSN_removed]"},creditCard:{enabled:!0,replacement:"[credit_card_removed]"},address:{enabled:!0,replacement:"[address_removed]"},passport:{enabled:!0,replacement:"[passport_number_removed]"},driversLicense:{enabled:!0,replacement:"[driver_license_removed]"},zipCode:{enabled:!0,replacement:"[zip_code_removed]"},bankAccount:{enabled:!0,replacement:"[bank_account_removed]"},dateOfBirth:{enabled:!0,replacement:"[date_removed]"}};for(const[r,t]of Object.entries(n))e[r]?e[r]={...e[r],...t}:e[r]=t;return e}function b(n,e={}){if(!m(n))return n;const r=l(e);let t=n;for(const[c,a]of Object.entries(r)){if(!a.enabled)continue;const d=s[c],o=d?.pattern||a.pattern,p=a.replacement||d?.replacement||"[PII removed]";o&&(t=t.replace(o,p))}return t}export{b as removePii};