@redocly/redoc-revel 0.133.0-next.3 → 0.133.0-next.5

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 (42) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/client/app/seo/SeoTags.js +1 -1
  3. package/dist/client/app/seo/hooks/useHreflangLinks.d.ts +10 -0
  4. package/dist/client/app/seo/hooks/useHreflangLinks.js +1 -0
  5. package/dist/client/app/utils/loadAndNavigate.js +1 -1
  6. package/dist/client/utils/auth/is-auth-route-path.d.ts +11 -0
  7. package/dist/client/utils/auth/is-auth-route-path.js +1 -0
  8. package/dist/markdoc/nodes/table.js +1 -1
  9. package/dist/server/plugins/catalog-entities/database/mappers/create-bff-related-entity.js +1 -1
  10. package/dist/server/plugins/catalog-entities/database/repositories/catalog-entities-repository.js +1 -1
  11. package/dist/server/plugins/catalog-entities/database/repositories/entities/entities-write-repository.js +1 -1
  12. package/dist/server/plugins/catalog-entities/database/repositories/relations/relations-write-repository.js +1 -1
  13. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/asyncapi-entities-extractor.js +1 -1
  14. package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/openapi-entities-extractor.js +1 -1
  15. package/dist/server/plugins/dev-onboarding/template/components/Container.js +9 -4
  16. package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
  17. package/dist/server/plugins/openapi-docs/ai-search-indexer.d.ts +11 -0
  18. package/dist/server/plugins/openapi-docs/ai-search-indexer.js +1 -0
  19. package/dist/server/plugins/openapi-docs/openapi-search-builder.d.ts +24 -0
  20. package/dist/server/plugins/openapi-docs/openapi-search-builder.js +1 -0
  21. package/dist/server/plugins/openapi-docs/search/get-ai-search-documents.js +1 -1
  22. package/dist/server/plugins/openapi-docs/search-indexer.d.ts +8 -10
  23. package/dist/server/plugins/openapi-docs/search-indexer.js +1 -1
  24. package/dist/server/plugins/search/documents/search-documents.js +1 -1
  25. package/dist/server/plugins/search/engines/flexsearch/index.js +1 -1
  26. package/dist/server/web-server/auth.d.ts +7 -2
  27. package/dist/server/web-server/auth.js +2 -2
  28. package/dist/server/web-server/routes/auth.js +1 -1
  29. package/dist/server/web-server/routes/cors-proxy.d.ts +1 -1
  30. package/dist/server/web-server/routes/cors-proxy.js +2 -2
  31. package/dist/server/web-server/routes/helpers/resolve-ui-locales-for-idp-login.d.ts +11 -0
  32. package/dist/server/web-server/routes/helpers/resolve-ui-locales-for-idp-login.js +1 -0
  33. package/dist/server/web-server/routes/index.js +1 -1
  34. package/dist/server/web-server/routes/mcp-routes/mcp-client-metadata.d.ts +14 -0
  35. package/dist/server/web-server/routes/mcp-routes/mcp-client-metadata.js +1 -0
  36. package/dist/server/web-server/routes/{mcp-oauth.d.ts → mcp-routes/mcp-oauth.d.ts} +3 -0
  37. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -0
  38. package/dist/server/web-server/routes/mcp-routes/mcp-routes.d.ts +4 -0
  39. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -0
  40. package/dist/server/web-server/utils.js +1 -1
  41. package/package.json +9 -9
  42. package/dist/server/web-server/routes/mcp-oauth.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @redocly/redoc-revel
2
2
 
3
+ ## 0.133.0-next.5
4
+
5
+ ### Minor Changes
6
+
7
+ - 0858f54ae5e: Added Client ID Metadata Document (CIMD) support to the MCP OAuth flow.
8
+ - 7e407eeb7c9: Added `hreflang` alternate links for translated pages.
9
+ - 7e407eeb7c9: Improved language picker accessibility by converting menu options to links.
10
+ - 49d39a04865: Fixed an issue where the login page didn't respect language preferences selected by the user.
11
+
12
+ ### Patch Changes
13
+
14
+ - 49d39a04865: Fixed 404 error when opening login from locale-prefixed URLs.
15
+ - 9f60402b867: Fixed an issue where navigating on a code walkthrough page prevented automatic scrolling to the active step.
16
+ - Updated dependencies [49d39a04865]
17
+ - Updated dependencies [7e407eeb7c9]
18
+ - Updated dependencies [7e407eeb7c9]
19
+ - Updated dependencies [59198fb803c]
20
+ - Updated dependencies [9f60402b867]
21
+ - @redocly/theme@0.65.0-next.5
22
+ - @redocly/realm-asyncapi-sdk@0.11.0-next.3
23
+ - @redocly/asyncapi-docs@1.10.0-next.5
24
+ - @redocly/graphql-docs@1.10.0-next.5
25
+ - @redocly/openapi-docs@3.21.0-next.5
26
+ - @redocly/portal-plugin-mock-server@0.18.0-next.5
27
+
28
+ ## 0.133.0-next.4
29
+
30
+ ### Minor Changes
31
+
32
+ - 84838a59f0f: Excluded buttons and links from heading content to improve accessibility.
33
+ - 1cc46010a1f: Separated OpenAPI AI search indexing from the standard search indexing flow in Realm.
34
+ This improved the internal search architecture to make future AI-specific enhancements safer without affecting the existing OpenAPI search behavior.
35
+
36
+ ### Patch Changes
37
+
38
+ - ba76c067857: Fixed unauthenticated callback execution in the MCP docs server.
39
+ - e13c79eb30c: Fixed an issue where searching in projects without searchable content caused a server error.
40
+ - f7725b9cc73: Fixed an issue where login and logout redirects caused 404 and URI mismatch errors with projects with path prefix.
41
+ - a160e4ca6f1: Updated `@redocly/openapi-core` to version `2.30.3`.
42
+ - ba76c067857: Fixed an issue where MCP auth URLs were displayed when MCP docs had been disabled.
43
+ - Updated dependencies [a160e4ca6f1]
44
+ - Updated dependencies [84838a59f0f]
45
+ - Updated dependencies [1cc46010a1f]
46
+ - Updated dependencies [ab8c509bb91]
47
+ - @redocly/openapi-docs@3.21.0-next.4
48
+ - @redocly/theme@0.65.0-next.4
49
+ - @redocly/portal-plugin-mock-server@0.18.0-next.4
50
+ - @redocly/asyncapi-docs@1.10.0-next.4
51
+ - @redocly/graphql-docs@1.10.0-next.4
52
+
3
53
  ## 0.133.0-next.3
4
54
 
5
55
  ### Minor Changes
@@ -99,6 +149,12 @@
99
149
  - @redocly/asyncapi-docs@1.10.0-next.0
100
150
  - @redocly/graphql-docs@1.10.0-next.0
101
151
 
152
+ ## 0.132.2
153
+
154
+ ### Patch Changes
155
+
156
+ - 8d30048cf1: Hardened Realm CORS proxy validation to block private network targets before proxying requests.
157
+
102
158
  ## 0.132.1
103
159
 
104
160
  ### Patch Changes
@@ -1 +1 @@
1
- import e from"react";import{Helmet as c}from"@dr.pogodin/react-helmet";import{combineUrls as u}from"@redocly/theme/core/utils";import{isTruthy as f}from"../../../utils/guards/is-truthy";import{usePageVersions as p}from"../../providers/page-data/hooks";import{getMetaTagsAttributes as g}from"../utils/get-meta-tags-attributes";function b(r){const{seo:t,slug:i}=r,{versions:l}=p();if(!t)return null;const o=g(t),s=[t.title,t.projectTitle].filter(f).join(" | "),a=l.find(n=>n.default)?.link||i;return e.createElement(c,null,e.createElement("title",null,s),t.siteUrl?e.createElement("link",{rel:"canonical",href:u(t.siteUrl,a)}):null,t.jsonLd?e.createElement("script",{type:"application/ld+json"},JSON.stringify(t.jsonLd)):null,o.map((n,m)=>e.createElement("meta",{key:m,...n})))}export{b as SeoTags};
1
+ import n from"react";import{Helmet as u}from"@dr.pogodin/react-helmet";import{combineUrls as p}from"@redocly/theme/core/utils";import{isTruthy as g}from"../../../utils/guards/is-truthy";import{usePageVersions as k}from"../../providers/page-data/hooks";import{getMetaTagsAttributes as d}from"../utils/get-meta-tags-attributes";import{useHreflangLinks as E}from"./hooks/useHreflangLinks";function U(o){const{seo:e,slug:i}=o,{versions:s}=k(),r=e?.siteUrl,{localeAlternates:a}=E(r);if(!e)return null;const m=d(e),c=[e.title,e.projectTitle].filter(g).join(" | "),f=s.find(t=>t.default)?.link||i;return n.createElement(u,null,n.createElement("title",null,c),r?n.createElement("link",{rel:"canonical",href:p(r,f)}):null,a.map(({hrefLang:t,href:l})=>n.createElement("link",{key:t,rel:"alternate",hrefLang:t,href:l})),e.jsonLd?n.createElement("script",{type:"application/ld+json"},JSON.stringify(e.jsonLd)):null,m.map((t,l)=>n.createElement("meta",{key:l,...t})))}export{U as SeoTags};
@@ -0,0 +1,10 @@
1
+ type HreflangLink = {
2
+ hrefLang: string;
3
+ href: string;
4
+ };
5
+ type HreflangLinksResult = {
6
+ localeAlternates: HreflangLink[];
7
+ };
8
+ export declare function useHreflangLinks(siteUrl?: string): HreflangLinksResult;
9
+ export {};
10
+ //# sourceMappingURL=useHreflangLinks.d.ts.map
@@ -0,0 +1 @@
1
+ import{useMemo as h}from"react";import{useLocation as l}from"react-router-dom";import{combineUrls as i,getPathnameForLocale as m,withPathPrefix as c,withoutPathPrefix as u}from"@redocly/theme/core/utils";import{DEFAULT_LOCALE_PLACEHOLDER as p}from"../../../../constants/common";import{useL10nConfig as L}from"../../hooks/useL10nConfig";function x(e){const n=l(),{locales:o,defaultLocale:t}=L();return h(()=>{if(!e)return{localeAlternates:[]};const f=u(n.pathname),a=o.filter(r=>r.code!==p).map(r=>({hrefLang:r.code.replaceAll("_","-"),href:i(e,c(m(f,t,r.code,o)))}));return a.length>0&&a.push({hrefLang:"x-default",href:i(e,c(m(f,t,t,o)))}),{localeAlternates:a}},[t,o,n.pathname,e])}export{x as useHreflangLinks};
@@ -1 +1 @@
1
- import{withPathPrefix as f}from"@redocly/theme/core/utils";import{isLocalLink as m}from"../../../utils/path/is-local-link.js";import{withLoadProgress as u}from"./withLoadProgress";import{scrollToAnchor as p}from"./scroll-to-anchor";let s;async function L({navigate:n,to:o,origin:a="browser",options:c}){o===""&&(o="/"),s=o;const{pathname:e,hash:d,search:i}=new URL(o,window.location.origin+window.location.pathname),t=decodeURIComponent(d),l=decodeURIComponent(window.location.hash),w=window.__LOADER,r=await u(w.tryLoad(e,void 0,i));if(r?.redirectTo){if(!m(r.redirectTo)){window.location.href=r.redirectTo;return}return L({navigate:n,to:r.redirectTo,origin:a,options:c})}if(o.startsWith(f("/_auth/"))){window.location.href=o;return}if(r&&s===o){if((e!==window.location.pathname||i!==window.location.search||t!==l)&&n({pathname:e,search:i,hash:t},{...c,state:{origin:a},unstable_flushSync:!0}),r.props?.disableAutoScroll)return;requestAnimationFrame(()=>{if(t){const h=t.slice(1);p(h)}else window.scrollTo(0,0)})}}export{L as loadAndNavigate};
1
+ import{isLocalLink as f}from"../../../utils/path/is-local-link.js";import{isAuthRoutePath as m}from"../../utils/auth/is-auth-route-path.js";import{withLoadProgress as u}from"./withLoadProgress";import{scrollToAnchor as p}from"./scroll-to-anchor";let s;async function A({navigate:i,to:o,origin:a="browser",options:c}){o===""&&(o="/"),s=o;const{pathname:n,hash:d,search:t}=new URL(o,window.location.origin+window.location.pathname),r=decodeURIComponent(d),l=decodeURIComponent(window.location.hash);if(m(n)){window.location.href=o;return}const w=window.__LOADER,e=await u(w.tryLoad(n,void 0,t));if(e?.redirectTo){if(!f(e.redirectTo)){window.location.href=e.redirectTo;return}return A({navigate:i,to:e.redirectTo,origin:a,options:c})}if(e&&s===o){if((n!==window.location.pathname||t!==window.location.search||r!==l)&&i({pathname:n,search:t,hash:r},{...c,state:{origin:a},unstable_flushSync:!0}),e.props?.disableAutoScroll)return;requestAnimationFrame(()=>{if(r){const h=r.slice(1);p(h)}else window.scrollTo(0,0)})}}export{A as loadAndNavigate};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Returns true if the pathname targets Realm reserved auth URLs (`/_auth/*`).
3
+ *
4
+ * @param pathname - Full pathname (e.g. from URL), optionally with path prefix.
5
+ * @returns `true` if the first path segment, after `withoutPathPrefix`, is `_auth`.
6
+ * @example
7
+ * isAuthRoutePath('/_auth/idp-login'); // true
8
+ * isAuthRoutePath('/es/_auth/idp-login'); // false
9
+ */
10
+ export declare function isAuthRoutePath(pathname: string): boolean;
11
+ //# sourceMappingURL=is-auth-route-path.d.ts.map
@@ -0,0 +1 @@
1
+ import{withoutPathPrefix as n}from"@redocly/theme/core/utils";const i="_auth";function s(e){const t=n(e);return(t.startsWith("/")?t:`/${t}`).split("/").filter(Boolean)}function a(e){return s(e)[0]===i}export{a as isAuthRoutePath};
@@ -1 +1 @@
1
- import n from"@markdoc/markdoc";import{isTag as m}from"../helpers/guards/is-tag.js";import{getInnerText as i}from"../helpers/get-inner-text.js";const o={transform(t,r){const s=t.transformAttributes(r),e=t.transformChildren(r);return new n.Tag("div",{className:"md-table-wrapper"},[new n.Tag("table",{...s,className:"md"},u(e))])}};function u(t){if(!t.length)return t;const[r,...s]=t;return!m(r)||r.name!=="thead"?t:[new n.Tag("thead",r.attributes,r.children.map(e=>!m(e)||e.name!=="tr"?e:new n.Tag("tr",e.attributes,e.children.map(a=>!m(a)||a.name!=="th"?a:new n.Tag("th",{...a.attributes,"data-label":i(a.children)},a.children))))),...s]}export{o as table};
1
+ import n from"@markdoc/markdoc";import{MARKDOWN_CLASS_NAME as i}from"@redocly/theme/core/constants";import{isTag as m}from"../helpers/guards/is-tag.js";import{getInnerText as u}from"../helpers/get-inner-text.js";const p={transform(t,r){const s=t.transformAttributes(r),e=t.transformChildren(r);return new n.Tag("div",{className:"md-table-wrapper"},[new n.Tag("table",{...s,className:i},o(e))])}};function o(t){if(!t.length)return t;const[r,...s]=t;return!m(r)||r.name!=="thead"?t:[new n.Tag("thead",r.attributes,r.children.map(e=>!m(e)||e.name!=="tr"?e:new n.Tag("tr",e.attributes,e.children.map(a=>!m(a)||a.name!=="th"?a:new n.Tag("th",{...a.attributes,"data-label":u(a.children)},a.children))))),...s]}export{p as table};
@@ -1 +1 @@
1
- import{logger as i}from"../../../../tools/notifiers/logger.js";import{relatedEntityDatabaseSchema as y,relatedEntityQueryRowDatabaseSchema as s}from"../../schemas/database-schemas.js";import{validateWithResult as o}from"../../utils/ajv-validator.js";import{readString as t}from"../../utils/read-string.js";function u(e){return!e.id||!e.type||!e.key||!e.title?(i.warn(`Invalid database bff catalog related entity: missing required fields (id: ${e.id}, type: ${e.type}, key: ${e.key}, title: ${e.title})`),null):{id:e.id,type:e.type,key:e.key,title:e.title,summary:e.summary,source:e.source,sourceFile:e.sourceFile,relationRole:e.relationRole,relationType:e.relationType,createdAt:e.createdAt,updatedAt:e.updatedAt,metadata:e.metadata,version:e.version,...e.revision!==null&&{revision:e.revision}}}function l(e){if(e){if(typeof e=="string")try{return e?JSON.parse(e):void 0}catch{return}if(typeof e=="object"&&e!==null)return e}}function p(e){return typeof e=="string"||e===null||typeof e=="object"&&e!==null&&!Array.isArray(e)?e:null}function m(e){const a=t(e.source_to_target_relation)??t(e.target_to_source_relation)??t(e.relationType)??t(e.relation_type)??t(e.relationName)??t(e.relation_name)??"";return{id:t(e.id)??"",type:t(e.type)??"",key:t(e.key)??"",title:t(e.title)??"",summary:t(e.summary,!0)??null,source:t(e.source)??"",source_file:t(e.source_file,!0)??t(e.sourceFile,!0)??null,direction:t(e.direction)??"",relation_field:a,created_at:t(e.created_at,!0)??"",updated_at:t(e.updated_at,!0)??"",metadata:p(e.metadata),version:t(e.version,!0)??null,revision:t(e.revision,!0)??null}}function k(e){const a=o(y,e);if(!a.success)return i.warn(`Invalid database bff catalog related entity, error: ${a.error}`),null;const r=a.data||{};return u({id:r.id,type:r.type,key:r.key,title:r.title,summary:r.summary||null,source:r.source,sourceFile:r.source_file||null,relationRole:r.relation_role||"source",relationType:r.relation_type||"",createdAt:r.created_at,updatedAt:r.updated_at,metadata:l(r.metadata),version:r.version||null,revision:null})}function R(e){const a=m(e),r=o(s,a);if(!r.success)return i.warn(`Invalid database bff catalog related entity query row (key: ${a.key||"unknown"}), error: ${r.error}`),null;const n=r.data||{},c=n.direction==="outgoing"?"source":"target",d=n.relation_field.replace("reverse:","");return u({id:n.id,type:n.type,key:n.key,title:n.title,summary:n.summary??null,source:n.source,sourceFile:n.source_file??null,relationRole:c,relationType:d,createdAt:n.created_at,updatedAt:n.updated_at,metadata:l(n.metadata),version:n.version??null,revision:n.revision??null})}export{k as createBffRelatedEntity,R as createBffRelatedEntityFromQueryRow};
1
+ import{logger as i}from"../../../../tools/notifiers/logger.js";import{relatedEntityDatabaseSchema as y,relatedEntityQueryRowDatabaseSchema as s}from"../../schemas/database-schemas.js";import{validateWithResult as o}from"../../utils/ajv-validator.js";import{readString as t}from"../../utils/read-string.js";function u(e){return!e.id||!e.type||!e.key||!e.title?(i.warn(`Invalid database bff catalog related entity: missing required fields (id: ${e.id}, type: ${e.type}, key: ${e.key}, title: ${e.title})`),null):{id:e.id,type:e.type,key:e.key,title:e.title,summary:e.summary,source:e.source,sourceFile:e.sourceFile,relationRole:e.relationRole,relationType:e.relationType,createdAt:e.createdAt,updatedAt:e.updatedAt,metadata:e.metadata,version:e.version,...e.revision!==null&&{revision:e.revision}}}function l(e){if(e){if(typeof e=="string")try{return e?JSON.parse(e):void 0}catch{return}if(typeof e=="object"&&e!==null)return e}}function p(e){return typeof e=="string"||e===null||typeof e=="object"&&e!==null&&!Array.isArray(e)?e:null}function m(e){const a=t(e.source_to_target_relation)??t(e.target_to_source_relation)??t(e.relationType)??t(e.relation_type)??t(e.relationName)??t(e.relation_name)??"";return{id:t(e.id)??"",type:t(e.type)??"",key:t(e.key)??"",title:t(e.title)??"",summary:t(e.summary,!0)??null,source:t(e.source)??"",source_file:t(e.source_file,!0)??t(e.sourceFile,!0)??null,direction:t(e.direction)??"",relation_field:a,created_at:t(e.created_at,!0)??t(e.createdAt,!0)??"",updated_at:t(e.updated_at,!0)??t(e.updatedAt,!0)??"",metadata:p(e.metadata),version:t(e.version,!0)??null,revision:t(e.revision,!0)??null}}function k(e){const a=o(y,e);if(!a.success)return i.warn(`Invalid database bff catalog related entity, error: ${a.error}`),null;const r=a.data||{};return u({id:r.id,type:r.type,key:r.key,title:r.title,summary:r.summary||null,source:r.source,sourceFile:r.source_file||null,relationRole:r.relation_role||"source",relationType:r.relation_type||"",createdAt:r.created_at,updatedAt:r.updated_at,metadata:l(r.metadata),version:r.version||null,revision:null})}function R(e){const a=m(e),r=o(s,a);if(!r.success)return i.warn(`Invalid database bff catalog related entity query row (key: ${a.key||"unknown"}), error: ${r.error}`),null;const n=r.data||{},c=n.direction==="outgoing"?"source":"target",d=n.relation_field.replace("reverse:","");return u({id:n.id,type:n.type,key:n.key,title:n.title,summary:n.summary??null,source:n.source,sourceFile:n.source_file??null,relationRole:c,relationType:d,createdAt:n.created_at,updatedAt:n.updated_at,metadata:l(n.metadata),version:n.version??null,revision:n.revision??null})}export{k as createBffRelatedEntity,R as createBffRelatedEntityFromQueryRow};
@@ -1 +1 @@
1
- import{logger as n}from"../../../../tools/notifiers/logger.js";import{telemetryTraceStep as s}from"../../../../telemetry/helpers/trace-step.js";import{BaseRepository as a}from"../../../../providers/database/base-repository.js";import{DatabaseConnectionFactory as c}from"../../../../providers/database/database-connection-factory.js";import{RelationsReadRepository as o}from"./relations/relations-read-repository.js";import{RevisionRepository as f}from"./common/revision-repository.js";import{BffEntitiesReadRepository as m}from"./bffEntities/bff-entities-read-repository.js";import{EntitiesReadRepository as l}from"./entities/entities-read-repository.js";import{EntitiesWriteRepository as p}from"./entities/entities-write-repository.js";import{RelationsWriteRepository as w}from"./relations/relations-write-repository.js";import{FiltersRepository as h}from"./common/filters-repository.js";class e extends a{static#t;entitiesRead;entitiesWrite;relationsRead;relationsWrite;bffEntitiesRead;filters;revisions;get transactionsManager(){return this.databaseClient.transactionsManager}constructor(t){super(t),this.revisions=new f(t.client),this.bffEntitiesRead=new m(t.client),this.relationsRead=new o(t.client),this.entitiesRead=new l(t.client),this.entitiesWrite=new p(t.client,this.organizationId,this.projectId),this.relationsWrite=new w(t.client,this.organizationId,this.projectId),this.filters=new h(t.client)}async sync(){return s("catalog_entities.remote_repository.sync",async()=>{await this.databaseClient.sync()})}static async#e(t){const r=await c.create(t);if(!r)throw new Error("Failed to create db connection for catalog entities repository");return new e(r)}static async getInstance(t){return await s("catalog_entities.remote_repository.get_instance",async r=>{if(e.#t)return e.#t;try{return e.#t=await e.#e(t),e.#t}catch(i){throw n.error("Error creating db connection for catalog entities repository",i),r?.error(i),e.#t=null,i}})}static async recreateInstance(t){const r=e.#t,i=await e.#e(t);return e.#t=i,r&&await r.close(),i}static async resetInstance(){const t=e.#t;e.#t=null,t&&await t.close()}}export{e as CatalogEntitiesRepository};
1
+ import{logger as n}from"../../../../tools/notifiers/logger.js";import{telemetryTraceStep as s}from"../../../../telemetry/helpers/trace-step.js";import{BaseRepository as a}from"../../../../providers/database/base-repository.js";import{DatabaseConnectionFactory as c}from"../../../../providers/database/database-connection-factory.js";import{RelationsReadRepository as o}from"./relations/relations-read-repository.js";import{RevisionRepository as f}from"./common/revision-repository.js";import{BffEntitiesReadRepository as m}from"./bffEntities/bff-entities-read-repository.js";import{EntitiesReadRepository as l}from"./entities/entities-read-repository.js";import{EntitiesWriteRepository as p}from"./entities/entities-write-repository.js";import{RelationsWriteRepository as w}from"./relations/relations-write-repository.js";import{FiltersRepository as h}from"./common/filters-repository.js";class e extends a{static#t;entitiesRead;entitiesWrite;relationsRead;relationsWrite;bffEntitiesRead;filters;revisions;get transactionsManager(){return this.databaseClient.transactionsManager}constructor(t){super(t),this.revisions=new f(t.client),this.bffEntitiesRead=new m(t.client),this.relationsRead=new o(t.client),this.entitiesRead=new l(t.client),this.entitiesWrite=new p(t.client,this.organizationId,this.projectId),this.relationsWrite=new w(t.client,this.organizationId,this.projectId),this.filters=new h(t.client)}async sync(){return s("catalog_entities.repository.sync",async()=>{await this.databaseClient.sync()})}static async#e(t){const r=await c.create(t);if(!r)throw new Error("Failed to create db connection for catalog entities repository");return new e(r)}static async getInstance(t){return await s("catalog_entities.repository.get_instance",async r=>{if(e.#t)return e.#t;try{return e.#t=await e.#e(t),e.#t}catch(i){throw n.error("Error creating db connection for catalog entities repository",i),r?.error(i),e.#t=null,i}})}static async recreateInstance(t){const r=e.#t,i=await e.#e(t);return e.#t=i,r&&await r.close(),i}static async resetInstance(){const t=e.#t;e.#t=null,t&&await t.close()}}export{e as CatalogEntitiesRepository};
@@ -1 +1 @@
1
- import{and as v,eq as c,isNull as R,or as z,sql as V}from"drizzle-orm";import{VERSION_NOT_SPECIFIED as F}from"@redocly/theme/core/constants";import{telemetryTraceStep as C}from"../../../../../telemetry/helpers/trace-step.js";import{sha1 as _}from"../../../../../utils/crypto/sha1.js";import{logger as y}from"../../../../../tools/notifiers/logger.js";import{envConfig as K}from"../../../../../config/env-config.js";import{entitiesTable as i}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{convertFilterToWhereCondition as O}from"../../../../../providers/database/pagination/filter.js";import{entitiesRelationsTable as a}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{promiseMapLimit as m}from"../../../../../utils/async/promise-map-limit.js";import{RevisionRepository as H}from"../common/revision-repository.js";import{VersionRepository as P}from"../common/version-repository.js";import{EntityAttributesWriteRepository as $}from"../entityAttributes/entity-attributes-write-repository.js";import{createEntityDbRecord as W}from"../../mappers/create-entity-db-record.js";import{createEntityReadModel as D}from"../../mappers/create-entity-read-model.js";import{createEntityRelationDbRecordFromFileSchema as b}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{createEntityRelationDbRecordFromDto as j}from"../../mappers/create-entity-relation-db-record-from-dto.js";import{EntitiesReadRepository as G}from"./entities-read-repository.js";import{RelationsReadRepository as M}from"../relations/relations-read-repository.js";import{RelationsWriteRepository as q}from"../relations/relations-write-repository.js";const E=15;class fe{#e;#t;#n;#o;#r;#i;#a;#u;#s;constructor(e,t,s){this.#e=e,this.#r=t,this.#i=s,this.#t=new H(e),this.#n=new P(e),this.#o=new $(e),this.#a=new G(e),this.#u=new M(e),this.#s=new q(e,t,s)}async createEntity({entity:e,source:t,fileHash:s,sourceFile:n,isRootEntity:r,isDeleted:o,rbacTeams:u,errorOnSkip:h=!1,revision:f}){return C("catalog_entities.remote_repository.create_entity",async()=>{const{relations:l=[],...p}=e,d=_(JSON.stringify(p)),g=e.version??F,A=f??new Date().toISOString(),L=await this.#t.shouldSkipRevisionCreation(e.key,g,d,r,o);if(Array.isArray(u)&&await this.#o.upsertEntityAttributes({entityKey:e.key,rbacTeams:u,organizationId:this.#r,projectId:this.#i}),L){if(h)throw new Error("Entity validation failed: entity already exists");return null}const{shouldSetNewCurrentRevision:w,hasCurrentRevision:T}=await this.#t.getCurrentRevisionInfo({key:e.key,version:g,revision:A}),k=W({entity:{...e,revision:A,hash:d,isCurrent:w,isDefaultVersion:w,isDeleted:o,version:g},organizationId:this.#r,projectId:this.#i,source:t,sourceFile:n??null,fileHash:s??null}),{key:S,...x}=k;if(w&&T&&(await this.#t.markAllRevisionsAsNotCurrent(S),await this.#n.markAllVersionsAsNotDefault(S)),K.isDevelopMode&&!K.REDOCLY_INTERNAL_DEV)return await this.#l(k,l);const N=await this.#e.client.insert(i).values(k).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:x}).returning();return N.length?(l&&await this.#s.createEntityRelations(l.map(I=>({...I,sourceKey:e.key,targetKey:I.key}))),D(N[0])):null})}async updateEntity(e,t){return C("catalog_entities.remote_repository.update_entity",async()=>{try{y.info(`Updating entity ${t.key} in remote database`);const{shouldSetNewCurrentRevision:s,hasCurrentRevision:n}=await this.#t.getCurrentRevisionInfo({key:t.key,version:e.version??t.version??F,revision:t.revision});s&&n&&(await this.#t.markAllRevisionsAsNotCurrent(t.key),await this.#n.markAllVersionsAsNotDefault(t.key));const r=W({entity:{...t,...e,hash:_(JSON.stringify({...t,...e})),isCurrent:s,isDefaultVersion:s,createdAt:t.createdAt??void 0},organizationId:this.#r,projectId:this.#i,source:"remote",sourceFile:null,fileHash:null}),{key:o,source:u,scorecardsStatus:h,...f}=r,l=await this.#e.client.insert(i).values(r).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:{...f,scorecardsStatus:V`CASE WHEN ${i.scorecardsStatus} = 'CALCULATING' THEN 'CANCELLED' ELSE 'OUTDATED' END`}}).returning();return l.length?D(l[0]):null}catch(s){return y.error("Error updating entity",s),null}})}async setEntitiesAsOutdated(e){try{const t=O(e);await this.#e.client.update(i).set({scorecardsStatus:"OUTDATED"}).where(t)}catch(t){y.error("Error updating entities as outdated",t)}}async#l(e,t){const{key:s,source:n,version:r,isDefaultVersion:o,...u}=e,l=await this.#e.client.select({id:i.id}).from(i).where(v(c(i.key,s),r?c(i.version,r):R(i.version))).limit(1).get()!=null?await this.#e.client.update(i).set(u).where(v(c(i.key,s),r?c(i.version,r):R(i.version))).returning():await this.#e.client.insert(i).values(e).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:u}).returning(),p=t?.map(d=>{const g=b({relation:d,sourceFile:e.sourceFile??"",fileHash:e.fileHash??"",sourceKey:e.key,sourceVersion:e.version??null,sourceRevision:e.revision??null,organizationId:this.#r,projectId:this.#i});return this.#e.client.insert(a).values(g).onConflictDoUpdate({target:[a.sourceKey,a.targetKey,a.sourceVersion,a.targetVersion,a.sourceRevision,a.targetRevision,a.sourceToTargetRelation],set:g}).run()})??[];return await m(p,E,async d=>d),D(l[0])}async softDeleteEntitiesWithRelations({filter:e,revision:t,fileHash:s}){const r={op:"AND",conditions:[e,{field:"is_deleted",operator:"equal",value:!1}]};for(;;){const o=await this.#a.getEntities({paginationParams:{filter:r,limit:500}});if(o.items.length===0)break;const u=await this.#c({entities:o.items,revision:t,fileHash:s});await this.#d(u,t)}}async deleteEntity(e){return C("catalog_entities.remote_repository.delete_entity",async()=>{try{return await this.#s.deleteEntityRelationsOnEntityRemoval(e),await this.#e.client.delete(i).where(c(i.id,e.id)),await this.#t.ensureDefaultAndCurrentRevisionForKey(e.key),e.id}catch(t){return y.error("Error deleting entity",t),null}})}async deleteEntities(e){try{const t=O(e);if(!t)return!1;const s=await this.#e.client.delete(i).where(t).returning({key:i.key,source:i.source,isCurrent:i.isCurrent,isDefaultVersion:i.isDefaultVersion,version:i.version});if(s.length===0)return!0;const n=s.reduce((r,o)=>((o.isCurrent||o.isDefaultVersion)&&r.add(o.key),r),new Set);if(n.size===0)return!0;await m(Array.from(n),E,async r=>this.#t.ensureDefaultAndCurrentRevisionForKey(r));for(const r of s)await this.#e.client.delete(a).where(z(v(c(a.sourceKey,r.key),...r.version?[c(a.sourceVersion,r.version)]:[R(a.sourceVersion)]),v(c(a.targetKey,r.key),...r.version?[c(a.targetVersion,r.version)]:[R(a.targetVersion)])));return!0}catch(t){return y.error("Error deleting entities",t),!1}}async updateEntityScorecardsStatus(e,t){try{return(await this.#e.client.update(i).set({scorecardsStatus:t}).where(c(i.id,e)).returning()).length>0}catch(s){return y.error("Error updating entity scorecards status",s),!1}}async updateEntityScorecardsStatusIfCalculating(e,t){try{return(await this.#e.client.update(i).set({scorecardsStatus:t}).where(V`${i.id} = ${e} AND ${i.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}catch(s){return y.error("Error updating entity scorecards status if calculating",s),!1}}async#c({entities:e,revision:t,fileHash:s}){try{return(await m(e,E,async r=>{const o={type:r.type,key:r.key,title:r.title,summary:r.summary??void 0,tags:r.tags??void 0,metadata:r.metadata??void 0,git:r.git??void 0,contact:r.contact??void 0,links:r.links??void 0,version:r.version??void 0};return this.createEntity({entity:o,sourceFile:r.sourceFile??"",fileHash:s,isDeleted:!0,errorOnSkip:!1,source:r.source,revision:t})})).filter(r=>r!==null)}catch(n){return y.error("Error soft deleting entities",n),[]}}async#d(e,t){try{if(e.length===0)return!0;const s=await m(e,E,async n=>(await this.#u.getRelationsForEntity(n.key,n.version,t)).map(o=>{if(!o)return null;const u=o.direction,h=o.sourceToTargetRelation,f=o.targetKey,l=u==="outgoing"?n.key:f,p=u==="outgoing"?f:n.key,d=u==="outgoing"?h:h.startsWith("reverse:")?h.slice(8):h;return!d||!l||!p?null:j({type:d,sourceKey:l,targetKey:p,sourceVersion:n.version,targetVersion:n.version,sourceRevision:t,targetRevision:t,isDeleted:!0},this.#r,this.#i)}).filter(o=>o!==null));return await m(s.flat(),E,async n=>this.#s.upsertEntityRelation(n)),!0}catch(s){return y.error("Error soft deleting entity relations",s),!1}}}export{fe as EntitiesWriteRepository};
1
+ import{and as v,eq as c,isNull as R,or as x,sql as I}from"drizzle-orm";import{VERSION_NOT_SPECIFIED as V}from"@redocly/theme/core/constants";import{sha1 as F}from"../../../../../utils/crypto/sha1.js";import{logger as y}from"../../../../../tools/notifiers/logger.js";import{envConfig as K}from"../../../../../config/env-config.js";import{entitiesTable as i}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{convertFilterToWhereCondition as O}from"../../../../../providers/database/pagination/filter.js";import{entitiesRelationsTable as a}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{promiseMapLimit as E}from"../../../../../utils/async/promise-map-limit.js";import{RevisionRepository as z}from"../common/revision-repository.js";import{VersionRepository as H}from"../common/version-repository.js";import{EntityAttributesWriteRepository as P}from"../entityAttributes/entity-attributes-write-repository.js";import{createEntityDbRecord as W}from"../../mappers/create-entity-db-record.js";import{createEntityReadModel as C}from"../../mappers/create-entity-read-model.js";import{createEntityRelationDbRecordFromFileSchema as j}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{createEntityRelationDbRecordFromDto as G}from"../../mappers/create-entity-relation-db-record-from-dto.js";import{EntitiesReadRepository as M}from"./entities-read-repository.js";import{RelationsReadRepository as $}from"../relations/relations-read-repository.js";import{RelationsWriteRepository as b}from"../relations/relations-write-repository.js";const m=15;class he{#e;#t;#n;#o;#r;#i;#a;#u;#s;constructor(e,t,s){this.#e=e,this.#r=t,this.#i=s,this.#t=new z(e),this.#n=new H(e),this.#o=new P(e),this.#a=new M(e),this.#u=new $(e),this.#s=new b(e,t,s)}async createEntity({entity:e,source:t,fileHash:s,sourceFile:n,isRootEntity:r,isDeleted:o,rbacTeams:u,errorOnSkip:h=!1,revision:f}){const{relations:l=[],...p}=e,d=F(JSON.stringify(p)),g=e.version??V,D=f??new Date().toISOString(),L=await this.#t.shouldSkipRevisionCreation(e.key,g,d,r,o);if(Array.isArray(u)&&await this.#o.upsertEntityAttributes({entityKey:e.key,rbacTeams:u,organizationId:this.#r,projectId:this.#i}),L){if(h)throw new Error("Entity validation failed: entity already exists");return null}const{shouldSetNewCurrentRevision:w,hasCurrentRevision:_}=await this.#t.getCurrentRevisionInfo({key:e.key,version:g,revision:D}),k=W({entity:{...e,revision:D,hash:d,isCurrent:w,isDefaultVersion:w,isDeleted:o,version:g},organizationId:this.#r,projectId:this.#i,source:t,sourceFile:n??null,fileHash:s??null}),{key:A,...T}=k;if(w&&_&&(await this.#t.markAllRevisionsAsNotCurrent(A),await this.#n.markAllVersionsAsNotDefault(A)),K.isDevelopMode&&!K.REDOCLY_INTERNAL_DEV)return await this.#l(k,l);const S=await this.#e.client.insert(i).values(k).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:T}).returning();return S.length?(l&&await this.#s.createEntityRelations(l.map(N=>({...N,sourceKey:e.key,targetKey:N.key}))),C(S[0])):null}async updateEntity(e,t){try{const{shouldSetNewCurrentRevision:s,hasCurrentRevision:n}=await this.#t.getCurrentRevisionInfo({key:t.key,version:e.version??t.version??V,revision:t.revision});s&&n&&(await this.#t.markAllRevisionsAsNotCurrent(t.key),await this.#n.markAllVersionsAsNotDefault(t.key));const r=W({entity:{...t,...e,hash:F(JSON.stringify({...t,...e})),isCurrent:s,isDefaultVersion:s,createdAt:t.createdAt??void 0},organizationId:this.#r,projectId:this.#i,source:"remote",sourceFile:null,fileHash:null}),{key:o,source:u,scorecardsStatus:h,...f}=r,l=await this.#e.client.insert(i).values(r).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:{...f,scorecardsStatus:I`CASE WHEN ${i.scorecardsStatus} = 'CALCULATING' THEN 'CANCELLED' ELSE 'OUTDATED' END`}}).returning();return l.length?C(l[0]):null}catch(s){return y.error("Error updating entity",s),null}}async setEntitiesAsOutdated(e){try{const t=O(e);await this.#e.client.update(i).set({scorecardsStatus:"OUTDATED"}).where(t)}catch(t){y.error("Error updating entities as outdated",t)}}async#l(e,t){const{key:s,source:n,version:r,isDefaultVersion:o,...u}=e,l=await this.#e.client.select({id:i.id}).from(i).where(v(c(i.key,s),r?c(i.version,r):R(i.version))).limit(1).get()!=null?await this.#e.client.update(i).set(u).where(v(c(i.key,s),r?c(i.version,r):R(i.version))).returning():await this.#e.client.insert(i).values(e).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:u}).returning(),p=t?.map(d=>{const g=j({relation:d,sourceFile:e.sourceFile??"",fileHash:e.fileHash??"",sourceKey:e.key,sourceVersion:e.version??null,sourceRevision:e.revision??null,organizationId:this.#r,projectId:this.#i});return this.#e.client.insert(a).values(g).onConflictDoUpdate({target:[a.sourceKey,a.targetKey,a.sourceVersion,a.targetVersion,a.sourceRevision,a.targetRevision,a.sourceToTargetRelation],set:g}).run()})??[];return await E(p,m,async d=>d),C(l[0])}async softDeleteEntitiesWithRelations({filter:e,revision:t,fileHash:s}){const r={op:"AND",conditions:[e,{field:"is_deleted",operator:"equal",value:!1}]};for(;;){const o=await this.#a.getEntities({paginationParams:{filter:r,limit:500}});if(o.items.length===0)break;const u=await this.#c({entities:o.items,revision:t,fileHash:s});await this.#d(u,t)}}async deleteEntity(e){try{return await this.#s.deleteEntityRelationsOnEntityRemoval(e),await this.#e.client.delete(i).where(c(i.id,e.id)),await this.#t.ensureDefaultAndCurrentRevisionForKey(e.key),e.id}catch(t){return y.error("Error deleting entity",t),null}}async deleteEntities(e){try{const t=O(e);if(!t)return!1;const s=await this.#e.client.delete(i).where(t).returning({key:i.key,source:i.source,isCurrent:i.isCurrent,isDefaultVersion:i.isDefaultVersion,version:i.version});if(s.length===0)return!0;const n=s.reduce((r,o)=>((o.isCurrent||o.isDefaultVersion)&&r.add(o.key),r),new Set);if(n.size===0)return!0;await E(Array.from(n),m,async r=>this.#t.ensureDefaultAndCurrentRevisionForKey(r));for(const r of s)await this.#e.client.delete(a).where(x(v(c(a.sourceKey,r.key),...r.version?[c(a.sourceVersion,r.version)]:[R(a.sourceVersion)]),v(c(a.targetKey,r.key),...r.version?[c(a.targetVersion,r.version)]:[R(a.targetVersion)])));return!0}catch(t){return y.error("Error deleting entities",t),!1}}async updateEntityScorecardsStatus(e,t){try{return(await this.#e.client.update(i).set({scorecardsStatus:t}).where(c(i.id,e)).returning()).length>0}catch(s){return y.error("Error updating entity scorecards status",s),!1}}async updateEntityScorecardsStatusIfCalculating(e,t){try{return(await this.#e.client.update(i).set({scorecardsStatus:t}).where(I`${i.id} = ${e} AND ${i.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}catch(s){return y.error("Error updating entity scorecards status if calculating",s),!1}}async#c({entities:e,revision:t,fileHash:s}){try{return(await E(e,m,async r=>{const o={type:r.type,key:r.key,title:r.title,summary:r.summary??void 0,tags:r.tags??void 0,metadata:r.metadata??void 0,git:r.git??void 0,contact:r.contact??void 0,links:r.links??void 0,version:r.version??void 0};return this.createEntity({entity:o,sourceFile:r.sourceFile??"",fileHash:s,isDeleted:!0,errorOnSkip:!1,source:r.source,revision:t})})).filter(r=>r!==null)}catch(n){return y.error("Error soft deleting entities",n),[]}}async#d(e,t){try{if(e.length===0)return!0;const s=await E(e,m,async n=>(await this.#u.getRelationsForEntity(n.key,n.version,t)).map(o=>{if(!o)return null;const u=o.direction,h=o.sourceToTargetRelation,f=o.targetKey,l=u==="outgoing"?n.key:f,p=u==="outgoing"?f:n.key,d=u==="outgoing"?h:h.startsWith("reverse:")?h.slice(8):h;return!d||!l||!p?null:G({type:d,sourceKey:l,targetKey:p,sourceVersion:n.version,targetVersion:n.version,sourceRevision:t,targetRevision:t,isDeleted:!0},this.#r,this.#i)}).filter(o=>o!==null));return await E(s.flat(),m,async n=>this.#s.upsertEntityRelation(n)),!0}catch(s){return y.error("Error soft deleting entity relations",s),!1}}}export{he as EntitiesWriteRepository};
@@ -1 +1 @@
1
- import{and as a,eq as i,ne as m,or as h,sql as g}from"drizzle-orm";import{entitiesRelationsTable as t}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{logger as c}from"../../../../../tools/notifiers/logger.js";import{telemetryTraceStep as l}from"../../../../../telemetry/helpers/trace-step.js";import{entitiesTable as o}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{promiseMapLimit as f}from"../../../../../utils/async/promise-map-limit.js";import{convertFilterToWhereCondition as v}from"../../../../../providers/database/pagination/filter.js";import{createEntityRelationReadModel as w}from"../../mappers/create-entity-relation-read-model.js";import{createEntityRelationDbRecordFromDto as _}from"../../mappers/create-entity-relation-db-record-from-dto.js";const E=15;class k{#e;#t;#r;constructor(r,e,n){this.#e=r,this.#t=e,this.#r=n}async createEntityRelation(r){return l("catalog_entities.remote_repository.create_entity_relation",async()=>{if(!r)return null;try{const e=_(r,this.#t,this.#r),n=await this.upsertEntityRelation(e);return w(n)}catch(e){throw c.error("Error creating entity relation",e),e}})}async createEntityRelations(r){return l("catalog_entities.remote_repository.create_entity_relations",async()=>await f(r,E,async e=>this.createEntityRelation(e)))}async upsertEntityRelation(r){if(!r)return null;try{const{sourceKey:e,targetKey:n,sourceVersion:R,targetVersion:s,sourceRevision:y,targetRevision:p,...d}=r,u=await this.#e.client.insert(t).values(r).onConflictDoUpdate({target:[t.sourceKey,t.targetKey,t.sourceVersion,t.targetVersion,t.sourceRevision,t.targetRevision,t.sourceToTargetRelation],set:d}).returning();return u?.length?u[0]:null}catch(e){return c.error("Error creating entity relation",e),null}}async deleteEntityRelation(r){return l("catalog_entities.remote_repository.delete_entity_relation",async()=>{try{return await this.#e.client.delete(t).where(i(t.id,r)),r}catch(e){return c.error("Error deleting entity relation",e),null}})}async deleteEntitiesRelations(r){return l("catalog_entities.remote_repository.delete_entities_relations",async()=>{try{const e=v(r);return e?(await this.#e.client.delete(t).where(e),!0):!1}catch(e){return c.error("Error deleting entities relations",e),!1}})}async deleteEntityRelationsOnEntityRemoval(r){const{key:e,version:n,revision:R}=r,s=n??"",y=R??"";if(((await this.#e.client.select({count:g`COUNT(*)`}).from(o).where(a(i(o.key,e),m(o.id,r.id))).get())?.count??0)===0){await this.#e.client.delete(t).where(h(i(t.sourceKey,e),i(t.targetKey,e)));return}if(((await this.#e.client.select({count:g`COUNT(*)`}).from(o).where(a(i(o.key,e),i(o.version,s),m(o.id,r.id))).get())?.count??0)===0){await this.#e.client.delete(t).where(h(a(i(t.sourceKey,e),i(t.sourceVersion,s)),a(i(t.targetKey,e),i(t.targetVersion,s))));return}await this.#e.client.delete(t).where(h(a(i(t.sourceKey,e),i(t.sourceVersion,s),i(t.sourceRevision,y)),a(i(t.targetKey,e),i(t.targetVersion,s),i(t.targetRevision,y))))}}export{k as RelationsWriteRepository};
1
+ import{and as a,eq as i,ne as d,or as u,sql as g}from"drizzle-orm";import{entitiesRelationsTable as t}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{logger as c}from"../../../../../tools/notifiers/logger.js";import{entitiesTable as o}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{promiseMapLimit as f}from"../../../../../utils/async/promise-map-limit.js";import{convertFilterToWhereCondition as v}from"../../../../../providers/database/pagination/filter.js";import{createEntityRelationReadModel as p}from"../../mappers/create-entity-relation-read-model.js";import{createEntityRelationDbRecordFromDto as w}from"../../mappers/create-entity-relation-db-record-from-dto.js";const E=15;class U{#e;#t;#r;constructor(r,e,n){this.#e=r,this.#t=e,this.#r=n}async createEntityRelation(r){try{const e=w(r,this.#t,this.#r),n=await this.upsertEntityRelation(e);return p(n)}catch(e){throw c.error("Error creating entity relation",e),e}}async createEntityRelations(r){return await f(r,E,async e=>this.createEntityRelation(e))}async upsertEntityRelation(r){if(!r)return null;try{const{sourceKey:e,targetKey:n,sourceVersion:h,targetVersion:s,sourceRevision:l,targetRevision:m,...R}=r,y=await this.#e.client.insert(t).values(r).onConflictDoUpdate({target:[t.sourceKey,t.targetKey,t.sourceVersion,t.targetVersion,t.sourceRevision,t.targetRevision,t.sourceToTargetRelation],set:R}).returning();return y?.length?y[0]:null}catch(e){return c.error("Error creating entity relation",e),null}}async deleteEntityRelation(r){try{return await this.#e.client.delete(t).where(i(t.id,r)),r}catch(e){return c.error("Error deleting entity relation",e),null}}async deleteEntitiesRelations(r){try{const e=v(r);return e?(await this.#e.client.delete(t).where(e),!0):!1}catch(e){return c.error("Error deleting entities relations",e),!1}}async deleteEntityRelationsOnEntityRemoval(r){const{key:e,version:n,revision:h}=r,s=n??"",l=h??"";if(((await this.#e.client.select({count:g`COUNT(*)`}).from(o).where(a(i(o.key,e),d(o.id,r.id))).get())?.count??0)===0){await this.#e.client.delete(t).where(u(i(t.sourceKey,e),i(t.targetKey,e)));return}if(((await this.#e.client.select({count:g`COUNT(*)`}).from(o).where(a(i(o.key,e),i(o.version,s),d(o.id,r.id))).get())?.count??0)===0){await this.#e.client.delete(t).where(u(a(i(t.sourceKey,e),i(t.sourceVersion,s)),a(i(t.targetKey,e),i(t.targetVersion,s))));return}await this.#e.client.delete(t).where(u(a(i(t.sourceKey,e),i(t.sourceVersion,s),i(t.sourceRevision,l)),a(i(t.targetKey,e),i(t.targetVersion,s),i(t.targetRevision,l))))}}export{U as RelationsWriteRepository};
@@ -1 +1 @@
1
- import{removeLeadingSlash as R}from"@redocly/theme/core/utils";import{toKebabCase as f}from"../../../../../../utils/string/to-kebab-case.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{promiseMapLimitWithStatus as $}from"../../../../../utils/async/promise-map-limit-with-status.js";import{removeMarkdocTags as d}from"../../../../../../markdoc/helpers/remove-markdoc-tags.js";import{BaseApiEntitiesExtractor as E}from"./base.js";import{extractPartsFromComponentsRef as y}from"../../../utils/extract-parts-from-components-ref.js";import{extractPartsFromChannelsMessageRef as w}from"../../../utils/extract-parts-from-channels-message-ref.js";const u=15;class M extends E{constructor(e){super("asyncapi",e)}mapApiDescriptionToEntity(e,t){const a=R(e.realRelativePath),n=e.document.info.title,s=this.resolveEntityKey({realRelativePath:e.realRelativePath,customKey:e.document["x-redocly-catalog-key"]});return{type:this.type,key:s,title:n,summary:d(e.document.info.description),tags:e.document.info.tags?.map(r=>r.name),metadata:{specType:this.specType,descriptionFile:a},version:t}}async loadApiDescriptions(){return(await this.context.cache.load(".","asyncapi-docs")).data}async processApiDescription(e,t,a,n){const s=this.getRbacTeamsForDefinition(e.relativePath),r=this.mapApiDescriptionToEntity(e,a);await this.catalogEntitiesService.createEntity({entity:r,sourceFile:e.realRelativePath,fileHash:e.hash,isRootEntity:!0,rbacTeams:s,source:"file"}),n.delete(`${r.key}:${a}`);const o=this.#a(e),[i,c]=await Promise.allSettled([this.#s(e,r.key,r.version,a,t,n,s),this.#n(o,e,r.key,r.version,a,t,n,s)]);i.status!=="fulfilled"&&this.context.logger.error("Schema processing failed:",i.reason),c.status!=="fulfilled"&&this.context.logger.error("Operation processing failed:",c.reason)}#a=e=>{const t=e.documentWithReferences.operations;return t?Object.entries(t).map(([a,n])=>({operationName:a,operation:n})):[]};#s=async(e,t,a,n,s,r,o)=>{const i=e.document.components?.schemas;if(!i)return;const c=Object.entries(i);if(c.length===0)return;const l=await $(c,u,async([m,h])=>{const g=await this.#r({schemaName:m,schema:h,description:e,parentKey:t,parentVersion:a,parentRevision:s,rbacTeams:o});r.delete(`${g?.key}:${n}`),g&&await this.catalogEntitiesService.createEntityRelation({sourceKey:t,type:"uses",targetKey:g.key,sourceFile:e.realRelativePath,fileHash:e.hash,sourceVersion:a,targetVersion:a,sourceRevision:s,targetRevision:s})},this.context.logger);l.count.failed>0&&this.context.logger.warn(`Schema processing completed with ${l.count.failed} failures out of ${c.length} schemas for ${e.realRelativePath}`)};#r=async({schemaName:e,schema:t,description:a,parentKey:n,parentVersion:s,parentRevision:r,rbacTeams:o})=>{const i=`${n}-${f(e)}`,c="title"in t?t.title??t.description:null,l=JSON.stringify(t),m={type:"data-schema",key:i,title:e,summary:c,tags:[],metadata:{specType:this.specType,schema:l},version:s};try{return await this.catalogEntitiesService.createEntity({entity:m,sourceFile:a.realRelativePath,fileHash:a.hash,rbacTeams:o,source:"file",revision:r})}catch(h){return this.context.logger.warn(`Failed to create data schema entity for ${e}: ${h instanceof Error?h.message:"Unknown error"}`),null}};#n=async(e,t,a,n,s,r,o,i)=>{if(!e.length)return;const c=await $(e,u,async({operationName:l,operation:m})=>{const h=await this.#o(m,l,t,a,n,r,i);h&&o.delete(`${h}:${s}`)},this.context.logger);c.count.failed>0&&this.context.logger.warn(`Operation processing completed with ${c.count.failed} failures out of ${e.length} operations for ${t.realRelativePath}`)};#o=async(e,t,a,n,s,r,o)=>{const i=await this.#i(e.messages??[],a,n),c=await this.#c(t,e,i,a,n,s,r,o);return c&&await this.#l({apiOperationKey:c.key,operation:e,descriptionUniqueKey:n,description:a,descriptionVersion:s,parentRevision:r}),c?.key??null};#i=async(e,t,a)=>{if(!e||e.length===0)return[];const n=new Set;for(const s of e){let r=null;if("$ref"in s){const o=s.$ref.startsWith("#/channels/")?this.#e(s.$ref,t):s.$ref;if(o){const i=y(o);if(i?.componentName&&i.componentType==="messages"){const c=t.documentWithReferences.components?.messages[i.componentName];c?.payload&&"$ref"in c.payload&&c.payload.$ref&&(r=c.payload.$ref)}}}else s.payload&&"$ref"in s.payload&&s.payload.$ref&&(r=s.payload.$ref);if(r){const o=y(r);o?.componentName&&o.componentType==="schemas"&&n.add(`${a}-${f(o.componentName)}`)}}return Array.from(n)};#c=async(e,t,a,n,s,r,o,i)=>{const c={type:"api-operation",key:`${s}-${f(e)}`,title:t.title||e,summary:t.summary??"",tags:t.tags?.map(l=>l.name)??[],metadata:{method:t.action==="send"?"PUBLISH":"SUBSCRIBE",path:t.channel.address??"",payload:t.action==="receive"?a:[],responses:t.action==="send"?a:[]},version:r};try{return await this.catalogEntitiesService.createEntity({entity:c,sourceFile:n.realRelativePath,fileHash:n.hash,rbacTeams:i,source:"file",revision:o})}catch(l){return this.context.logger.warn(`Failed to create API operation entity for ${e}: ${l instanceof Error?l.message:"Unknown error"}`),null}};#l=async({apiOperationKey:e,operation:t,descriptionUniqueKey:a,description:n,descriptionVersion:s,parentRevision:r})=>(await this.catalogEntitiesService.createEntityRelation({sourceKey:e,type:"partOf",targetKey:a,sourceFile:n.realRelativePath,fileHash:n.hash,sourceVersion:s,targetVersion:s,sourceRevision:r,targetRevision:r}),await this.#h(t,e,n,s,r),!Array.isArray(t.messages)||!t.messages?.length?[]:await this.#m(t.messages,e,n,a,s,r));#h=async(e,t,a,n,s)=>{e["x-catalog-relations"]?.length&&await p(e["x-catalog-relations"],u,async r=>{try{this.validateEntityRelationFileSchema(r),await this.catalogEntitiesService.createEntityRelation({sourceKey:t,type:r.type,targetKey:r.key,sourceFile:a.realRelativePath,fileHash:a.hash,sourceVersion:n,targetVersion:"",sourceRevision:s,targetRevision:""})}catch(o){this.context.logger.warn(`Error creating entity relation for operation ${t} based on custom property: ${o instanceof Error?o.message:"Unknown error"}`)}})};#m=async(e,t,a,n,s,r)=>e.length===0?[]:(await p(e,u,async i=>await this.#f(i,t,a,n,s,r)??"")).filter(i=>!!i);#f=async(e,t,a,n,s,r)=>"$ref"in e?this.#y(e,t,a,n,s,r):e.payload?this.#u(e,t,a,n,s,r):null;#y=async(e,t,a,n,s,r)=>{const o=e.$ref.startsWith("#/channels/")?this.#e(e.$ref,a):e.$ref;if(!o)return null;const i=y(o);if(!i||!i.componentName||i.componentType!=="messages")return null;const c=a.documentWithReferences.components?.messages[i.componentName];return!c?.payload||!("$ref"in c.payload)||!c.payload.$ref?null:this.#t(c.payload.$ref,t,a,n,s,r)};#e=(e,t)=>{const a=w(e);if(!a||!a.channelName||!a.messageName)return null;const n=t.documentWithReferences.channels?.[a.channelName];if(!n?.messages)return null;const s=n.messages[a.messageName];return!s||!("$ref"in s)||typeof s.$ref!="string"||!s.$ref.startsWith("#/components/messages/")?null:s.$ref};#u=async(e,t,a,n,s,r)=>!e.payload||!("$ref"in e.payload)||!e.payload.$ref?null:this.#t(e.payload.$ref,t,a,n,s,r);#t=async(e,t,a,n,s,r)=>{const o=y(e);if(!o||!o.componentName||o.componentType!=="schemas")return null;const i=`${n}-${f(o.componentName)}`;return await this.catalogEntitiesService.createEntityRelation({sourceKey:t,type:"uses",targetKey:i,sourceFile:a.realRelativePath,fileHash:a.hash,sourceVersion:s,targetVersion:s,sourceRevision:r,targetRevision:r}),i}}export{M as AsyncApiEntitiesExtractor};
1
+ import{removeLeadingSlash as R}from"@redocly/theme/core/utils";import{toKebabCase as f}from"../../../../../../utils/string/to-kebab-case.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{promiseMapLimitWithStatus as $}from"../../../../../utils/async/promise-map-limit-with-status.js";import{removeMarkdocTags as d}from"../../../../../../markdoc/helpers/remove-markdoc-tags.js";import{BaseApiEntitiesExtractor as E}from"./base.js";import{extractPartsFromComponentsRef as y}from"../../../utils/extract-parts-from-components-ref.js";import{extractPartsFromChannelsMessageRef as w}from"../../../utils/extract-parts-from-channels-message-ref.js";const u=15;class M extends E{constructor(e){super("asyncapi",e)}mapApiDescriptionToEntity(e,t){const a=R(e.realRelativePath),n=e.document.info.title,s=this.resolveEntityKey({realRelativePath:e.realRelativePath,customKey:e.document["x-redocly-catalog-key"]});return{type:this.type,key:s,title:n,summary:d(e.document.info.description),tags:e.document.info.tags?.map(r=>r.name),metadata:{specType:this.specType,descriptionFile:a},version:t}}async loadApiDescriptions(){return(await this.context.cache.load(".","asyncapi-docs")).data}async processApiDescription(e,t,a,n){const s=this.getRbacTeamsForDefinition(e.relativePath),r=this.mapApiDescriptionToEntity(e,a);await this.catalogEntitiesService.createEntity({entity:r,sourceFile:e.realRelativePath,fileHash:e.hash,isRootEntity:!0,rbacTeams:s,source:"file"}),n.delete(`${r.key}:${a}`);const o=this.#a(e),[i,c]=await Promise.allSettled([this.#s(e,r.key,r.version,a,t,n,s),this.#n(o,e,r.key,r.version,a,t,n,s)]);i.status!=="fulfilled"&&this.context.logger.error("Schema processing failed:",i.reason),c.status!=="fulfilled"&&this.context.logger.error("Operation processing failed:",c.reason)}#a=e=>{const t=e.documentWithReferences.operations;return t?Object.entries(t).map(([a,n])=>({operationName:a,operation:n})):[]};#s=async(e,t,a,n,s,r,o)=>{const i=e.document.components?.schemas;if(!i)return;const c=Object.entries(i);if(c.length===0)return;const l=await $(c,u,async([m,h])=>{const g=await this.#r({schemaName:m,schema:h,description:e,parentKey:t,parentVersion:a,parentRevision:s,rbacTeams:o});r.delete(`${g?.key}:${n}`),g&&await this.catalogEntitiesService.createEntityRelation({sourceKey:t,type:"uses",targetKey:g.key,sourceFile:e.realRelativePath,fileHash:e.hash,sourceVersion:a,targetVersion:a,sourceRevision:s,targetRevision:s})},this.context.logger);l.count.failed>0&&this.context.logger.warn(`Schema processing completed with ${l.count.failed} failures out of ${c.length} schemas for ${e.realRelativePath}`)};#r=async({schemaName:e,schema:t,description:a,parentKey:n,parentVersion:s,parentRevision:r,rbacTeams:o})=>{const i=`${n}-${f(e)}`,c="title"in t?t.title??t.description:null,l=JSON.stringify(t),m={type:"data-schema",key:i,title:e,summary:c,tags:[],metadata:{specType:this.specType,schema:l},version:s};try{return await this.catalogEntitiesService.createEntity({entity:m,sourceFile:a.realRelativePath,fileHash:a.hash,rbacTeams:o,source:"file",revision:r})}catch(h){return this.context.logger.warn(`Failed to create data schema entity for ${e}: ${h instanceof Error?h.message:"Unknown error"}`),null}};#n=async(e,t,a,n,s,r,o,i)=>{if(!e.length)return;const c=await $(e,u,async({operationName:l,operation:m})=>{const h=await this.#o(m,l,t,a,n,r,i);h&&o.delete(`${h}:${s}`)},this.context.logger);c.count.failed>0&&this.context.logger.warn(`Operation processing completed with ${c.count.failed} failures out of ${e.length} operations for ${t.realRelativePath}`)};#o=async(e,t,a,n,s,r,o)=>{const i=await this.#i(e.messages??[],a,n),c=await this.#c(t,e,i,a,n,s,r,o);return c&&await this.#l({apiOperationKey:c.key,operation:e,descriptionUniqueKey:n,description:a,descriptionVersion:s,parentRevision:r}),c?.key??null};#i=async(e,t,a)=>{if(!e||e.length===0)return[];const n=new Set;for(const s of e){let r=null;if("$ref"in s){const o=s.$ref.startsWith("#/channels/")?this.#e(s.$ref,t):s.$ref;if(o){const i=y(o);if(i?.componentName&&i.componentType==="messages"){const c=t.documentWithReferences.components?.messages[i.componentName];c?.payload&&"$ref"in c.payload&&c.payload.$ref&&(r=c.payload.$ref)}}}else s.payload&&"$ref"in s.payload&&s.payload.$ref&&(r=s.payload.$ref);if(r){const o=y(r);o?.componentName&&o.componentType==="schemas"&&n.add(`${a}-${f(o.componentName)}`)}}return Array.from(n)};#c=async(e,t,a,n,s,r,o,i)=>{const c={type:"api-operation",key:`${s}-${f(e)}`,title:t.title||e,summary:t.summary??"",tags:t.tags?.map(l=>l.name)??[],metadata:{method:t.action==="send"?"PUBLISH":"SUBSCRIBE",path:t.channel.address??"",payload:t.action==="receive"?a:[],responses:t.action==="send"?a:[]},version:r};try{return await this.catalogEntitiesService.createEntity({entity:c,sourceFile:n.realRelativePath,fileHash:n.hash,rbacTeams:i,source:"file",revision:o})}catch(l){return this.context.logger.warn(`Failed to create API operation entity for ${e}: ${l instanceof Error?l.message:"Unknown error"}`),null}};#l=async({apiOperationKey:e,operation:t,descriptionUniqueKey:a,description:n,descriptionVersion:s,parentRevision:r})=>(await this.catalogEntitiesService.createEntityRelation({sourceKey:e,type:"partOf",targetKey:a,sourceFile:n.realRelativePath,fileHash:n.hash,sourceVersion:s,targetVersion:s,sourceRevision:r,targetRevision:r}),await this.#h(t,e,n,s,r),!Array.isArray(t.messages)||!t.messages?.length?[]:await this.#m(t.messages,e,n,a,s,r));#h=async(e,t,a,n,s)=>{e["x-catalogRelations"]?.length&&await p(e["x-catalogRelations"],u,async r=>{try{this.validateEntityRelationFileSchema(r),await this.catalogEntitiesService.createEntityRelation({sourceKey:t,type:r.type,targetKey:r.key,sourceFile:a.realRelativePath,fileHash:a.hash,sourceVersion:n,targetVersion:"",sourceRevision:s,targetRevision:""})}catch(o){this.context.logger.warn(`Error creating entity relation for operation ${t} based on custom property: ${o instanceof Error?o.message:"Unknown error"}`)}})};#m=async(e,t,a,n,s,r)=>e.length===0?[]:(await p(e,u,async i=>await this.#f(i,t,a,n,s,r)??"")).filter(i=>!!i);#f=async(e,t,a,n,s,r)=>"$ref"in e?this.#y(e,t,a,n,s,r):e.payload?this.#u(e,t,a,n,s,r):null;#y=async(e,t,a,n,s,r)=>{const o=e.$ref.startsWith("#/channels/")?this.#e(e.$ref,a):e.$ref;if(!o)return null;const i=y(o);if(!i||!i.componentName||i.componentType!=="messages")return null;const c=a.documentWithReferences.components?.messages[i.componentName];return!c?.payload||!("$ref"in c.payload)||!c.payload.$ref?null:this.#t(c.payload.$ref,t,a,n,s,r)};#e=(e,t)=>{const a=w(e);if(!a||!a.channelName||!a.messageName)return null;const n=t.documentWithReferences.channels?.[a.channelName];if(!n?.messages)return null;const s=n.messages[a.messageName];return!s||!("$ref"in s)||typeof s.$ref!="string"||!s.$ref.startsWith("#/components/messages/")?null:s.$ref};#u=async(e,t,a,n,s,r)=>!e.payload||!("$ref"in e.payload)||!e.payload.$ref?null:this.#t(e.payload.$ref,t,a,n,s,r);#t=async(e,t,a,n,s,r)=>{const o=y(e);if(!o||!o.componentName||o.componentType!=="schemas")return null;const i=`${n}-${f(o.componentName)}`;return await this.catalogEntitiesService.createEntityRelation({sourceKey:t,type:"uses",targetKey:i,sourceFile:a.realRelativePath,fileHash:a.hash,sourceVersion:s,targetVersion:s,sourceRevision:r,targetRevision:r}),i}}export{M as AsyncApiEntitiesExtractor};
@@ -1 +1 @@
1
- import{REDOCLY_TEAMS_RBAC as $}from"@redocly/config";import{removeLeadingSlash as x}from"@redocly/theme/core/utils";import{toKebabCase as y}from"../../../../../../utils/string/to-kebab-case.js";import{promiseMapLimit as E}from"../../../../../utils/async/promise-map-limit.js";import{promiseMapLimitWithStatus as N}from"../../../../../utils/async/promise-map-limit-with-status.js";import{extractTeamsFromScopeItems as R}from"../../../../../utils/rbac.js";import{removeMarkdocTags as w}from"../../../../../../markdoc/helpers/remove-markdoc-tags.js";import{BaseApiEntitiesExtractor as b}from"./base.js";import{OpenapiSchemaResolver as S}from"../../../utils/openapi-schema-resolver.js";import{extractPartsFromComponentsRef as k}from"../../../utils/extract-parts-from-components-ref.js";import{isValidTagName as v}from"../../../utils/is-valid-tag-name.js";const A=["get","post","put","delete","patch"],O=15,T=15;class _ extends b{#e;constructor(e){super("openapi",e),this.#e=e.context.logger}async loadApiDescriptions(){return await this.actions.loadOpenApiDefinitions(this.context)}mapApiDescriptionToEntity(e,n){const r=x(e.realRelativePath),s=e.definition.info.title,t=this.resolveEntityKey({realRelativePath:e.realRelativePath,customKey:e.definition["x-redocly-catalog-key"]});return{type:this.type,key:t,title:s,summary:w(e.definition.info.description),tags:e.definition.tags?.filter(a=>a.name&&v(a.name)).map(a=>a.name),metadata:{specType:this.specType,descriptionFile:r},version:n}}async processApiDescription(e,n,r,s){if(S.clearSchemaCache(),!e?.definition?.paths)return;const t=this.getRbacTeamsForDefinition(e.relativePath),a=this.mapApiDescriptionToEntity(e,r);await this.catalogEntitiesService.createEntity({entity:a,sourceFile:e.realRelativePath,fileHash:e.hash,source:"file",isRootEntity:!0,rbacTeams:t,revision:n}),s.delete(`${a.key}:${r}`),await this.#s(e,a.key,a.version,r,n,s,t);const o=this.#r(e.definition.paths);if(o.length!==0){const c=(await N(o,O,async({operation:m,path:h,method:l})=>{if(!m.operationId)return;const f=await this.#n(m,h,l,e,a.key,a.version,n,t);f&&s.delete(`${f}:${r}`)},this.#e)).count.failed;c>0&&this.#e.warn(`Extraction completed with ${c} failures out of ${o.length} operations for ${a.key}`)}}#r(e){return Object.entries(e).flatMap(([n,r])=>A.filter(s=>r[s]).map(s=>{const t=r[s];if(!t)throw new Error(`Operation not found for method ${s} on path ${n}`);return{operation:t,path:n,method:s.toUpperCase()}}))}async#s(e,n,r,s,t,a,o){const i=e.definition.components?.schemas;if(!i)return;const c=Object.entries(i);c.length!==0&&await E(c,T,async([m,h])=>{const l=await this.#o({schemaName:m,schema:h,description:e,parentKey:n,parentVersion:r,parentRevision:t,rbacTeams:o});a.delete(`${l?.key}:${s}`),l&&await this.catalogEntitiesService.createEntityRelation({sourceKey:n,sourceVersion:r,sourceRevision:t,type:"uses",targetKey:l.key,targetVersion:r,targetRevision:t,sourceFile:e.realRelativePath,fileHash:e.hash})})}async#o({schemaName:e,schema:n,description:r,parentKey:s,parentVersion:t,parentRevision:a,rbacTeams:o}){const i=S.resolveSchemaRefs(n,r),c=JSON.stringify(i),m=i[$]?R(i[$]):o,h=R(i["x-rbac"]),l={type:"data-schema",key:`${s}-${y(e)}`,title:e,summary:i.description??i.title??null,tags:[],metadata:{specType:this.specType,schema:c},version:t};try{return await this.catalogEntitiesService.createEntity({entity:l,sourceFile:r.realRelativePath,fileHash:r.hash,rbacTeams:h.length?h:m,source:"file",revision:a})}catch(f){return this.#e.error(`Failed to create data schema entity for ${e}: ${f instanceof Error?f.message:"Unknown error"}`),null}}async#n(e,n,r,s,t,a,o,i){const c=await Promise.all([this.#h(e.requestBody??null,s),this.#m(e.responses??{},s)]),m=c[0].schemaNames,h=c[1].schemaNames;c[0].errors.length>0&&this.#e.warn(`Schema extraction errors for operation ${e.operationId}: ${c[0].errors.join(", ")}`),c[1].errors.length>0&&this.#e.warn(`Response schema extraction errors for operation ${e.operationId}: ${c[1].errors.join(", ")}`);const l=m.map(u=>`${t}-${y(u)}`),f=h.map(u=>`${t}-${y(u)}`),p=await this.#f({path:n,method:r,operation:e,requestBodySchemasKeys:l,responseSchemasKeys:f,description:s,parentKey:t,parentVersion:a,parentRevision:o,rbacTeams:i});return p&&await Promise.all([this.#i(m,e.operationId??"",s.realRelativePath,s.hash,t,a,o),this.#c(h,e.operationId??"",s.realRelativePath,s.hash,t,a,o),this.#p({apiOperationKey:p.key,operationRelations:e["x-catalog-relations"],descriptionUniqueKey:t,description:s,parentVersion:a,parentRevision:o})]),p?.key??null}async#i(e,n,r,s,t,a,o){return e.length===0||!n?[]:await this.#a(e,n,r,s,t,a,o)}async#c(e,n,r,s,t,a,o){return e.length===0||!n?[]:await this.#a(e,n,r,s,t,a,o)}#h(e,n){const r=[],s=[];try{if(e&&"content"in e&&e.content)for(const t of Object.values(e.content)){if(!t.schema?.$ref)continue;const a=this.#t(t.schema.$ref);a?.componentType==="schemas"&&a.componentName&&r.push(a.componentName)}if(e&&e.$ref){const t=this.#t(e.$ref);if(t?.componentType==="requestBodies"&&t.componentName){const a=n.definition.components?.requestBodies?.[t.componentName];if(a&&"content"in a)for(const o of Object.values(a.content??{})){if(!o.schema?.$ref)continue;const i=this.#t(o.schema.$ref);i?.componentType==="schemas"&&i.componentName&&r.push(i.componentName)}}}}catch(t){s.push(`Failed to extract request body schemas: ${t instanceof Error?t.message:"Unknown error"}`)}return{schemaNames:[...new Set(r)],errors:s}}#m(e,n){const r=[],s=[];try{for(const t of Object.values(e)){if(t&&"content"in t&&t.content)for(const a of Object.values(t.content)){if(!a.schema?.$ref)continue;const o=this.#t(a.schema.$ref);o?.componentType==="schemas"&&o.componentName&&r.push(o.componentName)}if(t&&t.$ref){const a=this.#t(t.$ref);if(a?.componentType==="responses"&&a.componentName){const o=n.definition.components?.responses?.[a.componentName];if(o&&"content"in o)for(const i of Object.values(o.content??{})){if(!i.schema?.$ref)continue;const c=this.#t(i.schema.$ref);c?.componentType==="schemas"&&c.componentName&&r.push(c.componentName)}}}}}catch(t){s.push(`Failed to extract response schemas: ${t instanceof Error?t.message:"Unknown error"}`)}return{schemaNames:[...new Set(r)],errors:s}}#t(e){try{return k(e)}catch{return{componentType:"",componentName:null}}}async#a(e,n,r,s,t,a,o){return e.length===0?[]:await E(e,T,async i=>await this.#l({operationId:n,schemaName:i,descriptionSourceFile:r,descriptionHash:s,parentKey:t,parentVersion:a,parentRevision:o}))}async#l({operationId:e,schemaName:n,descriptionSourceFile:r,descriptionHash:s,parentKey:t,parentVersion:a,parentRevision:o}){const i=`${t}-${y(n)}`;try{return await this.catalogEntitiesService.createEntityRelation({sourceKey:`${t}-${y(e)}`,type:"uses",targetKey:i,sourceFile:r,fileHash:s,sourceVersion:a,targetVersion:a,sourceRevision:o,targetRevision:o}),i}catch(c){throw this.#e.error(`Failed to create relation between operation ${e} and schema ${n}: ${c instanceof Error?c.message:"Unknown error"}`),c}}async#f({path:e,method:n,operation:r,requestBodySchemasKeys:s,responseSchemasKeys:t,description:a,parentKey:o,parentVersion:i,parentRevision:c,rbacTeams:m}){const h=`${r.operationId}`,l=`${o}-${y(h)}`,f=r[$]?R(r[$]):m,p=R(r["x-rbac"]),u={type:"api-operation",key:l,title:h,summary:w(r.summary),tags:r.tags?.filter(g=>v(g)),metadata:{path:e,method:n,payload:s,responses:t},version:i};try{return await this.catalogEntitiesService.createEntity({entity:u,sourceFile:a.realRelativePath,fileHash:a.hash,rbacTeams:p.length>0?p:f,source:"file",revision:c})}catch(g){return this.#e.error(`Failed to create API operation entity for ${h}: ${g instanceof Error?g.message:"Unknown error"}`),null}}async#p({apiOperationKey:e,operationRelations:n,descriptionUniqueKey:r,description:s,parentVersion:t,parentRevision:a}){try{await this.catalogEntitiesService.createEntityRelation({sourceKey:e,type:"partOf",targetKey:r,sourceFile:s.realRelativePath,fileHash:s.hash,sourceVersion:t,targetVersion:t,sourceRevision:a,targetRevision:a}),await this.#y(e,s,n,t,a)}catch(o){this.#e.error(`Failed to create API operation relations for ${e}: ${o instanceof Error?o.message:"Unknown error"}`)}}async#y(e,n,r,s,t){r?.length&&await E(r,O,async a=>{try{this.validateEntityRelationFileSchema(a),await this.catalogEntitiesService.createEntityRelation({sourceKey:e,type:a.type,targetKey:a.key,sourceFile:n.realRelativePath,fileHash:n.hash,sourceVersion:s,targetVersion:"",sourceRevision:t,targetRevision:""})}catch(o){this.context.logger.warn(`Error creating entity relation for operation ${e} based on custom property: ${o instanceof Error?o.message:"Unknown error"}`)}})}}export{_ as OpenApiEntitiesExtractor};
1
+ import{REDOCLY_TEAMS_RBAC as $}from"@redocly/config";import{removeLeadingSlash as x}from"@redocly/theme/core/utils";import{toKebabCase as y}from"../../../../../../utils/string/to-kebab-case.js";import{promiseMapLimit as E}from"../../../../../utils/async/promise-map-limit.js";import{promiseMapLimitWithStatus as N}from"../../../../../utils/async/promise-map-limit-with-status.js";import{extractTeamsFromScopeItems as R}from"../../../../../utils/rbac.js";import{removeMarkdocTags as w}from"../../../../../../markdoc/helpers/remove-markdoc-tags.js";import{BaseApiEntitiesExtractor as b}from"./base.js";import{OpenapiSchemaResolver as S}from"../../../utils/openapi-schema-resolver.js";import{extractPartsFromComponentsRef as k}from"../../../utils/extract-parts-from-components-ref.js";import{isValidTagName as v}from"../../../utils/is-valid-tag-name.js";const A=["get","post","put","delete","patch"],O=15,T=15;class _ extends b{#e;constructor(e){super("openapi",e),this.#e=e.context.logger}async loadApiDescriptions(){return await this.actions.loadOpenApiDefinitions(this.context)}mapApiDescriptionToEntity(e,n){const s=x(e.realRelativePath),r=e.definition.info.title,t=this.resolveEntityKey({realRelativePath:e.realRelativePath,customKey:e.definition["x-redocly-catalog-key"]});return{type:this.type,key:t,title:r,summary:w(e.definition.info.description),tags:e.definition.tags?.filter(a=>a.name&&v(a.name)).map(a=>a.name),metadata:{specType:this.specType,descriptionFile:s},version:n}}async processApiDescription(e,n,s,r){if(S.clearSchemaCache(),!e?.definition?.paths)return;const t=this.getRbacTeamsForDefinition(e.relativePath),a=this.mapApiDescriptionToEntity(e,s);await this.catalogEntitiesService.createEntity({entity:a,sourceFile:e.realRelativePath,fileHash:e.hash,source:"file",isRootEntity:!0,rbacTeams:t,revision:n}),r.delete(`${a.key}:${s}`),await this.#r(e,a.key,a.version,s,n,r,t);const o=this.#s(e.definition.paths);if(o.length!==0){const c=(await N(o,O,async({operation:m,path:h,method:l})=>{if(!m.operationId)return;const f=await this.#n(m,h,l,e,a.key,a.version,n,t);f&&r.delete(`${f}:${s}`)},this.#e)).count.failed;c>0&&this.#e.warn(`Extraction completed with ${c} failures out of ${o.length} operations for ${a.key}`)}}#s(e){return Object.entries(e).flatMap(([n,s])=>A.filter(r=>s[r]).map(r=>{const t=s[r];if(!t)throw new Error(`Operation not found for method ${r} on path ${n}`);return{operation:t,path:n,method:r.toUpperCase()}}))}async#r(e,n,s,r,t,a,o){const i=e.definition.components?.schemas;if(!i)return;const c=Object.entries(i);c.length!==0&&await E(c,T,async([m,h])=>{const l=await this.#o({schemaName:m,schema:h,description:e,parentKey:n,parentVersion:s,parentRevision:t,rbacTeams:o});a.delete(`${l?.key}:${r}`),l&&await this.catalogEntitiesService.createEntityRelation({sourceKey:n,sourceVersion:s,sourceRevision:t,type:"uses",targetKey:l.key,targetVersion:s,targetRevision:t,sourceFile:e.realRelativePath,fileHash:e.hash})})}async#o({schemaName:e,schema:n,description:s,parentKey:r,parentVersion:t,parentRevision:a,rbacTeams:o}){const i=S.resolveSchemaRefs(n,s),c=JSON.stringify(i),m=i[$]?R(i[$]):o,h=R(i["x-rbac"]),l={type:"data-schema",key:`${r}-${y(e)}`,title:e,summary:i.description??i.title??null,tags:[],metadata:{specType:this.specType,schema:c},version:t};try{return await this.catalogEntitiesService.createEntity({entity:l,sourceFile:s.realRelativePath,fileHash:s.hash,rbacTeams:h.length?h:m,source:"file",revision:a})}catch(f){return this.#e.error(`Failed to create data schema entity for ${e}: ${f instanceof Error?f.message:"Unknown error"}`),null}}async#n(e,n,s,r,t,a,o,i){const c=await Promise.all([this.#h(e.requestBody??null,r),this.#m(e.responses??{},r)]),m=c[0].schemaNames,h=c[1].schemaNames;c[0].errors.length>0&&this.#e.warn(`Schema extraction errors for operation ${e.operationId}: ${c[0].errors.join(", ")}`),c[1].errors.length>0&&this.#e.warn(`Response schema extraction errors for operation ${e.operationId}: ${c[1].errors.join(", ")}`);const l=m.map(u=>`${t}-${y(u)}`),f=h.map(u=>`${t}-${y(u)}`),p=await this.#f({path:n,method:s,operation:e,requestBodySchemasKeys:l,responseSchemasKeys:f,description:r,parentKey:t,parentVersion:a,parentRevision:o,rbacTeams:i});return p&&await Promise.all([this.#i(m,e.operationId??"",r.realRelativePath,r.hash,t,a,o),this.#c(h,e.operationId??"",r.realRelativePath,r.hash,t,a,o),this.#p({apiOperationKey:p.key,operationRelations:e["x-catalogRelations"],descriptionUniqueKey:t,description:r,parentVersion:a,parentRevision:o})]),p?.key??null}async#i(e,n,s,r,t,a,o){return e.length===0||!n?[]:await this.#a(e,n,s,r,t,a,o)}async#c(e,n,s,r,t,a,o){return e.length===0||!n?[]:await this.#a(e,n,s,r,t,a,o)}#h(e,n){const s=[],r=[];try{if(e&&"content"in e&&e.content)for(const t of Object.values(e.content)){if(!t.schema?.$ref)continue;const a=this.#t(t.schema.$ref);a?.componentType==="schemas"&&a.componentName&&s.push(a.componentName)}if(e&&e.$ref){const t=this.#t(e.$ref);if(t?.componentType==="requestBodies"&&t.componentName){const a=n.definition.components?.requestBodies?.[t.componentName];if(a&&"content"in a)for(const o of Object.values(a.content??{})){if(!o.schema?.$ref)continue;const i=this.#t(o.schema.$ref);i?.componentType==="schemas"&&i.componentName&&s.push(i.componentName)}}}}catch(t){r.push(`Failed to extract request body schemas: ${t instanceof Error?t.message:"Unknown error"}`)}return{schemaNames:[...new Set(s)],errors:r}}#m(e,n){const s=[],r=[];try{for(const t of Object.values(e)){if(t&&"content"in t&&t.content)for(const a of Object.values(t.content)){if(!a.schema?.$ref)continue;const o=this.#t(a.schema.$ref);o?.componentType==="schemas"&&o.componentName&&s.push(o.componentName)}if(t&&t.$ref){const a=this.#t(t.$ref);if(a?.componentType==="responses"&&a.componentName){const o=n.definition.components?.responses?.[a.componentName];if(o&&"content"in o)for(const i of Object.values(o.content??{})){if(!i.schema?.$ref)continue;const c=this.#t(i.schema.$ref);c?.componentType==="schemas"&&c.componentName&&s.push(c.componentName)}}}}}catch(t){r.push(`Failed to extract response schemas: ${t instanceof Error?t.message:"Unknown error"}`)}return{schemaNames:[...new Set(s)],errors:r}}#t(e){try{return k(e)}catch{return{componentType:"",componentName:null}}}async#a(e,n,s,r,t,a,o){return e.length===0?[]:await E(e,T,async i=>await this.#l({operationId:n,schemaName:i,descriptionSourceFile:s,descriptionHash:r,parentKey:t,parentVersion:a,parentRevision:o}))}async#l({operationId:e,schemaName:n,descriptionSourceFile:s,descriptionHash:r,parentKey:t,parentVersion:a,parentRevision:o}){const i=`${t}-${y(n)}`;try{return await this.catalogEntitiesService.createEntityRelation({sourceKey:`${t}-${y(e)}`,type:"uses",targetKey:i,sourceFile:s,fileHash:r,sourceVersion:a,targetVersion:a,sourceRevision:o,targetRevision:o}),i}catch(c){throw this.#e.error(`Failed to create relation between operation ${e} and schema ${n}: ${c instanceof Error?c.message:"Unknown error"}`),c}}async#f({path:e,method:n,operation:s,requestBodySchemasKeys:r,responseSchemasKeys:t,description:a,parentKey:o,parentVersion:i,parentRevision:c,rbacTeams:m}){const h=`${s.operationId}`,l=`${o}-${y(h)}`,f=s[$]?R(s[$]):m,p=R(s["x-rbac"]),u={type:"api-operation",key:l,title:h,summary:w(s.summary),tags:s.tags?.filter(g=>v(g)),metadata:{path:e,method:n,payload:r,responses:t},version:i};try{return await this.catalogEntitiesService.createEntity({entity:u,sourceFile:a.realRelativePath,fileHash:a.hash,rbacTeams:p.length>0?p:f,source:"file",revision:c})}catch(g){return this.#e.error(`Failed to create API operation entity for ${h}: ${g instanceof Error?g.message:"Unknown error"}`),null}}async#p({apiOperationKey:e,operationRelations:n,descriptionUniqueKey:s,description:r,parentVersion:t,parentRevision:a}){try{await this.catalogEntitiesService.createEntityRelation({sourceKey:e,type:"partOf",targetKey:s,sourceFile:r.realRelativePath,fileHash:r.hash,sourceVersion:t,targetVersion:t,sourceRevision:a,targetRevision:a}),await this.#y(e,r,n,t,a)}catch(o){this.#e.error(`Failed to create API operation relations for ${e}: ${o instanceof Error?o.message:"Unknown error"}`)}}async#y(e,n,s,r,t){s?.length&&await E(s,O,async a=>{try{this.validateEntityRelationFileSchema(a),await this.catalogEntitiesService.createEntityRelation({sourceKey:e,type:a.type,targetKey:a.key,sourceFile:n.realRelativePath,fileHash:n.hash,sourceVersion:r,targetVersion:"",sourceRevision:t,targetRevision:""})}catch(o){this.context.logger.warn(`Error creating entity relation for operation ${e} based on custom property: ${o instanceof Error?o.message:"Unknown error"}`)}})}}export{_ as OpenApiEntitiesExtractor};
@@ -1,4 +1,4 @@
1
- import t from"styled-components";import{Box as i}from"@redocly/portal-legacy-ui";const n=t(i)`
1
+ import r from"styled-components";import{Box as o}from"@redocly/portal-legacy-ui";import{H1_CLASS as t,H2_CLASS as i,H3_CLASS as a}from"@redocly/theme/core/constants";const f=r(o)`
2
2
  max-width: 100%;
3
3
  margin-right: auto;
4
4
  margin-left: auto;
@@ -13,12 +13,17 @@ import t from"styled-components";import{Box as i}from"@redocly/portal-legacy-ui"
13
13
 
14
14
  h1,
15
15
  h2,
16
- h3 {
16
+ h3,
17
+ .${t},
18
+ .${i},
19
+ .${a} {
17
20
  font-weight: var(--heading-font-weight);
18
21
  }
19
22
 
20
23
  h1:first-child,
21
- h2:first-child {
24
+ h2:first-child,
25
+ .${t}:first-child,
26
+ .${i}:first-child {
22
27
  margin: 0;
23
28
  font-weight: var(--font-weight-regular);
24
29
  line-height: 1.4;
@@ -28,4 +33,4 @@ import t from"styled-components";import{Box as i}from"@redocly/portal-legacy-ui"
28
33
  margin-bottom: 0;
29
34
  margin-top: 0;
30
35
  }
31
- `;export{n as Container};
36
+ `;export{f as Container};
@@ -1 +1 @@
1
- import{extractTokenFromAuthHeader as u}from"../../../plugins/mcp/utils/jwt.js";import{getUserParamsFromCookies as d}from"../../../web-server/auth.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as i,RBAC_ALL_OTHER_TEAMS as a,ServerRoutes as l}from"../../../../constants/common.js";import{withPathPrefix as h}from"@redocly/theme/core/utils";function c(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[i]&&e[i]!=="none"||e[a]&&e[a]!=="none")}function k(e,t){if(!t||Object.keys(t).length===0)return e;const r=t.content;if(r&&Object.keys(r).length>0&&Object.values(r).some(c))return!0;const s=t.teamFoldersBaseRoles;return c(s)?!0:e}async function R(e,t){const r=e.headers.get("Authorization");if(!r)return{isAuthenticated:!1};const s=u(r),o=t?.config?.ssoDirect||{},n=s?await d(o,{authorization:s}):{};return s&&n&&n.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:n.teams||[],email:n.email||"",claims:n,isAuthenticated:!0,idpAccessToken:n.idpAccessToken||void 0,idpId:n.idpId||void 0},accessToken:s}:{isAuthenticated:!1,isTokenValid:!1}}function O(e){return new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer realm="${e}${l.MCP_OAUTH_PROTECTED_RESOURCE}${h("/mcp")}"`,"Access-Control-Allow-Origin":"*"}})}function _(){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer error="invalid_token", error_description="Invalid or expired token"',"Access-Control-Allow-Origin":"*"}})}export{_ as constructInvalidTokenResponse,O as constructUnauthorizedResponse,R as handleMcpAuth,k as shouldHandleMcpAuth};
1
+ import{extractTokenFromAuthHeader as u}from"../../../plugins/mcp/utils/jwt.js";import{getUserParamsFromCookies as d}from"../../../web-server/auth.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as i,RBAC_ALL_OTHER_TEAMS as a,ServerRoutes as l}from"../../../../constants/common.js";import{withPathPrefix as h}from"@redocly/theme/core/utils";function c(e){return!e||typeof e!="object"||Object.keys(e).length===0?!1:!(e[i]&&e[i]!=="none"||e[a]&&e[a]!=="none")}function k(e,t){if(!t||Object.keys(t).length===0)return e;const r=t.content;if(r&&Object.keys(r).length>0&&Object.values(r).some(c))return!0;const o=t.teamFoldersBaseRoles;return c(o)?!0:e}async function R(e,t){const r=e.headers.get("Authorization");if(!r)return{isAuthenticated:!1};const o=u(r),s=t?.config?.ssoDirect||{},n=o?await d(s,{authorization:o}):{};return o&&n&&n.isAuthenticated?{isAuthenticated:!0,isTokenValid:!0,currentUser:{teams:n.teams||[],email:n.email||"",claims:n,isAuthenticated:!0,idpAccessToken:n.idpAccessToken||void 0,idpId:n.idpId||void 0},accessToken:o}:{isAuthenticated:!1,isTokenValid:!1}}function O(e){return e=e.replace(/^http:\/\//,"https://"),new Response(JSON.stringify({error:"unauthorized",message:"Authentication required"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":`Bearer resource_metadata="${e}${l.MCP_OAUTH_PROTECTED_RESOURCE}${h("/mcp")}"`,"Access-Control-Allow-Origin":"*"}})}function _(){return new Response(JSON.stringify({error:"invalid_token",message:"Invalid or expired token"}),{status:401,headers:{"Content-Type":"application/json","WWW-Authenticate":'Bearer error="invalid_token", error_description="Invalid or expired token"',"Access-Control-Allow-Origin":"*"}})}export{_ as constructInvalidTokenResponse,O as constructUnauthorizedResponse,R as handleMcpAuth,k as shouldHandleMcpAuth};
@@ -0,0 +1,11 @@
1
+ import type { OpenAPIParser, ContentItemModel, OperationModel, Options, SchemaModel } from '@redocly/openapi-docs';
2
+ import type { OperationParameter } from '@redocly/theme/core/types';
3
+ import type { SearchDocument } from '../../types';
4
+ import { OpenApiSearchBuilder } from './openapi-search-builder.js';
5
+ export declare class AiSearchIndexer extends OpenApiSearchBuilder {
6
+ constructor(parser: OpenAPIParser, options: Options, basePath: string);
7
+ addItem(item: ContentItemModel): SearchDocument | undefined;
8
+ protected buildOperationDocument(operation: OperationModel): SearchDocument | undefined;
9
+ protected addSchema(parameters: Record<string, OperationParameter>, schema: SchemaModel | undefined, mediaType: string | undefined, place: string, isResponse: boolean, path?: string[], visitedPointers?: Set<string>, level?: number): void;
10
+ }
11
+ //# sourceMappingURL=ai-search-indexer.d.ts.map
@@ -0,0 +1 @@
1
+ import{REDOCLY_TEAMS_RBAC as l}from"@redocly/config";import{OpenApiSearchBuilder as m}from"./openapi-search-builder.js";class p extends m{constructor(t,r,n){super(t,r,n)}addItem(t){try{return this.buildSearchDocument(t)}catch(r){console.error("Cannot add item to AI search indexer",r.message);return}}buildOperationDocument(t){return this.createOperationDocumentBase(t)}addSchema(t,r,n,d,i,a=[],o=new Set,u=0){if(!(!r||r.isCircular)){if(r.pointer){if(o.has(r.pointer))return;o.add(r.pointer)}if(!(u>this.options.generatedSamplesMaxDepth)){if(r.fields)for(const e of r.fields){if(r[l]||e.kind==="additionalProperties"||e.schema?.readOnly&&!i||e.schema?.writeOnly&&i)continue;const c=this.createSchemaFieldParameter(e,r,d,n,a),f=this.getParameterId(c);t[f]==null&&(t[f]=c,this.addSchema(t,e.schema,n,d,i,a.concat([e.name]),o,u+1))}r.items&&this.addSchema(t,r.items,n,d,i,a,o,u+1)}}}}export{p as AiSearchIndexer};
@@ -0,0 +1,24 @@
1
+ import type { OperationMenuItem } from '@redocly/openapi-docs/lib/models/types.js';
2
+ import type { ContentItemModel, OpenAPIParser, OperationModel, Options, SchemaModel } from '@redocly/openapi-docs';
3
+ import type { OperationParameter } from '@redocly/theme/core/types';
4
+ import type { SearchDocument } from '../../types';
5
+ type OperationParameterModel = OperationModel['parameters'][number];
6
+ type SchemaFieldModel = NonNullable<SchemaModel['fields']>[number];
7
+ export declare abstract class OpenApiSearchBuilder {
8
+ #private;
9
+ protected readonly parser: OpenAPIParser;
10
+ protected readonly options: Options;
11
+ protected readonly basePath: string;
12
+ constructor(parser: OpenAPIParser, options: Options, basePath: string);
13
+ getOperation(item: OperationMenuItem): OperationModel;
14
+ protected buildSearchDocument(item: ContentItemModel): SearchDocument | undefined;
15
+ protected abstract buildOperationDocument(operation: OperationModel, rbac: Record<string, string> | undefined): SearchDocument | undefined;
16
+ protected collectOperationSearchParameters(operation: OperationModel): OperationParameter[];
17
+ protected abstract addSchema(parameters: Record<string, OperationParameter>, schema: SchemaModel | undefined, mediaType: string | undefined, place: string, isResponse: boolean, path?: string[], visitedPointers?: Set<string>, level?: number): void;
18
+ protected createOperationDocumentBase(operation: OperationModel, rbac?: Record<string, string> | undefined): SearchDocument | undefined;
19
+ protected createOperationParameter(parameter: OperationParameterModel): OperationParameter;
20
+ protected createSchemaFieldParameter(field: SchemaFieldModel, schema: SchemaModel, place: string, mediaType: string | undefined, path: string[]): OperationParameter;
21
+ protected getParameterId(param: OperationParameter): string;
22
+ }
23
+ export {};
24
+ //# sourceMappingURL=openapi-search-builder.d.ts.map
@@ -0,0 +1 @@
1
+ import*as u from"@redocly/openapi-docs";import{REDOCLY_TEAMS_RBAC as i}from"@redocly/config";import{combineUrls as d}from"@redocly/theme/core/utils";import{removeMarkdownLinks as c,stripFormatting as a}from"./utils.js";const o=u.default||u;class g{parser;options;basePath;constructor(e,t,n){this.parser=e,this.options=t,this.basePath=n}getOperation(e){return o.getOperation(this.parser,e.operationDefinition,e.parent,{...this.options,internal_skipSamples:!0},e.href)}buildSearchDocument(e){switch(e.type){case"tag":case"section":return this.#e(e,!0);case"operation":return this.buildOperationDocument(this.getOperation(e),e[i]);case"rsrc":case"prompt":case"tool":return this.#e(e,!1);default:return}}collectOperationSearchParameters(e){const t={};for(const r of e.parameters){if(r[i])continue;const s=this.createOperationParameter(r);t[this.getParameterId(s)]=s}const n=new Set;this.addSchema(t,e.requestBody?.content?.mediaTypes[0]?.schema,e.requestBody?.content?.mediaTypes[0]?.name,"request fields",!1,[],n);for(const r of e.responses)this.addSchema(t,r.content?.mediaTypes[0]?.schema,r.content?.mediaTypes[0]?.name,`response ${r.code} fields`,!0,[],n);return Object.values(t)}createOperationDocumentBase(e,t){if(e.type!=="operation")return;const n=d(this.basePath,e.href);return{id:n,url:n,title:a(e.name),text:a(c(e.description||"")),httpMethod:e.httpVerb,httpPath:e.path,deprecated:e.deprecated,security:e.security.map(r=>r.schemes.map(s=>s.id)).flat().filter(Boolean),parameters:this.collectOperationSearchParameters(e),...t?{[i]:t}:{}}}createOperationParameter(e){const t=e.schema?.example||e.example;return{name:e.name,description:a(c(e.description)),place:`${e.in} parameters`,mediaType:void 0,type:e.schema?.type.toString()||"unknown",deepLink:o.generateDeepLink(e),required:e.required,example:t?JSON.stringify(t):void 0,enum:e.schema?.enum?.length?e.schema.enum:void 0}}createSchemaFieldParameter(e,t,n,r,s){const p=e.schema?.example||e.example,m=e.schema?.enum;return{name:e.name,description:a(e.description),place:n,mediaType:r,path:s,deepLink:o.generateDeepLink(e),type:e.schema?.type.toString()||"unknown",required:e.required||t.schema.required?.includes(e.name)||!1,example:p?JSON.stringify(p):void 0,enum:m?.length?m:void 0}}getParameterId(e){return[...e.path||[],e.name.toString()].join(".")+e.description+e.place}#e(e,t){const n=d(this.basePath,e.href);return{id:n,url:n,text:a(c(e.description||"")),title:t?a(e.name):e.name}}}export{g as OpenApiSearchBuilder};
@@ -1,4 +1,4 @@
1
- import{REDOCLY_TEAMS_RBAC as k}from"@redocly/config";import{basename as F,join as L}from"node:path";import{toMarkdown as _}from"../../../plugins/markdown/search/to-markdown.js";import{canDownloadApiDefinition as q,isResourcePubliclyAccessible as G}from"../../../utils/rbac.js";import{PUBLIC_API_DEFINITIONS_FOLDER as J}from"../../../constants/common.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as z}from"../../../../constants/common.js";import{getLlmsTxtMdPathBySlug as B}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{SearchIndexer as V}from"../search-indexer.js";import{getLocaleFromRelativePath as C}from"../../../fs/utils/get-locale-from-relative-path.js";import{extractDocumentSearchFacets as Y}from"./search-facets.js";import{formatDocumentMetadata as H}from"../../search/utils.js";import{llmsTxtLink as E}from"../../search/llmstxt/index.js";import{replaceFileExtension as K}from"../store-definition-bundles.js";const x=new Map,ge=({parser:s,options:c,info:t,tagOperations:i,relativePath:n,openapiContentItem:e,metadata:r,getSearchFacets:o,includeInLLMsTxt:a,excludeFromSearch:f})=>async(u,m,l)=>{if(f)return;const D=await u.getNavText?.()||F(u.fsPath),A=new V(s,c,u.baseSlug||u.slug),p=A.addItem(e);if(!p)return;const O=await l.getConfig(),S=Array.isArray(p.title)?p.title.join(" "):p.title;let g,d,$=x.get(n);if((e.type==="tag"||e.type==="section"&&e.infoDefinition)&&!$){const{all:b,publiclyAccessible:y}=ne(i.tagged,A,O),{all:T,publiclyAccessible:w}=te(i.untagged,A,O);x.set(n,{taggedSearchDocuments:new Map(b),untaggedSearchDocuments:T,publiclyAccessibleTaggedSearchDocuments:new Map(y),publiclyAccessibleUntaggedSearchDocuments:w})}$=x.get(n);const h="#";switch(e.type){case"operation":const b=A.getOperation(e);g=d=Z({title:S,security:s.definition.security,document:p,version:p.version||t.version,headingLevel:h,operation:b});break;case"section":if(e.infoDefinition){$=x.get(n),d=await M({parser:s,info:t,staticData:m,relativePath:n,headingLevel:h,pageName:D,config:O}),d+=`
1
+ import{REDOCLY_TEAMS_RBAC as k}from"@redocly/config";import{basename as F,join as L}from"node:path";import{toMarkdown as _}from"../../../plugins/markdown/search/to-markdown.js";import{canDownloadApiDefinition as q,isResourcePubliclyAccessible as G}from"../../../utils/rbac.js";import{PUBLIC_API_DEFINITIONS_FOLDER as J}from"../../../constants/common.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as z}from"../../../../constants/common.js";import{getLlmsTxtMdPathBySlug as B}from"../../../utils/llmstxt/get-llms-txt-md-path-by-slug.js";import{AiSearchIndexer as V}from"../ai-search-indexer.js";import{getLocaleFromRelativePath as C}from"../../../fs/utils/get-locale-from-relative-path.js";import{extractDocumentSearchFacets as Y}from"./search-facets.js";import{formatDocumentMetadata as H}from"../../search/utils.js";import{llmsTxtLink as E}from"../../search/llmstxt/index.js";import{replaceFileExtension as K}from"../store-definition-bundles.js";const x=new Map,ge=({parser:s,options:c,info:t,tagOperations:i,relativePath:n,openapiContentItem:e,metadata:r,getSearchFacets:o,includeInLLMsTxt:a,excludeFromSearch:f})=>async(u,m,l)=>{if(f)return;const D=await u.getNavText?.()||F(u.fsPath),A=new V(s,c,u.baseSlug||u.slug),p=A.addItem(e);if(!p)return;const O=await l.getConfig(),S=Array.isArray(p.title)?p.title.join(" "):p.title;let g,d,$=x.get(n);if((e.type==="tag"||e.type==="section"&&e.infoDefinition)&&!$){const{all:b,publiclyAccessible:y}=ne(i.tagged,A,O),{all:T,publiclyAccessible:w}=te(i.untagged,A,O);x.set(n,{taggedSearchDocuments:new Map(b),untaggedSearchDocuments:T,publiclyAccessibleTaggedSearchDocuments:new Map(y),publiclyAccessibleUntaggedSearchDocuments:w})}$=x.get(n);const h="#";switch(e.type){case"operation":const b=A.getOperation(e);g=d=Z({title:S,security:s.definition.security,document:p,version:p.version||t.version,headingLevel:h,operation:b});break;case"section":if(e.infoDefinition){$=x.get(n),d=await M({parser:s,info:t,staticData:m,relativePath:n,headingLevel:h,pageName:D,config:O}),d+=`
2
2
  `;for(const[T,w]of $?.publiclyAccessibleTaggedSearchDocuments?.entries()||[]){if(!w.length)continue;const U=s.definition.tags?.find(N=>N.name===T);d+=j({title:U?.["x-displayName"]||T,description:U?.description,operationSearchDocuments:w,headingLevel:`${h}#`})}const y=$?.publiclyAccessibleUntaggedSearchDocuments||[];y.length&&(d+=j({title:"Other",description:void 0,operationSearchDocuments:y,headingLevel:`${h}#`})),g=await M({parser:s,info:t,staticData:m,relativePath:n,headingLevel:h,pageName:D,config:O})}else e.ast&&(d=g=_(e.ast));break;case"tag":d=j({title:S,description:e.description,operationSearchDocuments:$?.publiclyAccessibleTaggedSearchDocuments.get(e.name)||[],headingLevel:h}),g=j({title:S,description:e.description,operationSearchDocuments:[],headingLevel:h});break;case"rsrc":case"prompt":case"tool":d=I({title:S,description:e.description,name:e.name,xMcpConfig:s.definition["x-mcp"],headingLevel:h}),g=d;break}return{async getLLMsTxts(){return[{title:e.name,description:e.type==="tag"?e.description:void 0,content:d||"",slug:u.slug,fsPath:u.fsPath,includeInLLMsTxt:a}]},async getSearchDocuments(){if(e.type==="operation"||e.type==="section"&&(e.infoDefinition||e.ast)||e.type==="tag"){const b=C(u.fsPath),y=Y({...p,...r},t,o);return[{title:S,description:Array.isArray(p.text)?p.text.join(" "):p.text,content:g||"",url:p.url??u.slug,fsPath:u.fsPath,locale:b,product:u.product?.name,rbacTeams:p.rbacTeams,facets:y}]}return[]}}};async function M({parser:s,info:c,staticData:t,relativePath:i,pageName:n,headingLevel:e,config:r}){const o=H(c["x-metadata"]);let a=c.title?`${e} ${c.title}
3
3
 
4
4
  `:`${e} ${n}
@@ -1,19 +1,17 @@
1
- import type { OperationMenuItem } from '@redocly/openapi-docs/src/models/types.js';
2
- import type { OperationModel, OpenAPIParser, ContentItemModel, Options } from '@redocly/openapi-docs';
1
+ import type { OpenAPIParser, ContentItemModel, OperationModel, Options, SchemaModel } from '@redocly/openapi-docs';
3
2
  import type { OpenAPIInfo } from '@redocly/openapi-docs/lib/types';
3
+ import type { OperationParameter } from '@redocly/theme/core/types';
4
4
  import type { SearchDocument } from '../../types';
5
- export declare class SearchIndexer {
5
+ import { OpenApiSearchBuilder } from './openapi-search-builder.js';
6
+ export declare class SearchIndexer extends OpenApiSearchBuilder {
6
7
  #private;
7
8
  constructor(parser: OpenAPIParser, options: Options, basePath: string);
8
9
  addItem(item: ContentItemModel): SearchDocument | undefined;
9
- addInfo(info: OpenAPIInfo, metadata: Record<string, any>): {
10
- id: string;
11
- url: string;
12
- text: string;
13
- title: string;
14
- metadata: Record<string, any>;
10
+ addInfo(info: OpenAPIInfo, metadata: Record<string, unknown>): SearchDocument & {
11
+ metadata: Record<string, unknown>;
15
12
  };
16
13
  getResult(): SearchDocument[];
17
- getOperation(item: OperationMenuItem): OperationModel;
14
+ protected buildOperationDocument(operation: OperationModel, rbac: Record<string, string> | undefined): SearchDocument | undefined;
15
+ protected addSchema(parameters: Record<string, OperationParameter>, schema: SchemaModel | undefined, mediaType: string | undefined, place: string, isResponse: boolean, path?: string[], visitedPointers?: Set<string>, level?: number): void;
18
16
  }
19
17
  //# sourceMappingURL=search-indexer.d.ts.map
@@ -1 +1 @@
1
- import*as y from"@redocly/openapi-docs";import{REDOCLY_TEAMS_RBAC as c}from"@redocly/config";import{combineUrls as l}from"@redocly/theme/core/utils";import{SEARCH_DOCUMENT_METADATA_KEY as k}from"../../constants/plugins/search.js";import{removeMarkdownLinks as p,stripFormatting as a}from"./utils.js";import{normalizeFrontmatterKeywords as O}from"../helpers/normalize-frontmatter-keywords.js";const h=y.default||y;class q{#n=[];#s;#r;#e;#i;constructor(e,t,n){this.#s=e,this.#r=t,this.#e=n}addItem(e){const{result:t}=O(e.keywords||e.operationDefinition?.keywords);t?.excludes&&e.type==="section"&&e.id===""&&(this.#i=t.excludes);try{let n;switch(e.type){case"tag":n=this.#c(e);break;case"operation":const d=this.getOperation(e);n=this.#d(d,e[c]);break;case"section":n=this.#p(e);break;case"rsrc":case"prompt":case"tool":n=this.#o(e);break}if(!n)return;const s=[...new Set([...t?.excludes?t.excludes:[],...this.#i?this.#i:[]])];return(t||s.length)&&(n[k]={curated:!0,...t,excludes:s}),this.#n.push(n),n}catch(n){console.error("Cannot add item to search indexer",n.message)}}addInfo(e,t){const n=this.#e,s={id:n,url:n,text:a(p(e.description||"")),title:a(`${e.title} (${e.version})`),metadata:t};return this.#n.push(s),s}#o(e){const t=l(this.#e,e.href);return{id:t,url:t,text:a(p(e.description||"")),title:e.name}}getResult(){return this.#n}getOperation(e){return h.getOperation(this.#s,e.operationDefinition,e.parent,{...this.#r,internal_skipSamples:!0},e.href)}#a(e){return[...e.path||[],e.name.toString()].join(".")+e.description+e.place}#d(e,t){if(e.type!=="operation")return;let n={};for(let r of e.parameters){if(r[c])continue;const o=r.schema?.example||r.example,i={name:r.name,description:a(p(r.description)),place:r.in+" parameters",mediaType:void 0,type:r.schema?.type.toString()||"unknown",deepLink:h.generateDeepLink(r),[c]:r[c],required:r.required,example:o?JSON.stringify(o):void 0,enum:r.schema?.enum?.length?r.schema.enum:void 0};n[this.#a(i)]=i}const s=new Set;this.#t(n,e.requestBody?.content?.mediaTypes[0]?.schema,e.requestBody?.content?.mediaTypes[0]?.name,"request fields",!1,[],s);for(let r of e.responses){const o=`response ${r.code} fields`;this.#t(n,r.content?.mediaTypes[0]?.schema,r.content?.mediaTypes[0]?.name,o,!0,[],s)}let d=l(this.#e,e.href);return{id:d,url:d,title:a(e.name),text:a(p(e.description||"")),httpMethod:e.httpVerb,httpPath:e.path,deprecated:e.deprecated,isAdditionalOperation:e.isAdditionalOperation,security:e.security.map(r=>r.schemes.map(o=>o.id)).flat().filter(Boolean),parameters:Object.values(n),badges:e.badges.length?e.badges:void 0,[c]:t}}#t(e,t,n,s,d,u=[],r=new Set,o=0){if(!(!t||t.isCircular)){if(t.pointer){if(r.has(t.pointer))return;r.add(t.pointer)}if(!(o>this.#r.generatedSamplesMaxDepth)){if(t?.fields)for(let i of t.fields){if(t[c]||i.kind==="additionalProperties"||i.schema?.readOnly&&!d||i.schema?.writeOnly&&d)continue;const m=i.schema?.example||i.example,f=i.schema?.enum,g={name:i.name,description:a(i.description),place:s,mediaType:n,path:u,deepLink:h.generateDeepLink(i),type:i.schema?.type.toString()||"unknown",required:i.required||t.schema.required?.includes(i.name)||!1,example:m?JSON.stringify(m):void 0,enum:f?.length?f:void 0},x=this.#a(g);e[x]==null&&(e[x]=g,this.#t(e,i.schema,n,s,d,u.concat([i.name]),r,o+1))}t?.items&&this.#t(e,t.items,n,s,d,u,r,o+1)}}}#c(e){const t=l(this.#e,e.href);return{id:t,url:t,text:a(p(e.description||"")),title:a(e.name)}}#p(e){const t=l(this.#e,e.href);return{id:t,url:t,text:a(p(e.description||"")),title:a(e.name)}}}export{q as SearchIndexer};
1
+ import{REDOCLY_TEAMS_RBAC as f}from"@redocly/config";import{SEARCH_DOCUMENT_METADATA_KEY as h}from"../../constants/plugins/search.js";import{removeMarkdownLinks as m,stripFormatting as l}from"./utils.js";import{OpenApiSearchBuilder as p}from"./openapi-search-builder.js";import{normalizeFrontmatterKeywords as x}from"../helpers/normalize-frontmatter-keywords.js";class b extends p{#t=[];#e;constructor(e,t,r){super(e,t,r)}addItem(e){const{result:t}=x(e.keywords||e.operationDefinition?.keywords);t?.excludes&&e.type==="section"&&e.id===""&&(this.#e=t.excludes);try{const r=this.buildSearchDocument(e);if(!r)return;const n=[...new Set([...t?.excludes?t.excludes:[],...this.#e?this.#e:[]])];return(t||n.length)&&(r[h]={curated:!0,...t,excludes:n}),this.#t.push(r),r}catch(r){console.error("Cannot add item to search indexer",r.message)}}addInfo(e,t){const r=this.basePath,n={id:r,url:r,text:l(m(e.description||"")),title:l(`${e.title} (${e.version})`),metadata:t};return this.#t.push(n),n}getResult(){return this.#t}buildOperationDocument(e,t){const r=this.createOperationDocumentBase(e,t);if(r)return{...r,isAdditionalOperation:e.isAdditionalOperation,badges:e.badges.length?e.badges:void 0}}addSchema(e,t,r,n,o,s=[],d=new Set,a=0){if(!(!t||t.isCircular)){if(t.pointer){if(d.has(t.pointer))return;d.add(t.pointer)}if(!(a>this.options.generatedSamplesMaxDepth)){if(t.fields)for(const i of t.fields){if(t[f]||i.kind==="additionalProperties"||i.schema?.readOnly&&!o||i.schema?.writeOnly&&o)continue;const c=this.createSchemaFieldParameter(i,t,n,r,s),u=this.getParameterId(c);e[u]==null&&(e[u]=c,this.addSchema(e,i.schema,r,n,o,s.concat([i.name]),d,a+1))}t.items&&this.addSchema(e,t.items,r,n,o,s,d,a+1)}}}}export{b as SearchIndexer};
@@ -1 +1 @@
1
- import{REDOCLY_ROUTE_RBAC as E,REDOCLY_TEAMS_RBAC as D}from"@redocly/config";import{existsSync as B}from"fs";import{rm as $}from"node:fs/promises";import y from"node:path";import{DEFAULT_LOCALE_PLACEHOLDER as H}from"../../../../constants/common.js";import{SEARCH_DATA_EXPORT_FOLDER as S}from"../../../constants/plugins/search.js";import{envConfig as m}from"../../../config/env-config.js";import{logger as i}from"../../../tools/notifiers/logger.js";import{extractTeamsFromScopeItems as M,getRbacTeamsListForResource as P}from"../../../utils/index.js";import{getSearchDocumentGroup as U}from"../utils.js";import{telemetry as V}from"../../../../cli/telemetry/index.js";import{telemetryTraceStep as A}from"../../../../cli/telemetry/helpers/trace-step.js";async function Q(R,r,f){await A("build.plugin.search.prepare_search_documents",async()=>{i.info("Preparing search documents and create indexes...");const u=m.SEARCH_DEV_DEBUG?y.join(r.contentDir,"..","public","client"):r.outdir;(m.SEARCH_DEV_DEBUG||m.isBuildMode)&&B(`${u}/${S}`)&&await $(`${u}/${S}`,{recursive:!0});const C=i.startTiming(),b=r.getConfig().access?.rbac,v=[H,...R.localeFolders.map(a=>a.toLowerCase())];for(const a of v)await A("build.plugin.search.prepare_search_documents.locale",async s=>{const F=r.getAllRoutesForLocale(a);let g=0,h=0;for(const t of F){if(g++,t.excludeFromSearch)continue;const d=new Map,{product:l}=t,L=await t.getStaticData?.(t,r)||{},c=await t.getSearchDocuments?.(t,{...L,[D]:t[D],[E]:t[E]},r),o=t.versions?.find(n=>n.active),w=P(t,b??{});if(c){if(c&&c.length){h+=c.length;for(const n in c){let e=c[n];const T=M(e?.[D]),_=T?.length?T:w,x=e.tags||[];e={...e,...o&&{version:o.version,isDefaultVersion:o.default,versionFolderId:o.folderId},...l&&{product:l},rbacTeams:_,tags:[...x,..._,...o?o.default?["v:default"]:[`v:${o.folderId}:${o.version}`]:["v:default"],...l?[`p:${l.name}`]:[]],url:e.url&&(e.path&&e.path.length>1?e.url:e.url.split("#")[0])};const p=U(e.facets);if(p){const O=d.get(p)??[];d.set(p,[...O,e])}}}for(const[n,e]of d)await f.addDocuments(e,{locale:a,group:n,outDir:u})}}s?.setAttribute("locale",a),s?.setAttribute("totalDocuments",g),s?.setAttribute("totalSearchDocuments",h)});if(f.cleanupFacetValues(r),i.infoTime(C,"Search indexes created"),m.SEARCH_DEV_DEBUG||m.isBuildMode){i.info("Writing out search data...");const a=i.startTiming();await f.export(u);const s=i.infoTime(a,"Search data written");s&&V.sendTimingPerformedMessage(s)}})}export{Q as prepareSearchDocuments};
1
+ import{REDOCLY_ROUTE_RBAC as S,REDOCLY_TEAMS_RBAC as g}from"@redocly/config";import{existsSync as A}from"fs";import{rm as B}from"node:fs/promises";import y from"node:path";import{DEFAULT_LOCALE_PLACEHOLDER as H}from"../../../../constants/common.js";import{SEARCH_DATA_EXPORT_FOLDER as D}from"../../../constants/plugins/search.js";import{envConfig as u}from"../../../config/env-config.js";import{logger as s}from"../../../tools/notifiers/logger.js";import{extractTeamsFromScopeItems as M,getRbacTeamsListForResource as P}from"../../../utils/index.js";import{getSearchDocumentGroup as U}from"../utils.js";import{telemetry as V}from"../../../../cli/telemetry/index.js";import{telemetryTraceStep as R}from"../../../../cli/telemetry/helpers/trace-step.js";async function Q(C,r,f){await R("build.plugin.search.prepare_search_documents",async()=>{s.info("Preparing search documents and create indexes...");const n=u.SEARCH_DEV_DEBUG?y.join(r.contentDir,"..","public","client"):r.outdir;(u.SEARCH_DEV_DEBUG||u.isBuildMode)&&A(`${n}/${D}`)&&await B(`${n}/${D}`,{recursive:!0});const b=s.startTiming(),v=r.getConfig().access?.rbac,F=[H,...C.localeFolders.map(a=>a.toLowerCase())];for(const a of F)await R("build.plugin.search.prepare_search_documents.locale",async c=>{const w=r.getAllRoutesForLocale(a);let h=0,T=0;for(const t of w){if(h++,t.excludeFromSearch)continue;const d=new Map,{product:l}=t,L=await t.getStaticData?.(t,r)||{},i=await t.getSearchDocuments?.(t,{...L,[g]:t[g],[S]:t[S]},r),o=t.versions?.find(m=>m.active),x=P(t,v??{});if(i){if(i&&i.length){T+=i.length;for(const m in i){let e=i[m];const _=M(e?.[g]),E=_?.length?_:x,$=e.tags||[];e={...e,...o&&{version:o.version,isDefaultVersion:o.default,versionFolderId:o.folderId},...l&&{product:l},rbacTeams:E,tags:[...$,...E,...o?o.default?["v:default"]:[`v:${o.folderId}:${o.version}`]:["v:default"],...l?[`p:${l.name}`]:[]],url:e.url&&(e.path&&e.path.length>1?e.url:e.url.split("#")[0])};const p=U(e.facets);if(p){const O=d.get(p)??[];d.set(p,[...O,e])}}}for(const[m,e]of d)await f.addDocuments(e,{locale:a,group:m,outDir:n})}}c?.setAttribute("locale",a),c?.setAttribute("totalDocuments",h),c?.setAttribute("totalSearchDocuments",T)});if(f.cleanupFacetValues(r),s.infoTime(b,"Search indexes created"),u.SEARCH_DEV_DEBUG||u.isBuildMode){s.info("Writing out search data...");const a=s.startTiming();await f.export(n);const c=s.infoTime(a,"Search data written");c&&V.sendTimingPerformedMessage(c),A(`${n}/${D}`)||s.warn("No content available for search. Consider hiding search in config.")}})}export{Q as prepareSearchDocuments};
@@ -1 +1 @@
1
- import{readFile as R,readdir as y,appendFile as E}from"node:fs/promises";import m from"node:path";import{existsSync as w}from"node:fs";import{SEARCH_GROUP_FACET_FIELD as F,SEARCH_PRODUCT_FIELD as C,SEARCH_VERSION_FIELD as $}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as A,SEARCH_DATA_EXPORT_FOLDER as _,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as O}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as v}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as N}from"../../../../config/env-config.js";import{ensureDir as h}from"../../../../utils/index.js";import{FlexSearchIndex as j}from"./search-index.js";class J{#e=new Map;#s;#t=0;async initIndexSchema(t){this.#s={document:{id:"id",tag:"tags",index:A},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#o(t,o){const e=this.#e.get(t);if(e)return e.find(n=>n.id===o)}#n(t,o){let e=this.#o(t,o);return e||(e=new j(o,this.#s),this.#e.set(t,[...this.#e.get(t)??[],e])),e}async addDocuments(t,o){if(t.length){const{group:e,locale:n,outDir:c}=o,i=this.#n(n,e);for(const a of t)i.add(a),N.isBuildMode&&(this.#t++,this.#t>=O&&await this.exportDocuments(c))}}getIndexesSize(){return this.#e.size}async search(t,o){return await v("search",async e=>{const{query:n,locale:c,filter:i,loadMore:a,auth:f}=t,l=this.#e.get(c)??[];let r=[],d="",x=["v:default"];if(e?.setAttribute("locale",c),i)for(const s of i)s.field===F?r=[...r,...s.values]:s.field===C?d=`p:${s.values[0]}`:s.field===$&&s.values.length&&s.values.length===2&&(x=[...x,`v:${s.values[0]}:${s.values[1]}`]);const g={auth:f,query:n,offset:0,SEARCH_GROUP_FACET_FIELD:F,product:d,versions:x},p=[];let u={facets:{},documents:{}};if(a){const s=this.#o(c,a.groupKey);s&&p.push(s.search({...g,offset:a.offset}))}else for(const s of l)r&&r.length?r.includes(s.id)&&p.push(s.search(g)):p.push(s.search(g));const I=await Promise.all(p);for(const s of I){u.documents={...u.documents,...s.documents};for(const[D,S]of Object.entries(s.facets))u.facets[D]?u.facets[D]=[...u.facets[D],...S]:u.facets[D]=S}return u})}async exportDocuments(t){const o='{"documents":[',e=h(m.join(t,_));for(const[n,c]of this.#e){const i=h(m.join(e,n));for(const a of c){const f=Array.from(a.documents);if(f.length===0)continue;const l=h(m.join(i,`${a.id}.json`)),r=!w(l),d=JSON.stringify(f).substring(1).slice(0,-1),x=r?o+d:","+d;await E(l,x,{encoding:"utf8"}),a.clearDocuments()}}this.#t=0}async exportIndexes(t){const o=h(m.join(t,_));for(const[e,n]of this.#e){const c=h(m.join(o,e));for(const i of n){const a=h(m.join(c,`${i.id}.json`)),f={};await i.export((r,d)=>{f[r]=d});const l=`],"index":${JSON.stringify(f)}}`;await E(a,l,{encoding:"utf8"})}}this.#e.clear()}async import(t){const o=`${t}/${_}`,e=await y(o);for(const n of e){const c=await y(`${o}/${n}`);for(const i of c){const a=`${o}/${n}/${i}`,f=JSON.parse(await R(a,"utf-8")),l=m.parse(i).name;await this.#n(n,l).import(f)}}}countFacets(t,o){const e={};for(const[n,c]of o)e[n]=c.values.map(i=>({value:i,count:0,isCounterVisible:!1}));return e}cleanupFacetValues(t){}}export{J as FlexSearch};
1
+ import{readFile as w,readdir as y,appendFile as E}from"node:fs/promises";import m from"node:path";import{existsSync as F}from"node:fs";import{SEARCH_GROUP_FACET_FIELD as I,SEARCH_PRODUCT_FIELD as C,SEARCH_VERSION_FIELD as $}from"../../../../../constants/common.js";import{SEARCH_INDEX_FIELDS as A,SEARCH_DATA_EXPORT_FOLDER as _,SEARCH_MAX_INMEMORY_DOCUMENTS_COUNT as O}from"../../../../constants/plugins/search.js";import{telemetryTraceStep as v}from"../../../../telemetry/helpers/trace-step.js";import{envConfig as N}from"../../../../config/env-config.js";import{ensureDir as h}from"../../../../utils/index.js";import{FlexSearchIndex as j}from"./search-index.js";class J{#e=new Map;#s;#t=0;async initIndexSchema(t){this.#s={document:{id:"id",tag:"tags",index:A},worker:!1,tokenize:"forward",context:{depth:2,resolution:9}}}#n(t,n){const e=this.#e.get(t);if(e)return e.find(o=>o.id===n)}#o(t,n){let e=this.#n(t,n);return e||(e=new j(n,this.#s),this.#e.set(t,[...this.#e.get(t)??[],e])),e}async addDocuments(t,n){if(t.length){const{group:e,locale:o,outDir:c}=n,i=this.#o(o,e);for(const a of t)i.add(a),N.isBuildMode&&(this.#t++,this.#t>=O&&await this.exportDocuments(c))}}getIndexesSize(){return this.#e.size}async search(t,n){return await v("search",async e=>{const{query:o,locale:c,filter:i,loadMore:a,auth:f}=t,l=this.#e.get(c)??[];let r=[],d="",x=["v:default"];if(e?.setAttribute("locale",c),i)for(const s of i)s.field===I?r=[...r,...s.values]:s.field===C?d=`p:${s.values[0]}`:s.field===$&&s.values.length&&s.values.length===2&&(x=[...x,`v:${s.values[0]}:${s.values[1]}`]);const g={auth:f,query:o,offset:0,SEARCH_GROUP_FACET_FIELD:I,product:d,versions:x},p=[];let u={facets:{},documents:{}};if(a){const s=this.#n(c,a.groupKey);s&&p.push(s.search({...g,offset:a.offset}))}else for(const s of l)r&&r.length?r.includes(s.id)&&p.push(s.search(g)):p.push(s.search(g));const R=await Promise.all(p);for(const s of R){u.documents={...u.documents,...s.documents};for(const[D,S]of Object.entries(s.facets))u.facets[D]?u.facets[D]=[...u.facets[D],...S]:u.facets[D]=S}return u})}async exportDocuments(t){const n='{"documents":[',e=h(m.join(t,_));for(const[o,c]of this.#e){const i=h(m.join(e,o));for(const a of c){const f=Array.from(a.documents);if(f.length===0)continue;const l=h(m.join(i,`${a.id}.json`)),r=!F(l),d=JSON.stringify(f).substring(1).slice(0,-1),x=r?n+d:","+d;await E(l,x,{encoding:"utf8"}),a.clearDocuments()}}this.#t=0}async exportIndexes(t){const n=h(m.join(t,_));for(const[e,o]of this.#e){const c=h(m.join(n,e));for(const i of o){const a=h(m.join(c,`${i.id}.json`)),f={};await i.export((r,d)=>{f[r]=d});const l=`],"index":${JSON.stringify(f)}}`;await E(a,l,{encoding:"utf8"})}}this.#e.clear()}async import(t){const n=`${t}/${_}`;if(!F(n))return;const e=await y(n);for(const o of e){const c=await y(`${n}/${o}`);for(const i of c){const a=`${n}/${o}/${i}`,f=JSON.parse(await w(a,"utf-8")),l=m.parse(i).name;await this.#o(o,l).import(f)}}}countFacets(t,n){const e={};for(const[o,c]of n)e[o]=c.values.map(i=>({value:i,count:0,isCounterVisible:!1}));return e}cleanupFacetValues(t){}}export{J as FlexSearch};
@@ -45,6 +45,7 @@ export declare function buildOidcLoginUrl(origin: string, { authorizationEndpoin
45
45
  redirectUriOverride?: string;
46
46
  sourceOverride?: 'portal' | 'mcp';
47
47
  branchOverride?: string | undefined;
48
+ uiLocales?: string;
48
49
  }): {
49
50
  loginUrl?: string;
50
51
  cookies?: Record<string, {
@@ -58,6 +59,8 @@ type McpAuthorizationCodePayload = {
58
59
  redirect_uri: string;
59
60
  id_token: string;
60
61
  idp_access_token?: string;
62
+ code_challenge?: string;
63
+ code_challenge_method?: string;
61
64
  iat: number;
62
65
  exp: number;
63
66
  };
@@ -66,6 +69,8 @@ export declare function createMcpAuthorizationCode(params: {
66
69
  idpAccessToken?: string;
67
70
  clientId: string;
68
71
  redirectUri: string;
72
+ codeChallenge?: string;
73
+ codeChallengeMethod?: string;
69
74
  ttlSec?: number;
70
75
  }): Promise<string>;
71
76
  export declare function verifyMcpAuthorizationCode(code: string): Promise<McpAuthorizationCodePayload>;
@@ -76,14 +81,14 @@ export declare function createMcpSessionResource(sessionId: string | null | unde
76
81
  };
77
82
  export declare function rewritePreviewAuthRedirectUri(redirectUri: string): string;
78
83
  export declare function parsePreviewBranch(origin: string): string | undefined;
79
- export declare function buildLoginUrl(idpLoginParams: AuthProviderLoginParams, redirectOrigin: string, redirectTo: string | null, inviteCode?: string): {
84
+ export declare function buildLoginUrl(idpLoginParams: AuthProviderLoginParams, redirectOrigin: string, redirectTo: string | null, inviteCode?: string, uiLocales?: string): {
80
85
  loginUrl?: string;
81
86
  cookies?: Record<string, {
82
87
  value: string;
83
88
  options: object;
84
89
  }>;
85
90
  };
86
- export declare function buildSAML2LoginUrl(origin: string, idpLoginParams: Saml2LoginParams, redirectTo: string | null, inviteCode?: string): {
91
+ export declare function buildSAML2LoginUrl(origin: string, idpLoginParams: Saml2LoginParams, redirectTo: string | null, inviteCode?: string, uiLocales?: string): {
87
92
  loginUrl: string;
88
93
  };
89
94
  export declare function encodeSAML2(samlRequest: string): string;
@@ -1,4 +1,4 @@
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 H,inflateSync as J}from"fflate";import{createHash as q}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as K}from"@redocly/config";import{AUTH_URL as Y,JWT_SECRET_KEY as I}from"../constants/common.js";import{envConfig as Q}from"../config/env-config.js";import{getPathPrefix as X,withPathPrefix as G}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as Z,REQUIRED_OIDC_SCOPES as R,ServerRoutes as N}from"../../constants/common.js";import{appendQueryParams as ee}from"../../utils/url/append-query-params.js";import{logger as te}from"../tools/notifiers/logger.js";import{randomString as ne}from"../utils/crypto/random-string.js";import{randomUUID as U}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as y,JwtTokenExpired as re}from"./jwt/types.js";import*as p from"./jwt/jwt.js";import{parseTeamClaimToArray as oe}from"../utils/index.js";import{arrayBufferToBase64 as ae,decodeBase64 as P,encodeBase64URL as se,urlSafeBase64 as v}from"./jwt/encode.js";import{formatSamlCertificate as ie}from"./utils/format-saml-certificate.js";function E(e){return e?.type===u.OIDC}function ce(e){return e?.type===u.SAML2}async function Je(e,t){if(E(t))return ue(e,t);if(ce(t))return de(e,t)}async function ue(e,t){const r=await V(e,t),n=new Set((t.scopes||[]).concat(R)),o=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:r.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(n).join(" "),extraParams:o,pkce:t.pkce}}function de(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function qe(e,t,r,n,o={}){const a=new Set((n.scopes||[]).concat(R));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:n.clientId,scope:Array.from(a).join(" "),code:t,redirect_uri:j(r),grant_type:"authorization_code",...n.clientSecret?{client_secret:n.clientSecret}:{},...o}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function me(e,{authorizationEndpoint:t,clientId:r,responseType:n,scope:o,extraParams:a,idpId:s,pkce:l},m,A,S){if(!t||!r||!n||!o)return{loginUrl:void 0};const c=new URL(t),f=S?.redirectUriOverride??`${e}${G(N.OIDC_CALLBACK)}`,_={state:U(),idpId:s,redirectUri:f,redirectTo:m,branch:S?.branchOverride??le(e),inviteCode:A,source:S?.sourceOverride??"portal"},h={};if(l){const d=v(ne(50)),x=v(q("sha256").update(d).digest("base64")),g="S256";c.searchParams.append("code_challenge",x),c.searchParams.append("code_challenge_method",g),h.code_verifier={value:d,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:X()||"/"}}}c.searchParams.append("client_id",r),c.searchParams.append("scope",o),c.searchParams.append("response_type",n),c.searchParams.append("redirect_uri",j(f)),c.searchParams.append("state",se(JSON.stringify(_)));for(const d in a)a[d]!==void 0&&c.searchParams.append(d,a[d]);return{loginUrl:c.toString(),cookies:h}}function We(e,t,r,n){const o=new URL(e);return o.searchParams.append("post_logout_redirect_uri",t),n&&o.searchParams.append("state",n),o.searchParams.append("id_token_hint",r),o.toString()}async function Ke(e){const t=Math.floor(Date.now()/1e3),r=t+(e.ttlSec??600);return p.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpAccessToken?{idp_access_token:e.idpAccessToken}:{},iat:t,exp:r},I,y.HS256)}async function Ye(e){await p.verify(e,I,y.HS256);const{payload:t}=p.decode(e);if(t.type!=="mcp_auth_code")throw new Error("Invalid authorization code type");if(!t.client_id||!t.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof t.exp=="number"&&Date.now()>=t.exp*1e3)throw new Error("Authorization code expired");return t}function Qe(e){const t=e||W(),r=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:r,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${r}`}}function j(e){return e.match(/^https:\/\/preview-[^\.]+--/)?"https://previewauth--"+e.split("--")[1]:e.match(/^(https:\/\/[^\.]+)--[^\.]+\.preview\./)?e.replace(/^(https:\/\/[^\.]+?)--[^\.]+\.preview\./,"$1.previewauth."):e}function le(e){return e.match(/^(https:\/\/[^\.]+)--([^\.]+)\.preview\./)?.[2]||void 0}function pe(e){return e.type===u.OIDC}function fe(e){return e.type===u.SAML2}function Xe(e,t,r,n){return pe(e)?me(t,e,r,n):fe(e)?he(t,e,r,n):{}}function he(e,t,r,n){const a=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
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 H,inflateSync as J}from"fflate";import{createHash as q}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as K}from"@redocly/config";import{AUTH_URL as Y,JWT_SECRET_KEY as L}from"../constants/common.js";import{envConfig as Q}from"../config/env-config.js";import{getPathPrefix as X,withPathPrefix as G}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as Z,REQUIRED_OIDC_SCOPES as R,ServerRoutes as N}from"../../constants/common.js";import{appendQueryParams as ee}from"../../utils/url/append-query-params.js";import{logger as te}from"../tools/notifiers/logger.js";import{randomString as ne}from"../utils/crypto/random-string.js";import{randomUUID as U}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as w,JwtTokenExpired as oe}from"./jwt/types.js";import*as f from"./jwt/jwt.js";import{parseTeamClaimToArray as re}from"../utils/index.js";import{arrayBufferToBase64 as ae,decodeBase64 as P,encodeBase64URL as se,urlSafeBase64 as v}from"./jwt/encode.js";import{formatSamlCertificate as ce}from"./utils/format-saml-certificate.js";function E(e){return e?.type===u.OIDC}function ie(e){return e?.type===u.SAML2}async function Je(e,t){if(E(t))return ue(e,t);if(ie(t))return de(e,t)}async function ue(e,t){const o=await V(e,t),n=new Set((t.scopes||[]).concat(R)),r=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:o.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(n).join(" "),extraParams:r,pkce:t.pkce}}function de(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function qe(e,t,o,n,r={}){const a=new Set((n.scopes||[]).concat(R));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:n.clientId,scope:Array.from(a).join(" "),code:t,redirect_uri:j(o),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 le(e,{authorizationEndpoint:t,clientId:o,responseType:n,scope:r,extraParams:a,idpId:s,pkce:d},m,A,p){if(!t||!o||!n||!r)return{loginUrl:void 0};const c=new URL(t),h=p?.redirectUriOverride??`${e}${G(N.OIDC_CALLBACK)}`,_={state:U(),idpId:s,redirectUri:h,redirectTo:m,branch:p?.branchOverride??me(e),inviteCode:A,source:p?.sourceOverride??"portal",uiLocales:p?.uiLocales},y={};if(d){const l=v(ne(50)),g=v(q("sha256").update(l).digest("base64")),x="S256";c.searchParams.append("code_challenge",g),c.searchParams.append("code_challenge_method",x),y.code_verifier={value:l,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:X()||"/"}}}c.searchParams.append("client_id",o),c.searchParams.append("scope",r),c.searchParams.append("response_type",n),c.searchParams.append("redirect_uri",j(h)),c.searchParams.append("state",se(JSON.stringify(_))),p?.uiLocales&&c.searchParams.append("ui_locales",p.uiLocales);for(const l in a)a[l]!==void 0&&c.searchParams.append(l,a[l]);return{loginUrl:c.toString(),cookies:y}}function We(e,t,o,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",o),r.toString()}async function Ke(e){const t=Math.floor(Date.now()/1e3),o=t+(e.ttlSec??600);return f.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpAccessToken?{idp_access_token:e.idpAccessToken}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:o},L,w.HS256)}async function Ye(e){await f.verify(e,L,w.HS256);const{payload:t}=f.decode(e);if(t.type!=="mcp_auth_code")throw new Error("Invalid authorization code type");if(!t.client_id||!t.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof t.exp=="number"&&Date.now()>=t.exp*1e3)throw new Error("Authorization code expired");return t}function Qe(e){const t=e||W(),o=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:o,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${o}`}}function j(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 pe(e){return e.type===u.OIDC}function fe(e){return e.type===u.SAML2}function Xe(e,t,o,n,r){return pe(e)?le(t,e,o,n,{uiLocales:r}):fe(e)?he(t,e,o,n,r):{}}function he(e,t,o,n,r){const s=`<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
4
  ID="_${U()}"
@@ -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=ye(a);return{loginUrl:ee(t.ssoUrl,{SAMLRequest:s,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:r,inviteCode:n,source:"portal"})})}}function ye(e){return ae(H(new TextEncoder().encode(e)).buffer)}function Ge(e){const t=P(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const r=J(new Uint8Array(atob(e).split("").map(n=>n.charCodeAt(0))));return new TextDecoder().decode(r)}function Ze(e){try{return JSON.parse(P(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function et(e){const t=new b().parseFromString(e,"application/xml"),n=i(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,a=i(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],l=s&&s.nodeValue||void 0,m=i(t,"//*[local-name(.)='Audience']/text()")[0],A=m&&m.nodeValue||void 0,c=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",f=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],_=f&&f.nodeValue||"",h=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],d=h&&h.nodeValue||"",x=i(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],g=we(x),M={},C=i(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(C.length)for(const T of C){const D=i(T,"./@Name")[0];if(D.nodeValue){const O=i(T,"./*[local-name(.)='AttributeValue']/text()")[0];O?.nodeValue&&(M[D.nodeValue]=O.nodeValue)}}return{uid:_,success:n,expiresAt:g,issuerId:l,entityId:A,attrs:M,cert:c,nameFormat:d,destination:a}}function we(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),r=L(Date.now()),n=L(Date.now()+720*60*1e3);return t?t>r&&t<n?n:t:r}function L(e){return Math.floor(e/1e3)}const k={},w={jwks:{}};async function V(e,t){if(!k[e]){const r=t.configurationUrl?await $(t.configurationUrl):t.configuration;k[e]=Se()?Ae(r):r}return k[e]}function Se(){const e=Q.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}function Ae(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const r of Object.keys(t)){const n=t[r];typeof n=="string"&&n.includes("://localhost")&&(t[r]=n.replace("://localhost","://host.docker.internal"))}return t}async function _e(e){for(const t of Object.keys(e)){const r=e[t];if(!E(r))continue;const n=await V(t,r);if(n.jwks_uri){const o=await $(n.jwks_uri);for(const a of o.keys)w.jwks[a.kid]={...a,idpId:t}}}}async function $(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function tt(e){return fetch(`${Y}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function nt(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=>xe(t.hostname,n))}function xe(e,t){return e===t||e.endsWith(`.${t}`)}async function rt(e,t){const r=new b().parseFromString(e,"application/xml"),n=i(r,"//*[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 o=ie(t),a=new B({publicCert:o});a.loadSignature(n);try{return a.checkSignature(e)}catch{return!1}}function ot(e,t,r,n){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=r["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=r["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=a?.match(/.+@.+/);return o=o||r["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?a:void 0),o=o?.toLowerCase(),{sub:e,given_name:r["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:r["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:r["http://schemas.microsoft.com/identity/claims/displayname"]||a,email:o,email_verified:!0,teams:n?oe(r[n]):[]}}function z(e,t={}){return e.map(r=>t[r]||r)}async function at(e,t){if(!t)return{};const r=t.authorization;if(!r)return{};try{const n=p.decode(r);if(n.header.alg===y.RS256){w.jwks[n.header.kid]===void 0&&await _e(e);const m=w.jwks[n.header.kid];if(!m)return w.jwks[n.header.kid]=null,{};await p.verify(r,m,y.RS256)}else await p.verify(r,I,y.HS256);const o=n.payload.idpId||w.jwks[n.header.kid]?.idpId,a=e[o]||{},s=Le(a),l=Ie(a);return{...n.payload,email:n.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...z(n.payload.teams||[],l),..."defaultTeams"in a&&a.defaultTeams||[],...z("teamsClaimName"in a&&n.payload[s||""]||[],l),Z])),name:ge(n.payload),isAuthenticated:!0,idpAccessToken:n.payload.idp_access_token||t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:r}}catch(n){n instanceof re||te.error("Malformed JWT token: %s",n.message)}return{}}function ge(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Ie(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function Le(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return K}}function i(e,t){return F.select(t,e)||[]}export{Xe as buildLoginUrl,me as buildOidcLoginUrl,We as buildOidcLogoutUrl,he as buildSAML2LoginUrl,Ke as createMcpAuthorizationCode,Qe as createMcpSessionResource,Ge as decodeSamlResponse,ye as encodeSAML2,ot as extractUserClaims,Je as getAuthProviderLoginParams,ue as getOidcLoginParams,V as getOidcMetadata,tt as getRedoclyTokenPayload,de as getSaml2LoginParams,at as getUserParamsFromCookies,ge as getUsernameFromPayload,E as isOidcProviderConfig,nt as isRedoclySso,ce as isSaml2ProviderConfig,qe as oidcExchangeCodeForToken,w as oidcJwksCache,k as oidcMetadataCache,Ze as parseOidcState,le as parsePreviewBranch,et as parseSamlResponse,j as rewritePreviewAuthRedirectUri,Ye as verifyMcpAuthorizationCode,rt as verifySAMLResponse};
12
+ </samlp:AuthnRequest>`,d=ye(s);return{loginUrl:ee(t.ssoUrl,{SAMLRequest:d,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:o,inviteCode:n,source:"portal",uiLocales:r})})}}function ye(e){return ae(H(new TextEncoder().encode(e)).buffer)}function Ge(e){const t=P(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const o=J(new Uint8Array(atob(e).split("").map(n=>n.charCodeAt(0))));return new TextDecoder().decode(o)}function Ze(e){try{return JSON.parse(P(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function et(e){const t=new b().parseFromString(e,"application/xml"),n=i(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,a=i(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],d=s&&s.nodeValue||void 0,m=i(t,"//*[local-name(.)='Audience']/text()")[0],A=m&&m.nodeValue||void 0,c=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",h=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],_=h&&h.nodeValue||"",y=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],l=y&&y.nodeValue||"",g=i(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=we(g),M={},k=i(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(k.length)for(const T of k){const D=i(T,"./@Name")[0];if(D.nodeValue){const O=i(T,"./*[local-name(.)='AttributeValue']/text()")[0];O?.nodeValue&&(M[D.nodeValue]=O.nodeValue)}}return{uid:_,success:n,expiresAt:x,issuerId:d,entityId:A,attrs:M,cert:c,nameFormat:l,destination:a}}function we(e){const t=typeof e?.nodeValue=="string"&&I(Date.parse(e.nodeValue)),o=I(Date.now()),n=I(Date.now()+720*60*1e3);return t?t>o&&t<n?n:t:o}function I(e){return Math.floor(e/1e3)}const C={},S={jwks:{}};async function V(e,t){if(!C[e]){const o=t.configurationUrl?await $(t.configurationUrl):t.configuration;C[e]=Se()?Ae(o):o}return C[e]}function Se(){const e=Q.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}function Ae(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const o of Object.keys(t)){const n=t[o];typeof n=="string"&&n.includes("://localhost")&&(t[o]=n.replace("://localhost","://host.docker.internal"))}return t}async function _e(e){for(const t of Object.keys(e)){const o=e[t];if(!E(o))continue;const n=await V(t,o);if(n.jwks_uri){const r=await $(n.jwks_uri);for(const a of r.keys)S.jwks[a.kid]={...a,idpId:t}}}}async function $(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function tt(e){return fetch(`${Y}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function nt(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=>ge(t.hostname,n))}function ge(e,t){return e===t||e.endsWith(`.${t}`)}async function ot(e,t){const o=new b().parseFromString(e,"application/xml"),n=i(o,"//*[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=ce(t),a=new B({publicCert:r});a.loadSignature(n);try{return a.checkSignature(e)}catch{return!1}}function rt(e,t,o,n){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=o["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 a=o["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=a?.match(/.+@.+/);return r=r||o["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?a:void 0),r=r?.toLowerCase(),{sub:e,given_name:o["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:o["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:o["http://schemas.microsoft.com/identity/claims/displayname"]||a,email:r,email_verified:!0,teams:n?re(o[n]):[]}}function z(e,t={}){return e.map(o=>t[o]||o)}async function at(e,t){if(!t)return{};const o=t.authorization;if(!o)return{};try{const n=f.decode(o);if(n.header.alg===w.RS256){S.jwks[n.header.kid]===void 0&&await _e(e);const m=S.jwks[n.header.kid];if(!m)return S.jwks[n.header.kid]=null,{};await f.verify(o,m,w.RS256)}else await f.verify(o,L,w.HS256);const r=n.payload.idpId||S.jwks[n.header.kid]?.idpId,a=e[r]||{},s=Ie(a),d=Le(a);return{...n.payload,email:n.payload.email?.toLowerCase(),idpId:r,teams:Array.from(new Set([...z(n.payload.teams||[],d),..."defaultTeams"in a&&a.defaultTeams||[],...z("teamsClaimName"in a&&n.payload[s||""]||[],d),Z])),name:xe(n.payload),isAuthenticated:!0,idpAccessToken:n.payload.idp_access_token||t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:o}}catch(n){n instanceof oe||te.error("Malformed JWT token: %s",n.message)}return{}}function xe(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Le(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function Ie(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return K}}function i(e,t){return F.select(t,e)||[]}export{Xe as buildLoginUrl,le as buildOidcLoginUrl,We as buildOidcLogoutUrl,he as buildSAML2LoginUrl,Ke as createMcpAuthorizationCode,Qe as createMcpSessionResource,Ge as decodeSamlResponse,ye as encodeSAML2,rt as extractUserClaims,Je as getAuthProviderLoginParams,ue as getOidcLoginParams,V as getOidcMetadata,tt as getRedoclyTokenPayload,de as getSaml2LoginParams,at as getUserParamsFromCookies,xe as getUsernameFromPayload,E as isOidcProviderConfig,nt as isRedoclySso,ie as isSaml2ProviderConfig,qe as oidcExchangeCodeForToken,S as oidcJwksCache,C as oidcMetadataCache,Ze as parseOidcState,me as parsePreviewBranch,et as parseSamlResponse,j as rewritePreviewAuthRedirectUri,Ye as verifyMcpAuthorizationCode,ot as verifySAMLResponse};
@@ -1 +1 @@
1
- import{setCookie as L,deleteCookie as q}from"hono/cookie";import{AuthProviderType as V}from"@redocly/config";import{withPathPrefix as M,getPathPrefix as R}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as b}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as $,ORG_SLUG as W,ORG_ID as Y}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as S}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{envConfig as z}from"../../config/env-config.js";import{getAuthProviderLoginParams as Q,isOidcProviderConfig as U,isSaml2ProviderConfig as Z,oidcExchangeCodeForToken as x,buildLoginUrl as ee,decodeSamlResponse as re,extractUserClaims as oe,parseSamlResponse as ne,parseOidcState as te,verifySAMLResponse as ie,getUsernameFromPayload as se,buildOidcLogoutUrl as ae,getOidcMetadata as H,getRedoclyTokenPayload as de,isRedoclySso as ce,rewritePreviewAuthRedirectUri as le,parsePreviewBranch as j,buildOidcLoginUrl as ue,createMcpSessionResource as A}from"../auth.js";import*as O from"../jwt/jwt.js";import{AlgorithmTypes as v}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(i){if(z.isProductionEnv)return i.newResponse(null,404,{});const{password:e,...r}=await i.req.json(),a=await O.sign({...r,name:r.username||r.email||"Unknown"},$,v.HS256);return L(i,"authorization",a,{path:R()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),i.newResponse(null,200,{})}function ve(){return async i=>{const e=i.get("logger"),r=encodeURIComponent(i.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${S.LOGIN}/?error=${encodeURIComponent(r)}`;return i.newResponse(null,301,{Location:a})}}function N(i){if(!i||!i.includes(S.MCP_CALLBACK))return null;try{const e=i.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function Pe(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=te(e.req.query("state")),m=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(S.MCP_CALLBACK),c=t?N(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,s=a?.[m];if(!U(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await H(m,s);if(a&&!d.token_endpoint){const u="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${u}`),e.text(u,500)}try{const u=d.token_endpoint,l=e.req.query("code"),h=e.req.query("error");if(h)return t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),pe(e,i,{slug:"/"},403,"403OIDC");if(!l){const w="Code is expected but not present";return r.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:w,error_details:null}]),new Response(`Forbidden: ${w}`,{status:403})}const C=typeof n.redirectUri=="string"?n.redirectUri:new URL(M(S.OIDC_CALLBACK),e.req.url).toString(),p=e.get("cookies")?.code_verifier,g=await x(u,l,C,s,{...s.tokenRequestCustomParams,...p?{code_verifier:p}:{}});if(g.error)return r.error(`Error from OIDC provider: "${g.error}"`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:`Token exchange error: ${g.error}`,error_details:g.error_description||null}]),e.text(`Forbidden: ${g.error_description||g.error}`,403);if(!g?.id_token){const w="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:w,error_details:null}]),new Response(`Forbidden: ${w}`,{status:403})}const{payload:f,header:_}=O.decode(g.id_token),o=_.alg===v.RS256;if(s.audience?.length&&![...b(f.aud||[]),...b(f[E]||[])].some(I=>s.audience?.includes(I))){const I="No valid audience found in id_token";return r.error(`OIDC login error: ${I}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:I,error_details:null}]),new Response(`Forbidden: ${I}`)}const P=o?g.id_token:await O.sign({...f,idpId:m},$,v.HS256);se(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const D=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+F*1e3;if(s.introspectEndpoint){const w=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:g.access_token})});if(w.ok){const T=(await w.json()).ext?.federatedIdentity;T&&(L(e,"federated_access_token",T.access_token||"",{path:R()||"/",httpOnly:!1,expires:new Date(D)}),L(e,"federated_id_token",T.id_token||"",{path:R()||"/",httpOnly:!1,expires:new Date(D)}))}else r.warn(`OIDC introspect error: ${w.statusText}`)}if(L(e,"authorization",P,{path:R()||"/",httpOnly:!0,expires:new Date(D)}),P!==g.id_token&&L(e,"idp_id_token",g.id_token||"",{path:R()||"/",httpOnly:!0,expires:new Date(D)}),L(e,"idp_access_token",g.access_token||"",{path:R()||"/",httpOnly:!0,expires:new Date(D)}),q(e,"code_verifier",{path:R()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(S.MCP_CALLBACK)){const I=`${e.req.url.split("?")[0].replace(S.OIDC_CALLBACK,"")}${n.redirectTo}`;return e.newResponse(null,302,{Location:I})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const G=e.newResponse(null,302,{Location:J});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),G}catch(u){const l=u instanceof Error?u.message:String(u),h=u instanceof Error?u.stack:String(u);if(r.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:l,error_details:h}]),u.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const y="Something went wrong";return r.error(`OIDC login error: ${y}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:y,error_details:null}]),e.text(y,500)}}function $e(i){return async e=>{const r=e.get("logger"),n=e.get("auth").claims?.idpId,t=i.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return U(t)||q(e,"authorization",{path:R()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let c;if(U(t)){const s=(await H(n,t)).end_session_endpoint;if(s){const d=new URL(e.req.url),y=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",u=e.req.header("x-forwarded-host")||d.host,l=`${y}://${u}`,h=j(l),C=h?ge(JSON.stringify({branch:j(l)})):void 0,p=h?`${le(l)}/_auth/logout`:`${l}/post-logout`;c=ae(s,p,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",C)}}return r.info("Logout successful"),q(e,"authorization",{path:R()||"/"}),e.newResponse(null,302,{Location:c||M("/")})}}function Ue(i){return async e=>{const r=i.getConfig().access?.logoutReturnUrl,a=r||M("/");return e.newResponse(null,302,{Location:a})}}function Te(i){return async e=>{const r=e.get("logger"),a=e.req.param("code"),n=z.BH_API_URL,m=(t,c,s)=>t&&c?`${t} ${c.charAt(0)}`:s;try{if(!n)throw new Error("BH_API_URL is not set");const t=i.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.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?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(M("/"))):(r.error("Invite error",await c.text()),e.redirect(M("/")));const s=await c.json(),d=new URL(M("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",s.organization.name),d.searchParams.set("invitedBy",m(s.invitedBy.firstName,s.invitedBy.lastName,s.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function qe(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=new URL(e.req.url),m=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,s=`${t}://${c}`;let d=n.searchParams.get("idpId");const y=n.searchParams.get("redirectTo"),u=Object.keys(a||{})[0];d=d||u;const l=n.searchParams.get("mcp_redirect_uri"),h=!!l;if(!a?.[d]){const o="Invalid idpId";if(r.error(`IdP login error: ${o}`),h){const P=N(y||void 0);k.sendMcpAuthorizationFailedMessage([{...A(P),error:o,error_details:null}])}return e.text(`Forbidden: ${o}`,403)}const p=d&&a?await Q(d,a[d]):void 0,g={};for(const o of Object.keys(p?.extraParams||{}))g[o]=n.searchParams.get(o)||p?.extraParams?.[o]||void 0;let f,_={};if(h&&l&&p&&p.type===V.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const o=ue("",{...p,extraParams:g},y,m,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});f=o.loginUrl,_=o.cookies||{}}else if(p){const o=ee({...p,extraParams:g},s,y,m);f=o.loginUrl,_=o.cookies||{}}return Object.keys(_).forEach(o=>{L(e,o,_[o].value,_[o].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:f||new URL(e.req.url).pathname})}}function be(i){return async e=>{const r=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),m=a.get("RelayState");if(typeof n!="string"||typeof m!="string"){const o="SAMLResponse is required";return r.error(`SAML2 login error: ${o}`),e.text(`Bad request: ${o}`,400)}const t=re(n),{success:c,uid:s,nameFormat:d,attrs:y,issuerId:u,expiresAt:l}=ne(t),{idpId:h,redirectTo:C}=JSON.parse(m);if(!c){const o="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const o="SAML2 Token Expired";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const p=i.getConfig().ssoDirect?.[h];if(!p||!Z(p)){const o="Cannot find valid IdP";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!(p.issuerId&&u&&X(p.issuerId,u))){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 ie(t,p.x509PublicCert)){const o="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const f=oe(s,d,y,p.teamsAttributeName);if(!f.sub){const o="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}if(!f.email){const o="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}const _=await O.sign({...f,idpId:h},$,v.HS256);return L(e,"authorization",_,{path:R()||"/",httpOnly:!0,expires:new Date(l*1e3)}),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:C||"/"})}}function Ee(i){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=M(B(a.pathname)),m=i.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,C])=>U(C)&&ce(C));if(!(m&&t))return e.newResponse(null,302,{Location:n});const s=e.req.query("token"),d=s&&await de(s);if(!d)return e.newResponse(null,302,{Location:n});if(!b(d[E]||[]).some(C=>C===W||C===Y))return e.newResponse(null,302,{Location:n});const l=await O.sign({...d,idpId:t?.at(0)},$,v.HS256),h=Date.now()+F*1e3;return L(e,"authorization",l,{path:R()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,qe as idpLoginHandler,Te as inviteHandler,$e as logoutHandler,Pe as oidcCallbackHandler,Ue as postLogoutHandler,ve as redoclyLoginCallbackHandler,Ee as redoclyTokenLoginHandler,be as samlCallbackHandler};
1
+ import{setCookie as R,deleteCookie as b}from"hono/cookie";import{AuthProviderType as X}from"@redocly/config";import{withPathPrefix as I,getPathPrefix as _}from"@redocly/theme/core/utils";import{compareURIs as W}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as F,JWT_SECRET_KEY as U,ORG_SLUG as Y,ORG_ID as Q}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as B,ServerRoutes as O}from"../../../constants/common.js";import{sanitizeRedirectPathname as z}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{envConfig as H}from"../../config/env-config.js";import{getAuthProviderLoginParams as Z,isOidcProviderConfig as $,isSaml2ProviderConfig as x,oidcExchangeCodeForToken as ee,buildLoginUrl as re,decodeSamlResponse as oe,extractUserClaims as ne,parseSamlResponse as te,parseOidcState as ie,verifySAMLResponse as se,getUsernameFromPayload as ae,buildOidcLogoutUrl as de,getOidcMetadata as j,getRedoclyTokenPayload as ce,isRedoclySso as le,rewritePreviewAuthRedirectUri as ue,parsePreviewBranch as N,buildOidcLoginUrl as pe,createMcpSessionResource as A}from"../auth.js";import*as D from"../jwt/jwt.js";import{AlgorithmTypes as v}from"../jwt/types.js";import{handleErrorPageRender as ge}from"../utils.js";import{encodeBase64URL as fe}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as me}from"./helpers/resolve-ui-locales-for-idp-login.js";async function ve(i){if(H.isProductionEnv)return i.newResponse(null,404,{});const{password:e,...r}=await i.req.json(),a=await D.sign({...r,name:r.username||r.email||"Unknown"},U,v.HS256);return R(i,"authorization",a,{path:_()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),i.newResponse(null,200,{})}function Ue(){return async i=>{const e=i.get("logger"),r=encodeURIComponent(i.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${O.LOGIN}/?error=${encodeURIComponent(r)}`;return i.newResponse(null,301,{Location:a})}}function K(i){if(!i||!i.includes(O.MCP_CALLBACK))return null;try{const e=i.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function $e(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,o=ie(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(O.MCP_CALLBACK),c=t?K(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,s=a?.[m];if(!$(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await j(m,s);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,u=e.req.query("code"),h=e.req.query("error");if(h)return t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),ge(e,i,{slug:"/"},403,"403OIDC");if(!u){const y="Code is expected but not present";return r.error(`OIDC login error: ${y}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:y,error_details:null}]),new Response(`Forbidden: ${y}`,{status:403})}const C=typeof o.redirectUri=="string"?o.redirectUri:new URL(I(O.OIDC_CALLBACK),e.req.url).toString(),w=e.get("cookies")?.code_verifier,l=await ee(p,u,C,s,{...s.tokenRequestCustomParams,...w?{code_verifier:w}:{}});if(l.error)return r.error(`Error from OIDC provider: "${l.error}"`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:`Token exchange error: ${l.error}`,error_details:l.error_description||null}]),e.text(`Forbidden: ${l.error_description||l.error}`,403);if(!l?.id_token){const y="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${y}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:y,error_details:null}]),new Response(`Forbidden: ${y}`,{status:403})}const{payload:f,header:S}=D.decode(l.id_token),n=S.alg===v.RS256;if(s.audience?.length&&![...q(f.aud||[]),...q(f[F]||[])].some(M=>s.audience?.includes(M))){const M="No valid audience found in id_token";return r.error(`OIDC login error: ${M}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:M,error_details:null}]),new Response(`Forbidden: ${M}`)}const g=n?l.id_token:await D.sign({...f,idpId:m},U,v.HS256);ae(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const P=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+B*1e3;if(s.introspectEndpoint){const y=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:l.access_token})});if(y.ok){const T=(await y.json()).ext?.federatedIdentity;T&&(R(e,"federated_access_token",T.access_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(P)}),R(e,"federated_id_token",T.id_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(P)}))}else r.warn(`OIDC introspect error: ${y.statusText}`)}if(R(e,"authorization",g,{path:_()||"/",httpOnly:!0,expires:new Date(P)}),g!==l.id_token&&R(e,"idp_id_token",l.id_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(P)}),R(e,"idp_access_token",l.access_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(P)}),b(e,"code_verifier",{path:_()||"/"}),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(O.MCP_CALLBACK)){const M=`${e.req.url.split("?")[0].replace(O.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:M})}const G=typeof o.redirectTo=="string"?o.redirectTo:void 0;let J=z(new URL(G||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),V}catch(p){const u=p instanceof Error?p.message:String(p),h=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${u}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:u,error_details:h}]),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const L="Something went wrong";return r.error(`OIDC login error: ${L}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:L,error_details:null}]),e.text(L,500)}}function Te(i){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,t=i.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return $(t)||b(e,"authorization",{path:_()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const s=(await j(o,t)).end_session_endpoint;if(s){const d=new URL(e.req.url),L=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${L}://${p}`,h=N(u),C=h?fe(JSON.stringify({branch:N(u)})):void 0,w=h?`${ue(u)}/_auth/logout`:`${u}${I(O.POST_LOGOUT)}`;c=de(s,w,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",C)}}return r.info("Logout successful"),b(e,"authorization",{path:_()||"/"}),e.newResponse(null,302,{Location:c||I("/")})}}function be(i){return async e=>{const r=i.getConfig().access?.logoutReturnUrl,a=r||I("/");return e.newResponse(null,302,{Location:a})}}function qe(i){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=H.BH_API_URL,m=(t,c,s)=>t&&c?`${t} ${c.charAt(0)}`:s;try{if(!o)throw new Error("BH_API_URL is not set");const t=i.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(I("/"));const c=await fetch(`${o}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(I("/"))):(r.error("Invite error",await c.text()),e.redirect(I("/")));const s=await c.json(),d=new URL(I("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",s.organization.name),d.searchParams.set("invitedBy",m(s.invitedBy.firstName,s.invitedBy.lastName,s.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Ee(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,o=new URL(e.req.url),m=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||o.host,s=`${t}://${c}`;let d=o.searchParams.get("idpId");const L=o.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const u=o.searchParams.get("mcp_redirect_uri"),h=!!u;if(!a?.[d]){const g="Invalid idpId";if(r.error(`IdP login error: ${g}`),h){const E=K(L||void 0);k.sendMcpAuthorizationFailedMessage([{...A(E),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const w=me({localePrefixParam:o.searchParams.get("localePrefix"),l10n:i.getGlobalData()?.l10n}),l=d&&a?await Z(d,a[d]):void 0,f={};for(const g of Object.keys(l?.extraParams||{}))f[g]=o.searchParams.get(g)||l?.extraParams?.[g]||void 0;let S,n={};if(h&&u&&l&&l.type===X.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=pe("",{...l,extraParams:f},L,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:w});S=g.loginUrl,n=g.cookies||{}}else if(l){const g=re({...l,extraParams:f},s,L,m,w);S=g.loginUrl,n=g.cookies||{}}return Object.keys(n).forEach(g=>{R(e,g,n[g].value,n[g].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:S||new URL(e.req.url).pathname})}}function Fe(i){return async e=>{const r=e.get("logger"),a=await e.req.formData(),o=a.get("SAMLResponse"),m=a.get("RelayState");if(typeof o!="string"||typeof m!="string"){const n="SAMLResponse is required";return r.error(`SAML2 login error: ${n}`),e.text(`Bad request: ${n}`,400)}const t=oe(o),{success:c,uid:s,nameFormat:d,attrs:L,issuerId:p,expiresAt:u}=te(t),{idpId:h,redirectTo:C}=JSON.parse(m);if(!c){const n="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!u||Math.ceil(Date.now()/1e3)>=u){const n="SAML2 Token Expired";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const w=i.getConfig().ssoDirect?.[h];if(!w||!x(w)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(w.issuerId&&p&&W(w.issuerId,p))){const n="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!await se(t,w.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=ne(s,d,L,w.teamsAttributeName);if(!f.sub){const n="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}if(!f.email){const n="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}const S=await D.sign({...f,idpId:h},U,v.HS256);return R(e,"authorization",S,{path:_()||"/",httpOnly:!0,expires:new Date(u*1e3)}),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:C||"/"})}}function Be(i){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=I(z(a.pathname)),m=i.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,C])=>$(C)&&le(C));if(!(m&&t))return e.newResponse(null,302,{Location:o});const s=e.req.query("token"),d=s&&await ce(s);if(!d)return e.newResponse(null,302,{Location:o});if(!q(d[F]||[]).some(C=>C===Y||C===Q))return e.newResponse(null,302,{Location:o});const u=await D.sign({...d,idpId:t?.at(0)},U,v.HS256),h=Date.now()+B*1e3;return R(e,"authorization",u,{path:_()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{ve as authorizeHandler,Ee as idpLoginHandler,qe as inviteHandler,Te as logoutHandler,$e as oidcCallbackHandler,be as postLogoutHandler,Ue as redoclyLoginCallbackHandler,Be as redoclyTokenLoginHandler,Fe as samlCallbackHandler};
@@ -1,7 +1,7 @@
1
1
  import type { Context } from 'hono';
2
2
  import type { Store } from '../../store.js';
3
3
  export declare function corsProxyHandler(store?: Store, proxyBasePath?: string): (ctx: Context) => Promise<Response>;
4
- export declare function isPrivateIp(ip: string): boolean;
4
+ export declare function isPrivateIp(rawIp: string): boolean;
5
5
  export declare function resolveCorsProxyTarget(requestUrl: string, proxyBasePath: string): URL | null;
6
6
  export declare const CORS_PROXY_STREAM_HEADER = "x-redocly-proxy-streaming";
7
7
  //# sourceMappingURL=cors-proxy.d.ts.map
@@ -1,2 +1,2 @@
1
- import x from"node:dns";import{isIP as $}from"node:net";import{withPathPrefix as D}from"@redocly/theme/core/utils";import{ServerRoutes as L}from"../../../constants/common.js";import{envConfig as E}from"../../config/env-config.js";import{getRequestOrigin as k}from"../utils/get-request-origin.js";const A=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),I=new Set(["cookie","cookie2","accept-encoding"]),M=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),y="x-redocly-proxy-streaming",H="x-http-method-override",S="x-redocly-cookie";function ee(o,e=D(L.CORS_PROXY)){return async t=>{const r=new URL(t.req.url).pathname;if(r===e||r===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
- Usage: ${e}/https://api.example.com/path`);const s=j(t.req.url,e);if(!s)return t.text("Invalid proxied URL",400);const a=o?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(u=>X(s,u)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const f=k(t),h=s.origin===f,m=s.pathname===e||s.pathname.startsWith(`${e}/`);if(h&&!m)return new Response("Please use a direct request",{status:308,headers:{Location:s.toString(),Vary:"origin","Cache-Control":"private"}});const l=await N(s.hostname);if((!E.isDevelopMode||E.isReunite)&&l&&Y(l))return t.text("Requests to private network addresses are not allowed",403);const i=new Headers,P=v(t.req.raw.headers);for(const[n,u]of t.req.raw.headers)P.has(n.toLowerCase())||I.has(n.toLowerCase())||i.append(n,u);const g=i.get(S);if(g){const n=t.req.raw.headers.get("cookie")||"";i.set("cookie",n?`${n}; ${g}`:g),i.delete(S)}const O=t.req.raw.headers.get("origin")||"";z(O)&&i.delete("origin");let p=t.req.method;const R=i.get(H);R&&(p=R.toUpperCase(),i.delete(H));const w={method:p,headers:i,redirect:"manual"};p!=="GET"&&p!=="HEAD"&&t.req.raw.body&&(w.body=t.req.raw.body,w.duplex="half");let c;try{c=await fetch(s,w)}catch(n){const u=n instanceof Error?n.message:"unknown error",_=n instanceof Error&&n.cause instanceof Error?`: ${n.cause.message}`:"";return t.text(`Failed to proxy request: ${u}${_}`,502)}const T=c.headers.get("content-type")||"";if(V(T)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),q=v(c.headers);for(const n of q)d.delete(n);for(const n of M)d.delete(n);return d.set(y,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function W(o){try{return decodeURIComponent(o)}catch{return o}}function b(o){return o.replace(/^(https?):\/(?!\/)/i,"$1://")}function U(o,e){return e?o.includes("?")?e==="?"?o:`${o.endsWith("?")||o.endsWith("&")?o:`${o}&`}${e.slice(1)}`:`${o}${e}`:o}function v(o){const e=new Set(A),t=o.get("connection");if(!t)return e;for(const r of t.split(",")){const s=r.trim().toLowerCase();s&&e.add(s)}return e}function z(o){const e=o.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function N(o){if($(o))return o;try{const{address:e}=await x.promises.lookup(o);return e}catch{return null}}function Y(o){const e=o.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);return e?C(e[1]):o.includes(":")?F(o):C(o)}function C(o){const e=o.split(".").map(Number);if(e.length!==4||e.some(s=>isNaN(s)))return!0;const[t,r]=e;return t===0||t===10||t===127||t===172&&r>=16&&r<=31||t===192&&r===168||t===169&&r===254||t===100&&r>=64&&r<=127}function F(o){const e=o.toLowerCase();return e==="::1"||e==="::"||e.startsWith("fc")||e.startsWith("fd")||e.startsWith("fe80")}function X(o,e){let t;try{t=new URL(e)}catch{return!1}if(o.origin!==t.origin)return!1;const r=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return o.pathname.startsWith(r)||o.pathname===t.pathname}function V(o){const e=o.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function j(o,e){const t=new URL(o),r=t.pathname===e,s=t.pathname.startsWith(`${e}/`);if(!r&&!s)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const f=[a,W(a)];for(const h of f){const m=b(h),l=U(m,t.search);try{const i=new URL(l);if(i.protocol==="http:"||i.protocol==="https:")return i}catch{continue}}return null}const te=y;export{te as CORS_PROXY_STREAM_HEADER,ee as corsProxyHandler,Y as isPrivateIp,j as resolveCorsProxyTarget};
1
+ import $ from"node:dns";import{isIP as E}from"node:net";import{withPathPrefix as L}from"@redocly/theme/core/utils";import{ServerRoutes as A}from"../../../constants/common.js";import{envConfig as H}from"../../config/env-config.js";import{getRequestOrigin as D}from"../utils/get-request-origin.js";const M=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),k=new Set(["cookie","cookie2","accept-encoding"]),b=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),y="x-redocly-proxy-streaming",x="x-http-method-override",S="x-redocly-cookie";function oe(n,e=L(A.CORS_PROXY)){return async t=>{const o=new URL(t.req.url).pathname;if(o===e||o===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
+ Usage: ${e}/https://api.example.com/path`);const r=J(t.req.url,e);if(!r)return t.text("Invalid proxied URL",400);const a=n?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(p=>V(r,p)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const f=D(t),h=r.origin===f,m=r.pathname===e||r.pathname.startsWith(`${e}/`);if(h&&!m)return new Response("Please use a direct request",{status:308,headers:{Location:r.toString(),Vary:"origin","Cache-Control":"private"}});const u=await Y(r.hostname);if((!H.isDevelopMode||H.isReunite)&&u&&F(u))return t.text("Requests to private network addresses are not allowed",403);const i=new Headers,I=v(t.req.raw.headers);for(const[s,p]of t.req.raw.headers)I.has(s.toLowerCase())||k.has(s.toLowerCase())||i.append(s,p);const g=i.get(S);if(g){const s=t.req.raw.headers.get("cookie")||"";i.set("cookie",s?`${s}; ${g}`:g),i.delete(S)}const O=t.req.raw.headers.get("origin")||"";U(O)&&i.delete("origin");let l=t.req.method;const R=i.get(x);R&&(l=R.toUpperCase(),i.delete(x));const w={method:l,headers:i,redirect:"manual"};l!=="GET"&&l!=="HEAD"&&t.req.raw.body&&(w.body=t.req.raw.body,w.duplex="half");let c;try{c=await fetch(r,w)}catch(s){const p=s instanceof Error?s.message:"unknown error",_=s instanceof Error&&s.cause instanceof Error?`: ${s.cause.message}`:"";return t.text(`Failed to proxy request: ${p}${_}`,502)}const T=c.headers.get("content-type")||"";if(G(T)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),q=v(c.headers);for(const s of q)d.delete(s);for(const s of b)d.delete(s);return d.set(y,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function N(n){try{return decodeURIComponent(n)}catch{return n}}function W(n){return n.replace(/^(https?):\/(?!\/)/i,"$1://")}function z(n,e){return e?n.includes("?")?e==="?"?n:`${n.endsWith("?")||n.endsWith("&")?n:`${n}&`}${e.slice(1)}`:`${n}${e}`:n}function v(n){const e=new Set(M),t=n.get("connection");if(!t)return e;for(const o of t.split(",")){const r=o.trim().toLowerCase();r&&e.add(r)}return e}function U(n){const e=n.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function Y(n){const e=P(n);if(E(e))return e;try{const{address:t}=await $.promises.lookup(e);return t}catch{return null}}function F(n){const e=P(n).toLowerCase();if(!E(e))return!0;const t=X(e);return t?C(t):e.includes(":")?j(e):C(e)}function P(n){return n.startsWith("[")&&n.endsWith("]")?n.slice(1,-1):n}function X(n){const e=n.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);if(e){const o=parseInt(e[1],16),r=parseInt(e[2],16);return[o>>8&255,o&255,r>>8&255,r&255].join(".")}return n.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1]??null}function C(n){const e=n.split(".").map(Number);if(e.length!==4||e.some(r=>isNaN(r)))return!0;const[t,o]=e;return t===0||t===10||t===127||t===172&&o>=16&&o<=31||t===192&&o===168||t===169&&o===254||t===100&&o>=64&&o<=127}function j(n){const e=B(n);return n==="::1"||n==="::"||e!==null&&(e&65024)===64512||e!==null&&(e&65472)===65152}function B(n){const[e]=n.split(":");if(!e)return null;const t=parseInt(e,16);return Number.isNaN(t)?null:t}function V(n,e){let t;try{t=new URL(e)}catch{return!1}if(n.origin!==t.origin)return!1;const o=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return n.pathname.startsWith(o)||n.pathname===t.pathname}function G(n){const e=n.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function J(n,e){const t=new URL(n),o=t.pathname===e,r=t.pathname.startsWith(`${e}/`);if(!o&&!r)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const f=[a,N(a)];for(const h of f){const m=W(h),u=z(m,t.search);try{const i=new URL(u);if(i.protocol==="http:"||i.protocol==="https:")return i}catch{continue}}return null}const re=y;export{re as CORS_PROXY_STREAM_HEADER,oe as corsProxyHandler,F as isPrivateIp,J as resolveCorsProxyTarget};
@@ -0,0 +1,11 @@
1
+ export type IdpLoginL10n = {
2
+ defaultLocale: string;
3
+ locales: {
4
+ code: string;
5
+ }[];
6
+ };
7
+ export declare function resolveUiLocalesForIdpLogin(options: {
8
+ localePrefixParam: string | null;
9
+ l10n: IdpLoginL10n | undefined;
10
+ }): string | undefined;
11
+ //# sourceMappingURL=resolve-ui-locales-for-idp-login.d.ts.map
@@ -0,0 +1 @@
1
+ function i(o,n){if(!o?.trim()||!n?.length)return;const e=o.split("/").filter(Boolean)[0];return e?n.find(t=>t.code.toLowerCase()===e.toLowerCase())?.code:void 0}function a(o){const{localePrefixParam:n,l10n:e}=o;return(e?i(n,e.locales):void 0)??e?.defaultLocale}export{a as resolveUiLocalesForIdpLogin};
@@ -1 +1 @@
1
- import{serveStatic as C}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as H}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as c}from"../../constants/common.js";import{envConfig as L}from"../../config/env-config.js";import{authMiddleware as S}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as P}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as I}from"../../plugins/dev-onboarding/api/routes/index.js";import{authorizeHandler as D,oidcCallbackHandler as h,logoutHandler as O,postLogoutHandler as M,idpLoginHandler as N,redoclyLoginCallbackHandler as v,samlCallbackHandler as B,redoclyTokenLoginHandler as w,inviteHandler as U}from"./auth.js";import{appDataHandler as G}from"./app-data.js";import{searchFacetsHandler as F,searchHandler as y}from"./search.js";import{dynamicRouteHandler as K}from"./dynamic-route.js";import{pageDataHandler as Y,sharedPageDataHandler as k}from"./page-data.js";import{pathPrefixRedirectHandler as b}from"./path-prefix-redirect.js";import{getRoutesByLineHandler as _,resolvePathHandler as E,resolvePathsHandler as V,resolveSlugHandler as T}from"./resolve-route.js";import{feedbackHandler as x}from"./feedback.js";import{loggerMiddleware as $}from"../middleware/loggerMiddleware.js";import{responseHeadersMiddleware as z}from"../middleware/responseHeadersMiddleware.js";import{idleTimeoutMiddleware as Z}from"../middleware/idleTimeoutMiddleware.js";import{otelTracesHandler as X}from"./otel/otel.js";import{healthCheckHandler as q}from"./health.js";import{askAiHandler as W}from"./ask-ai.js";import{semanticSearchHandler as j}from"./semantic-search.js";import{replayOauth2RedirectCallbackHandler as J}from"./replay-oauth2-redirect.js";import{corsProxyHandler as r}from"./cors-proxy.js";import{mcpOAuthProtectedResourceHandler as Q,mcpOAuthAuthorizationServerHandler as u,mcpDynamicClientRegistrationHandler as aa,mcpAuthorizationHandler as ea,mcpTokenPortalHandler as ia,mcpCallbackHandler as f}from"./mcp-oauth.js";import{corsMiddleware as d}from"../middleware/corsMiddleware.js";import{installApiRoutes as la}from"./api-routes/api-routes.js";import{cookieMiddleware as ta}from"../middleware/cookieMiddleware.js";import{staticContentHandler as ma}from"../routes/static-content.js";import{infoHandler as R}from"./info.js";import{catalogHandler as na}from"./catalog/catalog.js";import{catalogRelationsHandler as da}from"./catalog/catalog-relations.js";import{bffCatalogHandler as oa}from"./catalog/bff-catalog.js";import{bffCatalogRevisionsHandler as pa}from"./catalog/bff-catalog-revisions.js";import{bffCatalogRelatedEntitiesHandler as Aa}from"./catalog/bff-catalog-related-entities.js";import{catalogAuthMiddleware as t}from"../middleware/catalogAuthMiddleware.js";import{telemetryMiddleware as Oa}from"../middleware/telemetry-middleware.js";import{errorHandler as _a}from"./error.js";function ae(a,l,m){const{resolveRouteData:o,readStaticAsset:p}=m;a.use("*",Z()),a.use("*",ta()),a.use("*",P(l)),a.use("*",S(l)),a.use("*",$()),a.use("*",z(l)),a.use("*",Oa()),a.use(e("*"),C({root:`./${c}`,getContent:(n,s)=>ma(n,s,l,p),rewriteRequestPath:n=>H(n)})),a.use(e(i.FEEDBACK),d({allowMethods:["POST"]})),a.use(e(i.ASK_AI),d({allowMethods:["POST"]})),a.use(e(i.SEMANTIC_SEARCH),d({allowMethods:["POST"]})),a.use("*",Ea(l));const A=g(l);a.use(e(i.INFO),R()),L.NEW_CATALOG_ENABLED&&(a.use(e(i.CATALOG_ENTITIES),t({serverOutDir:l.serverOutDir}),na(l)),a.use(e(i.CATALOG_ENTITIES_RELATIONS),t({serverOutDir:l.serverOutDir}),da(l)),a.get(e(i.BFF_CATALOG_ENTITIES),t({serverOutDir:l.serverOutDir,protectReadMethods:!1}),oa(l)),a.get(e(i.BFF_CATALOG_RELATED_ENTITIES),t({serverOutDir:l.serverOutDir,protectReadMethods:!1}),Aa(l)),a.get(e(i.BFF_CATALOG_REVISIONS),t({serverOutDir:l.serverOutDir,protectReadMethods:!1}),pa(l))),a.get(e(i.SHARED_PAGE_DATA),k(l)),a.get(e(i.PAGE_DATA),Y(l,o)),a.get(e(i.APP_DATA),G(l)),a.post(e(i.SEARCH),A,y(l)),a.post(e(i.SEARCH_FACETS),A,F(l)),a.post(e(i.AUTHORIZATION),D),a.post(e(i.LOGOUT),O(l)),a.get(e(i.LOGOUT),O(l)),a.get(e(i.POST_LOGOUT),M(l)),a.get(e(i.OIDC_CALLBACK),h(l)),a.get(e(i.REDOCLY_TOKEN_LOGIN),w(l)),a.get(e(i.REDOCLY_LOGIN_CALLBACK),v()),a.get(e(i.IDP_LOGIN),N(l)),a.post(e(i.SAML_CALLBACK),B(l)),a.get(e(i.INVITE),U(l)),a.get(e(i.HEALTH),q),a.get(`${i.MCP_OAUTH_PROTECTED_RESOURCE}${e("/mcp")}`,Q()),a.get(i.MCP_OAUTH_AUTHORIZATION_SERVER,u()),a.post(e(i.MCP_DYNAMIC_CLIENT_REGISTRATION),aa()),a.get(e(i.MCP_AUTHORIZATION),ea()),a.post(e(i.MCP_TOKEN_PORTAL),ia()),a.get(e(i.MCP_CALLBACK),f()),a.get(e(`${i.MCP_CALLBACK}/*`),f()),I(a,l),a.all(e(i.CORS_PROXY),r(l)),a.all(e(`${i.CORS_PROXY}/*`),r(l)),la(a,l),a.post(e(i.FEEDBACK),x(l)),a.post(e(i.RESOLVE_ROUTE_BY_PATH),E(l)),a.post(e(i.RESOLVE_ROUTES_BY_PATHS),V(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),T(l)),a.post(e(i.ASK_AI),W(l)),a.post(e(i.SEMANTIC_SEARCH),j(l)),a.get(e(i.GET_ROUTES_BY_LINE),_(l)),a.post(e(i.OTEL_TRACES),X),a.get(e(i.REPLAY_OAUTH2_CALLBACK),J),a.all(e("/*"),K(l,o,p)),a.get("*",b),a.onError(_a)}function Ea(a){return async(l,m)=>{await a.waitForPluginsLifecycle(),await m()}}function ee(a,l){a.get(e(i.INFO),R()),a.post(e(i.RESOLVE_ROUTE_BY_PATH),E(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),T(l)),a.get(e(i.GET_ROUTES_BY_LINE),_(l))}export{ee as installDevRoutes,ae as installProdRoutes,Ea as waitForPluginsLifecycle};
1
+ import{serveStatic as R}from"hono/serve-static";import{withPathPrefix as i,withoutPathPrefix as H}from"@redocly/theme/core/utils";import{ServerRoutes as l}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as L}from"../../constants/common.js";import{envConfig as c}from"../../config/env-config.js";import{authMiddleware as S}from"../middleware/authMiddleware.js";import{ensureSearchData as C}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as g}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as D}from"../../plugins/dev-onboarding/api/routes/index.js";import{authorizeHandler as I,oidcCallbackHandler as P,logoutHandler as f,postLogoutHandler as h,idpLoginHandler as v,redoclyLoginCallbackHandler as w,samlCallbackHandler as M,redoclyTokenLoginHandler as N,inviteHandler as B}from"./auth.js";import{appDataHandler as G}from"./app-data.js";import{searchFacetsHandler as F,searchHandler as U}from"./search.js";import{dynamicRouteHandler as y}from"./dynamic-route.js";import{pageDataHandler as Y,sharedPageDataHandler as K}from"./page-data.js";import{pathPrefixRedirectHandler as k}from"./path-prefix-redirect.js";import{getRoutesByLineHandler as E,resolvePathHandler as r,resolvePathsHandler as b,resolveSlugHandler as O}from"./resolve-route.js";import{feedbackHandler as x}from"./feedback.js";import{loggerMiddleware as V}from"../middleware/loggerMiddleware.js";import{responseHeadersMiddleware as X}from"../middleware/responseHeadersMiddleware.js";import{idleTimeoutMiddleware as $}from"../middleware/idleTimeoutMiddleware.js";import{otelTracesHandler as q}from"./otel/otel.js";import{healthCheckHandler as u}from"./health.js";import{askAiHandler as z}from"./ask-ai.js";import{semanticSearchHandler as W}from"./semantic-search.js";import{replayOauth2RedirectCallbackHandler as Z}from"./replay-oauth2-redirect.js";import{corsProxyHandler as _}from"./cors-proxy.js";import{corsMiddleware as n}from"../middleware/corsMiddleware.js";import{installApiRoutes as j}from"./api-routes/api-routes.js";import{cookieMiddleware as J}from"../middleware/cookieMiddleware.js";import{staticContentHandler as Q}from"../routes/static-content.js";import{infoHandler as s}from"./info.js";import{catalogHandler as aa}from"./catalog/catalog.js";import{catalogRelationsHandler as ea}from"./catalog/catalog-relations.js";import{bffCatalogHandler as ia}from"./catalog/bff-catalog.js";import{bffCatalogRevisionsHandler as la}from"./catalog/bff-catalog-revisions.js";import{bffCatalogRelatedEntitiesHandler as ta}from"./catalog/bff-catalog-related-entities.js";import{catalogAuthMiddleware as t}from"../middleware/catalogAuthMiddleware.js";import{telemetryMiddleware as ma}from"../middleware/telemetry-middleware.js";import{errorHandler as da}from"./error.js";import{installMcpAuthRoutes as na}from"./mcp-routes/mcp-routes.js";function Wa(a,e,m){const{resolveRouteData:o,readStaticAsset:p}=m;a.use("*",$()),a.use("*",J()),a.use("*",g(e)),a.use("*",S(e)),a.use("*",V()),a.use("*",X(e)),a.use("*",ma()),a.use(i("*"),R({root:`./${L}`,getContent:(d,T)=>Q(d,T,e,p),rewriteRequestPath:d=>H(d)})),a.use(i(l.FEEDBACK),n({allowMethods:["POST"]})),a.use(i(l.ASK_AI),n({allowMethods:["POST"]})),a.use(i(l.SEMANTIC_SEARCH),n({allowMethods:["POST"]})),a.use("*",oa(e));const A=C(e);a.use(i(l.INFO),s()),c.NEW_CATALOG_ENABLED&&(a.use(i(l.CATALOG_ENTITIES),t({serverOutDir:e.serverOutDir}),aa(e)),a.use(i(l.CATALOG_ENTITIES_RELATIONS),t({serverOutDir:e.serverOutDir}),ea(e)),a.get(i(l.BFF_CATALOG_ENTITIES),t({serverOutDir:e.serverOutDir,protectReadMethods:!1}),ia(e)),a.get(i(l.BFF_CATALOG_RELATED_ENTITIES),t({serverOutDir:e.serverOutDir,protectReadMethods:!1}),ta(e)),a.get(i(l.BFF_CATALOG_REVISIONS),t({serverOutDir:e.serverOutDir,protectReadMethods:!1}),la(e))),a.get(i(l.SHARED_PAGE_DATA),K(e)),a.get(i(l.PAGE_DATA),Y(e,o)),a.get(i(l.APP_DATA),G(e)),a.post(i(l.SEARCH),A,U(e)),a.post(i(l.SEARCH_FACETS),A,F(e)),a.post(i(l.AUTHORIZATION),I),a.post(i(l.LOGOUT),f(e)),a.get(i(l.LOGOUT),f(e)),a.get(i(l.POST_LOGOUT),h(e)),a.get(i(l.OIDC_CALLBACK),P(e)),a.get(i(l.REDOCLY_TOKEN_LOGIN),N(e)),a.get(i(l.REDOCLY_LOGIN_CALLBACK),w()),a.get(i(l.IDP_LOGIN),v(e)),a.post(i(l.SAML_CALLBACK),M(e)),a.get(i(l.INVITE),B(e)),a.get(i(l.HEALTH),u),na(a,e),D(a,e),a.all(i(l.CORS_PROXY),_(e)),a.all(i(`${l.CORS_PROXY}/*`),_(e)),j(a,e),a.post(i(l.FEEDBACK),x(e)),a.post(i(l.RESOLVE_ROUTE_BY_PATH),r(e)),a.post(i(l.RESOLVE_ROUTES_BY_PATHS),b(e)),a.post(i(l.RESOLVE_ROUTE_BY_SLUG),O(e)),a.post(i(l.ASK_AI),z(e)),a.post(i(l.SEMANTIC_SEARCH),W(e)),a.get(i(l.GET_ROUTES_BY_LINE),E(e)),a.post(i(l.OTEL_TRACES),q),a.get(i(l.REPLAY_OAUTH2_CALLBACK),Z),a.all(i("/*"),y(e,o,p)),a.get("*",k),a.onError(da)}function oa(a){return async(e,m)=>{await a.waitForPluginsLifecycle(),await m()}}function Za(a,e){a.get(i(l.INFO),s()),a.post(i(l.RESOLVE_ROUTE_BY_PATH),r(e)),a.post(i(l.RESOLVE_ROUTE_BY_SLUG),O(e)),a.get(i(l.GET_ROUTES_BY_LINE),E(e))}export{Za as installDevRoutes,Wa as installProdRoutes,oa as waitForPluginsLifecycle};
@@ -0,0 +1,14 @@
1
+ export type ClientMetadataDocument = {
2
+ redirect_uris: string[];
3
+ client_name?: string;
4
+ grant_types?: string[];
5
+ response_types?: string[];
6
+ scope?: string;
7
+ token_endpoint_auth_method?: string;
8
+ };
9
+ export declare function isRedirectUriRegistered(redirectUri: string, registeredUris: string[], options?: {
10
+ ignorePort?: boolean;
11
+ }): boolean;
12
+ export declare function isClientMetadataDocumentUrl(clientId: string | null | undefined): clientId is string;
13
+ export declare function fetchClientMetadataDocument(clientId: string): Promise<ClientMetadataDocument>;
14
+ //# sourceMappingURL=mcp-client-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ import l from"@redocly/ajv";import{readStreamWithSizeLimit as p}from"../../../api-routes/helpers/read-stream-with-size-limit.js";import{ResponseSizeLimitError as m}from"../../../api-routes/errors/response-size-limit.js";const d=new l({allErrors:!0,strictSchema:!1,strictTypes:!1}),u={type:"object",properties:{redirect_uris:{type:"array",items:{type:"string",minLength:1},minItems:1},client_name:{type:"string",nullable:!0},grant_types:{type:"array",items:{type:"string"},nullable:!0},response_types:{type:"array",items:{type:"string"},nullable:!0},scope:{type:"string",nullable:!0},token_endpoint_auth_method:{type:"string",nullable:!0}},required:["redirect_uris"],additionalProperties:!0},i=d.compile(u),f=5e3,s=.0625;function S(e,t,r){if(!r?.ignorePort)return t.includes(e);const n=c(e);return n?t.some(o=>{const a=c(o);return!!a&&a.protocol===n.protocol&&a.hostname===n.hostname&&a.pathname===n.pathname}):!1}function c(e){try{return new URL(e)}catch{return null}}function h(e){if(!e)return!1;let t;try{t=new URL(e)}catch{return!1}return t.protocol==="https:"}async function _(e){if(!h(e))throw new Error("client_id must be an https URL");const t=await fetch(e,{method:"GET",redirect:"error",headers:{Accept:"application/json"},signal:AbortSignal.timeout(f)});if(!t.ok)throw new Error(`Failed to fetch client metadata: HTTP ${t.status}`);const r=t.headers.get("content-type")||"";if(!/^application\/(.+\+)?json/i.test(r))throw new Error(`Unexpected content-type for client metadata: ${r}`);if(!t.body)throw new Error("Client metadata response has no body");const n=await p({stream:t.body,maxSizeMB:s,onSizeExceededErrorHandler:()=>{throw new m(`Client metadata exceeds maximum size of ${s*1024} KB`)}});let o;try{o=JSON.parse(n.toString("utf-8"))}catch(a){throw new Error(`Invalid JSON in client metadata: ${a.message}`)}return y(o)}function y(e){if(!i(e)){const t=i.errors?.[0],r=t?`${t.instancePath||"/"} ${t.message}`.trim():"Validation failed";throw new Error(`Invalid client metadata: ${r}`)}return e}export{_ as fetchClientMetadataDocument,h as isClientMetadataDocumentUrl,S as isRedirectUriRegistered};
@@ -5,8 +5,11 @@ export type McpContextPayload = {
5
5
  mcpClientId: string | null;
6
6
  mcpState: string | null;
7
7
  mcpSessionId: string;
8
+ mcpCodeChallenge?: string | null;
9
+ mcpCodeChallengeMethod?: string | null;
8
10
  timestamp: number;
9
11
  };
12
+ export declare function verifyPkce(codeChallenge: string, codeChallengeMethod: string | undefined, codeVerifier: string): boolean;
10
13
  export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
11
14
  export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
12
15
  export declare function mcpOAuthProtectedResourceHandler(): Handler;
@@ -0,0 +1 @@
1
+ import{getCookie as C}from"hono/cookie";import{createHash as P,timingSafeEqual as v}from"node:crypto";import{ulid as E}from"ulid";import{AUTH_URL as f,JWT_SECRET_KEY as M}from"../../../constants/common.js";import{ServerRoutes as _}from"../../../../constants/common.js";import{withPathPrefix as u}from"@redocly/theme/core/utils";import{telemetry as m}from"../../../telemetry/index.js";import{envConfig as T}from"../../../config/env-config.js";import{createMcpAuthorizationCode as R,verifyMcpAuthorizationCode as D,createMcpSessionResource as h}from"../../auth.js";import*as g from"../../jwt/jwt.js";import{AlgorithmTypes as S}from"../../jwt/types.js";import{getRequestOrigin as k}from"../../utils/get-request-origin.js";import{fetchClientMetadataDocument as O,isClientMetadataDocumentUrl as y,isRedirectUriRegistered as L}from"./mcp-client-metadata.js";const o=(e,r,n=200,a)=>e.json(r,n,{"Content-Type":"application/json",...a??{}}),A=new Set(["S256"]),q="https://claude.ai/oauth/claude-code-client-metadata";function z(e,r,n){if(!A.has(r||""))return!1;const a=P("sha256").update(n).digest("base64url"),t=Buffer.from(a),s=Buffer.from(e);return t.length!==s.length?!1:v(t,s)}async function H(e){const r=Math.floor(Date.now()/1e3);return g.sign({type:"mcp_context",...e,iat:r,exp:r+600},M,S.HS256)}async function $(e){await g.verify(e,M,S.HS256);const{payload:r}=g.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function Q(){return async e=>{if(e.req.method!=="GET")return o(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=k(e);return o(e,{resource:`${r}${u("/mcp")}`,authorization_servers:[r,f].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${r}${_.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function X(){return async e=>{const r=k(e);return o(e,{issuer:f||"",authorization_endpoint:`${r}${u(_.MCP_AUTHORIZATION)}`,token_endpoint:`${r}${u(_.MCP_TOKEN_PORTAL)}`,jwks_uri:`${f||""}/.well-known/jwks.json`,scopes_supported:["openid","profile","email","offline_access"],registration_endpoint:`${r}${u(_.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,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"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0})}}function x(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{return o(e,{client_id:T.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(r){return o(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function ee(){return async e=>{const r=new URL(e.req.url),{searchParams:n}=r,a=n.get("redirect_uri"),t=n.get("client_id"),s=n.get("code_challenge"),i=n.get("code_challenge_method"),d=E();if(s&&!A.has(i||""))return o(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...A].join(", ")}`},400);if(y(t)&&!s)return o(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(y(t)&&a)try{const c=await O(t),l=t===q;if(!L(a,c.redirect_uris,{ignorePort:l}))return o(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(c){const l=c instanceof Error?c.message:String(c);return o(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${l}`},400)}m.sendMcpAuthorizationStartedMessage([{...h(d),redirect_uri:a||null}]);const p=k(e),w={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:t,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:s,mcpCodeChallengeMethod:s?i:null,timestamp:Date.now()};try{const c=await H(w),l=new URL(u(_.IDP_LOGIN),p);return l.searchParams.set("redirectTo",`${_.MCP_CALLBACK}/${c}`),l.searchParams.set("idpId","oidc"),e.redirect(l.toString())}catch(c){const l=c instanceof Error?c.message:String(c),U=c instanceof Error?c.stack:String(c);m.sendMcpAuthorizationFailedMessage([{...h(d),error:l,error_details:U}]);const I=new URL(u(`${f}/oauth2/auth`));return I.search=n.toString(),e.redirect(I.toString())}}}function re(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),n=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0,s=r.get("code_verifier")||void 0;if(n!=="authorization_code"||!a)return o(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await D(a);if(t&&t!==i.redirect_uri)return o(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(i.code_challenge){if(!s)return o(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!z(i.code_challenge,i.code_challenge_method,s))return o(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(T.OAUTH_CLIENT_ID&&i.client_id&&!y(i.client_id)&&i.client_id!==T.OAUTH_CLIENT_ID)return o(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=i.id_token;if(typeof d!="string"||d.length===0)return o(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let p=d;if(i.idp_access_token){const{payload:c,header:{kid:l}}=g.decode(d);p=await g.sign({...c,idp_access_token:i.idp_access_token},M,S.HS256,l)}return o(e,{access_token:p,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:d},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return o(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const n=r instanceof Error?r.message:String(r);return o(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}function te(){return async e=>{const r=new URL(e.req.url);let n=r.searchParams.get("context");if(!n&&r.pathname.startsWith(u(`${_.MCP_CALLBACK}/`))){const t=r.pathname.split("/");n=t[t.length-1]}if(!n)return m.sendMcpAuthorizationFailedMessage([{...h(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let a=null;try{const t=await $(n);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=C(e,"idp_id_token")||C(e,"authorization"),i=C(e,"idp_access_token");if(!s)return m.sendMcpAuthorizationFailedMessage([{...h(a),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await R({idToken:s,idpAccessToken:i||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),p=new URL(t.originalRedirectUri);return p.searchParams.set("code",d),t.mcpState&&p.searchParams.set("state",t.mcpState),m.sendMcpAuthorizationCompletedMessage([{...h(a),redirect_uri:t.originalRedirectUri||null}]),e.redirect(p.toString())}catch(t){const s=t instanceof Error?t.message:String(t),i=t instanceof Error?t.stack:String(t);return m.sendMcpAuthorizationFailedMessage([{...h(a),error:s,error_details:i}]),e.text(`Invalid MCP callback: ${s}`,400)}}}export{H as createMcpContextToken,ee as mcpAuthorizationHandler,te as mcpCallbackHandler,x as mcpDynamicClientRegistrationHandler,X as mcpOAuthAuthorizationServerHandler,Q as mcpOAuthProtectedResourceHandler,re as mcpTokenPortalHandler,$ as verifyAndParseMcpContextToken,z as verifyPkce};
@@ -0,0 +1,4 @@
1
+ import type { Hono } from 'hono';
2
+ import type { Store } from '../../../store';
3
+ export declare function installMcpAuthRoutes(router: Hono, store: Store): void;
4
+ //# sourceMappingURL=mcp-routes.d.ts.map
@@ -0,0 +1 @@
1
+ import{ServerRoutes as e}from"../../../../constants/common.js";import{EntitlementsProvider as m}from"../../../entitlements/entitlements-provider.js";import{withPathPrefix as n}from"@redocly/theme/core/utils";import{mcpOAuthProtectedResourceHandler as p,mcpOAuthAuthorizationServerHandler as s,mcpDynamicClientRegistrationHandler as A,mcpAuthorizationHandler as C,mcpTokenPortalHandler as l,mcpCallbackHandler as i}from"./mcp-oauth.js";function d(t,o){const c=o.getConfig().mcp,a=m.instance().canAccessFeature("mcp");!c?.hide&&!c?.docs?.hide&&!!a&&(t.get(`${e.MCP_OAUTH_PROTECTED_RESOURCE}${n("/mcp")}`,p()),t.get(e.MCP_OAUTH_AUTHORIZATION_SERVER,s()),t.post(n(e.MCP_DYNAMIC_CLIENT_REGISTRATION),A()),t.get(n(e.MCP_AUTHORIZATION),C()),t.post(n(e.MCP_TOKEN_PORTAL),l()),t.get(n(e.MCP_CALLBACK),i()),t.get(n(`${e.MCP_CALLBACK}/*`),i()))}export{d as installMcpAuthRoutes};
@@ -1 +1 @@
1
- import{setCookie as A}from"hono/cookie";import{withPathPrefix as f}from"@redocly/theme/core/utils";import{DEV_LOGIN_SLUG as E}from"../../constants/common.js";import{CACHE_CONTROL_NO_CACHE_HEADER_VALUE as b}from"../constants/common.js";import{getAuthProviderLoginParams as C,buildLoginUrl as y}from"./auth.js";import{renderPage as _}from"../ssr/index.js";import{telemetry as v}from"../../cli/telemetry/index.js";async function S(r,t,o,n){const{isAuthenticated:a}=r.get("auth"),e=r.req.raw.headers.get("x-forwarded-host"),i=r.req.raw.headers.get("x-forwarded-proto"),U=e?`${i==="http"||i==="https"?i:"https"}://${e}`:new URL(r.req.url).origin,l=t.getConfig().ssoDirect,u=Object.keys(l||{}),s=n||u[0],p=l?.[s];if(a)return z(r,t,{slug:o},403);const d=s&&p?await C(s,p):void 0,g=d?{...d,extraParams:{...d.extraParams,prompt:"login"}}:void 0,{loginUrl:w,cookies:h={}}=g&&y(g,U,o)||{},m=t.globalData.auth?.devLogin||u.length>1?H(o):w;return Object.keys(h).forEach(c=>{A(r,c,h[c].value,h[c].options)}),m?r.newResponse(null,302,{Location:m}):r.text("Unauthorized",401)}const L={};async function z(r,t,o,n,a){let e=L[n];if(!e){const i={templateId:String(a||n),fsPath:"/",...o,baseSlug:o.slug};e=(await _(i,{},r,t,v)).html,L[n]=e}return r.html(e,n,{"Cache-Control":b})}function H(r){const t=new URLSearchParams({redirectTo:f(r)});return`${f(E)}?${t}`}async function T(r){return r.text("Forbidden",P(r))}function k(r){return r.json({message:"Forbidden"},P(r))}function P(r){const{isAuthenticated:t}=r.get("auth");return t?403:401}function G(r){const t=r?.match(/(?:^|:)(\d{1,3}(?:\.\d{1,3}){3})$/);return t?t[1]:r}export{H as getLoginUrlWithRedirect,z as handleErrorPageRender,S as handleUnauthorized,k as handleUnauthorizedApiRequest,T as handleUnauthorizedAsset,G as normalizeIpAddress};
1
+ import{setCookie as A}from"hono/cookie";import{withPathPrefix as l}from"@redocly/theme/core/utils";import{DEV_LOGIN_SLUG as E}from"../../constants/common.js";import{CACHE_CONTROL_NO_CACHE_HEADER_VALUE as b}from"../constants/common.js";import{getAuthProviderLoginParams as C,buildLoginUrl as y}from"./auth.js";import{renderPage as _}from"../ssr/index.js";import{telemetry as v}from"../../cli/telemetry/index.js";async function S(r,t,o,n){const{isAuthenticated:a}=r.get("auth"),e=r.req.raw.headers.get("x-forwarded-host"),i=r.req.raw.headers.get("x-forwarded-proto"),U=e?`${i==="http"||i==="https"?i:"https"}://${e}`:new URL(r.req.url).origin,u=t.getConfig().ssoDirect,p=Object.keys(u||{}),s=n||p[0],g=u?.[s];if(a)return z(r,t,{slug:o},403);const d=s&&g?await C(s,g):void 0,m=d?{...d,extraParams:{...d.extraParams,prompt:"login"}}:void 0,{loginUrl:w,cookies:h={}}=m&&y(m,U,l(o))||{},f=t.globalData.auth?.devLogin||p.length>1?H(o):w;return Object.keys(h).forEach(c=>{A(r,c,h[c].value,h[c].options)}),f?r.newResponse(null,302,{Location:f}):r.text("Unauthorized",401)}const L={};async function z(r,t,o,n,a){let e=L[n];if(!e){const i={templateId:String(a||n),fsPath:"/",...o,baseSlug:o.slug};e=(await _(i,{},r,t,v)).html,L[n]=e}return r.html(e,n,{"Cache-Control":b})}function H(r){const t=new URLSearchParams({redirectTo:l(r)});return`${l(E)}?${t}`}async function T(r){return r.text("Forbidden",P(r))}function k(r){return r.json({message:"Forbidden"},P(r))}function P(r){const{isAuthenticated:t}=r.get("auth");return t?403:401}function G(r){const t=r?.match(/(?:^|:)(\d{1,3}(?:\.\d{1,3}){3})$/);return t?t[1]:r}export{H as getLoginUrlWithRedirect,z as handleErrorPageRender,S as handleUnauthorized,k as handleUnauthorizedApiRequest,T as handleUnauthorizedAsset,G as normalizeIpAddress};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/redoc-revel",
3
- "version": "0.133.0-next.3",
3
+ "version": "0.133.0-next.5",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "@opentelemetry/sdk-trace-web": "2.6.1",
30
30
  "@opentelemetry/semantic-conventions": "1.40.0",
31
31
  "@redocly/ajv": "8.18.0",
32
- "@redocly/openapi-core": "2.30.2",
32
+ "@redocly/openapi-core": "2.30.3",
33
33
  "@shikijs/transformers": "3.21.0",
34
34
  "@tanstack/react-query": "5.62.3",
35
35
  "@tanstack/react-table": "8.21.3",
@@ -76,7 +76,7 @@
76
76
  "reactjs-popup": "2.0.6",
77
77
  "semver": "7.7.3",
78
78
  "shiki": "3.21.0",
79
- "simple-git": "3.32.3",
79
+ "simple-git": "3.36.0",
80
80
  "sitemap": "7.1.1",
81
81
  "stream-http": "3.2.0",
82
82
  "styled-components": "5.3.11",
@@ -90,14 +90,14 @@
90
90
  "xpath": "0.0.34",
91
91
  "yaml-ast-parser": "0.0.43",
92
92
  "zod": "^3.25.76",
93
- "@redocly/asyncapi-docs": "1.10.0-next.3",
93
+ "@redocly/asyncapi-docs": "1.10.0-next.5",
94
94
  "@redocly/config": "0.48.1",
95
- "@redocly/graphql-docs": "1.10.0-next.3",
96
- "@redocly/openapi-docs": "3.21.0-next.3",
95
+ "@redocly/graphql-docs": "1.10.0-next.5",
96
+ "@redocly/openapi-docs": "3.21.0-next.5",
97
97
  "@redocly/portal-legacy-ui": "0.16.0-next.0",
98
- "@redocly/portal-plugin-mock-server": "0.18.0-next.3",
99
- "@redocly/realm-asyncapi-sdk": "0.11.0-next.2",
100
- "@redocly/theme": "0.65.0-next.3"
98
+ "@redocly/portal-plugin-mock-server": "0.18.0-next.5",
99
+ "@redocly/realm-asyncapi-sdk": "0.11.0-next.3",
100
+ "@redocly/theme": "0.65.0-next.5"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "react": "^19.2.4",
@@ -1 +0,0 @@
1
- import{getCookie as f}from"hono/cookie";import{ulid as S}from"ulid";import{AUTH_URL as k,JWT_SECRET_KEY as y}from"../../constants/common.js";import{ServerRoutes as p}from"../../../constants/common.js";import{withPathPrefix as l}from"@redocly/theme/core/utils";import{telemetry as _}from"../../telemetry/index.js";import{envConfig as C}from"../../config/env-config.js";import{createMcpAuthorizationCode as w,verifyMcpAuthorizationCode as A,createMcpSessionResource as u}from"../auth.js";import*as m from"../jwt/jwt.js";import{AlgorithmTypes as T}from"../jwt/types.js";import{getRequestOrigin as M}from"../utils/get-request-origin.js";const n=(e,r,o=200,a)=>e.json(r,o,{"Content-Type":"application/json",...a??{}});async function I(e){const r=Math.floor(Date.now()/1e3);return m.sign({type:"mcp_context",...e,iat:r,exp:r+600},y,T.HS256)}async function P(e){await m.verify(e,y,T.HS256);const{payload:r}=m.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function q(){return async e=>{if(e.req.method!=="GET")return n(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=M(e);return n(e,{resource:`${r}${l("/mcp")}`,authorization_servers:[r],bearer_methods_supported:["header"],resource_documentation:`${r}${p.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function b(){return async e=>{const r=M(e);return n(e,{issuer:k||"",authorization_endpoint:`${r}${l(p.MCP_AUTHORIZATION)}`,token_endpoint:`${r}${l(p.MCP_TOKEN_PORTAL)}`,jwks_uri:`${k||""}/.well-known/jwks.json`,registration_endpoint:`${r}${l(p.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 N(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{return n(e,{client_id:C.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(r){return n(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function j(){return async e=>{const r=new URL(e.req.url),{searchParams:o}=r,a=o.get("redirect_uri"),t=S();_.sendMcpAuthorizationStartedMessage([{...u(t),redirect_uri:a||null}]);const i=M(e),c={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:o.get("client_id"),mcpState:o.get("state"),mcpSessionId:t,timestamp:Date.now()};try{const s=await I(c),d=new URL(l(p.IDP_LOGIN),i);return d.searchParams.set("redirectTo",`${p.MCP_CALLBACK}/${s}`),d.searchParams.set("idpId","oidc"),e.redirect(d.toString())}catch(s){const d=s instanceof Error?s.message:String(s),h=s instanceof Error?s.stack:String(s);_.sendMcpAuthorizationFailedMessage([{...u(t),error:d,error_details:h}]);const g=new URL(l(`${k}/oauth2/auth`));return g.search=o.toString(),e.redirect(g.toString())}}}function F(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),o=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0;if(o!=="authorization_code"||!a)return n(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await A(a);if(t&&t!==i.redirect_uri)return n(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(C.OAUTH_CLIENT_ID&&i.client_id&&i.client_id!==C.OAUTH_CLIENT_ID)return n(e,{error:"invalid_client",error_description:"Client mismatch"},400);const c=i.id_token;if(typeof c!="string"||c.length===0)return n(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let s=c;if(i.idp_access_token){const{payload:h,header:{kid:g}}=m.decode(c);s=await m.sign({...h,idp_access_token:i.idp_access_token},y,T.HS256,g)}return n(e,{access_token:s,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:c},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return n(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const o=r instanceof Error?r.message:String(r);return n(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}function B(){return async e=>{const r=new URL(e.req.url);let o=r.searchParams.get("context");if(!o&&r.pathname.startsWith(l(`${p.MCP_CALLBACK}/`))){const t=r.pathname.split("/");o=t[t.length-1]}if(!o)return _.sendMcpAuthorizationFailedMessage([{...u(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let a=null;try{const t=await P(o);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const i=f(e,"idp_id_token")||f(e,"authorization"),c=f(e,"idp_access_token"),s=await w({idToken:i||"",idpAccessToken:c||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,ttlSec:600}),d=new URL(t.originalRedirectUri);return d.searchParams.set("code",s),t.mcpState&&d.searchParams.set("state",t.mcpState),_.sendMcpAuthorizationCompletedMessage([{...u(a),redirect_uri:t.originalRedirectUri||null}]),e.redirect(d.toString())}catch(t){const i=t instanceof Error?t.message:String(t),c=t instanceof Error?t.stack:String(t);return _.sendMcpAuthorizationFailedMessage([{...u(a),error:i,error_details:c}]),e.text(`Invalid MCP callback: ${i}`,400)}}}export{I as createMcpContextToken,j as mcpAuthorizationHandler,B as mcpCallbackHandler,N as mcpDynamicClientRegistrationHandler,b as mcpOAuthAuthorizationServerHandler,q as mcpOAuthProtectedResourceHandler,F as mcpTokenPortalHandler,P as verifyAndParseMcpContextToken};