@redocly/realm 0.138.0-next.12 → 0.138.0-next.13

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 (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/client/app/pages/Invite/Invite.js +1 -1
  3. package/dist/constants/l10n/langs/ar.js +1 -1
  4. package/dist/constants/l10n/langs/de.js +1 -1
  5. package/dist/constants/l10n/langs/en.js +1 -1
  6. package/dist/constants/l10n/langs/es.js +1 -1
  7. package/dist/constants/l10n/langs/fr.js +1 -1
  8. package/dist/constants/l10n/langs/hi.js +1 -1
  9. package/dist/constants/l10n/langs/it.js +1 -1
  10. package/dist/constants/l10n/langs/ja.js +1 -1
  11. package/dist/constants/l10n/langs/ko.js +1 -1
  12. package/dist/constants/l10n/langs/pl.js +1 -1
  13. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  14. package/dist/constants/l10n/langs/pt.js +1 -1
  15. package/dist/constants/l10n/langs/ru.js +1 -1
  16. package/dist/constants/l10n/langs/uk.js +1 -1
  17. package/dist/constants/l10n/langs/zh.js +1 -1
  18. package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/file-chunk-parser.d.ts +1 -1
  19. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
  20. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +1 -0
  21. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -5
  22. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -1
  23. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +9 -3
  24. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  25. package/dist/server/plugins/mcp/gateway-mcp/subject.js +1 -1
  26. package/dist/server/plugins/mcp/handlers/credentials-page/constants.d.ts +7 -0
  27. package/dist/server/plugins/mcp/handlers/credentials-page/constants.js +1 -0
  28. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.d.ts +5 -0
  29. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.js +1 -0
  30. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.d.ts +14 -0
  31. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.js +62 -0
  32. package/dist/server/plugins/mcp/handlers/credentials-page/layout.d.ts +17 -0
  33. package/dist/server/plugins/mcp/handlers/credentials-page/layout.js +11 -0
  34. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.d.ts +10 -0
  35. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.js +11 -0
  36. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.d.ts +10 -0
  37. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.js +1 -0
  38. package/dist/server/plugins/mcp/handlers/credentials-page/styles.d.ts +4 -0
  39. package/dist/server/plugins/mcp/handlers/credentials-page/styles.js +186 -0
  40. package/dist/server/plugins/mcp/handlers/credentials-page/types.d.ts +32 -0
  41. package/dist/server/plugins/mcp/handlers/credentials-page/types.js +0 -0
  42. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.d.ts +0 -1
  43. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
  44. package/dist/server/plugins/mcp/index.js +1 -1
  45. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  46. package/dist/server/plugins/mcp/types.d.ts +6 -0
  47. package/dist/server/plugins/mcp/utils/mode.d.ts +4 -1
  48. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  49. package/dist/server/plugins/scorecard-classic/template/components.js +1 -1
  50. package/dist/server/providers/database/client.d.ts +5 -0
  51. package/dist/server/providers/database/client.js +1 -1
  52. package/dist/server/providers/database/copy-migrations.js +1 -1
  53. package/dist/server/providers/database/database-connections-manager.js +1 -1
  54. package/dist/server/providers/database/has-project-database.d.ts +3 -0
  55. package/dist/server/providers/database/has-project-database.js +1 -0
  56. package/dist/server/web-server/auth.d.ts +0 -1
  57. package/dist/server/web-server/auth.js +4 -4
  58. package/dist/server/web-server/jwt/token-types.d.ts +1 -0
  59. package/dist/server/web-server/jwt/token-types.js +1 -1
  60. package/dist/server/web-server/routes/catalog/helpers/create-entity-schema.d.ts +1756 -1756
  61. package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.d.ts +295 -295
  62. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.d.ts +24 -0
  63. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.js +35 -0
  64. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.d.ts +15 -0
  65. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.js +1 -0
  66. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +3 -1
  67. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  68. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  69. package/package.json +5 -5
  70. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +0 -12
  71. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +0 -225
@@ -1 +1 @@
1
- import{secretStore as u}from"../gateway-mcp/secret-store.js";import{rejectCredential as b}from"../gateway-mcp/credential-validation.js";import{buildAllowedHostMatchers as A}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as j}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as v,getDefaultAgent as O}from"../gateway-mcp/fetch/fetch-bridge.js";import{logger as F}from"../../../tools/notifiers/logger.js";import{clearedPage as I,formPage as x,multiOutcomePage as N,notFoundPage as D,successPage as $}from"./mcp-credentials-page.js";const g={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},B=3e4,M=64*1024,U=/^bearer\s+/i;let P=fetch;function ee(e){P=e}const G=async(e,i)=>{const t=await i.getKv();if(e.method==="GET"){const n=new URL(e.url).searchParams.get("token")??"",o=await u.peekElicitation(t,n);return o?new Response(x(o,n),{status:200,headers:g}):m()}if(e.method==="POST"){const n=await e.formData(),o=d(n,"token"),s=d(n,"mode")||"token";return s==="multi"?z(t,n,o):s==="client-credentials"?K(t,n,o):s==="clear"?X(t,o):H(t,n,o)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function H(e,i,t){const n=d(i,"secret").trim(),o=await u.peekElicitation(e,t);if(!o)return m();const s=b(o.auth,n);if(s)return h(o,t,s);const l=await u.consumeElicitation(e,t);return l?(await u.setSecret(e,l.subject,l.api,l.scheme,S(l.auth,n)),new Response($(l),{status:200,headers:g})):m()}async function X(e,i){const t=await u.consumeElicitation(e,i);return t?(await u.deleteSecret(e,t.subject,t.api,t.scheme),new Response(I(t),{status:200,headers:g})):m()}async function z(e,i,t){const n=await u.peekElicitation(e,t);if(!n?.schemes||n.schemes.length<=1)return m();const o=n.schemes,s=[];for(let r=0;r<o.length;r++){const a=o[r],p=d(i,`secret__${r}`).trim(),E=d(i,`client_id__${r}`).trim(),_=d(i,`client_secret__${r}`).trim();if(a.hasExistingSecret&&d(i,`remove__${r}`)){if(p||E||_)return h(n,t,`${a.name}: enter a new value or tick "Remove the stored credential", not both.`);s.push({kind:"remove",scheme:a.name});continue}if(a.auth.kind==="oauth2ClientCredentials"){if(p){const C=b(a.auth,p);if(C)return h(n,t,`${a.name}: ${C}`);s.push({kind:"set",scheme:a.name,secret:S(a.auth,p)})}else if(E&&_)s.push({kind:"exchange",scheme:a.name,auth:a.auth,clientId:E,clientSecret:_});else if(E||_)return h(n,t,`${a.name}: enter both the client ID and the client secret, or leave both blank.`);continue}if(!p)continue;const y=b(a.auth,p);if(y)return h(n,t,`${a.name}: ${y}`);s.push({kind:"set",scheme:a.name,secret:S(a.auth,p)})}if(s.length===0)return h(n,t,"Fill in at least one credential, or check a box to remove one, before saving.");const l=[];for(const r of s){if(r.kind!=="exchange"){l.push(r);continue}const a=await R(r.auth,r.clientId,r.clientSecret);if(!a.ok)return T(n,t,a.reason,r.scheme);l.push({kind:"set",scheme:r.scheme,secret:a.secret})}const c=await u.consumeElicitation(e,t);if(!c)return m();for(const r of l)r.kind==="remove"?await u.deleteSecret(e,c.subject,c.api,r.scheme):await u.setSecret(e,c.subject,c.api,r.scheme,r.secret);const k=await u.getSecretsForSubject(e,c.subject),f={saved:l.filter(r=>r.kind==="set").map(r=>r.scheme),removed:l.filter(r=>r.kind==="remove").map(r=>r.scheme),stored:Object.keys(k[c.api]??{})};return new Response(N(c,f),{status:200,headers:g})}async function K(e,i,t){const n=d(i,"client_id").trim(),o=d(i,"client_secret").trim(),s=await u.peekElicitation(e,t);if(!s||s.auth.kind!=="oauth2ClientCredentials")return m();if(!n||!o)return h(s,t,"Enter both the client ID and the client secret before saving.");const l=await R(s.auth,n,o);if(!l.ok)return T(s,t,l.reason);const c=await u.consumeElicitation(e,t);return c?(await u.setSecret(e,c.subject,c.api,c.scheme,l.secret),new Response($(c),{status:200,headers:g})):m()}async function R(e,i,t){try{const n=A([{url:e.tokenUrl}]),o=j(e.tokenUrl,n),s=new AbortController,l=setTimeout(()=>s.abort(),B);let c;try{c=await P(o,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:i,client_secret:t}),redirect:"manual",signal:s.signal,dispatcher:O()})}finally{clearTimeout(l)}if(!c.ok)return w(`token endpoint returned ${c.status}`);let k;try{k=await v(c,M)}catch{return w("token response exceeded the size cap")}let f;try{f=JSON.parse(k)}catch{return w("token response was not valid JSON")}const r=typeof f=="object"&&f!==null&&"access_token"in f?f.access_token:void 0,a=typeof r=="string"?r.trim():"";return a?b(e,a)?w("token response had an access_token that cannot be sent in a header"):{ok:!0,secret:{header:"Authorization",value:`Bearer ${a}`}}:w("token response had no access_token")}catch(n){return w(n instanceof Error?n.message:String(n))}}function w(e){return{ok:!1,reason:e}}function T(e,i,t,n){return F.warn(`gateway oauth2 client_credentials exchange failed for ${e.api}: ${t}`),h(e,i,`${n?`${n}: we`:"We"} could not exchange those client credentials for a token. Check the client ID and secret, then try again.`)}function d(e,i){const t=e.get(i);return typeof t=="string"?t:""}function h(e,i,t){return new Response(x(e,i,t),{status:400,headers:g})}function m(){return new Response(D(),{status:404,headers:g})}function S(e,i){return e.kind==="apiKey"?{header:e.header,value:i}:e.kind==="cookie"?{header:"Cookie",value:`${e.name}=${i}`}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:J(i)}:{header:"Authorization",value:i}}function J(e){return`Bearer ${e.trim().replace(U,"")}`}var te=G;export{te as default,ee as setCredentialsFetchImpl};
1
+ import{secretStore as d}from"../gateway-mcp/secret-store.js";import{rejectCredential as _}from"../gateway-mcp/credential-validation.js";import{buildAllowedHostMatchers as C}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as A}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as T,getDefaultAgent as P}from"../gateway-mcp/fetch/fetch-bridge.js";import{logger as x}from"../../../tools/notifiers/logger.js";import{FORM_ACTIONS as k}from"./credentials-page/constants.js";import{credentialSchemesOf as w,schemeFieldName as f}from"./credentials-page/form-contract.js";import{formPage as N}from"./credentials-page/form-page.js";import{htmlDocument as O}from"./credentials-page/layout.js";import{declinedPage as R,expiredPage as I,outcomePage as j}from"./credentials-page/outcome-pages.js";const D={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"},$=3e4,F=64*1024,M=/^bearer\s+/i,U=/^\d+$/,B=async(e,t,o)=>{const n=await t.getKv(),r=L(e,t.baseUrl,o);if(e.method==="GET"){const i=new URL(e.url).searchParams.get("token")??"",c=await d.peekElicitation(n,i);return c?S({kv:n,site:r,token:i,target:c},{},200):p(r)}if(e.method==="POST"){const i=await e.formData(),c=u(i,"token"),a=await d.peekElicitation(n,c);if(!a)return p(r);const s={kv:n,site:r,token:c,target:a,form:i};switch(u(i,"action")){case k.remove:return b(s,z(a,u(i,"scheme")));case k.removeAll:return b(s,w(a));case k.decline:return K(s);default:return H(s)}}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function H(e){const{target:t,form:o}=e,n=w(t),r=n.map((s,l)=>u(o,f("client_id",l)).trim()),i=s=>S(e,{error:s,clientIds:r},400),c=X(n,o);if("error"in c)return i(c.error);if(c.writes.length===0)return i(G(n));const a=[];for(const s of c.writes){if(s.kind!=="exchange"){a.push(s);continue}const l=await J(s.auth,s.clientId,s.clientSecret);if(!l.ok)return x.warn(`gateway oauth2 client_credentials exchange failed for ${t.api}: ${l.reason}`),i(E(n,s.schemeIndex,"client_id","We could not exchange those client credentials for a token. Check the client ID and secret, then try again."));a.push({kind:"set",scheme:s.scheme,secret:l.secret})}return v(e,a)}function X(e,t){const o=[];for(const[n,r]of e.entries()){const i=u(t,f("secret",n)).trim();if(i){const s=_(r.auth,i);if(s)return{error:E(e,n,"secret",s)};o.push({kind:"set",scheme:r.name,secret:W(r.auth,i)});continue}if(r.auth.kind!=="oauth2ClientCredentials")continue;const c=u(t,f("client_id",n)).trim(),a=u(t,f("client_secret",n)).trim();if(c&&a)o.push({kind:"exchange",scheme:r.name,schemeIndex:n,auth:r.auth,clientId:c,clientSecret:a});else if(c||a)return{error:E(e,n,c?"client_secret":"client_id","Enter both the client ID and the client secret, or leave both blank.")}}return{writes:o}}function G(e){return e.length>1?{message:"Fill in at least one credential before saving."}:{message:e[0].auth.kind==="oauth2ClientCredentials"?"Enter an access token, or the client ID and the client secret, before saving.":"Enter a value before saving.",schemeIndex:0,field:"secret"}}function E(e,t,o,n){return{message:e.length>1?`${e[t].name}: ${n}`:n,schemeIndex:t,field:o}}async function b(e,t){const{kv:o,target:n}=e,r=await d.getStoredSchemes(o,n.subject,n.api),i=t.filter(c=>r[c.name]).map(c=>({kind:"remove",scheme:c.name}));return i.length===0?S(e,{error:{message:"No credential is stored there, so there is nothing to remove."}},400):v(e,i)}function z(e,t){const o=U.test(t)?w(e)[Number(t)]:void 0;return o?[o]:[]}async function K({kv:e,site:t,token:o}){return await d.consumeElicitation(e,o)?h(t,R(),200):p(t)}async function v({kv:e,site:t,token:o},n){const r=await d.consumeElicitation(e,o);if(!r)return p(t);for(const a of n)a.kind==="remove"?await d.deleteSecret(e,r.subject,r.api,a.scheme):await d.setSecret(e,r.subject,r.api,a.scheme,a.secret);const i=await d.getStoredSchemes(e,r.subject,r.api),c={action:n.some(a=>a.kind==="set")?"saved":"removed",schemes:n.map(a=>a.scheme),stored:Object.keys(i).sort()};return h(t,j(r,c),200)}async function S({kv:e,site:t,token:o,target:n},r,i){const c=await d.getStoredSchemes(e,n.subject,n.api);return h(t,N({site:t,target:n,token:o,stored:c,...r}),i)}function L(e,t,o){const n=o?.props?.branding??{},r=t&&URL.canParse(t)?t:e.url;return{...n,host:new URL(r).host}}async function J(e,t,o){try{const n=C([{url:e.tokenUrl}]),r=A(e.tokenUrl,n),i=new AbortController,c=setTimeout(()=>i.abort(),$);let a;try{a=await fetch(r,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:t,client_secret:o}),redirect:"manual",signal:i.signal,dispatcher:P()})}finally{clearTimeout(c)}if(!a.ok)return m(`token endpoint returned ${a.status}`);let s;try{s=await T(a,F)}catch{return m("token response exceeded the size cap")}let l;try{l=JSON.parse(s)}catch{return m("token response was not valid JSON")}const y=typeof l=="object"&&l!==null&&"access_token"in l?l.access_token:void 0,g=typeof y=="string"?y.trim():"";return g?_(e,g)?m("token response had an access_token that cannot be sent in a header"):{ok:!0,secret:{header:"Authorization",value:`Bearer ${g}`}}:m("token response had no access_token")}catch(n){return m(n instanceof Error?n.message:String(n))}}function m(e){return{ok:!1,reason:e}}function u(e,t){const o=e.get(t);return typeof o=="string"?o:""}function h(e,t,o){return new Response(O(t,e.colorScheme),{status:o,headers:D})}function p(e){return h(e,I(),404)}function W(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="cookie"?{header:"Cookie",value:`${e.name}=${t}`}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:Y(t)}:{header:"Authorization",value:t}}function Y(e){return`Bearer ${e.replace(M,"")}`}var ie=B;export{ie as default};
@@ -1 +1 @@
1
- import{fileURLToPath as U}from"node:url";import{dirname as $,join as h}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as D}from"@redocly/theme/core/constants/mcp";import{logger as c}from"../../tools/notifiers/logger.js";import{reporter as F}from"../../tools/notifiers/reporter.js";import{isDefined as B}from"../../../utils/guards/is-defined.js";import{MCP_CREDENTIALS_HANDLER_ID as P,MCP_CREDENTIALS_ROUTE as x,MCP_DOCS_SERVER_HANDLER_ID as E,MCP_NOT_ALLOWED_TEMPLATE_ID as V}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as J,bundledGraphqlDocumentsToMcpOpenApi as W,buildGraphqlDescriptions as X,collectIgnoredRoutes as z,filterIgnoredApiDescriptions as A,getCleanedUpApiDescriptions as K,isMcpInRedirects as Q}from"./utils.js";import{getMcpMode as R,isMcpElicitationEnabled as Y,isMcpGatewayEnabled as Z}from"./utils/mode.js";import{getPublicMcpEndpointStatus as _}from"./utils/public-endpoint.js";import{findGatedToolNames as ee}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as te}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as oe}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as ie,isGatewayEligible as re}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as se}from"./gateway-mcp/rate-limit/limits.js";import{resetCustomMcpTools as ne,scanCustomMcpTools as ae}from"./custom-tools/scan-custom-tools.js";import{telemetryTraceStep as ce}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as S}from"./constants.js";const M=$(U(import.meta.url));async function pe(){let i;const l={registeredToolNames:[],warnedNoOutputSchemaTools:new Set};let m=0,u=!1;return{id:"mcp",async processContent(e,n){await ce("com.redocly.build.plugin.mcp",async s=>{const o=await n.getConfig(),{mcp:r,redirects:f}=o;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",R()),s?.setAttribute("mcpGateway",String(Z())),r?.hide||r?.docs?.hide){ne(e,l);return}Q(f)&&await F.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(V,"@redocly/theme/layouts/McpNotAllowed");const p=!!o?.access?.requiresLogin,b=o?.access?.rbac||{},g=R(),d=Y(),{budgetPerExec:T}=se();if(!i){c.info("Configuring MCP servers...");const w=J(await e.loadApiDocuments(n)),O=W(await e.loadApiDocuments(n)),v=r?.docs?.ignore||[],L=K(A(w,v)),q=ie(r),C=new Map;for(const t of L){const H=t.definition.info?.title||"",j=t.definition.info?.version||"";C.set(t.relativePath,{name:H,description:typeof t.definition.info?.description=="string"?t.definition.info.description:"",version:j,servers:t.definition.servers||[],relativePath:t.relativePath,...q&&re(t.definition.info,t.relativePath,b)?{fetchEligible:!0}:{}})}const y=X(A(O,v)),I=Object.values(o.products||{}).map(t=>t?.name).filter(B),N=o.l10n?.locales?.map(t=>t.code)??n.fs.localeFolders,G=te({products:I,locales:N,requiresLogin:p,rbac:b,hasGraphqlApis:y.length>0,elicitationEnabled:d,hasCallableApis:C.values().some(t=>t.fetchEligible),feedbackHidden:o.feedback?.hide===!0});e.addMcpTools(h(M,"./docs-mcp/tools/index.js"),G),g==="code"&&e.addMcpTools(h(M,"./codemode/tools/execute.js"),[oe]),i={apiDescriptionsMap:Object.fromEntries(C),graphqlDescriptions:y}}const a=await ae(e,n,l);s?.setAttribute("customToolsCount",String(a)),a!==m&&(m=a,a>0&&c.info(`Registered ${a} custom MCP tool${a===1?"":"s"} from @mcp folders`)),await de({actions:e,handlerPath:h(M,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:i.apiDescriptionsMap,graphqlDescriptions:i.graphqlDescriptions,mcpConfig:r,mcpMode:g,elicitationEnabled:d,gatewayFetchBudgetPerExec:T,publicEndpointStatus:_(o)}),u||(c.info("Registered Docs MCP Server endpoint at /mcp"),u=!0)})},async afterRoutesCreated(e,n){const s=await n.getConfig(),{mcp:o}=s,r=!o?.hide&&!o?.docs?.hide;e.markGatedMcpTools(await ee(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:o?.docs?.name||S,publicEndpoint:_(s)==="served"}}})}}}var Se=pe;async function de({actions:i,handlerPath:l,apiDescriptionsMap:m,graphqlDescriptions:u,mcpConfig:e,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:o,publicEndpointStatus:r}){i.createRequestHandler(E,l);const f=p=>async(b,g)=>{const d=z(g.getAllRoutes(),e?.docs?.ignore||[]);return d.length>0&&c.verbose(`mcp.docs.ignore: hiding ${d.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:m,graphqlDescriptions:u,mcpDocsServerName:e?.docs?.name||S,mcpMode:n,elicitationEnabled:s,gatewayFetchBudgetPerExec:o,searchIgnoredRoutes:d,...p?{isPublicEndpoint:p}:{}},serverOutDir:i.serverOutDir,tools:i.getMcpTools().map(({importPath:T,...a})=>a)}}};if(i.addApiRoute({slug:k,requestHandlerId:E,httpMethod:"all",getStaticData:f(!1)}),r==="no-protected-content"?c.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${D}.`):r==="anonymous-denied"?c.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${D}.`):r==="served"&&(i.addApiRoute({slug:D,requestHandlerId:E,httpMethod:"all",getStaticData:f(!0)}),c.verbose(`Registered public Docs MCP Server endpoint at ${D}`)),s){const p=h(M,"./handlers/mcp-credentials-handler.js");i.createRequestHandler(P,p),i.addApiRoute({slug:x,requestHandlerId:P,httpMethod:"all",getStaticData:async()=>({props:{}})}),c.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Se as default,pe as mcpServerPlugin};
1
+ import{fileURLToPath as U}from"node:url";import{dirname as $,join as M}from"node:path";import{MCP_ROUTE as k,PUBLIC_MCP_ROUTE as b}from"@redocly/theme/core/constants/mcp";import{logger as p}from"../../tools/notifiers/logger.js";import{reporter as F}from"../../tools/notifiers/reporter.js";import{isDefined as B}from"../../../utils/guards/is-defined.js";import{MCP_CREDENTIALS_HANDLER_ID as P,MCP_CREDENTIALS_ROUTE as V,MCP_DOCS_SERVER_HANDLER_ID as T,MCP_NOT_ALLOWED_TEMPLATE_ID as x}from"../../constants/mcp.js";import{bundledOpenApiDocumentsToMcpOpenApi as J,bundledGraphqlDocumentsToMcpOpenApi as W,buildGraphqlDescriptions as X,collectIgnoredRoutes as z,filterIgnoredApiDescriptions as A,getCleanedUpApiDescriptions as K,isMcpInRedirects as Q}from"./utils.js";import{getMcpMode as R,isMcpElicitationEnabled as Y,isMcpGatewayEnabled as Z}from"./utils/mode.js";import{getPublicMcpEndpointStatus as _}from"./utils/public-endpoint.js";import{findGatedToolNames as ee}from"./utils/find-gated-tool-names.js";import{getDefaultToolSchemas as oe}from"./docs-mcp/tool-schemas.js";import{EXECUTE_TOOL_SCHEMA as te}from"./codemode/tools/execute-schema.js";import{isGatewayCapabilityEnabled as ie,isGatewayEligible as re}from"./gateway-mcp/eligibility.js";import{resolveGatewayFetchLimits as se}from"./gateway-mcp/rate-limit/limits.js";import{resetCustomMcpTools as ne,scanCustomMcpTools as ae}from"./custom-tools/scan-custom-tools.js";import{resolveSiteBranding as ce}from"./handlers/credentials-page/site-branding.js";import{telemetryTraceStep as pe}from"../../../cli/telemetry/helpers/trace-step.js";import{DEFAULT_DOCS_MCP_SERVER_NAME as S}from"./constants.js";const C=$(U(import.meta.url));async function de(){let i;const m={registeredToolNames:[],warnedNoOutputSchemaTools:new Set};let u=0,f=!1;return{id:"mcp",async processContent(e,a){await pe("com.redocly.build.plugin.mcp",async s=>{const t=await a.getConfig(),{mcp:r,redirects:g}=t;if(s?.setAttribute("config",JSON.stringify(r||{})),s?.setAttribute("mcpMode",R()),s?.setAttribute("mcpGateway",String(Z())),r?.hide||r?.docs?.hide){ne(e,m);return}Q(g)&&await F.panicOnBuildContentError('The reserved route "/mcp" cannot be used in redirect configuration. Please remove any redirects involving "/mcp" from your redocly.yaml config.'),e.createTemplate(x,"@redocly/theme/layouts/McpNotAllowed");const l=!!t?.access?.requiresLogin,h=t?.access?.rbac||{},d=R(),c=Y(),{budgetPerExec:D}=se();if(!i){p.info("Configuring MCP servers...");const w=J(await e.loadApiDocuments(a)),O=W(await e.loadApiDocuments(a)),v=r?.docs?.ignore||[],L=K(A(w,v)),q=ie(r),E=new Map;for(const o of L){const H=o.definition.info?.title||"",j=o.definition.info?.version||"";E.set(o.relativePath,{name:H,description:typeof o.definition.info?.description=="string"?o.definition.info.description:"",version:j,servers:o.definition.servers||[],relativePath:o.relativePath,...q&&re(o.definition.info,o.relativePath,h)?{fetchEligible:!0}:{}})}const y=X(A(O,v)),G=Object.values(t.products||{}).map(o=>o?.name).filter(B),I=t.l10n?.locales?.map(o=>o.code)??a.fs.localeFolders,N=oe({products:G,locales:I,requiresLogin:l,rbac:h,hasGraphqlApis:y.length>0,elicitationEnabled:c,hasCallableApis:E.values().some(o=>o.fetchEligible),feedbackHidden:t.feedback?.hide===!0});e.addMcpTools(M(C,"./docs-mcp/tools/index.js"),N),d==="code"&&e.addMcpTools(M(C,"./codemode/tools/execute.js"),[te]),i={apiDescriptionsMap:Object.fromEntries(E),graphqlDescriptions:y}}const n=await ae(e,a,m);s?.setAttribute("customToolsCount",String(n)),n!==u&&(u=n,n>0&&p.info(`Registered ${n} custom MCP tool${n===1?"":"s"} from @mcp folders`)),await le({actions:e,handlerPath:M(C,"./handlers/docs-mcp-handler.js"),apiDescriptionsMap:i.apiDescriptionsMap,graphqlDescriptions:i.graphqlDescriptions,mcpConfig:r,mcpMode:d,elicitationEnabled:c,gatewayFetchBudgetPerExec:D,publicEndpointStatus:_(t)}),f||(p.info("Registered Docs MCP Server endpoint at /mcp"),f=!0)})},async afterRoutesCreated(e,a){const s=await a.getConfig(),{mcp:t}=s,r=!t?.hide&&!t?.docs?.hide;e.markGatedMcpTools(await ee(e.getMcpTools())),e.setGlobalData({mcpData:{docs:{enabled:r,name:t?.docs?.name||S,publicEndpoint:_(s)==="served"}}})}}}var Oe=de;async function le({actions:i,handlerPath:m,apiDescriptionsMap:u,graphqlDescriptions:f,mcpConfig:e,mcpMode:a,elicitationEnabled:s,gatewayFetchBudgetPerExec:t,publicEndpointStatus:r}){i.createRequestHandler(T,m);const g=l=>async(h,d)=>{const c=z(d.getAllRoutes(),e?.docs?.ignore||[]);return c.length>0&&p.verbose(`mcp.docs.ignore: hiding ${c.length} page routes from MCP search`),{props:{config:{apiDescriptionsMap:u,graphqlDescriptions:f,mcpDocsServerName:e?.docs?.name||S,mcpMode:a,elicitationEnabled:s,gatewayFetchBudgetPerExec:t,searchIgnoredRoutes:c,...l?{isPublicEndpoint:l}:{}},serverOutDir:i.serverOutDir,tools:i.getMcpTools().map(({importPath:D,...n})=>n)}}};if(i.addApiRoute({slug:k,requestHandlerId:T,httpMethod:"all",getStaticData:g(!1)}),r==="no-protected-content"?p.info(`mcp.docs.publicEndpoint is enabled, but the project has no protected content - /mcp already serves anonymous users, skipping ${b}.`):r==="anonymous-denied"?p.info(`mcp.docs.publicEndpoint is enabled, but anonymous users are denied the MCP feature (rbac.features.mcp or requiresLogin) - skipping ${b}.`):r==="served"&&(i.addApiRoute({slug:b,requestHandlerId:T,httpMethod:"all",getStaticData:g(!0)}),p.verbose(`Registered public Docs MCP Server endpoint at ${b}`)),s){const l=M(C,"./handlers/mcp-credentials-handler.js");i.createRequestHandler(P,l),i.addApiRoute({slug:V,requestHandlerId:P,httpMethod:"all",getStaticData:async(h,d)=>{const{seo:c,colorMode:D}=d.getConfig(),{logo:n}=d.getGlobalData();return{props:{branding:ce({seo:c,logo:n,colorMode:D})}}}}),p.verbose("Registered MCP credentials endpoint at /mcp/credentials")}}export{Oe as default,de as mcpServerPlugin};
@@ -1,3 +1,3 @@
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(`
1
+ import{McpServer as C}from"@modelcontextprotocol/server";import{logger as S}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as _,MCP_TOOL_WORKER_KEY as O}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,u=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 c=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 d=async()=>{l=await c();const m={toolName:r,args:n,context:{...e,upstreamSecrets:l},extra:ee(s)};return await _.exec(O,[m],{timeout:Y})};let p=await d();const f=D(p);if(f.length>0&&i&&!o)return E(I(p,[{type:"text",text:V}]));if(f.length>0&&i&&o&&t?.getKv){S.info(`gateway mcp credentials prompt: era=${u?"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=u?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":p=await d();break;case"prependText":p=I(p,[{type:"text",text:g.text},...p.content]);break;case"none":break}}return E(p)}}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 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};
3
+ `)}function A(e,t,r,n,s){const[i,...u]=t,a=s.get(i.api),o=i.scheme??a?.defaultScheme??y,c=i.schemes&&i.schemes.length>1?i.schemes:void 0,l=c?void 0:T(i,s);return{subject:r,baseUrl:e.baseUrl??"",target:{api:i.api,name:i.name,scheme:o,...l?{schemeLabel:l}:{},...c?{acceptedSchemes:c}:{}},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:u.map(d=>d.name)}}}async function x(e,t,r,n,s,i,u,a){const o=H(t.mcpReq.inputResponses);if(o==="declined")return{kind:"none"};const c=r.filter(d=>R(d,i));if(c.length===0)return{kind:"prependText",text:M(r,u)};if(o==="accepted"){const d=await K(t.mcpReq.requestState(),{kv:s,subject:n,baseUrl:e.baseUrl??"",apis:c.map(f=>f.api)}),p=d?.apiName??c[0].name;return{kind:"prependText",text:j(p,d?.url)}}const l=A(e,c,n,s,u);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 u=t.filter(c=>R(c,s));if(u.length===0)return{kind:"prependText",text:M(t,i)};const a=A(e,u,r,n,i);return{kind:"prependText",text:await N(`${r}:${b(u[0])}`,()=>q(a))}}function ee(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function de(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,de as extractSerializableContext};
@@ -5,6 +5,7 @@ import type { McpErrorCodes, McpServerType } from './constants';
5
5
  import type { McpToolSchema } from '../../types';
6
6
  import type { Skill } from '../../types/plugins/skills.js';
7
7
  import type { UpstreamSecrets } from './gateway-mcp/secret-store.js';
8
+ import type { SiteBranding } from './handlers/credentials-page/types.js';
8
9
  type ObjectValues<T> = T[keyof T];
9
10
  export type McpServerType = ObjectValues<typeof McpServerType>;
10
11
  export type McpErrorCodeTypes = ObjectValues<typeof McpErrorCodes>;
@@ -40,6 +41,11 @@ export type McpDocsStaticData = PageStaticData & {
40
41
  skillsBaseUrl?: string;
41
42
  };
42
43
  };
44
+ export type CredentialsPageStaticData = PageStaticData & {
45
+ props?: {
46
+ branding?: SiteBranding;
47
+ };
48
+ };
43
49
  export type SkillMcpResource = {
44
50
  slug: string;
45
51
  name: string;
@@ -10,7 +10,10 @@ import type { McpMode } from '../types.js';
10
10
  * data / McpToolContext instead (see docs-mcp-handler / execute-mcp-tool).
11
11
  */
12
12
  export declare function isMcpCodeModeEnabled(): boolean;
13
- /** The gateway capability — a guarded `fetch` in the `execute` sandbox. Requires code mode. */
13
+ /**
14
+ * The gateway capability: a guarded `fetch` in the `execute` sandbox. Requires code mode and a
15
+ * project database.
16
+ */
14
17
  export declare function isMcpGatewayEnabled(): boolean;
15
18
  export declare function isMcpElicitationEnabled(): boolean;
16
19
  export declare function getMcpMode(): McpMode;
@@ -1 +1 @@
1
- import{EntitlementsProvider as e}from"../../../entitlements/entitlements-provider.js";function t(){return e.instance().entitlements?.mcpMode==="code"}function n(){return!!e.instance().canAccessFeature("mcpGateway")&&t()}function c(){return n()}function r(){return t()?"code":"tools"}export{r as getMcpMode,t as isMcpCodeModeEnabled,c as isMcpElicitationEnabled,n as isMcpGatewayEnabled};
1
+ import{EntitlementsProvider as e}from"../../../entitlements/entitlements-provider.js";import{hasProjectDatabase as n}from"../../../providers/database/has-project-database.js";function t(){return e.instance().entitlements?.mcpMode==="code"}function o(){return!!e.instance().canAccessFeature("mcpGateway")&&t()&&n()}function i(){return o()}function a(){return t()?"code":"tools"}export{a as getMcpMode,t as isMcpCodeModeEnabled,i as isMcpElicitationEnabled,o as isMcpGatewayEnabled};
@@ -1 +1 @@
1
- import*as t from"react";import{useState as i}from"react";import{getScorecardColorVariable as s}from"@redocly/theme/core/utils";import{Button as m}from"@redocly/theme/components/Button/Button";import{SubtractIcon as u}from"@redocly/theme/icons/SubtractIcon/SubtractIcon";import{AddIcon as p}from"@redocly/theme/icons/AddIcon/AddIcon";import{Gauge as E}from"./Gauge";import{Code as f,Dot as o,FilePreview as d,InstanceControlsWrap as v,InstanceMessage as x,InstanceReference as C,InstanceWrap as I,ContentWithDotWrap as a,EmojiWrap as b}from"./index.styles";import{SEVERITY_TO_COLOR as g,severityColor as S,severityIcon as R}from"./utils";function D({level:e,colorVariable:r}){return t.createElement(a,null,t.createElement(o,{colorVariable:r}),t.createElement("span",null,e))}function L({summary:e}){const r=Object.keys(e.levels).length;return t.createElement(E,{chunks:Object.entries(e.levels).map(([l,n],c)=>({share:n/e.total*100,colorVariable:s(c,r),title:`${n} ${l}`}))})}function P({severity:e}){return t.createElement(o,{colorVariable:g[e]})}function A({errors:e,warnings:r}){return t.createElement(a,null,t.createElement(o,{colorVariable:`--scorecard-color-${e>0?"error":r>0?"warning":"success"}`}),e>0?"Problem":r>0?"Warning":"Normal")}function N({errors:e,warnings:r}){return t.createElement(o,{colorVariable:`--scorecard-color-${e>0?"error":r>0?"warning":"success"}`})}function V({problem:e}){const[r,l]=i(!1);return t.createElement(v,{onClick:()=>l(n=>!n),className:r?"expanded":""},t.createElement(m,{variant:"text",size:"medium",icon:r?t.createElement(u,null):t.createElement(p,null)}),t.createElement(I,{borderColor:S(e.severity)},t.createElement("div",null,t.createElement(x,null,t.createElement(b,null,R(e.severity))," ",e.message)),r?t.createElement(d,{onClick:n=>n.stopPropagation()},t.createElement(C,null,t.createElement("strong",null,e.location.source.absoluteRef),e.location.pointer),t.createElement(f,null,e.codeframe)):null))}const T=e=>t.createElement(V,{problem:e});export{D as ApiLevel,V as Instance,P as InstanceStatus,N as LevelStatus,A as RuleStatus,L as StatusDistribution,T as renderRowSubComponent};
1
+ import*as e from"react";import{useState as i}from"react";import{getScorecardColorVariable as s}from"@redocly/theme/core/utils";import{Button as m}from"@redocly/theme/components/Button/Button";import{SubtractIcon as u}from"@redocly/theme/icons/SubtractIcon/SubtractIcon";import{AddIcon as p}from"@redocly/theme/icons/AddIcon/AddIcon";import{Gauge as E}from"./Gauge";import{Code as f,Dot as o,FilePreview as d,InstanceControlsWrap as v,InstanceMessage as x,InstanceReference as C,InstanceWrap as I,ContentWithDotWrap as a,EmojiWrap as b}from"./index.styles";import{SEVERITY_TO_COLOR as g,severityColor as S,severityIcon as R}from"./utils";function D({level:t,colorVariable:r}){return e.createElement(a,null,e.createElement(o,{colorVariable:r}),e.createElement("span",null,t))}function L({summary:t}){const r=Object.keys(t.levels).length;return e.createElement(E,{chunks:Object.entries(t.levels).map(([l,n],c)=>({share:n/t.total*100,colorVariable:s(c,r),title:`${n} ${l}`}))})}function P({severity:t}){return e.createElement(o,{colorVariable:g[t]})}function A({errors:t,warnings:r}){return e.createElement(a,null,e.createElement(o,{colorVariable:`--scorecard-color-${t>0?"error":r>0?"warning":"success"}`}),e.createElement("span",null,t>0?"Problem":r>0?"Warning":"Normal"))}function N({errors:t,warnings:r}){return e.createElement(o,{colorVariable:`--scorecard-color-${t>0?"error":r>0?"warning":"success"}`})}function V({problem:t}){const[r,l]=i(!1);return e.createElement(v,{onClick:()=>l(n=>!n),className:r?"expanded":""},e.createElement(m,{variant:"text",size:"medium",icon:r?e.createElement(u,null):e.createElement(p,null)}),e.createElement(I,{borderColor:S(t.severity)},e.createElement("div",null,e.createElement(x,null,e.createElement(b,null,R(t.severity))," ",t.message)),r?e.createElement(d,{onClick:n=>n.stopPropagation()},e.createElement(C,null,e.createElement("strong",null,t.location.source.absoluteRef),t.location.pointer),e.createElement(f,null,t.codeframe)):null))}const T=t=>e.createElement(V,{problem:t});export{D as ApiLevel,V as Instance,P as InstanceStatus,N as LevelStatus,A as RuleStatus,L as StatusDistribution,T as renderRowSubComponent};
@@ -17,6 +17,11 @@ export declare class DatabaseClient {
17
17
  static init(config: Config, options?: DatabaseClientInitOptions): Promise<DatabaseClient>;
18
18
  migrate(prefix: string, suffix?: string): Promise<void>;
19
19
  close(): Promise<void>;
20
+ /**
21
+ * Closes the client during error handling. Never throws, so the caller can rethrow
22
+ * the original error; a close failure is logged.
23
+ */
24
+ closeAfterFailure(): Promise<void>;
20
25
  sync(): Promise<void>;
21
26
  }
22
27
  export {};
@@ -1 +1 @@
1
- import a from"node:path";import{sql as o}from"drizzle-orm";import{drizzle as c}from"drizzle-orm/libsql";import{migrate as l}from"drizzle-orm/libsql/migrator";import{TransactionsManager as m}from"./transactions-manager.js";class e{dbClient;#t;constructor(t,i){this.dbClient=c(i(t)),this.#t=new m(this.dbClient)}get transactionsManager(){return this.#t}get client(){return this.#t.tx??this.dbClient}static async init(t,i){const{createClient:s}=await import("@libsql/client"),r=new e(t,s);return i?.runWithPragmaWalWriteOptimization&&await r.dbClient.run(o.raw("PRAGMA journal_mode = WAL;")),r}async migrate(t,i=""){await l(this.dbClient,{migrationsFolder:a.join(t,"migrations",i)})}async close(){this.dbClient.$client.close()}async sync(){try{await this.dbClient.$client.sync()}catch(t){if(d(t))return;throw t}}}function d(n){if(!(n instanceof Error))return!1;const t=n.message.toLowerCase();return t.includes("syncnotsupported")&&(t.includes('("file")')||t.includes('"file"'))}export{e as DatabaseClient};
1
+ import o from"node:path";import{sql as c}from"drizzle-orm";import{drizzle as l}from"drizzle-orm/libsql";import{migrate as m}from"drizzle-orm/libsql/migrator";import{logger as d}from"../../tools/notifiers/logger.js";import{TransactionsManager as h}from"./transactions-manager.js";class n{dbClient;#t;constructor(t,r){this.dbClient=l(r(t)),this.#t=new h(this.dbClient)}get transactionsManager(){return this.#t}get client(){return this.#t.tx??this.dbClient}static async init(t,r){const{createClient:a}=await import("@libsql/client"),i=new n(t,a);if(r?.runWithPragmaWalWriteOptimization)try{await i.dbClient.run(c.raw("PRAGMA journal_mode = WAL;"))}catch(s){throw await i.closeAfterFailure(),s}return i}async migrate(t,r=""){await m(this.dbClient,{migrationsFolder:o.join(t,"migrations",r)})}async close(){this.dbClient.$client.close()}async closeAfterFailure(){try{await this.close()}catch(t){d.warn("Failed to close the database client: %s",t)}}async sync(){try{await this.dbClient.$client.sync()}catch(t){if(u(t))return;throw t}}}function u(e){if(!(e instanceof Error))return!1;const t=e.message.toLowerCase();return t.includes("syncnotsupported")&&(t.includes('("file")')||t.includes('"file"'))}export{n as DatabaseClient};
@@ -1 +1 @@
1
- import a from"node:path";import{cpSync as c,existsSync as t,mkdirSync as m}from"node:fs";import{logger as i}from"../../tools/notifiers/logger.js";import{fromCurrentDir as d}from"../../utils/index.js";import{PACKAGE_NAME as f}from"../../../config/product-gates.js";const l=["@redocly/realm","@redocly/reef","@redocly/redoc-reef","@redocly/revel-reef"],A=r=>{if(!l.includes(f))return;const o="sqlite-db",e=d(import.meta.url,`./databases/${o}/migrations`),s=a.join(r.serverOutDir,"migrations",o);if(!t(e)){i.warn(`Migrations folder not found at ${e}`);return}t(r.serverOutDir)||m(r.serverOutDir,{recursive:!0});try{c(e,s,{recursive:!0})}catch(n){i.error("Failed to copy migrations folder: %s",n)}};export{A as copyMigrationsFolder};
1
+ import n from"node:path";import{cpSync as m,existsSync as i,mkdirSync as c}from"node:fs";import{logger as e}from"../../tools/notifiers/logger.js";import{fromCurrentDir as u}from"../../utils/index.js";import{hasProjectDatabase as f}from"./has-project-database.js";const D=r=>{if(!f())return;const o="sqlite-db",t=u(import.meta.url,`./databases/${o}/migrations`),a=n.join(r.serverOutDir,"migrations",o);if(!i(t)){e.warn(`Migrations folder not found at ${t}`);return}i(r.serverOutDir)||c(r.serverOutDir,{recursive:!0});try{m(t,a,{recursive:!0})}catch(s){e.error("Failed to copy migrations folder: %s",s)}};export{D as copyMigrationsFolder};
@@ -1 +1 @@
1
- import{logger as c}from"../../tools/notifiers/logger.js";import{SqldNotRunningError as y}from"./sqld-not-running-error.js";import{retryAsyncOperation as f}from"./utils/retry-async-operation.js";import{DatabasePathGenerator as h}from"./database-path-generator.js";import{DatabaseClient as w}from"./client.js";const m="_redocly_db_preconnect_cache_";class i{static get#t(){const t=globalThis;if(!t[m]){const n={connections:new Map,pendingConnections:new Map,failedConnections:new Set};t[m]=n}return t[m]}static get#i(){return i.#t.connections}static get#e(){return i.#t.failedConnections}static get#n(){return i.#t.pendingConnections}static async getLocalConnection(t){return i.#r(t,async()=>i.#a(t))}static async getRemoteConnection(t){return i.#r(t,async()=>i.#d(t))}static async#r(t,n){const e=i.#o(t),r=i.#i.get(e);if(r)if(r.client.dbClient.$client.closed)c.warn("Cached database client is closed. Reinitializing connection..."),i.#i.delete(e);else return r;return i.#l({key:e,initializer:n})}static#o=t=>{const n="syncUrl"in t&&typeof t.syncUrl=="string"?`|${t.syncUrl}`:"",e=t.additionalFolder??"";return`${t.baseDbDir}|${e}|${t.databaseName}${n}`};static async#l({key:t,initializer:n}){let e=i.#n.get(t);e||(i.#e.has(t)&&c.warn("Error connecting to the database, retrying..."),e=i.#s(t,n),i.#n.set(t,e));try{return await e}catch(r){return c.error("Error connecting to the database",r),i.#e.add(t),null}finally{i.#n.delete(t)}}static async#s(t,n){const e=await n();return i.#i.set(t,e),i.#e.delete(t),e}static async#a({removeExisting:t,baseDbDir:n,runWithPragmaWalWriteOptimization:e,databaseName:r,additionalFolder:a,migrationsFolder:d}){c.info("Initializing local database client...");const l=c.startTiming(),s=await i.#c({removeExisting:t,baseDbDir:n,databaseName:r,additionalFolder:a,migrationsFolder:d,runWithPragmaWalWriteOptimization:e});return c.infoTime(l,"Local database client initialized"),s}static async#d({removeExisting:t,baseDbDir:n,syncUrl:e,authToken:r,databaseName:a,additionalFolder:d,migrationsFolder:l}){c.info("Initializing remote database client...");const s=c.startTiming();return await f(async()=>{try{const o=await i.#c({removeExisting:t,baseDbDir:n,databaseName:a,additionalFolder:d,migrationsFolder:l,syncUrl:e,authToken:r});return c.infoTime(s,"Remote database client initialized"),o}catch(o){throw y.isSqldNotRunningError(o)?new y("SQLD server is not running yet, wait for production deployment",{cause:o}):o}},{maxRetries:3,retryStrategy:"exponential",delay:1e3,logger:c,messages:{retry:"Retrying remote database client initialization",maxRetriesReached:"Max retries reached, remote database client initialization failed"}})}static async#c({removeExisting:t,baseDbDir:n,databaseName:e,additionalFolder:r,migrationsFolder:a,runWithPragmaWalWriteOptimization:d,syncUrl:l,authToken:s}){const{databasePath:o}=await h.prepareDatabasePath({baseDir:n,databaseName:e,additionalFolder:r,removeExisting:t}),p=l&&s?{url:`file:${o}`,syncUrl:l,authToken:s}:{url:`file:${o}`},u=await w.init(p,{runWithPragmaWalWriteOptimization:d});return await u.migrate(n,a),{client:u,path:o}}}export{i as DatabaseConnectionsManager};
1
+ import{logger as c}from"../../tools/notifiers/logger.js";import{SqldNotRunningError as y}from"./sqld-not-running-error.js";import{retryAsyncOperation as h}from"./utils/retry-async-operation.js";import{DatabasePathGenerator as w}from"./database-path-generator.js";import{DatabaseClient as g}from"./client.js";import{describeTransientError as z}from"./transient-sqld-error.js";const u="_redocly_db_preconnect_cache_";class i{static get#t(){const t=globalThis;if(!t[u]){const r={connections:new Map,pendingConnections:new Map,failedConnections:new Set};t[u]=r}return t[u]}static get#i(){return i.#t.connections}static get#e(){return i.#t.failedConnections}static get#r(){return i.#t.pendingConnections}static async getLocalConnection(t){return i.#n(t,async()=>i.#a(t))}static async getRemoteConnection(t){return i.#n(t,async()=>i.#d(t))}static async#n(t,r){const e=i.#o(t),n=i.#i.get(e);if(n)if(n.client.dbClient.$client.closed)c.warn("Cached database client is closed. Reinitializing connection..."),i.#i.delete(e);else return n;return i.#l({key:e,initializer:r})}static#o=t=>{const r="syncUrl"in t&&typeof t.syncUrl=="string"?`|${t.syncUrl}`:"",e=t.additionalFolder??"";return`${t.baseDbDir}|${e}|${t.databaseName}${r}`};static async#l({key:t,initializer:r}){let e=i.#r.get(t);e||(i.#e.has(t)&&c.warn("Error connecting to the database, retrying..."),e=i.#s(t,r),i.#r.set(t,e));try{return await e}catch(n){return c.error("Error connecting to the database: %s",z(n)),i.#e.add(t),null}finally{i.#r.delete(t)}}static async#s(t,r){const e=await r();return i.#i.set(t,e),i.#e.delete(t),e}static async#a({removeExisting:t,baseDbDir:r,runWithPragmaWalWriteOptimization:e,databaseName:n,additionalFolder:a,migrationsFolder:d}){c.info("Initializing local database client...");const l=c.startTiming(),s=await i.#c({removeExisting:t,baseDbDir:r,databaseName:n,additionalFolder:a,migrationsFolder:d,runWithPragmaWalWriteOptimization:e});return c.infoTime(l,"Local database client initialized"),s}static async#d({removeExisting:t,baseDbDir:r,syncUrl:e,authToken:n,databaseName:a,additionalFolder:d,migrationsFolder:l}){c.info("Initializing remote database client...");const s=c.startTiming();return await h(async()=>{try{const o=await i.#c({removeExisting:t,baseDbDir:r,databaseName:a,additionalFolder:d,migrationsFolder:l,syncUrl:e,authToken:n});return c.infoTime(s,"Remote database client initialized"),o}catch(o){throw y.isSqldNotRunningError(o)?new y("SQLD server is not running yet, wait for production deployment",{cause:o}):o}},{maxRetries:3,retryStrategy:"exponential",delay:1e3,logger:c,messages:{retry:"Retrying remote database client initialization",maxRetriesReached:"Max retries reached, remote database client initialization failed"}})}static async#c({removeExisting:t,baseDbDir:r,databaseName:e,additionalFolder:n,migrationsFolder:a,runWithPragmaWalWriteOptimization:d,syncUrl:l,authToken:s}){const{databasePath:o}=await w.prepareDatabasePath({baseDir:r,databaseName:e,additionalFolder:n,removeExisting:t}),p=l&&s?{url:`file:${o}`,syncUrl:l,authToken:s}:{url:`file:${o}`},m=await g.init(p,{runWithPragmaWalWriteOptimization:d});try{await m.migrate(r,a)}catch(f){throw await m.closeAfterFailure(),f}return{client:m,path:o}}}export{i as DatabaseConnectionsManager};
@@ -0,0 +1,3 @@
1
+ /** Whether this product ships the project database (KV, migrations). Redoc and Revel do not. */
2
+ export declare function hasProjectDatabase(): boolean;
3
+ //# sourceMappingURL=has-project-database.d.ts.map
@@ -0,0 +1 @@
1
+ import{PACKAGE_NAME as e}from"../../../config/product-gates.js";const r=["@redocly/realm","@redocly/reef","@redocly/redoc-reef","@redocly/revel-reef"];function c(){return r.includes(e)}export{c as hasProjectDatabase};
@@ -78,7 +78,6 @@ type McpAuthorizationCodePayload = {
78
78
  redirect_uri: string;
79
79
  id_token: string;
80
80
  idp_id?: string;
81
- idp_access_token?: string;
82
81
  idp_access_token_enc?: string;
83
82
  idp_refresh_token_enc?: string;
84
83
  code_challenge?: string;
@@ -1,12 +1,12 @@
1
- import"../node-crypto-polyfill.js";import{DOMParser as v}from"@xmldom/xmldom";import{SignedXml as Z}from"xml-crypto";import ee from"xpath";import{deflateSync as te,inflateSync as ne}from"fflate";import{createHash as re}from"crypto";import{ulid as oe}from"ulid";import{AuthProviderType as f,DEFAULT_TEAM_CLAIM_NAME as j}from"@redocly/config";import{ALTERNATIVE_AUD_CLAIM_NAME as se,AUTH_URL as ie,JWT_SECRET_KEY as w,MAX_AUTH_TOKEN_COOKIE_BYTES as ae}from"../constants/common.js";import{ensureArray as R}from"../../utils/array/ensure-array.js";import{envConfig as ce}from"../config/env-config.js";import{decryptClaimValue as ue}from"../utils/crypto/decrypt-claim-value.js";import{getPathPrefix as de,withPathPrefix as le,withoutPathPrefix as me}from"@redocly/theme/core/utils";import{ServerRoutes as U}from"../../constants/api.js";import{AuthCookieNames as g,DEFAULT_AUTHENTICATED_TEAM as pe,REQUIRED_OIDC_SCOPES as V}from"../../constants/common.js";import{appendQueryParams as fe}from"../../utils/url/append-query-params.js";import{parsePreviewUrl as B}from"../../utils/url/parse-preview-url.js";import{logger as O}from"../tools/notifiers/logger.js";import{randomString as he}from"../utils/crypto/random-string.js";import{randomUUID as $}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as d,JwtTokenExpired as ye}from"./jwt/types.js";import*as m from"./jwt/jwt.js";import{InternalTokenTypes as T}from"./jwt/token-types.js";import{parseTeamClaimToArray as Ae}from"../utils/index.js";import{parseTeamNameTemplate as we}from"../utils/rbac-expand.js";import{arrayBufferToBase64 as Se,decodeBase64 as F,encodeBase64URL as _e,urlSafeBase64 as H}from"./jwt/encode.js";import{formatSamlCertificate as Te}from"./utils/format-saml-certificate.js";const xe=new Set(Object.values(T));function x(e){return e?.type===f.OIDC}function Ie(e){return e?.type===f.SAML2}async function It(e,t){if(x(t))return ge(e,t);if(Ie(t))return Ee(e,t)}async function ge(e,t){const n=await G(e,t),r=new Set((t.scopes||[]).concat(V)),o=t.authorizationRequestCustomParams||{};return{type:f.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function Ee(e,t){return{type:f.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}function K(e){return Array.from(new Set((e.scopes||[]).concat(V))).join(" ")}async function W(e,t,n){return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:t.clientId,...t.clientSecret?{client_secret:t.clientSecret}:{},...n}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(r=>r.json())}async function gt(e,t,n,r,o={}){return await W(e,r,{scope:K(r),code:t,redirect_uri:J(n),grant_type:"authorization_code",...o})}async function Et(e,t,n){return await W(e,n,{scope:K(n),grant_type:"refresh_token",refresh_token:t})}function ke(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:s,idpId:i,pkce:c},u,h,p){if(!t||!n||!r||!o)return{loginUrl:void 0};const a=new URL(t),A=p?.redirectUriOverride??`${e}${le(U.OIDC_CALLBACK)}`,S={state:$(),idpId:i,redirectUri:A,redirectTo:u,branch:p?.branchOverride??Le(e),inviteCode:h,source:p?.sourceOverride??"portal",uiLocales:p?.uiLocales},_={};if(c){const y=H(he(50)),E=H(re("sha256").update(y).digest("base64")),k="S256";a.searchParams.append("code_challenge",E),a.searchParams.append("code_challenge_method",k),_.code_verifier={value:y,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:de()||"/"}}}a.searchParams.append("client_id",n),a.searchParams.append("scope",o),a.searchParams.append("response_type",r),a.searchParams.append("redirect_uri",J(A)),a.searchParams.append("state",_e(JSON.stringify(S))),p?.uiLocales&&a.searchParams.append("ui_locales",p.uiLocales);for(const y in s)s[y]!==void 0&&a.searchParams.append(y,s[y]);return{loginUrl:a.toString(),cookies:_}}function kt(e,t,n,r){const o=new URL(e);return n&&(o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n)),o.toString()}function Re(e,t){const n=R(t?.audience??[]);return!n.length||z(e,n)}function z(e,t){const n=[e.aud,e[se]].flat();return t.some(r=>r!==""&&n.includes(r))}function Oe(e,t){return!t||!x(t)?!0:Re(e,t)&&Me(e,t)}function Me(e,t){return!x(t)||!Ke(t)||!t.clientId?!0:R(e.aud||[]).includes(t.clientId)}function Rt(e){if(!e)return!1;try{return m.decode(e).header.alg===d.RS256}catch{return!1}}async function Ot(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return m.sign({type:T.MCP_AUTHORIZATION_CODE,client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpId?{idp_id:e.idpId}:{},...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:n},w,d.HS256)}async function Mt(e){await m.verify(e,w,d.HS256);const{payload:t}=m.decode(e);if(t.type!==T.MCP_AUTHORIZATION_CODE)throw new Error("Invalid authorization code type");if(!t.client_id||!t.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof t.exp=="number"&&Date.now()>=t.exp*1e3)throw new Error("Authorization code expired");return t}async function Lt(e){const t=Math.floor(Date.now()/1e3);return m.sign({type:T.MCP_REFRESH_TOKEN,client_id:e.clientId,...e.idpId?{idp_id:e.idpId}:{},claims:e.claims,...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},session_exp:e.sessionExp,iat:t,exp:e.sessionExp},w,d.HS256)}async function Nt(e){await m.verify(e,w,d.HS256);const{payload:t}=m.decode(e);if(t.type!==T.MCP_REFRESH_TOKEN)throw new Error("Invalid refresh token type");if(!t.client_id||typeof t.claims!="object"||t.claims===null)throw new Error("Refresh token missing required claims");if(typeof t.session_exp!="number")throw new Error("Refresh token missing session expiry");return t}function Ct(e){const t=e||oe(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcpSession",uri:`urn:redocly:realm:mcp:session:${n}`}}function J(e){const t=B(e);if(!t)return e;const n=Ne(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,q(e,t);const r=Y(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,q(e,t)):e}function Le(e){const t=B(e);if(t)return Y(t.hostname)?.previewBranch}function q(e,t){const n=e.replace(/^https?:\/\/[^/?#]+/i,"");return`${t.protocol}//${t.host}${me(n)}`}function Y(e){const t=e.split(".preview.",2);if(t.length<2)return null;const[n,r]=t,o=n.indexOf("--");if(o===-1)return null;const s=n.slice(0,o),i=n.slice(o+2);return!s||!i||i.includes(".")?null:{projectSlug:s,previewBranch:i,after:r}}function Ne(e){const t=e.indexOf("."),n=t===-1?e:e.slice(0,t);if(!n.startsWith("preview-"))return null;const r=n.indexOf("--");if(r===-1)return null;const o=n.slice(r+2);if(!o)return null;const s=t===-1?"":e.slice(t);return{previewBranch:o,after:s}}function Ce(e){return e.type===f.OIDC}function De(e){return e.type===f.SAML2}function Dt(e,t,n,r,o){return Ce(e)?ke(t,e,n,r,{uiLocales:o}):De(e)?be(t,e,n,r,o):{}}function be(e,t,n,r,o){const i=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
1
+ import"../node-crypto-polyfill.js";import{DOMParser as j}from"@xmldom/xmldom";import{SignedXml as Z}from"xml-crypto";import ee from"xpath";import{deflateSync as te,inflateSync as ne}from"fflate";import{createHash as re}from"crypto";import{ulid as oe}from"ulid";import{AuthProviderType as f,DEFAULT_TEAM_CLAIM_NAME as U}from"@redocly/config";import{ALTERNATIVE_AUD_CLAIM_NAME as se,AUTH_URL as ie,JWT_SECRET_KEY as y,MAX_AUTH_TOKEN_COOKIE_BYTES as ae}from"../constants/common.js";import{ensureArray as R}from"../../utils/array/ensure-array.js";import{envConfig as ce}from"../config/env-config.js";import{decryptClaimValue as ue}from"../utils/crypto/decrypt-claim-value.js";import{decryptAesGcm as de}from"../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as le}from"../utils/crypto/encrypt-aes-gcm.js";import{getPathPrefix as me,withPathPrefix as pe,withoutPathPrefix as fe}from"@redocly/theme/core/utils";import{ServerRoutes as V}from"../../constants/api.js";import{AuthCookieNames as I,DEFAULT_AUTHENTICATED_TEAM as he,REQUIRED_OIDC_SCOPES as B}from"../../constants/common.js";import{appendQueryParams as ye}from"../../utils/url/append-query-params.js";import{parsePreviewUrl as $}from"../../utils/url/parse-preview-url.js";import{logger as O}from"../tools/notifiers/logger.js";import{randomString as Ae}from"../utils/crypto/random-string.js";import{randomUUID as M}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as d,JwtTokenExpired as we}from"./jwt/types.js";import*as m from"./jwt/jwt.js";import{InternalTokenTypes as T}from"./jwt/token-types.js";import{parseTeamClaimToArray as _e}from"../utils/index.js";import{parseTeamNameTemplate as Se}from"../utils/rbac-expand.js";import{arrayBufferToBase64 as Te,decodeBase64 as F,encodeBase64URL as xe,urlSafeBase64 as H}from"./jwt/encode.js";import{formatSamlCertificate as ge}from"./utils/format-saml-certificate.js";const Ie=new Set(Object.values(T));function x(e){return e?.type===f.OIDC}function Ee(e){return e?.type===f.SAML2}async function Rt(e,t){if(x(t))return ke(e,t);if(Ee(t))return Re(e,t)}async function ke(e,t){const n=await G(e,t),r=new Set((t.scopes||[]).concat(B)),o=t.authorizationRequestCustomParams||{};return{type:f.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function Re(e,t){return{type:f.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}function K(e){return Array.from(new Set((e.scopes||[]).concat(B))).join(" ")}async function z(e,t,n){return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:t.clientId,...t.clientSecret?{client_secret:t.clientSecret}:{},...n}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(r=>r.json())}async function Ot(e,t,n,r,o={}){return await z(e,r,{scope:K(r),code:t,redirect_uri:J(n),grant_type:"authorization_code",...o})}async function Mt(e,t,n){return await z(e,n,{scope:K(n),grant_type:"refresh_token",refresh_token:t})}function Oe(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:s,idpId:i,pkce:c},u,h,p){if(!t||!n||!r||!o)return{loginUrl:void 0};const a=new URL(t),w=p?.redirectUriOverride??`${e}${pe(V.OIDC_CALLBACK)}`,_={state:M(),idpId:i,redirectUri:w,redirectTo:u,branch:p?.branchOverride??Ce(e),inviteCode:h,source:p?.sourceOverride??"portal",uiLocales:p?.uiLocales},S={};if(c){const A=H(Ae(50)),E=H(re("sha256").update(A).digest("base64")),k="S256";a.searchParams.append("code_challenge",E),a.searchParams.append("code_challenge_method",k),S.code_verifier={value:A,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:me()||"/"}}}a.searchParams.append("client_id",n),a.searchParams.append("scope",o),a.searchParams.append("response_type",r),a.searchParams.append("redirect_uri",J(w)),a.searchParams.append("state",xe(JSON.stringify(_))),p?.uiLocales&&a.searchParams.append("ui_locales",p.uiLocales);for(const A in s)s[A]!==void 0&&a.searchParams.append(A,s[A]);return{loginUrl:a.toString(),cookies:S}}function Lt(e,t,n,r){const o=new URL(e);return n&&(o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n)),o.toString()}function Me(e,t){const n=R(t?.audience??[]);return!n.length||W(e,n)}function W(e,t){const n=[e.aud,e[se]].flat();return t.some(r=>r!==""&&n.includes(r))}function Le(e,t){return!t||!x(t)?!0:Me(e,t)&&Ne(e,t)}function Ne(e,t){return!x(t)||!We(t)||!t.clientId?!0:R(e.aud||[]).includes(t.clientId)}function Nt(e){if(!e)return!1;try{return m.decode(e).header.alg===d.RS256}catch{return!1}}async function Ct(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return m.sign({type:T.MCP_AUTHORIZATION_CODE,client_id:e.clientId,redirect_uri:e.redirectUri,id_token_enc:le(e.idToken,y),...e.idpId?{idp_id:e.idpId}:{},...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:n},y,d.HS256)}async function Dt(e){await m.verify(e,y,d.HS256);const{payload:t}=m.decode(e);if(t.type!==T.MCP_AUTHORIZATION_CODE)throw new Error("Invalid authorization code type");if(!t.client_id||!t.redirect_uri)throw new Error("Authorization code missing required claims");if(typeof t.exp=="number"&&Date.now()>=t.exp*1e3)throw new Error("Authorization code expired");if(typeof t.id_token_enc!="string")throw new Error("Authorization code missing session");return{...t,id_token:de(t.id_token_enc,y)}}async function bt(e){const t=Math.floor(Date.now()/1e3);return m.sign({type:T.MCP_REFRESH_TOKEN,client_id:e.clientId,...e.idpId?{idp_id:e.idpId}:{},claims:e.claims,...e.idpAccessTokenEnc?{idp_access_token_enc:e.idpAccessTokenEnc}:{},...e.idpRefreshTokenEnc?{idp_refresh_token_enc:e.idpRefreshTokenEnc}:{},session_exp:e.sessionExp,iat:t,exp:e.sessionExp,jti:M()},y,d.HS256)}async function Pt(e){await m.verify(e,y,d.HS256);const{payload:t}=m.decode(e);if(t.type!==T.MCP_REFRESH_TOKEN)throw new Error("Invalid refresh token type");if(!t.client_id||typeof t.claims!="object"||t.claims===null)throw new Error("Refresh token missing required claims");if(typeof t.session_exp!="number")throw new Error("Refresh token missing session expiry");return t}function vt(e){const t=e||oe(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcpSession",uri:`urn:redocly:realm:mcp:session:${n}`}}function J(e){const t=$(e);if(!t)return e;const n=De(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,q(e,t);const r=Y(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,q(e,t)):e}function Ce(e){const t=$(e);if(t)return Y(t.hostname)?.previewBranch}function q(e,t){const n=e.replace(/^https?:\/\/[^/?#]+/i,"");return`${t.protocol}//${t.host}${fe(n)}`}function Y(e){const t=e.split(".preview.",2);if(t.length<2)return null;const[n,r]=t,o=n.indexOf("--");if(o===-1)return null;const s=n.slice(0,o),i=n.slice(o+2);return!s||!i||i.includes(".")?null:{projectSlug:s,previewBranch:i,after:r}}function De(e){const t=e.indexOf("."),n=t===-1?e:e.slice(0,t);if(!n.startsWith("preview-"))return null;const r=n.indexOf("--");if(r===-1)return null;const o=n.slice(r+2);if(!o)return null;const s=t===-1?"":e.slice(t);return{previewBranch:o,after:s}}function be(e){return e.type===f.OIDC}function Pe(e){return e.type===f.SAML2}function jt(e,t,n,r,o){return be(e)?Oe(t,e,n,r,{uiLocales:o}):Pe(e)?ve(t,e,n,r,o):{}}function ve(e,t,n,r,o){const i=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
2
2
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
3
3
  Version="2.0"
4
- ID="_${$()}"
4
+ ID="_${M()}"
5
5
  IssueInstant="${new Date().toISOString()}"
6
- AssertionConsumerServiceURL="${e}${U.SAML_CALLBACK}"
6
+ AssertionConsumerServiceURL="${e}${V.SAML_CALLBACK}"
7
7
  AttributeConsumingServiceIndex="0">
8
8
  <saml:Issuer>${t.entityId}</saml:Issuer>
9
9
  <samlp:NameIDPolicy
10
10
  AllowCreate="true"
11
11
  Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
12
- </samlp:AuthnRequest>`,c=Pe(i);return{loginUrl:fe(t.ssoUrl,{SAMLRequest:c,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function Pe(e){return Se(te(new TextEncoder().encode(e)).buffer)}function bt(e){const t=F(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=ne(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function Pt(e){try{return JSON.parse(F(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function vt(e){const t=new v().parseFromString(e,"application/xml"),r=l(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,s=l(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",i=l(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],c=i&&i.nodeValue||void 0,u=l(t,"//*[local-name(.)='Audience']/text()")[0],h=u&&u.nodeValue||void 0,a=l(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",A=l(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],S=A&&A.nodeValue||"",_=l(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],y=_&&_.nodeValue||"",E=l(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],k=ve(E),N={},C=l(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(C.length)for(const D of C){const b=l(D,"./@Name")[0];if(b.nodeValue){const P=l(D,"./*[local-name(.)='AttributeValue']/text()")[0];P?.nodeValue&&(N[b.nodeValue]=P.nodeValue)}}return{uid:S,success:r,expiresAt:k,issuerId:c,entityId:h,attrs:N,cert:a,nameFormat:y,destination:s}}function ve(e){const t=typeof e?.nodeValue=="string"&&M(Date.parse(e.nodeValue)),n=M(Date.now()),r=M(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function M(e){return Math.floor(e/1e3)}const L={},I={jwks:{}};async function G(e,t){if(!L[e]){const n=t.configurationUrl?await Q(t.configurationUrl):t.configuration;L[e]=je()?Ve(n):n}return L[e]}function je(){const e=ce.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}const Ue=["authorization_endpoint","end_session_endpoint"];function Ve(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const n of Object.keys(t)){if(Ue.includes(String(n)))continue;const r=t[n];typeof r=="string"&&r.includes("://localhost")&&(t[n]=r.replace("://localhost","://host.docker.internal"))}return t}const Be=["kty","n","e","crv","x","y"];function $e(e,t){return Be.every(n=>e[n]===t[n])}async function Fe(e){const t={};for(const n of Object.keys(e)){const r=e[n];if(!x(r))continue;const o=await G(n,r);if(o.jwks_uri){const s=await Q(o.jwks_uri);for(const i of s.keys){const c=t[i.kid],u=c&&$e(c,i)?[...c.idpIds,n]:[n];t[i.kid]={...i,idpIds:u},I.jwks[i.kid]=t[i.kid]}}}}function He(e,t,n){const r=I.jwks[e]?.idpIds??[];return r.find(o=>{const s=n[o];if(!x(s))return!1;const i=R(s.audience??[]);return z(t,i.length?i:[s.clientId])})??r.at(-1)}async function Q(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function jt(e){return fetch(`${ie}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function Ke(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com"].some(r=>We(t.hostname,r))}function We(e,t){return e===t||e.endsWith(`.${t}`)}async function Ut(e,t){const n=new v().parseFromString(e,"application/xml"),r=l(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=Te(t),s=new Z({publicCert:o});s.loadSignature(r);try{return s.checkSignature(e)}catch{return!1}}function ze(e){const t=new Set,n=r=>{if(!(!r||typeof r!="object"||Array.isArray(r)))for(const[o,s]of Object.entries(r))typeof s=="string"?t.add(o):n(s)};return n(e),t}function Je(e,t,n){if(!t)return e;const r=ze(t),o=s=>{const i=n?.[s]||s;return r.has(i)||!!we(t,i)};return[...e.filter(o),...e.filter(s=>!o(s))]}async function Vt(e,t,n,r){const o=n?e[n]:void 0,s=Array.isArray(o)?Je(o.map(String),r?.rbacConfig,r?.teamsMapping):[];let i=s.length;const c=s.length+1;for(let u=0;u<c;u++){const h={...e,idpId:t};n&&s.length&&(h[n]=s.slice(0,i));const p=await m.sign(h,w,d.HS256),a=Buffer.byteLength(p)-ae;if(a<=0)return{token:p,teamsTotal:s.length,teamsKept:i,droppedTeams:s.slice(i)};if(i===0)return null;const A=Buffer.byteLength(JSON.stringify(s.slice(0,i))),S=Math.max(1,A/i*4/3);i=Math.max(0,i-Math.max(1,Math.ceil(a/S)))}return null}function Bt(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/^.+@.+/m)&&(o=e);const s=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],i=s?.match(/^.+@.+/m);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(i?s:void 0),o=o?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||s,email:o,email_verified:!0,teams:r?Ae(n[r]):[]}}function X(e,t={}){return e.map(n=>t[n]||n)}async function $t(e,t){if(!t)return{};const n=t[g.AUTHORIZATION];if(!n)return{};try{const r=m.decode(n);if(typeof r.payload.type=="string"&&xe.has(r.payload.type))return{};if(r.header.alg===d.RS256){I.jwks[r.header.kid]===void 0&&await Fe(e);const h=I.jwks[r.header.kid];if(!h)return I.jwks[r.header.kid]=null,{};await m.verify(n,h,d.RS256)}else await m.verify(n,w,d.HS256);const s=r.header.alg===d.RS256?He(r.header.kid,r.payload,e):r.payload.idpId,i=e[s]||{};if(r.header.alg===d.RS256&&typeof r.payload.exp!="number")return O.warn("Rejected id_token: it has no exp claim"),{};if(r.header.alg===d.RS256&&!Oe(r.payload,i))return O.warn("Rejected id_token: it was issued for another project"),{};const c=Ge(i),u=Ye(i);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:s,teams:Array.from(new Set([...X(r.payload.teams||[],u),..."defaultTeams"in i&&i.defaultTeams||[],...X("teamsClaimName"in i&&r.payload[c||""]||[],u),pe])),name:qe(r.payload),isAuthenticated:!0,idpAccessToken:ue(r.payload.idp_access_token,w)||t[g.IDP_ACCESS_TOKEN],federatedAccessToken:t[g.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[g.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof ye||O.error("Malformed JWT token: %s",r.message)}return{}}function qe(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Ye(e){switch(e.type){case f.SAML2:return e.teamsAttributeMap;case f.OIDC:return e.teamsClaimMap;default:return}}function Ge(e){switch(e.type){case f.SAML2:return e.teamsAttributeName??j;case f.OIDC:return e.teamsClaimName;default:return j}}function l(e,t){return ee.select(t,e)||[]}export{Dt as buildLoginUrl,ke as buildOidcLoginUrl,kt as buildOidcLogoutUrl,be as buildSAML2LoginUrl,Ot as createMcpAuthorizationCode,Lt as createMcpRefreshToken,Ct as createMcpSessionResource,bt as decodeSamlResponse,Fe as discoverJwks,Pe as encodeSAML2,Bt as extractUserClaims,It as getAuthProviderLoginParams,ge as getOidcLoginParams,G as getOidcMetadata,jt as getRedoclyTokenPayload,Ee as getSaml2LoginParams,$t as getUserParamsFromCookies,qe as getUsernameFromPayload,Re as idTokenAudienceIsAllowed,Oe as idTokenIsForThisProject,Rt as isIdpIssuedToken,x as isOidcProviderConfig,Ke as isRedoclySso,Ie as isSaml2ProviderConfig,gt as oidcExchangeCodeForToken,I as oidcJwksCache,L as oidcMetadataCache,Et as oidcRefreshTokenGrant,Je as orderTeamsRbacFirst,Pt as parseOidcState,Le as parsePreviewBranch,vt as parseSamlResponse,J as rewritePreviewAuthRedirectUri,Vt as signTokenWithinCookieSizeLimit,Mt as verifyMcpAuthorizationCode,Nt as verifyMcpRefreshToken,Ut as verifySAMLResponse};
12
+ </samlp:AuthnRequest>`,c=je(i);return{loginUrl:ye(t.ssoUrl,{SAMLRequest:c,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function je(e){return Te(te(new TextEncoder().encode(e)).buffer)}function Ut(e){const t=F(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=ne(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function Vt(e){try{return JSON.parse(F(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function Bt(e){const t=new j().parseFromString(e,"application/xml"),r=l(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,s=l(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",i=l(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],c=i&&i.nodeValue||void 0,u=l(t,"//*[local-name(.)='Audience']/text()")[0],h=u&&u.nodeValue||void 0,a=l(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",w=l(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],_=w&&w.nodeValue||"",S=l(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],A=S&&S.nodeValue||"",E=l(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],k=Ue(E),C={},D=l(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(D.length)for(const b of D){const P=l(b,"./@Name")[0];if(P.nodeValue){const v=l(b,"./*[local-name(.)='AttributeValue']/text()")[0];v?.nodeValue&&(C[P.nodeValue]=v.nodeValue)}}return{uid:_,success:r,expiresAt:k,issuerId:c,entityId:h,attrs:C,cert:a,nameFormat:A,destination:s}}function Ue(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),n=L(Date.now()),r=L(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function L(e){return Math.floor(e/1e3)}const N={},g={jwks:{}};async function G(e,t){if(!N[e]){const n=t.configurationUrl?await Q(t.configurationUrl):t.configuration;N[e]=Ve()?$e(n):n}return N[e]}function Ve(){const e=ce.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}const Be=["authorization_endpoint","end_session_endpoint"];function $e(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const n of Object.keys(t)){if(Be.includes(String(n)))continue;const r=t[n];typeof r=="string"&&r.includes("://localhost")&&(t[n]=r.replace("://localhost","://host.docker.internal"))}return t}const Fe=["kty","n","e","crv","x","y"];function He(e,t){return Fe.every(n=>e[n]===t[n])}async function Ke(e){const t={};for(const n of Object.keys(e)){const r=e[n];if(!x(r))continue;const o=await G(n,r);if(o.jwks_uri){const s=await Q(o.jwks_uri);for(const i of s.keys){const c=t[i.kid],u=c&&He(c,i)?[...c.idpIds,n]:[n];t[i.kid]={...i,idpIds:u},g.jwks[i.kid]=t[i.kid]}}}}function ze(e,t,n){const r=g.jwks[e]?.idpIds??[];return r.find(o=>{const s=n[o];if(!x(s))return!1;const i=R(s.audience??[]);return W(t,i.length?i:[s.clientId])})??r.at(-1)}async function Q(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function $t(e){return fetch(`${ie}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function We(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com"].some(r=>Je(t.hostname,r))}function Je(e,t){return e===t||e.endsWith(`.${t}`)}async function Ft(e,t){const n=new j().parseFromString(e,"application/xml"),r=l(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=ge(t),s=new Z({publicCert:o});s.loadSignature(r);try{return s.checkSignature(e)}catch{return!1}}function qe(e){const t=new Set,n=r=>{if(!(!r||typeof r!="object"||Array.isArray(r)))for(const[o,s]of Object.entries(r))typeof s=="string"?t.add(o):n(s)};return n(e),t}function Ye(e,t,n){if(!t)return e;const r=qe(t),o=s=>{const i=n?.[s]||s;return r.has(i)||!!Se(t,i)};return[...e.filter(o),...e.filter(s=>!o(s))]}async function Ht(e,t,n,r){const o=n?e[n]:void 0,s=Array.isArray(o)?Ye(o.map(String),r?.rbacConfig,r?.teamsMapping):[];let i=s.length;const c=s.length+1;for(let u=0;u<c;u++){const h={...e,idpId:t};n&&s.length&&(h[n]=s.slice(0,i));const p=await m.sign(h,y,d.HS256),a=Buffer.byteLength(p)-ae;if(a<=0)return{token:p,teamsTotal:s.length,teamsKept:i,droppedTeams:s.slice(i)};if(i===0)return null;const w=Buffer.byteLength(JSON.stringify(s.slice(0,i))),_=Math.max(1,w/i*4/3);i=Math.max(0,i-Math.max(1,Math.ceil(a/_)))}return null}function Kt(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/^.+@.+/m)&&(o=e);const s=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],i=s?.match(/^.+@.+/m);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(i?s:void 0),o=o?.toLowerCase(),{sub:e,given_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],family_name:n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],name:n["http://schemas.microsoft.com/identity/claims/displayname"]||s,email:o,email_verified:!0,teams:r?_e(n[r]):[]}}function X(e,t={}){return e.map(n=>t[n]||n)}async function zt(e,t){if(!t)return{};const n=t[I.AUTHORIZATION];if(!n)return{};try{const r=m.decode(n);if(typeof r.payload.type=="string"&&Ie.has(r.payload.type))return{};if(r.header.alg===d.RS256){g.jwks[r.header.kid]===void 0&&await Ke(e);const h=g.jwks[r.header.kid];if(!h)return g.jwks[r.header.kid]=null,{};await m.verify(n,h,d.RS256)}else await m.verify(n,y,d.HS256);const s=r.header.alg===d.RS256?ze(r.header.kid,r.payload,e):r.payload.idpId,i=e[s]||{};if(r.header.alg===d.RS256&&typeof r.payload.exp!="number")return O.warn("Rejected id_token: it has no exp claim"),{};if(r.header.alg===d.RS256&&!Le(r.payload,i))return O.warn("Rejected id_token: it was issued for another project"),{};const c=Xe(i),u=Qe(i);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:s,teams:Array.from(new Set([...X(r.payload.teams||[],u),..."defaultTeams"in i&&i.defaultTeams||[],...X("teamsClaimName"in i&&r.payload[c||""]||[],u),he])),name:Ge(r.payload),isAuthenticated:!0,idpAccessToken:ue(r.payload.idp_access_token,y)||t[I.IDP_ACCESS_TOKEN],federatedAccessToken:t[I.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[I.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof we||O.error("Malformed JWT token: %s",r.message)}return{}}function Ge(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Qe(e){switch(e.type){case f.SAML2:return e.teamsAttributeMap;case f.OIDC:return e.teamsClaimMap;default:return}}function Xe(e){switch(e.type){case f.SAML2:return e.teamsAttributeName??U;case f.OIDC:return e.teamsClaimName;default:return U}}function l(e,t){return ee.select(t,e)||[]}export{jt as buildLoginUrl,Oe as buildOidcLoginUrl,Lt as buildOidcLogoutUrl,ve as buildSAML2LoginUrl,Ct as createMcpAuthorizationCode,bt as createMcpRefreshToken,vt as createMcpSessionResource,Ut as decodeSamlResponse,Ke as discoverJwks,je as encodeSAML2,Kt as extractUserClaims,Rt as getAuthProviderLoginParams,ke as getOidcLoginParams,G as getOidcMetadata,$t as getRedoclyTokenPayload,Re as getSaml2LoginParams,zt as getUserParamsFromCookies,Ge as getUsernameFromPayload,Me as idTokenAudienceIsAllowed,Le as idTokenIsForThisProject,Nt as isIdpIssuedToken,x as isOidcProviderConfig,We as isRedoclySso,Ee as isSaml2ProviderConfig,Ot as oidcExchangeCodeForToken,g as oidcJwksCache,N as oidcMetadataCache,Mt as oidcRefreshTokenGrant,Ye as orderTeamsRbacFirst,Vt as parseOidcState,Ce as parsePreviewBranch,Bt as parseSamlResponse,J as rewritePreviewAuthRedirectUri,Ht as signTokenWithinCookieSizeLimit,Dt as verifyMcpAuthorizationCode,Pt as verifyMcpRefreshToken,Ft as verifySAMLResponse};
@@ -2,6 +2,7 @@ export declare const InternalTokenTypes: {
2
2
  readonly MCP_CONTEXT: 'mcp_context';
3
3
  readonly MCP_AUTHORIZATION_CODE: 'mcp_auth_code';
4
4
  readonly MCP_REFRESH_TOKEN: 'mcp_refresh_token';
5
+ readonly MCP_CONSENT: 'mcp_consent';
5
6
  readonly AI_ASSISTANT_ANONYMOUS: 'ai_assistant_anonymous';
6
7
  };
7
8
  export declare const McpAccessTokenMarker: {
@@ -1 +1 @@
1
- const e={MCP_CONTEXT:"mcp_context",MCP_AUTHORIZATION_CODE:"mcp_auth_code",MCP_REFRESH_TOKEN:"mcp_refresh_token",AI_ASSISTANT_ANONYMOUS:"ai_assistant_anonymous"},_={CLAIM:"token_use",VALUE:"mcp_access_token"};export{e as InternalTokenTypes,_ as McpAccessTokenMarker};
1
+ const _={MCP_CONTEXT:"mcp_context",MCP_AUTHORIZATION_CODE:"mcp_auth_code",MCP_REFRESH_TOKEN:"mcp_refresh_token",MCP_CONSENT:"mcp_consent",AI_ASSISTANT_ANONYMOUS:"ai_assistant_anonymous"},e={CLAIM:"token_use",VALUE:"mcp_access_token"};export{_ as InternalTokenTypes,e as McpAccessTokenMarker};