@redocly/realm 0.133.0-next.3 → 0.133.0-next.4
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.
- package/CHANGELOG.md +32 -0
- package/dist/markdoc/nodes/table.js +1 -1
- package/dist/server/plugins/catalog-entities/database/mappers/create-bff-related-entity.js +1 -1
- package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/asyncapi-entities-extractor.js +1 -1
- package/dist/server/plugins/catalog-entities/extensions/extractors/api-description/openapi-entities-extractor.js +1 -1
- package/dist/server/plugins/dev-onboarding/template/components/Container.js +9 -4
- package/dist/server/plugins/openapi-docs/ai-search-indexer.d.ts +11 -0
- package/dist/server/plugins/openapi-docs/ai-search-indexer.js +1 -0
- package/dist/server/plugins/openapi-docs/openapi-search-builder.d.ts +24 -0
- package/dist/server/plugins/openapi-docs/openapi-search-builder.js +1 -0
- package/dist/server/plugins/openapi-docs/search/get-ai-search-documents.js +1 -1
- package/dist/server/plugins/openapi-docs/search-indexer.d.ts +8 -10
- package/dist/server/plugins/openapi-docs/search-indexer.js +1 -1
- package/dist/server/plugins/search/documents/search-documents.js +1 -1
- package/dist/server/plugins/search/engines/flexsearch/index.js +1 -1
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/cors-proxy.d.ts +1 -1
- package/dist/server/web-server/routes/cors-proxy.js +2 -2
- package/dist/server/web-server/routes/index.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-routes.d.ts +4 -0
- package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -0
- package/dist/server/web-server/utils.js +1 -1
- package/package.json +7 -7
- package/dist/server/web-server/routes/mcp-oauth.js +0 -1
- /package/dist/server/web-server/routes/{mcp-oauth.d.ts → mcp-routes/mcp-oauth.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @redocly/realm
|
|
2
2
|
|
|
3
|
+
## 0.133.0-next.4
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 84838a59f0f: Excluded buttons and links from heading content to improve accessibility.
|
|
8
|
+
- 1cc46010a1f: Separated OpenAPI AI search indexing from the standard search indexing flow in Realm.
|
|
9
|
+
This improved the internal search architecture to make future AI-specific enhancements safer without affecting the existing OpenAPI search behavior.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- ba76c067857: Fixed unauthenticated callback execution in the MCP docs server.
|
|
14
|
+
- e13c79eb30c: Fixed an issue where searching in projects without searchable content caused a server error.
|
|
15
|
+
- f7725b9cc73: Fixed an issue where login and logout redirects caused 404 and URI mismatch errors with projects with path prefix.
|
|
16
|
+
- a160e4ca6f1: Updated `@redocly/openapi-core` to version `2.30.3`.
|
|
17
|
+
- 742e81770bc: Hardened Realm CORS proxy validation to block private network targets before proxying requests.
|
|
18
|
+
- ba76c067857: Fixed an issue where MCP auth URLs were displayed when MCP docs had been disabled.
|
|
19
|
+
- Updated dependencies [a160e4ca6f1]
|
|
20
|
+
- Updated dependencies [84838a59f0f]
|
|
21
|
+
- Updated dependencies [1cc46010a1f]
|
|
22
|
+
- Updated dependencies [ab8c509bb91]
|
|
23
|
+
- @redocly/openapi-docs@3.21.0-next.4
|
|
24
|
+
- @redocly/theme@0.65.0-next.4
|
|
25
|
+
- @redocly/portal-plugin-mock-server@0.18.0-next.4
|
|
26
|
+
- @redocly/asyncapi-docs@1.10.0-next.4
|
|
27
|
+
- @redocly/graphql-docs@1.10.0-next.4
|
|
28
|
+
|
|
3
29
|
## 0.133.0-next.3
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
|
@@ -101,6 +127,12 @@
|
|
|
101
127
|
- @redocly/asyncapi-docs@1.10.0-next.0
|
|
102
128
|
- @redocly/graphql-docs@1.10.0-next.0
|
|
103
129
|
|
|
130
|
+
## 0.132.2
|
|
131
|
+
|
|
132
|
+
### Patch Changes
|
|
133
|
+
|
|
134
|
+
- 8d30048cf1: Hardened Realm CORS proxy validation to block private network targets before proxying requests.
|
|
135
|
+
|
|
104
136
|
## 0.132.1
|
|
105
137
|
|
|
106
138
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import n from"@markdoc/markdoc";import{isTag as m}from"../helpers/guards/is-tag.js";import{getInnerText as
|
|
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{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-
|
|
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
|
|
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
|
|
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{
|
|
36
|
+
`;export{f as Container};
|
|
@@ -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{
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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};
|
|
@@ -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 q}from"hono/cookie";import{AuthProviderType as V}from"@redocly/config";import{withPathPrefix as I,getPathPrefix as _}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 D from"../jwt/jwt.js";import{AlgorithmTypes as P}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function De(i){if(z.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"},$,P.HS256);return R(i,"authorization",a,{path:_()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),i.newResponse(null,200,{})}function Pe(){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 ve(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(I(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:L}=D.decode(g.id_token),o=L.alg===P.RS256;if(s.audience?.length&&![...b(f.aud||[]),...b(f[E]||[])].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 v=o?g.id_token:await D.sign({...f,idpId:m},$,P.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 O=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&&(R(e,"federated_access_token",T.access_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}),R(e,"federated_id_token",T.id_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}))}else r.warn(`OIDC introspect error: ${w.statusText}`)}if(R(e,"authorization",v,{path:_()||"/",httpOnly:!0,expires:new Date(O)}),v!==g.id_token&&R(e,"idp_id_token",g.id_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),R(e,"idp_access_token",g.access_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),q(e,"code_verifier",{path:_()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(S.MCP_CALLBACK)){const M=`${e.req.url.split("?")[0].replace(S.OIDC_CALLBACK,"")}${n.redirectTo}`;return e.newResponse(null,302,{Location:M})}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.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}${I(S.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:_()||"/"}),e.newResponse(null,302,{Location:c||I("/")})}}function Ue(i){return async e=>{const r=i.getConfig().access?.logoutReturnUrl,a=r||I("/");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(I("/"));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(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 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 v=N(y||void 0);k.sendMcpAuthorizationFailedMessage([{...A(v),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,L={};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,L=o.cookies||{}}else if(p){const o=ee({...p,extraParams:g},s,y,m);f=o.loginUrl,L=o.cookies||{}}return Object.keys(L).forEach(o=>{R(e,o,L[o].value,L[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 L=await D.sign({...f,idpId:h},$,P.HS256);return R(e,"authorization",L,{path:_()||"/",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=I(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 D.sign({...d,idpId:t?.at(0)},$,P.HS256),h=Date.now()+F*1e3;return R(e,"authorization",l,{path:_()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{De as authorizeHandler,qe as idpLoginHandler,Te as inviteHandler,$e as logoutHandler,ve as oidcCallbackHandler,Ue as postLogoutHandler,Pe as redoclyLoginCallbackHandler,Ee as redoclyTokenLoginHandler,be 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(
|
|
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
|
|
2
|
-
Usage: ${e}/https://api.example.com/path`);const
|
|
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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStatic as
|
|
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 @@
|
|
|
1
|
+
import{getCookie as f}from"hono/cookie";import{ulid as A}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 u}from"../../../telemetry/index.js";import{envConfig as C}from"../../../config/env-config.js";import{createMcpAuthorizationCode as S,verifyMcpAuthorizationCode as w,createMcpSessionResource as _}from"../../auth.js";import*as m from"../../jwt/jwt.js";import{AlgorithmTypes as M}from"../../jwt/types.js";import{getRequestOrigin as T}from"../../utils/get-request-origin.js";const i=(e,r,o=200,s)=>e.json(r,o,{"Content-Type":"application/json",...s??{}});async function I(e){const r=Math.floor(Date.now()/1e3);return m.sign({type:"mcp_context",...e,iat:r,exp:r+600},y,M.HS256)}async function P(e){await m.verify(e,y,M.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 i(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=T(e);return i(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=T(e);return i(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 i(e,{error:"Method not allowed"},405);try{return i(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 i(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,s=o.get("redirect_uri"),t=A();u.sendMcpAuthorizationStartedMessage([{..._(t),redirect_uri:s||null}]);const n=T(e),c={isMcpFlow:!0,originalRedirectUri:s,mcpClientId:o.get("client_id"),mcpState:o.get("state"),mcpSessionId:t,timestamp:Date.now()};try{const a=await I(c),d=new URL(l(p.IDP_LOGIN),n);return d.searchParams.set("redirectTo",`${p.MCP_CALLBACK}/${a}`),d.searchParams.set("idpId","oidc"),e.redirect(d.toString())}catch(a){const d=a instanceof Error?a.message:String(a),h=a instanceof Error?a.stack:String(a);u.sendMcpAuthorizationFailedMessage([{..._(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 i(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),o=r.get("grant_type"),s=r.get("code"),t=r.get("redirect_uri")||void 0;if(o!=="authorization_code"||!s)return i(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const n=await w(s);if(t&&t!==n.redirect_uri)return i(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(C.OAUTH_CLIENT_ID&&n.client_id&&n.client_id!==C.OAUTH_CLIENT_ID)return i(e,{error:"invalid_client",error_description:"Client mismatch"},400);const c=n.id_token;if(typeof c!="string"||c.length===0)return i(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let a=c;if(n.idp_access_token){const{payload:h,header:{kid:g}}=m.decode(c);a=await m.sign({...h,idp_access_token:n.idp_access_token},y,M.HS256,g)}return i(e,{access_token:a,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:c},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return i(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const o=r instanceof Error?r.message:String(r);return i(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 u.sendMcpAuthorizationFailedMessage([{..._(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let s=null;try{const t=await P(o);if(s=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const n=f(e,"idp_id_token")||f(e,"authorization"),c=f(e,"idp_access_token");if(!n)return u.sendMcpAuthorizationFailedMessage([{..._(s),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const a=await S({idToken:n,idpAccessToken:c||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,ttlSec:600}),d=new URL(t.originalRedirectUri);return d.searchParams.set("code",a),t.mcpState&&d.searchParams.set("state",t.mcpState),u.sendMcpAuthorizationCompletedMessage([{..._(s),redirect_uri:t.originalRedirectUri||null}]),e.redirect(d.toString())}catch(t){const n=t instanceof Error?t.message:String(t),c=t instanceof Error?t.stack:String(t);return u.sendMcpAuthorizationFailedMessage([{..._(s),error:n,error_details:c}]),e.text(`Invalid MCP callback: ${n}`,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};
|
|
@@ -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
|
|
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/realm",
|
|
3
|
-
"version": "0.133.0-next.
|
|
3
|
+
"version": "0.133.0-next.4",
|
|
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.
|
|
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",
|
|
@@ -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.
|
|
93
|
+
"@redocly/asyncapi-docs": "1.10.0-next.4",
|
|
94
94
|
"@redocly/config": "0.48.1",
|
|
95
|
-
"@redocly/graphql-docs": "1.10.0-next.
|
|
96
|
-
"@redocly/openapi-docs": "3.21.0-next.
|
|
95
|
+
"@redocly/graphql-docs": "1.10.0-next.4",
|
|
96
|
+
"@redocly/openapi-docs": "3.21.0-next.4",
|
|
97
97
|
"@redocly/portal-legacy-ui": "0.16.0-next.0",
|
|
98
|
-
"@redocly/portal-plugin-mock-server": "0.18.0-next.
|
|
98
|
+
"@redocly/portal-plugin-mock-server": "0.18.0-next.4",
|
|
99
99
|
"@redocly/realm-asyncapi-sdk": "0.11.0-next.2",
|
|
100
|
-
"@redocly/theme": "0.65.0-next.
|
|
100
|
+
"@redocly/theme": "0.65.0-next.4"
|
|
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};
|
|
File without changes
|