@redocly/realm 0.133.0-next.2 → 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 +47 -0
- package/dist/markdoc/nodes/table.js +1 -1
- package/dist/server/plugins/catalog-entities/database/catalog-entities-publisher.js +3 -3
- package/dist/server/plugins/catalog-entities/database/mappers/create-bff-related-entity.js +1 -1
- package/dist/server/plugins/catalog-entities/database/repositories/entities/entities-write-repository.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/catalog-entities/plugin.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/catalog/helpers/upsert-pages-stats.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,46 @@
|
|
|
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
|
+
|
|
29
|
+
## 0.133.0-next.3
|
|
30
|
+
|
|
31
|
+
### Minor Changes
|
|
32
|
+
|
|
33
|
+
- e2b2f2e27f3: Updated Markdown links to display a clearer underline with a thicker underline on hover for improved visibility and accessibility.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [e2b2f2e27f3]
|
|
38
|
+
- @redocly/theme@0.65.0-next.3
|
|
39
|
+
- @redocly/asyncapi-docs@1.10.0-next.3
|
|
40
|
+
- @redocly/graphql-docs@1.10.0-next.3
|
|
41
|
+
- @redocly/openapi-docs@3.21.0-next.3
|
|
42
|
+
- @redocly/portal-plugin-mock-server@0.18.0-next.3
|
|
43
|
+
|
|
3
44
|
## 0.133.0-next.2
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
|
@@ -86,6 +127,12 @@
|
|
|
86
127
|
- @redocly/asyncapi-docs@1.10.0-next.0
|
|
87
128
|
- @redocly/graphql-docs@1.10.0-next.0
|
|
88
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
|
+
|
|
89
136
|
## 0.132.1
|
|
90
137
|
|
|
91
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,12 +1,12 @@
|
|
|
1
|
-
import{and as h,eq as g,inArray as R,isNotNull as A,isNull as E,ne as k,or as I,sql as S}from"drizzle-orm";import{ulid as
|
|
1
|
+
import{and as h,eq as g,inArray as R,isNotNull as A,isNull as E,ne as k,or as I,sql as S}from"drizzle-orm";import{ulid as $}from"ulid";import{logger as F}from"../../../tools/notifiers/logger.js";import{DatabaseConnectionFactory as v}from"../../../providers/database/database-connection-factory.js";import{entitiesAttributesTable as m}from"../../../providers/database/databases/sqlite-db/schemas/entities-attributes-table.js";import{entitiesRelationsTable as u}from"../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{entitiesTable as d}from"../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{BATCH_TRANSIENT_RETRY as L,withTransientErrorRetry as O}from"../../../providers/database/transient-sqld-error.js";import{envConfig as D}from"../../../config/env-config.js";import{ATTRIBUTES_UPSERT_SET as U,DEFAULT_CHUNK_SIZE as B,ENTITIES_UPSERT_SET as K,MAX_BATCH_PAYLOAD_BYTES as M,MAX_LOGGED_DB_ERROR_LENGTH as b,MIN_RETRYABLE_BATCH_SIZE as H,RELATIONS_UPSERT_SET as x}from"./consts.js";class c{static async publishFromLocalToRemote(e){if(D.isDevelopMode||!D.isProductionEnv)return!1;const{baseDbDir:t,chunkSize:s=B,changedSourceFiles:o=[],removedSourceFiles:n=[]}=e,a=$(),[i,r]=await Promise.all([v.create({baseDbDir:t,databaseType:"local"}),v.create({baseDbDir:t,databaseType:"remote"})]);if(!i||!r)return F.warn("Skipping entities remote sync: local or remote connection is unavailable"),!1;const l="changedSourceFiles"in e||"removedSourceFiles"in e,p=Array.from(new Set([...o,...n]));if(l&&p.length===0)return!0;const y=l?o.length?o:"none":void 0;try{return await c.#i({runId:a,chunkSize:s,localConnection:i,remoteConnection:r,sourceFilesFilter:y}),await c.#c({runId:a,chunkSize:s,localConnection:i,remoteConnection:r,sourceFilesFilter:y}),await c.#a({chunkSize:s,localConnection:i,remoteConnection:r,sourceFilesFilter:y}),await c.#l({runId:a,remoteConnection:r,sourceFilesFilter:l?p:void 0}),!0}catch(f){const w=c.#o(f);throw F.error(`[CatalogEntitiesPublisher.publishFromLocalToRemote] Failed runId=${a}. ${w}`),new Error(`[CatalogEntitiesPublisher.publishFromLocalToRemote] Failed runId=${a}. ${w}`,{cause:f instanceof Error?f:void 0})}}static#i(e){const{runId:t,localConnection:s,remoteConnection:o,sourceFilesFilter:n,chunkSize:a}=e,i=c.#e(n);return c.#t({chunkSize:a,sourceFilesFilter:n,selectRows:(r,l)=>s.client.client.select().from(d).where(h(g(d.source,"file"),i?R(d.sourceFile,i):void 0)).limit(l).offset(r).all(),decorateRow:r=>({...r,lastSeenRunId:t}),runUpsert:async r=>{await o.client.client.insert(d).values(r).onConflictDoUpdate({target:[d.key,d.source,d.revision,d.version],set:K}).run()},describeSkippedRow:r=>`entity key=${String(r.key)}`})}static#c(e){const{runId:t,localConnection:s,remoteConnection:o,sourceFilesFilter:n,chunkSize:a}=e,i=c.#e(n);return c.#t({chunkSize:a,sourceFilesFilter:n,selectRows:(r,l)=>s.client.client.select().from(u).where(h(A(u.sourceFile),i?R(u.sourceFile,i):void 0)).limit(l).offset(r).all(),decorateRow:r=>({...r,lastSeenRunId:t}),runUpsert:async r=>{await o.client.client.insert(u).values(r).onConflictDoUpdate({target:[u.sourceKey,u.targetKey,u.sourceVersion,u.targetVersion,u.sourceRevision,u.targetRevision,u.sourceToTargetRelation],set:x}).run()},describeSkippedRow:r=>{const l=r;return`relation sourceKey=${String(l.sourceKey)} targetKey=${String(l.targetKey)}`}})}static#a(e){const{localConnection:t,remoteConnection:s,sourceFilesFilter:o,chunkSize:n}=e,a=c.#e(o);return c.#t({chunkSize:n,sourceFilesFilter:o,selectRows:(i,r)=>t.client.client.select().from(m).where(a?S`EXISTS (
|
|
2
2
|
SELECT 1
|
|
3
3
|
FROM entities e
|
|
4
4
|
WHERE e.key = ${m.entityKey}
|
|
5
5
|
AND e.source = 'file'
|
|
6
6
|
AND e.source_file IN (${S.join(a.map(l=>S`${l}`),S`, `)})
|
|
7
|
-
)`:void 0).limit(r).offset(i).all(),runUpsert:async i=>{await s.client.client.insert(m).values(i).onConflictDoUpdate({target:[m.entityKey],set:U}).run()}})}static#e(e){return Array.isArray(e)&&e.length>0?e:void 0}static async#t({chunkSize:e,sourceFilesFilter:t,selectRows:s,decorateRow:o,runUpsert:n,describeSkippedRow:a}){if(t==="none")return{upserted:0,skipped:0};let i=0,r=0,l=0;for(;;){const p=await s(i,e);if(!p.length)return{upserted:r,skipped:l};const y=o?p.map(f=>o(f)):p;for(const f of c.#u(y,e,M)){const w=await c.#n({rows:f,runOperation:n,shouldSkipIsolatedRow:a?(
|
|
7
|
+
)`:void 0).limit(r).offset(i).all(),runUpsert:async i=>{await s.client.client.insert(m).values(i).onConflictDoUpdate({target:[m.entityKey],set:U}).run()}})}static#e(e){return Array.isArray(e)&&e.length>0?e:void 0}static async#t({chunkSize:e,sourceFilesFilter:t,selectRows:s,decorateRow:o,runUpsert:n,describeSkippedRow:a}){if(t==="none")return{upserted:0,skipped:0};let i=0,r=0,l=0;for(;;){const p=await s(i,e);if(!p.length)return{upserted:r,skipped:l};const y=o?p.map(f=>o(f)):p;for(const f of c.#u(y,e,M)){const w=await c.#n({rows:f,runOperation:n,shouldSkipIsolatedRow:a?(N,T)=>c.#f(T)?(F.warn(`[CatalogEntitiesPublisher.publishFromLocalToRemote] Skipping ${a(N)} due to remote insert error: ${c.#o(T)}`),!0):!1:void 0});r+=w.upserted,l+=w.skipped}i+=e}}static async#l({runId:e,remoteConnection:t,sourceFilesFilter:s}){const o=t.client.client,n=s?.length?s:void 0,a=await c.#r(()=>o.delete(u).where(h(A(u.sourceFile),n?R(u.sourceFile,n):void 0,I(E(u.lastSeenRunId),k(u.lastSeenRunId,e)))).run()),i=await c.#r(()=>o.delete(d).where(h(g(d.source,"file"),n?R(d.sourceFile,n):void 0,I(E(d.lastSeenRunId),k(d.lastSeenRunId,e)))).run()),r=await c.#r(()=>o.delete(m).where(S`NOT EXISTS (
|
|
8
8
|
SELECT 1
|
|
9
9
|
FROM entities e
|
|
10
10
|
WHERE e.key = ${m.entityKey}
|
|
11
11
|
AND COALESCE(e.is_deleted, 0) = 0
|
|
12
|
-
)`).run());return{staleEntitiesDeleted:i,staleRelationsDeleted:a,orphanAttributesDeleted:r}}static async#r(e){const t=await c.#s(e);return Number(t.rowsAffected??0)}static#u(e,t,s){const o=[];let n=[],a=0;for(const i of e){const r=c.#d(i);n.length>0&&(n.length>=t||a+r>s)&&(o.push(n),n=[],a=0),n.push(i),a+=r}return n.length>0&&o.push(n),o}static#d(e){try{return JSON.stringify(e).length}catch{return 0}}static async#n({rows:e,runOperation:t,shouldSkipIsolatedRow:s}){try{return await c.#s(()=>t(e)),{upserted:e.length,skipped:0}}catch(o){if(e.length<H){if(!s)throw o;if(await s(e[0],o))return{upserted:0,skipped:1};throw o}const n=Math.floor(e.length/2),a=await c.#n({rows:e.slice(0,n),runOperation:t,shouldSkipIsolatedRow:s}),i=await c.#n({rows:e.slice(n),runOperation:t,shouldSkipIsolatedRow:s});return{upserted:a.upserted+i.upserted,skipped:a.skipped+i.skipped}}}static#f(e){if(!(e instanceof Error))return!1;const t=e.message.toLowerCase();return t.includes("code: 11")||t.includes("too many sql variables")||t.includes("string or blob too big")||t.includes("statement too long")||t.includes("request body too large")||t.includes("payload too large")||t.includes("body too large")||t.includes("resource exhausted")}static#o(e){const s=(e instanceof Error?e.message:typeof e=="string"?e:JSON.stringify(e)).split(" params:")[0],n=c.#p(s).replace(/\s+/g," ").trim();return n.length>b?`${n.slice(0,b)}...`:n}static#p(e){const t="Failed query:",s=e.indexOf(t);if(s===-1)return e;const o=e.slice(0,s).trim();return o?`${o}. ${t} <omitted for brevity>`:`${t} <omitted for brevity>`}static#s(e){return
|
|
12
|
+
)`).run());return{staleEntitiesDeleted:i,staleRelationsDeleted:a,orphanAttributesDeleted:r}}static async#r(e){const t=await c.#s(e);return Number(t.rowsAffected??0)}static#u(e,t,s){const o=[];let n=[],a=0;for(const i of e){const r=c.#d(i);n.length>0&&(n.length>=t||a+r>s)&&(o.push(n),n=[],a=0),n.push(i),a+=r}return n.length>0&&o.push(n),o}static#d(e){try{return JSON.stringify(e).length}catch{return 0}}static async#n({rows:e,runOperation:t,shouldSkipIsolatedRow:s}){try{return await c.#s(()=>t(e)),{upserted:e.length,skipped:0}}catch(o){if(e.length<H){if(!s)throw o;if(await s(e[0],o))return{upserted:0,skipped:1};throw o}const n=Math.floor(e.length/2),a=await c.#n({rows:e.slice(0,n),runOperation:t,shouldSkipIsolatedRow:s}),i=await c.#n({rows:e.slice(n),runOperation:t,shouldSkipIsolatedRow:s});return{upserted:a.upserted+i.upserted,skipped:a.skipped+i.skipped}}}static#f(e){if(!(e instanceof Error))return!1;const t=e.message.toLowerCase();return t.includes("code: 11")||t.includes("too many sql variables")||t.includes("string or blob too big")||t.includes("statement too long")||t.includes("request body too large")||t.includes("payload too large")||t.includes("body too large")||t.includes("resource exhausted")}static#o(e){const s=(e instanceof Error?e.message:typeof e=="string"?e:JSON.stringify(e)).split(" params:")[0],n=c.#p(s).replace(/\s+/g," ").trim();return n.length>b?`${n.slice(0,b)}...`:n}static#p(e){const t="Failed query:",s=e.indexOf(t);if(s===-1)return e;const o=e.slice(0,s).trim();return o?`${o}. ${t} <omitted for brevity>`:`${t} <omitted for brevity>`}static#s(e){return O(e,L)}}export{c as CatalogEntitiesPublisher};
|
|
@@ -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{and as v,eq as c,isNull as R,or as z,sql as V}from"drizzle-orm";import{VERSION_NOT_SPECIFIED as F}from"@redocly/theme/core/constants";import{telemetryTraceStep as C}from"../../../../../telemetry/helpers/trace-step.js";import{sha1 as _}from"../../../../../utils/crypto/sha1.js";import{logger as
|
|
1
|
+
import{and as v,eq as c,isNull as R,or as z,sql as V}from"drizzle-orm";import{VERSION_NOT_SPECIFIED as F}from"@redocly/theme/core/constants";import{telemetryTraceStep as C}from"../../../../../telemetry/helpers/trace-step.js";import{sha1 as _}from"../../../../../utils/crypto/sha1.js";import{logger as y}from"../../../../../tools/notifiers/logger.js";import{envConfig as K}from"../../../../../config/env-config.js";import{entitiesTable as i}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-table.js";import{convertFilterToWhereCondition as O}from"../../../../../providers/database/pagination/filter.js";import{entitiesRelationsTable as a}from"../../../../../providers/database/databases/sqlite-db/schemas/entities-relations-table.js";import{promiseMapLimit as m}from"../../../../../utils/async/promise-map-limit.js";import{RevisionRepository as H}from"../common/revision-repository.js";import{VersionRepository as P}from"../common/version-repository.js";import{EntityAttributesWriteRepository as $}from"../entityAttributes/entity-attributes-write-repository.js";import{createEntityDbRecord as W}from"../../mappers/create-entity-db-record.js";import{createEntityReadModel as D}from"../../mappers/create-entity-read-model.js";import{createEntityRelationDbRecordFromFileSchema as b}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{createEntityRelationDbRecordFromDto as j}from"../../mappers/create-entity-relation-db-record-from-dto.js";import{EntitiesReadRepository as G}from"./entities-read-repository.js";import{RelationsReadRepository as M}from"../relations/relations-read-repository.js";import{RelationsWriteRepository as q}from"../relations/relations-write-repository.js";const E=15;class fe{#e;#t;#n;#o;#r;#i;#a;#u;#s;constructor(e,t,s){this.#e=e,this.#r=t,this.#i=s,this.#t=new H(e),this.#n=new P(e),this.#o=new $(e),this.#a=new G(e),this.#u=new M(e),this.#s=new q(e,t,s)}async createEntity({entity:e,source:t,fileHash:s,sourceFile:n,isRootEntity:r,isDeleted:o,rbacTeams:u,errorOnSkip:h=!1,revision:f}){return C("catalog_entities.remote_repository.create_entity",async()=>{const{relations:l=[],...p}=e,d=_(JSON.stringify(p)),g=e.version??F,A=f??new Date().toISOString(),L=await this.#t.shouldSkipRevisionCreation(e.key,g,d,r,o);if(Array.isArray(u)&&await this.#o.upsertEntityAttributes({entityKey:e.key,rbacTeams:u,organizationId:this.#r,projectId:this.#i}),L){if(h)throw new Error("Entity validation failed: entity already exists");return null}const{shouldSetNewCurrentRevision:w,hasCurrentRevision:T}=await this.#t.getCurrentRevisionInfo({key:e.key,version:g,revision:A}),k=W({entity:{...e,revision:A,hash:d,isCurrent:w,isDefaultVersion:w,isDeleted:o,version:g},organizationId:this.#r,projectId:this.#i,source:t,sourceFile:n??null,fileHash:s??null}),{key:S,...x}=k;if(w&&T&&(await this.#t.markAllRevisionsAsNotCurrent(S),await this.#n.markAllVersionsAsNotDefault(S)),K.isDevelopMode&&!K.REDOCLY_INTERNAL_DEV)return await this.#l(k,l);const N=await this.#e.client.insert(i).values(k).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:x}).returning();return N.length?(l&&await this.#s.createEntityRelations(l.map(I=>({...I,sourceKey:e.key,targetKey:I.key}))),D(N[0])):null})}async updateEntity(e,t){return C("catalog_entities.remote_repository.update_entity",async()=>{try{y.info(`Updating entity ${t.key} in remote database`);const{shouldSetNewCurrentRevision:s,hasCurrentRevision:n}=await this.#t.getCurrentRevisionInfo({key:t.key,version:e.version??t.version??F,revision:t.revision});s&&n&&(await this.#t.markAllRevisionsAsNotCurrent(t.key),await this.#n.markAllVersionsAsNotDefault(t.key));const r=W({entity:{...t,...e,hash:_(JSON.stringify({...t,...e})),isCurrent:s,isDefaultVersion:s,createdAt:t.createdAt??void 0},organizationId:this.#r,projectId:this.#i,source:"remote",sourceFile:null,fileHash:null}),{key:o,source:u,scorecardsStatus:h,...f}=r,l=await this.#e.client.insert(i).values(r).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:{...f,scorecardsStatus:V`CASE WHEN ${i.scorecardsStatus} = 'CALCULATING' THEN 'CANCELLED' ELSE 'OUTDATED' END`}}).returning();return l.length?D(l[0]):null}catch(s){return y.error("Error updating entity",s),null}})}async setEntitiesAsOutdated(e){try{const t=O(e);await this.#e.client.update(i).set({scorecardsStatus:"OUTDATED"}).where(t)}catch(t){y.error("Error updating entities as outdated",t)}}async#l(e,t){const{key:s,source:n,version:r,isDefaultVersion:o,...u}=e,l=await this.#e.client.select({id:i.id}).from(i).where(v(c(i.key,s),r?c(i.version,r):R(i.version))).limit(1).get()!=null?await this.#e.client.update(i).set(u).where(v(c(i.key,s),r?c(i.version,r):R(i.version))).returning():await this.#e.client.insert(i).values(e).onConflictDoUpdate({target:[i.key,i.source,i.revision,i.version],set:u}).returning(),p=t?.map(d=>{const g=b({relation:d,sourceFile:e.sourceFile??"",fileHash:e.fileHash??"",sourceKey:e.key,sourceVersion:e.version??null,sourceRevision:e.revision??null,organizationId:this.#r,projectId:this.#i});return this.#e.client.insert(a).values(g).onConflictDoUpdate({target:[a.sourceKey,a.targetKey,a.sourceVersion,a.targetVersion,a.sourceRevision,a.targetRevision,a.sourceToTargetRelation],set:g}).run()})??[];return await m(p,E,async d=>d),D(l[0])}async softDeleteEntitiesWithRelations({filter:e,revision:t,fileHash:s}){const r={op:"AND",conditions:[e,{field:"is_deleted",operator:"equal",value:!1}]};for(;;){const o=await this.#a.getEntities({paginationParams:{filter:r,limit:500}});if(o.items.length===0)break;const u=await this.#c({entities:o.items,revision:t,fileHash:s});await this.#d(u,t)}}async deleteEntity(e){return C("catalog_entities.remote_repository.delete_entity",async()=>{try{return await this.#s.deleteEntityRelationsOnEntityRemoval(e),await this.#e.client.delete(i).where(c(i.id,e.id)),await this.#t.ensureDefaultAndCurrentRevisionForKey(e.key),e.id}catch(t){return y.error("Error deleting entity",t),null}})}async deleteEntities(e){try{const t=O(e);if(!t)return!1;const s=await this.#e.client.delete(i).where(t).returning({key:i.key,source:i.source,isCurrent:i.isCurrent,isDefaultVersion:i.isDefaultVersion,version:i.version});if(s.length===0)return!0;const n=s.reduce((r,o)=>((o.isCurrent||o.isDefaultVersion)&&r.add(o.key),r),new Set);if(n.size===0)return!0;await m(Array.from(n),E,async r=>this.#t.ensureDefaultAndCurrentRevisionForKey(r));for(const r of s)await this.#e.client.delete(a).where(z(v(c(a.sourceKey,r.key),...r.version?[c(a.sourceVersion,r.version)]:[R(a.sourceVersion)]),v(c(a.targetKey,r.key),...r.version?[c(a.targetVersion,r.version)]:[R(a.targetVersion)])));return!0}catch(t){return y.error("Error deleting entities",t),!1}}async updateEntityScorecardsStatus(e,t){try{return(await this.#e.client.update(i).set({scorecardsStatus:t}).where(c(i.id,e)).returning()).length>0}catch(s){return y.error("Error updating entity scorecards status",s),!1}}async updateEntityScorecardsStatusIfCalculating(e,t){try{return(await this.#e.client.update(i).set({scorecardsStatus:t}).where(V`${i.id} = ${e} AND ${i.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}catch(s){return y.error("Error updating entity scorecards status if calculating",s),!1}}async#c({entities:e,revision:t,fileHash:s}){try{return(await m(e,E,async r=>{const o={type:r.type,key:r.key,title:r.title,summary:r.summary??void 0,tags:r.tags??void 0,metadata:r.metadata??void 0,git:r.git??void 0,contact:r.contact??void 0,links:r.links??void 0,version:r.version??void 0};return this.createEntity({entity:o,sourceFile:r.sourceFile??"",fileHash:s,isDeleted:!0,errorOnSkip:!1,source:r.source,revision:t})})).filter(r=>r!==null)}catch(n){return y.error("Error soft deleting entities",n),[]}}async#d(e,t){try{if(e.length===0)return!0;const s=await m(e,E,async n=>(await this.#u.getRelationsForEntity(n.key,n.version,t)).map(o=>{if(!o)return null;const u=o.direction,h=o.sourceToTargetRelation,f=o.targetKey,l=u==="outgoing"?n.key:f,p=u==="outgoing"?f:n.key,d=u==="outgoing"?h:h.startsWith("reverse:")?h.slice(8):h;return!d||!l||!p?null:j({type:d,sourceKey:l,targetKey:p,sourceVersion:n.version,targetVersion:n.version,sourceRevision:t,targetRevision:t,isDeleted:!0},this.#r,this.#i)}).filter(o=>o!==null));return await m(s.flat(),E,async n=>this.#s.upsertEntityRelation(n)),!0}catch(s){return y.error("Error soft deleting entity relations",s),!1}}}export{fe as EntitiesWriteRepository};
|
|
@@ -1 +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 +1 @@
|
|
|
1
|
-
import{FileHashStatus as A,FileType as
|
|
1
|
+
import{FileHashStatus as A,FileType as f}from"../../persistence/file-hashes/types.js";import{envConfig as F}from"../../config/env-config.js";import{CATALOG_BASE_SLUG as h}from"../../../constants/catalog-entities.js";import{telemetryTraceStep as N}from"../../../cli/telemetry/helpers/trace-step.js";import{catalogDataCollector as m}from"./utils/catalog-data-collector.js";import{CATALOG_FILTERS_CACHE_NAMESPACE as H,ENTITIES_MAP_GLOBAL_DATA_KEY as L}from"../../constants/plugins/catalog-entities.js";import{CacheService as G}from"../../persistence/cache/services/cache-service.js";import{getTemplatePath as b}from"./utils/get-template-path.js";import{getCompleteCatalogConfig as y}from"./get-complete-catalog-config.js";import{AsyncApiEntitiesExtractor as x}from"./extensions/extractors/api-description/asyncapi-entities-extractor.js";import{GraphqlEntitiesExtractor as B}from"./extensions/extractors/api-description/graphql-entities-extractor.js";import{FileHashesService as M}from"../../persistence/file-hashes/services/file-hashes-service.js";import{CatalogEntitiesService as k}from"./database/catalog-entities-service.js";import{CatalogEntitiesPublisher as Y}from"./database/catalog-entities-publisher.js";import{ArazzoEntitiesExtractor as j}from"./extensions/extractors/api-description/arazzo-entities-extractor.js";import{FsEntitiesExtractor as q}from"./extensions/extractors/fs-entities-extractor.js";import{HashManager as z}from"./utils/hash-manager.js";import{RbacConfigHashCache as U}from"./utils/rbac-config-hash-cache.js";import{OpenApiEntitiesExtractor as W}from"./extensions/extractors/api-description/openapi-entities-extractor.js";const J="catalog-entity-template",Z="catalog-entity";let D=!0;async function $(){return{id:"CatalogEntities",requiredEntitlements:["catalog"],async processContent(t,e){const i=await e.getConfig(),r=y(i.entitiesCatalog);if(!r.show)return;const{logger:o}=e,s=t.registerServerPropsGetter(Z,b("../get-server-props.js")),g=t.createTemplate(J,b("../template/index.js"));t.addRoute({duplicateInAllLocales:!0,slug:h,fsPath:"",templateId:g,excludeFromSidebar:!0,hasClientRoutes:!0,serverPropsGetterIds:[s],getNavText:()=>Promise.resolve("Catalog"),getStaticData:async()=>({props:{catalogConfig:r}})});const[n]=Object.entries(r.catalogs??{}).find(([T,p])=>!p?.hide)||[];n&&t.addRedirect(h,{type:302,to:`${h}/${n}`},{trackOriginalSource:!1}),o.info("Catalog Entities plugin finished")},async afterRoutesCreated(t,e){await N("build.plugin.catalog_entities",async i=>{const r=await e.getConfig(),o=y(r.entitiesCatalog);if(i?.setAttribute("config",JSON.stringify(o)),!o.show)return;const{logger:s}=e;m.resetForRun();const g=D&&F.isDevelopMode,n=await G.getInstance({baseDbDir:t.serverOutDir,databaseType:"local"}),T=new U(n),{rbacConfigChanged:p}=await T.syncAndDetectChange(r.access?.rbac??null),l=g||p,a=await k.getInstance({baseDbDir:t.serverOutDir,removeExisting:g,databaseType:"local",runWithPragmaWalWriteOptimization:!0}),E=await M.getInstance({baseDbDir:t.serverOutDir,databaseType:"local"}),c=new z(E),S=[new q({fileHashManager:c,context:e,catalogEntitiesService:a,catalogConfig:o,shouldCalculateEntities:l}),new W({actions:t,context:e,catalogEntitiesService:a,fileHashManager:c,fileType:f.OPENAPI_DESCRIPTION,shouldCalculateEntities:l}),new x({actions:t,context:e,catalogEntitiesService:a,fileHashManager:c,fileType:f.ASYNCAPI_DESCRIPTION,shouldCalculateEntities:l}),new B({actions:t,context:e,catalogEntitiesService:a,fileHashManager:c,fileType:f.GRAPHQL_DESCRIPTION,shouldCalculateEntities:l}),new j({actions:t,context:e,catalogEntitiesService:a,fileHashManager:c,fileType:f.ARAZZO_DESCRIPTION,shouldCalculateEntities:l})];s.info("Starting entities extractors...");const P=s.startTiming();await a.transaction(async()=>{await Promise.all(S.map(async d=>d.extract()))});const O=a.getEntitySources();t.setGlobalData({[L]:O}),await n.deleteByNamespace(H),s.infoTime(P,"Entities extractors finished");const{changedSourceFiles:I,removedSourceFiles:C}=m.getPublishDelta(),_=s.startTiming();await Y.publishFromLocalToRemote(p?{baseDbDir:t.serverOutDir}:{baseDbDir:t.serverOutDir,changedSourceFiles:I,removedSourceFiles:C})&&s.infoTime(_,"Entities published to the database"),C.length&&await E.deleteFileHashes({op:"AND",conditions:[{field:"status",operator:"equal",value:A.OUTDATED},{field:"file_path",operator:"in",value:C}]});const v=m.getFileHashConfirmations();for(const{fileType:d,filePath:w,hash:R}of v)await E.upsertFileHash({fileType:d,filePath:w,hash:R,status:A.UP_TO_DATE});const u=await m.getCatalogEntitiesData(a);i?.setAttribute("totalEntities",u.totalEntitiesCount),i?.setAttribute("entitiesCountByType",JSON.stringify(u.countOfEntitiesByType)),i?.setAttribute("totalFilesSkippedByHash",u.totalFilesSkippedByHash),i?.setAttribute("totalProcessedFiles",u.totalProcessedFiles),i?.setAttribute("extractors",u.extractors),D=!1})}}}var Ct=$;export{$ as catalogEntitiesPlugin,Ct as default};
|
|
@@ -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 +1 @@
|
|
|
1
|
-
import{envConfig as t}from"../../../../config/env-config.js";async function u({ctx:r,catalogEntitiesService:s,addedEntities:n,removedEntities:
|
|
1
|
+
import{envConfig as t}from"../../../../config/env-config.js";async function u({ctx:r,catalogEntitiesService:s,addedEntities:n,removedEntities:i}){if(!t.isProductionEnv){r.get("logger").info("Skipping pages stats upsert in non-production environment");return}if(!t.BH_API_URL||!t.ORGANIZATION_ID||!t.PROJECT_ID)throw new Error("Pages stats service not configured");const o=r.req.header("apiKey");if(!o)throw new Error("API key is required to upsert pages stats");const{total:a}=await s.getEntitiesCount("remote",n,i?.length),p={remoteEntityPages:a,isReported:!1},g=new URL(`/api/orgs/${t.ORGANIZATION_ID}/projects/${t.PROJECT_ID}/project-pages-stats`,t.BH_API_URL).toString();try{const e=await fetch(g,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify(p)});if(!e.ok)throw new Error(e.statusText)}catch(e){throw new Error(`Failed to upsert project pages stats: ${e?.message}`)}}export{u as upsertPagesStats};
|
|
@@ -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
|