@redocly/realm 0.138.0-next.6 → 0.138.0-next.7

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 (25) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/client/app/pages/DevLogin/DevLoginForm.js +1 -1
  3. package/dist/client/app/pages/Invite/Invite.js +1 -1
  4. package/dist/server/plugins/dev-onboarding/template/App.js +5 -5
  5. package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +3 -3
  6. package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
  7. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +1 -7
  8. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -6
  9. package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
  10. package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-guard.js +1 -1
  11. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.js +1 -1
  12. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.d.ts +0 -6
  13. package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
  14. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -4
  15. package/dist/server/plugins/scorecard-classic/template/Grid/GridHeader.js +21 -8
  16. package/dist/server/plugins/scorecard-classic/template/ScorecardDashboard.js +3 -3
  17. package/dist/server/plugins/scorecard-classic/template/index.styles.d.ts +1 -0
  18. package/dist/server/plugins/scorecard-classic/template/index.styles.js +15 -5
  19. package/dist/server/plugins/scorecard-classic/template/views.js +1 -1
  20. package/dist/server/web-server/routes/cors-proxy.js +2 -2
  21. package/package.json +4 -4
  22. /package/dist/server/{plugins/mcp/gateway-mcp/fetch → web-server/utils}/ssrf-blocklist.d.ts +0 -0
  23. /package/dist/server/{plugins/mcp/gateway-mcp/fetch → web-server/utils}/ssrf-blocklist.js +0 -0
  24. /package/dist/server/{plugins/mcp/gateway-mcp/fetch → web-server/utils}/ssrf-safe-lookup.d.ts +0 -0
  25. /package/dist/server/{plugins/mcp/gateway-mcp/fetch → web-server/utils}/ssrf-safe-lookup.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @redocly/realm
2
2
 
3
+ ## 0.138.0-next.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 55a98f9decd: Hardened CORS proxy validation of request targets.
8
+ - 15ad3df0069: Improved keyboard navigation for catalog filters, scorecard table sorting, and the "API keys" and "Logs" tabs on app pages in developer onboarding.
9
+ - 15ad3df0069: Improved keyboard and screen reader accessibility for image lightboxes, feedback reasons, select menus such as the version picker, and schema variant switches in API reference.
10
+ - Updated dependencies [15ad3df0069]
11
+ - Updated dependencies [15ad3df0069]
12
+ - @redocly/theme@0.70.0-next.5
13
+ - @redocly/api-docs@0.3.0-next.5
14
+
3
15
  ## 0.138.0-next.6
4
16
 
5
17
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import a,{useState as T}from"react";import C from"styled-components";import{Button as o}from"@redocly/theme/components/Button/Button";import{isEmailValid as x}from"../../pages/DevLogin/utils";import{DEFAULT_AUTHENTICATED_TEAM as n}from"../../../../constants/common";import{TeamsSelector as g}from"./styled";function F({teams:s,formState:{values:t,errors:l},setFormState:m,onSubmit:d}){const[u,p]=T([]),f=()=>m({values:t,errors:void 0}),c=()=>{t?.email&&x(t.email)?(d(),l&&f()):m({values:t,errors:{...l,email:"Please enter a correct email"}})},h=({target:e})=>{m({errors:l,values:{...t,[e.name]:e.value}})},E=({target:e})=>{const i=[...t?.teams||[],e.name];e.checked?m({errors:l,values:{...t,teams:i}}):m({errors:l,values:{...t,teams:t.teams?.filter(k=>k!==e.name)}})},b=e=>{e.preventDefault(),c()},y=()=>{const e=prompt("Enter custom team name");e&&p(i=>[...i,e])};return a.createElement("form",{id:"main_form",onSubmit:b},a.createElement("div",{className:"email-screen"},a.createElement("span",{className:"label","data-cy":"email-input-label"},"Email address"),a.createElement("input",{type:"email",id:"email",name:"email",autoFocus:!0,value:t?.email||"",onChange:h,"data-cy":"email-input",className:l?.email?"error":""}),l?.email&&a.createElement("span",{id:"email-screen-email-error",className:"email-error-block","data-cy":"incorrect-email-error"},l.email),a.createElement(a.Fragment,null,a.createElement("span",{className:"label","data-cy":"email-input-label"},"Select teams"),a.createElement(g,null,s.concat(u).map(e=>a.createElement(r,{key:e},a.createElement("input",{type:"checkbox",name:e,id:e,checked:t?.teams?.includes(e)||!1,onChange:E}),a.createElement("label",{htmlFor:e},e))),a.createElement(r,{key:n},a.createElement("input",{type:"checkbox",name:n,id:n,disabled:!0,checked:!0}),a.createElement("label",{htmlFor:n,title:"This team is enabled for all authenticated users by default"},n)),a.createElement(o,{type:"button",size:"small",fullWidth:!0,onClick:y},"+ Add custom team"))),a.createElement(o,{fullWidth:!0,size:"large",variant:"primary",onClick:c,"data-cy":"log-in-button"},"Login")))}const r=C.div`
1
+ import a,{useState as T}from"react";import C from"styled-components";import{Button as o}from"@redocly/theme/components/Button/Button";import{isEmailValid as x}from"../../pages/DevLogin/utils";import{DEFAULT_AUTHENTICATED_TEAM as n}from"../../../../constants/common";import{TeamsSelector as g}from"./styled";function F({teams:s,formState:{values:t,errors:l},setFormState:m,onSubmit:d}){const[u,p]=T([]),f=()=>m({values:t,errors:void 0}),c=()=>{t?.email&&x(t.email)?(d(),l&&f()):m({values:t,errors:{...l,email:"Please enter a correct email"}})},h=({target:e})=>{m({errors:l,values:{...t,[e.name]:e.value}})},E=({target:e})=>{const i=[...t?.teams||[],e.name];e.checked?m({errors:l,values:{...t,teams:i}}):m({errors:l,values:{...t,teams:t.teams?.filter(k=>k!==e.name)}})},b=e=>{e.preventDefault(),c()},y=()=>{const e=prompt("Enter custom team name");e&&p(i=>[...i,e])};return a.createElement("form",{id:"main_form",onSubmit:b},a.createElement("div",{className:"email-screen"},a.createElement("span",{className:"label",id:"email-label","data-cy":"email-input-label"},"Email address"),a.createElement("input",{type:"email",id:"email","aria-labelledby":"email-label",name:"email",autoFocus:!0,value:t?.email||"",onChange:h,"data-cy":"email-input",className:l?.email?"error":""}),l?.email&&a.createElement("span",{id:"email-screen-email-error",className:"email-error-block","data-cy":"incorrect-email-error"},l.email),a.createElement(a.Fragment,null,a.createElement("span",{className:"label","data-cy":"email-input-label"},"Select teams"),a.createElement(g,null,s.concat(u).map(e=>a.createElement(r,{key:e},a.createElement("input",{type:"checkbox",name:e,id:e,checked:t?.teams?.includes(e)||!1,onChange:E}),a.createElement("label",{htmlFor:e},e))),a.createElement(r,{key:n},a.createElement("input",{type:"checkbox",name:n,id:n,disabled:!0,checked:!0}),a.createElement("label",{htmlFor:n,title:"This team is enabled for all authenticated users by default"},n)),a.createElement(o,{type:"button",size:"small",fullWidth:!0,onClick:y},"+ Add custom team"))),a.createElement(o,{fullWidth:!0,size:"large",variant:"primary",onClick:c,"data-cy":"log-in-button"},"Login")))}const r=C.div`
2
2
  display: flex;
3
3
  overflow: hidden;
4
4
 
@@ -1,4 +1,4 @@
1
- import e,{useState as m}from"react";import s from"styled-components";import{Button as d}from"@redocly/theme/components/Button/Button";import{H3 as p}from"@redocly/theme/components/Typography/H3";import{ServerRoutes as u}from"../../../../constants/api";import{withPathPrefix as f}from"@redocly/theme/core/utils";function g(){const t=new URLSearchParams(window.location.search);return{code:t.get("code")||"",org:t.get("org")||"",invitedBy:t.get("invitedBy")||""}}function P(){const{code:t,org:a,invitedBy:i}=g(),[o,l]=m(t),c=async r=>{if(r.preventDefault(),!o)return;const n=new URL(f(u.IDP_LOGIN),window.location.href);n.searchParams.set("inviteCode",o),n.searchParams.set("redirectTo","/"),window.location.href=n.toString()};return e.createElement(v,null,e.createElement(p,null,"Join ",a||"Redocly"),e.createElement("form",{onSubmit:c},e.createElement("div",{className:"form-content"},!t&&e.createElement(e.Fragment,null,e.createElement("span",{className:"label"},"Enter your invite code"),e.createElement("input",{type:"text",value:o,onChange:r=>l(r.target.value),placeholder:"Invite code",autoFocus:!0})),i&&e.createElement("div",{className:"invite-info"},e.createElement("strong",null,i)," has invited you to join"),e.createElement(d,{fullWidth:!0,size:"large",variant:"primary",type:"submit",disabled:!o},"Accept invite"))))}const v=s.div`
1
+ import e,{useState as m}from"react";import d from"styled-components";import{Button as s}from"@redocly/theme/components/Button/Button";import{H3 as p}from"@redocly/theme/components/Typography/H3";import{ServerRoutes as u}from"../../../../constants/api";import{withPathPrefix as f}from"@redocly/theme/core/utils";function g(){const t=new URLSearchParams(window.location.search);return{code:t.get("code")||"",org:t.get("org")||"",invitedBy:t.get("invitedBy")||""}}function P(){const{code:t,org:a,invitedBy:i}=g(),[o,l]=m(t),c=async r=>{if(r.preventDefault(),!o)return;const n=new URL(f(u.IDP_LOGIN),window.location.href);n.searchParams.set("inviteCode",o),n.searchParams.set("redirectTo","/"),window.location.href=n.toString()};return e.createElement(v,null,e.createElement(p,null,"Join ",a||"Redocly"),e.createElement("form",{onSubmit:c},e.createElement("div",{className:"form-content"},!t&&e.createElement(e.Fragment,null,e.createElement("span",{className:"label",id:"invite-code-label"},"Enter your invite code"),e.createElement("input",{type:"text","aria-labelledby":"invite-code-label",value:o,onChange:r=>l(r.target.value),placeholder:"Invite code",autoFocus:!0})),i&&e.createElement("div",{className:"invite-info"},e.createElement("strong",null,i)," has invited you to join"),e.createElement(s,{fullWidth:!0,size:"large",variant:"primary",type:"submit",disabled:!o},"Accept invite"))))}const v=d.div`
2
2
  font-family: var(--font-family-base);
3
3
  display: flex;
4
4
  justify-content: center;
@@ -1,15 +1,15 @@
1
- import*as e from"react";import a from"styled-components";import{useQuery as f}from"@tanstack/react-query";import{useParams as b}from"react-router";import{Box as h,Flex as v}from"@redocly/portal-legacy-ui";import{Admonition as y}from"@redocly/theme/markdoc/components/Admonition/Admonition";import{useTranslate as E}from"../../../../client/app/hooks";import x from"../../../../client/app/CircularProgress";import{Container as A}from"./components/Container";import w from"./components/AppOverview";import T from"./components/ApiKeys";import C from"./components/ApiLogs";import k from"./components/CreateApiKey";import{Description as I}from"./components/Description";import{CallbackUrl as _}from"./components/CallbackUrl";const B="app-tab-actions-portal",m="api_keys",P="logs";function L(D){const{id:s}=b(),{isLoading:p,error:l,data:t}=f({queryKey:["APP:"+s],queryFn:()=>fetch(`/api/api-keys/apps/${s}`).then(n=>n.json())}),{translate:c}=E(),d=n=>o.findIndex(r=>r.id===n),o=[{id:m,name:c("dev.main.tab.appKeys","API keys"),content:e.createElement(e.Fragment,null,e.createElement(T,{credentials:t?.credentials,attributes:t?.attributes}),e.createElement(I,{text:t?.description}),t?.callbackUrl!==void 0&&e.createElement(_,{text:t.callbackUrl})),show:!0},{id:P,name:c("dev.main.tab.logs","Logs"),content:e.createElement(C,null),show:!!t?.supportsLogs}].filter(n=>n.show),[i,g]=e.useState(0),u=t?.canCreateKey&&i===d(m);return e.createElement(h,{my:"2rem"},e.createElement(A,null,p?e.createElement(v,{justifyContent:"center",mt:"2em"},e.createElement(x,null)):l?e.createElement(y,{type:"danger"},l?.message??"Something went wrong"):t?e.createElement(e.Fragment,null,e.createElement(w,{app:t}),e.createElement(K,null,e.createElement(S,null,o.map(({name:n},r)=>e.createElement("span",{key:n,className:i===r?"active":"",onClick:()=>g(r)},n))),e.createElement("div",{id:B}),u&&e.createElement(k,{initialEnabledApis:(t.credentials||[]).flatMap(n=>n.apiProductIds||[])})),e.createElement(z,null,o[i].content)):null))}const W=a.i`
1
+ import*as e from"react";import i from"styled-components";import{useQuery as v}from"@tanstack/react-query";import{useParams as x}from"react-router";import{Box as y,Flex as E}from"@redocly/portal-legacy-ui";import{Admonition as A}from"@redocly/theme/markdoc/components/Admonition/Admonition";import{useTabList as w}from"@redocly/theme/core/hooks";import{useTranslate as T}from"../../../../client/app/hooks";import C from"../../../../client/app/CircularProgress";import{Container as P}from"./components/Container";import k from"./components/AppOverview";import I from"./components/ApiKeys";import L from"./components/ApiLogs";import _ from"./components/CreateApiKey";import{Description as B}from"./components/Description";import{CallbackUrl as K}from"./components/CallbackUrl";const S="app-tab-actions-portal",p="api_keys",z="logs";function D(U){const{id:s}=x(),{isLoading:m,error:l,data:t}=v({queryKey:["APP:"+s],queryFn:()=>fetch(`/api/api-keys/apps/${s}`).then(a=>a.json())}),{translate:c}=T(),d=a=>r.findIndex(o=>o.id===a),r=[{id:p,name:c("dev.main.tab.appKeys","API keys"),content:e.createElement(e.Fragment,null,e.createElement(I,{credentials:t?.credentials,attributes:t?.attributes}),e.createElement(B,{text:t?.description}),t?.callbackUrl!==void 0&&e.createElement(K,{text:t.callbackUrl})),show:!0},{id:z,name:c("dev.main.tab.logs","Logs"),content:e.createElement(L,null),show:!!t?.supportsLogs}].filter(a=>a.show),[n,g]=e.useState(0),{tabListProps:u,getTabProps:b,getTabPanelProps:f}=w({count:r.length,activeIndex:n,onChange:g}),h=t?.canCreateKey&&n===d(p);return e.createElement(y,{my:"2rem"},e.createElement(P,null,m?e.createElement(E,{justifyContent:"center",mt:"2em"},e.createElement(C,null)):l?e.createElement(A,{type:"danger"},l?.message??"Something went wrong"):t?e.createElement(e.Fragment,null,e.createElement(k,{app:t}),e.createElement(F,null,e.createElement(O,{...u},r.map(({name:a},o)=>e.createElement("span",{key:a,className:n===o?"active":"",...b(o)},a))),e.createElement("div",{id:S}),h&&e.createElement(_,{initialEnabledApis:(t.credentials||[]).flatMap(a=>a.apiProductIds||[])})),e.createElement(j,{...f(n)},r[n].content)):null))}const ae=i.i`
2
2
  display: inline-block;
3
3
  width: 12px;
4
4
  height: 12px;
5
5
  background-repeat: no-repeat;
6
6
  background-size: 12px 12px;
7
7
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='black' viewBox='0 0 451.847 451.847' width='12px' height='12px' %3E%3Cpath d='M225.923 354.706c-8.098 0-16.195-3.092-22.369-9.263L9.27 151.157c-12.359-12.359-12.359-32.397 0-44.751 12.354-12.354 32.388-12.354 44.748 0l171.905 171.915 171.906-171.909c12.359-12.354 32.391-12.354 44.744 0 12.365 12.354 12.365 32.392 0 44.751L248.292 345.449c-6.177 6.172-14.274 9.257-22.369 9.257z' /%3E%3C/svg%3E");
8
- `,K=a.div`
8
+ `,F=i.div`
9
9
  display: flex;
10
10
  justify-content: space-between;
11
11
  align-items: end;
12
- `,S=a.div`
12
+ `,O=i.div`
13
13
  display: flex;
14
14
  align-items: center;
15
15
  height: 42px;
@@ -27,6 +27,6 @@ import*as e from"react";import a from"styled-components";import{useQuery as f}fr
27
27
  > span.active {
28
28
  border-bottom: 2px solid var(--border-color-primary);
29
29
  }
30
- `,z=a.div`
30
+ `,j=i.div`
31
31
  margin-top: var(--spacing-base);
32
- `;export{W as ArrowIcon,B as TAB_ACTIONS_PORTAL_ID,L as default};
32
+ `;export{ae as ArrowIcon,S as TAB_ACTIONS_PORTAL_ID,D as default};
@@ -1,5 +1,5 @@
1
1
  import{getCallableApis as n,isFetchCapable as o}from"../../../gateway-mcp/callable-apis.js";import{FETCH_TOOL_NAME as i}from"../../constants.js";const l=["```ts","/**"," * Call a documented API. Only the callable APIs' documented hosts are reachable."," * Responses cap at 1 MiB and redirects are not followed."," */","declare function fetch(url: string | URL, init?: {"," method?: string;"," /** `content-type` is set automatically for URLSearchParams/FormData unless you set one. */"," headers?: Record<string, string>;"," /** string \xB7 URLSearchParams (\u2192 application/x-www-form-urlencoded) \xB7 FormData (\u2192 multipart/form-data) */"," body?: string | URLSearchParams | FormData;"," /** Security scheme name (from the operation's `security` or `getSecuritySchemes`) whose credential to send. Omit to let the server choose. */"," securityScheme?: string;","}): Promise<{"," status: number;"," statusText: string;"," ok: boolean;"," headers: { get(name: string): string | null };"," json(): Promise<unknown>;"," text(): Promise<string>;","}>;","```"].join(`
2
- `),c=["listApis","getEndpoints","getEndpointInfo"],h=["Example - list the paid invoices of a billing API:","","```ts","const { items } = await tools.listApis({ filter: 'billing' });","const [api] = items;",`if (!api) return { error: 'no API matches "billing"' };`,"const { endpoints } = await tools.getEndpoints({ name: api.name });","const ep = endpoints.find((e) => e.method === 'GET' && e.path.includes('invoices'));","const info = await tools.getEndpointInfo({ name: api.name, path: ep.path, method: ep.method });","// Replace every `{placeholder}` first.","const url = new URL((info.servers?.[0]?.url ?? '') + ep.path);","// Every name below comes from `info.endpoint.parameters` - read them first, never guess them.","url.searchParams.set('filter', 'status:paid');","url.searchParams.set('sort', '-createdTime');","url.searchParams.set('limit', '100');","try {"," const res = await fetch(url, { method: ep.method });"," return res.ok ? await res.json() : { status: res.status, error: await res.text() };","} catch (error) {"," return { error: error instanceof Error ? error.message : String(error), endpointId: info };","}","```"].join(`
3
- `),p=["Example - call an endpoint you already read:","","```ts","// Call `getEndpointInfo` first for the server URL, the path, and the parameter names.","// Replace every `{placeholder}` first.","const url = new URL('https://api.example.com/invoices');","url.searchParams.set('filter', 'status:paid');","try {"," const res = await fetch(url, { method: 'GET' });"," return res.ok ? await res.json() : { status: res.status, error: await res.text() };","} catch (error) {"," return { error: error instanceof Error ? error.message : String(error) };","}","```"].join(`
4
- `);function m(r){return c.every(t=>r.includes(t))?h:p}function d(r){const t=new Map;for(const{name:e,version:a}of r){if(!e)continue;const s=t.get(e)??[];a&&s.push(a),t.set(e,s)}return[...t.entries()].map(([e,a])=>a.length>1?`${e} (${a.map(s=>`v${s}`).join(", ")})`:e).join(", ")}const g={id:i,purpose:"Call the documented APIs over HTTP",isActive:o,annotations:{readOnlyHint:!1,destructiveHint:!0,openWorldHint:!0},renderSection(r,t){const e=d(n(r));return["## `fetch` \u2014 call the documented APIs","",l,"","Always call `getEndpointInfo` before `fetch` for the exact URL, required parameters, and body shape. Put filtering, sorting, and paging in the query parameters, and keep sandbox JS for what the endpoint cannot express. Never guess.","","A `servers[].url` or an endpoint path can hold `{placeholders}`. Replace every one before you call `fetch`. Use the server variable `default`. Ask the user when the `default` only repeats the variable name, such as `{organizationId}`.","","When an operation accepts more than one security scheme, pass `securityScheme` with the scheme name the user holds a credential for; without it the server picks one, and a mismatched credential fails upstream.","",`Callable APIs (their documented hosts are reachable via \`fetch\`): ${e}`,"",m(t)].join(`
2
+ `),c=["listApis","getEndpoints","getEndpointInfo"],p=["Example - list the paid invoices of a billing API:","","```ts","const { items } = await tools.listApis({ filter: 'billing' });","const [api] = items;",`if (!api) return { error: 'no API matches "billing"' };`,"const { endpoints } = await tools.getEndpoints({ name: api.name });","const ep = endpoints.find((e) => e.method === 'GET' && e.path.includes('invoices'));","const info = await tools.getEndpointInfo({ name: api.name, path: ep.path, method: ep.method });","// Replace every `{placeholder}` first.","const url = new URL((info.servers?.[0]?.url ?? '') + ep.path);","// Every name below comes from `info.endpoint.parameters` - read them first, never guess them.","url.searchParams.set('filter', 'status:paid');","url.searchParams.set('sort', '-createdTime');","url.searchParams.set('limit', '100');","try {"," const res = await fetch(url, { method: ep.method });"," return res.ok ? await res.json() : { status: res.status, error: await res.text() };","} catch (error) {"," return { error: error instanceof Error ? error.message : String(error), endpointId: info };","}","```"].join(`
3
+ `),d=["Example - call an endpoint you already read:","","```ts","// Call `getEndpointInfo` first for the server URL, the path, and the parameter names.","// Replace every `{placeholder}` first.","const url = new URL('https://api.example.com/invoices');","url.searchParams.set('filter', 'status:paid');","try {"," const res = await fetch(url, { method: 'GET' });"," return res.ok ? await res.json() : { status: res.status, error: await res.text() };","} catch (error) {"," return { error: error instanceof Error ? error.message : String(error) };","}","```"].join(`
4
+ `);function h(r){return c.every(t=>r.includes(t))?p:d}function m(r){const t=new Map;for(const{name:e,version:a}of r){if(!e)continue;const s=t.get(e)??[];a&&s.push(a),t.set(e,s)}return[...t.entries()].map(([e,a])=>a.length>1?`${e} (${a.map(s=>`v${s}`).join(", ")})`:e).join(", ")}const g={id:i,purpose:"Call the documented APIs over HTTP",isActive:o,annotations:{readOnlyHint:!1,destructiveHint:!0,openWorldHint:!0},renderSection(r,t){const e=m(n(r));return["## `fetch` \u2014 call the documented APIs","",l,"","Always call `getEndpointInfo` before `fetch` for the exact URL, required parameters, and body shape. Put filtering, sorting, and paging in the query parameters, and keep sandbox JS for what the endpoint cannot express. Never guess.","","A `servers[].url` or an endpoint path can hold `{placeholders}`. Replace every one before you call `fetch`. Use the server variable `default`. Ask the user when the `default` only repeats the variable name, such as `{organizationId}`.","",`Callable APIs (their documented hosts are reachable via \`fetch\`): ${e}`,"",h(t)].join(`
5
5
  `)}};export{l as FETCH_DECLARATION,g as fetchCapability};
@@ -1,2 +1,2 @@
1
- import{MANAGE_API_CREDENTIALS_TOOL_NAME as P}from"../../constants.js";import{acceptedCredentialsLink as k,buildCredentialsAcceptedHint as w,buildUpstreamCredentialFallback as S,buildUpstreamCredentialInputRequired as I,credentialElicitationAnswer as E,dedupeElicitation as C,isStatelessRequest as j,schemeLabelOf as q,supportsUrlElicitation as T}from"../../gateway-mcp/elicitation.js";import{getCallableApis as u,isFetchCapable as H}from"../../gateway-mcp/callable-apis.js";import{secretStore as R}from"../../gateway-mcp/secret-store.js";import{MISSING_SESSION_HANDLE_MESSAGE as U,resolveSecretSubject as $}from"../../gateway-mcp/subject.js";import{upstreamAuthKey as L}from"../../gateway-mcp/upstream-auth.js";import{resolveUpstreamApiAuth as N}from"../../gateway-mcp/upstream-auth-scheme.js";import{apiTitleKey as O,countApiTitles as _}from"../../docs-mcp/utils/api-titles.js";function Q(e){return!!e.elicitationEnabled&&H(e)}function V(e){const s=u(e),t=s.map(i=>i.relativePath),r=[...t,...M(s)];return{name:P,title:"Manage API credentials",description:["Get a secure link where the user sets, replaces, or removes the credentials this server sends to a documented API.","Call it when the user wants to change or clear an API key, or when a tool result tells you the stored credentials were rejected.","The link covers every credential the API accepts, so scheme is optional - pass it only when a tool result names the scheme that was rejected.","It returns a link for the user to open - never ask for the secret in the conversation and never pass it as an argument."].join(`
2
- `),annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},schema:{type:"object",required:["api"],additionalProperties:!1,properties:{api:{type:"string",description:`The API to manage credentials for: its \`filePath\` or the \`name\` from \`listApis\`.${t.length>0?` The file paths are: ${t.join(", ")}.`:""}`,minLength:1,...r.length>0?{enum:r}:{}},scheme:{type:"string",description:"Name of the security scheme to manage the credential for, as named in the API description (a tool result that reports a rejected credential names it). Omit it to let the link cover every scheme the API accepts.",minLength:1},sessionHandle:{type:"string",description:"Handle returned by createAnonymousSession. Required while the caller is anonymous, so the credentials are stored against the same subject the API calls use.",minLength:1}}}}}function M(e){const s=_(e);return e.map(t=>t.name).filter(t=>s.get(O(t))===1)}function X(e,s){return async(t,r)=>{const i=$(e.user,typeof t.sessionHandle=="string"?t.sessionHandle:void 0);if(i.kind==="invalid")return m(i.message);if(i.kind==="anonymous")return m(U);const{subject:c}=i,p=typeof t.api=="string"?t.api:"",n=u(e).find(o=>o.relativePath===p||o.name===p);if(!n){const o=u(e).map(b=>b.relativePath).join(", ");return m(`Unknown api "${p}". Credentials can only be managed for the APIs this server can call: ${o||"none"}.`)}const d=E(r.mcpReq.inputResponses);if(d==="accepted"){const o=await k(r.mcpReq.requestState(),{kv:await s.getKv(),subject:c,baseUrl:e.baseUrl??"",apis:[n.relativePath]});return{content:[{type:"text",text:w(n.name,o?.url)}],isError:!1}}if(d==="declined")return{content:[{type:"text",text:`The user declined the credentials prompt for the "${n.name}" API, so this call minted no link.`}],isError:!1};const a=await N(e,n.relativePath),h=typeof t.scheme=="string"?t.scheme:void 0,y=Object.keys(a.schemes);if(h&&!a.schemes[h])return m(`Unknown scheme "${h}" for api "${n.relativePath}". The security schemes this server can store credentials for are: ${y.join(", ")}.`);const l=h??a.defaultScheme,f=q(a.schemes,l),A=await s.getKv(),v={subject:c,baseUrl:e.baseUrl??"",target:{api:n.relativePath,name:n.name,scheme:l,...f?{schemeLabel:f}:{}},auth:a.schemes[l],apiAuth:a,kv:A,hasExistingSecret:await R.hasSecret(A,c,n.relativePath,l),reason:{kind:"requested"}};if(j(r.mcpReq)&&T(s.getClientCapabilities()))return await I(v);const g=L({api:n.relativePath,scheme:l});return{content:[{type:"text",text:await C(`${c}:${g}`,()=>S(v))}],isError:!1}}}function m(e){return{content:[{type:"text",text:e}],isError:!0}}export{Q as isManageApiCredentialsToolAvailable,X as manageApiCredentials,V as manageApiCredentialsSchema};
1
+ import{MANAGE_API_CREDENTIALS_TOOL_NAME as P}from"../../constants.js";import{acceptedCredentialsLink as k,buildCredentialsAcceptedHint as w,buildUpstreamCredentialFallback as S,buildUpstreamCredentialInputRequired as I,credentialElicitationAnswer as E,dedupeElicitation as C,isStatelessRequest as j,schemeLabelOf as q,supportsUrlElicitation as T}from"../../gateway-mcp/elicitation.js";import{getCallableApis as u,isFetchCapable as H}from"../../gateway-mcp/callable-apis.js";import{secretStore as R}from"../../gateway-mcp/secret-store.js";import{MISSING_SESSION_HANDLE_MESSAGE as U,resolveSecretSubject as $}from"../../gateway-mcp/subject.js";import{upstreamAuthKey as L}from"../../gateway-mcp/upstream-auth.js";import{resolveUpstreamApiAuth as N}from"../../gateway-mcp/upstream-auth-scheme.js";import{apiTitleKey as O,countApiTitles as _}from"../../docs-mcp/utils/api-titles.js";function Q(e){return!!e.elicitationEnabled&&H(e)}function V(e){const s=u(e),t=s.map(r=>r.relativePath),i=[...t,...M(s)];return{name:P,title:"Manage API credentials",description:["Get a secure link where the user sets, replaces, or removes the credentials this server sends to a documented API.","Call it when the user wants to change or clear an API key, or when a tool result tells you the stored credentials were rejected.","The link covers every credential the API accepts, so scheme is optional - pass it only when a tool result names the scheme that was rejected.","It returns a link for the user to open - never ask for the secret in the conversation and never pass it as an argument."].join(`
2
+ `),annotations:{readOnlyHint:!1,destructiveHint:!1,idempotentHint:!1,openWorldHint:!1},schema:{type:"object",required:["api"],additionalProperties:!1,properties:{api:{type:"string",description:`The API to manage credentials for: its \`filePath\` or the \`name\` from \`listApis\`.${t.length>0?` The file paths are: ${t.join(", ")}.`:""}`,minLength:1,...i.length>0?{enum:i}:{}},scheme:{type:"string",description:"Name of the security scheme to manage the credential for, as named in the API description (a tool result that reports a rejected credential names it). Omit it to let the link cover every scheme the API accepts.",minLength:1},sessionHandle:{type:"string",description:"Handle returned by createAnonymousSession. Required while the caller is anonymous, so the credentials are stored against the same subject the API calls use.",minLength:1}}}}}function M(e){const s=_(e);return e.map(t=>t.name).filter(t=>s.get(O(t))===1)}function X(e,s){return async(t,i)=>{const r=$(e.user,typeof t.sessionHandle=="string"?t.sessionHandle:void 0);if(r.kind==="invalid")return m(r.message);if(r.kind==="anonymous")return m(U);const{subject:c}=r,p=typeof t.api=="string"?t.api:"",n=u(e).find(l=>l.relativePath===p||l.name===p);if(!n){const l=u(e).map(b=>b.relativePath).join(", ");return m(`Unknown api "${p}". Credentials can only be managed for the APIs this server can call: ${l||"none"}.`)}const d=E(i.mcpReq.inputResponses);if(d==="accepted"){const l=await k(i.mcpReq.requestState(),{kv:await s.getKv(),subject:c,baseUrl:e.baseUrl??"",apis:[n.relativePath]});return{content:[{type:"text",text:w(n.name,l?.url)}],isError:!1}}if(d==="declined")return{content:[{type:"text",text:`The user declined the credentials prompt for the "${n.name}" API, so this call minted no link.`}],isError:!1};const a=await N(e,n.relativePath),o=typeof t.scheme=="string"?t.scheme:void 0,y=Object.keys(a.schemes);if(o&&!a.schemes[o])return m(`Unknown scheme "${o}" for api "${n.relativePath}". The security schemes this server can store credentials for are: ${y.join(", ")}.`);const h=o??a.defaultScheme,f=o?q(a.schemes,o):void 0,A=await s.getKv(),v={subject:c,baseUrl:e.baseUrl??"",target:{api:n.relativePath,name:n.name,scheme:h,...f?{schemeLabel:f}:{}},auth:a.schemes[h],apiAuth:a,kv:A,hasExistingSecret:await R.hasSecret(A,c,n.relativePath,h),reason:{kind:"requested"}};if(j(i.mcpReq)&&T(s.getClientCapabilities()))return await I(v);const g=L({api:n.relativePath,scheme:h});return{content:[{type:"text",text:await C(`${c}:${g}`,()=>S(v))}],isError:!1}}}function m(e){return{content:[{type:"text",text:e}],isError:!0}}export{Q as isManageApiCredentialsToolAvailable,X as manageApiCredentials,V as manageApiCredentialsSchema};
@@ -1,6 +1,6 @@
1
1
  import type { InputRequiredResult } from '@modelcontextprotocol/server';
2
2
  import type { UpstreamAuthRequiredEvent } from './upstream-auth.js';
3
- import type { SchemeChoice, UpstreamApiAuth, UpstreamAuthScheme } from './upstream-auth-scheme.js';
3
+ import type { UpstreamApiAuth, UpstreamAuthScheme } from './upstream-auth-scheme.js';
4
4
  import type { KvService } from '@redocly/config';
5
5
  export declare const CREDENTIALS_INPUT_REQUEST_KEY = "credentials";
6
6
  export type CredentialTarget = {
@@ -71,11 +71,5 @@ export declare function buildUpstreamCredentialFallback(request: CredentialElici
71
71
  * likely a permissions problem than a missing key.
72
72
  */
73
73
  export declare function buildCredentialsToolHint(event: UpstreamAuthRequiredEvent, schemeLabel?: string): string;
74
- /**
75
- * Text for an operation that accepts several security schemes and has no stored credential for
76
- * any of them. Picking one here is what sent an API key as a session cookie, so the model is told
77
- * to ask the user which credential they hold and pass it back as `scheme`.
78
- */
79
- export declare function buildSchemeChoiceHint(event: UpstreamAuthRequiredEvent, choices: readonly SchemeChoice[]): string;
80
74
  export {};
81
75
  //# sourceMappingURL=elicitation.d.ts.map
@@ -1,6 +1,5 @@
1
- import{inputRequired as u,inputResponse as A}from"@modelcontextprotocol/server";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as P}from"../../../constants/mcp.js";import{MANAGE_API_CREDENTIALS_TOOL_NAME as h}from"../constants.js";import{secretStore as d,ELICITATION_TTL_MS as g}from"./secret-store.js";import{operationsForScheme as k,orderedSchemesOf as E}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",l=new Map;function F(e,t){const r=l.get(e);if(r)return r;const n=t().finally(()=>{l.delete(e)});return l.set(e,n),n}function H(e){return!!e?.elicitation?.url}function D(e){return e.envelope!==void 0}function q(e){const t=A(e,m);if(t.kind==="elicit")return t.action==="accept"?"accepted":"declined"}function B(e,t){return[`The user accepted the prompt to add credentials for the "${e}" API.`,...t?["The credentials page:",t]:[],"",`Wait until the user says they saved the credentials, then retry the call that needed them. Do not call \`${h}\` again unless the user asks for a new link.`].join(`
2
- `)}async function Y(e,{kv:t,subject:r,baseUrl:n,apis:a}){if(typeof e!="string")return;const i=await d.peekElicitation(t,e);if(!(i?.subject!==r||!a.includes(i.api)))return{apiName:i.name,url:w(n,e)}}function W(e,t){return Object.keys(e).length>1?t:void 0}async function z(e){const{token:t,url:r}=await $(e);return u({inputRequests:{[m]:u.elicitUrl({message:C(e),url:r})},requestState:t})}async function G(e){const{url:t}=await $(e);return O(e,t,S(e.subject))}function J(e,t){const r=f(e);if(e.status===401&&!e.sentScheme){const[i,o]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${r}. ${i}`,"",o].join(`
3
- `)}const n=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${r}).`:`The "${e.name}" API rejected the request with ${r}.`,a=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[n,"",`Do not retry the same call. Call \`${h}\` with ${a} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
4
- `)}function f({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}function K(e,t){const r=t.map(({name:n,label:a,description:i})=>`- ${n} \u2014 ${a}${i?`: ${i}`:""}`);return[`The "${e.name}" API rejected the request with ${f(e)} and this operation accepts more than one kind of credential:`,"",...r,"","Ask the user which one of these they have, and wait for the answer. Never guess and never reuse a value meant for another kind.",`Then call \`${h}\` with api: "${e.api}" and scheme set to the name they picked to get a link where they enter it.`].join(`
5
- `)}async function $({kv:e,subject:t,baseUrl:r,target:n,auth:a,apiAuth:i,hasExistingSecret:o}){const s=i?await b(e,t,n.api,i):[],c=await d.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:a,hasExistingSecret:o,...s.length>1?{schemes:s}:{}});return{token:c,url:w(r,c)}}async function b(e,t,r,n){const i=(await d.getSecretsForSubject(e,t))[r]??{};return E(n.schemes).map(o=>{const s=k(n,o),c=n.descriptions?.[o];return{name:o,...c?{label:c}:{},auth:n.schemes[o],hasExistingSecret:!!i[o],...s.length>0?{operations:s}:{}}})}function S(e){return e.startsWith(p)?e.slice(p.length):void 0}function C({target:e,hasExistingSecret:t,reason:r}){const n=`The link works for ${T()} minutes.`,a=x(e),i=r.kind==="authFailure"&&r.detail?` The API answered: ${r.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${a}. They go straight to the API and never enter the chat. ${n}`:`${v(a)} needs credentials.${i} Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function x({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function v(e){return e.charAt(0).toUpperCase()+e.slice(1)}function T(){return Math.round(g/6e4)}function w(e,t){const r=I(P);if(!e)return`${r}?token=${t}`;try{const n=new URL(r,e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}${r}?token=${t}`}}function O({target:e,reason:t,hasExistingSecret:r},n,a){const o=[`**Stop and show the user this link. They have to open it in a browser to ${r?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",n,"",`The link is valid ${T()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:r?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],a?`Once the user confirms they saved them, call this tool again with sessionHandle: "${a}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],s=t.kind==="authFailure"?t.otherApiNames??[]:[];if(s.length>0){const c=s.map(y=>`"${y}"`).join(", ");o.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return o.join(`
6
- `)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,Y as acceptedCredentialsLink,B as buildCredentialsAcceptedHint,J as buildCredentialsToolHint,K as buildSchemeChoiceHint,G as buildUpstreamCredentialFallback,z as buildUpstreamCredentialInputRequired,q as credentialElicitationAnswer,F as dedupeElicitation,D as isStatelessRequest,W as schemeLabelOf,H as supportsUrlElicitation};
1
+ import{inputRequired as h,inputResponse as y}from"@modelcontextprotocol/server";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as A}from"../../../constants/mcp.js";import{MANAGE_API_CREDENTIALS_TOOL_NAME as u}from"../constants.js";import{secretStore as l,ELICITATION_TTL_MS as P}from"./secret-store.js";import{operationsForScheme as E,orderedSchemesOf as g}from"./upstream-auth-scheme.js";import{ANONYMOUS_SUBJECT_PREFIX as p}from"./subject.js";const m="credentials",d=new Map;function F(e,t){const r=d.get(e);if(r)return r;const n=t().finally(()=>{d.delete(e)});return d.set(e,n),n}function D(e){return!!e?.elicitation?.url}function H(e){return e.envelope!==void 0}function q(e){const t=y(e,m);if(t.kind==="elicit")return t.action==="accept"?"accepted":"declined"}function B(e,t){return[`The user accepted the prompt to add credentials for the "${e}" API.`,...t?["The credentials page:",t]:[],"",`Wait until the user says they saved the credentials, then retry the call that needed them. Do not call \`${u}\` again unless the user asks for a new link.`].join(`
2
+ `)}async function Y(e,{kv:t,subject:r,baseUrl:n,apis:o}){if(typeof e!="string")return;const i=await l.peekElicitation(t,e);if(!(i?.subject!==r||!o.includes(i.api)))return{apiName:i.name,url:T(n,e)}}function W(e,t){return Object.keys(e).length>1?t:void 0}async function z(e){const{token:t,url:r}=await f(e);return h({inputRequests:{[m]:h.elicitUrl({message:C(e),url:r})},requestState:t})}async function G(e){const{url:t}=await f(e);return _(e,t,S(e.subject))}function J(e,t){const r=b(e);if(e.status===401&&!e.sentScheme){const[i,s]=e.callerHeader?[`Your code set the "${e.callerHeader}" header itself, so the stored credential was not sent.`,"Remove that header from the request and retry. Do not replace the stored credentials."]:["No stored credential was sent with this request, so this result says nothing about the stored credentials.","Do not replace them. Check whether this operation needs a different kind of credential or a different route."];return[`The "${e.name}" API returned ${r}. ${i}`,"",s].join(`
3
+ `)}const n=e.status===401?`The "${e.name}" API rejected the stored ${t?`"${t}" `:""}credential (${r}).`:`The "${e.name}" API rejected the request with ${r}.`,o=t?`api: "${e.api}" and scheme: "${t}"`:`api: "${e.api}"`;return[n,"",`Do not retry the same call. Call \`${u}\` with ${o} to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
4
+ `)}function b({status:e,detail:t}){return t?`HTTP ${e}: ${t}`:`HTTP ${e}`}async function f({kv:e,subject:t,baseUrl:r,target:n,auth:o,apiAuth:i,hasExistingSecret:s}){const a=i?await k(e,t,n.api,i):[],c=await l.createElicitation(e,{subject:t,api:n.api,name:n.name,scheme:n.scheme,...n.schemeLabel?{schemeLabel:n.schemeLabel}:{},auth:o,hasExistingSecret:s,...a.length>1?{schemes:a}:{}});return{token:c,url:T(r,c)}}async function k(e,t,r,n){const i=(await l.getSecretsForSubject(e,t))[r]??{};return g(n.schemes).map(s=>{const a=E(n,s),c=n.descriptions?.[s];return{name:s,...c?{label:c}:{},auth:n.schemes[s],hasExistingSecret:!!i[s],...a.length>0?{operations:a}:{}}})}function S(e){return e.startsWith(p)?e.slice(p.length):void 0}function C({target:e,hasExistingSecret:t,reason:r}){const n=`The link works for ${$()} minutes.`,o=x(e),i=r.kind==="authFailure"&&r.detail?` The API answered: ${r.detail}.`:"";return t?`Open this page in a browser to replace or remove the stored credentials for ${o}. They go straight to the API and never enter the chat. ${n}`:`${O(o)} needs credentials.${i} Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function x({name:e,schemeLabel:t}){return t?`the "${t}" scheme of the "${e}" API`:`the "${e}" API`}function O(e){return e.charAt(0).toUpperCase()+e.slice(1)}function $(){return Math.round(P/6e4)}function T(e,t){const r=I(A);if(!e)return`${r}?token=${t}`;try{const n=new URL(r,e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}${r}?token=${t}`}}function _({target:e,reason:t,hasExistingSecret:r},n,o){const s=[`**Stop and show the user this link. They have to open it in a browser to ${r?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",n,"",`The link is valid ${$()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status}${t.detail?` (${t.detail})`:""} and needs credentials.`:r?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,...e.schemeLabel?[`The operation authenticates with its "${e.schemeLabel}" security scheme.`]:[],o?`Once the user confirms they saved them, call this tool again with sessionHandle: "${o}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],a=t.kind==="authFailure"?t.otherApiNames??[]:[];if(a.length>0){const c=a.map(w=>`"${w}"`).join(", ");s.push("",`The remaining APIs (${c}) will prompt for credentials on subsequent calls.`)}return s.join(`
5
+ `)}export{m as CREDENTIALS_INPUT_REQUEST_KEY,Y as acceptedCredentialsLink,B as buildCredentialsAcceptedHint,J as buildCredentialsToolHint,G as buildUpstreamCredentialFallback,z as buildUpstreamCredentialInputRequired,q as credentialElicitationAnswer,F as dedupeElicitation,H as isStatelessRequest,W as schemeLabelOf,D as supportsUrlElicitation};
@@ -1 +1 @@
1
- import{Agent as Q}from"undici";import{envConfig as Z}from"../../../../config/env-config.js";import{logger as N}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as I}from"./ssrf-guard.js";import{createSsrfSafeLookup as q}from"./ssrf-safe-lookup.js";import{SsrfBlockedError as W}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as R,GatewayFetchSystemError as ee,GatewayFetchUserError as g}from"./fetch-errors.js";import{redactHeaders as J,redactSecretsFromString as te}from"./redact.js";import{extractUpstreamDetail as re}from"./upstream-detail.js";import{normalizeFetchPath as ne}from"./normalize-path.js";import{reportGatewayFetch as K}from"./fetch-telemetry.js";import{logGatewayFetchAudit as V}from"./fetch-audit.js";const X="x-forwarded-for",oe=3e4,se=1024*1024,ae=new Set([401,403,407]);let C;function ce(){if(!C){const e=Z.isDevelopMode;e&&N.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),C=new Q({connect:{lookup:q({allowLoopback:e})}})}return C}function _e({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:a=ce(),timeoutMs:h=oe,maxResponseBytes:l=se,maxFetchesPerExec:w,clientIp:k}){const f=[...e,...t.flatMap(o=>o.matchers)],$=[...new Set(t.map(o=>o.name).filter(Boolean))].join(", "),m=t.flatMap(o=>Object.values(o.secrets??{})),D=[r,...m.map(o=>o.value)].filter(o=>!!o),x=m.map(o=>o.header);let O=0;return async o=>{const y=o.method??"GET",T=Date.now();let B,b,A,E={},H,_,j;try{if(w!==void 0){const u=Math.max(0,w);if(O+=1,O>u)throw new g(`Outbound fetch budget exceeded (max ${u} fetches per execute); split the work across calls`)}const d=le(o.url,f,$);B=d.hostname,b=ne(d.pathname);const v=t.filter(u=>u.matchers.some(z=>z.test(d.hostname))),s=v.length===1?v[0]:void 0;A=s?.slug;const S=pe(o.securityScheme,s),F=S?[S]:s?ye(s,d.pathname,y):void 0,L=s&&F?Se(s,F):void 0,P=L?s?.secrets?.[L]:void 0,M=be({callerHeaders:o.headers,secret:P,authorization:r,clientIp:k,credentialHeaders:s?.credentialHeaders});E=M.headers,H=P&&!M.callerHeader?L:void 0;const G=new AbortController;j=setTimeout(()=>G.abort(),h);const i=await c(d,{method:y,headers:E,body:o.body,redirect:"manual",signal:G.signal,dispatcher:a});_=i.status;let U="";try{U=await we(i,l)}finally{if(ae.has(i.status)&&s&&n){const u=re(U,i.headers.get("content-type"),D);n({api:s.slug,name:s.name,status:i.status,...L?{scheme:L}:{},...F?.length?{schemes:[...F]}:{},...H?{sentScheme:H}:{},...M.callerHeader?{callerHeader:M.callerHeader}:{},...u?{detail:u}:{}})}}let p=Ae(i.headers);if(fe(i.status)){p=J(p,x);const{location:u}=p;u&&!he(u,d,f)&&delete p.location}return K({host:d.hostname,method:y,outcome:"success",durationMs:Date.now()-T,path:b,api:A,status:i.status,bytes:Buffer.byteLength(U,"utf8")}),V({rawUrl:d.href,method:y,host:d.hostname,outcome:"success",durationMs:Date.now()-T,injectedSecretValues:D,path:b,api:A,status:i.status,injectedScheme:H,requestHeaders:E,requestBody:o.body,responseHeaders:p,responseBody:U}),{status:i.status,statusText:i.statusText,ok:i.ok,headers:p,bodyText:U}}catch(d){const{outcome:v,userSafe:s}=ie(d),S=B??(s instanceof R?s.host:void 0)??ue(o.url)??"invalid-url";throw K({host:S,method:y,outcome:v,durationMs:Date.now()-T,path:b,api:A,status:_}),V({rawUrl:o.url,method:y,host:S,outcome:v,durationMs:Date.now()-T,injectedSecretValues:D,path:b,api:A,status:_,injectedScheme:H,requestHeaders:E,requestBody:o.body}),s||(N.error(`Gateway MCP fetch failed [${y} ${S}]: ${te(me(d),D)} headers=${JSON.stringify(J(E,x))}`),new ee("Upstream fetch failed due to an internal error"))}finally{j!==void 0&&clearTimeout(j)}}}function de(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof g||t instanceof W)return t;t=t.cause}}function ie(e){const t=de(e);return{outcome:t instanceof R||t instanceof W?"blocked":t?"user_error":"system_error",userSafe:t}}function le(e,t,r){try{return I(e,t)}catch(n){throw n instanceof R&&r?new R(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function ue(e){try{return new URL(e).hostname||void 0}catch{return}}function fe(e){return e>=300&&e<400}function he(e,t,r){try{return!!I(new URL(e,t).href,r)}catch{return!1}}function me(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function we(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw Y(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let a=0,h="";try{for(;;){const{done:l,value:w}=await n.read();if(l)break;if(a+=w.byteLength,a>t)throw Y(t,a);h+=c.decode(w,{stream:!0})}return h+c.decode()}finally{await n.cancel().catch(()=>{})}}function Y(e,t){return new g(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function ye(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:ge(e)}function Se(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const a=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>a?c:n})}function pe(e,t){if(e==null)return;if(typeof e!="string")throw new g("securityScheme must be a security scheme name (a string)");if(!t)throw new g("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new g(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function ge(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function be({callerHeaders:e,secret:t,authorization:r,clientIp:n,credentialHeaders:c}){const a={},h=new Set,l=t??(r?{header:"Authorization",value:r}:void 0),w=l?new Set((c??[]).map(f=>f.toLowerCase()).filter(f=>f!==l.header.toLowerCase())):new Set;for(const[f,$]of Object.entries(e??{})){const m=f.toLowerCase();m==="host"||m==="content-length"||m!==X&&(w.has(m)||(h.add(m),a[f]=$))}if(n&&(a[X]=n),!l)return{headers:a};const k=l.header.toLowerCase();return h.has(k)?{headers:a,callerHeader:k}:(a[l.header]=l.value,{headers:a})}function Ae(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{_e as buildFetchBridge,ce as getDefaultAgent,we as readCappedBody};
1
+ import{Agent as Q}from"undici";import{envConfig as Z}from"../../../../config/env-config.js";import{logger as N}from"../../../../tools/notifiers/logger.js";import{assertUrlAllowed as I}from"./ssrf-guard.js";import{createSsrfSafeLookup as q}from"../../../../web-server/utils/ssrf-safe-lookup.js";import{SsrfBlockedError as W}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as R,GatewayFetchSystemError as ee,GatewayFetchUserError as g}from"./fetch-errors.js";import{redactHeaders as J,redactSecretsFromString as te}from"./redact.js";import{extractUpstreamDetail as re}from"./upstream-detail.js";import{normalizeFetchPath as ne}from"./normalize-path.js";import{reportGatewayFetch as K}from"./fetch-telemetry.js";import{logGatewayFetchAudit as V}from"./fetch-audit.js";const X="x-forwarded-for",oe=3e4,se=1024*1024,ae=new Set([401,403,407]);let C;function ce(){if(!C){const e=Z.isDevelopMode;e&&N.warn("Gateway MCP SSRF guard: loopback upstreams are ALLOWED (develop mode only, never in runtime)."),C=new Q({connect:{lookup:q({allowLoopback:e})}})}return C}function _e({allowedHosts:e,apis:t=[],authorization:r,onAuthRequired:n,fetchImpl:c=fetch,dispatcher:a=ce(),timeoutMs:h=oe,maxResponseBytes:l=se,maxFetchesPerExec:w,clientIp:k}){const f=[...e,...t.flatMap(o=>o.matchers)],$=[...new Set(t.map(o=>o.name).filter(Boolean))].join(", "),m=t.flatMap(o=>Object.values(o.secrets??{})),D=[r,...m.map(o=>o.value)].filter(o=>!!o),x=m.map(o=>o.header);let O=0;return async o=>{const y=o.method??"GET",T=Date.now();let B,b,A,E={},H,_,j;try{if(w!==void 0){const u=Math.max(0,w);if(O+=1,O>u)throw new g(`Outbound fetch budget exceeded (max ${u} fetches per execute); split the work across calls`)}const d=le(o.url,f,$);B=d.hostname,b=ne(d.pathname);const v=t.filter(u=>u.matchers.some(z=>z.test(d.hostname))),s=v.length===1?v[0]:void 0;A=s?.slug;const S=pe(o.securityScheme,s),F=S?[S]:s?ye(s,d.pathname,y):void 0,L=s&&F?Se(s,F):void 0,P=L?s?.secrets?.[L]:void 0,M=be({callerHeaders:o.headers,secret:P,authorization:r,clientIp:k,credentialHeaders:s?.credentialHeaders});E=M.headers,H=P&&!M.callerHeader?L:void 0;const G=new AbortController;j=setTimeout(()=>G.abort(),h);const i=await c(d,{method:y,headers:E,body:o.body,redirect:"manual",signal:G.signal,dispatcher:a});_=i.status;let U="";try{U=await we(i,l)}finally{if(ae.has(i.status)&&s&&n){const u=re(U,i.headers.get("content-type"),D);n({api:s.slug,name:s.name,status:i.status,...L?{scheme:L}:{},...F?.length?{schemes:[...F]}:{},...H?{sentScheme:H}:{},...M.callerHeader?{callerHeader:M.callerHeader}:{},...u?{detail:u}:{}})}}let p=Ae(i.headers);if(fe(i.status)){p=J(p,x);const{location:u}=p;u&&!he(u,d,f)&&delete p.location}return K({host:d.hostname,method:y,outcome:"success",durationMs:Date.now()-T,path:b,api:A,status:i.status,bytes:Buffer.byteLength(U,"utf8")}),V({rawUrl:d.href,method:y,host:d.hostname,outcome:"success",durationMs:Date.now()-T,injectedSecretValues:D,path:b,api:A,status:i.status,injectedScheme:H,requestHeaders:E,requestBody:o.body,responseHeaders:p,responseBody:U}),{status:i.status,statusText:i.statusText,ok:i.ok,headers:p,bodyText:U}}catch(d){const{outcome:v,userSafe:s}=ie(d),S=B??(s instanceof R?s.host:void 0)??ue(o.url)??"invalid-url";throw K({host:S,method:y,outcome:v,durationMs:Date.now()-T,path:b,api:A,status:_}),V({rawUrl:o.url,method:y,host:S,outcome:v,durationMs:Date.now()-T,injectedSecretValues:D,path:b,api:A,status:_,injectedScheme:H,requestHeaders:E,requestBody:o.body}),s||(N.error(`Gateway MCP fetch failed [${y} ${S}]: ${te(me(d),D)} headers=${JSON.stringify(J(E,x))}`),new ee("Upstream fetch failed due to an internal error"))}finally{j!==void 0&&clearTimeout(j)}}}function de(e){let t=e;for(let r=0;r<5&&t instanceof Error;r++){if(t instanceof g||t instanceof W)return t;t=t.cause}}function ie(e){const t=de(e);return{outcome:t instanceof R||t instanceof W?"blocked":t?"user_error":"system_error",userSafe:t}}function le(e,t,r){try{return I(e,t)}catch(n){throw n instanceof R&&r?new R(`${n.message}. \`fetch\` reaches only the documented hosts of the callable APIs (${r}); other documented APIs can be read through the tools but not called.`,n.host):n}}function ue(e){try{return new URL(e).hostname||void 0}catch{return}}function fe(e){return e>=300&&e<400}function he(e,t,r){try{return!!I(new URL(e,t).href,r)}catch{return!1}}function me(e){if(!(e instanceof Error))return String(e);const t=e.cause;return t instanceof Error?`${e.message}: ${t.message}`:e.message}async function we(e,t){const r=Number(e.headers.get("content-length"));if(Number.isFinite(r)&&r>t)throw Y(t,r);if(!e.body)return"";const n=e.body.getReader(),c=new TextDecoder;let a=0,h="";try{for(;;){const{done:l,value:w}=await n.read();if(l)break;if(a+=w.byteLength,a>t)throw Y(t,a);h+=c.decode(w,{stream:!0})}return h+c.decode()}finally{await n.cancel().catch(()=>{})}}function Y(e,t){return new g(`Response is too large (over ${e} bytes, got ${t}); request less data`)}function ye(e,t,r){const n=e.routes?.find(c=>c.method===r&&c.pattern.test(t));return n?n.schemes:ge(e)}function Se(e,t){const r=t.filter(n=>e.secrets?.[n]);return r.length===0?t[0]:r.reduce((n,c)=>{const a=e.secrets?.[n]?.savedAt??0;return(e.secrets?.[c]?.savedAt??0)>a?c:n})}function pe(e,t){if(e==null)return;if(typeof e!="string")throw new g("securityScheme must be a security scheme name (a string)");if(!t)throw new g("securityScheme cannot be applied: the URL host does not identify exactly one callable API");const r=t.schemePriority??[];if(r.length>0&&!r.includes(e))throw new g(`Unknown securityScheme "${e}" for ${t.name}; valid values: ${r.join(", ")}`);return e}function ge(e){const t=Object.keys(e.secrets??{});if(t.length===0)return e.defaultScheme?[e.defaultScheme]:[];const r=(e.schemePriority??[]).filter(n=>t.includes(n));return[...r,...t.filter(n=>!r.includes(n))]}function be({callerHeaders:e,secret:t,authorization:r,clientIp:n,credentialHeaders:c}){const a={},h=new Set,l=t??(r?{header:"Authorization",value:r}:void 0),w=l?new Set((c??[]).map(f=>f.toLowerCase()).filter(f=>f!==l.header.toLowerCase())):new Set;for(const[f,$]of Object.entries(e??{})){const m=f.toLowerCase();m==="host"||m==="content-length"||m!==X&&(w.has(m)||(h.add(m),a[f]=$))}if(n&&(a[X]=n),!l)return{headers:a};const k=l.header.toLowerCase();return h.has(k)?{headers:a,callerHeader:k}:(a[l.header]=l.value,{headers:a})}function Ae(e){const t={};return e.forEach((r,n)=>{t[n.toLowerCase()]=r}),t}export{_e as buildFetchBridge,ce as getDefaultAgent,we as readCappedBody};
@@ -1 +1 @@
1
- import{isIP as d}from"node:net";import{isHostAllowed as h}from"./allowed-hosts.js";import{isBlockedIp as w}from"./ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchUserError as r}from"./fetch-errors.js";import{findUnresolvedPlaceholders as a,unresolvedPlaceholderMessage as i}from"./url-placeholders.js";function v(o,t){let e;try{e=new URL(o)}catch{throw new r(`Invalid URL: ${o}`)}if(e.protocol!=="http:"&&e.protocol!=="https:")throw new r(`Only http(s) URLs are allowed, got ${e.protocol}`);if(e.username||e.password)throw new r("URLs must not contain embedded credentials");const n=a(e.hostname);if(n.length>0)throw new r(i(n));const s=c(e.hostname);if(f(s)||!h(s,t))throw new p(`Host "${s}" is not allowed`,s);const l=a(e.pathname);if(l.length>0)throw new r(i(l));return e}function f(o){const t=c(o.toLowerCase()).split("%")[0];return t==="localhost"||t.endsWith(".localhost")?!0:d(t)!==0?w(t):!1}function c(o){return o.replace(/^\[|\]$/g,"")}export{v as assertUrlAllowed,f as isBlockedAddress};
1
+ import{isIP as d}from"node:net";import{isHostAllowed as h}from"./allowed-hosts.js";import{isBlockedIp as w}from"../../../../web-server/utils/ssrf-blocklist.js";import{GatewayFetchBlockedError as p,GatewayFetchUserError as r}from"./fetch-errors.js";import{findUnresolvedPlaceholders as a,unresolvedPlaceholderMessage as i}from"./url-placeholders.js";function v(o,t){let e;try{e=new URL(o)}catch{throw new r(`Invalid URL: ${o}`)}if(e.protocol!=="http:"&&e.protocol!=="https:")throw new r(`Only http(s) URLs are allowed, got ${e.protocol}`);if(e.username||e.password)throw new r("URLs must not contain embedded credentials");const n=a(e.hostname);if(n.length>0)throw new r(i(n));const s=c(e.hostname);if(f(s)||!h(s,t))throw new p(`Host "${s}" is not allowed`,s);const l=a(e.pathname);if(l.length>0)throw new r(i(l));return e}function f(o){const t=c(o.toLowerCase()).split("%")[0];return t==="localhost"||t.endsWith(".localhost")?!0:d(t)!==0?w(t):!1}function c(o){return o.replace(/^\[|\]$/g,"")}export{v as assertUrlAllowed,f as isBlockedAddress};
@@ -1 +1 @@
1
- import{isIP as l}from"node:net";import{getFeedbackClientIp as a}from"../../../../web-server/utils/feedback-ip.js";import{extractMappedIpv4 as c}from"../fetch/ssrf-blocklist.js";function I(t){const n=a(t);if(n)return f(n)}function f(t){if(l(t)===6){const n=c(t);return n||`${u(t)}::/64`}return t}function u(t){const n=t.split("%")[0],[e,i=""]=n.split("::"),r=e?e.split(":"):[],o=i?i.split(":"):[],s=8-r.length-o.length;return[...r,...Array(Math.max(0,s)).fill("0"),...o].slice(0,4).map(p=>parseInt(p||"0",16).toString(16)).join(":")}export{I as ipRateLimitKey};
1
+ import{isIP as l}from"node:net";import{getFeedbackClientIp as a}from"../../../../web-server/utils/feedback-ip.js";import{extractMappedIpv4 as c}from"../../../../web-server/utils/ssrf-blocklist.js";function I(t){const n=a(t);if(n)return f(n)}function f(t){if(l(t)===6){const n=c(t);return n||`${u(t)}::/64`}return t}function u(t){const n=t.split("%")[0],[e,i=""]=n.split("::"),r=e?e.split(":"):[],o=i?i.split(":"):[],s=8-r.length-o.length;return[...r,...Array(Math.max(0,s)).fill("0"),...o].slice(0,4).map(p=>parseInt(p||"0",16).toString(16)).join(":")}export{I as ipRateLimitKey};
@@ -27,15 +27,9 @@ export type UpstreamApiAuth = {
27
27
  routes: UpstreamAuthRoute[];
28
28
  descriptions?: Record<string, string>;
29
29
  };
30
- export type SchemeChoice = {
31
- name: string;
32
- label: string;
33
- description?: string;
34
- };
35
30
  type SecuritySchemeLoader = typeof getApiDescriptionFromFs;
36
31
  export declare const DEFAULT_SCHEME_NAME = "default";
37
32
  export declare function resolveUpstreamApiAuth(context: McpToolContext, apiRelativePath: string, loadDescription?: SecuritySchemeLoader): Promise<UpstreamApiAuth>;
38
- export declare function schemeChoicesOf(auth: UpstreamApiAuth, names: readonly string[]): SchemeChoice[];
39
33
  export declare function credentialHeadersOf(schemes: Record<string, UpstreamAuthScheme>): string[];
40
34
  export declare function orderedSchemesOf(schemes: Record<string, UpstreamAuthScheme>): string[];
41
35
  export declare function operationsForScheme(auth: UpstreamApiAuth, scheme: string): string[];
@@ -1,2 +1,2 @@
1
- import{getApiDescriptionFromFs as O}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as S}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],w=new Set(S),C=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function L(e,t,n=O){const r=Object.values(e.apiDescriptionsMap??{}).find(i=>i.relativePath===t);if(!r)return c();try{const i=await n({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return i?E(i,r.servers??[]):c()}catch{return c()}}function E(e,t){const n=$(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=x(e,r);return{schemes:n,defaultScheme:i,routes:v(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function N(e,t){return t.flatMap(n=>{const r=e.schemes[n];if(!r)return[];const i=e.descriptions?.[n];return[{name:n,label:j(r),...i?{description:i}:{}}]})}function U(e){return[...new Set(Object.values(e).map(P))]}function q(e){return a(Object.keys(e),e)}function z(e,t){const n=new Set;for(const r of e.routes)r.schemes.includes(t)&&n.add(`${r.method} ${r.path}`);return[...n]}function P(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function j(e){switch(e.kind){case"bearer":return"API key or bearer token";case"cookie":return`browser session cookie (${e.name})`;case"apiKey":return`API key in the ${e.header} header`;case"oauth2ClientCredentials":return"OAuth2 client credentials";default:return"Authorization header value"}}function x(e,t){const n={};for(const r of t){const i=e.components?.securitySchemes?.[r],o=k(i)?i.description:void 0;if(typeof o!="string")continue;const s=o.split(`
2
- `)[0].trim();s&&(n[r]=s.slice(0,A))}return n}function $(e){const t={};for(const[n,r]of Object.entries(e.components?.securitySchemes??{})){if(!k(r))continue;const i=T(r);i&&(t[n]=i)}return t}function T(e){if(e.type==="oauth2"){const t=e.flows?.clientCredentials;return t?{kind:"oauth2ClientCredentials",tokenUrl:t.tokenUrl,scopes:Object.keys(t.scopes??{})}:{kind:"bearer"}}if(e.type==="http"&&e.scheme==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,t){return[...e].sort((n,r)=>h.indexOf(t[n].kind)-h.indexOf(t[r].kind)||e.indexOf(n)-e.indexOf(r))}function m(e,t){const n=[];for(const r of e??[])for(const i of Object.keys(r))t[i]&&!n.includes(i)&&n.push(i);return n}function v(e,t,n){const r=I(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=H(r,o);if(f)for(const[p,b]of Object.entries(s)){if(!w.has(p.toLowerCase()))continue;const u=b?.security??e.security;if(u===void 0)continue;const d=m(u,t);d.length===0&&u.length>0||i.push({pattern:f,path:o,method:p.toUpperCase(),schemes:a(d,t)})}}return i}function H(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(C).map(_).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function I(e){const t=[...new Set(e.map(n=>R(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function R(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/(?<!\/)\/+$/,"")}function _(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function k(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,U as credentialHeadersOf,z as operationsForScheme,q as orderedSchemesOf,L as resolveUpstreamApiAuth,N as schemeChoicesOf};
1
+ import{getApiDescriptionFromFs as S}from"../docs-mcp/tools/openapi/utils.js";import{OPERATION_METHODS as b}from"../docs-mcp/tools/openapi/constants.js";const A=120,l="default",g={kind:"raw"},h=["oauth2ClientCredentials","bearer","apiKey","cookie","raw"],E=new Set(b),j=/\{(?<!\{[^}]*?\{)[^}]*\}/g;function c(){return{schemes:{[l]:g},defaultScheme:l,routes:[]}}async function M(e,t,n=S){const r=Object.values(e.apiDescriptionsMap??{}).find(i=>i.relativePath===t);if(!r)return c();try{const i=await n({relativePath:r.relativePath,outdir:e.outdir||"",user:e.user,rbac:e.config?.access?.rbac||{},requiresLogin:!!e.config?.access?.requiresLogin});return i?w(i,r.servers??[]):c()}catch{return c()}}function w(e,t){const n=x(e),r=Object.keys(n);if(r.length===0)return c();const i=m(e.security,n)[0]??a(r,n)[0],o=C(e,r);return{schemes:n,defaultScheme:i,routes:H(e,n,t),...Object.keys(o).length>0?{descriptions:o}:{}}}function L(e){return[...new Set(Object.values(e).map(P))]}function N(e){return a(Object.keys(e),e)}function U(e,t){const n=new Set;for(const r of e.routes)r.schemes.includes(t)&&n.add(`${r.method} ${r.path}`);return[...n]}function P(e){return e.kind==="apiKey"?e.header:e.kind==="cookie"?"Cookie":"Authorization"}function C(e,t){const n={};for(const r of t){const i=e.components?.securitySchemes?.[r],o=O(i)?i.description:void 0;if(typeof o!="string")continue;const s=o.split(`
2
+ `)[0].trim();s&&(n[r]=s.slice(0,A))}return n}function x(e){const t={};for(const[n,r]of Object.entries(e.components?.securitySchemes??{})){if(!O(r))continue;const i=T(r);i&&(t[n]=i)}return t}function T(e){if(e.type==="oauth2"){const t=e.flows?.clientCredentials;return t?{kind:"oauth2ClientCredentials",tokenUrl:t.tokenUrl,scopes:Object.keys(t.scopes??{})}:{kind:"bearer"}}if(e.type==="http"&&e.scheme==="bearer")return{kind:"bearer"};if(e.type==="apiKey"&&e.in==="header")return{kind:"apiKey",header:e.name||"X-Api-Key"};if(e.type==="apiKey"&&e.in==="cookie"&&e.name)return{kind:"cookie",name:e.name}}function a(e,t){return[...e].sort((n,r)=>h.indexOf(t[n].kind)-h.indexOf(t[r].kind)||e.indexOf(n)-e.indexOf(r))}function m(e,t){const n=[];for(const r of e??[])for(const i of Object.keys(r))t[i]&&!n.includes(i)&&n.push(i);return n}function H(e,t,n){const r=_(n),i=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||typeof s!="object")continue;const f=R(r,o);if(f)for(const[p,k]of Object.entries(s)){if(!E.has(p.toLowerCase()))continue;const u=k?.security??e.security;if(u===void 0)continue;const d=m(u,t);d.length===0&&u.length>0||i.push({pattern:f,path:o,method:p.toUpperCase(),schemes:a(d,t)})}}return i}function R(e,t){if(t.startsWith("/"))return new RegExp(`^${e}${y(t)}/?$`)}function y(e){return e.split(j).map(v).join("[^/]+").replace(/(?<!\/)\/+$/,"")}function _(e){const t=[...new Set(e.map(n=>$(n.url)).filter(Boolean).map(y))];return t.length>0?`(?:${t.join("|")})?`:""}function $(e){if(!e)return"";const t=e.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),n=t.indexOf("/");return n===-1?"":t.slice(n).split(/[?#]/)[0].replace(/(?<!\/)\/+$/,"")}function v(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function O(e){return typeof e=="object"&&e!==null&&"type"in e}export{l as DEFAULT_SCHEME_NAME,L as credentialHeadersOf,U as operationsForScheme,N as orderedSchemesOf,M as resolveUpstreamApiAuth};
@@ -1,5 +1,3 @@
1
- import{McpServer as L}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as j,MCP_TOOL_WORKER_KEY as D}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as F}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as H,stripAuthRequiredMeta as q,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{acceptedCredentialsLink as K,buildCredentialsAcceptedHint as N,buildCredentialsToolHint as P,buildSchemeChoiceHint as $,buildUpstreamCredentialFallback as y,buildUpstreamCredentialInputRequired as B,credentialElicitationAnswer as v,dedupeElicitation as x,isStatelessRequest as G,schemeLabelOf as V,supportsUrlElicitation as W}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as T,resolveUpstreamApiAuth as Y,schemeChoicesOf as J}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as Q,resolveSecretSubject as X}from"../gateway-mcp/subject.js";const Z=6e4;function fe(e,t){return new L({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}})}function he(e,t){return r=>async(n,s)=>{S.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,u=G(s.mcpReq),o=i?X(e.user,n.sessionHandle):{kind:"anonymous"},l=o.kind==="resolved"?o.subject:void 0;if(o.kind==="invalid")return{content:[{type:"text",text:o.message}],isError:!0};const d=async()=>{if(!(!i||!l||!t?.getKv))try{const p=await t.getKv();return await F.getSecretsForSubject(p,l)}catch(p){S.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(p)}`);return}};let c;const f=async()=>{c=await d();const p={toolName:r,args:n,context:{...e,upstreamSecrets:c},extra:se(s)};return await j.exec(D,[p],{timeout:Z})};let a=await f();const m=H(a);if(m.length>0&&i&&!l)return q(I(a,[{type:"text",text:Q}]));if(m.length>0&&i&&l&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${u?"2026-07-28":"2025"} clientElicitation=${z(t)}`);const p=await t.getKv(),h=await ee(e,m),k=m.map(O=>te(O,h)),E=re(c),g=u?await ne(e,s,k,l,p,E,h,t):await ie(e,k,l,p,E,h);switch(g.kind){case"inputRequired":return g.result;case"retry":a=await f();break;case"prependText":a=I(a,[{type:"text",text:g.text},...a.content]);break;case"none":break}}return q(a)}}function I(e,t){const{structuredContent:r,...n}=e;return{...n,content:t,isError:!0}}function z(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}async function ee(e,t){const r=new Map;for(const n of new Set(t.map(s=>s.api)))r.set(n,await Y(e,n));return r}function te(e,t){const r=t.get(e.api);if(!r)return{...e,scheme:e.scheme??T};const n=e.scheme&&r.schemes[e.scheme]?e.scheme:r.defaultScheme;return{...e,scheme:n}}function re(e){const t=new Set;for(const[r,n]of Object.entries(e??{}))for(const s of Object.keys(n))t.add(b({api:r,scheme:s}));return t}function R(e,t){return e.status===401&&!t.has(b(e))}function A(e,t){const r=t.get(e.api),n=e.sentScheme??e.scheme;return r&&n?V(r.schemes,n):void 0}function M(e,t){const r=t.get(e.api);return!r||!e.schemes?.length?[]:J(r,e.schemes)}function C(e,t){return M(e,t).length>1}function w(e,t){return e.map(r=>P(r,A(r,t))).join(`
1
+ import{McpServer as C}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as O,MCP_TOOL_WORKER_KEY as _}from"../../../workers/mcp-tool-worker-pool.js";import{secretStore as L}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as D,stripAuthRequiredMeta as E,upstreamAuthKey as b}from"../gateway-mcp/upstream-auth.js";import{acceptedCredentialsLink as K,buildCredentialsAcceptedHint as j,buildCredentialsToolHint as F,buildUpstreamCredentialFallback as q,buildUpstreamCredentialInputRequired as v,credentialElicitationAnswer as H,dedupeElicitation as N,isStatelessRequest as P,schemeLabelOf as $,supportsUrlElicitation as B}from"../gateway-mcp/elicitation.js";import{DEFAULT_SCHEME_NAME as y,resolveUpstreamApiAuth as G}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as V,resolveSecretSubject as W}from"../gateway-mcp/subject.js";const Y=6e4;function ue(e,t){return new C({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}})}function le(e,t){return r=>async(n,s)=>{S.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,c=P(s.mcpReq),a=i?W(e.user,n.sessionHandle):{kind:"anonymous"},o=a.kind==="resolved"?a.subject:void 0;if(a.kind==="invalid")return{content:[{type:"text",text:a.message}],isError:!0};const u=async()=>{if(!(!i||!o||!t?.getKv))try{const m=await t.getKv();return await L.getSecretsForSubject(m,o)}catch(m){S.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(m)}`);return}};let l;const p=async()=>{l=await u();const m={toolName:r,args:n,context:{...e,upstreamSecrets:l},extra:ee(s)};return await O.exec(_,[m],{timeout:Y})};let d=await p();const f=D(d);if(f.length>0&&i&&!o)return E(I(d,[{type:"text",text:V}]));if(f.length>0&&i&&o&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${c?"2026-07-28":"2025"} clientElicitation=${J(t)}`);const m=await t.getKv(),h=await Q(e,f),w=f.map(U=>X(U,h)),k=Z(l),g=c?await x(e,s,w,o,m,k,h,t):await z(e,w,o,m,k,h);switch(g.kind){case"inputRequired":return g.result;case"retry":d=await p();break;case"prependText":d=I(d,[{type:"text",text:g.text},...d.content]);break;case"none":break}}return E(d)}}function I(e,t){const{structuredContent:r,...n}=e;return{...n,content:t,isError:!0}}function J(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}async function Q(e,t){const r=new Map;for(const n of new Set(t.map(s=>s.api)))r.set(n,await G(e,n));return r}function X(e,t){const r=t.get(e.api);if(!r)return{...e,scheme:e.scheme??y};const n=e.scheme&&r.schemes[e.scheme]?e.scheme:r.defaultScheme;return{...e,scheme:n}}function Z(e){const t=new Set;for(const[r,n]of Object.entries(e??{}))for(const s of Object.keys(n))t.add(b({api:r,scheme:s}));return t}function R(e,t){return e.status===401&&!t.has(b(e))}function T(e,t){const r=t.get(e.api),n=e.sentScheme??e.scheme;return r&&n?$(r.schemes,n):void 0}function M(e,t){return e.map(r=>F(r,T(r,t))).join(`
2
2
 
3
- `)}function U(e,t,r){const n=e.map(s=>$(s,M(s,r)));return t.length>0&&n.push(w(t,r)),n.join(`
4
-
5
- `)}function _(e,t,r,n,s){const[i,...u]=t,o=s.get(i.api),l=i.scheme??o?.defaultScheme??T,d=A(i,s);return{subject:r,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:l,...d?{schemeLabel:d}:{}},auth:o?.schemes[l]??{kind:"raw"},...o?{apiAuth:o}:{},kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:i.status,...i.detail?{detail:i.detail}:{},otherApiNames:u.map(c=>c.name)}}}async function ne(e,t,r,n,s,i,u,o){const l=v(t.mcpReq.inputResponses);if(l==="declined")return{kind:"none"};const d=r.filter(a=>R(a,i));if(d.length===0)return{kind:"prependText",text:w(r,u)};if(l==="accepted"){const a=await K(t.mcpReq.requestState(),{kv:s,subject:n,baseUrl:e.baseUrl??"",apis:d.map(p=>p.api)}),m=a?.apiName??d[0].name;return{kind:"prependText",text:N(m,a?.url)}}const c=d.filter(a=>C(a,u));if(c.length>0){const a=r.filter(m=>!c.includes(m));return{kind:"prependText",text:U(c,a,u)}}const f=_(e,d,n,s,u);return W(o?.getClientCapabilities())?{kind:"inputRequired",result:await B(f)}:{kind:"prependText",text:await y(f)}}async function ie(e,t,r,n,s,i){const u=t.filter(c=>R(c,s));if(u.length===0)return{kind:"prependText",text:w(t,i)};const o=u.filter(c=>C(c,i));if(o.length>0){const c=t.filter(f=>!o.includes(f));return{kind:"prependText",text:U(o,c,i)}}const l=_(e,u,r,n,i);return{kind:"prependText",text:await x(`${r}:${b(u[0])}`,()=>y(l))}}function se(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function ge(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,client:e.client,clientVersion:e.clientVersion,requestId:e.requestId,messageId:e.messageId,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes,internalBaseUrl:e.internalBaseUrl,sandboxToolSchemas:e.sandboxToolSchemas,fs:void 0}}export{fe as createMcpServer,he as createToolDispatch,ge as extractSerializableContext};
3
+ `)}function A(e,t,r,n,s){const[i,...c]=t,a=s.get(i.api),o=i.scheme??a?.defaultScheme??y,l=(i.schemes?.length??0)<=1?T(i,s):void 0;return{subject:r,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:o,...l?{schemeLabel:l}:{}},auth:a?.schemes[o]??{kind:"raw"},...a?{apiAuth:a}:{},kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:i.status,...i.detail?{detail:i.detail}:{},otherApiNames:c.map(p=>p.name)}}}async function x(e,t,r,n,s,i,c,a){const o=H(t.mcpReq.inputResponses);if(o==="declined")return{kind:"none"};const u=r.filter(p=>R(p,i));if(u.length===0)return{kind:"prependText",text:M(r,c)};if(o==="accepted"){const p=await K(t.mcpReq.requestState(),{kv:s,subject:n,baseUrl:e.baseUrl??"",apis:u.map(f=>f.api)}),d=p?.apiName??u[0].name;return{kind:"prependText",text:j(d,p?.url)}}const l=A(e,u,n,s,c);return B(a?.getClientCapabilities())?{kind:"inputRequired",result:await v(l)}:{kind:"prependText",text:await q(l)}}async function z(e,t,r,n,s,i){const c=t.filter(u=>R(u,s));if(c.length===0)return{kind:"prependText",text:M(t,i)};const a=A(e,c,r,n,i);return{kind:"prependText",text:await N(`${r}:${b(c[0])}`,()=>q(a))}}function ee(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function pe(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,client:e.client,clientVersion:e.clientVersion,requestId:e.requestId,messageId:e.messageId,upstreamSecrets:e.upstreamSecrets,searchIgnoredRoutes:e.searchIgnoredRoutes,internalBaseUrl:e.internalBaseUrl,sandboxToolSchemas:e.sandboxToolSchemas,fs:void 0}}export{ue as createMcpServer,le as createToolDispatch,pe as extractSerializableContext};
@@ -1,12 +1,12 @@
1
- import o from"react";import l from"styled-components";import{flexRender as a}from"@tanstack/react-table";const C=({headerGroups:e,gridTemplateColumns:t,groupHeaderTitle:n})=>o.createElement(s,null,n&&o.createElement(g,{gridTemplateColumns:t,role:"row"},o.createElement(d,{gridColumn:"1",role:"cell"}),o.createElement(d,{gridColumn:"2 / -1",role:"cell"},o.createElement("span",null,n))),e.map((i,c)=>o.createElement(p,{key:`header-group-${c}`,gridTemplateColumns:t,role:"row"},i.headers.map((r,m)=>o.createElement(u,{key:r.id,gridColumn:m+1,onClick:r.column.getToggleSortingHandler(),role:"cell"},a(r.column.columnDef.header,r.getContext()),r.column.getCanSort()?o.createElement(x,null,r.column.getIsSorted()?r.column.getIsSorted()==="desc"?"\u25BE":"\u25B4":"\u21D5"):null))))),s=l.div`
1
+ import r from"react";import t from"styled-components";import{flexRender as g}from"@tanstack/react-table";const w=({headerGroups:e,gridTemplateColumns:i,groupHeaderTitle:l})=>r.createElement(p,null,l&&r.createElement(u,{gridTemplateColumns:i,role:"row"},r.createElement(c,{gridColumn:"1",role:"cell"}),r.createElement(c,{gridColumn:"2 / -1",role:"cell"},r.createElement("span",null,l))),e.map((a,m)=>r.createElement(b,{key:`header-group-${m}`,gridTemplateColumns:i,role:"row"},a.headers.map((o,s)=>{const d=g(o.column.columnDef.header,o.getContext()),n=o.column.getIsSorted();return r.createElement(x,{key:o.id,gridColumn:s+1,$sortable:o.column.getCanSort(),role:"columnheader","aria-sort":n==="asc"?"ascending":n==="desc"?"descending":void 0},o.column.getCanSort()?r.createElement(f,{type:"button",onClick:o.column.getToggleSortingHandler()},d,r.createElement(h,{"aria-hidden":"true"},n?n==="desc"?"\u25BE":"\u25B4":"\u21D5")):d)})))),p=t.div`
2
2
  border: 1px solid var(--border-color-primary);
3
3
  min-width: 775px;
4
- `,g=l.div`
4
+ `,u=t.div`
5
5
  display: grid;
6
6
  grid-template-columns: ${e=>e.gridTemplateColumns};
7
7
  width: 100%;
8
8
  min-width: 775px;
9
- `,d=l.div`
9
+ `,c=t.div`
10
10
  padding: 5px 15px;
11
11
  background-color: var(--bg-color-raised);
12
12
  font-weight: bold;
@@ -21,12 +21,12 @@ import o from"react";import l from"styled-components";import{flexRender as a}fro
21
21
  &:nth-child(2) {
22
22
  border-bottom: 1px solid var(--border-color-primary);
23
23
  }
24
- `,p=l.div`
24
+ `,b=t.div`
25
25
  display: grid;
26
26
  grid-template-columns: ${e=>e.gridTemplateColumns};
27
27
  width: 100%;
28
- `,u=l.div`
29
- padding: 5px 15px;
28
+ `,x=t.div`
29
+ padding: ${({$sortable:e})=>e?"0":"5px 15px"};
30
30
  background-color: var(--bg-color-raised);
31
31
  font-weight: bold;
32
32
  color: var(--text-color-secondary);
@@ -37,6 +37,19 @@ import o from"react";import l from"styled-components";import{flexRender as a}fro
37
37
  &:first-child {
38
38
  justify-content: flex-start;
39
39
  }
40
- `,x=l.span`
40
+ `,f=t.button`
41
+ display: block;
42
+ width: 100%;
43
+ height: 100%;
44
+ box-sizing: border-box;
45
+ margin: 0;
46
+ padding: 5px 15px;
47
+ border: 0;
48
+ background: none;
49
+ font: inherit;
50
+ color: inherit;
51
+ text-align: inherit;
52
+ cursor: pointer;
53
+ `,h=t.span`
41
54
  font-family: monospace;
42
- `;export{C as GridHeader};
55
+ `;export{w as GridHeader};
@@ -1,8 +1,8 @@
1
- import*as e from"react";import{useState as k}from"react";import N from"styled-components";import{HighlightContext as x}from"@redocly/portal-legacy-ui";import{breakpoints as O,getScorecardColorVariable as B}from"@redocly/theme/core/utils";import{FilterContent as D}from"@redocly/theme/components/Filter/FilterContent";import{FilterPopover as I}from"@redocly/theme/components/Filter/FilterPopover";import{CatalogPageWrapper as L,CatalogPageContent as M,CatalogTitle as R,CatalogDescription as W,CatalogPageDescriptionWrapper as j}from"@redocly/theme/components/CatalogClassic/CatalogClassic";import{useModalScrollLock as $}from"@redocly/theme/core/hooks";import{CatalogClassicActions as H}from"@redocly/theme/components/CatalogClassic/CatalogClassicActions";import{Sidebar as _}from"@redocly/theme/components/Sidebar/Sidebar";import{CardValue as p,CardsWrapper as q,ModeSwitcherTab as z,ModeSwitcherTabs as G,CardRow as J,ProblemsByStatusCardBody as K,ScoreCardStatusCardBody as Q,TableWrap as d,ContentWithDotWrap as U,ScorecardCard as h,ScorecardCardTitle as S}from"./index.styles";import{NON_CONFORMANT as T}from"./utils";import{ApiLevel as X,StatusDistribution as Y}from"./components";import{ApiListView as Z,TeamsView as ee,RulesView as te}from"./views";import{useData as re}from"./useData";function he(y){const{apis:v,summary:r,problemsByRule:A,nonConformantByTeam:P,sortedStatsByTeam:m,filterTerm:a,setFilterTerm:u,filters:l,scorecardInfo:f,hasTeam:i,hasPublishedAt:V,catalogConfig:E,teamLabel:n,teamProperty:w}=re(y),b={apis:"APIs",rules:"Rules",...i&&{teams:n}},[C,g]=e.useState(!1),[o,s]=k("apis");return $(C),e.createElement(x.Provider,{value:[a]},e.createElement(L,null,e.createElement(ae,{menu:e.createElement(D,{setFilterTerm:u,filters:l,filterTerm:a,filterValuesCasing:E.filterValuesCasing})}),C&&e.createElement(I,{onClose:()=>g(!1),setFilterTerm:u,filterTerm:a,filters:l,filterValuesCasing:E.filterValuesCasing}),e.createElement(H,{onOpenFilter:()=>g(!0),filters:l,filterTerm:a}),e.createElement(M,{id:"scorecard-page-content"},e.createElement(j,null,e.createElement(R,null,"API design scorecard"),e.createElement(W,null,"See the health of your API designs.")),e.createElement(q,null,e.createElement(h,null,e.createElement(S,null,"Scorecard status"),e.createElement(Q,null,e.createElement("div",null,e.createElement(p,null,r.total),e.createElement("br",null),"APIs"),e.createElement("div",null,Object.entries(r.levels).map(([t,c],F)=>e.createElement(U,{key:t},e.createElement(p,{style:{width:"40px",textAlign:"right"}},c),e.createElement(X,{level:t,colorVariable:B(F,Object.values(r.levels).length)}))))),e.createElement(Y,{summary:r})),i&&e.createElement(h,null,e.createElement(S,null,`${T} APIs by ${n}`),e.createElement(K,null,P.slice(0,4).map((t,c)=>e.createElement(J,{key:c},e.createElement("span",{style:{flex:1,textAlign:"left"}},t.team),e.createElement(p,null,t.levels[T].apis))),m.length>4&&e.createElement("a",{href:"#",onClick:()=>s("teams")},"See all (",m.length,")")))),e.createElement(G,{"data-component-name":"Scorecard/ModeSwitcherTabs"},Object.keys(b).map(t=>t&&e.createElement(z,{"data-component-name":"Scorecard/ModeSwitcherTab",key:t,isActive:o===t,onClick:()=>s(t)},b[t]))),e.createElement(d,{isActive:o==="apis"},e.createElement(Z,{data:v,levelNames:f.levelNames,hasTeam:i,hasPublishedAt:V,teamLabel:n})),e.createElement(d,{isActive:o==="rules"},e.createElement(te,{data:A})),i&&e.createElement(d,{isActive:o==="teams"},e.createElement(ee,{data:m,levelNames:f.levelNames,filters:l,setActiveTab:s,teamLabel:n,teamProperty:w})))))}const ae=N(_)`
1
+ import*as e from"react";import{useState as F}from"react";import N from"styled-components";import{HighlightContext as x}from"@redocly/portal-legacy-ui";import{breakpoints as B,getScorecardColorVariable as L}from"@redocly/theme/core/utils";import{FilterContent as O}from"@redocly/theme/components/Filter/FilterContent";import{FilterPopover as D}from"@redocly/theme/components/Filter/FilterPopover";import{CatalogPageWrapper as I,CatalogPageContent as M,CatalogTitle as R,CatalogDescription as W,CatalogPageDescriptionWrapper as j}from"@redocly/theme/components/CatalogClassic/CatalogClassic";import{useModalScrollLock as $}from"@redocly/theme/core/hooks";import{CatalogClassicActions as H}from"@redocly/theme/components/CatalogClassic/CatalogClassicActions";import{Sidebar as _}from"@redocly/theme/components/Sidebar/Sidebar";import{CardValue as p,CardsWrapper as q,ModeSwitcherTab as z,ModeSwitcherTabs as G,CardRow as J,ProblemsByStatusCardBody as K,ScoreCardStatusCardBody as Q,TableWrap as d,ContentWithDotWrap as U,ScorecardCard as h,ScorecardCardTitle as S,LinkButton as X}from"./index.styles";import{NON_CONFORMANT as T}from"./utils";import{ApiLevel as Y,StatusDistribution as Z}from"./components";import{ApiListView as ee,TeamsView as te,RulesView as re}from"./views";import{useData as ae}from"./useData";function Se(y){const{apis:v,summary:r,problemsByRule:A,nonConformantByTeam:P,sortedStatsByTeam:m,filterTerm:a,setFilterTerm:u,filters:l,scorecardInfo:f,hasTeam:i,hasPublishedAt:V,catalogConfig:E,teamLabel:n,teamProperty:w}=ae(y),b={apis:"APIs",rules:"Rules",...i&&{teams:n}},[C,g]=e.useState(!1),[o,s]=F("apis");return $(C),e.createElement(x.Provider,{value:[a]},e.createElement(I,null,e.createElement(le,{menu:e.createElement(O,{setFilterTerm:u,filters:l,filterTerm:a,filterValuesCasing:E.filterValuesCasing})}),C&&e.createElement(D,{onClose:()=>g(!1),setFilterTerm:u,filterTerm:a,filters:l,filterValuesCasing:E.filterValuesCasing}),e.createElement(H,{onOpenFilter:()=>g(!0),filters:l,filterTerm:a}),e.createElement(M,{id:"scorecard-page-content"},e.createElement(j,null,e.createElement(R,null,"API design scorecard"),e.createElement(W,null,"See the health of your API designs.")),e.createElement(q,null,e.createElement(h,null,e.createElement(S,null,"Scorecard status"),e.createElement(Q,null,e.createElement("div",null,e.createElement(p,null,r.total),e.createElement("br",null),"APIs"),e.createElement("div",null,Object.entries(r.levels).map(([t,c],k)=>e.createElement(U,{key:t},e.createElement(p,{style:{width:"40px",textAlign:"right"}},c),e.createElement(Y,{level:t,colorVariable:L(k,Object.values(r.levels).length)}))))),e.createElement(Z,{summary:r})),i&&e.createElement(h,null,e.createElement(S,null,`${T} APIs by ${n}`),e.createElement(K,null,P.slice(0,4).map((t,c)=>e.createElement(J,{key:c},e.createElement("span",{style:{flex:1,textAlign:"left"}},t.team),e.createElement(p,null,t.levels[T].apis))),m.length>4&&e.createElement(X,{onClick:()=>s("teams")},"See all (",m.length,")")))),e.createElement(G,{"data-component-name":"Scorecard/ModeSwitcherTabs"},Object.keys(b).map(t=>t&&e.createElement(z,{"data-component-name":"Scorecard/ModeSwitcherTab",key:t,isActive:o===t,onClick:()=>s(t)},b[t]))),e.createElement(d,{isActive:o==="apis"},e.createElement(ee,{data:v,levelNames:f.levelNames,hasTeam:i,hasPublishedAt:V,teamLabel:n})),e.createElement(d,{isActive:o==="rules"},e.createElement(re,{data:A})),i&&e.createElement(d,{isActive:o==="teams"},e.createElement(te,{data:m,levelNames:f.levelNames,filters:l,setActiveTab:s,teamLabel:n,teamProperty:w})))))}const le=N(_)`
2
2
  display: none;
3
3
 
4
- @media screen and (min-width: ${O.medium}) {
4
+ @media screen and (min-width: ${B.medium}) {
5
5
  display: flex;
6
6
  }
7
7
  --menu-container-padding-top: 0;
8
- `;export{he as ScorecardDashboard};
8
+ `;export{Se as ScorecardDashboard};
@@ -64,4 +64,5 @@ export declare const LevelComplianceCardTitle: import("styled-components/dist/ty
64
64
  }, never>>> & string;
65
65
  export declare const ContentWithDotWrap: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
66
66
  export declare const EmojiWrap: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never> & Partial<Pick<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>>> & string;
67
+ export declare const LinkButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, keyof import("react").ButtonHTMLAttributes<HTMLButtonElement> | keyof import("react").ClassAttributes<HTMLButtonElement>> & import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "about" | "accessKey" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "autoCapitalize" | "autoCorrect" | "autoFocus" | "autoSave" | "children" | "className" | "color" | "content" | "contentEditable" | "contextMenu" | "dangerouslySetInnerHTML" | "datatype" | "defaultChecked" | "defaultValue" | "dir" | "disabled" | "draggable" | "enterKeyHint" | "exportparts" | "form" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "hidden" | "id" | "inert" | "inlist" | "inputMode" | "is" | "itemID" | "itemProp" | "itemRef" | "itemScope" | "itemType" | "key" | "lang" | "name" | "nonce" | "onAbort" | "onAbortCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAuxClick" | "onAuxClickCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onBeforeToggle" | "onBlur" | "onBlurCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onChange" | "onChangeCapture" | "onClick" | "onClickCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onContextMenu" | "onContextMenuCapture" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onError" | "onErrorCapture" | "onFocus" | "onFocusCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onInput" | "onInputCapture" | "onInvalid" | "onInvalidCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onLoad" | "onLoadCapture" | "onLoadStart" | "onLoadStartCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onPaste" | "onPasteCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerMove" | "onPointerMoveCapture" | "onPointerOut" | "onPointerOutCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerUp" | "onPointerUpCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onReset" | "onResetCapture" | "onScroll" | "onScrollCapture" | "onScrollEnd" | "onScrollEndCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onSelect" | "onSelectCapture" | "onStalled" | "onStalledCapture" | "onSubmit" | "onSubmitCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onToggle" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onTransitionCancel" | "onTransitionCancelCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "onTransitionRun" | "onTransitionRunCapture" | "onTransitionStart" | "onTransitionStartCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onWheel" | "onWheelCapture" | "part" | "popover" | "popoverTarget" | "popoverTargetAction" | "prefix" | "property" | "radioGroup" | "ref" | "rel" | "resource" | "results" | "rev" | "role" | "security" | "slot" | "spellCheck" | "style" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "tabIndex" | "title" | "translate" | "type" | "typeof" | "unselectable" | "value" | "vocab"> & Partial<Pick<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, keyof import("react").ButtonHTMLAttributes<HTMLButtonElement> | keyof import("react").ClassAttributes<HTMLButtonElement>> & import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "about" | "accessKey" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-braillelabel" | "aria-brailleroledescription" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colindextext" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-description" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowindextext" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "autoCapitalize" | "autoCorrect" | "autoFocus" | "autoSave" | "children" | "className" | "color" | "content" | "contentEditable" | "contextMenu" | "dangerouslySetInnerHTML" | "datatype" | "defaultChecked" | "defaultValue" | "dir" | "disabled" | "draggable" | "enterKeyHint" | "exportparts" | "form" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "hidden" | "id" | "inert" | "inlist" | "inputMode" | "is" | "itemID" | "itemProp" | "itemRef" | "itemScope" | "itemType" | "key" | "lang" | "name" | "nonce" | "onAbort" | "onAbortCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAuxClick" | "onAuxClickCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onBeforeToggle" | "onBlur" | "onBlurCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onChange" | "onChangeCapture" | "onClick" | "onClickCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onContextMenu" | "onContextMenuCapture" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onError" | "onErrorCapture" | "onFocus" | "onFocusCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onInput" | "onInputCapture" | "onInvalid" | "onInvalidCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onLoad" | "onLoadCapture" | "onLoadStart" | "onLoadStartCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onPaste" | "onPasteCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerEnter" | "onPointerLeave" | "onPointerMove" | "onPointerMoveCapture" | "onPointerOut" | "onPointerOutCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerUp" | "onPointerUpCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onReset" | "onResetCapture" | "onScroll" | "onScrollCapture" | "onScrollEnd" | "onScrollEndCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onSelect" | "onSelectCapture" | "onStalled" | "onStalledCapture" | "onSubmit" | "onSubmitCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onToggle" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onTransitionCancel" | "onTransitionCancelCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "onTransitionRun" | "onTransitionRunCapture" | "onTransitionStart" | "onTransitionStartCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onWheel" | "onWheelCapture" | "part" | "popover" | "popoverTarget" | "popoverTargetAction" | "prefix" | "property" | "radioGroup" | "ref" | "rel" | "resource" | "results" | "rev" | "role" | "security" | "slot" | "spellCheck" | "style" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "tabIndex" | "title" | "translate" | "type" | "typeof" | "unselectable" | "value" | "vocab">>> & string;
67
68
  //# sourceMappingURL=index.styles.d.ts.map
@@ -55,7 +55,7 @@ import r from"styled-components";import{Popup as t}from"reactjs-popup";import{Bu
55
55
  border: 1px solid var(--border-color-primary);
56
56
  background-color: var(${o=>o.colorVariable});
57
57
  margin-top: 1px;
58
- `,v=r.p`
58
+ `,u=r.p`
59
59
  margin: 16px 0 32px 0;
60
60
  font-size: 16px;
61
61
  color: var(--text-color-secondary);
@@ -88,7 +88,7 @@ import r from"styled-components";import{Popup as t}from"reactjs-popup";import{Bu
88
88
  @media screen and (max-width: 767px) {
89
89
  flex-direction: column;
90
90
  }
91
- `,u=r.input`
91
+ `,v=r.input`
92
92
  border: var(--filter-input-border);
93
93
  min-width: var(--filter-input-min-width);
94
94
  width: 100%;
@@ -197,12 +197,12 @@ import r from"styled-components";import{Popup as t}from"reactjs-popup";import{Bu
197
197
  text-align: center;
198
198
  display: block;
199
199
  }
200
- `,E=r(i)`
200
+ `,B=r(i)`
201
201
  display: flex;
202
202
  flex-direction: row;
203
203
  justify-content: space-between;
204
204
  gap: 10px;
205
- `,B=r.div`
205
+ `,E=r.div`
206
206
  display: flex;
207
207
  flex-direction: row;
208
208
  align-items: center;
@@ -217,4 +217,14 @@ import r from"styled-components";import{Popup as t}from"reactjs-popup";import{Bu
217
217
  'Noto Color Emoji',
218
218
  'EmojiOne Color';
219
219
  vertical-align: middle;
220
- `;export{C as AppPageWrapper,f as CardRow,a as CardValue,b as CardsWrapper,v as CatalogDescription,n as CatalogPageWrapper,W as Code,B as ContentWithDotWrap,m as Dot,M as EmojiWrap,I as FilePreview,g as GaugeNumber,P as GroupTh,T as InstanceControlsWrap,$ as InstanceMessage,z as InstanceReference,j as InstanceWrap,E as LevelComplianceCardTitle,y as ModeSwitcherTab,h as ModeSwitcherTabs,x as ProblemsByStatusCardBody,s as ScoreCardStatusCardBody,c as ScorecardCard,i as ScorecardCardTitle,S as SortIcon,u as StyledInput,k as StyledPopup,w as TableWrap};
220
+ `,D=r.button.attrs({type:"button"})`
221
+ padding: 0;
222
+ margin: 0;
223
+ border: 0;
224
+ background: none;
225
+ font: inherit;
226
+ font-weight: var(--link-font-weight);
227
+ color: var(--link-color-primary);
228
+ text-align: left;
229
+ cursor: pointer;
230
+ `;export{C as AppPageWrapper,f as CardRow,a as CardValue,b as CardsWrapper,u as CatalogDescription,n as CatalogPageWrapper,W as Code,E as ContentWithDotWrap,m as Dot,M as EmojiWrap,I as FilePreview,g as GaugeNumber,P as GroupTh,T as InstanceControlsWrap,$ as InstanceMessage,z as InstanceReference,j as InstanceWrap,B as LevelComplianceCardTitle,D as LinkButton,y as ModeSwitcherTab,h as ModeSwitcherTabs,x as ProblemsByStatusCardBody,s as ScoreCardStatusCardBody,c as ScorecardCard,i as ScorecardCardTitle,S as SortIcon,v as StyledInput,k as StyledPopup,w as TableWrap};
@@ -1 +1 @@
1
- import*as e from"react";import{useCallback as H,useMemo as b,useState as G,useEffect as _}from"react";import{useLocation as T}from"react-router";import{Flex as U}from"@redocly/portal-legacy-ui";import{Button as x}from"@redocly/theme/components/Button/Button";import{AddIcon as I}from"@redocly/theme/icons/AddIcon/AddIcon";import{SubtractIcon as D}from"@redocly/theme/icons/SubtractIcon/SubtractIcon";import{ArrowLeftIcon as K}from"@redocly/theme/icons/ArrowLeftIcon/ArrowLeftIcon";import{getScorecardColorVariable as z,withoutPathPrefix as O}from"@redocly/theme/core/utils";import{Link as P}from"../../../../client/app/Link";import N from"../../../../client/app/CircularProgress";import{isDefined as R}from"../../../../utils/guards/is-defined.js";import{removeLeadingSlash as Z}from"../../../../utils/url/remove-leading-slash.js";import{StatusByLevelWidget as J}from"./StatusByLevelWidget";import{OTHER_TEAMS_LABEL as Q,NON_CONFORMANT as X}from"./utils";import{LevelIndicator as Y}from"./LevelIndicator";import{AppPageWrapper as V,CardValue as v,CardsWrapper as j,CatalogDescription as ee,ModeSwitcherTab as te,ModeSwitcherTabs as re,CardRow as B,ProblemsByStatusCardBody as W,ScoreCardStatusCardBody as F,TableWrap as ne,LevelComplianceCardTitle as le,ContentWithDotWrap as C,ScorecardCard as S,ScorecardCardTitle as w}from"./index.styles";import{ApiLevel as M,InstanceStatus as k,LevelStatus as $,RuleStatus as q}from"./components";import{Grid as A}from"./Grid/Grid";import{useDetailedScorecard as ae}from"./useDetailedScorecard";import{Gauge as se,GaugeValue as ce}from"./Gauge";function Te(p){const u=T(),{data:d,levelNames:i,hasPublishedAt:n,hasTeam:s,teamLabel:l}=p,f=H((a,c,E)=>{const g=a.getValue(E)?new Date(a.getValue(E)).getTime():0,t=c.getValue(E)?new Date(c.getValue(E)).getTime():0;return g>t?1:-1},[]),m=b(()=>[{id:"title",header:"API",accessorKey:"api.title",size:200,cell:({getValue:a,row:c})=>e.createElement(P,{to:O(u.pathname)+"/apis/"+encodeURIComponent(Z(c.original.api.link))},a())},n?{header:"Published at",accessorKey:"api.publishedAt",sortingFn:f,cell:({getValue:a})=>{const c=a();return c?new Date(c).toLocaleDateString():"-"}}:void 0,s?{header:l,accessorKey:"api.team"}:void 0,{id:"level",header:"Level",accessorKey:"scorecard.scorecardLevel",cell:({getValue:a,row:c})=>e.createElement(M,{level:a(),colorVariable:z(c.original?.scorecard.scorecardLevelIdx,i.length+1)})},...i.map(a=>({id:a,header:`${a}`,accessorKey:"scorecard.levels",cell:({getValue:c,row:E})=>{const g=c(),t=E.original.api.link;return g?.[a]?e.createElement(Y,{key:t,uniqueErrors:g[a].uniqueErrors,uniqueWarnings:g[a].uniqueWarnings,id:t}):null}}))].filter(R),[f,i,u.pathname,n,s,l]);return e.createElement(A,{columns:m,data:d,sortBy:[{id:"title",desc:!1}]})}function Ie(p){const u=T(),{data:d}=p,i=b(()=>[{header:"Rules",accessorKey:"ruleId",cell:({getValue:n})=>e.createElement(P,{to:O(u.pathname)+"/rules/"+encodeURIComponent(n())},n())},{id:"status",header:"Status",accessorKey:"status",size:200,cell:({row:n})=>e.createElement(q,{errors:n.original.errorApisCount,warnings:n.original.warningApisCount})},{header:"Problem APIs",size:200,accessorKey:"apis",cell:({getValue:n})=>n().length,sortDescFirst:!0},{header:"Problem count",size:200,accessorKey:"count",sortDescFirst:!0}],[u.pathname]);return e.createElement(A,{columns:i,data:d,sortBy:[{id:"status",desc:!0}]})}function De(p){const{data:u,levelNames:d,filters:i,setActiveTab:n,teamLabel:s,teamProperty:l}=p,f=b(()=>[{id:"team",header:`API ${s}`,accessorKey:"team",size:200,cell:({getValue:m})=>e.createElement("a",{href:"#",onClick:a=>{a.preventDefault(),n("apis"),i.find(c=>c.property===l)?.selectOption(m())}},m())},...[X,...d].map(m=>({id:m,header:`${m}`,accessorFn:a=>a.levels[m]?.apis}))],[i,d,n,s,l]);return e.createElement(A,{columns:f,data:u,sortBy:[{id:"team",desc:!1}],groupHeaderTitle:"APIs by level"})}function oe(p){const{data:u,title:d}=p,i=b(()=>[{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:n})=>n.getCanExpand()?e.createElement(x,{onClick:n.getToggleExpandedHandler(),variant:"text",size:"medium",icon:n.getIsExpanded()?e.createElement(D,null):e.createElement(I,null)}):null},{header:"Rules",accessorKey:"ruleId",cell:({getValue:n,row:s})=>s.getCanExpand()?e.createElement("span",{onClick:s.getToggleExpandedHandler()},n()):null},{header:"Status",accessorKey:"status",size:200,cell:({getValue:n})=>{const s=n();return e.createElement(q,{errors:s?.errorsCount,warnings:s?.warningsCount})},enableSorting:!1},{header:"Problem count",accessorKey:"problemsCount",size:200,sortDescFirst:!0}],[]);return e.createElement(A,{columns:i,data:u,sortBy:[{id:"ruleId",desc:!1}],empty:e.createElement(e.Fragment,null,"\u2705 This API is compliant with ",e.createElement("strong",null,d)," level rules"),subRowKey:"problems"})}function Ke(p){const{catalogSlug:u,slug:d,data:i,hasTeam:n,hasPublishedAt:s,teamLabel:l}=p,{apis:f,apisCount:m,warningApisCount:a,errorApisCount:c,levels:E,count:g}=i,t=f.length===0,h=t?void 0:{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:r})=>r.getCanExpand()?e.createElement(x,{onClick:r.getToggleExpandedHandler(),variant:"text",size:"medium",icon:r.getIsExpanded()?e.createElement(D,null):e.createElement(I,null)}):null},L=b(()=>[h,{header:"API",accessorKey:"title"},s?{header:"Published at",accessorKey:"publishedAt",cell:({getValue:r})=>{const o=r();return o?new Date(o).toLocaleDateString():"-"}}:void 0,n?{header:l,accessorKey:"team",cell:({getValue:r})=>r()||Q}:void 0,{header:"Problem count",size:200,accessorKey:"count",cell:({getValue:r})=>r()||0,sortDescFirst:!0}].filter(R),[n,s,l,t]),y=b(()=>Object.entries(i.teams).sort((r,o)=>o[1]-r[1]),[i.teams]);return e.createElement(V,null,e.createElement(x,{icon:e.createElement(K,null),iconPosition:"left",variant:"ghost",size:"large",to:`${u}scorecard`},"Back to dashboard"),e.createElement("h1",null,"Rule ",d),e.createElement(j,null,e.createElement(S,null,e.createElement(w,null,"Rule status"),e.createElement(F,null,e.createElement("div",null,e.createElement(v,null,m),e.createElement("br",null),"APIs"),e.createElement("div",null,e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},m-a-c),e.createElement(k,{severity:"Pass"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},a),e.createElement(k,{severity:"Warn"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},c),e.createElement(k,{severity:"Error"}))))),e.createElement(S,null,e.createElement(w,null,"Problems by level"),e.createElement(W,null,Object.entries(E).map(([r,o])=>e.createElement(B,{key:r},e.createElement("span",null,r),e.createElement(se,{chunks:[{share:o.errors/g*100,colorVariable:"--scorecard-color-error"},{share:o.warnings/g*100,colorVariable:"--scorecard-color-warning"}]}),e.createElement(ce,null,o.errors+o.warnings))))),e.createElement(S,null,e.createElement(w,null,`Problems by ${l}`),e.createElement(W,null,y.slice(0,4).map(([r,o])=>e.createElement(B,{key:r},e.createElement("span",{style:{flex:1,textAlign:"left"}},r),e.createElement(v,null,o)))))),e.createElement(A,{columns:L,data:f,sortBy:[{id:"title",desc:!1}],subRowKey:"problems",slug:d}))}function ze(p){const{slug:u,api:d,teamLabel:i,catalogSlug:n}=p,{api:s}=d||{},{scorecard:l,isLoading:f}=ae(`/${u}`),m=l?.levels||{},[a,c]=G(Object.keys(m)[0]),E=b(()=>s?.publishedAt instanceof Date?s?.publishedAt.toLocaleDateString():s?.publishedAt,[s]);_(()=>{if(l){const t=l.levels||{};c(Object.keys(t)[0])}},[l]);const g=b(()=>{if(!l?.levels)return{};const t={};for(const h of Object.keys(l.levels)){const L=l.levels[h].problems.reduce((y,r)=>(y[r.ruleId]||(y[r.ruleId]=[]),y[r.ruleId].push(r),y),{});t[h]=Object.entries(L).map(([y,r])=>({ruleId:y,problems:[...r.filter(o=>o.severity==="error"),...r.filter(o=>o.severity==="warn")],problemsCount:r.length,status:{errorsCount:r.filter(o=>o.severity==="error").length,warningsCount:r.filter(o=>o.severity==="warn").length}}))}return t},[l?.levels]);return f?e.createElement(U,{justifyContent:"center",mt:"2em"},e.createElement(N,null)):l?e.createElement(V,null,e.createElement(x,{icon:e.createElement(K,null),iconPosition:"left",variant:"ghost",size:"large",to:`${n}scorecard`},"Back to dashboard"),e.createElement("h1",null,s?.title||u),e.createElement(ee,null,E&&e.createElement(e.Fragment,null,"Published at: ",new Date().toLocaleString(void 0,{timeZoneName:"short"}),e.createElement("br",null)),e.createElement(P,{to:u,external:!0},"Documentation")),e.createElement(j,null,e.createElement(S,null,e.createElement(le,null,e.createElement("span",null,"Scorecard level"),e.createElement(M,{level:l.scorecardLevel,colorVariable:z(l.scorecardLevelIdx,Object.keys(m).length)})),e.createElement(F,null,e.createElement("div",null,"Compliance by level"),e.createElement("div",null,Object.entries(l.levels).map(([t,h])=>e.createElement(C,{key:t},e.createElement(v,{style:{width:"100px",textAlign:"right"}},t),e.createElement($,{errors:h.errors,warnings:h.warnings})))))),e.createElement(J,{title:"Rules compliance",levels:Object.entries(l.levels).map(([t,h])=>({errors:h.uniqueErrors,warnings:h.uniqueWarnings,total:p.rulesPerLevel[t].length,name:t}))}),e.createElement(S,null,e.createElement(w,null,i),e.createElement(v,{style:{fontWeight:"normal"}},s?.team))),e.createElement(re,{style:{justifyContent:"flex-start"},"data-component-name":"Scorecard/ModeSwitcherTabs"},Object.keys(l.levels).map(t=>e.createElement(te,{"data-component-name":"Scorecard/ModeSwitcherTab",key:t,isActive:a===t,onClick:()=>c(t)},e.createElement(C,null,e.createElement("span",null,t)," ",e.createElement($,{errors:l.levels[t].errors,warnings:l.levels[t].warnings}))))),Object.keys(m).map(t=>e.createElement(ne,{key:t,isActive:a===t},e.createElement(oe,{title:t,data:g[t]})))):e.createElement("div",null,"Scorecard not found")}export{Te as ApiListView,ze as ApiPage,oe as ApiView,Ke as RuleView,Ie as RulesView,De as TeamsView};
1
+ import*as e from"react";import{useCallback as H,useMemo as b,useState as G,useEffect as _}from"react";import{useLocation as T}from"react-router";import{Flex as U}from"@redocly/portal-legacy-ui";import{Button as x}from"@redocly/theme/components/Button/Button";import{AddIcon as I}from"@redocly/theme/icons/AddIcon/AddIcon";import{SubtractIcon as K}from"@redocly/theme/icons/SubtractIcon/SubtractIcon";import{ArrowLeftIcon as D}from"@redocly/theme/icons/ArrowLeftIcon/ArrowLeftIcon";import{getScorecardColorVariable as z,withoutPathPrefix as O}from"@redocly/theme/core/utils";import{Link as P}from"../../../../client/app/Link";import N from"../../../../client/app/CircularProgress";import{isDefined as R}from"../../../../utils/guards/is-defined.js";import{removeLeadingSlash as Z}from"../../../../utils/url/remove-leading-slash.js";import{StatusByLevelWidget as J}from"./StatusByLevelWidget";import{OTHER_TEAMS_LABEL as Q,NON_CONFORMANT as X}from"./utils";import{LevelIndicator as Y}from"./LevelIndicator";import{AppPageWrapper as V,CardValue as v,CardsWrapper as j,CatalogDescription as ee,ModeSwitcherTab as te,ModeSwitcherTabs as re,CardRow as B,ProblemsByStatusCardBody as W,ScoreCardStatusCardBody as F,TableWrap as ne,LevelComplianceCardTitle as le,ContentWithDotWrap as C,ScorecardCard as S,ScorecardCardTitle as L,LinkButton as ae}from"./index.styles";import{ApiLevel as M,InstanceStatus as k,LevelStatus as $,RuleStatus as q}from"./components";import{Grid as A}from"./Grid/Grid";import{useDetailedScorecard as se}from"./useDetailedScorecard";import{Gauge as ce,GaugeValue as oe}from"./Gauge";function Ie(p){const u=T(),{data:d,levelNames:i,hasPublishedAt:n,hasTeam:s,teamLabel:l}=p,f=H((a,o,E)=>{const g=a.getValue(E)?new Date(a.getValue(E)).getTime():0,t=o.getValue(E)?new Date(o.getValue(E)).getTime():0;return g>t?1:-1},[]),m=b(()=>[{id:"title",header:"API",accessorKey:"api.title",size:200,cell:({getValue:a,row:o})=>e.createElement(P,{to:O(u.pathname)+"/apis/"+encodeURIComponent(Z(o.original.api.link))},a())},n?{header:"Published at",accessorKey:"api.publishedAt",sortingFn:f,cell:({getValue:a})=>{const o=a();return o?new Date(o).toLocaleDateString():"-"}}:void 0,s?{header:l,accessorKey:"api.team"}:void 0,{id:"level",header:"Level",accessorKey:"scorecard.scorecardLevel",cell:({getValue:a,row:o})=>e.createElement(M,{level:a(),colorVariable:z(o.original?.scorecard.scorecardLevelIdx,i.length+1)})},...i.map(a=>({id:a,header:`${a}`,accessorKey:"scorecard.levels",cell:({getValue:o,row:E})=>{const g=o(),t=E.original.api.link;return g?.[a]?e.createElement(Y,{key:t,uniqueErrors:g[a].uniqueErrors,uniqueWarnings:g[a].uniqueWarnings,id:t}):null}}))].filter(R),[f,i,u.pathname,n,s,l]);return e.createElement(A,{columns:m,data:d,sortBy:[{id:"title",desc:!1}]})}function Ke(p){const u=T(),{data:d}=p,i=b(()=>[{header:"Rules",accessorKey:"ruleId",cell:({getValue:n})=>e.createElement(P,{to:O(u.pathname)+"/rules/"+encodeURIComponent(n())},n())},{id:"status",header:"Status",accessorKey:"status",size:200,cell:({row:n})=>e.createElement(q,{errors:n.original.errorApisCount,warnings:n.original.warningApisCount})},{header:"Problem APIs",size:200,accessorKey:"apis",cell:({getValue:n})=>n().length,sortDescFirst:!0},{header:"Problem count",size:200,accessorKey:"count",sortDescFirst:!0}],[u.pathname]);return e.createElement(A,{columns:i,data:d,sortBy:[{id:"status",desc:!0}]})}function De(p){const{data:u,levelNames:d,filters:i,setActiveTab:n,teamLabel:s,teamProperty:l}=p,f=b(()=>[{id:"team",header:`API ${s}`,accessorKey:"team",size:200,cell:({getValue:m})=>e.createElement(ae,{onClick:()=>{n("apis"),i.find(a=>a.property===l)?.selectOption(m())}},m())},...[X,...d].map(m=>({id:m,header:`${m}`,accessorFn:a=>a.levels[m]?.apis}))],[i,d,n,s,l]);return e.createElement(A,{columns:f,data:u,sortBy:[{id:"team",desc:!1}],groupHeaderTitle:"APIs by level"})}function ie(p){const{data:u,title:d}=p,i=b(()=>[{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:n})=>n.getCanExpand()?e.createElement(x,{onClick:n.getToggleExpandedHandler(),variant:"text",size:"medium",icon:n.getIsExpanded()?e.createElement(K,null):e.createElement(I,null)}):null},{header:"Rules",accessorKey:"ruleId",cell:({getValue:n,row:s})=>s.getCanExpand()?e.createElement("span",{onClick:s.getToggleExpandedHandler()},n()):null},{header:"Status",accessorKey:"status",size:200,cell:({getValue:n})=>{const s=n();return e.createElement(q,{errors:s?.errorsCount,warnings:s?.warningsCount})},enableSorting:!1},{header:"Problem count",accessorKey:"problemsCount",size:200,sortDescFirst:!0}],[]);return e.createElement(A,{columns:i,data:u,sortBy:[{id:"ruleId",desc:!1}],empty:e.createElement(e.Fragment,null,"\u2705 This API is compliant with ",e.createElement("strong",null,d)," level rules"),subRowKey:"problems"})}function ze(p){const{catalogSlug:u,slug:d,data:i,hasTeam:n,hasPublishedAt:s,teamLabel:l}=p,{apis:f,apisCount:m,warningApisCount:a,errorApisCount:o,levels:E,count:g}=i,t=f.length===0,h=t?void 0:{id:"expand",maxSize:40,size:40,header:()=>null,enableSorting:!1,cell:({row:r})=>r.getCanExpand()?e.createElement(x,{onClick:r.getToggleExpandedHandler(),variant:"text",size:"medium",icon:r.getIsExpanded()?e.createElement(K,null):e.createElement(I,null)}):null},w=b(()=>[h,{header:"API",accessorKey:"title"},s?{header:"Published at",accessorKey:"publishedAt",cell:({getValue:r})=>{const c=r();return c?new Date(c).toLocaleDateString():"-"}}:void 0,n?{header:l,accessorKey:"team",cell:({getValue:r})=>r()||Q}:void 0,{header:"Problem count",size:200,accessorKey:"count",cell:({getValue:r})=>r()||0,sortDescFirst:!0}].filter(R),[n,s,l,t]),y=b(()=>Object.entries(i.teams).sort((r,c)=>c[1]-r[1]),[i.teams]);return e.createElement(V,null,e.createElement(x,{icon:e.createElement(D,null),iconPosition:"left",variant:"ghost",size:"large",to:`${u}scorecard`},"Back to dashboard"),e.createElement("h1",null,"Rule ",d),e.createElement(j,null,e.createElement(S,null,e.createElement(L,null,"Rule status"),e.createElement(F,null,e.createElement("div",null,e.createElement(v,null,m),e.createElement("br",null),"APIs"),e.createElement("div",null,e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},m-a-o),e.createElement(k,{severity:"Pass"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},a),e.createElement(k,{severity:"Warn"})),e.createElement(C,null,e.createElement(v,{style:{textAlign:"right"}},o),e.createElement(k,{severity:"Error"}))))),e.createElement(S,null,e.createElement(L,null,"Problems by level"),e.createElement(W,null,Object.entries(E).map(([r,c])=>e.createElement(B,{key:r},e.createElement("span",null,r),e.createElement(ce,{chunks:[{share:c.errors/g*100,colorVariable:"--scorecard-color-error"},{share:c.warnings/g*100,colorVariable:"--scorecard-color-warning"}]}),e.createElement(oe,null,c.errors+c.warnings))))),e.createElement(S,null,e.createElement(L,null,`Problems by ${l}`),e.createElement(W,null,y.slice(0,4).map(([r,c])=>e.createElement(B,{key:r},e.createElement("span",{style:{flex:1,textAlign:"left"}},r),e.createElement(v,null,c)))))),e.createElement(A,{columns:w,data:f,sortBy:[{id:"title",desc:!1}],subRowKey:"problems",slug:d}))}function Oe(p){const{slug:u,api:d,teamLabel:i,catalogSlug:n}=p,{api:s}=d||{},{scorecard:l,isLoading:f}=se(`/${u}`),m=l?.levels||{},[a,o]=G(Object.keys(m)[0]),E=b(()=>s?.publishedAt instanceof Date?s?.publishedAt.toLocaleDateString():s?.publishedAt,[s]);_(()=>{if(l){const t=l.levels||{};o(Object.keys(t)[0])}},[l]);const g=b(()=>{if(!l?.levels)return{};const t={};for(const h of Object.keys(l.levels)){const w=l.levels[h].problems.reduce((y,r)=>(y[r.ruleId]||(y[r.ruleId]=[]),y[r.ruleId].push(r),y),{});t[h]=Object.entries(w).map(([y,r])=>({ruleId:y,problems:[...r.filter(c=>c.severity==="error"),...r.filter(c=>c.severity==="warn")],problemsCount:r.length,status:{errorsCount:r.filter(c=>c.severity==="error").length,warningsCount:r.filter(c=>c.severity==="warn").length}}))}return t},[l?.levels]);return f?e.createElement(U,{justifyContent:"center",mt:"2em"},e.createElement(N,null)):l?e.createElement(V,null,e.createElement(x,{icon:e.createElement(D,null),iconPosition:"left",variant:"ghost",size:"large",to:`${n}scorecard`},"Back to dashboard"),e.createElement("h1",null,s?.title||u),e.createElement(ee,null,E&&e.createElement(e.Fragment,null,"Published at: ",new Date().toLocaleString(void 0,{timeZoneName:"short"}),e.createElement("br",null)),e.createElement(P,{to:u,external:!0},"Documentation")),e.createElement(j,null,e.createElement(S,null,e.createElement(le,null,e.createElement("span",null,"Scorecard level"),e.createElement(M,{level:l.scorecardLevel,colorVariable:z(l.scorecardLevelIdx,Object.keys(m).length)})),e.createElement(F,null,e.createElement("div",null,"Compliance by level"),e.createElement("div",null,Object.entries(l.levels).map(([t,h])=>e.createElement(C,{key:t},e.createElement(v,{style:{width:"100px",textAlign:"right"}},t),e.createElement($,{errors:h.errors,warnings:h.warnings})))))),e.createElement(J,{title:"Rules compliance",levels:Object.entries(l.levels).map(([t,h])=>({errors:h.uniqueErrors,warnings:h.uniqueWarnings,total:p.rulesPerLevel[t].length,name:t}))}),e.createElement(S,null,e.createElement(L,null,i),e.createElement(v,{style:{fontWeight:"normal"}},s?.team))),e.createElement(re,{style:{justifyContent:"flex-start"},"data-component-name":"Scorecard/ModeSwitcherTabs"},Object.keys(l.levels).map(t=>e.createElement(te,{"data-component-name":"Scorecard/ModeSwitcherTab",key:t,isActive:a===t,onClick:()=>o(t)},e.createElement(C,null,e.createElement("span",null,t)," ",e.createElement($,{errors:l.levels[t].errors,warnings:l.levels[t].warnings}))))),Object.keys(m).map(t=>e.createElement(ne,{key:t,isActive:a===t},e.createElement(ie,{title:t,data:g[t]})))):e.createElement("div",null,"Scorecard not found")}export{Ie as ApiListView,Oe as ApiPage,ie as ApiView,ze as RuleView,Ke as RulesView,De as TeamsView};
@@ -1,2 +1,2 @@
1
- import $ from"node:dns";import{isIP as E}from"node:net";import{withPathPrefix as L}from"@redocly/theme/core/utils";import{ServerRoutes as A}from"../../../constants/api.js";import{envConfig as H}from"../../config/env-config.js";import{getRequestOrigin as D}from"../utils/get-request-origin.js";const M=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),k=new Set(["cookie","cookie2","accept-encoding"]),b=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),y="x-redocly-proxy-streaming",x="x-http-method-override",S="x-redocly-cookie";function oe(n,e=L(A.CORS_PROXY)){return async t=>{const o=new URL(t.req.url).pathname;if(o===e||o===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
- Usage: ${e}/https://api.example.com/path`);const r=J(t.req.url,e);if(!r)return t.text("Invalid proxied URL",400);const a=n?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(p=>V(r,p)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const f=D(t),h=r.origin===f,m=r.pathname===e||r.pathname.startsWith(`${e}/`);if(h&&!m)return new Response("Please use a direct request",{status:308,headers:{Location:r.toString(),Vary:"origin","Cache-Control":"private"}});const u=await Y(r.hostname);if((!H.isDevelopMode||H.isReunite)&&u&&F(u))return t.text("Requests to private network addresses are not allowed",403);const i=new Headers,I=v(t.req.raw.headers);for(const[s,p]of t.req.raw.headers)I.has(s.toLowerCase())||k.has(s.toLowerCase())||i.append(s,p);const g=i.get(S);if(g){const s=t.req.raw.headers.get("cookie")||"";i.set("cookie",s?`${s}; ${g}`:g),i.delete(S)}const O=t.req.raw.headers.get("origin")||"";U(O)&&i.delete("origin");let l=t.req.method;const R=i.get(x);R&&(l=R.toUpperCase(),i.delete(x));const w={method:l,headers:i,redirect:"manual"};l!=="GET"&&l!=="HEAD"&&t.req.raw.body&&(w.body=t.req.raw.body,w.duplex="half");let c;try{c=await fetch(r,w)}catch(s){const p=s instanceof Error?s.message:"unknown error",_=s instanceof Error&&s.cause instanceof Error?`: ${s.cause.message}`:"";return t.text(`Failed to proxy request: ${p}${_}`,502)}const T=c.headers.get("content-type")||"";if(G(T)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),q=v(c.headers);for(const s of q)d.delete(s);for(const s of b)d.delete(s);return d.set(y,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function N(n){try{return decodeURIComponent(n)}catch{return n}}function W(n){return n.replace(/^(https?):\/(?!\/)/i,"$1://")}function z(n,e){return e?n.includes("?")?e==="?"?n:`${n.endsWith("?")||n.endsWith("&")?n:`${n}&`}${e.slice(1)}`:`${n}${e}`:n}function v(n){const e=new Set(M),t=n.get("connection");if(!t)return e;for(const o of t.split(",")){const r=o.trim().toLowerCase();r&&e.add(r)}return e}function U(n){const e=n.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function Y(n){const e=P(n);if(E(e))return e;try{const{address:t}=await $.promises.lookup(e);return t}catch{return null}}function F(n){const e=P(n).toLowerCase();if(!E(e))return!0;const t=X(e);return t?C(t):e.includes(":")?j(e):C(e)}function P(n){return n.startsWith("[")&&n.endsWith("]")?n.slice(1,-1):n}function X(n){const e=n.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);if(e){const o=parseInt(e[1],16),r=parseInt(e[2],16);return[o>>8&255,o&255,r>>8&255,r&255].join(".")}return n.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1]??null}function C(n){const e=n.split(".").map(Number);if(e.length!==4||e.some(r=>isNaN(r)))return!0;const[t,o]=e;return t===0||t===10||t===127||t===172&&o>=16&&o<=31||t===192&&o===168||t===169&&o===254||t===100&&o>=64&&o<=127}function j(n){const e=B(n);return n==="::1"||n==="::"||e!==null&&(e&65024)===64512||e!==null&&(e&65472)===65152}function B(n){const[e]=n.split(":");if(!e)return null;const t=parseInt(e,16);return Number.isNaN(t)?null:t}function V(n,e){let t;try{t=new URL(e)}catch{return!1}if(n.origin!==t.origin)return!1;const o=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return n.pathname.startsWith(o)||n.pathname===t.pathname}function G(n){const e=n.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function J(n,e){const t=new URL(n),o=t.pathname===e,r=t.pathname.startsWith(`${e}/`);if(!o&&!r)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const f=[a,N(a)];for(const h of f){const m=W(h),u=z(m,t.search);try{const i=new URL(u);if(i.protocol==="http:"||i.protocol==="https:")return i}catch{continue}}return null}const re=y;export{re as CORS_PROXY_STREAM_HEADER,oe as corsProxyHandler,F as isPrivateIp,J as resolveCorsProxyTarget};
1
+ import L from"node:dns";import{isIP as H}from"node:net";import{Agent as $}from"undici";import{withPathPrefix as M}from"@redocly/theme/core/utils";import{ServerRoutes as D}from"../../../constants/api.js";import{envConfig as y}from"../../config/env-config.js";import{getRequestOrigin as b}from"../utils/get-request-origin.js";import{SsrfBlockedError as N}from"../utils/ssrf-blocklist.js";import{createSsrfSafeLookup as W}from"../utils/ssrf-safe-lookup.js";const z=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),U=new Set(["cookie","cookie2","accept-encoding"]),Y=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),S="x-redocly-proxy-streaming",x="x-http-method-override",P="x-redocly-cookie",v="Requests to private network addresses are not allowed",F=new $({connect:{lookup:W()}});function ue(n,e=M(D.CORS_PROXY)){return async t=>{const r=new URL(t.req.url).pathname;if(r===e||r===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
+ Usage: ${e}/https://api.example.com/path`);const s=ne(t.req.url,e);if(!s)return t.text("Invalid proxied URL",400);const a=n?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(f=>ee(s,f)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const m=b(t),g=s.origin===m,w=s.pathname===e||s.pathname.startsWith(`${e}/`);if(g&&!w)return new Response("Please use a direct request",{status:308,headers:{Location:s.toString(),Vary:"origin","Cache-Control":"private"}});const u=await j(s.hostname),p=!y.isDevelopMode||y.isReunite;if(p&&u&&J(u))return t.text(v,403);const i=new Headers,A=T(t.req.raw.headers);for(const[o,f]of t.req.raw.headers)A.has(o.toLowerCase())||U.has(o.toLowerCase())||i.append(o,f);const R=i.get(P);if(R){const o=t.req.raw.headers.get("cookie")||"";i.set("cookie",o?`${o}; ${R}`:R),i.delete(P)}const O=t.req.raw.headers.get("origin")||"";X(O)&&i.delete("origin");let l=t.req.method;const E=i.get(x);E&&(l=E.toUpperCase(),i.delete(x));const h={method:l,headers:i,redirect:"manual"};p&&(h.dispatcher=F),l!=="GET"&&l!=="HEAD"&&t.req.raw.body&&(h.body=t.req.raw.body,h.duplex="half");let c;try{c=await fetch(s,h)}catch(o){if(o instanceof Error&&o.cause instanceof N)return t.text(v,403);const f=o instanceof Error?o.message:"unknown error",q=o instanceof Error&&o.cause instanceof Error?`: ${o.cause.message}`:"";return t.text(`Failed to proxy request: ${f}${q}`,502)}const _=c.headers.get("content-type")||"";if(te(_)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),k=T(c.headers);for(const o of k)d.delete(o);for(const o of Y)d.delete(o);return d.set(S,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function B(n){try{return decodeURIComponent(n)}catch{return n}}function G(n){return n.replace(/^(https?):\/(?!\/)/i,"$1://")}function V(n,e){return e?n.includes("?")?e==="?"?n:`${n.endsWith("?")||n.endsWith("&")?n:`${n}&`}${e.slice(1)}`:`${n}${e}`:n}function T(n){const e=new Set(z),t=n.get("connection");if(!t)return e;for(const r of t.split(",")){const s=r.trim().toLowerCase();s&&e.add(s)}return e}function X(n){const e=n.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function j(n){const e=C(n);if(H(e))return e;try{const{address:t}=await L.promises.lookup(e);return t}catch{return null}}function J(n){const e=C(n).toLowerCase();if(!H(e))return!0;const t=K(e);return t?I(t):e.includes(":")?Q(e):I(e)}function C(n){return n.startsWith("[")&&n.endsWith("]")?n.slice(1,-1):n}function K(n){const e=n.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);if(e){const r=parseInt(e[1],16),s=parseInt(e[2],16);return[r>>8&255,r&255,s>>8&255,s&255].join(".")}return n.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1]??null}function I(n){const e=n.split(".").map(Number);if(e.length!==4||e.some(s=>isNaN(s)))return!0;const[t,r]=e;return t===0||t===10||t===127||t===172&&r>=16&&r<=31||t===192&&r===168||t===169&&r===254||t===100&&r>=64&&r<=127}function Q(n){const e=Z(n);return n==="::1"||n==="::"||e!==null&&(e&65024)===64512||e!==null&&(e&65472)===65152}function Z(n){const[e]=n.split(":");if(!e)return null;const t=parseInt(e,16);return Number.isNaN(t)?null:t}function ee(n,e){let t;try{t=new URL(e)}catch{return!1}if(n.origin!==t.origin)return!1;const r=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return n.pathname.startsWith(r)||n.pathname===t.pathname}function te(n){const e=n.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function ne(n,e){const t=new URL(n),r=t.pathname===e,s=t.pathname.startsWith(`${e}/`);if(!r&&!s)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const m=[a,B(a)];for(const g of m){const w=G(g),u=V(w,t.search);try{const p=new URL(u);if(p.protocol==="http:"||p.protocol==="https:")return p}catch{continue}}return null}const le=S;export{le as CORS_PROXY_STREAM_HEADER,ue as corsProxyHandler,J as isPrivateIp,ne as resolveCorsProxyTarget};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/realm",
3
- "version": "0.138.0-next.6",
3
+ "version": "0.138.0-next.7",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,13 +33,13 @@
33
33
  "@opentelemetry/sdk-trace-web": "2.8.0",
34
34
  "@opentelemetry/semantic-conventions": "1.40.0",
35
35
  "@redocly/ajv": "8.18.0",
36
- "@redocly/api-docs": "0.3.0-next.4",
37
- "@redocly/config": "0.61.0",
36
+ "@redocly/api-docs": "0.3.0-next.5",
37
+ "@redocly/config": "0.61.1",
38
38
  "@redocly/openapi-core": "2.55.0",
39
39
  "@redocly/portal-legacy-ui": "0.19.0",
40
40
  "@redocly/portal-plugin-mock-server": "0.23.0-next.0",
41
41
  "@redocly/realm-asyncapi-sdk": "0.16.0-next.2",
42
- "@redocly/theme": "0.70.0-next.4",
42
+ "@redocly/theme": "0.70.0-next.5",
43
43
  "@shikijs/transformers": "3.21.0",
44
44
  "@tanstack/react-query": "5.62.3",
45
45
  "@tanstack/react-table": "8.21.3",