@redocly/redoc 0.129.0-next.2 → 0.129.0-next.3

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 (241) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/bin.js +1 -1
  3. package/dist/cli/develop.d.ts +1 -1
  4. package/dist/cli/develop.js +1 -1
  5. package/dist/cli/prepare/analytics/collectors/get-file-extensions-usage.js +1 -1
  6. package/dist/cli/prepare/analytics/collectors/get-frontmatter-usage.js +1 -1
  7. package/dist/cli/prepare/analytics/collectors/get-markdoc-usage.js +1 -1
  8. package/dist/cli/prepare/analytics/collectors/get-nested-configs-usage.js +1 -1
  9. package/dist/cli/prepare/analytics/collectors/get-refs-usage.js +1 -1
  10. package/dist/cli/prepare/index.d.ts +1 -1
  11. package/dist/cli/translations/collect-config-translations.js +1 -1
  12. package/dist/cli/translations/update-translations.js +1 -1
  13. package/dist/client/TestProvider.js +1 -1
  14. package/dist/client/app/Sidebar/RequestAccessButton.js +2 -2
  15. package/dist/client/app/Sidebar/Sidebar.js +7 -2
  16. package/dist/client/app/Sidebar/useSidebarItems.d.ts +1 -0
  17. package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
  18. package/dist/client/app/hooks/catalog/useFetchCatalogEntities.js +1 -1
  19. package/dist/client/app/hooks/catalog/useFetchCatalogEntitiesRelations.js +1 -1
  20. package/dist/client/app/hooks/catalog/useFetchCatalogEntityRevisions.d.ts +10 -0
  21. package/dist/client/app/hooks/catalog/useFetchCatalogEntityRevisions.js +1 -0
  22. package/dist/client/app/hooks/index.d.ts +1 -0
  23. package/dist/client/app/hooks/index.js +1 -1
  24. package/dist/client/app/hooks/useLoginUrl.js +1 -1
  25. package/dist/client/app/pages/DevLogin/DevLogin.js +1 -1
  26. package/dist/client/app/pages/Login/Login.js +1 -1
  27. package/dist/client/app/utils/loadAndNavigate.js +1 -1
  28. package/dist/client/browser-entry.js +2 -2
  29. package/dist/client/providers/page-data/hooks.d.ts +2 -1
  30. package/dist/client/providers/page-data/hooks.js +1 -1
  31. package/dist/client/providers/theme/ThemeDataProvider.js +1 -1
  32. package/dist/client/runtime/loader.js +1 -1
  33. package/dist/client/utils/catalog/inject-catalog-items.d.ts +1 -1
  34. package/dist/client/utils/catalog/inject-catalog-items.js +1 -1
  35. package/dist/constants/common.d.ts +0 -4
  36. package/dist/constants/common.js +1 -1
  37. package/dist/constants/l10n/langs/ar.js +1 -1
  38. package/dist/constants/l10n/langs/de.js +1 -1
  39. package/dist/constants/l10n/langs/en.js +1 -1
  40. package/dist/constants/l10n/langs/es.js +1 -1
  41. package/dist/constants/l10n/langs/fr.js +1 -1
  42. package/dist/constants/l10n/langs/hi.js +1 -1
  43. package/dist/constants/l10n/langs/it.js +1 -1
  44. package/dist/constants/l10n/langs/ja.js +1 -1
  45. package/dist/constants/l10n/langs/ko.js +1 -1
  46. package/dist/constants/l10n/langs/pl.js +1 -1
  47. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  48. package/dist/constants/l10n/langs/pt.js +1 -1
  49. package/dist/constants/l10n/langs/ru.js +1 -1
  50. package/dist/constants/l10n/langs/uk.js +1 -1
  51. package/dist/constants/l10n/langs/zh.js +1 -1
  52. package/dist/server/api-routes/execute-api-route.d.ts +0 -7
  53. package/dist/server/api-routes/execute-api-route.js +1 -1
  54. package/dist/server/api-routes/helpers/setup-logger.d.ts +2 -2
  55. package/dist/server/api-routes/helpers/setup-logger.js +1 -1
  56. package/dist/server/api-routes/run-api-routes-worker.js +1 -1
  57. package/dist/server/fs/cache.d.ts +1 -2
  58. package/dist/server/fs/cache.js +1 -1
  59. package/dist/server/fs/content-fs.d.ts +9 -4
  60. package/dist/server/fs/content-fs.js +1 -1
  61. package/dist/server/fs/fs.d.ts +6 -6
  62. package/dist/server/fs/fs.js +1 -3
  63. package/dist/server/fs/load-error.d.ts +2 -2
  64. package/dist/server/fs/load-error.js +1 -1
  65. package/dist/server/fs/utils/async-storage.d.ts +0 -8
  66. package/dist/server/fs/utils/async-storage.js +1 -1
  67. package/dist/server/fs/utils/isVirtualFile.d.ts +1 -1
  68. package/dist/server/fs/utils/isVirtualFile.js +1 -1
  69. package/dist/server/plugins/api-functions/index.js +1 -1
  70. package/dist/server/plugins/arazzo-docs/arazzo-doc-loader.d.ts +1 -1
  71. package/dist/server/plugins/arazzo-docs/arazzo-doc-loader.js +2 -2
  72. package/dist/server/plugins/arazzo-docs/index.d.ts +1 -1
  73. package/dist/server/plugins/arazzo-docs/index.js +1 -1
  74. package/dist/server/plugins/asyncapi-docs/asyncapi-doc-loader.d.ts +1 -1
  75. package/dist/server/plugins/asyncapi-docs/asyncapi-doc-loader.js +2 -2
  76. package/dist/server/plugins/asyncapi-docs/index.d.ts +1 -1
  77. package/dist/server/plugins/asyncapi-docs/index.js +1 -1
  78. package/dist/server/plugins/asyncapi-docs/is-asyncapi-doc.js +1 -1
  79. package/dist/server/plugins/catalog-entities/database/catalog-entities-service.d.ts +23 -18
  80. package/dist/server/plugins/catalog-entities/database/catalog-entities-service.js +1 -1
  81. package/dist/server/plugins/catalog-entities/database/mappers/create-bff-entity.js +1 -1
  82. package/dist/server/plugins/catalog-entities/database/mappers/create-bff-related-entity.d.ts +9 -0
  83. package/dist/server/plugins/catalog-entities/database/mappers/create-bff-related-entity.js +1 -1
  84. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-db-record.d.ts +1 -0
  85. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-db-record.js +1 -1
  86. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-read-model.js +1 -1
  87. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-dto.js +1 -1
  88. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-file-schema.js +1 -1
  89. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation.js +1 -1
  90. package/dist/server/plugins/catalog-entities/database/mappers/map-entity-relation-row.d.ts +12 -0
  91. package/dist/server/plugins/catalog-entities/database/mappers/map-entity-relation-row.js +1 -0
  92. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-read-repository.d.ts +15 -4
  93. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-read-repository.js +67 -55
  94. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-repository.d.ts +21 -15
  95. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-repository.js +1 -1
  96. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-write-repository.d.ts +16 -3
  97. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-write-repository.js +1 -1
  98. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-relations-repository.d.ts +17 -0
  99. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-relations-repository.js +1 -0
  100. package/dist/server/plugins/catalog-entities/database/repositories/utils.d.ts +4 -4
  101. package/dist/server/plugins/catalog-entities/database/repositories/utils.js +1 -1
  102. package/dist/server/plugins/catalog-entities/entities/{extract-entities-content.d.ts → extract-file-content.d.ts} +1 -1
  103. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/arazzo-entities-extractor.d.ts +2 -2
  104. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/arazzo-entities-extractor.js +1 -1
  105. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/asyncapi-entities-extractor.d.ts +2 -2
  106. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/asyncapi-entities-extractor.js +1 -1
  107. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/base.d.ts +2 -2
  108. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/base.js +1 -1
  109. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/graphql-entities-extractor.d.ts +2 -2
  110. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/graphql-entities-extractor.js +2 -2
  111. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/openapi-entities-extractor.d.ts +2 -2
  112. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/openapi-entities-extractor.js +1 -1
  113. package/dist/server/plugins/catalog-entities/extensions/extractors/fs-entities-extractor.js +1 -1
  114. package/dist/server/plugins/catalog-entities/get-server-props.d.ts +1 -2
  115. package/dist/server/plugins/catalog-entities/get-server-props.js +1 -1
  116. package/dist/server/plugins/catalog-entities/schemas/database-schemas.d.ts +64 -0
  117. package/dist/server/plugins/catalog-entities/schemas/database-schemas.js +1 -1
  118. package/dist/server/plugins/catalog-entities/schemas/dto-schemas.d.ts +6 -12
  119. package/dist/server/plugins/catalog-entities/schemas/dto-schemas.js +1 -1
  120. package/dist/server/plugins/catalog-entities/schemas/read-model-schemas.d.ts +4 -8
  121. package/dist/server/plugins/catalog-entities/schemas/read-model-schemas.js +1 -1
  122. package/dist/server/plugins/catalog-entities/utils/catalog-data-collector.js +1 -1
  123. package/dist/server/plugins/catalog-entities/utils/read-string.d.ts +7 -0
  124. package/dist/server/plugins/catalog-entities/utils/read-string.js +1 -0
  125. package/dist/server/plugins/config-parser/loaders/content-slugs-loader.js +1 -1
  126. package/dist/server/plugins/config-parser/loaders/nearest-redocly-config-loader.js +1 -1
  127. package/dist/server/plugins/config-parser/loaders/utils/read-and-validate-config.js +1 -1
  128. package/dist/server/plugins/config-parser/loaders/yaml-parser.js +1 -1
  129. package/dist/server/plugins/default-theme/resolve-products-config.js +1 -1
  130. package/dist/server/plugins/ensure-frontmatter-theme-compatibility.js +1 -1
  131. package/dist/server/plugins/entitlements/index.js +1 -1
  132. package/dist/server/plugins/graphql-docs/index.js +1 -1
  133. package/dist/server/plugins/l10n/index.js +1 -1
  134. package/dist/server/plugins/lifecycle.js +2 -2
  135. package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/filesets-resolver.js +1 -1
  136. package/dist/server/plugins/markdown/attribute-resolvers/resolve-code-snippet-from-file.js +1 -1
  137. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.d.ts +1 -1
  138. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
  139. package/dist/server/plugins/markdown/attribute-resolvers/resolve-open-api-ref.js +1 -1
  140. package/dist/server/plugins/markdown/attribute-resolvers/resolve-parsed-yaml.js +1 -1
  141. package/dist/server/plugins/markdown/attribute-resolvers/resolve-raw-content.js +1 -1
  142. package/dist/server/plugins/markdown/attribute-resolvers/resolve-svg-content.js +1 -1
  143. package/dist/server/plugins/markdown/compiler.js +1 -1
  144. package/dist/server/plugins/markdown/get-server-props.js +1 -1
  145. package/dist/server/plugins/markdown/index.js +1 -1
  146. package/dist/server/plugins/markdown/markdoc/partials.js +1 -1
  147. package/dist/server/plugins/markdown/markdoc/resolve-raw-partials.js +1 -1
  148. package/dist/server/plugins/markdown/markdown-static-data-loader.js +1 -1
  149. package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +1 -1
  150. package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
  151. package/dist/server/plugins/mcp/docs-mcp/tools/get-endpoint-info.d.ts +7 -2
  152. package/dist/server/plugins/mcp/docs-mcp/tools/get-endpoint-info.js +1 -1
  153. package/dist/server/plugins/mcp/docs-mcp/tools/get-endpoints.d.ts +7 -2
  154. package/dist/server/plugins/mcp/docs-mcp/tools/get-endpoints.js +1 -1
  155. package/dist/server/plugins/mcp/docs-mcp/tools/get-full-api-description.d.ts +7 -2
  156. package/dist/server/plugins/mcp/docs-mcp/tools/get-full-api-description.js +1 -1
  157. package/dist/server/plugins/mcp/docs-mcp/tools/get-security-schemes.d.ts +7 -2
  158. package/dist/server/plugins/mcp/docs-mcp/tools/get-security-schemes.js +1 -1
  159. package/dist/server/plugins/mcp/docs-mcp/tools/index.d.ts +4 -3
  160. package/dist/server/plugins/mcp/docs-mcp/tools/index.js +1 -1
  161. package/dist/server/plugins/mcp/docs-mcp/tools/list-apis.d.ts +5 -2
  162. package/dist/server/plugins/mcp/docs-mcp/tools/list-apis.js +1 -1
  163. package/dist/server/plugins/mcp/docs-mcp/tools/utils.d.ts +6 -0
  164. package/dist/server/plugins/mcp/docs-mcp/tools/utils.js +6 -6
  165. package/dist/server/plugins/mcp/docs-mcp/tools/whoami.js +1 -1
  166. package/dist/server/plugins/mcp/docs-mcp/utils.d.ts +3 -3
  167. package/dist/server/plugins/mcp/docs-mcp/utils.js +1 -1
  168. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  169. package/dist/server/plugins/mcp/index.js +1 -1
  170. package/dist/server/plugins/mcp/servers/docs-server.d.ts +10 -3
  171. package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
  172. package/dist/server/plugins/mcp/types.d.ts +26 -2
  173. package/dist/server/plugins/mcp/utils.d.ts +9 -8
  174. package/dist/server/plugins/mcp/utils.js +1 -1
  175. package/dist/server/plugins/mcp/workers/run-api-routes-worker.js +1 -1
  176. package/dist/server/plugins/nav-utils.js +1 -1
  177. package/dist/server/plugins/openapi-docs/decorators.d.ts +3 -2
  178. package/dist/server/plugins/openapi-docs/decorators.js +1 -1
  179. package/dist/server/plugins/openapi-docs/index.d.ts +2 -2
  180. package/dist/server/plugins/openapi-docs/index.js +1 -1
  181. package/dist/server/plugins/openapi-docs/is-openapi-doc.js +1 -1
  182. package/dist/server/plugins/openapi-docs/load-definition.d.ts +1 -1
  183. package/dist/server/plugins/openapi-docs/load-definition.js +3 -3
  184. package/dist/server/plugins/pages/index.js +1 -1
  185. package/dist/server/plugins/search/llmstxt/index.js +5 -5
  186. package/dist/server/plugins/sidebars/index.js +3 -3
  187. package/dist/server/plugins/utils.js +1 -1
  188. package/dist/server/providers/database/databases/catalog-sqlite/schemas/entities-relations-table.d.ts +25 -46
  189. package/dist/server/providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js +1 -1
  190. package/dist/server/providers/database/databases/catalog-sqlite/schemas/entities-table.d.ts +20 -3
  191. package/dist/server/providers/database/databases/catalog-sqlite/schemas/entities-table.js +1 -1
  192. package/dist/server/providers/database/pagination/combined-filters.d.ts +4 -1
  193. package/dist/server/providers/database/pagination/combined-filters.js +1 -1
  194. package/dist/server/providers/database/pagination/filter.d.ts +1 -0
  195. package/dist/server/providers/database/pagination/filter.js +1 -1
  196. package/dist/server/providers/database/pagination/index.d.ts +4 -1
  197. package/dist/server/store.js +1 -1
  198. package/dist/server/tools/notifiers/formatter.d.ts +2 -0
  199. package/dist/server/tools/notifiers/formatter.js +3 -3
  200. package/dist/server/tools/notifiers/logger.d.ts +18 -39
  201. package/dist/server/tools/notifiers/logger.js +2 -6
  202. package/dist/server/tools/notifiers/reporter.js +9 -9
  203. package/dist/server/tools/notifiers/terminal-manager.d.ts +8 -0
  204. package/dist/server/tools/notifiers/terminal-manager.js +5 -0
  205. package/dist/server/types/fs.d.ts +5 -10
  206. package/dist/server/types/plugins/api-routes.d.ts +0 -3
  207. package/dist/server/types/plugins/common.d.ts +0 -1
  208. package/dist/server/utils/is-valid-iso-date.d.ts +5 -0
  209. package/dist/server/utils/is-valid-iso-date.js +1 -0
  210. package/dist/server/utils/lifecycle-hooks.js +1 -1
  211. package/dist/server/utils/queue.js +1 -1
  212. package/dist/server/utils/redirects/find-redirect.d.ts +4 -2
  213. package/dist/server/utils/redirects/find-redirect.js +1 -1
  214. package/dist/server/utils/resolve-asset-path.js +1 -1
  215. package/dist/server/utils/validate-and-sanitize-string.d.ts +29 -0
  216. package/dist/server/utils/validate-and-sanitize-string.js +1 -0
  217. package/dist/server/web-server/auth.d.ts +5 -0
  218. package/dist/server/web-server/auth.js +3 -3
  219. package/dist/server/web-server/http.js +2 -2
  220. package/dist/server/web-server/middleware/loggerMiddleware.js +1 -1
  221. package/dist/server/web-server/routes/auth.js +1 -1
  222. package/dist/server/web-server/routes/catalog/bff-catalog-related-entities.js +1 -1
  223. package/dist/server/web-server/routes/catalog/bff-catalog.js +1 -1
  224. package/dist/server/web-server/routes/catalog/catalog-relations.js +1 -1
  225. package/dist/server/web-server/routes/mcp-oauth.js +1 -1
  226. package/dist/server/web-server/routes/search.js +1 -1
  227. package/dist/server/web-server/utils/get-redirect-login-url.js +1 -1
  228. package/dist/utils/auth/build-login-url.d.ts +1 -3
  229. package/dist/utils/auth/build-login-url.js +1 -1
  230. package/dist/utils/env/is-local-development.d.ts +13 -0
  231. package/dist/utils/env/is-local-development.js +1 -0
  232. package/dist/utils/env/is-production.d.ts +13 -0
  233. package/dist/utils/env/is-production.js +1 -0
  234. package/dist/utils/env/is-web-view.d.ts +14 -0
  235. package/dist/utils/env/is-web-view.js +1 -0
  236. package/dist/utils/path/remove-fragment.d.ts +16 -0
  237. package/dist/utils/path/remove-fragment.js +1 -0
  238. package/package.json +10 -10
  239. package/dist/server/web-server/routes/otel/types.d.ts +0 -61
  240. package/dist/server/web-server/routes/otel/types.js +0 -1
  241. /package/dist/server/plugins/catalog-entities/entities/{extract-entities-content.js → extract-file-content.js} +0 -0
@@ -1 +1 @@
1
- import*as t from"path";import{cliCommandNames as o}from"../../constants/common.js";import{PUBLIC_STATIC_FOLDER as i}from"../constants/common.js";import{logger as n}from"../tools/notifiers/logger.js";import{blue as p,gray as m}from"../tools/notifiers/helpers/colors.js";import{loadEnvVariables as f}from"./envs/load-env-variables.js";import{copyFolderRecursiveSync as a}from"./fs.js";import{validateInstalledVersion as E}from"./validate-installed-version.js";import{PACKAGE_NAME as l}from"../../config/product-gates.js";import{PORTAL_VERSION as u}from"../version.js";function P(e){return e?"true":"false"}function R(e){return e?"true":"false"}function v(e){process.env.REPORTER_VERBOSE=P(e.verbose),process.env.INSPECT_MODE="inspect"in e?R(e.inspect):"false",e.pathPrefix&&(process.env.REDOCLY_PREFIX_PATHS=e.pathPrefix)}function O({contentDir:e,outdir:r}){a(t.join(e,i),t.join(r,i))}async function b(e,r,s){f(r["project-dir"]),process.env.REDOCLY_RUNNING_COMMAND=e;const c=e===o.DEVELOP?" Previewing with":"Building with";switch(n.logSticky("product",`${c} ${p(`${l}@${u}`)}`),n.logSticky("server",` \u{1F310} Preview URL: ${m("server starting...")}`),e){case o.DEVELOP:case o.BUILD:case o.PREPARE:v(r);break;default:break}await E(),O(s)}export{b as beforeCommand};
1
+ import*as i from"path";import{cliCommandNames as r}from"../../constants/common.js";import{PUBLIC_STATIC_FOLDER as t}from"../constants/common.js";import{logger as n}from"../tools/notifiers/logger.js";import{blue as s,gray as c}from"../tools/notifiers/helpers/colors.js";import{loadEnvVariables as f}from"./envs/load-env-variables.js";import{copyFolderRecursiveSync as l}from"./fs.js";import{validateInstalledVersion as a}from"./validate-installed-version.js";import{PACKAGE_NAME as E}from"../../config/product-gates.js";import{PORTAL_VERSION as P}from"../version.js";function u(e){return e?"true":"false"}function v(e){e["log-level"]&&(process.env.REDOCLY_LOG_LEVEL=String(e["log-level"])),process.env.INSPECT_MODE="inspect"in e?u(e.inspect):"false",e.pathPrefix&&(process.env.REDOCLY_PREFIX_PATHS=e.pathPrefix)}function L({contentDir:e,outdir:o}){l(i.join(e,t),i.join(o,t))}async function N(e,o,p){f(o["project-dir"]),process.env.REDOCLY_RUNNING_COMMAND=e;const m=e===r.DEVELOP?" Previewing with":"Building with";switch(n.logInFooter("product",`${m} ${s(`${E}@${P}`)}`),n.logInFooter("server",` \u{1F310} Preview URL: ${c("server starting...")}`),e){case r.DEVELOP:case r.BUILD:case r.PREPARE:v(o);break;default:break}await a(),L(p)}export{N as beforeCommand};
@@ -1 +1 @@
1
- function c(i){return"data"in i}function a(i){return"execute"in i}class o{#t=[];#e=null;#s;#i;constructor(t){this.#c(t),this.#s=t.batchSize,this.#i=t.batchProcessor}async addItem(t){return new Promise((s,h)=>{if(a(t)){const r={execute:async(...n)=>{try{const e=await t.execute(...n);return s(e),e}catch(e){throw console.error("Failed to execute item:",e),h(e),e}}};this.#t.push(r)}else if(c(t))this.#t.push(t),s(t.data);else{h(new Error("Invalid item type"));return}this.#h()})}#h(){this.#e||this.#t.length===0||this.#s&&this.#t.length<this.#s||(this.#e=this.#n().catch(t=>{console.error("Batch queue processing failed:",t)}).finally(()=>{this.#e=null,this.#t.length>0&&this.#h()}))}async#n(){for(;this.#t.length>=(this.#s??1);)await this.#r()}async processRemainingItems(){for(this.#e&&await this.#e;this.#t.length>0;)await this.#r()}async#r(){const t=this.#s??this.#t.length,s=this.#t.splice(0,t);s.length&&await this.#i(s)}#c(t){if(t.batchSize&&t.batchSize<=0)throw new Error("Batch size must be greater than 0")}get isProcessing(){return this.#e!==null}}export{o as BatchQueue,c as isDataItem,a as isExecutableItem};
1
+ import{logger as a}from"../tools/notifiers/logger.js";function c(i){return"data"in i}function o(i){return"execute"in i}class l{#t=[];#e=null;#s;#i;constructor(t){this.#a(t),this.#s=t.batchSize,this.#i=t.batchProcessor}async addItem(t){return new Promise((e,h)=>{if(o(t)){const r={execute:async(...n)=>{try{const s=await t.execute(...n);return e(s),s}catch(s){throw h(s),s}}};this.#t.push(r)}else if(c(t))this.#t.push(t),e(t.data);else{h(new Error("Invalid item type"));return}this.#h()})}#h(){this.#e||this.#t.length===0||this.#s&&this.#t.length<this.#s||(this.#e=this.#n().catch(t=>{a.error(`Batch queue processing failed: ${t.message}`)}).finally(()=>{this.#e=null,this.#t.length>0&&this.#h()}))}async#n(){for(;this.#t.length>=(this.#s??1);)await this.#r()}async processRemainingItems(){for(this.#e&&await this.#e;this.#t.length>0;)await this.#r()}async#r(){const t=this.#s??this.#t.length,e=this.#t.splice(0,t);e.length&&await this.#i(e)}#a(t){if(t.batchSize&&t.batchSize<=0)throw new Error("Batch size must be greater than 0")}get isProcessing(){return this.#e!==null}}export{l as BatchQueue,c as isDataItem,o as isExecutableItem};
@@ -3,12 +3,14 @@ import type { WildcardRedirectsTree } from '../../types';
3
3
  /**
4
4
  * Finds a matching redirect configuration for a given slug.
5
5
  *
6
- * - Normalizes the input `slug` by converting it to lowercase and applying route normalization.
6
+ * **IMPORTANT:** The `slug` parameter must be normalized before passing to this function.
7
+ * This function does NOT perform normalization internally.
8
+ *
7
9
  * - First checks for a direct match in the `redirects` configuration.
8
10
  * - If no direct match is found, searches for wildcard redirects (patterns ending with `*`).
9
11
  * - For wildcard matches, preserves the remaining path after the wildcard prefix if the redirect target also ends with `*`.
10
12
  *
11
- * @param slug - The URL slug to find a redirect for.
13
+ * @param slug - The normalized (lowercased) URL slug to find a redirect for.
12
14
  * @param redirects - The redirects configuration object.
13
15
  * @param wildcardRedirectsTree - The wildcard redirects converted to a tree structure to optimize the search.
14
16
  * @returns An object containing the redirect `to` URL and optional `type`, or `null` if no redirect is found.
@@ -1 +1 @@
1
- import{normalizeRouteSlug as a}from"../../../utils/path/normalize-route-slug.js";import{removeLeadingSlash as f}from"../../../utils/url/remove-leading-slash.js";function m(i,r,c){const t=a(i.toLowerCase()),e=r[t];if(e&&e.to)return{to:e.to,type:e.type};const n=s(t,c);if(n){let o=r[n]?.to;if(o){if(o.endsWith("*")){const d=n.split("*")[0],l=i.substring(d.length);o=o.replace(/\*$/,l)}return{...r[n],to:o}}}return null}function s(i,r){const c=f(i).split("/");let t=r;for(const e of c){if(!t[e])break;t=t[e]}return t["*"]||null}export{m as findRedirect};
1
+ import{removeLeadingSlash as f}from"../../../utils/url/remove-leading-slash.js";function p(e,i,o){e=e.toLowerCase();const t=i[e];if(t&&t.to)return{to:t.to,type:t.type};const r=a(e,o);if(r){let n=i[r]?.to;if(n){if(n.endsWith("*")){const c=r.split("*")[0],d=e.substring(c.length);n=n.replace(/\*$/,d)}return{...i[r],to:n}}}return null}function a(e,i){const o=f(e).split("/");let t=i;for(const r of o){if(!t[r])break;t=t[r]}return t["*"]||null}export{p as findRedirect};
@@ -1 +1 @@
1
- import i from"node:path";import{withPathPrefix as f}from"@redocly/theme/core/utils";import{PUBLIC_STATIC_FOLDER as p}from"../constants/common.js";import{isLocalLink as u}from"../../utils/path/is-local-link.js";import{copyStaticFile as F,FileNotFoundError as d}from"./fs.js";async function h(r,n,o){if(!u(r))return r;const t=r.startsWith("/")?i.posix.join(p,r):void 0;if(t&&await n.exists(t))return f(r);const c=r.startsWith("/")?r.slice(1):i.posix.join(i.dirname(o.fromFileRelativePath),r),m=await n.getFileInfo(c);if(!m)throw new d(`Cannot resolve asset path: ${r}`,r);return F(o.contentDir,m.realRelativePath,o.outdir)}export{h as resolveAssetPath};
1
+ import i from"node:path";import{withPathPrefix as f}from"@redocly/theme/core/utils";import{PUBLIC_STATIC_FOLDER as p}from"../constants/common.js";import{isLocalLink as u}from"../../utils/path/is-local-link.js";import{copyStaticFile as F,FileNotFoundError as d}from"./fs.js";async function h(r,n,o){if(!u(r))return r;const t=r.startsWith("/")?i.posix.join(p,r):void 0;if(t&&await n.exists(t))return f(r);const c=r.startsWith("/")?r.slice(1):i.posix.join(i.dirname(o.fromFileRelativePath),r),m=n.getFileInfo(c);if(!m)throw new d(`Cannot resolve asset path: ${r}`,r);return F(o.contentDir,m.realRelativePath,o.outdir)}export{h as resolveAssetPath};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Options for validating a string parameter
3
+ */
4
+ type IsValidSanitizedStringOptions = {
5
+ /**
6
+ * Regular expression pattern that the string must match
7
+ */
8
+ pattern: RegExp;
9
+ /**
10
+ * Maximum allowed length (default: 1000)
11
+ */
12
+ maxLength?: number;
13
+ /**
14
+ * Whether empty strings are allowed (default: false)
15
+ */
16
+ allowEmpty?: boolean;
17
+ };
18
+ /**
19
+ * Validates if a string parameter is valid according to the provided options.
20
+ * Can be undefined/null or a string.
21
+ * If it's a string, it must match the provided pattern and length constraints.
22
+ *
23
+ * @param value - The string parameter from query string (can be undefined, null, or string)
24
+ * @param options - Validation options including pattern, maxLength, etc.
25
+ * @returns true if the value is valid (or undefined/null), false otherwise
26
+ */
27
+ export declare function isValidSanitizedString(value: string | undefined | null, options: IsValidSanitizedStringOptions): boolean;
28
+ export {};
29
+ //# sourceMappingURL=validate-and-sanitize-string.d.ts.map
@@ -0,0 +1 @@
1
+ function i(t,e){if(t==null)return!0;if(typeof t!="string")return!1;const r=t.trim();if(r==="")return e.allowEmpty??!1;const n=e.maxLength??1e3;return!(r.length>n||!e.pattern.test(r))}export{i as isValidSanitizedString};
@@ -67,6 +67,11 @@ export declare function createMcpAuthorizationCode(params: {
67
67
  ttlSec?: number;
68
68
  }): Promise<string>;
69
69
  export declare function verifyMcpAuthorizationCode(code: string): Promise<McpAuthorizationCodePayload>;
70
+ export declare function createMcpSessionResource(sessionId: string | null | undefined): {
71
+ id: string;
72
+ object: "mcp_session";
73
+ uri: string;
74
+ };
70
75
  export declare function rewritePreviewAuthRedirectUri(redirectUri: string): string;
71
76
  export declare function parsePreviewBranch(origin: string): string | undefined;
72
77
  export declare function buildLoginUrl(idpLoginParams: AuthProviderLoginParams, redirectOrigin: string, redirectTo: string | null, inviteCode?: string): {
@@ -1,7 +1,7 @@
1
- import"../node-crypto-polyfill.js";import{DOMParser as b}from"@xmldom/xmldom";import{SignedXml as B}from"xml-crypto";import F from"xpath";import{deflateSync as J,inflateSync as q}from"fflate";import{createHash as H}from"crypto";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as W}from"@redocly/config";import{AUTH_URL as K,JWT_SECRET_KEY as _}from"../constants/common.js";import{getPathPrefix as Q,withPathPrefix as X}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as Y,REQUIRED_OIDC_SCOPES as D,ServerRoutes as P}from"../../constants/common.js";import{appendQueryParams as G}from"../../utils/url/append-query-params.js";import{logger as Z}from"../tools/notifiers/logger.js";import{randomString as ee}from"../utils/crypto/random-string.js";import{randomUUID as v}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as I,JwtTokenExpired as te}from"./jwt/types.js";import*as p from"./jwt/jwt.js";import{parseTeamClaimToArray as ne}from"../utils/index.js";import{arrayBufferToBase64 as ae,decodeBase64 as R,encodeBase64URL as re,urlSafeBase64 as j}from"./jwt/encode.js";import{formatSamlCertificate as oe}from"./utils/format-saml-certificate.js";function E(e){return e?.type===u.OIDC}function se(e){return e?.type===u.SAML2}async function Ve(e,t){if(E(t))return ie(e,t);if(se(t))return ce(e,t)}async function ie(e,t){const a=await V(e,t),n=new Set((t.scopes||[]).concat(D)),r=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:a.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(n).join(" "),extraParams:r,pkce:t.pkce}}function ce(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function ze(e,t,a,n,r={}){const o=new Set((n.scopes||[]).concat(D));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:n.clientId,scope:Array.from(o).join(" "),code:t,redirect_uri:N(a),grant_type:"authorization_code",...n.clientSecret?{client_secret:n.clientSecret}:{},...r}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function de(e,{authorizationEndpoint:t,clientId:a,responseType:n,scope:r,extraParams:o,idpId:s,pkce:i},m,A,w){if(!t||!a||!n||!r)return{loginUrl:void 0};const d=new URL(t),f=w?.redirectUriOverride??`${e}${X(P.OIDC_CALLBACK)}`,S={state:v(),idpId:s,redirectUri:f,redirectTo:m,branch:w?.branchOverride??ue(e),inviteCode:A,source:w?.sourceOverride??"portal"},h={};if(i){const l=j(ee(50)),g=j(H("sha256").update(l).digest("base64")),x="S256";d.searchParams.append("code_challenge",g),d.searchParams.append("code_challenge_method",x),h.code_verifier={value:l,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:Q()||"/"}}}d.searchParams.append("client_id",a),d.searchParams.append("scope",r),d.searchParams.append("response_type",n),d.searchParams.append("redirect_uri",N(f)),d.searchParams.append("state",re(JSON.stringify(S)));for(const l in o)o[l]!==void 0&&d.searchParams.append(l,o[l]);return{loginUrl:d.toString(),cookies:h}}function $e(e,t,a,n){const r=new URL(e);return r.searchParams.append("post_logout_redirect_uri",t),n&&r.searchParams.append("state",n),r.searchParams.append("id_token_hint",a),r.toString()}async function Be(e){const t=Math.floor(Date.now()/1e3),a=t+(e.ttlSec??600);return p.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,iat:t,exp:a},_)}async function Fe(e){const{header:t,payload:a}=p.decode(e),r=Object.values(I).includes(t.alg)?t.alg:I.HS256;if(await p.verify(e,_,r),!a||typeof a!="object")throw new Error("Malformed authorization code");const o=a;if((o.typ??o.type)!=="mcp_auth_code")throw new Error("Invalid authorization code type");const i=a;if(!i.client_id||!i.redirect_uri)throw new Error("Authorization code missing required claims");if(i.exp&&Date.now()>=i.exp*1e3)throw new Error("Authorization code expired");return i}function N(e){return e.match(/^https:\/\/preview-[^\.]+--/)?"https://previewauth--"+e.split("--")[1]:e.match(/^(https:\/\/[^\.]+)--[^\.]+\.preview\./)?e.replace(/^(https:\/\/[^\.]+?)--[^\.]+\.preview\./,"$1.previewauth."):e}function ue(e){return e.match(/^(https:\/\/[^\.]+)--([^\.]+)\.preview\./)?.[2]||void 0}function le(e){return e.type===u.OIDC}function me(e){return e.type===u.SAML2}function Je(e,t,a,n){return le(e)?de(t,e,a,n):me(e)?pe(t,e,a,n):{}}function pe(e,t,a,n){const o=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
1
+ import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";import{SignedXml as B}from"xml-crypto";import F from"xpath";import{deflateSync as J,inflateSync as q}from"fflate";import{createHash as H}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as d,DEFAULT_TEAM_CLAIM_NAME as K}from"@redocly/config";import{AUTH_URL as Q,JWT_SECRET_KEY as _}from"../constants/common.js";import{getPathPrefix as X,withPathPrefix as Y}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as G,REQUIRED_OIDC_SCOPES as D,ServerRoutes as P}from"../../constants/common.js";import{appendQueryParams as Z}from"../../utils/url/append-query-params.js";import{logger as ee}from"../tools/notifiers/logger.js";import{randomString as te}from"../utils/crypto/random-string.js";import{randomUUID as R}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as I,JwtTokenExpired as ne}from"./jwt/types.js";import*as p from"./jwt/jwt.js";import{parseTeamClaimToArray as re}from"../utils/index.js";import{arrayBufferToBase64 as oe,decodeBase64 as j,encodeBase64URL as ae,urlSafeBase64 as v}from"./jwt/encode.js";import{formatSamlCertificate as se}from"./utils/format-saml-certificate.js";function E(e){return e?.type===d.OIDC}function ie(e){return e?.type===d.SAML2}async function ze(e,t){if(E(t))return ce(e,t);if(ie(t))return ue(e,t)}async function ce(e,t){const n=await V(e,t),r=new Set((t.scopes||[]).concat(D)),o=t.authorizationRequestCustomParams||{};return{type:d.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function ue(e,t){return{type:d.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function Be(e,t,n,r,o={}){const a=new Set((r.scopes||[]).concat(D));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:r.clientId,scope:Array.from(a).join(" "),code:t,redirect_uri:N(n),grant_type:"authorization_code",...r.clientSecret?{client_secret:r.clientSecret}:{},...o}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function de(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:a,idpId:s,pkce:i},l,A,w){if(!t||!n||!r||!o)return{loginUrl:void 0};const u=new URL(t),f=w?.redirectUriOverride??`${e}${Y(P.OIDC_CALLBACK)}`,S={state:R(),idpId:s,redirectUri:f,redirectTo:l,branch:w?.branchOverride??me(e),inviteCode:A,source:w?.sourceOverride??"portal"},h={};if(i){const m=v(te(50)),g=v(H("sha256").update(m).digest("base64")),x="S256";u.searchParams.append("code_challenge",g),u.searchParams.append("code_challenge_method",x),h.code_verifier={value:m,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:X()||"/"}}}u.searchParams.append("client_id",n),u.searchParams.append("scope",o),u.searchParams.append("response_type",r),u.searchParams.append("redirect_uri",N(f)),u.searchParams.append("state",ae(JSON.stringify(S)));for(const m in a)a[m]!==void 0&&u.searchParams.append(m,a[m]);return{loginUrl:u.toString(),cookies:h}}function Fe(e,t,n,r){const o=new URL(e);return o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n),o.toString()}async function Je(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return p.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,iat:t,exp:n},_)}async function qe(e){const{header:t,payload:n}=p.decode(e),o=Object.values(I).includes(t.alg)?t.alg:I.HS256;if(await p.verify(e,_,o),!n||typeof n!="object")throw new Error("Malformed authorization code");const a=n;if((a.typ??a.type)!=="mcp_auth_code")throw new Error("Invalid authorization code type");const i=n;if(!i.client_id||!i.redirect_uri)throw new Error("Authorization code missing required claims");if(i.exp&&Date.now()>=i.exp*1e3)throw new Error("Authorization code expired");return i}function He(e){const t=e||W(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${n}`}}function N(e){return e.match(/^https:\/\/preview-[^\.]+--/)?"https://previewauth--"+e.split("--")[1]:e.match(/^(https:\/\/[^\.]+)--[^\.]+\.preview\./)?e.replace(/^(https:\/\/[^\.]+?)--[^\.]+\.preview\./,"$1.previewauth."):e}function me(e){return e.match(/^(https:\/\/[^\.]+)--([^\.]+)\.preview\./)?.[2]||void 0}function le(e){return e.type===d.OIDC}function pe(e){return e.type===d.SAML2}function We(e,t,n,r){return le(e)?de(t,e,n,r):pe(e)?fe(t,e,n,r):{}}function fe(e,t,n,r){const a=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
2
2
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
3
3
  Version="2.0"
4
- ID="_${v()}"
4
+ ID="_${R()}"
5
5
  IssueInstant="${new Date().toISOString()}"
6
6
  AssertionConsumerServiceURL="${e}${P.SAML_CALLBACK}"
7
7
  AttributeConsumingServiceIndex="0">
@@ -9,4 +9,4 @@ import"../node-crypto-polyfill.js";import{DOMParser as b}from"@xmldom/xmldom";im
9
9
  <samlp:NameIDPolicy
10
10
  AllowCreate="true"
11
11
  Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
12
- </samlp:AuthnRequest>`,s=fe(o);return{loginUrl:G(t.ssoUrl,{SAMLRequest:s,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:a,inviteCode:n,source:"portal"})})}}function fe(e){return ae(J(new TextEncoder().encode(e)).buffer)}function qe(e){const t=R(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const a=q(new Uint8Array(atob(e).split("").map(n=>n.charCodeAt(0))));return new TextDecoder().decode(a)}function He(e){try{return JSON.parse(R(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function We(e){const t=new b().parseFromString(e,"application/xml"),n=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,o=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],i=s&&s.nodeValue||void 0,m=c(t,"//*[local-name(.)='Audience']/text()")[0],A=m&&m.nodeValue||void 0,d=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",f=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],S=f&&f.nodeValue||"",h=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],l=h&&h.nodeValue||"",g=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=he(g),T={},k=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(k.length)for(const C of k){const O=c(C,"./@Name")[0];if(O.nodeValue){const U=c(C,"./*[local-name(.)='AttributeValue']/text()")[0];U?.nodeValue&&(T[O.nodeValue]=U.nodeValue)}}return{uid:S,success:n,expiresAt:x,issuerId:i,entityId:A,attrs:T,cert:d,nameFormat:l,destination:o}}function he(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),a=L(Date.now()),n=L(Date.now()+720*60*1e3);return t?t>a&&t<n?n:t:a}function L(e){return Math.floor(e/1e3)}const M={},y={jwks:{}};async function V(e,t){return M[e]||(M[e]=t.configurationUrl?await z(t.configurationUrl):t.configuration),M[e]}async function ye(e){for(const t of Object.keys(e)){const a=e[t];if(!E(a))continue;const n=await V(t,a);if(n.jwks_uri){const r=await z(n.jwks_uri);for(const o of r.keys)y.jwks[o.kid]={...o,idpId:t}}}}async function z(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function Ke(e){return fetch(`${K}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function Qe(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(n=>we(t.hostname,n))}function we(e,t){return e===t||e.endsWith(`.${t}`)}async function Xe(e,t){const a=new b().parseFromString(e),n=c(a,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!n)throw new Error("Cannot find Signature in the SAML response");const r=oe(t),o=new B({publicCert:r});o.loadSignature(n);try{return o.checkSignature(e)}catch{return!1}}function Ye(e,t,a,n){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=a["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let r;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(r=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(r=e);const o=a["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=o?.match(/.+@.+/);return r=r||a["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?o:void 0),r=r?.toLowerCase(),{sub:e,given_name:a["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:a["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:a["http://schemas.microsoft.com/identity/claims/displayname"]||o,email:r,email_verified:!0,teams:n?ne(a[n]):[]}}function $(e,t={}){return e.map(a=>t[a]||a)}async function Ge(e,t){if(!t)return{};const a=t.authorization;if(!a)return{};try{const n=p.decode(a);if(n.header.alg===I.RS256){y.jwks[n.header.kid]===void 0&&await ye(e);const m=y.jwks[n.header.kid];if(!m)return y.jwks[n.header.kid]=null,{};await p.verify(a,m,n.header.alg)}else await p.verify(a,_,n.header.alg);const r=n.payload.idpId||y.jwks[n.header.kid]?.idpId,o=e[r]||{},s=ge(o),i=Se(o);return{...n.payload,email:n.payload.email?.toLowerCase(),idpId:r,teams:Array.from(new Set([...$(n.payload.teams||[],i),..."defaultTeams"in o&&o.defaultTeams||[],...$("teamsClaimName"in o&&n.payload[s||""]||[],i),Y])),name:Ae(n.payload),isAuthenticated:!0,idpAccessToken:t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:a}}catch(n){n instanceof te||Z.error("Malformed JWT token: %s",n.message)}return{}}function Ae(e){return e.name||e.given_name||e.email}function Se(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function ge(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return W}}function c(e,t){return F.select(t,e)||[]}export{Je as buildLoginUrl,de as buildOidcLoginUrl,$e as buildOidcLogoutUrl,pe as buildSAML2LoginUrl,Be as createMcpAuthorizationCode,qe as decodeSamlResponse,fe as encodeSAML2,Ye as extractUserClaims,Ve as getAuthProviderLoginParams,ie as getOidcLoginParams,V as getOidcMetadata,Ke as getRedoclyTokenPayload,ce as getSaml2LoginParams,Ge as getUserParamsFromCookies,Ae as getUsernameFromPayload,E as isOidcProviderConfig,Qe as isRedoclySso,se as isSaml2ProviderConfig,ze as oidcExchangeCodeForToken,He as parseOidcState,ue as parsePreviewBranch,We as parseSamlResponse,N as rewritePreviewAuthRedirectUri,Fe as verifyMcpAuthorizationCode,Xe as verifySAMLResponse};
12
+ </samlp:AuthnRequest>`,s=he(a);return{loginUrl:Z(t.ssoUrl,{SAMLRequest:s,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal"})})}}function he(e){return oe(J(new TextEncoder().encode(e)).buffer)}function Ke(e){const t=j(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=q(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function Qe(e){try{return JSON.parse(j(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function Xe(e){const t=new U().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,a=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],i=s&&s.nodeValue||void 0,l=c(t,"//*[local-name(.)='Audience']/text()")[0],A=l&&l.nodeValue||void 0,u=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",f=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],S=f&&f.nodeValue||"",h=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],m=h&&h.nodeValue||"",g=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=ye(g),T={},k=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(k.length)for(const C of k){const O=c(C,"./@Name")[0];if(O.nodeValue){const b=c(C,"./*[local-name(.)='AttributeValue']/text()")[0];b?.nodeValue&&(T[O.nodeValue]=b.nodeValue)}}return{uid:S,success:r,expiresAt:x,issuerId:i,entityId:A,attrs:T,cert:u,nameFormat:m,destination:a}}function ye(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),n=L(Date.now()),r=L(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function L(e){return Math.floor(e/1e3)}const M={},y={jwks:{}};async function V(e,t){return M[e]||(M[e]=t.configurationUrl?await $(t.configurationUrl):t.configuration),M[e]}async function we(e){for(const t of Object.keys(e)){const n=e[t];if(!E(n))continue;const r=await V(t,n);if(r.jwks_uri){const o=await $(r.jwks_uri);for(const a of o.keys)y.jwks[a.kid]={...a,idpId:t}}}}async function $(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function Ye(e){return fetch(`${Q}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function Ge(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Ae(t.hostname,r))}function Ae(e,t){return e===t||e.endsWith(`.${t}`)}async function Ze(e,t){const n=new U().parseFromString(e),r=c(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=se(t),a=new B({publicCert:o});a.loadSignature(r);try{return a.checkSignature(e)}catch{return!1}}function et(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(o=e);const a=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=a?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?a:void 0),o=o?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||a,email:o,email_verified:!0,teams:r?re(n[r]):[]}}function z(e,t={}){return e.map(n=>t[n]||n)}async function tt(e,t){if(!t)return{};const n=t.authorization;if(!n)return{};try{const r=p.decode(n);if(r.header.alg===I.RS256){y.jwks[r.header.kid]===void 0&&await we(e);const l=y.jwks[r.header.kid];if(!l)return y.jwks[r.header.kid]=null,{};await p.verify(n,l,r.header.alg)}else await p.verify(n,_,r.header.alg);const o=r.payload.idpId||y.jwks[r.header.kid]?.idpId,a=e[o]||{},s=xe(a),i=ge(a);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...z(r.payload.teams||[],i),..."defaultTeams"in a&&a.defaultTeams||[],...z("teamsClaimName"in a&&r.payload[s||""]||[],i),G])),name:Se(r.payload),isAuthenticated:!0,idpAccessToken:t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:n}}catch(r){r instanceof ne||ee.error("Malformed JWT token: %s",r.message)}return{}}function Se(e){return e.name||e.given_name||e.email}function ge(e){switch(e.type){case d.SAML2:return e.teamsAttributeMap;case d.OIDC:return e.teamsClaimMap;default:return}}function xe(e){switch(e.type){case d.SAML2:return e.teamsAttributeName;case d.OIDC:return e.teamsClaimName;default:return K}}function c(e,t){return F.select(t,e)||[]}export{We as buildLoginUrl,de as buildOidcLoginUrl,Fe as buildOidcLogoutUrl,fe as buildSAML2LoginUrl,Je as createMcpAuthorizationCode,He as createMcpSessionResource,Ke as decodeSamlResponse,he as encodeSAML2,et as extractUserClaims,ze as getAuthProviderLoginParams,ce as getOidcLoginParams,V as getOidcMetadata,Ye as getRedoclyTokenPayload,ue as getSaml2LoginParams,tt as getUserParamsFromCookies,Se as getUsernameFromPayload,E as isOidcProviderConfig,Ge as isRedoclySso,ie as isSaml2ProviderConfig,Be as oidcExchangeCodeForToken,Qe as parseOidcState,me as parsePreviewBranch,Xe as parseSamlResponse,N as rewritePreviewAuthRedirectUri,qe as verifyMcpAuthorizationCode,Ze as verifySAMLResponse};
@@ -1,2 +1,2 @@
1
- import*as E from"node:http";import{AsyncLocalStorage as k}from"node:async_hooks";import{Readable as u,Stream as v}from"node:stream";import{pipeline as A}from"node:stream/promises";import{ReadableStream as H}from"node:stream/web";import{getPathPrefix as C}from"@redocly/theme/core/utils";import{ALLOWED_CORS_ORIGINS as L}from"../../constants/common.js";import{logger as l}from"../tools/notifiers/logger.js";import{shutdowner as x}from"../tools/shutdowner.js";import{getClientIp as I}from"./utils/get-client-ip.js";import{normalizeIpAddress as T}from"./utils.js";const S=new k;globalThis.redoclyCookieStorage=S;function z(n,f=4e3){return new Promise((m,p)=>{const d=E.createServer(async(r,t)=>{const y=r.headers.cookie||"";S.run(y,async()=>{let s;try{const e=new URL(r.url||"",`http://${r.headers.host}`),o=[],c=r.rawHeaders.length;for(let i=0;i<c;i+=2)o.push([r.rawHeaders[i],r.rawHeaders[i+1]]);const a={headers:o,method:r.method};r.method==="GET"||r.method==="HEAD"||(a.body=u.toWeb(r),a.duplex="half");const h=new Request(e.toString(),a),R=I(h)||"127.0.0.1",b=h?.headers?.get("x-forwarded-host")||T(r.socket.remoteAddress);h.context={remoteAddr:{hostname:b,port:r.socket.localPort,ipAddress:R},url:e},s=await n.fetch(h)}catch(e){s=new Response(null,{status:500}),e instanceof Error&&(e.name==="TimeoutError"||e.constructor.name==="TimeoutError"?s=new Response(null,{status:504}):e.code==="ERR_INVALID_URL"&&(s=new Response("Invalid URL",{status:404})),l.error(`Error while handling request: %s
2
- %s`,e.message,e.stack))}const w=r.headers.origin;w&&L.some(e=>w===e)&&(t.setHeader("Access-Control-Allow-Origin",w),t.setHeader("Access-Control-Allow-Credentials","true"));for(const[e,o]of s.headers||[])try{e==="set-cookie"?t.setHeader(e,s.headers.getSetCookie(e)):t.setHeader(e,o)}catch(c){l.error(`Error while setting header: ${c?.message}`)}if(t.setHeader("Vary","Cookie"),t.statusCode=s.status||500,s.body)try{if(P(s)){const e=new v.Transform({transform(o,c,a){if(t.closed||t.destroyed)return a();t.write(o,c)?process.nextTick(a):t.once("drain",a)},flush(o){if(t.closed||t.destroyed)return o();t.end(),o()}});await A(s.body instanceof H?u.fromWeb(s.body):s.body,e)}else{const e=await s.text();t.setHeader("Content-Length",Buffer.byteLength(e)),t.end(e)}}catch(e){console.error(e);const o=e instanceof Error?e:new Error("unknown error",{cause:e});t.destroy(o)}else t.end()})});x.registerShutdownCallback(()=>new Promise(r=>{l.verbose("Shutting down http server"),d.close(()=>{l.verbose("Http server shut down"),r()})})),d.listen(parseInt(String(f),10)).on("listening",()=>{l.logSticky("server",` \u{1F310} Preview URL: http://127.0.0.1:${f}${C()}`),m(d)}).on("error",p)})}function P(n){const f=n.headers.get("content-type")||"",m=n.headers.get("x-accel-buffering")||"",p=n.headers.get("content-encoding"),d=n.headers.get("content-length"),r=n.headers.get("transfer-encoding");return p||r||d||/^no$/i.test(m)||!/^(text\/(?!event-stream\b))/i.test(f)}export{z as startHttpServer};
1
+ import*as E from"node:http";import{AsyncLocalStorage as A}from"node:async_hooks";import{Readable as u,Stream as H}from"node:stream";import{pipeline as k}from"node:stream/promises";import{ReadableStream as C}from"node:stream/web";import{getPathPrefix as S}from"@redocly/theme/core/utils";import{ALLOWED_CORS_ORIGINS as L}from"../../constants/common.js";import{logger as d}from"../tools/notifiers/logger.js";import{shutdowner as x}from"../tools/shutdowner.js";import{isDevelopMode as I}from"../utils/envs/is-develop-mode.js";import{getClientIp as T}from"./utils/get-client-ip.js";import{normalizeIpAddress as $}from"./utils.js";const y=new A;globalThis.redoclyCookieStorage=y;function B(n,c=4e3){return new Promise((m,p)=>{const l=E.createServer(async(r,t)=>{const R=r.headers.cookie||"";y.run(R,async()=>{let s;try{const e=new URL(r.url||"",`http://${r.headers.host}`),o=[],f=r.rawHeaders.length;for(let i=0;i<f;i+=2)o.push([r.rawHeaders[i],r.rawHeaders[i+1]]);const a={headers:o,method:r.method};r.method==="GET"||r.method==="HEAD"||(a.body=u.toWeb(r),a.duplex="half");const h=new Request(e.toString(),a),b=T(h)||"127.0.0.1",v=h?.headers?.get("x-forwarded-host")||$(r.socket.remoteAddress);h.context={remoteAddr:{hostname:v,port:r.socket.localPort,ipAddress:b},url:e},s=await n.fetch(h)}catch(e){s=new Response(null,{status:500}),e instanceof Error&&(e.name==="TimeoutError"||e.constructor.name==="TimeoutError"?s=new Response(null,{status:504}):e.code==="ERR_INVALID_URL"&&(s=new Response("Invalid URL",{status:404})),d.error(`Error while handling request: %s
2
+ %s`,e.message,e.stack))}const w=r.headers.origin;w&&L.some(e=>w===e)&&(t.setHeader("Access-Control-Allow-Origin",w),t.setHeader("Access-Control-Allow-Credentials","true"));for(const[e,o]of s.headers||[])try{e==="set-cookie"?t.setHeader(e,s.headers.getSetCookie(e)):t.setHeader(e,o)}catch(f){d.error(`Error while setting header: ${f?.message}`)}if(t.setHeader("Vary","Cookie"),t.statusCode=s.status||500,s.body)try{if(P(s)){const e=new H.Transform({transform(o,f,a){if(t.closed||t.destroyed)return a();t.write(o,f)?process.nextTick(a):t.once("drain",a)},flush(o){if(t.closed||t.destroyed)return o();t.end(),o()}});await k(s.body instanceof C?u.fromWeb(s.body):s.body,e)}else{const e=await s.text();t.setHeader("Content-Length",Buffer.byteLength(e)),t.end(e)}}catch(e){console.error(e);const o=e instanceof Error?e:new Error("unknown error",{cause:e});t.destroy(o)}else t.end()})});x.registerShutdownCallback(()=>new Promise(r=>{d.verbose("Shutting down http server"),l.close(()=>{d.verbose("Http server shut down"),r()})})),l.listen(parseInt(String(c),10)).on("listening",()=>{I()?d.logInFooter("server",` \u{1F310} Preview URL: http://127.0.0.1:${c}${S()}`):d.logInFooter("server",`Server started at: http://127.0.0.1:${c}${S()}`),m(l)}).on("error",p)})}function P(n){const c=n.headers.get("content-type")||"",m=n.headers.get("x-accel-buffering")||"",p=n.headers.get("content-encoding"),l=n.headers.get("content-length"),r=n.headers.get("transfer-encoding");return p||r||l||/^no$/i.test(m)||!/^(text\/(?!event-stream\b))/i.test(c)}export{B as startHttpServer};
@@ -1 +1 @@
1
- import{Logger as m}from"../../tools/notifiers/logger.js";import{getClientIp as u}from"../utils/get-client-ip.js";function c(){return async(e,r)=>{const t=new m(d(e));if(e.set("logger",t),process.env.NODE_ENV!=="production")return await r();const s=t.startTiming();try{await r(),t.updateContext(a(e)),t.httpTime(s)}catch(o){throw t.updateContext(a(e)),t.httpTime(s),o}}}const d=e=>{const r=e.req.method,t=new URL(e.req.url).pathname,s=e.req.header("user-agent")||void 0,o=u(e.req.raw),i=e.req.raw.context.remoteAddr,n=e.get("auth");return{email:n?.claims?.email,subject:n?.claims?.sub,teams:n?.teams,ipAddress:o||i?.hostname,userAgent:s,method:r,pathname:t}},a=e=>({statusCode:e.res.status});export{c as loggerMiddleware};
1
+ import{Logger as m}from"../../tools/notifiers/logger.js";import{getClientIp as u}from"../utils/get-client-ip.js";import{LogLevel as d}from"../../tools/notifiers/formatter.js";function c(){return async(e,r)=>{const t=new m({context:g(e)});if(e.set("logger",t),!t.shouldLog(d.HTTP))return await r();const s=t.startTiming();try{await r(),t.updateContext(a(e)),t.httpTime(s)}catch(o){throw t.updateContext(a(e)),t.httpTime(s),o}}}const g=e=>{const r=e.req.method,t=new URL(e.req.url).pathname,s=e.req.header("user-agent")||void 0,o=u(e.req.raw),i=e.req.raw.context.remoteAddr,n=e.get("auth");return{email:n?.claims?.email,subject:n?.claims?.sub,teams:n?.teams,ipAddress:o||i?.hostname,userAgent:s,method:r,pathname:t}},a=e=>({statusCode:e.res.status});export{c as loggerMiddleware};
@@ -1 +1 @@
1
- import{setCookie as C,deleteCookie as T}from"hono/cookie";import{AuthProviderType as z}from"@redocly/config";import{withPathPrefix as k,getPathPrefix as L}from"@redocly/theme/core/utils";import{compareURIs as K}from"../../../utils/url/compare-uris.js";import{ensureArray as S}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as U,JWT_SECRET_KEY as A,ORG_SLUG as V}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as q,ServerRoutes as O}from"../../../constants/common.js";import{sanitizeRedirectPathname as b}from"../../../utils/url/sanitize-redirect-pathname.js";import{getAuthProviderLoginParams as J,isOidcProviderConfig as $,isSaml2ProviderConfig as G,oidcExchangeCodeForToken as X,buildLoginUrl as W,decodeSamlResponse as Y,extractUserClaims as Q,parseSamlResponse as Z,parseOidcState as x,verifySAMLResponse as ee,getUsernameFromPayload as re,buildOidcLogoutUrl as oe,getOidcMetadata as E,getRedoclyTokenPayload as ne,isRedoclySso as te,rewritePreviewAuthRedirectUri as ie,parsePreviewBranch as B,buildOidcLoginUrl as se}from"../auth.js";import*as P from"../jwt/jwt.js";import{AlgorithmTypes as ae}from"../jwt/types.js";import{handleErrorPageRender as de}from"../utils.js";import{encodeBase64URL as ce}from"../jwt/encode.js";async function _e(d){if(process.env.NODE_ENV==="production")return d.newResponse(null,404,{});const{password:e,...r}=await d.req.json(),a=await P.sign({...r,name:r.username||r.email||"Unknown"},A);return C(d,"authorization",a,{path:L()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),d.newResponse(null,200,{})}function Ie(){return async d=>{const e=d.get("logger"),r=encodeURIComponent(d.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${O.LOGIN}/?error=${encodeURIComponent(r)}`;return d.newResponse(null,301,{Location:a})}}function De(d){return async e=>{const r=e.get("logger"),a=d.getConfig().ssoDirect,n=x(e.req.query("state")),g=n.idpId,s=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(O.MCP_CALLBACK),t=a?.[g];if(!$(t))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const u=await E(g,t);if(a&&!u.token_endpoint){const f="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${f}`),e.text(f,500)}try{const f=u.token_endpoint,R=e.req.query("code");if(e.req.query("error"))return de(e,d,{slug:"/"},403,"403OIDC");if(!R){const w="Code is expected but not present";return r.error(`OIDC login error: ${w}`),new Response(`Forbidden: ${w}`,{status:403})}const h=e.req.header("x-forwarded-host"),y=e.req.header("x-forwarded-proto")||"https",l=s?n.redirectUri:new URL(k(O.OIDC_CALLBACK),h?`${y}://${h}`:e.req.url).toString(),_=e.get("cookies")?.code_verifier,c=await X(f,R,l,t,{...t.tokenRequestCustomParams,..._?{code_verifier:_}:{}});if(c.error)return r.error(`Error from OIDC provider: "${c.error}"`),e.text(`Forbidden: ${c.error_description||c.error}`,403);if(!c?.id_token){const w="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${w}`),new Response(`Forbidden: ${w}`,{status:403})}const{payload:m,header:o}=P.decode(c.id_token),N=o.alg===ae.RS256;if(t.audience?.length&&![...S(m.aud||[]),...S(m[U]||[])].some(I=>t.audience?.includes(I))){const I="No valid audience found in id_token";return r.error(`OIDC login error: ${I}`),new Response(`Forbidden: ${I}`)}const M=N?c.id_token:await P.sign({...m,idpId:g},A);re(m)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const D=t?.tokenExpirationTime?Date.now()+t.tokenExpirationTime*1e3:m.exp*1e3||Date.now()+q*1e3;if(t.introspectEndpoint){const w=await fetch(t.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:c.access_token})});if(w.ok){const v=(await w.json()).ext?.federatedIdentity;v&&(C(e,"federated_access_token",v.access_token||"",{path:L()||"/",httpOnly:!1,expires:new Date(D)}),C(e,"federated_id_token",v.id_token||"",{path:L()||"/",httpOnly:!1,expires:new Date(D)}))}else r.warn(`OIDC introspect error: ${w.statusText}`)}if(C(e,"authorization",M,{path:L()||"/",httpOnly:!0,expires:new Date(D)}),M!==c.id_token&&C(e,"idp_id_token",c.id_token||"",{path:L()||"/",httpOnly:!0,expires:new Date(D)}),C(e,"idp_access_token",c.access_token||"",{path:L()||"/",httpOnly:!0,expires:new Date(D)}),T(e,"code_verifier",{path:L()||"/"}),s&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(O.MCP_CALLBACK)){const w=e.req.url.split("?")[0].replace(O.OIDC_CALLBACK,""),I=k(n.redirectTo),v=`${w}${I}`;return e.newResponse(null,302,{Location:v})}const j=typeof n.redirectTo=="string"?n.redirectTo:void 0;let H=b(new URL(j||"/",e.req.url).pathname);const F=e.newResponse(null,302,{Location:H});return r.updateContext({email:m.email,subject:m.sub}),r.info("OIDC login successful"),F}catch(f){if(r.error(`OIDC login error: ${f.message}`),f.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const i="Something went wrong";return r.error(`OIDC login error: ${i}`),e.text(i,500)}}function ve(d){return async e=>{const r=e.get("logger"),n=e.get("auth").claims?.idpId,s=d.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(s)||T(e,"authorization",{path:L()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let t;if($(s)){const u=(await E(n,s)).end_session_endpoint;if(u){const i=new URL(e.req.url),f=e.req.header("x-forwarded-proto")||i.protocol.slice(0,-1)||"https",R=e.req.header("x-forwarded-host")||i.host,p=`${f}://${R}`,h=B(p),y=h?ce(JSON.stringify({branch:B(p)})):void 0,l=h?`${ie(p)}/_auth/logout`:p;t=oe(u,l,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return r.info("Logout successful"),T(e,"authorization",{path:L()||"/"}),e.newResponse(null,302,{Location:t||k("/")})}}function Oe(d){return async e=>{const r=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,g=(s,t,u)=>s&&t?`${s} ${t.charAt(0)}`:u;try{if(!n)throw new Error("BH_API_URL is not set");const s=d.getConfig().ssoDirect;if(!s||!Object.keys(s).length)return r.warn("Invite no sso configured to handle"),e.redirect(k("/"));const t=await fetch(`${n}/user-invites/public/${a}`);if(!t.ok)return t.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(k("/"))):(r.error("Invite error",await t.text()),e.redirect(k("/")));const u=await t.json(),i=new URL(k("/invite"),e.req.url);return i.searchParams.set("code",a),i.searchParams.set("org",u.organization.name),i.searchParams.set("invitedBy",g(u.invitedBy.firstName,u.invitedBy.lastName,u.invitedBy.name)),e.newResponse(null,302,{Location:i.toString()})}catch(s){return r.error("Error processing invite",{error:s,inviteCode:a}),e.text(s.message||"Failed to process invite",400)}}}function Pe(d){return async e=>{const r=e.get("logger"),a=d.getConfig().ssoDirect,n=new URL(e.req.url),g=e.req.query("inviteCode"),s=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",t=e.req.header("x-forwarded-host")||n.host,u=`${s}://${t}`;let i=n.searchParams.get("idpId");const f=n.searchParams.get("redirectTo"),R=Object.keys(a||{})[0];i=i||R;const p=n.searchParams.get("mcp_redirect_uri"),h=!!p;if(!a?.[i]){const o="Invalid idpId";return r.error(`IdP login error: ${o}`),e.text(`Forbidden: ${o}`,403)}const l=i&&a?await J(i,a[i]):void 0,_={};for(const o of Object.keys(l?.extraParams||{}))_[o]=n.searchParams.get(o)||l?.extraParams?.[o]||void 0;let c,m={};if(h&&p&&l&&l.type===z.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${p}`);const o=se("",{...l,extraParams:_},f,g,{redirectUriOverride:p,sourceOverride:"mcp",branchOverride:void 0});c=o.loginUrl,m=o.cookies||{}}else if(l){const o=W({...l,extraParams:_},u,f,g);c=o.loginUrl,m=o.cookies||{}}return Object.keys(m).forEach(o=>{C(e,o,m[o].value,m[o].options)}),r.info(`IdP login initiated for ID '${i}'`),e.newResponse(null,302,{Location:c||new URL(e.req.url).pathname})}}function Ae(d){return async e=>{const r=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),g=a.get("RelayState");if(typeof n!="string"||typeof g!="string"){const o="SAMLResponse is required";return r.error(`SAML2 login error: ${o}`),e.text(`Bad request: ${o}`,400)}const s=Y(n),{success:t,uid:u,nameFormat:i,attrs:f,issuerId:R,expiresAt:p}=Z(s),{idpId:h,redirectTo:y}=JSON.parse(g);if(!t){const o="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!p||Math.ceil(Date.now()/1e3)>=p){const o="SAML2 Token Expired";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const l=d.getConfig().ssoDirect?.[h];if(!l||!G(l)){const o="Cannot find valid IdP";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!(l.issuerId&&R&&K(l.issuerId,R))){const o="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!await ee(s,l.x509PublicCert)){const o="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const c=Q(u,i,f,l.teamsAttributeName);if(!c.sub){const o="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}if(!c.email){const o="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}const m=await P.sign({...c,idpId:h},A);return C(e,"authorization",m,{path:L()||"/",httpOnly:!0,expires:new Date(p*1e3)}),r.updateContext({email:c.email,subject:c.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function $e(d){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=k(b(a.pathname)),g=d.getConfig().ssoDirect,s=Object.entries(g||{}).find(([,y])=>$(y)&&te(y));if(!(g&&s))return e.newResponse(null,302,{Location:n});const u=e.req.query("token"),i=u&&await ne(u);if(!i)return e.newResponse(null,302,{Location:n});if(!S(i[U]||[]).some(y=>y===V))return e.newResponse(null,302,{Location:n});const p=await P.sign({...i,idpId:s?.at(0)},A),h=Date.now()+q*1e3;return C(e,"authorization",p,{path:L()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{_e as authorizeHandler,Pe as idpLoginHandler,Oe as inviteHandler,ve as logoutHandler,De as oidcCallbackHandler,Ie as redoclyLoginCallbackHandler,$e as redoclyTokenLoginHandler,Ae as samlCallbackHandler};
1
+ import{setCookie as L,deleteCookie as q}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as M,getPathPrefix as C}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as U}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(s){if(process.env.NODE_ENV==="production")return s.newResponse(null,404,{});const{password:e,...o}=await s.req.json(),a=await S.sign({...o,name:o.username||o.email||"Unknown"},v);return L(s,"authorization",a,{path:C()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),s.newResponse(null,200,{})}function Pe(){return async s=>{const e=s.get("logger"),o=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${o}`);const a=`${D.LOGIN}/?error=${encodeURIComponent(o)}`;return s.newResponse(null,301,{Location:a})}}function j(s){if(!s||!s.includes(D.MCP_CALLBACK))return null;try{const e=s.split("/"),o=e[e.length-1];if(o){const a=Buffer.from(o,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function Se(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?j(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,i=a?.[f];if(!$(i))return o.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return o.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,s,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(M(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),_=e.get("cookies")?.code_verifier,u=await Z(p,l,A,i,{...i.tokenRequestCustomParams,..._?{code_verifier:_}:{}});if(u.error)return o.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:r,header:T}=S.decode(u.id_token),H=T.alg===ue.RS256;if(i.audience?.length&&![...U(r.aud||[]),...U(r[E]||[])].some(R=>i.audience?.includes(R))){const R="No valid audience found in id_token";return o.error(`OIDC login error: ${R}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:R,error_details:null}),new Response(`Forbidden: ${R}`)}const b=H?u.id_token:await S.sign({...r,idpId:f},v);ie(r)||o.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const O=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:r.exp*1e3||Date.now()+F*1e3;if(i.introspectEndpoint){const h=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}))}else o.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:C()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),q(e,"code_verifier",{path:C()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),R=M(n.redirectTo),P=`${h}${R}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return o.updateContext({email:r.email,subject:r.sub}),o.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(o.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return o.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return o.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(s){return async e=>{const o=e.get("logger"),n=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(t)||q(e,"authorization",{path:C()||"/"}),o.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const i=(await z(n,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:l;c=se(i,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return o.info("Logout successful"),q(e,"authorization",{path:C()||"/"}),e.newResponse(null,302,{Location:c||M("/")})}}function $e(s){return async e=>{const o=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!n)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return o.warn("Invite no sso configured to handle"),e.redirect(M("/"));const c=await fetch(`${n}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(o.warn(`Invite ${a} not found redirect to homepage`),e.redirect(M("/"))):(o.error("Invite error",await c.text()),e.redirect(M("/")));const i=await c.json(),d=new URL(M("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",f(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return o.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Te(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,i=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const r="Invalid idpId";if(o.error(`IdP login error: ${r}`),m){const T=j(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(T),error:r,error_details:null})}return e.text(`Forbidden: ${r}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const r of Object.keys(g?.extraParams||{}))A[r]=n.searchParams.get(r)||g?.extraParams?.[r]||void 0;let _,u={};if(m&&l&&g&&g.type===G.OIDC){o.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const r=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});_=r.loginUrl,u=r.cookies||{}}else if(g){const r=x({...g,extraParams:A},i,w,f);_=r.loginUrl,u=r.cookies||{}}return Object.keys(u).forEach(r=>{L(e,r,u[r].value,u[r].options)}),o.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:_||new URL(e.req.url).pathname})}}function qe(s){return async e=>{const o=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const r="SAMLResponse is required";return o.error(`SAML2 login error: ${r}`),e.text(`Bad request: ${r}`,400)}const t=ee(n),{success:c,uid:i,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);if(!c){const r="SAML2 assertion is not successful";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const r="SAML2 Token Expired";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const g=s.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const r="Cannot find valid IdP";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){const r="IssuerID is misconfigured or untrusted assertions issuer received";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!await te(t,g.x509PublicCert)){const r="SAMLResponse signature invalid";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const _=re(i,d,w,g.teamsAttributeName);if(!_.sub){const r="The provider did not return a valid user identity.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}if(!_.email){const r="The provider did not return a valid user email.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}const u=await S.sign({..._,idpId:m},v);return L(e,"authorization",u,{path:C()||"/",httpOnly:!0,expires:new Date(l*1e3)}),o.updateContext({email:_.email,subject:_.sub}),o.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function Ue(s){return async e=>{const o=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=M(B(a.pathname)),f=s.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const i=e.req.query("token"),d=i&&await ae(i);if(!d)return e.newResponse(null,302,{Location:n});if(!U(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:C()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),o.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,$e as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,Pe as redoclyLoginCallbackHandler,Ue as redoclyTokenLoginHandler,qe as samlCallbackHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as n}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as m}from"../../../../constants/common.js";import{ALLOWED_CATALOG_QUERY_PARAMS as l}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as u}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as y}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as d}from"../../../providers/database/pagination/schemas.js";import{ENTITY_RELATION_FROM_DATABASE as c}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";const f=["id","key","title","type","summary","source","sourceFile","createdAt","updatedAt"],g=async({catalogEntitiesService:i,ctx:e})=>n("catalog_entities.bff.related_entities.get_related_entities",async r=>{try{const t=e.req.param(m);if(!t)return r?.error(new Error("Entity key is required")),e.json({message:"Entity key is required"},400);r?.setAttribute("pathParams",JSON.stringify({entityKey:t}));const a=e.req.query();r?.setAttribute("queryParams",JSON.stringify(u(a,l)));const o=d(f,c).parse(a),s=await i.getRelatedEntities(t,o);return r?.setAttribute("relatedEntitiesCount",s.items.length),e.json(s)}catch(t){return console.error(t),r?.error(t),e.json({message:t.message},500)}});function q(i){return async e=>n("catalog_entities.bff.related_entities",async r=>{r?.setAttribute("method",e.req.method);const t=await y.getInstance({baseDbDir:i.serverOutDir});return g({catalogEntitiesService:t,ctx:e})})}export{q as bffCatalogRelatedEntitiesHandler};
1
+ import{telemetryTraceStep as n}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as m}from"../../../../constants/common.js";import{ALLOWED_CATALOG_QUERY_PARAMS as l}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as u}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as y}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as d}from"../../../providers/database/pagination/schemas.js";import{ENTITY_RELATION_FROM_DATABASE as c}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";const f=["id","key","title","type","summary","source","sourceFile","createdAt","updatedAt","version","revision"],g=async({catalogEntitiesService:i,ctx:t})=>n("catalog_entities.bff.related_entities.get_related_entities",async r=>{try{const e=t.req.param(m);if(!e)return r?.error(new Error("Entity key is required")),t.json({message:"Entity key is required"},400);r?.setAttribute("pathParams",JSON.stringify({entityKey:e}));const s=t.req.query();r?.setAttribute("queryParams",JSON.stringify(u(s,l)));const o=d(f,c).parse(s),a=await i.getRelatedEntities(e,o);return r?.setAttribute("relatedEntitiesCount",a.items.length),t.json(a)}catch(e){return console.error(e),r?.error(e),t.json({message:e.message},500)}});function q(i){return async t=>n("catalog_entities.bff.related_entities",async r=>{r?.setAttribute("method",t.req.method);const e=await y.getInstance({baseDbDir:i.serverOutDir});return g({catalogEntitiesService:e,ctx:t})})}export{q as bffCatalogRelatedEntitiesHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as o}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as u}from"../../../../constants/common.js";import{ALLOWED_CATALOG_QUERY_PARAMS as g}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as f}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as c}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as y}from"../../../providers/database/pagination/schemas.js";const m=["type","key","title","summary","tags","metadata","metadata.*","git","contact","links","id","source","sourceFile","version","revision","createdAt","updatedAt","domains","owners"],l=async({catalogEntitiesService:i,ctx:t})=>o("catalog_entities.bff.get_entities",async e=>{const r=t.req.query();e?.setAttribute("queryParams",JSON.stringify(f(r,g)));const n=y(m).parse(r),a=await i.getEntitiesWithRelations(n);return e?.setAttribute("entitiesCount",a.items.length),t.json(a)}),d=async({catalogEntitiesService:i,ctx:t})=>o("catalog_entities.bff.get_entity",async e=>{const r=t.req.param(u);if(!r)return e?.error(new Error("Entity key is required")),t.json({message:"Entity key is required"},400);const n=t.req.query();e?.setAttribute("queryParams",JSON.stringify(n)),e?.setAttribute("pathParams",JSON.stringify({entityKey:r}));const a=y(m).parse(n),s=await i.getEntityWithRelationsByKey(r,a);return s?(e?.setAttribute("entity",JSON.stringify(s)),t.json(s)):(e?.error(new Error("Entity not found")),t.json({message:"Entity not found"},404))});function _(i){return async t=>o("catalog_entities.bff",async e=>{e?.setAttribute("method",t.req.method);const r=await c.getInstance({baseDbDir:i.serverOutDir});return t.req.param("entityKey")?d({catalogEntitiesService:r,ctx:t}):l({catalogEntitiesService:r,ctx:t})})}export{_ as bffCatalogHandler};
1
+ import{telemetryTraceStep as o}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_KEY as u}from"../../../../constants/common.js";import{ALLOWED_CATALOG_QUERY_PARAMS as l}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as d}from"../../../../utils/object/allowlist-object.js";import{CatalogEntitiesService as f}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as g}from"../../../providers/database/pagination/schemas.js";import{isValidIsoDate as c}from"../../../utils/is-valid-iso-date.js";import{isValidSanitizedString as v}from"../../../utils/validate-and-sanitize-string.js";const E=["type","key","title","summary","tags","metadata","metadata.*","git","contact","links","id","source","sourceFile","version","revision","createdAt","updatedAt","domains","owners"],p=async({catalogEntitiesService:n,ctx:t})=>o("catalog_entities.bff.get_entities",async e=>{const r=t.req.query();e?.setAttribute("queryParams",JSON.stringify(d(r,l)));const i=g(E).parse(r),a=await n.getEntitiesWithRelations(i);return e?.setAttribute("entitiesCount",a.items.length),t.json(a)}),A=async({catalogEntitiesService:n,ctx:t})=>o("catalog_entities.bff.get_entity",async e=>{const r=t.req.param(u);if(!r)return e?.error(new Error("Entity key is required")),t.json({message:"Entity key is required"},400);const i=t.req.query("revision");let a=null;if(i){if(!c(i))return e?.error(new Error("Invalid revision parameter")),t.json({message:"Invalid revision parameter: must be a valid ISO 8601 date-time string"},400);a=i}const m=t.req.query("version");if(!v(m,{pattern:/^[a-zA-Z0-9._-]+$/,maxLength:100,allowEmpty:!0}))return e?.error(new Error("Invalid version parameter")),t.json({message:"Invalid version parameter: version must contain only alphanumeric characters, dots, hyphens, and underscores, and must not exceed 100 characters"},400);const y=t.req.query();e?.setAttribute("queryParams",JSON.stringify(y)),e?.setAttribute("pathParams",JSON.stringify({entityKey:r}));const s=await n.getEntityWithRelationsByKey(r,{revision:a,version:m});return s?(e?.setAttribute("entity",JSON.stringify(s)),t.json(s)):(e?.error(new Error("Entity not found")),t.json({message:"Entity not found"},404))});function j(n){return async t=>o("catalog_entities.bff",async e=>{e?.setAttribute("method",t.req.method);const r=await f.getInstance({baseDbDir:n.serverOutDir});return t.req.param("entityKey")?A({catalogEntitiesService:r,ctx:t}):p({catalogEntitiesService:r,ctx:t})})}export{j as bffCatalogHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as l}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_RELATION_ID as d}from"../../../../constants/common.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as c,ALLOWED_CATALOG_QUERY_PARAMS as E}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as f}from"../../../../utils/object/allowlist-object.js";import{entitiesRelationsDtoSchema as R,entityRelationDtoSchema as A}from"../../../plugins/catalog-entities/schemas/dto-schemas.js";import{CatalogEntitiesService as _}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as w}from"../../../providers/database/pagination/schemas.js";import{createValidator as y}from"../../../plugins/catalog-entities/utils/ajv-validator.js";import{ENTITY_RELATION_TO_DATABASE as b}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";import{CacheService as h}from"../../../persistence/cache/services/cache-service.js";const j=y(A,{errorPrefix:"Entity relation validation failed:",dataVar:"relation"}),O=y(R,{errorPrefix:"Entities relations validation failed:",dataVar:"relation"}),q=["id","organizationId","projectId","sourceKey","targetKey","sourceId","targetId","type","createdAt","updatedAt"],u=async a=>{await(await h.getInstance({baseDbDir:a})).deleteByNamespace(c)},T=async({catalogEntitiesService:a,ctx:e})=>l("catalog_entities.relations.get_entity_relation",async n=>{const t=e.req.param(d);if(!t)return n?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);n?.setAttribute("pathParams",JSON.stringify({entityRelationId:t}));const i=await a.getEntityRelationById(t);return i?(n?.setAttribute("entityRelation",JSON.stringify(i)),e.json(i)):(n?.error(new Error("Entity relation not found")),e.json({message:"Entity relation not found"},404))}),p=async({catalogEntitiesService:a,ctx:e})=>l("catalog_entities.relations.get_entities_relations",async n=>{try{const t=e.req.query();n?.setAttribute("queryParams",JSON.stringify(f(t,E)));const i=w(q,b).parse(t),r=await a.getEntitiesRelations(i);return n?.setAttribute("entitiesRelationsCount",r.items.length),e.json(r)}catch(t){return console.error(t),n?.error(new Error("Failed to get entities relations")),e.json({message:"Failed to get entities relations"},500)}}),I=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.create_entity_relation",async t=>{const i=e.get("logger");try{const r=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(r));const o=j(r),s=await a.createEntityRelation(o);return s?(u(n),t?.setAttribute("entityRelation",JSON.stringify(s)),e.json(s)):(t?.error(new Error("Failed to create entity relation")),e.json({message:"Failed to create entity relation"},500))}catch(r){return i.error(r),r instanceof Error&&r.message.includes("validation failed")?(t?.error(new Error(r.message)),e.json({message:r.message},400)):(t?.error(r),e.json({message:"Failed to create entity relation"},500))}}),N=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.bulk_upsert_entities_relations",async t=>{const i=e.get("logger");try{const r=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(r));const o=O(r),s=await a.createEntitiesRelations(o);if(!s.length)return t?.error(new Error("Failed to create entity relations")),e.json({message:"Failed to create entity relations"},500);u(n);const g=s.filter(m=>m.status==="ok");return t?.setAttribute("totalSuccess",g.length),t?.setAttribute("totalFailed",s.length-g.length),e.json(s,207)}catch(r){return i.error(r),r instanceof Error&&r.message.includes("validation failed")?(t?.error(new Error(r.message)),e.json({message:r.message},400)):(t?.error(r),e.json({message:"Failed to create entity relations"},500))}}),P=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.update_entity_relation",async t=>{const i=e.get("logger"),r=e.req.param(d);if(!r)return t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);t?.setAttribute("pathParams",JSON.stringify({entityRelationId:r}));try{const o=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(o));const s=await a.updateEntityRelation(r,o);return s?(u(n),t?.setAttribute("entityRelation",JSON.stringify(s)),e.json(s)):(t?.error(new Error("Failed to update entity relation")),e.json({message:"Failed to update entity relation"},500))}catch(o){return i.error(o),o instanceof Error&&o.message.includes("validation failed")?(t?.error(new Error(o.message)),e.json({message:o.message},400)):(t?.error(o),e.json({message:o.message},500))}}),D=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.delete_entity_relation",async t=>{const i=e.req.param(d);return i?(t?.setAttribute("pathParams",JSON.stringify({entityRelationId:i})),await a.deleteEntityRelation(i),u(n),new Response(null,{status:204})):(t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400))}),v={GET:p,POST:I,PUT:N},F={GET:T,DELETE:D,PATCH:P};function U(a){return async e=>l("catalog_entities.relations",async n=>{n?.setAttribute("method",e.req.method);const t=await _.getInstance({baseDbDir:a.serverOutDir}),i=e.req.method,r=e.req.param(d)?F:v;return r[i]?await r[i]({catalogEntitiesService:t,ctx:e,serverOutDir:a.serverOutDir}):(n?.error(new Error("Method not allowed")),e.json({message:"Method not allowed"},405))})}export{U as catalogRelationsHandler};
1
+ import{telemetryTraceStep as l}from"../../../telemetry/helpers/trace-step.js";import{CATALOG_ENTITY_RELATION_ID as d}from"../../../../constants/common.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as c,ALLOWED_CATALOG_QUERY_PARAMS as E}from"../../../constants/plugins/catalog-entities.js";import{allowlistObject as f}from"../../../../utils/object/allowlist-object.js";import{entitiesRelationsDtoSchema as R,entityRelationDtoSchema as A}from"../../../plugins/catalog-entities/schemas/dto-schemas.js";import{CatalogEntitiesService as _}from"../../../plugins/catalog-entities/database/catalog-entities-service.js";import{createPaginationParamsValidator as w}from"../../../providers/database/pagination/schemas.js";import{createValidator as y}from"../../../plugins/catalog-entities/utils/ajv-validator.js";import{ENTITY_RELATION_TO_DATABASE as b}from"../../../plugins/catalog-entities/database/mappers/field-transformations.js";import{CacheService as h}from"../../../persistence/cache/services/cache-service.js";const j=y(A,{errorPrefix:"Entity relation validation failed:",dataVar:"relation"}),O=y(R,{errorPrefix:"Entities relations validation failed:",dataVar:"relation"}),q=["id","organizationId","projectId","sourceKey","targetKey","type","createdAt","updatedAt"],u=async a=>{await(await h.getInstance({baseDbDir:a})).deleteByNamespace(c)},T=async({catalogEntitiesService:a,ctx:e})=>l("catalog_entities.relations.get_entity_relation",async n=>{const t=e.req.param(d);if(!t)return n?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);n?.setAttribute("pathParams",JSON.stringify({entityRelationId:t}));const i=await a.getEntityRelationById(t);return i?(n?.setAttribute("entityRelation",JSON.stringify(i)),e.json(i)):(n?.error(new Error("Entity relation not found")),e.json({message:"Entity relation not found"},404))}),p=async({catalogEntitiesService:a,ctx:e})=>l("catalog_entities.relations.get_entities_relations",async n=>{try{const t=e.req.query();n?.setAttribute("queryParams",JSON.stringify(f(t,E)));const i=w(q,b).parse(t),r=await a.getEntitiesRelations(i);return n?.setAttribute("entitiesRelationsCount",r.items.length),e.json(r)}catch(t){return console.error(t),n?.error(new Error("Failed to get entities relations")),e.json({message:"Failed to get entities relations"},500)}}),N=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.create_entity_relation",async t=>{const i=e.get("logger");try{const r=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(r));const o=j(r),s=await a.createEntityRelation(o);return s?(u(n),t?.setAttribute("entityRelation",JSON.stringify(s)),e.json(s)):(t?.error(new Error("Failed to create entity relation")),e.json({message:"Failed to create entity relation"},500))}catch(r){return i.error(r),r instanceof Error&&r.message.includes("validation failed")?(t?.error(new Error(r.message)),e.json({message:r.message},400)):(t?.error(r),e.json({message:"Failed to create entity relation"},500))}}),P=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.bulk_upsert_entities_relations",async t=>{const i=e.get("logger");try{const r=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(r));const o=O(r),s=await a.createEntitiesRelations(o);if(!s.length)return t?.error(new Error("Failed to create entity relations")),e.json({message:"Failed to create entity relations"},500);u(n);const g=s.filter(m=>m.status==="ok");return t?.setAttribute("totalSuccess",g.length),t?.setAttribute("totalFailed",s.length-g.length),e.json(s,207)}catch(r){return i.error(r),r instanceof Error&&r.message.includes("validation failed")?(t?.error(new Error(r.message)),e.json({message:r.message},400)):(t?.error(r),e.json({message:"Failed to create entity relations"},500))}}),D=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.update_entity_relation",async t=>{const i=e.get("logger"),r=e.req.param(d);if(!r)return t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400);t?.setAttribute("pathParams",JSON.stringify({entityRelationId:r}));try{const o=await e.req.json();t?.setAttribute("requestBody",JSON.stringify(o));const s=await a.updateEntityRelation(r,o);return s?(u(n),t?.setAttribute("entityRelation",JSON.stringify(s)),e.json(s)):(t?.error(new Error("Failed to update entity relation")),e.json({message:"Failed to update entity relation"},500))}catch(o){return i.error(o),o instanceof Error&&o.message.includes("validation failed")?(t?.error(new Error(o.message)),e.json({message:o.message},400)):(t?.error(o),e.json({message:o.message},500))}}),v=async({catalogEntitiesService:a,ctx:e,serverOutDir:n})=>l("catalog_entities.relations.delete_entity_relation",async t=>{const i=e.req.param(d);return i?(t?.setAttribute("pathParams",JSON.stringify({entityRelationId:i})),await a.deleteEntityRelation(i),u(n),new Response(null,{status:204})):(t?.error(new Error("Entity relation id is required")),e.json({message:"Entity relation id is required"},400))}),F={GET:p,POST:N,PUT:P},I={GET:T,DELETE:v,PATCH:D};function U(a){return async e=>l("catalog_entities.relations",async n=>{n?.setAttribute("method",e.req.method);const t=await _.getInstance({baseDbDir:a.serverOutDir}),i=e.req.method,r=e.req.param(d)?I:F;return r[i]?await r[i]({catalogEntitiesService:t,ctx:e,serverOutDir:a.serverOutDir}):(n?.error(new Error("Method not allowed")),e.json({message:"Method not allowed"},405))})}export{U as catalogRelationsHandler};
@@ -1 +1 @@
1
- import{getCookie as _}from"hono/cookie";import{AUTH_URL as p}from"../../constants/common.js";import{ServerRoutes as c}from"../../../constants/common.js";import{withPathPrefix as l}from"@redocly/theme/core/utils";import{logger as m}from"../../tools/notifiers/logger.js";import{createMcpAuthorizationCode as f,verifyMcpAuthorizationCode as g}from"../auth.js";const s=(e,t,o=200,r)=>e.json(t,o,{"Content-Type":"application/json",...r??{}});function k(){return async e=>{if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=new URL(e.req.url),o=e.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",r=e.req.header("x-forwarded-host")||t.host,n=`${o}://${r}`;return s(e,{resource:`${n}/mcp`,authorization_servers:[n],bearer_methods_supported:["header"],resource_documentation:`${n}/.well-known/oauth-authorization-server`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function T(){return async e=>{const t=new URL(e.req.url),o=e.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",r=e.req.header("x-forwarded-host")||t.host,n=`${o}://${r}`;return s(e,{issuer:p||"",authorization_endpoint:`${n}${c.MCP_AUTHORIZATION}`,token_endpoint:`${n}${c.MCP_TOKEN_PORTAL}`,jwks_uri:`${p||""}/.well-known/jwks.json`,registration_endpoint:`${n}${c.MCP_DYNAMIC_CLIENT_REGISTRATION}`,scopes_supported:["openid","profile","email","offline_access"],response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"]})}}function R(){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:process.env.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500)}}}function S(){return async e=>{const t=new URL(e.req.url),{searchParams:o}=t,r={isMcpFlow:!0,originalRedirectUri:o.get("redirect_uri"),mcpClientId:o.get("client_id"),mcpState:o.get("state"),timestamp:Date.now()};try{const n=new URL(e.req.url),i=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",a=e.req.header("x-forwarded-host")||n.host,u=`${i}://${a}`,h=Buffer.from(JSON.stringify(r)).toString("base64url"),d=new URL(c.IDP_LOGIN,u);return d.searchParams.set("redirectTo",`${l(c.MCP_CALLBACK)}/${h}`),d.searchParams.set("idpId","oidc"),e.redirect(d.toString())}catch{const i=new URL(l(`${p}/oauth2/auth`));return i.search=o.toString(),e.redirect(i.toString())}}}function A(){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),o=t.get("grant_type"),r=t.get("code"),n=t.get("redirect_uri")||void 0;if(o!=="authorization_code"||!r)return s(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await g(r);if(n&&n!==i.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(process.env.OAUTH_CLIENT_ID&&i.client_id&&i.client_id!==process.env.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const a=i.id_token;return typeof a!="string"||a.length===0?s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400):s(e,{access_token:a,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:a},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:t.message},500)}}}function v(){return async e=>{const t=new URL(e.req.url);let o=t.searchParams.get("context");if(!o&&t.pathname.startsWith(l(`${c.MCP_CALLBACK}/`))){const r=t.pathname.split("/"),n=r[r.length-1];if(n)try{o=Buffer.from(n,"base64url").toString("utf-8")}catch(i){m.error("[OAuth Debug] Error decoding context:",i)}}if(!o)return e.text("Missing context parameter",400);try{const r=typeof o=="string"&&o.startsWith("{")?JSON.parse(o):JSON.parse(decodeURIComponent(o));if(!r.isMcpFlow||!r.originalRedirectUri)throw new Error("Invalid MCP context");const n=_(e,"idp_id_token")||_(e,"authorization"),i=await f({idToken:n||"",clientId:r.mcpClientId||"",redirectUri:r.originalRedirectUri,ttlSec:600}),a=new URL(r.originalRedirectUri);return a.searchParams.set("code",i),r.mcpState&&a.searchParams.set("state",r.mcpState),e.redirect(a.toString())}catch(r){return e.text(`Invalid MCP callback: ${r.message}`,400)}}}export{S as mcpAuthorizationHandler,v as mcpCallbackHandler,R as mcpDynamicClientRegistrationHandler,T as mcpOAuthAuthorizationServerHandler,k as mcpOAuthProtectedResourceHandler,A as mcpTokenPortalHandler};
1
+ import{getCookie as f}from"hono/cookie";import{ulid as C}from"ulid";import{AUTH_URL as m}from"../../constants/common.js";import{ServerRoutes as d}from"../../../constants/common.js";import{withPathPrefix as g}from"@redocly/theme/core/utils";import{logger as S}from"../../tools/notifiers/logger.js";import{telemetry as p}from"../../telemetry/index.js";import{createMcpAuthorizationCode as M,verifyMcpAuthorizationCode as y,createMcpSessionResource as l}from"../auth.js";const a=(e,t,n=200,i)=>e.json(t,n,{"Content-Type":"application/json",...i??{}});function $(){return async e=>{if(e.req.method!=="GET")return a(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=new URL(e.req.url),n=e.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",i=e.req.header("x-forwarded-host")||t.host,r=`${n}://${i}`;return a(e,{resource:`${r}/mcp`,authorization_servers:[r],bearer_methods_supported:["header"],resource_documentation:`${r}/.well-known/oauth-authorization-server`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function q(){return async e=>{const t=new URL(e.req.url),n=e.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",i=e.req.header("x-forwarded-host")||t.host,r=`${n}://${i}`;return a(e,{issuer:m||"",authorization_endpoint:`${r}${d.MCP_AUTHORIZATION}`,token_endpoint:`${r}${d.MCP_TOKEN_PORTAL}`,jwks_uri:`${m||""}/.well-known/jwks.json`,registration_endpoint:`${r}${d.MCP_DYNAMIC_CLIENT_REGISTRATION}`,scopes_supported:["openid","profile","email","offline_access"],response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"]})}}function z(){return async e=>{if(e.req.method!=="POST")return a(e,{error:"Method not allowed"},405);try{return a(e,{client_id:process.env.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(t){return a(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500)}}}function L(){return async e=>{const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),r=C();p.sendMcpAuthorizationStartedMessage({...l(r),redirect_uri:i||null});const s={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:n.get("client_id"),mcpState:n.get("state"),mcpSessionId:r,timestamp:Date.now()};try{const o=new URL(e.req.url),c=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",_=e.req.header("x-forwarded-host")||o.host,u=`${c}://${_}`,w=Buffer.from(JSON.stringify(s)).toString("base64url"),h=new URL(d.IDP_LOGIN,u);return h.searchParams.set("redirectTo",`${g(d.MCP_CALLBACK)}/${w}`),h.searchParams.set("idpId","oidc"),e.redirect(h.toString())}catch(o){const c=o instanceof Error?o.message:String(o),_=o instanceof Error?o.stack:String(o);p.sendMcpAuthorizationFailedMessage({...l(r),error:c,error_details:_});const u=new URL(g(`${m}/oauth2/auth`));return u.search=n.toString(),e.redirect(u.toString())}}}function O(){return async e=>{if(e.req.method!=="POST")return a(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),r=t.get("redirect_uri")||void 0;if(n!=="authorization_code"||!i)return a(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const s=await y(i);if(r&&r!==s.redirect_uri)return a(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(process.env.OAUTH_CLIENT_ID&&s.client_id&&s.client_id!==process.env.OAUTH_CLIENT_ID)return a(e,{error:"invalid_client",error_description:"Client mismatch"},400);const o=s.id_token;return typeof o!="string"||o.length===0?a(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400):a(e,{access_token:o,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:o},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return a(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){return a(e,{error:"server_error",error_description:"Failed to process token request",error_details:t.message},500)}}}function D(){return async e=>{const t=new URL(e.req.url);let n=t.searchParams.get("context");if(!n&&t.pathname.startsWith(g(`${d.MCP_CALLBACK}/`))){const r=t.pathname.split("/"),s=r[r.length-1];if(s)try{n=Buffer.from(s,"base64url").toString("utf-8")}catch(o){S.error("[OAuth Debug] Error decoding context:",o)}}if(!n)return p.sendMcpAuthorizationFailedMessage({...l(null),error:"Missing context parameter",error_details:null}),e.text("Missing context parameter",400);let i=null;try{const r=typeof n=="string"&&n.startsWith("{")?JSON.parse(n):JSON.parse(decodeURIComponent(n));if(i=r.mcpSessionId||null,!r.isMcpFlow||!r.originalRedirectUri)throw new Error("Invalid MCP context");const s=f(e,"idp_id_token")||f(e,"authorization"),o=await M({idToken:s||"",clientId:r.mcpClientId||"",redirectUri:r.originalRedirectUri,ttlSec:600}),c=new URL(r.originalRedirectUri);return c.searchParams.set("code",o),r.mcpState&&c.searchParams.set("state",r.mcpState),p.sendMcpAuthorizationCompletedMessage({...l(i),redirect_uri:r.originalRedirectUri||null}),e.redirect(c.toString())}catch(r){const s=r instanceof Error?r.message:String(r),o=r instanceof Error?r.stack:String(r);return p.sendMcpAuthorizationFailedMessage({...l(i),error:s,error_details:o}),e.text(`Invalid MCP callback: ${s}`,400)}}}export{L as mcpAuthorizationHandler,D as mcpCallbackHandler,z as mcpDynamicClientRegistrationHandler,q as mcpOAuthAuthorizationServerHandler,$ as mcpOAuthProtectedResourceHandler,O as mcpTokenPortalHandler};
@@ -1 +1 @@
1
- import{telemetryTraceStep as b}from"../../telemetry/helpers/trace-step.js";import{expandTeamsForRead as p}from"../../utils/rbac.js";function C(e){return async t=>await b("search",async s=>{const u=t.get("logger"),o=t.get("auth"),y=u.startTiming(),a=e.getConfig().requiresLogin&&!o.isAuthenticated,r={...await t.req.json(),auth:{...o,teams:p(e.config.rbac,o.teams)}};s?.setAttribute("engine",e?.searchEngine?.type),s?.setAttribute("query",r.query||""),s?.setAttribute("user",r.auth.claims.email??"anonymous"),s?.setAttribute("noAccess",a);const g=e.getSearchFacets(),i=a?{}:e.searchEngine?await e.searchEngine.search(r,g):{};let f=0;if(Object.keys(i).length){const h=i.documents;for(const[c,n]of Object.entries(h))f+=n.length}return u.infoTime(y,`Search with query "${r.query||""}". Total results: ${f}`),s?.setAttribute("resultsCount",f),t.json(i)})}function F(e){return async t=>await b("search.facets",async s=>{const u=t.get("logger"),o=t.get("auth"),m=e.getConfig().requiresLogin&&!o.isAuthenticated,a={...await t.req.json(),auth:o};s?.setAttribute("engine",e?.searchEngine?.type),s?.setAttribute("user",a.auth.claims.email??"anonymous"),s?.setAttribute("noAccess",m);const r=u.startTiming(),g=e.getSearchFacets(),i=m?{}:e.searchEngine?await e.searchEngine.countFacets(a,g):{},f=!!a.field;let h=[];if(f){const c=a.field,n=c&&g.get(c);if(n){const l={...n};l.values=i?.[c]||[],h.push(l)}}else{const c=new Map;for(const[n,l]of g){const A=i?.[n],d={...l};d.values=A||l.values.map(T=>({value:T,count:0})),c.set(n,d)}h=Array.from(c,([,n])=>n)}return u.infoTime(r,"Search facets"),t.json(h)})}export{F as searchFacetsHandler,C as searchHandler};
1
+ import{telemetryTraceStep as d}from"../../telemetry/helpers/trace-step.js";import{expandTeamsForRead as p}from"../../utils/rbac.js";function C(e){return async t=>await d("search",async s=>{const u=t.get("logger"),o=t.get("auth"),y=u.startTiming(),a=e.getConfig().requiresLogin&&!o.isAuthenticated,r={...await t.req.json(),auth:{...o,teams:p(e.config.rbac,o.teams)}};s?.setAttribute("engine",e?.searchEngine?.type),s?.setAttribute("query",r.query||""),s?.setAttribute("user",r.auth.claims.email??"anonymous"),s?.setAttribute("noAccess",a);const g=e.getSearchFacets(),i=a?{}:e.searchEngine?await e.searchEngine.search(r,g):{};let f=0;if(Object.keys(i).length){const h=i.documents;for(const[c,n]of Object.entries(h))f+=n.length}return u.infoTime(y,`Search with query "${r.query||""}". Total results: ${f}`),s?.setAttribute("resultsCount",f),t.json(i)})}function F(e){return async t=>await d("search.facets",async s=>{const u=t.get("logger"),o=t.get("auth"),m=e.getConfig().requiresLogin&&!o.isAuthenticated,a={...await t.req.json(),auth:o};s?.setAttribute("engine",e?.searchEngine?.type),s?.setAttribute("user",a.auth.claims.email??"anonymous"),s?.setAttribute("noAccess",m);const r=u.startTiming(),g=e.getSearchFacets(),i=m?{}:e.searchEngine?await e.searchEngine.countFacets(a,g):{},f=!!a.field;let h=[];if(f){const c=a.field,n=c&&g.get(c);if(n){const l={...n};l.values=i?.[c]||[],h.push(l)}}else{const c=new Map;for(const[n,l]of g){const A=i?.[n],b={...l};b.values=A||l.values.map(T=>({value:T,count:0})),c.set(n,b)}h=Array.from(c,([,n])=>n)}return u.verboseTime(r,"Search facets"),t.json(h)})}export{F as searchFacetsHandler,C as searchHandler};
@@ -1 +1 @@
1
- import{getLocaleFromPathname as a}from"@redocly/theme/core/utils";import{buildLoginUrl as c}from"../../../utils/auth/build-login-url.js";import{getLoginUrlWithRedirect as g}from"../utils.js";function m(o,e){const r=o.globalData.auth?.devLogin,n=o.getConfig().ssoDirect||{},t=Object.keys(n);if(r||t.length>1)return g(e);if(t.length===1){const l=n[t[0]],i=o.globalData.l10n;return c(t[0],l.type,a(e,i?.defaultLocale,i?.locales),e)}}export{m as getRedirectLoginUrl};
1
+ import{getLocaleFromPathname as l}from"@redocly/theme/core/utils";import{buildLoginUrl as a}from"../../../utils/auth/build-login-url.js";import{getLoginUrlWithRedirect as c}from"../utils.js";function m(o,t){const i=o.globalData.auth?.devLogin,r=o.getConfig().ssoDirect||{},e=Object.keys(r);if(i||e.length>1)return c(t);if(e.length===1){const n=o.globalData.l10n;return a(e[0],l(t,n?.defaultLocale,n?.locales),t)}}export{m as getRedirectLoginUrl};
@@ -1,4 +1,3 @@
1
- import type { AuthProviderType } from '@redocly/config';
2
1
  /**
3
2
  * Builds a login URL for a given identity provider (IdP).
4
3
  *
@@ -7,7 +6,6 @@ import type { AuthProviderType } from '@redocly/config';
7
6
  * `idpId`, `localePrefix`, `redirectTo`, and any additional `extraParams`.
8
7
  *
9
8
  * @param idpId - The unique identifier of the identity provider.
10
- * @param type - The type of authentication provider (e.g., "saml", "oidc").
11
9
  * @param localePrefix - The locale prefix (e.g., "en", "fr").
12
10
  * @param redirectTo - Optional redirect target after login.
13
11
  * @param extraParams - Additional query parameters to append to the login URL.
@@ -20,5 +18,5 @@ import type { AuthProviderType } from '@redocly/config';
20
18
  * // "/api/idp/login?idpId=okta-123&localePrefix=en&redirectTo=%2Fdashboard&prompt=login"
21
19
  * ```
22
20
  */
23
- export declare function buildLoginUrl(idpId: string, type: AuthProviderType, localePrefix: string, redirectTo?: string, extraParams?: Record<string, string>): string | undefined;
21
+ export declare function buildLoginUrl(idpId: string, localePrefix: string, redirectTo?: string, extraParams?: Record<string, string>): string | undefined;
24
22
  //# sourceMappingURL=build-login-url.d.ts.map
@@ -1 +1 @@
1
- import{withPathPrefix as u}from"@redocly/theme/core/utils";import{DEFAULT_PROVIDERS_LOGIN_URLS as f,ServerRoutes as a}from"../../constants/common.js";import{appendQueryParams as P}from"../url/append-query-params.js";function c(r,e,o,t,i={}){if(!r)return;const n=f?.[e]||u(a.IDP_LOGIN),m={idpId:r,localePrefix:o,redirectTo:t||"",...i};return P(n,m)}export{c as buildLoginUrl};
1
+ import{ServerRoutes as u}from"../../constants/common.js";import{appendQueryParams as m}from"../url/append-query-params.js";function s(r,e,o,n={}){if(!r)return;const t=u.IDP_LOGIN,i={idpId:r,localePrefix:e,redirectTo:o||"",...n};return m(t,i)}export{s as buildLoginUrl};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Checks if the current environment is local development.
3
+ *
4
+ * @param - This function takes no parameters.
5
+ * @returns `true` if the current environment is local development, otherwise `false`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * isLocalDevelopment();
10
+ * // true (in local development), false (in other environments)
11
+ */
12
+ export declare const isLocalDevelopment: () => boolean;
13
+ //# sourceMappingURL=is-local-development.d.ts.map
@@ -0,0 +1 @@
1
+ const e=()=>process.env.REDOCLY_ENV==="local";export{e as isLocalDevelopment};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Checks if the current environment is production.
3
+ *
4
+ * @param - This function takes no parameters.
5
+ * @returns `true` if the current environment is production, otherwise `false`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * isProduction();
10
+ * // true (in production), false (in other environments)
11
+ */
12
+ export declare const isProduction: () => boolean;
13
+ //# sourceMappingURL=is-production.d.ts.map
@@ -0,0 +1 @@
1
+ const o=()=>process.env.REDOCLY_ENV==="production";export{o as isProduction};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Checks if the current environment is a web view.
3
+ *
4
+ * @param - This function takes no parameters.
5
+ * @returns `true` if the current environment is a web view, otherwise `false`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * isWebView();
10
+ * // true (in web view), false (in other environments)
11
+ * ```
12
+ */
13
+ export declare const isWebView: () => boolean;
14
+ //# sourceMappingURL=is-web-view.d.ts.map
@@ -0,0 +1 @@
1
+ const e=()=>process.env.REDOCLY_ENV==="development";export{e as isWebView};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Removes the fragment identifier (`#...`) from a given path string.
3
+ *
4
+ * If the path contains a `#`, returns the substring before the first `#`.
5
+ * Otherwise, returns the path unchanged.
6
+ *
7
+ * @param path - The path string to process.
8
+ * @returns The path without its fragment identifier, or `undefined` if input is `undefined`.
9
+ *
10
+ * @example
11
+ * removeFragment('docs/page.md#section'); // "docs/page.md"
12
+ * removeFragment('docs/guide'); // "docs/guide"
13
+ * removeFragment(undefined); // undefined
14
+ */
15
+ export declare function removeFragment<T extends string | undefined>(path: T): T;
16
+ //# sourceMappingURL=remove-fragment.d.ts.map
@@ -0,0 +1 @@
1
+ function e(r){return r&&r.split("#")[0]}export{e as removeFragment};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/redoc",
3
- "version": "0.129.0-next.2",
3
+ "version": "0.129.0-next.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  "@opentelemetry/sdk-trace-web": "2.0.1",
33
33
  "@opentelemetry/semantic-conventions": "1.34.0",
34
34
  "@redocly/ajv": "8.17.1",
35
- "@redocly/openapi-core": "2.13.0",
35
+ "@redocly/openapi-core": "2.14.1",
36
36
  "@shikijs/transformers": "^1.22.2",
37
37
  "@tanstack/react-query": "5.62.3",
38
38
  "@tanstack/react-table": "8.21.3",
@@ -71,10 +71,10 @@
71
71
  "os-browserify": "0.3.0",
72
72
  "path-browserify": "1.0.1",
73
73
  "picomatch": "2.3.1",
74
- "react": "^19.2.1",
74
+ "react": "19.2.3",
75
75
  "react-calendar": "5.1.0",
76
76
  "react-date-picker": "11.0.0",
77
- "react-dom": "^19.2.1",
77
+ "react-dom": "19.2.3",
78
78
  "react-router-dom": "^6.21.1",
79
79
  "react-select": "5.10.1",
80
80
  "reactjs-popup": "2.0.6",
@@ -93,18 +93,18 @@
93
93
  "xml-crypto": "6.0.1",
94
94
  "xpath": "0.0.34",
95
95
  "yaml-ast-parser": "0.0.43",
96
- "@redocly/asyncapi-docs": "1.6.0-next.2",
96
+ "@redocly/asyncapi-docs": "1.6.0-next.3",
97
97
  "@redocly/config": "0.41.1",
98
98
  "@redocly/graphql-docs": "1.6.0-next.0",
99
- "@redocly/openapi-docs": "3.17.0-next.2",
99
+ "@redocly/openapi-docs": "3.17.0-next.3",
100
100
  "@redocly/portal-legacy-ui": "0.12.0-next.0",
101
- "@redocly/portal-plugin-mock-server": "0.14.0-next.2",
101
+ "@redocly/portal-plugin-mock-server": "0.14.0-next.3",
102
102
  "@redocly/realm-asyncapi-sdk": "0.7.0-next.1",
103
- "@redocly/theme": "0.61.0-next.2"
103
+ "@redocly/theme": "0.61.0-next.3"
104
104
  },
105
105
  "peerDependencies": {
106
- "react": "^19.2.1",
107
- "react-dom": "^19.2.1"
106
+ "react": "19.2.3",
107
+ "react-dom": "19.2.3"
108
108
  },
109
109
  "nx": {
110
110
  "implicitDependencies": [