@redocly/redoc 0.129.0-next.5 → 0.129.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/constants/common.d.ts +1 -0
  3. package/dist/constants/common.js +1 -1
  4. package/dist/server/plugins/catalog-entities/database/constants/relation-normalization.d.ts +3 -0
  5. package/dist/server/plugins/catalog-entities/database/constants/relation-normalization.js +1 -0
  6. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-dto.js +1 -1
  7. package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-file-schema.js +1 -1
  8. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-bff-repository.d.ts +14 -0
  9. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-bff-repository.js +112 -0
  10. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-read-repository.js +12 -130
  11. package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-write-repository.js +1 -1
  12. package/dist/server/plugins/catalog-entities/utils/normalize-relation.d.ts +77 -0
  13. package/dist/server/plugins/catalog-entities/utils/normalize-relation.js +1 -0
  14. package/dist/server/plugins/config-parser/format-error.js +13 -5
  15. package/dist/server/plugins/config-parser/loaders/utils/read-and-validate-config.js +1 -1
  16. package/dist/server/providers/database/pagination/filter.d.ts +1 -0
  17. package/dist/server/providers/database/pagination/filter.js +1 -1
  18. package/dist/server/web-server/auth.js +2 -2
  19. package/dist/server/web-server/routes/auth.d.ts +1 -0
  20. package/dist/server/web-server/routes/auth.js +1 -1
  21. package/dist/server/web-server/routes/index.js +1 -1
  22. package/dist/server/web-server/routes/mcp-oauth.d.ts +10 -0
  23. package/dist/server/web-server/routes/mcp-oauth.js +1 -1
  24. package/dist/server/web-server/utils/get-request-origin.d.ts +3 -0
  25. package/dist/server/web-server/utils/get-request-origin.js +1 -0
  26. package/package.json +9 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @redocly/redoc
2
2
 
3
+ ## 0.129.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 13cdd871a0: Added `access` configuration object to group authentication settings (`requiresLogin`, `logoutReturnUrl`, `residency`, `sso`, `rbac`) and `logoutReturnUrl` option to customize post-logout redirect URL.
8
+
9
+ Fixed config validation error formatting to handle cases where location source may be undefined or not a proper Source object, preventing errors when formatting validation problems.
10
+
11
+ - ebaff45e00: Added tooltips to items in the API docs overview section.
12
+ - 39c941417d: Added support for OpenAPI 3.2 `dataValue` and `serializedValue`.
13
+ - eb3abbb06e: Added support for OpenAPI 3.2 Sequential and Streaming Data.
14
+
15
+ ### Patch Changes
16
+
17
+ - 77452207bf: Fixed an issue where a trailing slash was automatically added to URLs in the **Servers** section of API docs.
18
+ - edb34c5f33: Fixed an issue where breadcrumbs appeared on non-nested pages in projects with a path prefix.
19
+ - 1937581907: Fixed layout shift in `x-tags` with `schemaDefinition`.
20
+ - 93df42565c: Fixed an issue where image assets were incorrectly flagged as broken links in OpenAPI description.
21
+ - b63f469f50: Improved loading of description files for the MCP Docs server.
22
+ - ecc3ea32aa: Fixed an issue where the path prefix was added twice to the login page.
23
+ - 1245fefad6: Fixed an issue where Docs MCP required authentication for publicly available docs.
24
+ - 29a7308fe1: Updated `@redocly/openapi-core` to version `2.14.3`.
25
+ - Updated dependencies [77452207bf]
26
+ - Updated dependencies [ebaff45e00]
27
+ - Updated dependencies [edb34c5f33]
28
+ - Updated dependencies [1937581907]
29
+ - Updated dependencies [4c662c0042]
30
+ - Updated dependencies [39c941417d]
31
+ - Updated dependencies [eb3abbb06e]
32
+ - Updated dependencies [de9da5f277]
33
+ - Updated dependencies [8fdaab071f]
34
+ - Updated dependencies [9f686fedb3]
35
+ - Updated dependencies [b28161bbc8]
36
+ - Updated dependencies [49ddba9c14]
37
+ - Updated dependencies [29a7308fe1]
38
+ - @redocly/openapi-docs@3.17.0
39
+ - @redocly/theme@0.61.0
40
+ - @redocly/portal-plugin-mock-server@0.14.0
41
+ - @redocly/realm-asyncapi-sdk@0.7.0
42
+ - @redocly/asyncapi-docs@1.6.0
43
+ - @redocly/graphql-docs@1.6.0
44
+ - @redocly/portal-legacy-ui@0.12.0
45
+
46
+ ## 0.129.0-next.6
47
+
48
+ ### Minor Changes
49
+
50
+ - 13cdd871a0: Added `access` configuration object to group authentication settings (`requiresLogin`, `logoutReturnUrl`, `residency`, `sso`, `rbac`) and `logoutReturnUrl` option to customize post-logout redirect URL.
51
+
52
+ Fixed config validation error formatting to handle cases where location source may be undefined or not a proper Source object, preventing errors when formatting validation problems.
53
+
3
54
  ## 0.129.0-next.5
4
55
 
5
56
  ### Patch Changes
@@ -54,6 +54,7 @@ export declare const ServerRoutes: {
54
54
  readonly SEARCH: "/_search";
55
55
  readonly SEARCH_FACETS: "/_search-facets";
56
56
  readonly LOGOUT: "/logout";
57
+ readonly POST_LOGOUT: "/post-logout";
57
58
  readonly LOGIN: "/login";
58
59
  readonly IDP_LOGIN: "/_auth/idp-login";
59
60
  readonly INVITE: "/invite/:code";
@@ -1 +1 @@
1
- const E="openapi_docs",n="asyncapi_docs",A="graphql_docs",T="/_spec-gql",O="markdoc",p="tags",i="*",R={"*":"read"},L=["openid","email"],I=1440*60,C="anonymous",l="authenticated",S="**",d="Reunite",N="translations.yaml",P="redocly.yaml",u="@redocly/theme",D="@theme",h="/app-data.json",x="/eject-component",e=process.env.SERVER_EDITOR_APP_URL||"http://127.0.0.1:3000",g=process.env.REDOCLY_CORS_ORIGINS?process.env.REDOCLY_CORS_ORIGINS.split(",").map(o=>o.trim()).filter(Boolean).concat(e):[e];var a;(function(o){o.BUILD="build",o.DEVELOP="develop",o.PREPARE="prepare"})(a||(a={}));var r;(function(o){o.RATING="rating",o.SENTIMENT="sentiment",o.COMMENT="comment",o.MOOD="mood",o.PROBLEM="problem",o.SCALE="scale"})(r||(r={}));const t="entityKey",c="entityRelationId",U={INFO:"/_info",EJECT_COMPONENT:"/eject-component/:componentName",SHARED_PAGE_DATA:"/page-data/shared/*",PAGE_DATA:"/page-data/*",APP_DATA:"/app-data.json",AUTHORIZATION:"/authorize",OIDC_CALLBACK:"/_auth/oidc",SAML_CALLBACK:"/_auth/saml2",REDOCLY_CALLBACK:"/_auth/redocly",REPLAY_OAUTH2_CALLBACK:"/_auth/replay-oauth2",REDOCLY_LOGIN_CALLBACK:"/_auth/redocly/redocly-login",REDOCLY_TOKEN_LOGIN:"/_auth/redocly-token-login",SEARCH:"/_search",SEARCH_FACETS:"/_search-facets",LOGOUT:"/logout",LOGIN:"/login",IDP_LOGIN:"/_auth/idp-login",INVITE:"/invite/:code",RESOLVE_ROUTE_BY_PATH:"/resolve-route-by-path",RESOLVE_ROUTES_BY_PATHS:"/resolve-routes-by-paths",RESOLVE_ROUTE_BY_SLUG:"/resolve-route-by-slug",GET_ROUTES_BY_LINE:"/get-routes-by-line",FEEDBACK:"/feedback",TELEMETRY:"/_events",OTEL_TRACES:"/_otel/v1/traces",HEALTH:"/_health",ASK_AI:"/_ask-ai",CATALOG_ENTITIES:`/catalog-entities/:${t}?`,CATALOG_ENTITIES_RELATIONS:`/catalog-entities-relations/:${c}?`,BFF_CATALOG_ENTITIES:`/bff/catalog-entities/:${t}?`,BFF_CATALOG_RELATED_ENTITIES:`/bff/catalog-related-entities/:${t}`,BFF_CATALOG_REVISIONS:`/bff/catalog-revisions/:${t}`,MCP_OAUTH_AUTHORIZATION_SERVER:"/.well-known/oauth-authorization-server",MCP_OAUTH_PROTECTED_RESOURCE:"/.well-known/oauth-protected-resource/mcp",MCP_DYNAMIC_CLIENT_REGISTRATION:"/_mcp/register",MCP_AUTHORIZATION:"/_mcp/oauth2/auth",MCP_TOKEN_PORTAL:"/_mcp/oauth2/token-portal",MCP_CALLBACK:"/_mcp/oauth/callback",SCORECARDS:"/scorecards"};var _;(function(o){o.AI_SEARCH="aiSearch"})(_||(_={}));const m="/login",M="/invite",G={AUTH0_PASS:"/auth/auth0-pass"},b={NotExist:"ENOENT"},H="default_locale",v="sidebar-",F="PUBLIC_",y=["BROKEN_LINK","MARKDOC","RESOLVE"],B=["logo","navbar","products","footer","sidebar","scripts","links","feedback","search","aiAssistant","colorMode","navigation","codeSnippet","markdown","openapi","graphql","analytics","userMenu","versionPicker","breadcrumbs","catalog","entitiesCatalog","scorecard","scorecards","scorecardClassic","mcp","banner"],s="redocly_category",k="redocly_product",f="redocly_version",K="redocly_teams",V=s,Y={asyncapi:"asyncapi",apiFunctions:"apiFunctions",reactPages:"reactPages",catalog:"catalog",catalogClassic:"catalogClassic",lint:"lint",scorecard:"scorecard",scorecards:"scorecards",l10n:"l10n",openapi:"openapi",graphql:"graphql",markdown:"markdown",devOnboarding:"devOnboarding",seo:"seo",redirects:"redirects",customPlugins:"customPlugins",themeEjecting:"themeEjecting",products:"products",breadcrumbs:"breadcrumbs",mockServer:"mockServer",sso:"sso",rbac:"rbac",analytics:"analytics",removeAttribution:"removeAttribution",advancedSearch:"advancedSearch",soap:"soap",ssoDirect:"ssoDirect",codeWalkthrough:"codeWalkthrough",aiSearchLimit:"aiSearchLimit",mcp:"mcp",banner:"banner"};export{g as ALLOWED_CORS_ORIGINS,n as ASYNC_API_DOCS_TEMPLATE_ID,t as CATALOG_ENTITY_KEY,c as CATALOG_ENTITY_RELATION_ID,P as CONFIG_FILE_NAME,O as CUSTOM_MARKDOC_OPTIONS_PATH,p as CUSTOM_MARKDOC_TAGS_PATH,C as DEFAULT_ANONYMOUS_VISITOR_TEAM,l as DEFAULT_AUTHENTICATED_TEAM,I as DEFAULT_COOKIE_EXPIRATION,H as DEFAULT_LOCALE_PLACEHOLDER,S as DEFAULT_RBAC_SCOPE,d as DEFAULT_SSO_IDP_TITLE,u as DEFAULT_THEME_NAME,m as DEV_LOGIN_SLUG,x as EJECT_COMPONENT_URL,G as ExternalRoutes,Y as FEATURE,r as FEEDBACK_TYPES,b as FsErrors,h as GLOBAL_DATA_URL,T as GRAPHQL_SPEC_SLUG,A as GRAPHQL_TEMPLATE_ID,M as INVITE_SLUG,y as MARKDOC_ERROR_TYPES,E as OPENAPI_DOCS_TEMPLATE_ID,F as PUBLIC_ENV_PREFIX,R as PUBLIC_RBAC_SCOPE_ITEM,i as RBAC_ALL_OTHER_TEAMS,L as REQUIRED_OIDC_SCOPES,_ as RbacFeatures,s as SEARCH_CATEGORY_FIELD,V as SEARCH_GROUP_FACET_FIELD,k as SEARCH_PRODUCT_FIELD,K as SEARCH_RBAC_FIELD,f as SEARCH_VERSION_FIELD,e as SERVER_EDITOR_APP_URL,v as SIDEBAR_PREFIX,U as ServerRoutes,N as TRANSLATIONS_FILE_NAME,B as UI_ACCESSIBLE_CONFIG_PROPS,D as USER_THEME_ALIAS,a as cliCommandNames};
1
+ const E="openapi_docs",n="asyncapi_docs",A="graphql_docs",T="/_spec-gql",O="markdoc",p="tags",i="*",R={"*":"read"},L=["openid","email"],I=1440*60,C="anonymous",l="authenticated",S="**",d="Reunite",N="translations.yaml",P="redocly.yaml",u="@redocly/theme",D="@theme",h="/app-data.json",x="/eject-component",e=process.env.SERVER_EDITOR_APP_URL||"http://127.0.0.1:3000",g=process.env.REDOCLY_CORS_ORIGINS?process.env.REDOCLY_CORS_ORIGINS.split(",").map(o=>o.trim()).filter(Boolean).concat(e):[e];var a;(function(o){o.BUILD="build",o.DEVELOP="develop",o.PREPARE="prepare"})(a||(a={}));var r;(function(o){o.RATING="rating",o.SENTIMENT="sentiment",o.COMMENT="comment",o.MOOD="mood",o.PROBLEM="problem",o.SCALE="scale"})(r||(r={}));const t="entityKey",c="entityRelationId",U={INFO:"/_info",EJECT_COMPONENT:"/eject-component/:componentName",SHARED_PAGE_DATA:"/page-data/shared/*",PAGE_DATA:"/page-data/*",APP_DATA:"/app-data.json",AUTHORIZATION:"/authorize",OIDC_CALLBACK:"/_auth/oidc",SAML_CALLBACK:"/_auth/saml2",REDOCLY_CALLBACK:"/_auth/redocly",REPLAY_OAUTH2_CALLBACK:"/_auth/replay-oauth2",REDOCLY_LOGIN_CALLBACK:"/_auth/redocly/redocly-login",REDOCLY_TOKEN_LOGIN:"/_auth/redocly-token-login",SEARCH:"/_search",SEARCH_FACETS:"/_search-facets",LOGOUT:"/logout",POST_LOGOUT:"/post-logout",LOGIN:"/login",IDP_LOGIN:"/_auth/idp-login",INVITE:"/invite/:code",RESOLVE_ROUTE_BY_PATH:"/resolve-route-by-path",RESOLVE_ROUTES_BY_PATHS:"/resolve-routes-by-paths",RESOLVE_ROUTE_BY_SLUG:"/resolve-route-by-slug",GET_ROUTES_BY_LINE:"/get-routes-by-line",FEEDBACK:"/feedback",TELEMETRY:"/_events",OTEL_TRACES:"/_otel/v1/traces",HEALTH:"/_health",ASK_AI:"/_ask-ai",CATALOG_ENTITIES:`/catalog-entities/:${t}?`,CATALOG_ENTITIES_RELATIONS:`/catalog-entities-relations/:${c}?`,BFF_CATALOG_ENTITIES:`/bff/catalog-entities/:${t}?`,BFF_CATALOG_RELATED_ENTITIES:`/bff/catalog-related-entities/:${t}`,BFF_CATALOG_REVISIONS:`/bff/catalog-revisions/:${t}`,MCP_OAUTH_AUTHORIZATION_SERVER:"/.well-known/oauth-authorization-server",MCP_OAUTH_PROTECTED_RESOURCE:"/.well-known/oauth-protected-resource/mcp",MCP_DYNAMIC_CLIENT_REGISTRATION:"/_mcp/register",MCP_AUTHORIZATION:"/_mcp/oauth2/auth",MCP_TOKEN_PORTAL:"/_mcp/oauth2/token-portal",MCP_CALLBACK:"/_mcp/oauth/callback",SCORECARDS:"/scorecards"};var _;(function(o){o.AI_SEARCH="aiSearch"})(_||(_={}));const m="/login",M="/invite",G={AUTH0_PASS:"/auth/auth0-pass"},b={NotExist:"ENOENT"},H="default_locale",v="sidebar-",F="PUBLIC_",y=["BROKEN_LINK","MARKDOC","RESOLVE"],B=["logo","navbar","products","footer","sidebar","scripts","links","feedback","search","aiAssistant","colorMode","navigation","codeSnippet","markdown","openapi","graphql","analytics","userMenu","versionPicker","breadcrumbs","catalog","entitiesCatalog","scorecard","scorecards","scorecardClassic","mcp","banner"],s="redocly_category",k="redocly_product",f="redocly_version",K="redocly_teams",V=s,Y={asyncapi:"asyncapi",apiFunctions:"apiFunctions",reactPages:"reactPages",catalog:"catalog",catalogClassic:"catalogClassic",lint:"lint",scorecard:"scorecard",scorecards:"scorecards",l10n:"l10n",openapi:"openapi",graphql:"graphql",markdown:"markdown",devOnboarding:"devOnboarding",seo:"seo",redirects:"redirects",customPlugins:"customPlugins",themeEjecting:"themeEjecting",products:"products",breadcrumbs:"breadcrumbs",mockServer:"mockServer",sso:"sso",rbac:"rbac",analytics:"analytics",removeAttribution:"removeAttribution",advancedSearch:"advancedSearch",soap:"soap",ssoDirect:"ssoDirect",codeWalkthrough:"codeWalkthrough",aiSearchLimit:"aiSearchLimit",mcp:"mcp",banner:"banner"};export{g as ALLOWED_CORS_ORIGINS,n as ASYNC_API_DOCS_TEMPLATE_ID,t as CATALOG_ENTITY_KEY,c as CATALOG_ENTITY_RELATION_ID,P as CONFIG_FILE_NAME,O as CUSTOM_MARKDOC_OPTIONS_PATH,p as CUSTOM_MARKDOC_TAGS_PATH,C as DEFAULT_ANONYMOUS_VISITOR_TEAM,l as DEFAULT_AUTHENTICATED_TEAM,I as DEFAULT_COOKIE_EXPIRATION,H as DEFAULT_LOCALE_PLACEHOLDER,S as DEFAULT_RBAC_SCOPE,d as DEFAULT_SSO_IDP_TITLE,u as DEFAULT_THEME_NAME,m as DEV_LOGIN_SLUG,x as EJECT_COMPONENT_URL,G as ExternalRoutes,Y as FEATURE,r as FEEDBACK_TYPES,b as FsErrors,h as GLOBAL_DATA_URL,T as GRAPHQL_SPEC_SLUG,A as GRAPHQL_TEMPLATE_ID,M as INVITE_SLUG,y as MARKDOC_ERROR_TYPES,E as OPENAPI_DOCS_TEMPLATE_ID,F as PUBLIC_ENV_PREFIX,R as PUBLIC_RBAC_SCOPE_ITEM,i as RBAC_ALL_OTHER_TEAMS,L as REQUIRED_OIDC_SCOPES,_ as RbacFeatures,s as SEARCH_CATEGORY_FIELD,V as SEARCH_GROUP_FACET_FIELD,k as SEARCH_PRODUCT_FIELD,K as SEARCH_RBAC_FIELD,f as SEARCH_VERSION_FIELD,e as SERVER_EDITOR_APP_URL,v as SIDEBAR_PREFIX,U as ServerRoutes,N as TRANSLATIONS_FILE_NAME,B as UI_ACCESSIBLE_CONFIG_PROPS,D as USER_THEME_ALIAS,a as cliCommandNames};
@@ -0,0 +1,3 @@
1
+ import type { EntityRelationType } from '@redocly/theme/core/types';
2
+ export declare const RELATION_NORMALIZATION_MAP: Record<EntityRelationType, EntityRelationType>;
3
+ //# sourceMappingURL=relation-normalization.d.ts.map
@@ -0,0 +1 @@
1
+ const e={owns:"owns",ownedBy:"owns",hasParts:"hasParts",partOf:"hasParts",creates:"creates",createdBy:"creates",implements:"implements",implementedBy:"implements",dependsOn:"dependsOn",dependencyOf:"dependsOn",uses:"uses",usedBy:"uses",extends:"extends",extendedBy:"extends",supersedes:"supersedes",supersededBy:"supersedes",hasMember:"hasMember",memberOf:"hasMember",triggers:"triggers",triggeredBy:"triggers",returns:"returns",returnedBy:"returns",produces:"produces",consumes:"consumes",linksTo:"linksTo",compatibleWith:"compatibleWith",relatesTo:"relatesTo"};export{e as RELATION_NORMALIZATION_MAP};
@@ -1 +1 @@
1
- import{ulid as o}from"ulid";function i(e,r,s){const t=new Date().toISOString();return{id:e.id||`cer_${o()}`,organizationId:r,projectId:s,sourceKey:e.sourceKey,sourceVersion:e.sourceVersion??"",sourceRevision:e.sourceRevision??"",sourceToTargetRelation:e.type,targetKey:e.targetKey,targetVersion:e.targetVersion??"",targetRevision:e.targetRevision??"",targetToSourceRelation:`reverse:${e.type}`,sourceFile:e.sourceFile||null,fileHash:e.fileHash||null,isDeleted:e.isDeleted??!1,createdAt:e.createdAt||t,updatedAt:t}}export{i as createEntityRelationDbRecordFromDto};
1
+ import{ulid as i}from"ulid";import{normalizeRelation as c}from"../../utils/normalize-relation.js";function a(e,o,s){const r=new Date().toISOString(),t=c({type:e.type,sourceKey:e.sourceKey,targetKey:e.targetKey,sourceVersion:e.sourceVersion,targetVersion:e.targetVersion,sourceRevision:e.sourceRevision,targetRevision:e.targetRevision});return{id:e.id||`cer_${i()}`,organizationId:o,projectId:s,sourceKey:t.sourceKey,sourceVersion:t.sourceVersion??"",sourceRevision:t.sourceRevision??"",sourceToTargetRelation:t.type,targetKey:t.targetKey,targetVersion:t.targetVersion??"",targetRevision:t.targetRevision??"",targetToSourceRelation:`reverse:${t.type}`,sourceFile:e.sourceFile||null,fileHash:e.fileHash||null,isDeleted:e.isDeleted??!1,createdAt:e.createdAt||r,updatedAt:r}}export{a as createEntityRelationDbRecordFromDto};
@@ -1 +1 @@
1
- import{ulid as v}from"ulid";function g({relation:e,sourceFile:o,fileHash:n,sourceKey:s,sourceVersion:t,sourceRevision:r,organizationId:c,projectId:d}){const i=new Date().toISOString();return{id:`cer_${v()}`,organizationId:c,projectId:d,sourceKey:s,sourceVersion:t&&e.version?t:"",sourceRevision:r&&e.revision?r:"",sourceToTargetRelation:e.type,targetKey:e.key,targetVersion:t&&e.version?e.version:"",targetRevision:r&&e.revision?e.revision:"",targetToSourceRelation:`reverse:${e.type}`,sourceFile:o,fileHash:n,isDeleted:!1,createdAt:i,updatedAt:i}}export{g as createEntityRelationDbRecordFromFileSchema};
1
+ import{ulid as g}from"ulid";import{normalizeRelation as l}from"../../utils/normalize-relation.js";function R({relation:e,sourceFile:n,fileHash:s,sourceKey:a,sourceVersion:r,sourceRevision:o,organizationId:c,projectId:u}){const i=new Date().toISOString(),t=l({type:e.type,sourceKey:a,targetKey:e.key,sourceVersion:r&&e.version?r:null,targetVersion:r&&e.version?e.version:null,sourceRevision:o&&e.revision?o:null,targetRevision:o&&e.revision?e.revision:null});return{id:`cer_${g()}`,organizationId:c,projectId:u,sourceKey:t.sourceKey,sourceVersion:t.sourceVersion??"",sourceRevision:t.sourceRevision??"",sourceToTargetRelation:t.type,targetKey:t.targetKey,targetVersion:t.targetVersion??"",targetRevision:t.targetRevision??"",targetToSourceRelation:`reverse:${t.type}`,sourceFile:n,fileHash:s,isDeleted:!1,createdAt:i,updatedAt:i}}export{R as createEntityRelationDbRecordFromFileSchema};
@@ -0,0 +1,14 @@
1
+ import type { BffCatalogEntity } from '@redocly/theme/core/types';
2
+ import type { PaginationParams } from '../../../../../providers/database/pagination/schemas.js';
3
+ import type { DatabaseClient } from '../../../../../providers/database/client.js';
4
+ import type { ListResult } from './catalog-entities-local-read-repository.js';
5
+ export declare class CatalogEntitiesBffRepository {
6
+ #private;
7
+ constructor(db: DatabaseClient, attachedDatabasePath: string);
8
+ getEntitiesWithRelations(paginationParams?: PaginationParams): Promise<ListResult<BffCatalogEntity>>;
9
+ getEntityWithRelationsByKey(entityKey: string, filter?: {
10
+ revision?: string | null;
11
+ version?: string | null;
12
+ }): Promise<BffCatalogEntity | null>;
13
+ }
14
+ //# sourceMappingURL=catalog-entities-bff-repository.d.ts.map
@@ -0,0 +1,112 @@
1
+ import{and as c,desc as H,eq as s,notExists as v,sql as e}from"drizzle-orm";import{unionAll as C}from"drizzle-orm/sqlite-core";import{logger as M}from"../../../../../tools/notifiers/logger.js";import{entitiesTable as i}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as k}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{applyPagination as S}from"../../../../../providers/database/pagination/index.js";import{applyFilter as q,excludeFieldsFromFilter as j,getAllFilterFieldValues as O}from"../../../../../providers/database/pagination/filter.js";import{createBffEntity as A}from"../../mappers/create-bff-entity.js";import{FIELDS_TO_SELECT_FOR_ENTITY as n,FIELDS_TO_SELECT_FOR_ENTITY_RELATION as $,createEntityFieldsForSelect as W}from"../utils.js";class Z{#e;#t;constructor(t,o){this.#e=t,this.#t=o}async getEntitiesWithRelations(t={}){const o=O(t.filter,"owners");if(o.length>0)return this.#c(t,o);const m=O(t.filter,"domains");return m.length>0?this.#l(t,m):this.#a(t)}async#c(t,o){const m=this.#s(),l=this.#n(),d=this.#o(),y=l.as("combined_relations"),u=d.as("combined_target_entities"),h=o.map(r=>e`
2
+ EXISTS (
3
+ SELECT 1 FROM (${l}) cr
4
+ WHERE cr.source_key = ${r}
5
+ AND cr.source_to_target_relation = 'owns'
6
+ AND cr.target_key = base_entities.key
7
+ )
8
+ `),_=h.length===1?h[0]:e`(${e.join(h,e` OR `)})`,b=O(t.filter,"domains");let w=_;if(b.length>0){const r=b.map(a=>e`
9
+ EXISTS (
10
+ SELECT 1 FROM (${l}) cr
11
+ JOIN (${d}) d ON d.key = ${a}
12
+ WHERE d.type = 'domain'
13
+ AND d.is_current = 1
14
+ AND cr.source_key = ${a}
15
+ AND cr.target_key = base_entities.key
16
+ AND cr.source_to_target_relation = 'hasParts'
17
+ )
18
+ `),f=r.length===1?r[0]:e`(${e.join(r,e` OR `)})`;w=e`(${_} AND ${f})`}const R=j(t.filter,["owners","domains"]),g={...t,filter:R,baseWhereCondition:w},E=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();try{const r=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();S(r,{...g,limit:void 0,skip:void 0,after:void 0,before:void 0});const f=this.#e.client.select({count:e`count(*)`}).from(r.as("count_subquery")),a=t.limit||10;S(E,{...g,limit:a+1});const p=E.as("paged_entities"),N=this.#e.client.with(y,u,p).select({...W("paged_entities"),domains:this.#i("paged_entities").as("domains"),owners:this.#r("paged_entities").as("owners")}).from(p),[F,I]=await Promise.all([N.run(),f.run()]),D=F.rows,L=Number(I.rows[0]?.count||0),Q=D.length>a;return{items:D.slice(0,a).map(T=>A(T)).filter(T=>T!==null),hasMore:Q,total:L}}catch(r){return M.error("Error getting entities with relations (owner optimized path):",r),{items:[],hasMore:!1,total:0}}}async#l(t,o){const m=this.#s(),l=this.#n(),d=this.#o(),y=l.as("combined_relations"),u=d.as("combined_target_entities"),h=o.map(r=>e`
19
+ EXISTS (
20
+ SELECT 1 FROM (${l}) cr
21
+ JOIN (${d}) d ON d.key = ${r}
22
+ WHERE d.type = 'domain'
23
+ AND d.is_current = 1
24
+ AND cr.source_key = ${r}
25
+ AND cr.target_key = base_entities.key
26
+ AND cr.source_to_target_relation = 'hasParts'
27
+ )
28
+ `),_=h.length===1?h[0]:e`(${e.join(h,e` OR `)})`,b=O(t.filter,"owners");let w=_;if(b.length>0){const r=b.map(a=>e`
29
+ EXISTS (
30
+ SELECT 1 FROM (${l}) cr
31
+ WHERE cr.source_key = ${a}
32
+ AND cr.source_to_target_relation = 'owns'
33
+ AND cr.target_key = base_entities.key
34
+ )
35
+ `),f=r.length===1?r[0]:e`(${e.join(r,e` OR `)})`;w=e`(${_} AND ${f})`}const R=j(t.filter,["domains","owners"]),g={...t,filter:R,baseWhereCondition:w},E=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();try{const r=this.#e.client.select(n).from(m.as("base_entities")).$dynamic();S(r,{...g,limit:void 0,skip:void 0,after:void 0,before:void 0});const f=this.#e.client.select({count:e`count(*)`}).from(r.as("count_subquery")),a=t.limit||10;S(E,{...g,limit:a+1});const p=E.as("paged_entities"),N=this.#e.client.with(y,u,p).select({...W("paged_entities"),domains:this.#i("paged_entities").as("domains"),owners:this.#r("paged_entities").as("owners")}).from(p),[F,I]=await Promise.all([N.run(),f.run()]),D=F.rows,L=Number(I.rows[0]?.count||0),Q=D.length>a;return{items:D.slice(0,a).map(T=>A(T)).filter(T=>T!==null),hasMore:Q,total:L}}catch(r){return M.error("Error getting entities with relations (domain optimized path):",r),{items:[],hasMore:!1,total:0}}}async#a(t){const o=this.#s(),m=this.#n(),l=this.#o(),d=m.as("combined_relations"),y=l.as("combined_target_entities"),u=this.#e.client.select(n).from(o.as("base_entities")).$dynamic(),{whereCondition:h}=q(u,t.filter);h&&u.where(h);try{const _=this.#e.client.select(n).from(o.as("base_entities_count")).$dynamic();h&&_.where(h);const b=this.#e.client.select({count:e`count(*)`}).from(_.as("count_subquery")),w=t.limit||10;S(u,{...t,limit:w+1});const R=u.as("paged_entities"),g=this.#e.client.with(d,y,R).select({...W("paged_entities"),domains:this.#i("paged_entities").as("domains"),owners:this.#r("paged_entities").as("owners")}).from(R),[E,r]=await Promise.all([g.run(),b.run()]),f=E.rows,a=Number(r.rows[0]?.count||0),p=f.length>w;return{items:f.slice(0,w).map(N=>A(N)).filter(N=>N!==null),hasMore:p,total:a}}catch(_){return M.error("Error getting entities with relations (optimized path):",_),{items:[],hasMore:!1,total:0}}}#s(){return this.#t?C(this.#e.client.select(n).from(e`remote.entities`).where(c(s(e.raw("is_current"),1),s(e.raw("is_deleted"),!1))),this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1),v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1)))}#n(){return this.#t?C(this.#e.client.select($).from(e`remote.entities_relations`),this.#e.client.select($).from(k).where(v(this.#e.client.select({id:$.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${k.sourceKey}`)))):this.#e.client.select($).from(k)}#o(){return this.#t?C(this.#e.client.select(n).from(e`remote.entities`).where(c(s(e.raw("is_current"),1),s(e.raw("is_deleted"),!1))),this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1),v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(n).from(i).where(c(s(i.isCurrent,!0),s(i.isDeleted,!1)))}async getEntityWithRelationsByKey(t,o){if(!t||t.trim()==="")return null;const m=o?.revision,l=o?.version,d=m?null:await this.#m(t,l||null),y=l??d?.version,u=m||d?.revision||null,_=(this.#t?C(this.#e.client.select(n).from(e`remote.entities`).where(s(i.key,t)),this.#e.client.select(n).from(i).where(c(s(i.key,t),v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(e`remote.key = ${i.key}`))))):this.#e.client.select(n).from(i).where(s(i.key,t))).as("e"),w=(this.#t?C(this.#e.client.select($).from(e`remote.entities_relations`),this.#e.client.select($).from(k).where(v(this.#e.client.select({id:$.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${k.sourceKey}`)))):this.#e.client.select($).from(k)).as("combined_relations"),g=(this.#t?C(this.#e.client.select(n).from(e`remote.entities`),this.#e.client.select(n).from(i).where(v(this.#e.client.select({id:n.id}).from(e`remote.entities as remote`).where(e`remote.key = ${i.key}`)))):this.#e.client.select(n).from(i)).as("combined_target_entities"),E=this.#e.client.with(_,w,g).select({...W("e"),domains:this.#i("e").as("domains"),owners:this.#r("e").as("owners")}).from(_).$dynamic();u?E.where(c(s(e.raw("revision"),u),y?s(e.raw("version"),y):void 0)):E.where(s(e.raw("is_current"),1)),E.limit(1);const r=await E.run();if(r.rows.length===0)return null;const f=r.rows[0];return A(f)}async#m(t,o){if(o){const y=await(this.#t?C(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(c(e`key = ${t}`,e`version = ${o}`)).orderBy(e.raw("revision DESC")),this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.version,o),v(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.version = ${o}`))))).orderBy(H(i.revision))):this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.version,o))).orderBy(H(i.revision))).limit(1).run();if(y.rows.length>0){const u=y.rows[0];return{version:u.version||null,revision:u.revision||null}}return null}const l=await(this.#t?C(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(c(e`key = ${t}`,e`is_current = 1`)),this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.isCurrent,!0),v(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(c(e`remote.key = ${i.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({version:i.version,revision:i.revision}).from(i).where(c(s(i.key,t),s(i.isCurrent,!0)))).limit(1).run();if(l.rows.length>0){const d=l.rows[0];return{version:d.version||null,revision:d.revision||null}}return null}#i(t){return e`
36
+ COALESCE(
37
+ (
38
+ SELECT json_group_array(
39
+ json_object(
40
+ 'id', d.id,
41
+ 'key', d.key,
42
+ 'type', d.type,
43
+ 'title', d.title,
44
+ 'summary', d.summary,
45
+ 'created_at', d.created_at,
46
+ 'updated_at', d.updated_at,
47
+ 'source', d.source,
48
+ 'source_file', d.source_file
49
+ )
50
+ )
51
+ FROM (
52
+ SELECT DISTINCT d.*
53
+ FROM combined_relations er
54
+ JOIN combined_target_entities d ON d.key = er.source_key
55
+ WHERE er.source_to_target_relation = 'hasParts'
56
+ AND er.target_key = ${e.raw(`${t}.key`)}
57
+ AND d.type = 'domain'
58
+ AND d.is_current = 1
59
+ AND CASE
60
+ WHEN ${e.raw(`${t}.version`)} = ''
61
+ THEN er.target_version = ''
62
+ ELSE (er.target_version = ${e.raw(`${t}.version`)} OR er.target_version = '')
63
+ END
64
+ AND CASE
65
+ WHEN ${e.raw(`${t}.revision`)} = ''
66
+ THEN 1 = 1
67
+ ELSE (er.target_revision <= ${e.raw(`${t}.revision`)} OR er.target_revision = '')
68
+ END
69
+ LIMIT 10
70
+ ) d
71
+ ),
72
+ json_array()
73
+ )
74
+ `}#r(t){return e`
75
+ COALESCE(
76
+ (
77
+ SELECT json_group_array(
78
+ json_object(
79
+ 'id', o.id,
80
+ 'key', o.key,
81
+ 'type', o.type,
82
+ 'title', o.title,
83
+ 'summary', o.summary,
84
+ 'created_at', o.created_at,
85
+ 'updated_at', o.updated_at,
86
+ 'source', o.source,
87
+ 'source_file', o.source_file
88
+ )
89
+ )
90
+ FROM (
91
+ SELECT DISTINCT o.*
92
+ FROM combined_relations er
93
+ JOIN combined_target_entities o ON o.key = er.source_key
94
+ WHERE er.source_to_target_relation = 'owns'
95
+ AND er.target_key = ${e.raw(`${t}.key`)}
96
+ AND o.is_current = 1
97
+ AND CASE
98
+ WHEN ${e.raw(`${t}.version`)} = ''
99
+ THEN er.target_version = ''
100
+ ELSE (er.target_version = ${e.raw(`${t}.version`)} OR er.target_version = '')
101
+ END
102
+ AND CASE
103
+ WHEN ${e.raw(`${t}.revision`)} = ''
104
+ THEN 1 = 1
105
+ ELSE (er.target_revision <= ${e.raw(`${t}.revision`)} OR er.target_revision = '')
106
+ END
107
+ LIMIT 10
108
+ ) o
109
+ ),
110
+ json_array()
111
+ )
112
+ `}}export{Z as CatalogEntitiesBffRepository};
@@ -1,4 +1,4 @@
1
- import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as X,sql as e}from"drizzle-orm";import{unionAll as y}from"drizzle-orm/sqlite-core";import{logger as L}from"../../../../../tools/notifiers/logger.js";import{VERSION_NOT_SPECIFIED as O}from"@redocly/theme/core/constants";import{applyPagination as T}from"../../../../../providers/database/pagination/index.js";import{applyFilter as b}from"../../../../../providers/database/pagination/filter.js";import{createBffEntity as F}from"../../mappers/create-bff-entity.js";import{FIELDS_TO_SELECT_FOR_ENTITY as a,FIELDS_TO_SELECT_FOR_ENTITY_RELATION as d,createEntityFieldsForSelect as B}from"../utils.js";import{createEntityReadModel as g}from"../../mappers/create-entity-read-model.js";import{createEntityRelation as H}from"../../mappers/create-entity-relation.js";import{entitiesTable as t}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as h}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{CatalogEntitiesRelationsRepository as W}from"./catalog-entities-relations-repository.js";class ne{#e;#t=void 0;#i;constructor(i){this.#e=i,this.#i=new W(this.#e,"")}async attachDatabase(i){this.#t!==i&&(this.#t=i,await this.#e.client.run(e`ATTACH DATABASE ${i} AS remote`),this.#i=new W(this.#e,i))}async getEntities(i={}){const s=this.#t?y(this.#e.client.select(a).from(e`remote.entities`),this.#e.client.select(a).from(t).where(E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(a).from(t),n=this.#e.client.select(a).from(s.as("combined_entities")),c=this.#e.client.select(a).from(s.as("combined_entities")).$dynamic(),m=T(c,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),l=this.#e.client.$count(m),u=n.$dynamic(),_=i.limit||10,f=T(u,{...i,limit:_+1}),[N,k]=await Promise.all([f.run(),l]),S=N.rows,v=S.length>_;return{items:S.slice(0,_).map(w=>g(w)).filter(w=>w!==null),hasMore:v,total:k}}async getEntityKeysAndVersionsBySourceFile(i){const s=this.#t?y(this.#e.client.select({keyVersion:e`key || ':' || COALESCE(version, ${O})`.as("keyVersion")}).from(e`remote.entities`).where(o(e`source_file = ${i}`,e`source = 'file'`,e`key IS NOT NULL`)),this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${O})`.as("keyVersion")}).from(t).where(o(r(t.sourceFile,i),r(t.source,"file"),e`${t.key} IS NOT NULL`,E(this.#e.client.select({key:e.raw("key")}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`))))):this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${O})`.as("keyVersion")}).from(t).where(o(r(t.sourceFile,i),r(t.source,"file"),Q(t.key))),n=await this.#e.client.selectDistinct({keyVersion:e`combined_keys_versions.keyVersion`}).from(s.as("combined_keys_versions")).run();return new Set(n.rows.map(c=>c.keyVersion))}async listEntityRevisions(i,s){const n=[r(t.key,i),r(t.isDeleted,!1),...s?[r(t.version,s)]:[]],c=this.#t?y(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(e`remote.entities`).where(o(e`key = ${i}`,s?e`version = ${s}`:void 0)),this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(o(...n,E(this.#e.client.select({version:e.raw("version")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.version = ${t.version}`,e`remote.revision = ${t.revision}`)))))):this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(o(...n));return(await this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(c.as("combined_revisions")).orderBy(e.raw("is_current DESC"),e.raw("updated_at DESC"),e.raw("created_at DESC")).run()).rows.map(l=>({version:l.version||null,revision:l.revision??"",isCurrent:l.is_current!==null?!!l.is_current:!1,createdAt:l.created_at||null,updatedAt:l.updated_at||null,isDefaultVersion:l.is_default_version!==null?!!l.is_default_version:!1}))}async getEntitiesCountByTypes(){const i=this.#t?y(this.#e.client.select({type:e`type`}).from(e`remote.entities`).where(o(r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select({type:t.type}).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({type:t.type}).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1)));return this.#e.client.select({type:t.type,count:V()}).from(i.as("combined_entities")).groupBy(t.type).where(r(t.isDeleted,!1))}async getEntityByKey(i){const c=(await(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(o(r(t.key,i),r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select(a).from(t).where(o(r(t.key,i),r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(a).from(t).where(o(r(t.key,i),r(t.isCurrent,!0),r(t.isDeleted,!1)))).run()).rows[0];return c?g(c):null}async getOneOutdatedEntity(){const i=this.#t?y(this.#e.client.select(a).from(e`remote.entities`),this.#e.client.select(a).from(t).where(E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(a).from(t),n=(await this.#e.client.select(a).from(i.as("combined_entities")).where(X(e`combined_entities.scorecards_status = 'OUTDATED'`,e`combined_entities.scorecards_status IS NULL`)).orderBy(e`combined_entities.updated_at ASC`).limit(1).run()).rows[0];return n?g(n):null}async getEntitiesRelations(i={}){const s=this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`),this.#e.client.select(d).from(h).where(E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${h.sourceKey}`)))):this.#e.client.select(d).from(h),n=this.#e.client.select(d).from(s.as("combined_entities_relations")).$dynamic(),c=this.#e.client.select(d).from(s.as("combined_entities_relations")).$dynamic(),m=T(c,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),l=this.#e.client.$count(m),u=i.limit||10,_=T(n,{...i,limit:u+1}),[f,N]=await Promise.all([_.run(),l]),k=f.rows,S=k.length>u;return{items:k.slice(0,u).map(v=>H(v)).filter(v=>v!==null),hasMore:S,total:N}}async getEntityRelationById(i){const c=(await(this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`).where(r(h.id,i)),this.#e.client.select(d).from(h).where(o(r(h.id,i),E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.id = ${h.id}`))))):this.#e.client.select(d).from(h).where(r(h.id,i))).run()).rows[0];return c?H(c):null}async getEntitiesWithRelations(i={}){const n=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(o(r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1)))).as("e"),m=(this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`),this.#e.client.select(d).from(h).where(E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${h.sourceKey}`)))):this.#e.client.select(d).from(h)).as("combined_relations"),u=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(o(r(e.raw("is_current"),1),r(e.raw("is_deleted"),!1))),this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(a).from(t).where(o(r(t.isCurrent,!0),r(t.isDeleted,!1)))).as("combined_target_entities"),_=this.#e.client.with(n,m,u).select({...B("e"),domains:this.#r("e").as("domains"),owners:this.#s("e").as("owners")}).from(n).as("entities_with_relations"),f={...a,domains:_.domains,owners:_.owners},N=this.#e.client.with(_).select(f).from(_).$dynamic(),k=this.#e.client.with(_).select(f).from(_).$dynamic(),{whereCondition:S}=b(N,i.filter);S&&N.where(S);const{whereCondition:v}=b(k,i.filter);v&&k.where(v);try{const w=T(k,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),p=this.#e.client.$count(w),R=i.limit||10,A=T(N,{...i,limit:R+1}),[M,U]=await Promise.all([A.run(),p]),D=M.rows,q=D.length>R;return{items:D.slice(0,R).map(C=>F(C)).filter(C=>C!==null),hasMore:q,total:U}}catch(w){return L.error("Error getting entities with relations:",w),{items:[],hasMore:!1,total:0}}}async#n(i,s){if(s){const l=await(this.#t?y(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(o(e`key = ${i}`,e`version = ${s}`)).orderBy(e.raw("revision DESC")),this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.version,s),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.version = ${s}`))))).orderBy(I(t.revision))):this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.version,s))).orderBy(I(t.revision))).limit(1).run();if(l.rows.length>0){const u=l.rows[0];return{version:u.version||null,revision:u.revision||null}}return null}const c=await(this.#t?y(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision")}).from(e`remote.entities`).where(o(e`key = ${i}`,e`is_current = 1`)),this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.isCurrent,!0),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(o(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({version:t.version,revision:t.revision}).from(t).where(o(r(t.key,i),r(t.isCurrent,!0)))).limit(1).run();if(c.rows.length>0){const m=c.rows[0];return{version:m.version||null,revision:m.revision||null}}return null}async getEntityWithRelationsByKey(i,s){const n=s?.revision,c=s?.version,m=n?null:await this.#n(i,c||null),l=c??m?.version,u=n||m?.revision||null,f=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`).where(r(t.key,i)),this.#e.client.select(a).from(t).where(o(r(t.key,i),E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`))))):this.#e.client.select(a).from(t).where(r(t.key,i))).as("e"),k=(this.#t?y(this.#e.client.select(d).from(e`remote.entities_relations`),this.#e.client.select(d).from(h).where(E(this.#e.client.select({id:d.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${h.sourceKey}`)))):this.#e.client.select(d).from(h)).as("combined_relations"),v=(this.#t?y(this.#e.client.select(a).from(e`remote.entities`),this.#e.client.select(a).from(t).where(E(this.#e.client.select({id:a.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(a).from(t)).as("combined_target_entities"),w=this.#e.client.with(f,k,v).select({...B("e"),domains:this.#r("e").as("domains"),owners:this.#s("e").as("owners")}).from(f).$dynamic();u?w.where(o(r(e.raw("revision"),u),l?r(e.raw("version"),l):void 0)):w.where(r(e.raw("is_current"),1)),w.limit(1);const p=await w.run();if(p.rows.length===0)return null;const R=p.rows[0];return F(R)}async getRelationsForEntity(i,s,n){return this.#i.getRelationsForEntity(i,s,n)}async getRelatedEntities(i,s={}){return this.#i.getRelatedEntities(i,s)}async getCatalogFilters({entitiesTypes:i=[],emptyFilters:s=[]}){if(!s.length)return{};try{return await this.#o(i),(s.includes("domains")||s.includes("owners"))&&await this.#a(),await this.#c(s)}catch(n){return console.error("Error fetching catalog filters:",n),{}}finally{await this.#u()}}async#o(i){if(this.#t?await this.#e.client.run(e`
1
+ import{and as l,count as L,eq as n,isNotNull as v,notExists as E,or as D,sql as e}from"drizzle-orm";import{unionAll as f}from"drizzle-orm/sqlite-core";import{logger as b}from"../../../../../tools/notifiers/logger.js";import{VERSION_NOT_SPECIFIED as R}from"@redocly/theme/core/constants";import{applyPagination as S}from"../../../../../providers/database/pagination/index.js";import{FIELDS_TO_SELECT_FOR_ENTITY as c,FIELDS_TO_SELECT_FOR_ENTITY_RELATION as m}from"../utils.js";import{createEntityReadModel as O}from"../../mappers/create-entity-read-model.js";import{createEntityRelation as C}from"../../mappers/create-entity-relation.js";import{entitiesTable as t}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as y}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{CatalogEntitiesRelationsRepository as I}from"./catalog-entities-relations-repository.js";import{CatalogEntitiesBffRepository as g}from"./catalog-entities-bff-repository.js";class j{#e;#t=void 0;#i;#s;constructor(i){this.#e=i,this.#i=new I(this.#e,this.#t||""),this.#s=new g(this.#e,this.#t||"")}async attachDatabase(i){this.#t!==i&&(this.#t=i,await this.#e.client.run(e`ATTACH DATABASE ${i} AS remote`),this.#i=new I(this.#e,i),this.#s=new g(this.#e,i))}async getEntities(i={}){const s=this.#t?f(this.#e.client.select(c).from(e`remote.entities`),this.#e.client.select(c).from(t).where(E(this.#e.client.select({id:c.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(c).from(t),r=this.#e.client.select(c).from(s.as("combined_entities")),o=this.#e.client.select(c).from(s.as("combined_entities")).$dynamic(),_=S(o,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),a=this.#e.client.$count(_),u=r.$dynamic(),d=i.limit||10,h=S(u,{...i,limit:d+1}),[T,N]=await Promise.all([h.run(),a]),p=T.rows,w=p.length>d;return{items:p.slice(0,d).map(k=>O(k)).filter(k=>k!==null),hasMore:w,total:N}}async getEntityKeysAndVersionsBySourceFile(i){const s=this.#t?f(this.#e.client.select({keyVersion:e`key || ':' || COALESCE(version, ${R})`.as("keyVersion")}).from(e`remote.entities`).where(l(e`source_file = ${i}`,e`source = 'file'`,e`key IS NOT NULL`)),this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${R})`.as("keyVersion")}).from(t).where(l(n(t.sourceFile,i),n(t.source,"file"),e`${t.key} IS NOT NULL`,E(this.#e.client.select({key:e.raw("key")}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`))))):this.#e.client.select({keyVersion:e`${t.key} || ':' || COALESCE(${t.version}, ${R})`.as("keyVersion")}).from(t).where(l(n(t.sourceFile,i),n(t.source,"file"),v(t.key))),r=await this.#e.client.selectDistinct({keyVersion:e`combined_keys_versions.keyVersion`}).from(s.as("combined_keys_versions")).run();return new Set(r.rows.map(o=>o.keyVersion))}async listEntityRevisions(i,s){const r=[n(t.key,i),n(t.isDeleted,!1),...s?[n(t.version,s)]:[]],o=this.#t?f(this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(e`remote.entities`).where(l(e`key = ${i}`,s?e`version = ${s}`:void 0)),this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(l(...r,E(this.#e.client.select({version:e.raw("version")}).from(e`remote.entities as remote`).where(l(e`remote.key = ${t.key}`,e`remote.version = ${t.version}`,e`remote.revision = ${t.revision}`)))))):this.#e.client.select({version:t.version,revision:t.revision,isCurrent:t.isCurrent,createdAt:t.createdAt,updatedAt:t.updatedAt,isDefaultVersion:t.isDefaultVersion}).from(t).where(l(...r));return(await this.#e.client.select({version:e.raw("version"),revision:e.raw("revision"),isCurrent:e.raw("is_current"),createdAt:e.raw("created_at"),updatedAt:e.raw("updated_at"),isDefaultVersion:e.raw("is_default_version")}).from(o.as("combined_revisions")).orderBy(e.raw("is_current DESC"),e.raw("updated_at DESC"),e.raw("created_at DESC")).run()).rows.map(a=>({version:a.version||null,revision:a.revision??"",isCurrent:a.is_current!==null?!!a.is_current:!1,createdAt:a.created_at||null,updatedAt:a.updated_at||null,isDefaultVersion:a.is_default_version!==null?!!a.is_default_version:!1}))}async getEntitiesCountByTypes(){const i=this.#t?f(this.#e.client.select({type:e`type`}).from(e`remote.entities`).where(l(n(e.raw("is_current"),1),n(e.raw("is_deleted"),!1))),this.#e.client.select({type:t.type}).from(t).where(l(n(t.isCurrent,!0),n(t.isDeleted,!1),E(this.#e.client.select({id:e.raw("id")}).from(e`remote.entities as remote`).where(l(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select({type:t.type}).from(t).where(l(n(t.isCurrent,!0),n(t.isDeleted,!1)));return this.#e.client.select({type:t.type,count:L()}).from(i.as("combined_entities")).groupBy(t.type).where(n(t.isDeleted,!1))}async getEntityByKey(i){const o=(await(this.#t?f(this.#e.client.select(c).from(e`remote.entities`).where(l(n(t.key,i),n(e.raw("is_current"),1),n(e.raw("is_deleted"),!1))),this.#e.client.select(c).from(t).where(l(n(t.key,i),n(t.isCurrent,!0),n(t.isDeleted,!1),E(this.#e.client.select({id:c.id}).from(e`remote.entities as remote`).where(l(e`remote.key = ${t.key}`,e`remote.is_current = 1`)))))):this.#e.client.select(c).from(t).where(l(n(t.key,i),n(t.isCurrent,!0),n(t.isDeleted,!1)))).run()).rows[0];return o?O(o):null}async getOneOutdatedEntity(){const i=this.#t?f(this.#e.client.select(c).from(e`remote.entities`),this.#e.client.select(c).from(t).where(E(this.#e.client.select({id:c.id}).from(e`remote.entities as remote`).where(e`remote.key = ${t.key}`)))):this.#e.client.select(c).from(t),r=(await this.#e.client.select(c).from(i.as("combined_entities")).where(D(e`combined_entities.scorecards_status = 'OUTDATED'`,e`combined_entities.scorecards_status IS NULL`)).orderBy(e`combined_entities.updated_at ASC`).limit(1).run()).rows[0];return r?O(r):null}async getEntitiesRelations(i={}){const s=this.#t?f(this.#e.client.select(m).from(e`remote.entities_relations`),this.#e.client.select(m).from(y).where(E(this.#e.client.select({id:m.id}).from(e`remote.entities_relations as remote`).where(e`remote.source_key = ${y.sourceKey}`)))):this.#e.client.select(m).from(y),r=this.#e.client.select(m).from(s.as("combined_entities_relations")).$dynamic(),o=this.#e.client.select(m).from(s.as("combined_entities_relations")).$dynamic(),_=S(o,{...i,limit:void 0,skip:void 0,after:void 0,before:void 0}),a=this.#e.client.$count(_),u=i.limit||10,d=S(r,{...i,limit:u+1}),[h,T]=await Promise.all([d.run(),a]),N=h.rows,p=N.length>u;return{items:N.slice(0,u).map(w=>C(w)).filter(w=>w!==null),hasMore:p,total:T}}async getEntityRelationById(i){const o=(await(this.#t?f(this.#e.client.select(m).from(e`remote.entities_relations`).where(n(y.id,i)),this.#e.client.select(m).from(y).where(l(n(y.id,i),E(this.#e.client.select({id:m.id}).from(e`remote.entities_relations as remote`).where(e`remote.id = ${y.id}`))))):this.#e.client.select(m).from(y).where(n(y.id,i))).run()).rows[0];return o?C(o):null}async getEntitiesWithRelations(i={}){return this.#s.getEntitiesWithRelations(i)}async getEntityWithRelationsByKey(i,s){return this.#s.getEntityWithRelationsByKey(i,s)}async getRelationsForEntity(i,s,r){return this.#i.getRelationsForEntity(i,s,r)}async getRelatedEntities(i,s={}){return this.#i.getRelatedEntities(i,s)}async getCatalogFilters({entitiesTypes:i=[],emptyFilters:s=[]}){if(!s.length)return{};try{return await this.#r(i),(s.includes("domains")||s.includes("owners"))&&await this.#n(),await this.#o(s)}catch(r){return console.error("Error fetching catalog filters:",r),{}}finally{await this.#l()}}async#r(i){if(this.#t?await this.#e.client.run(e`
2
2
  CREATE TEMP TABLE IF NOT EXISTS temp_combined_entities AS
3
3
  SELECT
4
4
  e.key,
@@ -25,7 +25,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
25
25
  metadata
26
26
  FROM entities
27
27
  WHERE is_current = 1
28
- `),i.length){const s=i.map(n=>`'${n.replace(/'/g,"''")}'`).join(",");await this.#e.client.run(e.raw(`
28
+ `),i.length){const s=i.map(r=>`'${r.replace(/'/g,"''")}'`).join(",");await this.#e.client.run(e.raw(`
29
29
  CREATE TEMP TABLE IF NOT EXISTS temp_filtered_entities AS
30
30
  SELECT * FROM temp_combined_entities
31
31
  WHERE type IN (${s})
@@ -34,7 +34,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
34
34
  SELECT * FROM temp_combined_entities
35
35
  `);await this.#e.client.run(e`
36
36
  CREATE INDEX IF NOT EXISTS idx_temp_filtered_type ON temp_filtered_entities(type)
37
- `)}async#a(){this.#t?await this.#e.client.run(e`
37
+ `)}async#n(){this.#t?await this.#e.client.run(e`
38
38
  CREATE TEMP TABLE IF NOT EXISTS temp_combined_relations AS
39
39
  SELECT
40
40
  source_key,
@@ -62,12 +62,12 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
62
62
  CREATE INDEX IF NOT EXISTS idx_temp_rel_target ON temp_combined_relations(target_key)
63
63
  `),await this.#e.client.run(e`
64
64
  CREATE INDEX IF NOT EXISTS idx_temp_rel_relation ON temp_combined_relations(source_to_target_relation)
65
- `)}async#c(i){const s={},n=[],c=i.filter(u=>u.startsWith("metadata."));i.includes("type")&&n.push(`
65
+ `)}async#o(i){const s={},r=[],o=i.filter(u=>u.startsWith("metadata."));i.includes("type")&&r.push(`
66
66
  SELECT 'type' as filter_name, type as value, COUNT(*) as count
67
67
  FROM temp_filtered_entities
68
68
  WHERE type IS NOT NULL
69
69
  GROUP BY type
70
- `),i.includes("tags")&&n.push(`
70
+ `),i.includes("tags")&&r.push(`
71
71
  SELECT 'tags' as filter_name, tag.value as value, COUNT(DISTINCT tfe.key) as count
72
72
  FROM temp_filtered_entities tfe,
73
73
  json_each(COALESCE(tfe.tags, json_array())) as tag
@@ -76,7 +76,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
76
76
  AND tag.value IS NOT NULL
77
77
  AND tag.value != ''
78
78
  GROUP BY tag.value
79
- `),i.includes("domains")&&n.push(`
79
+ `),i.includes("domains")&&r.push(`
80
80
  SELECT 'domains' as filter_name, tce.key as value, COUNT(DISTINCT tfe.key) as count
81
81
  FROM temp_combined_relations tcr
82
82
  INNER JOIN temp_filtered_entities tfe ON (
@@ -89,7 +89,7 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
89
89
  )
90
90
  WHERE tce.type = 'domain'
91
91
  GROUP BY tce.key
92
- `),i.includes("owners")&&n.push(`
92
+ `),i.includes("owners")&&r.push(`
93
93
  SELECT 'owners' as filter_name, owner_key as value, COUNT(DISTINCT entity_key) as count
94
94
  FROM (
95
95
  SELECT
@@ -109,130 +109,12 @@ import{and as o,count as V,desc as I,eq as r,isNotNull as Q,notExists as E,or as
109
109
  INNER JOIN temp_combined_entities tce ON tce.key = owner_key
110
110
  WHERE owner_key IS NOT NULL
111
111
  GROUP BY owner_key
112
- `);for(const u of c)await this.#l(u,s);if(n.length===0)return s;const m=n.join(" UNION ALL "),l=await this.#e.client.run(e.raw(m));if(l?.rows)for(const u of l.rows){const _=u.filter_name,f=u.value,N=Number(u.count)||0;s[_]||(s[_]=[]),f&&s[_].push({value:f,count:N})}return s}async#l(i,s){const n=i.substring(9),c=`$.${j(n)}`,m=await this.#e.client.run(e.raw(`
113
- SELECT json_extract(metadata, '${c}') as value, COUNT(*) as count
112
+ `);for(const u of o)await this.#a(u,s);if(r.length===0)return s;const _=r.join(" UNION ALL "),a=await this.#e.client.run(e.raw(_));if(a?.rows)for(const u of a.rows){const d=u.filter_name,h=u.value,T=Number(u.count)||0;s[d]||(s[d]=[]),h&&s[d].push({value:h,count:T})}return s}async#a(i,s){const r=i.substring(9),o=`$.${$(r)}`,_=await this.#e.client.run(e.raw(`
113
+ SELECT json_extract(metadata, '${o}') as value, COUNT(*) as count
114
114
  FROM temp_filtered_entities
115
115
  WHERE metadata IS NOT NULL
116
116
  AND metadata != ''
117
- AND json_extract(metadata, '${c}') IS NOT NULL
118
- AND json_extract(metadata, '${c}') != ''
117
+ AND json_extract(metadata, '${o}') IS NOT NULL
118
+ AND json_extract(metadata, '${o}') != ''
119
119
  GROUP BY value
120
- `));m?.rows&&(s[i]=m.rows.map(l=>({value:l.value,count:Number(l.count)||0})).filter(l=>l.value))}async#u(){try{await this.#e.client.run(e`DROP TABLE IF EXISTS temp_combined_entities`),await this.#e.client.run(e`DROP TABLE IF EXISTS temp_filtered_entities`),await this.#e.client.run(e`DROP TABLE IF EXISTS temp_combined_relations`)}catch(i){L.error("Error cleaning up temp tables:",i)}}#r(i){return e`
121
- COALESCE(
122
- (
123
- SELECT json_group_array(
124
- json_object(
125
- 'id', d.id,
126
- 'key', d.key,
127
- 'type', d.type,
128
- 'title', d.title,
129
- 'summary', d.summary,
130
- 'created_at', d.created_at,
131
- 'updated_at', d.updated_at,
132
- 'source', d.source,
133
- 'source_file', d.source_file
134
- )
135
- )
136
- FROM (
137
- SELECT DISTINCT d.*
138
- FROM combined_relations er
139
- JOIN combined_target_entities d ON d.key = CASE
140
- WHEN er.source_key = ${e.raw(`${i}.key`)} THEN er.target_key
141
- WHEN er.target_key = ${e.raw(`${i}.key`)} THEN er.source_key
142
- END
143
- WHERE d.type = 'domain'
144
- AND d.is_current = 1
145
- AND (
146
- (
147
- er.source_key = ${e.raw(`${i}.key`)}
148
- AND CASE
149
- WHEN ${e.raw(`${i}.version`)} = ''
150
- THEN er.source_version = ''
151
- ELSE (er.source_version = ${e.raw(`${i}.version`)} OR er.source_version = '')
152
- END
153
- AND CASE
154
- WHEN ${e.raw(`${i}.revision`)} = ''
155
- THEN 1 = 1
156
- ELSE (er.source_revision <= ${e.raw(`${i}.revision`)} OR er.source_revision = '')
157
- END
158
- )
159
- OR (
160
- er.target_key = ${e.raw(`${i}.key`)}
161
- AND CASE
162
- WHEN ${e.raw(`${i}.version`)} = ''
163
- THEN er.target_version = ''
164
- ELSE (er.target_version = ${e.raw(`${i}.version`)} OR er.target_version = '')
165
- END
166
- AND CASE
167
- WHEN ${e.raw(`${i}.revision`)} = ''
168
- THEN 1 = 1
169
- ELSE (er.target_revision <= ${e.raw(`${i}.revision`)} OR er.target_revision = '')
170
- END
171
- )
172
- )
173
- LIMIT 10
174
- ) d
175
- ),
176
- json_array()
177
- )
178
- `}#s(i){return e`
179
- COALESCE(
180
- (
181
- SELECT json_group_array(
182
- json_object(
183
- 'id', o.id,
184
- 'key', o.key,
185
- 'type', o.type,
186
- 'title', o.title,
187
- 'summary', o.summary,
188
- 'created_at', o.created_at,
189
- 'updated_at', o.updated_at,
190
- 'source', o.source,
191
- 'source_file', o.source_file
192
- )
193
- )
194
- FROM (
195
- SELECT DISTINCT o.*
196
- FROM combined_relations er
197
- JOIN combined_target_entities o
198
- ON o.key = CASE
199
- WHEN er.source_key = ${e.raw(`${i}.key`)} THEN er.target_key
200
- ELSE er.source_key
201
- END
202
- WHERE ((er.source_key = ${e.raw(`${i}.key`)} AND er.source_to_target_relation = 'ownedBy')
203
- OR (er.target_key = ${e.raw(`${i}.key`)} AND er.source_to_target_relation = 'owns'))
204
- AND o.is_current = 1
205
- AND (
206
- (
207
- er.source_key = ${e.raw(`${i}.key`)}
208
- AND CASE
209
- WHEN ${e.raw(`${i}.version`)} = ''
210
- THEN er.source_version = ''
211
- ELSE (er.source_version = ${e.raw(`${i}.version`)} OR er.source_version = '')
212
- END
213
- AND CASE
214
- WHEN ${e.raw(`${i}.revision`)} = ''
215
- THEN 1 = 1
216
- ELSE (er.source_revision <= ${e.raw(`${i}.revision`)} OR er.source_revision = '')
217
- END
218
- )
219
- OR (
220
- er.target_key = ${e.raw(`${i}.key`)}
221
- AND CASE
222
- WHEN ${e.raw(`${i}.version`)} = ''
223
- THEN er.target_version = ''
224
- ELSE (er.target_version = ${e.raw(`${i}.version`)} OR er.target_version = '')
225
- END
226
- AND CASE
227
- WHEN ${e.raw(`${i}.revision`)} = ''
228
- THEN 1 = 1
229
- ELSE (er.target_revision <= ${e.raw(`${i}.revision`)} OR er.target_revision = '')
230
- END
231
- )
232
- )
233
- LIMIT 10
234
- ) o
235
- ),
236
- json_array()
237
- )
238
- `}}function j($){return $.replace(/[^a-zA-Z0-9._-]/g,"")}export{ne as CatalogEntitiesLocalReadRepository};
120
+ `));_?.rows&&(s[i]=_.rows.map(a=>({value:a.value,count:Number(a.count)||0})).filter(a=>a.value))}async#l(){try{await this.#e.client.run(e`DROP TABLE IF EXISTS temp_combined_entities`),await this.#e.client.run(e`DROP TABLE IF EXISTS temp_filtered_entities`),await this.#e.client.run(e`DROP TABLE IF EXISTS temp_combined_relations`)}catch(i){b.error("Error cleaning up temp tables:",i)}}}function $(A){return A.replace(/[^a-zA-Z0-9._-]/g,"")}export{j as CatalogEntitiesLocalReadRepository};
@@ -1 +1 @@
1
- import{and as d,desc as k,eq as u,isNull as v,or as R,sql as D}from"drizzle-orm";import{logger as h}from"../../../../../tools/notifiers/logger.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{sha1 as T}from"../../../../../utils/crypto/sha1.js";import{isWebView as O}from"../../../../../../utils/env/is-web-view.js";import{VERSION_NOT_SPECIFIED as S}from"@redocly/theme/core/constants";import{createEntityDbRecord as x}from"../../mappers/create-entity-db-record.js";import{createEntityRelationDbRecordFromFileSchema as I}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{entitiesTable as e}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as o}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{convertFilterToWhereCondition as K}from"../../../../../providers/database/pagination/filter.js";import{compareVersionsDescending as L}from"../../../utils/version-compare.js";const w=15;class Q{#e;#t;#r;constructor(t,r,s){this.#e=t,this.#t=r,this.#r=s}async createEntity({entity:t,fileHash:r,sourceFile:s,revision:a=new Date().toISOString(),isRootEntity:i=!1,isDeleted:n=!1}){try{const{relations:c=[],...l}=t,y=T(JSON.stringify(l)),f=t.version??S;if(await this.#s(t.key,f,y,i,n))return{result:"skipped",entityKey:t.key};const{shouldBeCurrent:g,shouldBeDefaultVersion:m}=await this.#o(t.key,f),E=x({entity:{...t,revision:a,hash:y,isCurrent:g,isDefaultVersion:m,isDeleted:n,version:f},sourceFile:s,organizationId:this.#t,projectId:this.#r,source:"file",fileHash:r}),{key:A,source:$,...F}=E;if(await this.#a({key:A,isCurrent:g,isDefaultVersion:m}),O())return await this.#c(E,c),{result:"created",entityKey:t.key,entityRevision:a,entityVersion:f};const N=this.#e.client.insert(e).values(E).onConflictDoUpdate({target:[e.key,e.source,e.revision,e.version],set:F}),B=c?.length&&c.length>0?this.#e.client.insert(o).values(c.map(V=>I({relation:V,sourceFile:s,fileHash:r,sourceKey:t.key,sourceVersion:f,sourceRevision:a??null,organizationId:this.#t,projectId:this.#r}))).onConflictDoNothing({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision]}).run():Promise.resolve();return await p([N,B],w,async V=>V),{result:"created",entityKey:t.key,entityRevision:a,entityVersion:f}}catch(c){return console.error(c),h.error("Error adding entity",c),{result:"error",entityKey:t.key}}}async deleteEntity(t){try{return await this.#e.client.delete(e).where(u(e.key,t)),t}catch(r){return h.error("Error deleting entity",r),null}}async deleteEntities(t){try{const r=K(t);if(!r)return!1;const s=await this.#e.client.delete(e).where(r).returning({key:e.key,source:e.source,isCurrent:e.isCurrent,isDefaultVersion:e.isDefaultVersion,version:e.version});if(s.length===0)return!0;const a=s.reduce((i,n)=>((n.isCurrent||n.isDefaultVersion)&&i.add(n.key),i),new Set);if(a.size===0)return!0;await p(Array.from(a),w,async i=>this.#u(i));for(const i of s)await this.#e.client.delete(o).where(R(d(u(o.sourceKey,i.key),...i.version?[u(o.sourceVersion,i.version)]:[v(o.sourceVersion)]),d(u(o.targetKey,i.key),...i.version?[u(o.targetVersion,i.version)]:[v(o.targetVersion)])));return!0}catch(r){return h.error("Error deleting entities",r),!1}}async deleteEntityRelation(t){try{return await this.#e.client.delete(o).where(u(o.id,t)),t}catch{return null}}async softDeleteEntities(t,r,s){try{const a=t.map(n=>{const c={type:n.type,key:n.key,title:n.title,summary:n.summary??void 0,tags:n.tags??void 0,metadata:n.metadata??void 0,git:n.git??void 0,contact:n.contact??void 0,links:n.links??void 0,version:n.version??void 0};return this.createEntity({entity:c,revision:r,sourceFile:n.sourceFile??"",fileHash:s,isDeleted:!0})});return await p(a,w,async n=>n)}catch(a){return h.error("Error soft deleting entities",a),[]}}async deleteEntityRelations(t){try{const r=K(t);return r?(await this.#e.client.delete(o).where(r),!0):!1}catch(r){return h.error("Error deleting entity relations",r),!1}}async upsertEntityRelation(t){if(!t)return null;try{const{sourceKey:r,targetKey:s,sourceVersion:a,targetVersion:i,sourceRevision:n,targetRevision:c,...l}=t,y=await this.#e.client.insert(o).values(t).onConflictDoUpdate({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision],set:l}).returning();return y?.length?y[0]:null}catch(r){return h.error("Error creating entity relation",r),null}}async#s(t,r,s,a,i){if(a||i)return!1;const n=await this.#e.client.select({hash:e.hash,isDeleted:e.isDeleted}).from(e).where(d(u(e.key,t),u(e.source,"file"),r?u(e.version,r):v(e.version))).orderBy(k(e.revision)).limit(1).run();if(n.rows.length===0)return!1;const c=n.rows[0];return c.isDeleted?!1:c.hash===s}async#i(t){return(await this.#e.client.select({version:e.version,isDefaultVersion:e.isDefaultVersion,revision:e.revision,createdAt:e.createdAt}).from(e).where(d(u(e.key,t),u(e.source,"file"))).orderBy(k(e.createdAt)).run()).rows.map(s=>({version:s.version,isDefaultVersion:!!s.isDefaultVersion,revision:s.revision,createdAt:new Date(s.createdAt)}))}#n(t){const r=t.find(i=>i.isDefaultVersion);if(r)return r;const{versionedEntities:s,unversionedEntities:a}=t.reduce((i,n)=>(n.version!==null&&n.version!==S?i.versionedEntities.push(n):i.unversionedEntities.push(n),i),{versionedEntities:[],unversionedEntities:[]});if(s.length>0){s.sort((c,l)=>L(c.version,l.version));const i=s[0].version,n=s.filter(c=>c.version===i);return n.sort((c,l)=>l.createdAt.getTime()-c.createdAt.getTime()),n[0]}return a.length>0?(a.sort((i,n)=>n.createdAt.getTime()-i.createdAt.getTime()),a[0]):null}async#o(t,r){const s=await this.#e.client.select({currentDefaultVersion:D`max(case when ${e.isDefaultVersion} = 1 then ${e.version} else null end)`,versionMatchCount:D`count(case when ${e.version} = ${r} then 1 else null end)`}).from(e).where(d(u(e.key,t),u(e.source,"file"))).get(),a=s?.currentDefaultVersion,i=(s?.versionMatchCount??0)>0;return r&&r===a?{shouldBeCurrent:!0,shouldBeDefaultVersion:!1}:{shouldBeCurrent:!i,shouldBeDefaultVersion:!i}}async#a({key:t,isCurrent:r,isDefaultVersion:s}){if(!r&&!s)return;const a={},i=[];r&&(a.isCurrent=!1,i.push(u(e.isCurrent,!0))),s&&(a.isDefaultVersion=!1,i.push(u(e.isDefaultVersion,!0))),await this.#e.client.update(e).set(a).where(d(u(e.key,t),u(e.source,"file"),R(...i))).run()}#u=async t=>{const r=await this.#i(t),s=this.#n(r);if(!s){h.warn("No latest version found for key",t);return}await this.#e.client.update(e).set({isDefaultVersion:!0,isCurrent:!0}).where(d(u(e.key,t),u(e.revision,s.revision),s.version?u(e.version,s.version):v(e.version))).run()};async#c(t,r){const{key:s,source:a,version:i,isDefaultVersion:n,...c}=t,f=(await this.#e.client.select({id:e.id}).from(e).where(d(u(e.key,s),u(e.source,a??"file"),i?u(e.version,i):v(e.version))).limit(1).run()).rows.length>0?this.#e.client.update(e).set(c).where(d(u(e.key,s),u(e.source,a??"file"),i?u(e.version,i):v(e.version))).run():this.#e.client.insert(e).values(t).run(),C=r?.map(g=>{const m=I({relation:g,sourceFile:t.sourceFile??"",fileHash:t.fileHash??"",sourceKey:t.key,sourceVersion:t.version??null,sourceRevision:t.revision??null,organizationId:this.#t,projectId:this.#r});return this.#e.client.insert(o).values(m).onConflictDoUpdate({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision],set:m}).run()})??[];await p([f,...C],w,async g=>g)}async updateEntityScorecardsStatus(t,r){try{return(await this.#e.client.update(e).set({scorecardsStatus:r}).where(u(e.id,t)).returning()).length>0}catch(s){return h.error("Error updating entity scorecards status",s),!1}}async updateEntityScorecardsStatusIfCalculating(t,r){try{return(await this.#e.client.update(e).set({scorecardsStatus:r}).where(D`${e.id} = ${t} AND ${e.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}catch(s){return h.error("Error updating entity scorecards status if calculating",s),!1}}}export{Q as CatalogEntitiesLocalWriteRepository};
1
+ import{and as d,desc as k,eq as u,isNull as v,or as R,sql as D}from"drizzle-orm";import{logger as h}from"../../../../../tools/notifiers/logger.js";import{promiseMapLimit as p}from"../../../../../utils/async/promise-map-limit.js";import{sha1 as T}from"../../../../../utils/crypto/sha1.js";import{isWebView as O}from"../../../../../../utils/env/is-web-view.js";import{VERSION_NOT_SPECIFIED as S}from"@redocly/theme/core/constants";import{createEntityDbRecord as x}from"../../mappers/create-entity-db-record.js";import{createEntityRelationDbRecordFromFileSchema as I}from"../../mappers/create-entity-relation-db-record-from-file-schema.js";import{entitiesTable as e}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-table.js";import{entitiesRelationsTable as o}from"../../../../../providers/database/databases/catalog-sqlite/schemas/entities-relations-table.js";import{convertFilterToWhereCondition as K}from"../../../../../providers/database/pagination/filter.js";import{compareVersionsDescending as L}from"../../../utils/version-compare.js";const w=15;class Q{#e;#t;#r;constructor(t,r,s){this.#e=t,this.#t=r,this.#r=s}async createEntity({entity:t,fileHash:r,sourceFile:s,revision:a=new Date().toISOString(),isRootEntity:i=!1,isDeleted:n=!1}){try{const{relations:c=[],...l}=t,y=T(JSON.stringify(l)),f=t.version??S;if(await this.#s(t.key,f,y,i,n))return{result:"skipped",entityKey:t.key};const{shouldBeCurrent:g,shouldBeDefaultVersion:m}=await this.#o(t.key,f),E=x({entity:{...t,revision:a,hash:y,isCurrent:g,isDefaultVersion:m,isDeleted:n,version:f},sourceFile:s,organizationId:this.#t,projectId:this.#r,source:"file",fileHash:r}),{key:A,source:U,...F}=E;if(await this.#a({key:A,isCurrent:g,isDefaultVersion:m}),O())return await this.#c(E,c),{result:"created",entityKey:t.key,entityRevision:a,entityVersion:f};const N=this.#e.client.insert(e).values(E).onConflictDoUpdate({target:[e.key,e.source,e.revision,e.version],set:F}),B=c?.length&&c.length>0?this.#e.client.insert(o).values(c.map(V=>I({relation:V,sourceFile:s,fileHash:r,sourceKey:t.key,sourceVersion:f,sourceRevision:a??null,organizationId:this.#t,projectId:this.#r}))).onConflictDoNothing({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision]}).run():Promise.resolve();return await p([N,B],w,async V=>V),{result:"created",entityKey:t.key,entityRevision:a,entityVersion:f}}catch(c){return h.error("Error adding entity",c),{result:"error",entityKey:t.key}}}async deleteEntity(t){try{return await this.#e.client.delete(e).where(u(e.key,t)),t}catch(r){return h.error("Error deleting entity",r),null}}async deleteEntities(t){try{const r=K(t);if(!r)return!1;const s=await this.#e.client.delete(e).where(r).returning({key:e.key,source:e.source,isCurrent:e.isCurrent,isDefaultVersion:e.isDefaultVersion,version:e.version});if(s.length===0)return!0;const a=s.reduce((i,n)=>((n.isCurrent||n.isDefaultVersion)&&i.add(n.key),i),new Set);if(a.size===0)return!0;await p(Array.from(a),w,async i=>this.#u(i));for(const i of s)await this.#e.client.delete(o).where(R(d(u(o.sourceKey,i.key),...i.version?[u(o.sourceVersion,i.version)]:[v(o.sourceVersion)]),d(u(o.targetKey,i.key),...i.version?[u(o.targetVersion,i.version)]:[v(o.targetVersion)])));return!0}catch(r){return h.error("Error deleting entities",r),!1}}async deleteEntityRelation(t){try{return await this.#e.client.delete(o).where(u(o.id,t)),t}catch{return null}}async softDeleteEntities(t,r,s){try{const a=t.map(n=>{const c={type:n.type,key:n.key,title:n.title,summary:n.summary??void 0,tags:n.tags??void 0,metadata:n.metadata??void 0,git:n.git??void 0,contact:n.contact??void 0,links:n.links??void 0,version:n.version??void 0};return this.createEntity({entity:c,revision:r,sourceFile:n.sourceFile??"",fileHash:s,isDeleted:!0})});return await p(a,w,async n=>n)}catch(a){return h.error("Error soft deleting entities",a),[]}}async deleteEntityRelations(t){try{const r=K(t);return r?(await this.#e.client.delete(o).where(r),!0):!1}catch(r){return h.error("Error deleting entity relations",r),!1}}async upsertEntityRelation(t){if(!t)return null;try{const{sourceKey:r,targetKey:s,sourceVersion:a,targetVersion:i,sourceRevision:n,targetRevision:c,...l}=t,y=await this.#e.client.insert(o).values(t).onConflictDoUpdate({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision],set:l}).returning();return y?.length?y[0]:null}catch(r){return h.error("Error creating entity relation",r),null}}async#s(t,r,s,a,i){if(a||i)return!1;const n=await this.#e.client.select({hash:e.hash,isDeleted:e.isDeleted}).from(e).where(d(u(e.key,t),u(e.source,"file"),r?u(e.version,r):v(e.version))).orderBy(k(e.revision)).limit(1).run();if(n.rows.length===0)return!1;const c=n.rows[0];return c.isDeleted?!1:c.hash===s}async#i(t){return(await this.#e.client.select({version:e.version,isDefaultVersion:e.isDefaultVersion,revision:e.revision,createdAt:e.createdAt}).from(e).where(d(u(e.key,t),u(e.source,"file"))).orderBy(k(e.createdAt)).run()).rows.map(s=>({version:s.version,isDefaultVersion:!!s.isDefaultVersion,revision:s.revision,createdAt:new Date(s.createdAt)}))}#n(t){const r=t.find(i=>i.isDefaultVersion);if(r)return r;const{versionedEntities:s,unversionedEntities:a}=t.reduce((i,n)=>(n.version!==null&&n.version!==S?i.versionedEntities.push(n):i.unversionedEntities.push(n),i),{versionedEntities:[],unversionedEntities:[]});if(s.length>0){s.sort((c,l)=>L(c.version,l.version));const i=s[0].version,n=s.filter(c=>c.version===i);return n.sort((c,l)=>l.createdAt.getTime()-c.createdAt.getTime()),n[0]}return a.length>0?(a.sort((i,n)=>n.createdAt.getTime()-i.createdAt.getTime()),a[0]):null}async#o(t,r){const s=await this.#e.client.select({currentDefaultVersion:D`max(case when ${e.isDefaultVersion} = 1 then ${e.version} else null end)`,versionMatchCount:D`count(case when ${e.version} = ${r} then 1 else null end)`}).from(e).where(d(u(e.key,t),u(e.source,"file"))).get(),a=s?.currentDefaultVersion,i=(s?.versionMatchCount??0)>0;return r&&r===a?{shouldBeCurrent:!0,shouldBeDefaultVersion:!1}:{shouldBeCurrent:!i,shouldBeDefaultVersion:!i}}async#a({key:t,isCurrent:r,isDefaultVersion:s}){if(!r&&!s)return;const a={},i=[];r&&(a.isCurrent=!1,i.push(u(e.isCurrent,!0))),s&&(a.isDefaultVersion=!1,i.push(u(e.isDefaultVersion,!0))),await this.#e.client.update(e).set(a).where(d(u(e.key,t),u(e.source,"file"),R(...i))).run()}#u=async t=>{const r=await this.#i(t),s=this.#n(r);if(!s){h.warn("No latest version found for key",t);return}await this.#e.client.update(e).set({isDefaultVersion:!0,isCurrent:!0}).where(d(u(e.key,t),u(e.revision,s.revision),s.version?u(e.version,s.version):v(e.version))).run()};async#c(t,r){const{key:s,source:a,version:i,isDefaultVersion:n,...c}=t,f=(await this.#e.client.select({id:e.id}).from(e).where(d(u(e.key,s),u(e.source,a??"file"),i?u(e.version,i):v(e.version))).limit(1).run()).rows.length>0?this.#e.client.update(e).set(c).where(d(u(e.key,s),u(e.source,a??"file"),i?u(e.version,i):v(e.version))).run():this.#e.client.insert(e).values(t).onConflictDoUpdate({target:[e.key,e.source,e.revision,e.version],set:c}).run(),C=r?.map(g=>{const m=I({relation:g,sourceFile:t.sourceFile??"",fileHash:t.fileHash??"",sourceKey:t.key,sourceVersion:t.version??null,sourceRevision:t.revision??null,organizationId:this.#t,projectId:this.#r});return this.#e.client.insert(o).values(m).onConflictDoUpdate({target:[o.sourceKey,o.targetKey,o.sourceVersion,o.targetVersion,o.sourceRevision,o.targetRevision],set:m}).run()})??[];await p([f,...C],w,async g=>g)}async updateEntityScorecardsStatus(t,r){try{return(await this.#e.client.update(e).set({scorecardsStatus:r}).where(u(e.id,t)).returning()).length>0}catch(s){return h.error("Error updating entity scorecards status",s),!1}}async updateEntityScorecardsStatusIfCalculating(t,r){try{return(await this.#e.client.update(e).set({scorecardsStatus:r}).where(D`${e.id} = ${t} AND ${e.scorecardsStatus} = 'CALCULATING'`).returning()).length>0}catch(s){return h.error("Error updating entity scorecards status if calculating",s),!1}}}export{Q as CatalogEntitiesLocalWriteRepository};
@@ -0,0 +1,77 @@
1
+ import type { EntityRelationDtoSchema } from '../schemas/dto-schemas.js';
2
+ /**
3
+ * Normalized relation data structure.
4
+ * Derived from EntityRelationDtoSchema with required (but nullable) version/revision fields.
5
+ */
6
+ export type NormalizedRelation = Pick<EntityRelationDtoSchema, 'type' | 'sourceKey' | 'targetKey'> & {
7
+ /**
8
+ * Source entity version (may be swapped from original)
9
+ */
10
+ sourceVersion: string | null;
11
+ /**
12
+ * Target entity version (may be swapped from original)
13
+ */
14
+ targetVersion: string | null;
15
+ /**
16
+ * Source entity revision (may be swapped from original)
17
+ */
18
+ sourceRevision: string | null;
19
+ /**
20
+ * Target entity revision (may be swapped from original)
21
+ */
22
+ targetRevision: string | null;
23
+ };
24
+ /**
25
+ * Input relation data structure.
26
+ * Core relation fields from EntityRelationDtoSchema.
27
+ */
28
+ export type RelationInput = Pick<EntityRelationDtoSchema, 'type' | 'sourceKey' | 'targetKey' | 'sourceVersion' | 'targetVersion' | 'sourceRevision' | 'targetRevision'>;
29
+ /**
30
+ * Normalizes a relation to its canonical form.
31
+ *
32
+ * This function:
33
+ * 1. Looks up the canonical relation type in the normalization map
34
+ * 2. If the relation type differs from its canonical form, swaps source and target (including versions and revisions)
35
+ * 3. Uses the canonical relation type
36
+ * 4. For unknown relation types, passes through without modification
37
+ *
38
+ * @param relation - The relation to normalize
39
+ * @returns Normalized relation with canonical type and potentially swapped entities
40
+ *
41
+ * @example
42
+ * // Normalize ownedBy -> owns with swapped entities
43
+ * normalizeRelation({
44
+ * type: 'ownedBy',
45
+ * sourceKey: 'api-1',
46
+ * targetKey: 'team-1',
47
+ * sourceVersion: 'v1',
48
+ * targetVersion: null,
49
+ * sourceRevision: null,
50
+ * targetRevision: null,
51
+ * })
52
+ * // Returns:
53
+ * // {
54
+ * // type: 'owns',
55
+ * // sourceKey: 'team-1', // swapped
56
+ * // targetKey: 'api-1', // swapped
57
+ * // sourceVersion: null, // swapped
58
+ * // targetVersion: 'v1', // swapped
59
+ * // sourceRevision: null,
60
+ * // targetRevision: null,
61
+ * // }
62
+ *
63
+ * @example
64
+ * // Canonical type passes through unchanged
65
+ * normalizeRelation({
66
+ * type: 'owns',
67
+ * sourceKey: 'team-1',
68
+ * targetKey: 'api-1',
69
+ * sourceVersion: null,
70
+ * targetVersion: 'v1',
71
+ * sourceRevision: null,
72
+ * targetRevision: null,
73
+ * })
74
+ * // Returns: same as input
75
+ */
76
+ export declare function normalizeRelation(relation: RelationInput): NormalizedRelation;
77
+ //# sourceMappingURL=normalize-relation.d.ts.map
@@ -0,0 +1 @@
1
+ import{RELATION_NORMALIZATION_MAP as r}from"../database/constants/relation-normalization.js";function o(e){const s=r[e.type];return s?e.type===s?{type:s,sourceKey:e.sourceKey,targetKey:e.targetKey,sourceVersion:e.sourceVersion??null,targetVersion:e.targetVersion??null,sourceRevision:e.sourceRevision??null,targetRevision:e.targetRevision??null}:{type:s,sourceKey:e.targetKey,targetKey:e.sourceKey,sourceVersion:e.targetVersion??null,targetVersion:e.sourceVersion??null,sourceRevision:e.targetRevision??null,targetRevision:e.sourceRevision??null}:{type:e.type,sourceKey:e.sourceKey,targetKey:e.targetKey,sourceVersion:e.sourceVersion??null,targetVersion:e.targetVersion??null,sourceRevision:e.sourceRevision??null,targetRevision:e.targetRevision??null}}export{o as normalizeRelation};
@@ -1,9 +1,17 @@
1
- import{getLineColLocation as f,isAbsoluteUrl as s}from"@redocly/openapi-core";import{getCodeframe as l}from"@redocly/openapi-core";import m from"node:path";import{gray as u}from"../../tools/notifiers/helpers/colors.js";function h(r,n){return`Config validation error:
1
+ import{getLineColLocation as u,isAbsoluteUrl as m}from"@redocly/openapi-core";import{getCodeframe as g}from"@redocly/openapi-core";import l from"node:path";import{gray as i}from"../../tools/notifiers/helpers/colors.js";function d(a,s){return`Config validation error:
2
2
 
3
- `+i(r);function i(e){const o=e.location[0],a=s(o.source.absoluteRef)?o.source.absoluteRef:m.relative(n,o.source.absoluteRef),t=f(o),c=o.pointer?u(`at ${o.pointer}`):"";return`${`${a}:${t.start.line}:${t.start.col}`} ${c}
3
+ `+c(a);function c(o){const t=o.location[0];if(!t)return o.message;if(!t.source){const n=t.pointer??"";return`${o.message}${n?` at ${n}`:""}`}const e=t.source?.absoluteRef,r=e&&m(e)?e:e?l.relative(s,e):"config";try{if(typeof t.source.getAst!="function"){const $=t.pointer?i(`at ${t.pointer}`):"";return`${r} ${$}
4
4
 
5
- ${e.message}
5
+ ${o.message}
6
6
 
7
- `+l(t,!0)+`
7
+ `}const n=u(t),f=t.pointer?i(`at ${t.pointer}`):"";return`${`${r}:${n.start.line}:${n.start.col}`} ${f}
8
8
 
9
- `}}export{h as formatConfigProblem};
9
+ ${o.message}
10
+
11
+ `+g(n,!0)+`
12
+
13
+ `}catch{const n=t.pointer?i(`at ${t.pointer}`):"";return`${r} ${n}
14
+
15
+ ${o.message}
16
+
17
+ `}}}export{d as formatConfigProblem};
@@ -1 +1 @@
1
- import h from"path";import{lintConfig as C,loadConfig as P,createConfigTypes as T}from"@redocly/openapi-core";import{deepMerge as b}from"../../../../../utils/object/deep-merge.js";import{readEnvVariable as R}from"../../../../utils/envs/read-env-variable.js";import{logger as a}from"../../../../tools/notifiers/logger.js";import{safeParsePartial as A}from"../../safe-parse.js";import{formatConfigProblem as x}from"../../format-error.js";import{ExternalResolver as E}from"../../../../fs/utils/external-ref-resolver.js";import{resolveMutuallyExclusiveProps as $}from"../../resolve-mutual-exclusion.js";async function W(o,r,t,c){const u=r.getFileInfo(o)?.realRelativePath||o;async function y(){const i=new E(r),l=h.join(r.cwd,u),n=await P({configPath:l,externalRefResolver:i}),f=await c(n.resolvedConfig);if(f===void 0)return n.resolvedConfig;const g=[...n.document?.source?await C({config:n,externalConfigTypes:T(f,n)}):[],...n.document?.source?$(n.resolvedConfig,n.document?.source):[]];if(g.length>0)for(const w of g)t(new Error(x(w,r.cwd)));return n.resolvedConfig}const s=await r.exists(o)?await y():{},v=j(s),p=await c(s);"residency"in s&&s.residency&&typeof s.residency=="string"&&(/^https?:\/\/.*/.test(s.residency)||t(new Error(`Invalid residency URL: "${s.residency}". Residency must start with "http://" or "https://".`)));let d=p?A(p,s):s;const{env:m}=d;if(m){const i=R("REDOCLY_ENV")||"development",l=m[i]||{};d=b(d,l)}const e=M(d,v);if(e.imports&&e.imports.length>0){a.warn("The 'imports' property is deprecated. Please use 'plugins' property instead.");const i=new Set([...e.plugins||[],...e.imports.map(l=>h.posix.join(l,"plugin.js"))]);e.plugins=Array.from(i),delete e.imports}if(e.catalog&&(a.warn("The 'catalog' property is deprecated. Please use 'catalogClassic' property instead."),e.catalogClassic={...e.catalog},delete e.catalog),e.scorecard&&(a.warn("The 'scorecard' property is deprecated. Please use 'scorecardClassic' property instead."),e.scorecardClassic=e.scorecard,delete e.scorecard),e.search?.ai){a.warn("The 'search.ai' property is deprecated. Please use 'aiAssistant' property instead.");const i={...e.search?.ai,...e.aiAssistant,suggestions:e.aiAssistant?.suggestions?.length?e.aiAssistant.suggestions:e.search?.ai.suggestions||[]};e.aiAssistant=i,delete e.search.ai}return e}function j(o){if(!o.theme)return[];a.warn("The 'theme' property in redocly.yaml is deprecated. Please move all of the properties from 'theme' to the root of the config.");const r=[];for(const t of Object.keys(o.theme))o[t]==null?r.push(t):a.warn(`Detected both '${t}' and 'theme.${t}' properties in redocly.yaml. The 'theme.${t}' property will be ignored and needs to be removed or merged into the '${t}'.`);return r}function M(o,r){if(!o.theme||r.length===0)return o;const t={...o};for(const c of r)t[c]=o.theme[c];return delete t.theme,t}export{W as readAndValidateConfig};
1
+ import w from"path";import{lintConfig as R,loadConfig as C,createConfigTypes as T}from"@redocly/openapi-core";import{deepMerge as U}from"../../../../../utils/object/deep-merge.js";import{readEnvVariable as A}from"../../../../utils/envs/read-env-variable.js";import{logger as l}from"../../../../tools/notifiers/logger.js";import{safeParsePartial as L}from"../../safe-parse.js";import{formatConfigProblem as $}from"../../format-error.js";import{ExternalResolver as q}from"../../../../fs/utils/external-ref-resolver.js";import{resolveMutuallyExclusiveProps as x}from"../../resolve-mutual-exclusion.js";function f(o,e,t,r){e in o&&o[e]&&typeof o[e]=="string"&&(/^https?:\/\/.*/.test(o[e])||r(new Error(`Invalid ${t} URL: "${o[e]}". ${t} must start with "http://" or "https://".`)))}function j(o,e){if("access"in o&&o.access&&typeof o.access=="object"){const t=o.access;f(t,"logoutReturnUrl","access.logoutReturnUrl",e),f(t,"residency","access.residency",e);const r=["requiresLogin","logoutReturnUrl","residency","sso","rbac"];for(const n of r)n in t&&t[n]!==void 0&&n in o&&o[n]!==void 0&&e(new Error(`Property '${n}' is defined both at root level and in 'access' object. Please use 'access.${n}' to define this configuration.`))}}async function z(o,e,t,r){const m=e.getFileInfo(o)?.realRelativePath||o;async function d(){const c=new q(e),u=w.join(e.cwd,m),i=await C({configPath:u,externalRefResolver:c}),y=await r(i.resolvedConfig);if(y===void 0)return i.resolvedConfig;const v=[...i.document?.source?await R({config:i,externalConfigTypes:T(y,i)}):[],...i.document?.source?x(i.resolvedConfig,i.document?.source):[]];if(v.length>0)for(const b of v)t(new Error($(b,e.cwd)));return i.resolvedConfig}const a=await e.exists(o)?await d():{},P=E(a),g=await r(a);f(a,"residency","Residency",t),f(a,"logoutReturnUrl","Logout return URL",t),j(a,t);let p=g?L(g,a):a;const{env:h}=p;if(h){const c=A("REDOCLY_ENV")||"development",u=h[c]||{};p=U(p,u)}const s=M(p,P);if(s.imports&&s.imports.length>0){l.warn("The 'imports' property is deprecated. Please use 'plugins' property instead.");const c=new Set([...s.plugins||[],...s.imports.map(u=>w.posix.join(u,"plugin.js"))]);s.plugins=Array.from(c),delete s.imports}if(s.catalog&&(l.warn("The 'catalog' property is deprecated. Please use 'catalogClassic' property instead."),s.catalogClassic={...s.catalog},delete s.catalog),s.scorecard&&(l.warn("The 'scorecard' property is deprecated. Please use 'scorecardClassic' property instead."),s.scorecardClassic=s.scorecard,delete s.scorecard),s.search?.ai){l.warn("The 'search.ai' property is deprecated. Please use 'aiAssistant' property instead.");const c={...s.search?.ai,...s.aiAssistant,suggestions:s.aiAssistant?.suggestions?.length?s.aiAssistant.suggestions:s.search?.ai.suggestions||[]};s.aiAssistant=c,delete s.search.ai}return k(s)}function k(o){const e="access"in o?o.access:void 0;if(!e||typeof e!="object")return o;const t={...o},r=[{name:"requiresLogin",type:"boolean"},{name:"logoutReturnUrl",type:"string"},{name:"residency",type:"string"},{name:"sso",type:"string | string[]"},{name:"rbac",type:"object"}],n=[];for(const m of r){const d=m.name;d in t&&t[d]!==void 0&&!(d in e)&&n.push(d)}return n.length>0&&l.warn(`The following properties at root level are deprecated: ${n.join(", ")}. Please move them to the 'access' object.`),"requiresLogin"in e&&e.requiresLogin!==void 0&&(t.requiresLogin=e.requiresLogin),"logoutReturnUrl"in e&&e.logoutReturnUrl!==void 0&&(t.logoutReturnUrl=e.logoutReturnUrl),"residency"in e&&e.residency!==void 0&&(t.residency=e.residency),"sso"in e&&e.sso!==void 0&&(t.sso=e.sso),"rbac"in e&&e.rbac!==void 0&&(t.rbac=e.rbac),delete t.access,t}function E(o){if(!o.theme)return[];l.warn("The 'theme' property in redocly.yaml is deprecated. Please move all of the properties from 'theme' to the root of the config.");const e=[];for(const t of Object.keys(o.theme))o[t]==null?e.push(t):l.warn(`Detected both '${t}' and 'theme.${t}' properties in redocly.yaml. The 'theme.${t}' property will be ignored and needs to be removed or merged into the '${t}'.`);return e}function M(o,e){if(!o.theme||e.length===0)return o;const t={...o};for(const r of e)t[r]=o.theme[r];return delete t.theme,t}export{z as readAndValidateConfig};
@@ -7,6 +7,7 @@ export declare function applyFilter<T extends SQLiteSelect | SQLiteDelete>(sqlBu
7
7
  };
8
8
  export declare function convertFilterToWhereCondition(filter?: Filter): SQL | undefined;
9
9
  export declare function getFirstFilterFieldValue(filter: Filter | undefined, fieldName: string): FilterClause['value'] | undefined;
10
+ export declare function getAllFilterFieldValues(filter: Filter | undefined, fieldName: string): string[];
10
11
  export declare function parseFilterQuery(query?: string, availableFields?: string[], fieldsTransformations?: Record<string, string>): Filter | undefined;
11
12
  export declare function excludeFieldsFromFilter(filter: Filter | undefined, fieldsToExclude: string[]): Filter | undefined;
12
13
  //# sourceMappingURL=filter.d.ts.map
@@ -1 +1 @@
1
- import{and as O,between as R,eq as T,inArray as C,isNotNull as L,isNull as I,like as v,ne as k,notBetween as y,notInArray as W,notLike as b,or as d,sql as a}from"drizzle-orm";import{isFilterCondition as F}from"./types.js";import{transformToSnakeCase as q}from"./utils/transform-to-snake-case.js";import{isFieldAllowed as D}from"./utils/field-pattern-matcher.js";import{OPERATORS as h}from"./constants.js";const U=["domains","owners"],P=["tags"];function z(t,e){const o=J(e);return o?{sqlBuilder:t,whereCondition:o}:{sqlBuilder:t,whereCondition:null}}function J(t){if(t)return F(t)?j(t):x(t)}const j=t=>{const{op:e,conditions:o}=t,n=[];for(const l of o){if(F(l)){const r=j(l);r&&n.push(r);continue}const i=x(l);i&&n.push(i)}return e===h.AND?O(...n):d(...n)},H=(t,e)=>e.length>1?`$.${e.slice(1).join(".")}.${t}`:`$.${t}`,m=({operator:t,value:e,field:o,parentFields:n,negation:l})=>{const i=a.identifier(n[0]),r=H(o,n);switch(t){case"equal":return l?a`(json_extract(${i}, ${r}) != ${e} OR json_extract(${i}, ${r}) IS NULL)`:a`json_extract(${i}, ${r}) == ${e}`;case"equalNull":return l?a`json_extract(${i}, ${r}) IS NOT NULL`:a`json_extract(${i}, ${r}) IS NULL`;case"contains":return l?a`(json_extract(${i}, ${r}) NOT LIKE ${e} OR json_extract(${i}, ${r}) IS NULL)`:a`json_extract(${i}, ${r}) LIKE ${e}`;case"in":if(Array.isArray(e)){const s=e.map(c=>a`json_extract(${i}, ${r}) == ${c}`);return l?a`(NOT (${d(...s)}) OR json_extract(${i}, ${r}) IS NULL)`:d(...s)}break;case"between":if(Array.isArray(e)&&e.length===2)return l?a`(NOT (json_extract(${i}, ${r}) BETWEEN ${e[0]} AND ${e[1]}) OR json_extract(${i}, ${r}) IS NULL)`:a`json_extract(${i}, ${r}) BETWEEN ${e[0]} AND ${e[1]}`;break;default:throw new Error(`Unsupported operator: ${t} for json path`)}};function G(t,e){if(!t)return;const o=l=>{if(F(l)){for(const i of l.conditions){const r=o(i);if(r!==void 0)return r}return}if(l.field===e)return l.value},n=o(t);return typeof n=="string"?n.trim()===""?null:n.trim():n??null}const M=t=>{const{field:e,operator:o,value:n,modifier:l,parentFields:i}=t,r=l==="not";if(!i||i.length<1)throw new Error("convertNestedFilterClauseToSql called without parentFields");switch(o){case"equal":return m(n===null?{operator:"equalNull",value:n,field:e,parentFields:i,negation:r}:{operator:o,value:n,field:e,parentFields:i,negation:r});case"contains":if(typeof n=="string"){const s=n.startsWith("%")||n.endsWith("%")?n:`%${n}%`;return m({operator:o,value:s,field:e,parentFields:i,negation:r})}break;case"in":if(Array.isArray(n))return m({operator:o,value:n,field:e,parentFields:i,negation:r});break;case"between":if(Array.isArray(n)&&n.length===2)return m({operator:o,value:n,field:e,parentFields:i,negation:r});break;default:throw new Error(`Unsupported filter operator: ${o}`)}},x=t=>{const{field:e,operator:o,value:n,modifier:l,parentFields:i}=t;if(i&&i.length>=1)return M(t);const r=l==="not",s=a.identifier(e),c=P.includes(e),f=U.includes(e);switch(o){case"equal":return n===null?r?L(s):I(s):f?r?a`NOT EXISTS (SELECT 1 FROM json_each(${s}) WHERE json_extract(json_each.value, '$.key') = ${n})`:a`EXISTS (SELECT 1 FROM json_each(${s}) WHERE json_extract(json_each.value, '$.key') = ${n})`:c?r?a`NOT EXISTS (SELECT 1 FROM json_each(${s}) WHERE json_each.value = ${n})`:a`EXISTS (SELECT 1 FROM json_each(${s}) WHERE json_each.value = ${n})`:r?k(s,n):T(s,n);case"contains":if(typeof n=="string"){const u=n.startsWith("%")||n.endsWith("%")?n:`%${n}%`;return r?b(s,u):v(s,u)}break;case"in":if(Array.isArray(n)){if(f){const u=n.map(p=>a`EXISTS (SELECT 1 FROM json_each(${s}) WHERE json_extract(json_each.value, '$.key') = ${p})`);return r?a`NOT (${d(...u)})`:d(...u)}else if(c){const u=n.map(p=>a`EXISTS (SELECT 1 FROM json_each(${s}) WHERE json_each.value = ${p})`);return r?a`NOT (${d(...u)})`:d(...u)}return r?W(s,n):C(s,n)}break;case"between":if(Array.isArray(n)&&n.length===2)return r?y(s,n[0],n[1]):R(s,n[0],n[1]);break;default:throw new Error(`Unsupported filter operator: ${o}`)}},A=["'",'"'];function Z(t="",e=[],o={}){if(!t||!t.trim())return;const n=g(t);return w(n,e,o)}function S(t){const e=X(t);if(e.operator===":"){const o=N(e.value,",");if(o.length>1)return{operator:"in",field:e.field,parentFields:e.parentFields,value:o.map(E),modifier:e.modifier};const n=N(e.value,"..");return n.length===2?{operator:"between",field:e.field,parentFields:e.parentFields,value:[E(n[0]),E(n[1])],modifier:e.modifier}:{operator:"equal",field:e.field,parentFields:e.parentFields,value:e.value==="null"?null:E(e.value),modifier:e.modifier}}else if(e.operator==="~")return{operator:"contains",field:e.field,parentFields:e.parentFields,value:E(e.value),modifier:e.modifier};throw new Error(`Unsupported filter clause: ${t}`)}function X(t){const e="^(?<modifier>^-?)",o="(?<field>[a-z][\\w.]*)",n="(?<operator>:|~)",l="(?<value>.+)",i=new RegExp(`^${e}${o}${n}${l}$`,"i"),r=t.match(i);if(!r||!r.groups)throw new Error(`Invalid filter clause: ${t}`);const{operator:s,value:c,modifier:f}=r.groups,u=r.groups.field.split(".");return{field:u.pop(),parentFields:u,operator:s,value:c,modifier:f==="-"?"not":void 0}}function N(t,e){const o=[];let n="",l=!1,i=0;for(let r=0;r<t.length;r++){const s=t[r-1],c=t[r];if(i>0){i--;continue}s!=="\\"&&A.includes(c)&&(l=!l),!l&&t.slice(r,r+e.length)===e?(o.push(n),n="",i=e.length-1):n+=c}return n&&o.push(n),o}function E(t){for(const e of A)if(t.startsWith(e)&&t.endsWith(e))return t.slice(1,-1);return t}function g(t){let e="",o=0,n=!1,l="";const i=[];for(let s=0;s<t.length;s++){const c=t[s],f=t[s+1];if(['"',"'"].includes(c)&&(s===0||t[s-1]!=="\\")){if(!n){n=!0,l=c,e+=c;continue}if(c===l){n=!1,l="",e+=c;continue}}if(n){e+=c;continue}if(o===0){const u=f==="A"&&t.slice(s+1,s+5)===`${h.AND} `;if(c===" "&&u){const $=e.trim();$&&(i.push(S($)),e="");const _=g(t.slice(s+5));return{op:h.AND,conditions:[...i,_]}}const p=f==="O"&&t.slice(s+1,s+4)===`${h.OR} `;if(c===" "&&p){const $=e.trim();$&&(i.push(S($)),e="");const _=g(t.slice(s+4));return{op:h.OR,conditions:[...i,_]}}}if(c==="("){const u=e.trim();o===0&&u&&(i.push(S(u)),e=""),o++,e+=c;continue}if(c===")"){if(o--,e+=c,o===0){const u=e.slice(1,-1);i.push(g(u)),e=""}continue}e+=c}const r=e.trim();return r&&i.push(S(r)),i.length===1?i[0]:{op:h.AND,conditions:i}}function w(t,e,o){if("field"in t){const n=t.parentFields?.length?`${t.parentFields.join(".")}.${t.field}`:t.field;if(!D(n,e))throw new Error(`Invalid filter field: ${n}`);const l=t.parentFields?.length?t.field:o[t.field]||q(t.field);return{...t,field:l}}else return{...t,conditions:t.conditions.map(n=>w(n,e,o))}}function ee(t,e){if(!t)return;const o=i=>e.includes(i),n=i=>{if(F(i)){const r=i.conditions.map(n).filter(s=>s!==null);return r.length===0?null:r.length===1?r[0]:{...i,conditions:r}}else return o(i.field)?null:i};return n(t)||void 0}export{z as applyFilter,J as convertFilterToWhereCondition,ee as excludeFieldsFromFilter,G as getFirstFilterFieldValue,Z as parseFilterQuery};
1
+ import{and as O,between as R,eq as T,inArray as C,isNotNull as v,isNull as L,like as I,ne as y,notBetween as k,notInArray as W,notLike as b,or as d,sql as u}from"drizzle-orm";import{isFilterCondition as m}from"./types.js";import{transformToSnakeCase as q}from"./utils/transform-to-snake-case.js";import{isFieldAllowed as D}from"./utils/field-pattern-matcher.js";import{OPERATORS as h}from"./constants.js";const U=["domains","owners"],P=["tags"];function z(t,e){const o=J(e);return o?{sqlBuilder:t,whereCondition:o}:{sqlBuilder:t,whereCondition:null}}function J(t){if(t)return m(t)?A(t):j(t)}const A=t=>{const{op:e,conditions:o}=t,n=[];for(const s of o){if(m(s)){const i=A(s);i&&n.push(i);continue}const r=j(s);r&&n.push(r)}return e===h.AND?O(...n):d(...n)},H=(t,e)=>e.length>1?`$.${e.slice(1).join(".")}.${t}`:`$.${t}`,E=({operator:t,value:e,field:o,parentFields:n,negation:s})=>{const r=u.identifier(n[0]),i=H(o,n);switch(t){case"equal":return s?u`(json_extract(${r}, ${i}) != ${e} OR json_extract(${r}, ${i}) IS NULL)`:u`json_extract(${r}, ${i}) == ${e}`;case"equalNull":return s?u`json_extract(${r}, ${i}) IS NOT NULL`:u`json_extract(${r}, ${i}) IS NULL`;case"contains":return s?u`(json_extract(${r}, ${i}) NOT LIKE ${e} OR json_extract(${r}, ${i}) IS NULL)`:u`json_extract(${r}, ${i}) LIKE ${e}`;case"in":if(Array.isArray(e)){const l=e.map(c=>u`json_extract(${r}, ${i}) == ${c}`);return s?u`(NOT (${d(...l)}) OR json_extract(${r}, ${i}) IS NULL)`:d(...l)}break;case"between":if(Array.isArray(e)&&e.length===2)return s?u`(NOT (json_extract(${r}, ${i}) BETWEEN ${e[0]} AND ${e[1]}) OR json_extract(${r}, ${i}) IS NULL)`:u`json_extract(${r}, ${i}) BETWEEN ${e[0]} AND ${e[1]}`;break;default:throw new Error(`Unsupported operator: ${t} for json path`)}};function G(t,e){if(!t)return;const o=s=>{if(m(s)){for(const r of s.conditions){const i=o(r);if(i!==void 0)return i}return}if(s.field===e)return s.value},n=o(t);return typeof n=="string"?n.trim()===""?null:n.trim():n??null}function Z(t,e){if(!t)return[];const o=[],n=s=>{if(m(s)){for(const r of s.conditions)n(r);return}if(s.field===e){if(Array.isArray(s.value))o.push(...s.value.map(r=>String(r).trim()).filter(r=>r!==""));else if(s.value!==null&&s.value!==void 0){const r=String(s.value).trim();r!==""&&o.push(r)}}};return n(t),o}const M=t=>{const{field:e,operator:o,value:n,modifier:s,parentFields:r}=t,i=s==="not";if(!r||r.length<1)throw new Error("convertNestedFilterClauseToSql called without parentFields");switch(o){case"equal":return E(n===null?{operator:"equalNull",value:n,field:e,parentFields:r,negation:i}:{operator:o,value:n,field:e,parentFields:r,negation:i});case"contains":if(typeof n=="string"){const l=n.startsWith("%")||n.endsWith("%")?n:`%${n}%`;return E({operator:o,value:l,field:e,parentFields:r,negation:i})}break;case"in":if(Array.isArray(n))return E({operator:o,value:n,field:e,parentFields:r,negation:i});break;case"between":if(Array.isArray(n)&&n.length===2)return E({operator:o,value:n,field:e,parentFields:r,negation:i});break;default:throw new Error(`Unsupported filter operator: ${o}`)}},j=t=>{const{field:e,operator:o,value:n,modifier:s,parentFields:r}=t;if(r&&r.length>=1)return M(t);const i=s==="not",l=u.identifier(e),c=P.includes(e),f=U.includes(e);switch(o){case"equal":return n===null?i?v(l):L(l):f?i?u`NOT EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_extract(json_each.value, '$.key') = ${n})`:u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_extract(json_each.value, '$.key') = ${n})`:c?i?u`NOT EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_each.value = ${n})`:u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_each.value = ${n})`:i?y(l,n):T(l,n);case"contains":if(typeof n=="string"){const a=n.startsWith("%")||n.endsWith("%")?n:`%${n}%`;return i?b(l,a):I(l,a)}break;case"in":if(Array.isArray(n)){if(f){const a=n.map(p=>u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_extract(json_each.value, '$.key') = ${p})`);return i?u`NOT (${d(...a)})`:d(...a)}else if(c){const a=n.map(p=>u`EXISTS (SELECT 1 FROM json_each(${l}) WHERE json_each.value = ${p})`);return i?u`NOT (${d(...a)})`:d(...a)}return i?W(l,n):C(l,n)}break;case"between":if(Array.isArray(n)&&n.length===2)return i?k(l,n[0],n[1]):R(l,n[0],n[1]);break;default:throw new Error(`Unsupported filter operator: ${o}`)}},x=["'",'"'];function ee(t="",e=[],o={}){if(!t||!t.trim())return;const n=g(t);return w(n,e,o)}function S(t){const e=X(t);if(e.operator===":"){const o=N(e.value,",");if(o.length>1)return{operator:"in",field:e.field,parentFields:e.parentFields,value:o.map(F),modifier:e.modifier};const n=N(e.value,"..");return n.length===2?{operator:"between",field:e.field,parentFields:e.parentFields,value:[F(n[0]),F(n[1])],modifier:e.modifier}:{operator:"equal",field:e.field,parentFields:e.parentFields,value:e.value==="null"?null:F(e.value),modifier:e.modifier}}else if(e.operator==="~")return{operator:"contains",field:e.field,parentFields:e.parentFields,value:F(e.value),modifier:e.modifier};throw new Error(`Unsupported filter clause: ${t}`)}function X(t){const e="^(?<modifier>^-?)",o="(?<field>[a-z][\\w.]*)",n="(?<operator>:|~)",s="(?<value>.+)",r=new RegExp(`^${e}${o}${n}${s}$`,"i"),i=t.match(r);if(!i||!i.groups)throw new Error(`Invalid filter clause: ${t}`);const{operator:l,value:c,modifier:f}=i.groups,a=i.groups.field.split(".");return{field:a.pop(),parentFields:a,operator:l,value:c,modifier:f==="-"?"not":void 0}}function N(t,e){const o=[];let n="",s=!1,r=0;for(let i=0;i<t.length;i++){const l=t[i-1],c=t[i];if(r>0){r--;continue}l!=="\\"&&x.includes(c)&&(s=!s),!s&&t.slice(i,i+e.length)===e?(o.push(n),n="",r=e.length-1):n+=c}return n&&o.push(n),o}function F(t){for(const e of x)if(t.startsWith(e)&&t.endsWith(e))return t.slice(1,-1);return t}function g(t){let e="",o=0,n=!1,s="";const r=[];for(let l=0;l<t.length;l++){const c=t[l],f=t[l+1];if(['"',"'"].includes(c)&&(l===0||t[l-1]!=="\\")){if(!n){n=!0,s=c,e+=c;continue}if(c===s){n=!1,s="",e+=c;continue}}if(n){e+=c;continue}if(o===0){const a=f==="A"&&t.slice(l+1,l+5)===`${h.AND} `;if(c===" "&&a){const $=e.trim();$&&(r.push(S($)),e="");const _=g(t.slice(l+5));return{op:h.AND,conditions:[...r,_]}}const p=f==="O"&&t.slice(l+1,l+4)===`${h.OR} `;if(c===" "&&p){const $=e.trim();$&&(r.push(S($)),e="");const _=g(t.slice(l+4));return{op:h.OR,conditions:[...r,_]}}}if(c==="("){const a=e.trim();o===0&&a&&(r.push(S(a)),e=""),o++,e+=c;continue}if(c===")"){if(o--,e+=c,o===0){const a=e.slice(1,-1);r.push(g(a)),e=""}continue}e+=c}const i=e.trim();return i&&r.push(S(i)),r.length===1?r[0]:{op:h.AND,conditions:r}}function w(t,e,o){if("field"in t){const n=t.parentFields?.length?`${t.parentFields.join(".")}.${t.field}`:t.field;if(!D(n,e))throw new Error(`Invalid filter field: ${n}`);const s=t.parentFields?.length?t.field:o[t.field]||q(t.field);return{...t,field:s}}else return{...t,conditions:t.conditions.map(n=>w(n,e,o))}}function ne(t,e){if(!t)return;const o=r=>e.includes(r),n=r=>{if(m(r)){const i=r.conditions.map(n).filter(l=>l!==null);return i.length===0?null:i.length===1?i[0]:{...r,conditions:i}}else return o(r.field)?null:r};return n(t)||void 0}export{z as applyFilter,J as convertFilterToWhereCondition,ne as excludeFieldsFromFilter,Z as getAllFilterFieldValues,G as getFirstFilterFieldValue,ee as parseFilterQuery};
@@ -1,4 +1,4 @@
1
- import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";import{SignedXml as B}from"xml-crypto";import F from"xpath";import{deflateSync as J,inflateSync as q}from"fflate";import{createHash as H}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as d,DEFAULT_TEAM_CLAIM_NAME as K}from"@redocly/config";import{AUTH_URL as Q,JWT_SECRET_KEY as _}from"../constants/common.js";import{getPathPrefix as X,withPathPrefix as Y}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as G,REQUIRED_OIDC_SCOPES as D,ServerRoutes as P}from"../../constants/common.js";import{appendQueryParams as Z}from"../../utils/url/append-query-params.js";import{logger as ee}from"../tools/notifiers/logger.js";import{randomString as te}from"../utils/crypto/random-string.js";import{randomUUID as R}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as I,JwtTokenExpired as ne}from"./jwt/types.js";import*as p from"./jwt/jwt.js";import{parseTeamClaimToArray as re}from"../utils/index.js";import{arrayBufferToBase64 as oe,decodeBase64 as j,encodeBase64URL as ae,urlSafeBase64 as v}from"./jwt/encode.js";import{formatSamlCertificate as se}from"./utils/format-saml-certificate.js";function E(e){return e?.type===d.OIDC}function ie(e){return e?.type===d.SAML2}async function ze(e,t){if(E(t))return ce(e,t);if(ie(t))return ue(e,t)}async function ce(e,t){const n=await V(e,t),r=new Set((t.scopes||[]).concat(D)),o=t.authorizationRequestCustomParams||{};return{type:d.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function ue(e,t){return{type:d.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function Be(e,t,n,r,o={}){const a=new Set((r.scopes||[]).concat(D));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:r.clientId,scope:Array.from(a).join(" "),code:t,redirect_uri:N(n),grant_type:"authorization_code",...r.clientSecret?{client_secret:r.clientSecret}:{},...o}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function de(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:a,idpId:s,pkce:i},l,A,w){if(!t||!n||!r||!o)return{loginUrl:void 0};const u=new URL(t),f=w?.redirectUriOverride??`${e}${Y(P.OIDC_CALLBACK)}`,S={state:R(),idpId:s,redirectUri:f,redirectTo:l,branch:w?.branchOverride??me(e),inviteCode:A,source:w?.sourceOverride??"portal"},h={};if(i){const m=v(te(50)),g=v(H("sha256").update(m).digest("base64")),x="S256";u.searchParams.append("code_challenge",g),u.searchParams.append("code_challenge_method",x),h.code_verifier={value:m,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:X()||"/"}}}u.searchParams.append("client_id",n),u.searchParams.append("scope",o),u.searchParams.append("response_type",r),u.searchParams.append("redirect_uri",N(f)),u.searchParams.append("state",ae(JSON.stringify(S)));for(const m in a)a[m]!==void 0&&u.searchParams.append(m,a[m]);return{loginUrl:u.toString(),cookies:h}}function Fe(e,t,n,r){const o=new URL(e);return o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n),o.toString()}async function Je(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return p.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,iat:t,exp:n},_)}async function qe(e){const{header:t,payload:n}=p.decode(e),o=Object.values(I).includes(t.alg)?t.alg:I.HS256;if(await p.verify(e,_,o),!n||typeof n!="object")throw new Error("Malformed authorization code");const a=n;if((a.typ??a.type)!=="mcp_auth_code")throw new Error("Invalid authorization code type");const i=n;if(!i.client_id||!i.redirect_uri)throw new Error("Authorization code missing required claims");if(i.exp&&Date.now()>=i.exp*1e3)throw new Error("Authorization code expired");return i}function He(e){const t=e||W(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${n}`}}function N(e){return e.match(/^https:\/\/preview-[^\.]+--/)?"https://previewauth--"+e.split("--")[1]:e.match(/^(https:\/\/[^\.]+)--[^\.]+\.preview\./)?e.replace(/^(https:\/\/[^\.]+?)--[^\.]+\.preview\./,"$1.previewauth."):e}function me(e){return e.match(/^(https:\/\/[^\.]+)--([^\.]+)\.preview\./)?.[2]||void 0}function le(e){return e.type===d.OIDC}function pe(e){return e.type===d.SAML2}function We(e,t,n,r){return le(e)?de(t,e,n,r):pe(e)?fe(t,e,n,r):{}}function fe(e,t,n,r){const a=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
1
+ import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";import{SignedXml as B}from"xml-crypto";import F from"xpath";import{deflateSync as J,inflateSync as q}from"fflate";import{createHash as H}from"crypto";import{ulid as W}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as K}from"@redocly/config";import{AUTH_URL as Q,JWT_SECRET_KEY as _}from"../constants/common.js";import{getPathPrefix as X,withPathPrefix as Y}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as G,REQUIRED_OIDC_SCOPES as D,ServerRoutes as P}from"../../constants/common.js";import{appendQueryParams as Z}from"../../utils/url/append-query-params.js";import{logger as ee}from"../tools/notifiers/logger.js";import{randomString as te}from"../utils/crypto/random-string.js";import{randomUUID as R}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as I,JwtTokenExpired as ne}from"./jwt/types.js";import*as p from"./jwt/jwt.js";import{parseTeamClaimToArray as re}from"../utils/index.js";import{arrayBufferToBase64 as ae,decodeBase64 as v,encodeBase64URL as oe,urlSafeBase64 as j}from"./jwt/encode.js";import{formatSamlCertificate as se}from"./utils/format-saml-certificate.js";function N(e){return e?.type===u.OIDC}function ie(e){return e?.type===u.SAML2}async function ze(e,t){if(N(t))return ce(e,t);if(ie(t))return ue(e,t)}async function ce(e,t){const n=await V(e,t),r=new Set((t.scopes||[]).concat(D)),a=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:a,pkce:t.pkce}}function ue(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function Be(e,t,n,r,a={}){const o=new Set((r.scopes||[]).concat(D));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:r.clientId,scope:Array.from(o).join(" "),code:t,redirect_uri:E(n),grant_type:"authorization_code",...r.clientSecret?{client_secret:r.clientSecret}:{},...a}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function de(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:a,extraParams:o,idpId:s,pkce:l},m,A,w){if(!t||!n||!r||!a)return{loginUrl:void 0};const c=new URL(t),f=w?.redirectUriOverride??`${e}${Y(P.OIDC_CALLBACK)}`,S={state:R(),idpId:s,redirectUri:f,redirectTo:m,branch:w?.branchOverride??me(e),inviteCode:A,source:w?.sourceOverride??"portal"},h={};if(l){const d=j(te(50)),g=j(H("sha256").update(d).digest("base64")),x="S256";c.searchParams.append("code_challenge",g),c.searchParams.append("code_challenge_method",x),h.code_verifier={value:d,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:X()||"/"}}}c.searchParams.append("client_id",n),c.searchParams.append("scope",a),c.searchParams.append("response_type",r),c.searchParams.append("redirect_uri",E(f)),c.searchParams.append("state",oe(JSON.stringify(S)));for(const d in o)o[d]!==void 0&&c.searchParams.append(d,o[d]);return{loginUrl:c.toString(),cookies:h}}function Fe(e,t,n,r){const a=new URL(e);return a.searchParams.append("post_logout_redirect_uri",t),r&&a.searchParams.append("state",r),a.searchParams.append("id_token_hint",n),a.toString()}async function Je(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return p.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,iat:t,exp:n},_)}async function qe(e){const{header:t,payload:n}=p.decode(e),a=Object.values(I).includes(t.alg)?t.alg:I.HS256;if(await p.verify(e,_,a),n.type!=="mcp_auth_code")throw new Error("Invalid authorization code type");if(!n.client_id||!n.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof n.exp=="number"&&Date.now()>=n.exp*1e3)throw new Error("Authorization code expired");return n}function He(e){const t=e||W(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${n}`}}function E(e){return e.match(/^https:\/\/preview-[^\.]+--/)?"https://previewauth--"+e.split("--")[1]:e.match(/^(https:\/\/[^\.]+)--[^\.]+\.preview\./)?e.replace(/^(https:\/\/[^\.]+?)--[^\.]+\.preview\./,"$1.previewauth."):e}function me(e){return e.match(/^(https:\/\/[^\.]+)--([^\.]+)\.preview\./)?.[2]||void 0}function le(e){return e.type===u.OIDC}function pe(e){return e.type===u.SAML2}function We(e,t,n,r){return le(e)?de(t,e,n,r):pe(e)?fe(t,e,n,r):{}}function fe(e,t,n,r){const o=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
2
2
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
3
3
  Version="2.0"
4
4
  ID="_${R()}"
@@ -9,4 +9,4 @@ import"../node-crypto-polyfill.js";import{DOMParser as U}from"@xmldom/xmldom";im
9
9
  <samlp:NameIDPolicy
10
10
  AllowCreate="true"
11
11
  Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
12
- </samlp:AuthnRequest>`,s=he(a);return{loginUrl:Z(t.ssoUrl,{SAMLRequest:s,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal"})})}}function he(e){return oe(J(new TextEncoder().encode(e)).buffer)}function Ke(e){const t=j(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=q(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function Qe(e){try{return JSON.parse(j(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function Xe(e){const t=new U().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,a=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],i=s&&s.nodeValue||void 0,l=c(t,"//*[local-name(.)='Audience']/text()")[0],A=l&&l.nodeValue||void 0,u=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",f=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],S=f&&f.nodeValue||"",h=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],m=h&&h.nodeValue||"",g=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=ye(g),T={},k=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(k.length)for(const C of k){const O=c(C,"./@Name")[0];if(O.nodeValue){const b=c(C,"./*[local-name(.)='AttributeValue']/text()")[0];b?.nodeValue&&(T[O.nodeValue]=b.nodeValue)}}return{uid:S,success:r,expiresAt:x,issuerId:i,entityId:A,attrs:T,cert:u,nameFormat:m,destination:a}}function ye(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),n=L(Date.now()),r=L(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function L(e){return Math.floor(e/1e3)}const M={},y={jwks:{}};async function V(e,t){return M[e]||(M[e]=t.configurationUrl?await $(t.configurationUrl):t.configuration),M[e]}async function we(e){for(const t of Object.keys(e)){const n=e[t];if(!E(n))continue;const r=await V(t,n);if(r.jwks_uri){const o=await $(r.jwks_uri);for(const a of o.keys)y.jwks[a.kid]={...a,idpId:t}}}}async function $(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function Ye(e){return fetch(`${Q}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function Ge(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Ae(t.hostname,r))}function Ae(e,t){return e===t||e.endsWith(`.${t}`)}async function Ze(e,t){const n=new U().parseFromString(e),r=c(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=se(t),a=new B({publicCert:o});a.loadSignature(r);try{return a.checkSignature(e)}catch{return!1}}function et(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(o=e);const a=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=a?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?a:void 0),o=o?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||a,email:o,email_verified:!0,teams:r?re(n[r]):[]}}function z(e,t={}){return e.map(n=>t[n]||n)}async function tt(e,t){if(!t)return{};const n=t.authorization;if(!n)return{};try{const r=p.decode(n);if(r.header.alg===I.RS256){y.jwks[r.header.kid]===void 0&&await we(e);const l=y.jwks[r.header.kid];if(!l)return y.jwks[r.header.kid]=null,{};await p.verify(n,l,r.header.alg)}else await p.verify(n,_,r.header.alg);const o=r.payload.idpId||y.jwks[r.header.kid]?.idpId,a=e[o]||{},s=xe(a),i=ge(a);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...z(r.payload.teams||[],i),..."defaultTeams"in a&&a.defaultTeams||[],...z("teamsClaimName"in a&&r.payload[s||""]||[],i),G])),name:Se(r.payload),isAuthenticated:!0,idpAccessToken:t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:n}}catch(r){r instanceof ne||ee.error("Malformed JWT token: %s",r.message)}return{}}function Se(e){return e.name||e.given_name||e.email}function ge(e){switch(e.type){case d.SAML2:return e.teamsAttributeMap;case d.OIDC:return e.teamsClaimMap;default:return}}function xe(e){switch(e.type){case d.SAML2:return e.teamsAttributeName;case d.OIDC:return e.teamsClaimName;default:return K}}function c(e,t){return F.select(t,e)||[]}export{We as buildLoginUrl,de as buildOidcLoginUrl,Fe as buildOidcLogoutUrl,fe as buildSAML2LoginUrl,Je as createMcpAuthorizationCode,He as createMcpSessionResource,Ke as decodeSamlResponse,he as encodeSAML2,et as extractUserClaims,ze as getAuthProviderLoginParams,ce as getOidcLoginParams,V as getOidcMetadata,Ye as getRedoclyTokenPayload,ue as getSaml2LoginParams,tt as getUserParamsFromCookies,Se as getUsernameFromPayload,E as isOidcProviderConfig,Ge as isRedoclySso,ie as isSaml2ProviderConfig,Be as oidcExchangeCodeForToken,Qe as parseOidcState,me as parsePreviewBranch,Xe as parseSamlResponse,N as rewritePreviewAuthRedirectUri,qe as verifyMcpAuthorizationCode,Ze as verifySAMLResponse};
12
+ </samlp:AuthnRequest>`,s=he(o);return{loginUrl:Z(t.ssoUrl,{SAMLRequest:s,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal"})})}}function he(e){return ae(J(new TextEncoder().encode(e)).buffer)}function Ke(e){const t=v(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=q(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function Qe(e){try{return JSON.parse(v(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function Xe(e){const t=new U().parseFromString(e,"application/xml"),r=i(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,o=i(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],l=s&&s.nodeValue||void 0,m=i(t,"//*[local-name(.)='Audience']/text()")[0],A=m&&m.nodeValue||void 0,c=i(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",f=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],S=f&&f.nodeValue||"",h=i(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],d=h&&h.nodeValue||"",g=i(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=ye(g),k={},T=i(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(T.length)for(const C of T){const O=i(C,"./@Name")[0];if(O.nodeValue){const b=i(C,"./*[local-name(.)='AttributeValue']/text()")[0];b?.nodeValue&&(k[O.nodeValue]=b.nodeValue)}}return{uid:S,success:r,expiresAt:x,issuerId:l,entityId:A,attrs:k,cert:c,nameFormat:d,destination:o}}function ye(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),n=L(Date.now()),r=L(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function L(e){return Math.floor(e/1e3)}const M={},y={jwks:{}};async function V(e,t){return M[e]||(M[e]=t.configurationUrl?await $(t.configurationUrl):t.configuration),M[e]}async function we(e){for(const t of Object.keys(e)){const n=e[t];if(!N(n))continue;const r=await V(t,n);if(r.jwks_uri){const a=await $(r.jwks_uri);for(const o of a.keys)y.jwks[o.kid]={...o,idpId:t}}}}async function $(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function Ye(e){return fetch(`${Q}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function Ge(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Ae(t.hostname,r))}function Ae(e,t){return e===t||e.endsWith(`.${t}`)}async function Ze(e,t){const n=new U().parseFromString(e),r=i(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const a=se(t),o=new B({publicCert:a});o.loadSignature(r);try{return o.checkSignature(e)}catch{return!1}}function et(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let a;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(a=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(a=e);const o=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=o?.match(/.+@.+/);return a=a||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?o:void 0),a=a?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||o,email:a,email_verified:!0,teams:r?re(n[r]):[]}}function z(e,t={}){return e.map(n=>t[n]||n)}async function tt(e,t){if(!t)return{};const n=t.authorization;if(!n)return{};try{const r=p.decode(n);if(r.header.alg===I.RS256){y.jwks[r.header.kid]===void 0&&await we(e);const m=y.jwks[r.header.kid];if(!m)return y.jwks[r.header.kid]=null,{};await p.verify(n,m,r.header.alg)}else await p.verify(n,_,r.header.alg);const a=r.payload.idpId||y.jwks[r.header.kid]?.idpId,o=e[a]||{},s=xe(o),l=ge(o);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:a,teams:Array.from(new Set([...z(r.payload.teams||[],l),..."defaultTeams"in o&&o.defaultTeams||[],...z("teamsClaimName"in o&&r.payload[s||""]||[],l),G])),name:Se(r.payload),isAuthenticated:!0,idpAccessToken:t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:n}}catch(r){r instanceof ne||ee.error("Malformed JWT token: %s",r.message)}return{}}function Se(e){return e.name||e.given_name||e.email}function ge(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function xe(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return K}}function i(e,t){return F.select(t,e)||[]}export{We as buildLoginUrl,de as buildOidcLoginUrl,Fe as buildOidcLogoutUrl,fe as buildSAML2LoginUrl,Je as createMcpAuthorizationCode,He as createMcpSessionResource,Ke as decodeSamlResponse,he as encodeSAML2,et as extractUserClaims,ze as getAuthProviderLoginParams,ce as getOidcLoginParams,V as getOidcMetadata,Ye as getRedoclyTokenPayload,ue as getSaml2LoginParams,tt as getUserParamsFromCookies,Se as getUsernameFromPayload,N as isOidcProviderConfig,Ge as isRedoclySso,ie as isSaml2ProviderConfig,Be as oidcExchangeCodeForToken,Qe as parseOidcState,me as parsePreviewBranch,Xe as parseSamlResponse,E as rewritePreviewAuthRedirectUri,qe as verifyMcpAuthorizationCode,Ze as verifySAMLResponse};
@@ -4,6 +4,7 @@ export declare function authorizeHandler(ctx: Context): Promise<Response>;
4
4
  export declare function redoclyLoginCallbackHandler(): Handler;
5
5
  export declare function oidcCallbackHandler(store: Store): Handler;
6
6
  export declare function logoutHandler(store: Store): (ctx: Context) => Promise<Response>;
7
+ export declare function postLogoutHandler(store: Store): (ctx: Context) => Promise<Response>;
7
8
  export declare function inviteHandler(store: Store): (ctx: Context) => Promise<Response>;
8
9
  export declare function idpLoginHandler(store: Store): (ctx: Context) => Promise<Response>;
9
10
  export declare function samlCallbackHandler(store: Store): Handler;
@@ -1 +1 @@
1
- import{setCookie as L,deleteCookie as q}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as M,getPathPrefix as C}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as U}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(s){if(process.env.NODE_ENV==="production")return s.newResponse(null,404,{});const{password:e,...o}=await s.req.json(),a=await S.sign({...o,name:o.username||o.email||"Unknown"},v);return L(s,"authorization",a,{path:C()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),s.newResponse(null,200,{})}function Pe(){return async s=>{const e=s.get("logger"),o=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${o}`);const a=`${D.LOGIN}/?error=${encodeURIComponent(o)}`;return s.newResponse(null,301,{Location:a})}}function j(s){if(!s||!s.includes(D.MCP_CALLBACK))return null;try{const e=s.split("/"),o=e[e.length-1];if(o){const a=Buffer.from(o,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function Se(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?j(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,i=a?.[f];if(!$(i))return o.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return o.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,s,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(M(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),_=e.get("cookies")?.code_verifier,u=await Z(p,l,A,i,{...i.tokenRequestCustomParams,..._?{code_verifier:_}:{}});if(u.error)return o.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:r,header:T}=S.decode(u.id_token),H=T.alg===ue.RS256;if(i.audience?.length&&![...U(r.aud||[]),...U(r[E]||[])].some(R=>i.audience?.includes(R))){const R="No valid audience found in id_token";return o.error(`OIDC login error: ${R}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:R,error_details:null}),new Response(`Forbidden: ${R}`)}const b=H?u.id_token:await S.sign({...r,idpId:f},v);ie(r)||o.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const O=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:r.exp*1e3||Date.now()+F*1e3;if(i.introspectEndpoint){const h=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}))}else o.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:C()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),q(e,"code_verifier",{path:C()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),R=M(n.redirectTo),P=`${h}${R}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return o.updateContext({email:r.email,subject:r.sub}),o.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(o.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return o.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return o.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(s){return async e=>{const o=e.get("logger"),n=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(t)||q(e,"authorization",{path:C()||"/"}),o.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const i=(await z(n,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:l;c=se(i,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return o.info("Logout successful"),q(e,"authorization",{path:C()||"/"}),e.newResponse(null,302,{Location:c||M("/")})}}function $e(s){return async e=>{const o=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!n)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return o.warn("Invite no sso configured to handle"),e.redirect(M("/"));const c=await fetch(`${n}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(o.warn(`Invite ${a} not found redirect to homepage`),e.redirect(M("/"))):(o.error("Invite error",await c.text()),e.redirect(M("/")));const i=await c.json(),d=new URL(M("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",f(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return o.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Te(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,i=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const r="Invalid idpId";if(o.error(`IdP login error: ${r}`),m){const T=j(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(T),error:r,error_details:null})}return e.text(`Forbidden: ${r}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const r of Object.keys(g?.extraParams||{}))A[r]=n.searchParams.get(r)||g?.extraParams?.[r]||void 0;let _,u={};if(m&&l&&g&&g.type===G.OIDC){o.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const r=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});_=r.loginUrl,u=r.cookies||{}}else if(g){const r=x({...g,extraParams:A},i,w,f);_=r.loginUrl,u=r.cookies||{}}return Object.keys(u).forEach(r=>{L(e,r,u[r].value,u[r].options)}),o.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:_||new URL(e.req.url).pathname})}}function qe(s){return async e=>{const o=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const r="SAMLResponse is required";return o.error(`SAML2 login error: ${r}`),e.text(`Bad request: ${r}`,400)}const t=ee(n),{success:c,uid:i,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);if(!c){const r="SAML2 assertion is not successful";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const r="SAML2 Token Expired";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const g=s.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const r="Cannot find valid IdP";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){const r="IssuerID is misconfigured or untrusted assertions issuer received";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!await te(t,g.x509PublicCert)){const r="SAMLResponse signature invalid";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const _=re(i,d,w,g.teamsAttributeName);if(!_.sub){const r="The provider did not return a valid user identity.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}if(!_.email){const r="The provider did not return a valid user email.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}const u=await S.sign({..._,idpId:m},v);return L(e,"authorization",u,{path:C()||"/",httpOnly:!0,expires:new Date(l*1e3)}),o.updateContext({email:_.email,subject:_.sub}),o.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function Ue(s){return async e=>{const o=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=M(B(a.pathname)),f=s.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const i=e.req.query("token"),d=i&&await ae(i);if(!d)return e.newResponse(null,302,{Location:n});if(!U(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:C()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),o.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,$e as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,Pe as redoclyLoginCallbackHandler,Ue as redoclyTokenLoginHandler,qe as samlCallbackHandler};
1
+ import{setCookie as L,deleteCookie as T}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as R,getPathPrefix as _}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(i){if(process.env.NODE_ENV==="production")return i.newResponse(null,404,{});const{password:e,...r}=await i.req.json(),a=await S.sign({...r,name:r.username||r.email||"Unknown"},v);return L(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=`${D.LOGIN}/?error=${encodeURIComponent(r)}`;return i.newResponse(null,301,{Location:a})}}function H(i){if(!i||!i.includes(D.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 Se(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?H(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,s=a?.[f];if(!$(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,s);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,i,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return r.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(R(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),C=e.get("cookies")?.code_verifier,u=await Z(p,l,A,s,{...s.tokenRequestCustomParams,...C?{code_verifier:C}:{}});if(u.error)return r.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:o,header:U}=S.decode(u.id_token),j=U.alg===ue.RS256;if(s.audience?.length&&![...q(o.aud||[]),...q(o[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({...I(c),error:M,error_details:null}),new Response(`Forbidden: ${M}`)}const b=j?u.id_token:await S.sign({...o,idpId:f},v);ie(o)||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:o.exp*1e3||Date.now()+F*1e3;if(s.introspectEndpoint){const h=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}))}else r.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:_()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),T(e,"code_verifier",{path:_()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),M=R(n.redirectTo),P=`${h}${M}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return r.updateContext({email:o.email,subject:o.sub}),r.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return r.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(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 $(t)||T(e,"authorization",{path:_()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const s=(await z(n,t)).end_session_endpoint;if(s){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:`${l}/post-logout`;c=se(s,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return r.info("Logout successful"),T(e,"authorization",{path:_()||"/"}),e.newResponse(null,302,{Location:c||R("/")})}}function $e(i){return async e=>{const r=i.getConfig().logoutReturnUrl,a=r||R("/");return e.newResponse(null,302,{Location:a})}}function Ue(i){return async e=>{const r=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(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(R("/"));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(R("/"))):(r.error("Invite error",await c.text()),e.redirect(R("/")));const s=await c.json(),d=new URL(R("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",s.organization.name),d.searchParams.set("invitedBy",f(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 Te(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,s=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const o="Invalid idpId";if(r.error(`IdP login error: ${o}`),m){const U=H(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(U),error:o,error_details:null})}return e.text(`Forbidden: ${o}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const o of Object.keys(g?.extraParams||{}))A[o]=n.searchParams.get(o)||g?.extraParams?.[o]||void 0;let C,u={};if(m&&l&&g&&g.type===G.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const o=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});C=o.loginUrl,u=o.cookies||{}}else if(g){const o=x({...g,extraParams:A},s,w,f);C=o.loginUrl,u=o.cookies||{}}return Object.keys(u).forEach(o=>{L(e,o,u[o].value,u[o].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:C||new URL(e.req.url).pathname})}}function qe(i){return async e=>{const r=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const o="SAMLResponse is required";return r.error(`SAML2 login error: ${o}`),e.text(`Bad request: ${o}`,400)}const t=ee(n),{success:c,uid:s,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);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 g=i.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const o="Cannot find valid IdP";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){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 te(t,g.x509PublicCert)){const o="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const C=re(s,d,w,g.teamsAttributeName);if(!C.sub){const o="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}if(!C.email){const o="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}const u=await S.sign({...C,idpId:m},v);return L(e,"authorization",u,{path:_()||"/",httpOnly:!0,expires:new Date(l*1e3)}),r.updateContext({email:C.email,subject:C.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function be(i){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=R(B(a.pathname)),f=i.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const s=e.req.query("token"),d=s&&await ae(s);if(!d)return e.newResponse(null,302,{Location:n});if(!q(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:_()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,Ue as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,$e as postLogoutHandler,Pe as redoclyLoginCallbackHandler,be as redoclyTokenLoginHandler,qe as samlCallbackHandler};
@@ -1 +1 @@
1
- import{serveStatic as f}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as s}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as R}from"../../constants/common.js";import{authMiddleware as c}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as S}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as r}from"../../plugins/dev-onboarding/api/routes/index.js";import{authorizeHandler as I,oidcCallbackHandler as P,logoutHandler as T,idpLoginHandler as h,redoclyLoginCallbackHandler as D,samlCallbackHandler as N,redoclyTokenLoginHandler as M,inviteHandler as B}from"./auth.js";import{appDataHandler as U}from"./app-data.js";import{searchFacetsHandler as G,searchHandler as w}from"./search.js";import{dynamicRouteHandler as F}from"./dynamic-route.js";import{pageDataHandler as v,sharedPageDataHandler as K}from"./page-data.js";import{pathPrefixRedirectHandler as y}from"./path-prefix-redirect.js";import{getRoutesByLineHandler as E,resolvePathHandler as o,resolvePathsHandler as k,resolveSlugHandler as p}from"./resolve-route.js";import{feedbackHandler as Y}from"./feedback.js";import{loggerMiddleware as b}from"../middleware/loggerMiddleware.js";import{responseHeadersMiddleware as V}from"../middleware/responseHeadersMiddleware.js";import{idleTimeoutMiddleware as x}from"../middleware/idleTimeoutMiddleware.js";import{otelTracesHandler as z}from"./otel/otel.js";import{healthCheckHandler as Z}from"./health.js";import{askAiHandler as $}from"./ask-ai.js";import{replayOauth2RedirectCallbackHandler as q}from"./replay-oauth2-redirect.js";import{mcpOAuthProtectedResourceHandler as W,mcpOAuthAuthorizationServerHandler as j,mcpDynamicClientRegistrationHandler as J,mcpAuthorizationHandler as Q,mcpTokenPortalHandler as X,mcpCallbackHandler as _}from"./mcp-oauth.js";import{corsMiddleware as O}from"../middleware/corsMiddleware.js";import{installApiRoutes as u}from"./api-routes/api-routes.js";import{cookieMiddleware as aa}from"../middleware/cookieMiddleware.js";import{staticContentHandler as ea}from"../routes/static-content.js";import{infoHandler as C}from"./info.js";import{catalogHandler as ia}from"./catalog/catalog.js";import{catalogRelationsHandler as la}from"./catalog/catalog-relations.js";import{bffCatalogHandler as ma}from"./catalog/bff-catalog.js";import{bffCatalogRevisionsHandler as ta}from"./catalog/bff-catalog-revisions.js";import{bffCatalogRelatedEntitiesHandler as na}from"./catalog/bff-catalog-related-entities.js";import{catalogAuthMiddleware as L}from"../middleware/catalogAuthMiddleware.js";import{telemetryMiddleware as Aa}from"../middleware/telemetry-middleware.js";import{errorHandler as da}from"./error.js";function qa(a,l,m){const{resolveRouteData:n,readStaticAsset:A}=m;a.use("*",x()),a.use("*",aa()),a.use("*",S(l)),a.use("*",c(l)),a.use("*",b()),a.use("*",V(l)),a.use("*",Aa()),a.use(e("*"),f({root:`./${R}`,getContent:(t,H)=>ea(t,H,l,A),rewriteRequestPath:t=>s(t)})),a.use(e(i.FEEDBACK),O({allowMethods:["POST"]})),a.use(e(i.ASK_AI),O({allowMethods:["POST"]})),a.use("*",Ta(l));const d=g(l);a.use(e(i.INFO),C()),process.env.NEW_CATALOG_ENABLED==="true"&&(a.use(e(i.CATALOG_ENTITIES),L(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES_RELATIONS),L(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES),ia(l)),a.use(e(i.CATALOG_ENTITIES_RELATIONS),la(l)),a.get(e(i.BFF_CATALOG_ENTITIES),ma(l)),a.get(e(i.BFF_CATALOG_RELATED_ENTITIES),na(l)),a.get(e(i.BFF_CATALOG_REVISIONS),ta(l))),a.get(e(i.SHARED_PAGE_DATA),K(l)),a.get(e(i.PAGE_DATA),v(l,n)),a.get(e(i.APP_DATA),U(l)),a.post(e(i.SEARCH),d,w(l)),a.post(e(i.SEARCH_FACETS),d,G(l)),a.post(e(i.AUTHORIZATION),I),a.post(e(i.LOGOUT),T(l)),a.get(e(i.LOGOUT),T(l)),a.get(e(i.OIDC_CALLBACK),P(l)),a.get(e(i.REDOCLY_TOKEN_LOGIN),M(l)),a.get(e(i.REDOCLY_LOGIN_CALLBACK),D()),a.get(e(i.IDP_LOGIN),h(l)),a.post(e(i.SAML_CALLBACK),N(l)),a.get(e(i.INVITE),B(l)),a.get(e(i.HEALTH),Z),a.get(e(i.MCP_OAUTH_PROTECTED_RESOURCE),W()),a.get(e(i.MCP_OAUTH_AUTHORIZATION_SERVER),j()),a.post(e(i.MCP_DYNAMIC_CLIENT_REGISTRATION),J()),a.get(e(i.MCP_AUTHORIZATION),Q()),a.post(e(i.MCP_TOKEN_PORTAL),X()),a.get(e(i.MCP_CALLBACK),_()),a.get(e(`${i.MCP_CALLBACK}/*`),_()),r(a,l),u(a,l),a.post(e(i.FEEDBACK),Y(l)),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTES_BY_PATHS),k(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.post(e(i.ASK_AI),$(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l)),a.post(e(i.OTEL_TRACES),z),a.get(e(i.REPLAY_OAUTH2_CALLBACK),q),a.all(e("/*"),F(l,n,A)),a.get("*",y),a.onError(da)}function Ta(a){return async(l,m)=>{await a.waitForPluginsLifecycle(),await m()}}function Wa(a,l){a.get(e(i.INFO),C()),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l))}export{Wa as installDevRoutes,qa as installProdRoutes,Ta as waitForPluginsLifecycle};
1
+ import{serveStatic as f}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as s}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as R}from"../../constants/common.js";import{authMiddleware as c}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as S}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as P}from"../../plugins/dev-onboarding/api/routes/index.js";import{authorizeHandler as I,oidcCallbackHandler as r,logoutHandler as d,postLogoutHandler as h,idpLoginHandler as D,redoclyLoginCallbackHandler as N,samlCallbackHandler as M,redoclyTokenLoginHandler as U,inviteHandler as B}from"./auth.js";import{appDataHandler as G}from"./app-data.js";import{searchFacetsHandler as w,searchHandler as F}from"./search.js";import{dynamicRouteHandler as v}from"./dynamic-route.js";import{pageDataHandler as K,sharedPageDataHandler as y}from"./page-data.js";import{pathPrefixRedirectHandler as k}from"./path-prefix-redirect.js";import{getRoutesByLineHandler as E,resolvePathHandler as o,resolvePathsHandler as Y,resolveSlugHandler as p}from"./resolve-route.js";import{feedbackHandler as b}from"./feedback.js";import{loggerMiddleware as V}from"../middleware/loggerMiddleware.js";import{responseHeadersMiddleware as x}from"../middleware/responseHeadersMiddleware.js";import{idleTimeoutMiddleware as z}from"../middleware/idleTimeoutMiddleware.js";import{otelTracesHandler as Z}from"./otel/otel.js";import{healthCheckHandler as $}from"./health.js";import{askAiHandler as q}from"./ask-ai.js";import{replayOauth2RedirectCallbackHandler as W}from"./replay-oauth2-redirect.js";import{mcpOAuthProtectedResourceHandler as j,mcpOAuthAuthorizationServerHandler as J,mcpDynamicClientRegistrationHandler as Q,mcpAuthorizationHandler as X,mcpTokenPortalHandler as u,mcpCallbackHandler as _}from"./mcp-oauth.js";import{corsMiddleware as O}from"../middleware/corsMiddleware.js";import{installApiRoutes as aa}from"./api-routes/api-routes.js";import{cookieMiddleware as ea}from"../middleware/cookieMiddleware.js";import{staticContentHandler as ia}from"../routes/static-content.js";import{infoHandler as L}from"./info.js";import{catalogHandler as la}from"./catalog/catalog.js";import{catalogRelationsHandler as ta}from"./catalog/catalog-relations.js";import{bffCatalogHandler as ma}from"./catalog/bff-catalog.js";import{bffCatalogRevisionsHandler as na}from"./catalog/bff-catalog-revisions.js";import{bffCatalogRelatedEntitiesHandler as Aa}from"./catalog/bff-catalog-related-entities.js";import{catalogAuthMiddleware as C}from"../middleware/catalogAuthMiddleware.js";import{telemetryMiddleware as Ta}from"../middleware/telemetry-middleware.js";import{errorHandler as da}from"./error.js";function Wa(a,l,t){const{resolveRouteData:n,readStaticAsset:A}=t;a.use("*",z()),a.use("*",ea()),a.use("*",S(l)),a.use("*",c(l)),a.use("*",V()),a.use("*",x(l)),a.use("*",Ta()),a.use(e("*"),f({root:`./${R}`,getContent:(m,H)=>ia(m,H,l,A),rewriteRequestPath:m=>s(m)})),a.use(e(i.FEEDBACK),O({allowMethods:["POST"]})),a.use(e(i.ASK_AI),O({allowMethods:["POST"]})),a.use("*",Ea(l));const T=g(l);a.use(e(i.INFO),L()),process.env.NEW_CATALOG_ENABLED==="true"&&(a.use(e(i.CATALOG_ENTITIES),C(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES_RELATIONS),C(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES),la(l)),a.use(e(i.CATALOG_ENTITIES_RELATIONS),ta(l)),a.get(e(i.BFF_CATALOG_ENTITIES),ma(l)),a.get(e(i.BFF_CATALOG_RELATED_ENTITIES),Aa(l)),a.get(e(i.BFF_CATALOG_REVISIONS),na(l))),a.get(e(i.SHARED_PAGE_DATA),y(l)),a.get(e(i.PAGE_DATA),K(l,n)),a.get(e(i.APP_DATA),G(l)),a.post(e(i.SEARCH),T,F(l)),a.post(e(i.SEARCH_FACETS),T,w(l)),a.post(e(i.AUTHORIZATION),I),a.post(e(i.LOGOUT),d(l)),a.get(e(i.LOGOUT),d(l)),a.get(e(i.POST_LOGOUT),h(l)),a.get(e(i.OIDC_CALLBACK),r(l)),a.get(e(i.REDOCLY_TOKEN_LOGIN),U(l)),a.get(e(i.REDOCLY_LOGIN_CALLBACK),N()),a.get(e(i.IDP_LOGIN),D(l)),a.post(e(i.SAML_CALLBACK),M(l)),a.get(e(i.INVITE),B(l)),a.get(e(i.HEALTH),$),a.get(e(i.MCP_OAUTH_PROTECTED_RESOURCE),j()),a.get(e(i.MCP_OAUTH_AUTHORIZATION_SERVER),J()),a.post(e(i.MCP_DYNAMIC_CLIENT_REGISTRATION),Q()),a.get(e(i.MCP_AUTHORIZATION),X()),a.post(e(i.MCP_TOKEN_PORTAL),u()),a.get(e(i.MCP_CALLBACK),_()),a.get(e(`${i.MCP_CALLBACK}/*`),_()),P(a,l),aa(a,l),a.post(e(i.FEEDBACK),b(l)),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTES_BY_PATHS),Y(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.post(e(i.ASK_AI),q(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l)),a.post(e(i.OTEL_TRACES),Z),a.get(e(i.REPLAY_OAUTH2_CALLBACK),W),a.all(e("/*"),v(l,n,A)),a.get("*",k),a.onError(da)}function Ea(a){return async(l,t)=>{await a.waitForPluginsLifecycle(),await t()}}function ja(a,l){a.get(e(i.INFO),L()),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l))}export{ja as installDevRoutes,Wa as installProdRoutes,Ea as waitForPluginsLifecycle};
@@ -1,4 +1,14 @@
1
1
  import type { Handler } from 'hono';
2
+ export type McpContextPayload = {
3
+ isMcpFlow: boolean;
4
+ originalRedirectUri: string | null;
5
+ mcpClientId: string | null;
6
+ mcpState: string | null;
7
+ mcpSessionId: string;
8
+ timestamp: number;
9
+ };
10
+ export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
11
+ export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
2
12
  export declare function mcpOAuthProtectedResourceHandler(): Handler;
3
13
  export declare function mcpOAuthAuthorizationServerHandler(): Handler;
4
14
  /**
@@ -1 +1 @@
1
- import{getCookie as f}from"hono/cookie";import{ulid as C}from"ulid";import{AUTH_URL as m}from"../../constants/common.js";import{ServerRoutes as d}from"../../../constants/common.js";import{withPathPrefix as g}from"@redocly/theme/core/utils";import{logger as S}from"../../tools/notifiers/logger.js";import{telemetry as p}from"../../telemetry/index.js";import{createMcpAuthorizationCode as M,verifyMcpAuthorizationCode as y,createMcpSessionResource as l}from"../auth.js";const a=(e,t,n=200,i)=>e.json(t,n,{"Content-Type":"application/json",...i??{}});function $(){return async e=>{if(e.req.method!=="GET")return a(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=new URL(e.req.url),n=e.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",i=e.req.header("x-forwarded-host")||t.host,r=`${n}://${i}`;return a(e,{resource:`${r}/mcp`,authorization_servers:[r],bearer_methods_supported:["header"],resource_documentation:`${r}/.well-known/oauth-authorization-server`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function q(){return async e=>{const t=new URL(e.req.url),n=e.req.header("x-forwarded-proto")||t.protocol.slice(0,-1)||"https",i=e.req.header("x-forwarded-host")||t.host,r=`${n}://${i}`;return a(e,{issuer:m||"",authorization_endpoint:`${r}${d.MCP_AUTHORIZATION}`,token_endpoint:`${r}${d.MCP_TOKEN_PORTAL}`,jwks_uri:`${m||""}/.well-known/jwks.json`,registration_endpoint:`${r}${d.MCP_DYNAMIC_CLIENT_REGISTRATION}`,scopes_supported:["openid","profile","email","offline_access"],response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"]})}}function z(){return async e=>{if(e.req.method!=="POST")return a(e,{error:"Method not allowed"},405);try{return a(e,{client_id:process.env.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(t){return a(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500)}}}function L(){return async e=>{const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),r=C();p.sendMcpAuthorizationStartedMessage({...l(r),redirect_uri:i||null});const s={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:n.get("client_id"),mcpState:n.get("state"),mcpSessionId:r,timestamp:Date.now()};try{const o=new URL(e.req.url),c=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",_=e.req.header("x-forwarded-host")||o.host,u=`${c}://${_}`,w=Buffer.from(JSON.stringify(s)).toString("base64url"),h=new URL(d.IDP_LOGIN,u);return h.searchParams.set("redirectTo",`${g(d.MCP_CALLBACK)}/${w}`),h.searchParams.set("idpId","oidc"),e.redirect(h.toString())}catch(o){const c=o instanceof Error?o.message:String(o),_=o instanceof Error?o.stack:String(o);p.sendMcpAuthorizationFailedMessage({...l(r),error:c,error_details:_});const u=new URL(g(`${m}/oauth2/auth`));return u.search=n.toString(),e.redirect(u.toString())}}}function O(){return async e=>{if(e.req.method!=="POST")return a(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),r=t.get("redirect_uri")||void 0;if(n!=="authorization_code"||!i)return a(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const s=await y(i);if(r&&r!==s.redirect_uri)return a(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(process.env.OAUTH_CLIENT_ID&&s.client_id&&s.client_id!==process.env.OAUTH_CLIENT_ID)return a(e,{error:"invalid_client",error_description:"Client mismatch"},400);const o=s.id_token;return typeof o!="string"||o.length===0?a(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400):a(e,{access_token:o,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:o},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return a(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){return a(e,{error:"server_error",error_description:"Failed to process token request",error_details:t.message},500)}}}function D(){return async e=>{const t=new URL(e.req.url);let n=t.searchParams.get("context");if(!n&&t.pathname.startsWith(g(`${d.MCP_CALLBACK}/`))){const r=t.pathname.split("/"),s=r[r.length-1];if(s)try{n=Buffer.from(s,"base64url").toString("utf-8")}catch(o){S.error("[OAuth Debug] Error decoding context:",o)}}if(!n)return p.sendMcpAuthorizationFailedMessage({...l(null),error:"Missing context parameter",error_details:null}),e.text("Missing context parameter",400);let i=null;try{const r=typeof n=="string"&&n.startsWith("{")?JSON.parse(n):JSON.parse(decodeURIComponent(n));if(i=r.mcpSessionId||null,!r.isMcpFlow||!r.originalRedirectUri)throw new Error("Invalid MCP context");const s=f(e,"idp_id_token")||f(e,"authorization"),o=await M({idToken:s||"",clientId:r.mcpClientId||"",redirectUri:r.originalRedirectUri,ttlSec:600}),c=new URL(r.originalRedirectUri);return c.searchParams.set("code",o),r.mcpState&&c.searchParams.set("state",r.mcpState),p.sendMcpAuthorizationCompletedMessage({...l(i),redirect_uri:r.originalRedirectUri||null}),e.redirect(c.toString())}catch(r){const s=r instanceof Error?r.message:String(r),o=r instanceof Error?r.stack:String(r);return p.sendMcpAuthorizationFailedMessage({...l(i),error:s,error_details:o}),e.text(`Invalid MCP callback: ${s}`,400)}}}export{L as mcpAuthorizationHandler,D as mcpCallbackHandler,z as mcpDynamicClientRegistrationHandler,q as mcpOAuthAuthorizationServerHandler,$ as mcpOAuthProtectedResourceHandler,O as mcpTokenPortalHandler};
1
+ import{getCookie as C}from"hono/cookie";import{ulid as w}from"ulid";import{AUTH_URL as _,JWT_SECRET_KEY as y}from"../../constants/common.js";import{ServerRoutes as d}from"../../../constants/common.js";import{withPathPrefix as m}from"@redocly/theme/core/utils";import{telemetry as l}from"../../telemetry/index.js";import{createMcpAuthorizationCode as k,verifyMcpAuthorizationCode as S,createMcpSessionResource as u}from"../auth.js";import*as h from"../jwt/jwt.js";import{getRequestOrigin as g}from"../utils/get-request-origin.js";const n=(e,r,o=200,a)=>e.json(r,o,{"Content-Type":"application/json",...a??{}});async function T(e){const r=Math.floor(Date.now()/1e3);return h.sign({type:"mcp_context",...e,iat:r,exp:r+600},y)}async function I(e){await h.verify(e,y);const{payload:r}=h.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function O(){return async e=>{if(e.req.method!=="GET")return n(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=g(e);return n(e,{resource:`${r}/mcp`,authorization_servers:[r],bearer_methods_supported:["header"],resource_documentation:`${r}/.well-known/oauth-authorization-server`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function D(){return async e=>{const r=g(e);return n(e,{issuer:_||"",authorization_endpoint:`${r}${d.MCP_AUTHORIZATION}`,token_endpoint:`${r}${d.MCP_TOKEN_PORTAL}`,jwks_uri:`${_||""}/.well-known/jwks.json`,registration_endpoint:`${r}${d.MCP_DYNAMIC_CLIENT_REGISTRATION}`,scopes_supported:["openid","profile","email","offline_access"],response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"]})}}function $(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{return n(e,{client_id:process.env.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(r){return n(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function q(){return async e=>{const r=new URL(e.req.url),{searchParams:o}=r,a=o.get("redirect_uri"),t=w();l.sendMcpAuthorizationStartedMessage({...u(t),redirect_uri:a||null});const s=g(e),c={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:o.get("client_id"),mcpState:o.get("state"),mcpSessionId:t,timestamp:Date.now()};try{const i=await T(c),p=new URL(d.IDP_LOGIN,s);return p.searchParams.set("redirectTo",`${m(d.MCP_CALLBACK)}/${i}`),p.searchParams.set("idpId","oidc"),e.redirect(p.toString())}catch(i){const p=i instanceof Error?i.message:String(i),M=i instanceof Error?i.stack:String(i);l.sendMcpAuthorizationFailedMessage({...u(t),error:p,error_details:M});const f=new URL(m(`${_}/oauth2/auth`));return f.search=o.toString(),e.redirect(f.toString())}}}function H(){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 s=await S(a);if(t&&t!==s.redirect_uri)return n(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(process.env.OAUTH_CLIENT_ID&&s.client_id&&s.client_id!==process.env.OAUTH_CLIENT_ID)return n(e,{error:"invalid_client",error_description:"Client mismatch"},400);const c=s.id_token;return typeof c!="string"||c.length===0?n(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400):n(e,{access_token:c,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(m(`${d.MCP_CALLBACK}/`))){const t=r.pathname.split("/");o=t[t.length-1]}if(!o)return l.sendMcpAuthorizationFailedMessage({...u(null),error:"Missing context parameter",error_details:null}),e.text("Missing context parameter",400);let a=null;try{const t=await I(o);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=C(e,"idp_id_token")||C(e,"authorization"),c=await k({idToken:s||"",clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,ttlSec:600}),i=new URL(t.originalRedirectUri);return i.searchParams.set("code",c),t.mcpState&&i.searchParams.set("state",t.mcpState),l.sendMcpAuthorizationCompletedMessage({...u(a),redirect_uri:t.originalRedirectUri||null}),e.redirect(i.toString())}catch(t){const s=t instanceof Error?t.message:String(t),c=t instanceof Error?t.stack:String(t);return l.sendMcpAuthorizationFailedMessage({...u(a),error:s,error_details:c}),e.text(`Invalid MCP callback: ${s}`,400)}}}export{T as createMcpContextToken,q as mcpAuthorizationHandler,b as mcpCallbackHandler,$ as mcpDynamicClientRegistrationHandler,D as mcpOAuthAuthorizationServerHandler,O as mcpOAuthProtectedResourceHandler,H as mcpTokenPortalHandler,I as verifyAndParseMcpContextToken};
@@ -0,0 +1,3 @@
1
+ import type { Context } from 'hono';
2
+ export declare function getRequestOrigin(ctx: Context): string;
3
+ //# sourceMappingURL=get-request-origin.d.ts.map
@@ -0,0 +1 @@
1
+ function s(r){const e=new URL(r.req.url),o=r.req.header("x-forwarded-proto")||e.protocol.slice(0,-1)||"https",t=r.req.header("x-forwarded-host")||e.host;return`${o}://${t}`}export{s as getRequestOrigin};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/redoc",
3
- "version": "0.129.0-next.5",
3
+ "version": "0.129.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -93,14 +93,14 @@
93
93
  "xml-crypto": "6.0.1",
94
94
  "xpath": "0.0.34",
95
95
  "yaml-ast-parser": "0.0.43",
96
- "@redocly/asyncapi-docs": "1.6.0-next.5",
97
- "@redocly/config": "0.41.1",
98
- "@redocly/graphql-docs": "1.6.0-next.0",
99
- "@redocly/openapi-docs": "3.17.0-next.5",
100
- "@redocly/portal-legacy-ui": "0.12.0-next.0",
101
- "@redocly/portal-plugin-mock-server": "0.14.0-next.5",
102
- "@redocly/realm-asyncapi-sdk": "0.7.0-next.1",
103
- "@redocly/theme": "0.61.0-next.3"
96
+ "@redocly/config": "0.41.2",
97
+ "@redocly/graphql-docs": "1.6.0",
98
+ "@redocly/openapi-docs": "3.17.0",
99
+ "@redocly/asyncapi-docs": "1.6.0",
100
+ "@redocly/portal-legacy-ui": "0.12.0",
101
+ "@redocly/portal-plugin-mock-server": "0.14.0",
102
+ "@redocly/realm-asyncapi-sdk": "0.7.0",
103
+ "@redocly/theme": "0.61.0"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "react": "19.2.3",