@redocly/revel 0.138.0 → 0.138.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @redocly/revel
2
2
 
3
+ ## 0.138.2
4
+
5
+ ## 0.138.1
6
+
7
+ ### Patch Changes
8
+
9
+ - 05163ee7071: Fixed an issue where page content jumped to the side while long pages were loading.
10
+ - 20996e6bfa6: Fixed an issue where the sign-in to the authenticated Docs MCP server failed with `Invalid MCP callback` or `invalid_grant`.
11
+ - 6bb2e1e62d0: Fixed security vulnerability `CVE-2026-93687` by removing the `braces` dependency.
12
+ - Updated dependencies [05163ee7071]
13
+ - @redocly/theme@0.70.1
14
+ - @redocly/api-docs@0.3.1
15
+
3
16
  ## 0.138.0
4
17
 
5
18
  ### Minor Changes
@@ -1 +1 @@
1
- import{REDOCLY_ROUTE_RBAC as E,REDOCLY_TEAMS_RBAC as v}from"@redocly/config";import{mcpTagSchemas as Z,schemaDefinitionTag as ee,markdocParser as U}from"@redocly/api-docs";import{withPathPrefix as q}from"@redocly/theme/core/utils";import{API_DOCS_TEMPLATE_ID as V,GRAPHQL_SPEC_SLUG as te,PUBLIC_RBAC_SCOPE_ITEM as x}from"../../../constants/common.js";import{DEPRECATED_PUBLIC_API_DEFINITIONS_FOLDER as oe,PUBLIC_API_DEFINITIONS_FOLDER as re}from"../../constants/common.js";import{API_DOCS_SHARED_DATA_PREFIX as w}from"../../constants/plugins/api-docs.js";import{fromCurrentDir as ae}from"../../utils/paths.js";import{normalizeRouteSlug as se}from"../../../utils/path/normalize-route-slug.js";import{createSlugify as ie}from"../create-slugify.js";import{apiDocLoader as ne,apiDocsLoader as ce}from"./loader.js";import{getTemplatePath as j}from"./get-template-path.js";import{storeDefinitionBundles as pe}from"./store-definition-bundles.js";import{telemetryTraceStep as ue}from"../../../cli/telemetry/helpers/trace-step.js";import{searchResolver as de}from"./search/search-resolver.js";import{getAiDocumentsStore as le}from"./search/get-ai-search-documents.js";import{getApiConfigByPath as me}from"../get-api-config.js";import{getRouteSlugsForPath as fe}from"../utils.js";import{collectItemRoutes as he}from"./utils/collect-item-routes.js";import{isChunkCurrent as ge}from"./utils/decorators.js";import{applyNavItemToRoot as De}from"./utils/apply-nav-item-to-root.js";import{buildRoutesMapping as Se}from"./utils/build-routes-mapping.js";import{buildItemPositions as Pe}from"./utils/build-item-positions.js";import{resolveApiDocTitle as ye}from"./utils/resolve-api-doc-title.js";import{resolveConfigRbac as Ie}from"./utils/resolve-config-rbac.js";import{relativePathToBaseSlug as Ae}from"./utils/relative-path-to-base-slug.js";import{normalizeApiVersion as ve}from"./utils/normalize-api-version.js";import{importApiDocsBuilder as W}from"./import-api-docs-builder.js";import{envConfig as $}from"../../config/env-config.js";import{logger as Re}from"../../tools/notifiers/logger.js";const T="openapi-spec-download",be={asyncapi:"AsyncAPI Docs",graphql:"GraphQL Docs",openapi:"OpenAPI Docs"};async function nt(){let t=[],r=new Map;const s=new WeakMap;let n=new Set,m={};function k(o,h,A){const y=s.get(o)??new Map;s.set(o,y);const g=h??"";return y.get(g)===A?!1:(y.set(g,A),!0)}return{id:"api-docs",requiredEntitlements:["openapi","asyncapi","graphql"],loaders:{"api-doc":ne,"api-docs":ce},processContent:async(o,h)=>{await ue("com.redocly.build.plugin.apiDocs",async()=>{o.createRequestHandler(T,ae(import.meta.url,"./spec-download.api.js")),o.addApiRoute({slug:re+"/*",requestHandlerId:T,httpMethod:"all",[v]:x,getStaticData:async()=>({props:{}})}),o.addApiRoute({slug:oe+"/*",requestHandlerId:T,httpMethod:"all",[v]:x,getStaticData:async()=>({props:{}})}),o.addApiRoute({slug:te+"/*",requestHandlerId:T,httpMethod:"all",[v]:x,getStaticData:async()=>({props:{}})});const A=o.createTemplate(V,j("../../../client/templates/api-docs/template.js")),y=o.registerServerPropsGetter(V,j("./get-server-props.js")),g=await h.getConfig();r=new Map,m={};const I=await o.loadApiDocuments(h);pe(I,o.outdir);const C=!!g.plugins?.some(e=>typeof e=="string"&&e.startsWith("@redocly/portal-plugin-async-api/"));C&&I.some(e=>e.type==="asyncapi")&&Re.warn("The plugin '@redocly/portal-plugin-async-api' is deprecated. Please remove it from your config to use built-in AsyncAPI docs.");const c=C?I.filter(e=>e.type!=="asyncapi"):I;t=c.map(({markdocChunks:e,relativePath:P,isVirtual:p,customOutputRelativeFile:a,realRelativePath:f})=>({chunks:e??[],relativePath:P,realRelativePath:f,isVirtual:a!=null||p}));const{buildItems:i}=await W(o.serverOutDir),D=(await h.cache.load("content-slugs","content-slugs")).data,S=ie(D),O=e=>se(S(e));for(const e of c){const P=e.customOutputRelativeFile||e.relativePath,p=O(P);Ee(e,p);const{items:a,store:f}=await i({type:e.type,document:e.document,options:e.options,basePath:p,markdownParser:U}),R=`${w}${e.relativePath}`,M=$.isDevelopMode?e.relativePath:void 0,u=Te(e.document),b=u?.["x-metadata"],_=e.isVirtual?void 0:b?.apiId,N={document:e,basePath:p,sourcePath:M,apiId:_};await o.createSharedData(R,Q(N,a,f));const K=Ie(e.config),F=!!e.options.excludeFromSearch,Y=e.type==="openapi",l=he(a,K).map(d=>{const B=d.metadata?.seo,H={...we(d.link,p),fsPath:P,templateId:A,sharedData:[{key:"apiDocsStore",id:R}],serverPropsGetterIds:[y],excludeFromSearch:F,getStaticData:Ce(d.label,B),[v]:d[v]};return Y?{...H,...d.httpVerb?{httpVerb:d.httpVerb,metadata:{subType:"openapi-operation"}}:{}}:H});if(!l.length)continue;const G=ye(u,e.type,a),X=ve(u?.version),L={type:e.type,title:G,version:X,description:typeof u?.description=="string"?u.description:void 0,summary:typeof u?.summary=="string"?u.summary:void 0,...u?.["x-metadata"]&&typeof u["x-metadata"]=="object"?u["x-metadata"]:{}};l[0]={...l[0],metadata:L,hasClientRoutes:!0,getSidebar:(d,B)=>De(z(a),p,B),getNavText:()=>G??be[e.type]??"API Docs",getSearchDocuments:de(a,L,o.getSearchFacets,o.setSearchFacets,F,f,e.document),getAiDocumentsStore:le({items:a,metadata:L,store:f,document:e.document,specType:e.type,excludeFromSearch:F,basePath:p,getSearchFacets:o.getSearchFacets,markdocTags:{...Z,schemaDefinition:ee}})};const J=l[0];l[0]=l[l.length-1],l[l.length-1]=J;for(const d of l)o.addRoute(d);if(_){const d=me(e.config?.apis,e.realRelativePath);m[_]={[v]:d.rbac,[E]:{fsPath:P}}}r.set(R,N)}})},afterRoutesCreated:async(o,h)=>{const{buildItems:A,simplifyAstStructure:y}=await W(o.serverOutDir),g=new Set,I=new Set;for(const{chunks:c,relativePath:i,isVirtual:D}of t){const S=`${w}${i}`,O=fe(o.getAllRoutes(),i);for(const{node:e,markdown:P,pointer:p,key:a,relativePath:f}of c){const{ast:R,compoundHash:M}=await o.parseMarkdoc({input:{content:P,relativePath:f,isVirtual:D},resource:p,context:h,deps:{sharedDataIds:[`${w}${f}`],routeSlugs:O}});if(g.add(p),!(a==="graphql-docs"?e?.description?.value!==void 0:!!a&&ge(e,a,P))||!k(e,a,M))continue;I.add(S);const b=y(R);if(a==="graphql-docs"){e.description&&(e.description.value=b);const _=`${w}${f}`;_e(r.get(_),b)}else a&&(e[a]=b)}}const C=n.difference(g);for(const c of C)h.cache.delete(c);n=g,Object.entries(m).forEach(([c,i])=>{const D=i[E]?.fsPath;if(!D)return;const S=o.getRouteByFsPath(D)?.baseSlug;m[c]={...i,[E]:{...i[E],slug:S},slug:S}}),o.setGlobalData({apiProducts:m});for(const[c,i]of r){if(!I.has(c))continue;const{items:D,store:S}=await A({type:i.document.type,document:i.document.document,options:i.document.options,basePath:i.basePath,markdownParser:U});await o.createSharedData(c,Q(i,D,S))}}}}function Q({document:t,basePath:r,sourcePath:s,apiId:n},m,k){return{items:m,store:k,options:t.options,baseSlug:r,routesMapping:$.isDevelopMode?Se(m,r):{},graphqlPositions:$.isDevelopMode&&t.type==="graphql"?Pe(m,r):void 0,sourcePath:s,apiId:n}}function _e(t,r){t?.document.type!=="graphql"||!t.document.options||(t.document.options.info={...t.document.options.info||{},description:r})}function Ce(t,r){return async function(s){return{props:{settings:{baseUrlPath:q(s.baseSlug)},disableAutoScroll:!0,seo:{title:t,...r}}}}}function Ee(t,r){const s=t.options?.mockServer,n=q(`/_mock${Ae(t.relativePath)}`);s?.url===n&&(t.options={...t.options,mockServer:{...s,url:q(`/_mock${r}`)}})}function we(t,r){if(t===r)return{};const s=r==="/"?"/":`${r}/`;if(t.startsWith(s)){const n=t.slice(s.length);return n?{slugSuffix:n}:{}}return{slug:t}}function Te(t){if(t&&typeof t=="object"&&"info"in t)return t.info}function z(t){return t.map(({content:r,items:s,...n})=>({...n,...s&&{items:z(s)}}))}export{nt as apiDocsPlugin,we as toRouteSlugFields};
1
+ import{REDOCLY_ROUTE_RBAC as C,REDOCLY_TEAMS_RBAC as A}from"@redocly/config";import{mcpTagSchemas as tt,schemaDefinitionTag as et,markdocParser as U}from"@redocly/api-docs";import{withPathPrefix as q}from"@redocly/theme/core/utils";import{API_DOCS_TEMPLATE_ID as V,GRAPHQL_SPEC_SLUG as ot,PUBLIC_RBAC_SCOPE_ITEM as x}from"../../../constants/common.js";import{DEPRECATED_PUBLIC_API_DEFINITIONS_FOLDER as rt,PUBLIC_API_DEFINITIONS_FOLDER as at}from"../../constants/common.js";import{API_DOCS_SHARED_DATA_PREFIX as w}from"../../constants/plugins/api-docs.js";import{fromCurrentDir as st}from"../../utils/paths.js";import{normalizeRouteSlug as it}from"../../../utils/path/normalize-route-slug.js";import{removeTrailingSlash as nt}from"../../../utils/url/remove-trailing-slash.js";import{createSlugify as ct}from"../create-slugify.js";import{apiDocLoader as pt,apiDocsLoader as ut}from"./loader.js";import{getTemplatePath as j}from"./get-template-path.js";import{storeDefinitionBundles as lt}from"./store-definition-bundles.js";import{telemetryTraceStep as dt}from"../../../cli/telemetry/helpers/trace-step.js";import{searchResolver as mt}from"./search/search-resolver.js";import{getAiDocumentsStore as ft}from"./search/get-ai-search-documents.js";import{getApiConfigByPath as gt}from"../get-api-config.js";import{getRouteSlugsForPath as ht}from"../utils.js";import{collectItemRoutes as Dt}from"./utils/collect-item-routes.js";import{isChunkCurrent as St}from"./utils/decorators.js";import{applyNavItemToRoot as Pt}from"./utils/apply-nav-item-to-root.js";import{buildRoutesMapping as yt}from"./utils/build-routes-mapping.js";import{buildItemPositions as It}from"./utils/build-item-positions.js";import{resolveApiDocTitle as At}from"./utils/resolve-api-doc-title.js";import{resolveConfigRbac as W}from"./utils/resolve-config-rbac.js";import{relativePathToBaseSlug as vt}from"./utils/relative-path-to-base-slug.js";import{normalizeApiVersion as Rt}from"./utils/normalize-api-version.js";import{importApiDocsBuilder as Q}from"./import-api-docs-builder.js";import{envConfig as $}from"../../config/env-config.js";import{logger as bt}from"../../tools/notifiers/logger.js";const T="openapi-spec-download",_t={asyncapi:"AsyncAPI Docs",graphql:"GraphQL Docs",openapi:"OpenAPI Docs"};async function pe(){let e=[],r=new Map;const s=new WeakMap;let n=new Set,m={};function k(o,g,v){const y=s.get(o)??new Map;s.set(o,y);const h=g??"";return y.get(h)===v?!1:(y.set(h,v),!0)}return{id:"api-docs",requiredEntitlements:["openapi","asyncapi","graphql"],loaders:{"api-doc":pt,"api-docs":ut},processContent:async(o,g)=>{await dt("com.redocly.build.plugin.apiDocs",async()=>{o.createRequestHandler(T,st(import.meta.url,"./spec-download.api.js")),o.addApiRoute({slug:at+"/*",requestHandlerId:T,httpMethod:"all",[A]:x,getStaticData:async()=>({props:{}})}),o.addApiRoute({slug:rt+"/*",requestHandlerId:T,httpMethod:"all",[A]:x,getStaticData:async()=>({props:{}})}),o.addApiRoute({slug:ot+"/*",requestHandlerId:T,httpMethod:"all",[A]:x,getStaticData:async()=>({props:{}})});const v=o.createTemplate(V,j("../../../client/templates/api-docs/template.js")),y=o.registerServerPropsGetter(V,j("./get-server-props.js")),h=await g.getConfig();r=new Map,m={};const I=await o.loadApiDocuments(g);lt(I,o.outdir);const E=!!h.plugins?.some(t=>typeof t=="string"&&t.startsWith("@redocly/portal-plugin-async-api/"));E&&I.some(t=>t.type==="asyncapi")&&bt.warn("The plugin '@redocly/portal-plugin-async-api' is deprecated. Please remove it from your config to use built-in AsyncAPI docs.");const c=E?I.filter(t=>t.type!=="asyncapi"):I;e=c.map(({markdocChunks:t,relativePath:P,isVirtual:p,customOutputRelativeFile:a,realRelativePath:f})=>({chunks:t??[],relativePath:P,realRelativePath:f,isVirtual:a!=null||p}));const{buildItems:i}=await Q(o.serverOutDir),D=(await g.cache.load("content-slugs","content-slugs")).data,S=ct(D),O=t=>it(S(t));for(const t of c){const P=t.customOutputRelativeFile||t.relativePath,p=O(P);wt(t,p);const{items:a,store:f}=await i({type:t.type,document:t.document,options:t.options,basePath:p,markdownParser:U}),R=`${w}${t.relativePath}`,M=$.isDevelopMode?t.relativePath:void 0,u=kt(t.document),b=u?.["x-metadata"],_=t.isVirtual?void 0:b?.apiId,N={document:t,basePath:p,sourcePath:M,apiId:_};await o.createSharedData(R,z(N,a,f));const Y=W(t.config),F=!!t.options.excludeFromSearch,X=t.type==="openapi",d=Dt(a,Y).map(l=>{const B=l.metadata?.seo,H={...Tt(l.link,p),fsPath:P,templateId:v,sharedData:[{key:"apiDocsStore",id:R}],serverPropsGetterIds:[y],excludeFromSearch:F,getStaticData:Et(l.label,B),[A]:l[A]};return X?{...H,...l.httpVerb?{httpVerb:l.httpVerb,metadata:{subType:"openapi-operation"}}:{}}:H});if(!d.length)continue;const G=At(u,t.type,a),J=Rt(u?.version),L={type:t.type,title:G,version:J,description:typeof u?.description=="string"?u.description:void 0,summary:typeof u?.summary=="string"?u.summary:void 0,...u?.["x-metadata"]&&typeof u["x-metadata"]=="object"?u["x-metadata"]:{}};d[0]={...d[0],metadata:L,hasClientRoutes:!0,getSidebar:(l,B)=>Pt(K(a),p,B),getNavText:()=>G??_t[t.type]??"API Docs",getSearchDocuments:mt(a,L,o.getSearchFacets,o.setSearchFacets,F,f,t.document),getAiDocumentsStore:ft({items:a,metadata:L,store:f,document:t.document,specType:t.type,excludeFromSearch:F,basePath:p,getSearchFacets:o.getSearchFacets,markdocTags:{...tt,schemaDefinition:et}})};const Z=d[0];d[0]=d[d.length-1],d[d.length-1]=Z;for(const l of d)o.addRoute(l);if(_){const l=gt(t.config?.apis,t.realRelativePath);m[_]={[A]:l.rbac,[C]:{fsPath:P}}}r.set(R,N)}})},afterRoutesCreated:async(o,g)=>{const{buildItems:v,simplifyAstStructure:y}=await Q(o.serverOutDir),h=new Set,I=new Set;for(const{chunks:c,relativePath:i,isVirtual:D}of e){const S=`${w}${i}`,O=ht(o.getAllRoutes(),i);for(const{node:t,markdown:P,pointer:p,key:a,relativePath:f}of c){const{ast:R,compoundHash:M}=await o.parseMarkdoc({input:{content:P,relativePath:f,isVirtual:D},resource:p,context:g,deps:{sharedDataIds:[`${w}${f}`],routeSlugs:O}});if(h.add(p),!(a==="graphql-docs"?t?.description?.value!==void 0:!!a&&St(t,a,P))||!k(t,a,M))continue;I.add(S);const b=y(R);if(a==="graphql-docs"){t.description&&(t.description.value=b);const _=`${w}${f}`;Ct(r.get(_),b)}else a&&(t[a]=b)}}const E=n.difference(h);for(const c of E)g.cache.delete(c);n=h,Object.entries(m).forEach(([c,i])=>{const D=i[C]?.fsPath;if(!D)return;const S=o.getRouteByFsPath(D)?.baseSlug;m[c]={...i,[C]:{...i[C],slug:S},slug:S}}),o.setGlobalData({apiProducts:m});for(const[c,i]of r){if(!I.has(c))continue;const{items:D,store:S}=await v({type:i.document.type,document:i.document.document,options:i.document.options,basePath:i.basePath,markdownParser:U});await o.createSharedData(c,z(i,D,S))}}}}function z({document:e,basePath:r,sourcePath:s,apiId:n},m,k){return{[A]:W(e.config),[C]:{slug:nt(r),fsPath:e.customOutputRelativeFile||e.relativePath},items:m,store:k,options:e.options,baseSlug:r,routesMapping:$.isDevelopMode?yt(m,r):{},graphqlPositions:$.isDevelopMode&&e.type==="graphql"?It(m,r):void 0,sourcePath:s,apiId:n}}function Ct(e,r){e?.document.type!=="graphql"||!e.document.options||(e.document.options.info={...e.document.options.info||{},description:r})}function Et(e,r){return async function(s){return{props:{settings:{baseUrlPath:q(s.baseSlug)},disableAutoScroll:!0,seo:{title:e,...r}}}}}function wt(e,r){const s=e.options?.mockServer,n=q(`/_mock${vt(e.relativePath)}`);s?.url===n&&(e.options={...e.options,mockServer:{...s,url:q(`/_mock${r}`)}})}function Tt(e,r){if(e===r)return{};const s=r==="/"?"/":`${r}/`;if(e.startsWith(s)){const n=e.slice(s.length);return n?{slugSuffix:n}:{}}return{slug:e}}function kt(e){if(e&&typeof e=="object"&&"info"in e)return e.info}function K(e){return e.map(({content:r,items:s,...n})=>({...n,...s&&{items:K(s)}}))}export{pe as apiDocsPlugin,Tt as toRouteSlugFields};
@@ -1 +1 @@
1
- import{getCookie as H}from"hono/cookie";import{createHash as F,timingSafeEqual as ne}from"node:crypto";import{ulid as oe}from"ulid";import{isRedoclySso as ie,isOidcProviderConfig as R,createMcpAuthorizationCode as se,verifyMcpAuthorizationCode as ae,createMcpRefreshToken as G,verifyMcpRefreshToken as ce,createMcpSessionResource as M,oidcRefreshTokenGrant as de,getOidcMetadata as pe,getUserParamsFromCookies as ue,idTokenIsForThisProject as le}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{resolveSiteBranding as _e}from"../../../plugins/mcp/handlers/credentials-page/site-branding.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{AUTH_URL as fe,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as K}from"../../../constants/common.js";import{ServerRoutes as I}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as w}from"@redocly/theme/core/utils";import{telemetry as y}from"../../../telemetry/index.js";import{envConfig as q}from"../../../config/env-config.js";import*as C from"../../jwt/jwt.js";import{InternalTokenTypes as O,McpAccessTokenMarker as U}from"../../jwt/token-types.js";import{AlgorithmTypes as A,JwtAlgorithmNotImplemented as he,JwtTokenExpired as me,JwtTokenInvalid as ge,JwtTokenNotBefore as Ce,JwtTokenSignatureMismatched as Te}from"../../jwt/types.js";import{getRequestOrigin as L}from"../../utils/get-request-origin.js";import{encryptClaimValue as B}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as ke}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as J}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as Se,isClientMetadataDocumentUrl as j,isRedirectUriRegistered as Ie}from"./mcp-client-metadata.js";import{CONSENT_PAGE_HEADERS as V,MCP_CONSENT_TTL_SEC as we,consentInvalidPage as Me,consentPage as Ee}from"./mcp-consent-page.js";import{MCP_CONSENT_MEMORY_DAYS as ye,hasConsent as Ae,redeemAuthorizationCode as Re,redeemRefreshToken as Pe,saveConsent as ve}from"./mcp-oauth-store.js";const s=(r,e,t=200,n)=>r.json(e,t,{"Content-Type":"application/json",...n??{}}),z=new Set(["S256"]),$=["authorization_code","refresh_token"],Oe="https://claude.ai/oauth/claude-code-client-metadata";function Ue(r,e,t){if(!r||!z.has(e||""))return!1;const n=F("sha256").update(t).digest("base64url"),i=Buffer.from(n),a=Buffer.from(r);return i.length!==a.length?!1:ne(i,a)}async function Le(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONTEXT,...r,iat:e,exp:e+600},m,A.HS256)}async function Ne(r){await C.verify(r,m,A.HS256);const{payload:e}=C.decode(r);if(e.type!==O.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function fr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=L(e);return s(e,{resource:`${t}${w("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${I.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function hr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=L(e);return s(e,{issuer:t,authorization_endpoint:`${t}${w(I.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${w(I.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${w(I.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...$],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function mr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:q.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...$],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}const De=new Set(["127.0.0.1","localhost","[::1]"]),He=new Set(["http:","https:","ws:","wss:","ftp:","file:"]),be=new Set(["javascript:","data:","blob:","about:","vbscript:","intent:","chrome:","view-source:","ms-msdt:","ms-appx-web:"]),qe=2048,je=/[\s\p{Cc}]/u;function W(r){return r.protocol==="http:"&&De.has(r.hostname)}function Y(r){if(!r||r.length>qe||r.includes("#")||je.test(r))return;const e=URL.parse(r);if(e){if(e.protocol==="https:"||W(e))return r.toLowerCase().startsWith(`${e.protocol}//`)?e:void 0;if(!(He.has(e.protocol)||be.has(e.protocol)))return e}}function Z(r){const e=r||{},t=Object.keys(e),n=t.find(a=>R(e[a])&&ie(e[a])),i=t.find(a=>R(e[a]));return n||i||t[0]}function gr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),a=n.get("client_id"),c=n.get("code_challenge"),o=n.get("code_challenge_method"),d=oe();if(!i||!Y(i))return s(e,{error:"invalid_request",error_description:"redirect_uri must be an https URL, a loopback http URL, or a private-use scheme"},400);if(!c)return s(e,{error:"invalid_request",error_description:"code_challenge is required"},400);if(!z.has(o||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...z].join(", ")}`},400);let p;if(j(a))try{const u=await Se(a);p=u.client_name;const _=a===Oe;if(!Ie(i,u.redirect_uris,{ignorePort:_}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(u){const _=u instanceof Error?u.message:String(u);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${_}`},400)}y.sendMcpAuthorizationStartedMessage([{...M(d),redirect_uri:i||null}]);const l=L(e),k={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:a,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:c,mcpCodeChallengeMethod:o,mcpClientName:p,timestamp:Date.now()};try{const u=await Le(k),_=new URL(w(I.IDP_LOGIN),l);_.searchParams.set("redirectTo",`${I.MCP_CALLBACK}/${u}`);const h=Z(r.getConfig().ssoDirect);return h?(_.searchParams.set("idpId",h),e.redirect(_.toString())):s(e,{error:"not_found"},404)}catch(u){const _=u instanceof Error?u.message:String(u),h=u instanceof Error?u.stack:String(u);y.sendMcpAuthorizationFailedMessage([{...M(d),error:_,error_details:h}]);const S=new URL(w(`${fe}/oauth2/auth`));return S.search=n.toString(),e.redirect(S.toString())}}}async function X(r){const e=Math.floor(Date.now()/1e3),t=r.ttlSec?e+r.ttlSec:Math.min(typeof r.claims.exp=="number"?r.claims.exp:1/0,e+b);return{accessToken:await C.sign({...r.claims,[U.CLAIM]:U.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e}:{},exp:t},m,A.HS256,r.kid),expiresIn:Math.max(t-e,0)}}async function ze(r,e,t){const n=T=>s(r,{error:"invalid_grant",error_description:T},400),i=r.get("logger"),a=t.get("refresh_token"),c=t.get("client_id")||void 0;if(!a)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let o;try{o=await ce(a)}catch(T){return i?.warn("MCP refresh token rejected: %s",T instanceof Error?T.message:String(T)),n("Invalid refresh token")}if(c&&c!==o.client_id)return n("Client mismatch");const d=o.idp_id?e.getConfig().ssoDirect?.[o.idp_id]:void 0;let p,l,k=o.idp_access_token_enc,u=o.idp_refresh_token_enc;if(o.idp_refresh_token_enc){let T;try{T=ke(o.idp_refresh_token_enc,m)}catch{return n("Invalid refresh token")}if(!o.idp_id||!R(d))return n("Unknown identity provider");let f;try{const g=await pe(o.idp_id,d);if(!g?.token_endpoint)return n("Identity provider has no token endpoint");f=await de(g.token_endpoint,T,d)}catch(g){return i?.warn("MCP refresh failed to reach the IdP token endpoint: %s",g instanceof Error?g.message:String(g)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(f.error||!(f.id_token||f.access_token))return i?.warn("MCP refresh rejected by IdP: %s",f.error||"empty token response"),n("Refresh rejected by identity provider");if(f.refresh_token&&(u=J(f.refresh_token,m)),f.access_token&&(k=B(f.access_token,m)),f.id_token){try{const g=C.decode(f.id_token);p=g.payload,l=g.header.kid}catch{return n("Invalid token response from identity provider")}if(p.iss!==o.claims.iss||p.sub!==o.claims.sub)return i?.warn("MCP refresh rejected: the id_token names another issuer or subject"),n("Invalid refresh token")}}const _=Math.min(R(d)&&d.tokenExpirationTime||b,b),h=p??o.claims;if(!le(h,d))return i?.warn("MCP refresh rejected: the id_token was issued for another project"),n("Invalid refresh token");if(!await Pe(await e.getKv(),a,o.exp))return i?.warn("MCP refresh rejected: the refresh token was already used"),n("Refresh token already used");const S=await X({claims:h,kid:l,idpId:o.idp_id,idpAccessTokenEnc:k,ttlSec:p?void 0:_}),D=await G({clientId:o.client_id,idpId:o.idp_id,claims:h,idpAccessTokenEnc:k,idpRefreshTokenEnc:u,sessionExp:o.session_exp});return s(r,{access_token:S.accessToken,token_type:"Bearer",expires_in:S.expiresIn,refresh_token:D,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Cr(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),a=t.get("redirect_uri")||void 0,c=t.get("code_verifier")||void 0;if(!$.includes(n))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(n==="refresh_token")return await ze(e,r,t);if(!i)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const o=await ae(i);if(a&&a!==o.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(!c)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!Ue(o.code_challenge,o.code_challenge_method,c))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400);if(q.OAUTH_CLIENT_ID&&o.client_id&&!j(o.client_id)&&o.client_id!==q.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=o.id_token;if(typeof d!="string"||d.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const p=await Q(d,r.getConfig().ssoDirect);if(!p)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);if(!await Re(await r.getKv(),i,o.exp))return s(e,{error:"invalid_grant",error_description:"Authorization code already used"},400);const l=o.idp_access_token_enc,{accessToken:k,expiresIn:u}=await X({claims:p.payload,kid:p.kid,idpId:o.idp_id,idpAccessTokenEnc:l}),h=Math.floor(Date.now()/1e3)+K,S=await G({clientId:o.client_id,idpId:o.idp_id,claims:p.payload,idpAccessTokenEnc:l,idpRefreshTokenEnc:o.idp_refresh_token_enc,sessionExp:h}),D={access_token:k,token_type:"Bearer",expires_in:u,refresh_token:S,scope:E.join(" ")};return s(e,D,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(o){return e.get("logger")?.warn("MCP token exchange failed: %s",o instanceof Error?o.message:String(o)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const n=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}async function Q(r,e){const t=await ue(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:n,header:i}=C.decode(r);if(n[U.CLAIM]!==U.VALUE)return{payload:n,kid:i.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function N(r){return F("sha256").update(r).digest("base64url")}async function $e(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONSENT,session_hash:N(r.sessionToken),context_hash:N(r.contextToken),iat:e,exp:e+we},m,A.HS256)}async function Fe(r,e){try{await C.verify(r,m,A.HS256)}catch{return!1}const{payload:t}=C.decode(r);return t.type===O.MCP_CONSENT&&t.session_hash===N(e.sessionToken)&&t.context_hash===N(e.contextToken)}function x(r,e){return async t=>{const n=new URL(t.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(w(`${I.MCP_CALLBACK}/`))){const c=n.pathname.split("/");i=c[c.length-1]}if(!i)return t.text("Missing context parameter",400);let a=null;try{const c=await Ne(i);a=c.mcpSessionId||null;const o=c.originalRedirectUri,d=Y(o);if(!c.isMcpFlow||!o||!d)throw new Error("Invalid MCP context");const p=H(t,v.AUTHORIZATION),l=p?await Q(p,r.getConfig().ssoDirect):void 0;return!p||!l?(y.sendMcpAuthorizationFailedMessage([{...M(a),error:"Authentication required",error_details:null}]),t.text("Authentication required",401)):await e(t,{contextToken:i,mcpContext:c,redirectUri:o,redirectUrl:d,sessionToken:p,session:l})}catch(c){const o=Be(c);return y.sendMcpAuthorizationFailedMessage([{...M(a),...o}]),t.text(`Invalid MCP callback: ${o.error}`,400)}}}async function ee(r,e,{mcpContext:t,redirectUri:n,redirectUrl:i,sessionToken:a,session:c}){const o=H(r,v.IDP_ACCESS_TOKEN),d=H(r,v.IDP_REFRESH_TOKEN),p=await se({idToken:a,idpId:c.idpId??Z(e.getConfig().ssoDirect),idpAccessTokenEnc:o?B(o,m):void 0,idpRefreshTokenEnc:d?J(d,m):void 0,clientId:t.mcpClientId||"",redirectUri:n,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(i);return l.searchParams.set("code",p),t.mcpState&&l.searchParams.set("state",t.mcpState),y.sendMcpAuthorizationCompletedMessage([{...M(t.mcpSessionId||null),redirect_uri:n}]),r.redirect(l.toString())}function Ge(r,{mcpContext:e,redirectUrl:t}){const n=new URL(t);return n.searchParams.set("error","access_denied"),e.mcpState&&n.searchParams.set("state",e.mcpState),y.sendMcpAuthorizationFailedMessage([{...M(e.mcpSessionId||null),error:"access_denied",error_details:null}]),r.redirect(n.toString())}function Ke({payload:r}){return[r.email,r.name,r.sub].find(e=>typeof e=="string"&&e.length>0)}function re({mcpContext:r,redirectUri:e,session:t}){const{sub:n}=t.payload;if(!(typeof n!="string"||n.length===0))return{idpId:t.idpId,subject:n,clientId:r.mcpClientId||"",redirectUri:e}}function te(r,e){const{seo:t,colorMode:n}=e.getConfig(),{logo:i}=e.getGlobalData();return{..._e({seo:t,logo:i,colorMode:n}),host:new URL(L(r)).host}}function Tr(r){return x(r,async(e,t)=>{const n=re(t);if(W(t.redirectUrl)||n&&await Ae(await r.getKv(),n))return ee(e,r,t);const{mcpClientId:i,mcpClientName:a}=t.mcpContext,c=Ee({site:te(e,r),user:Ke(t.session),clientName:a||void 0,clientIdHost:j(i)?new URL(i).host:void 0,redirectUrl:t.redirectUrl,scopes:E,accessLifetimeHours:K/3600,rememberDays:n?ye:void 0,consentToken:await $e(t)});return new Response(c,{status:200,headers:V})})}function kr(r){return x(r,async(e,t)=>{const n=await e.req.formData(),i=n.get("consent");if(typeof i!="string"||!await Fe(i,t))return new Response(Me(te(e,r)),{status:403,headers:V});if(n.get("decision")==="allow"){const a=re(t);return a&&await ve(await r.getKv(),a),ee(e,r,t)}return Ge(e,t)})}function Be(r){return r instanceof Error&&[he,me,ge,Ce,Te].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{$ as SUPPORTED_MCP_GRANT_TYPES,Le as createMcpContextToken,gr as mcpAuthorizationHandler,Tr as mcpCallbackHandler,kr as mcpConsentHandler,mr as mcpDynamicClientRegistrationHandler,hr as mcpOAuthAuthorizationServerHandler,fr as mcpOAuthProtectedResourceHandler,Cr as mcpTokenPortalHandler,Z as resolveMcpIdpId,Ne as verifyAndParseMcpContextToken,Ue as verifyPkce};
1
+ import{getCookie as H}from"hono/cookie";import{createHash as G,timingSafeEqual as oe}from"node:crypto";import{ulid as ie}from"ulid";import{isRedoclySso as se,isOidcProviderConfig as P,createMcpAuthorizationCode as ae,verifyMcpAuthorizationCode as ce,createMcpRefreshToken as K,verifyMcpRefreshToken as de,createMcpSessionResource as M,oidcRefreshTokenGrant as pe,getOidcMetadata as ue,getUserParamsFromCookies as le,idTokenIsForThisProject as _e}from"../../auth.js";import{isMcpContentProtected as R}from"../../../plugins/mcp/auth/auth-handlers.js";import{resolveSiteBranding as fe}from"../../../plugins/mcp/handlers/credentials-page/site-branding.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{AUTH_URL as he,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as B}from"../../../constants/common.js";import{ServerRoutes as I}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as w}from"@redocly/theme/core/utils";import{telemetry as y}from"../../../telemetry/index.js";import{envConfig as q}from"../../../config/env-config.js";import{hasProjectDatabase as j}from"../../../providers/database/has-project-database.js";import*as C from"../../jwt/jwt.js";import{InternalTokenTypes as O,McpAccessTokenMarker as U}from"../../jwt/token-types.js";import{AlgorithmTypes as A,JwtAlgorithmNotImplemented as me,JwtTokenExpired as ge,JwtTokenInvalid as Ce,JwtTokenNotBefore as Te,JwtTokenSignatureMismatched as ke}from"../../jwt/types.js";import{getRequestOrigin as L}from"../../utils/get-request-origin.js";import{encryptClaimValue as J}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as Se}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as V}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as Ie,isClientMetadataDocumentUrl as z,isRedirectUriRegistered as we}from"./mcp-client-metadata.js";import{CONSENT_PAGE_HEADERS as W,MCP_CONSENT_TTL_SEC as Me,consentInvalidPage as Ee,consentPage as ye}from"./mcp-consent-page.js";import{MCP_CONSENT_MEMORY_DAYS as Ae,hasConsent as Pe,redeemAuthorizationCode as Re,redeemRefreshToken as ve,saveConsent as Oe}from"./mcp-oauth-store.js";const s=(r,e,t=200,n)=>r.json(e,t,{"Content-Type":"application/json",...n??{}}),$=new Set(["S256"]),F=["authorization_code","refresh_token"],Ue="https://claude.ai/oauth/claude-code-client-metadata";function Le(r,e,t){if(!r||!$.has(e||""))return!1;const n=G("sha256").update(t).digest("base64url"),i=Buffer.from(n),a=Buffer.from(r);return i.length!==a.length?!1:oe(i,a)}async function Ne(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONTEXT,...r,iat:e,exp:e+600},m,A.HS256)}async function De(r){await C.verify(r,m,A.HS256);const{payload:e}=C.decode(r);if(e.type!==O.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function mr(r){return async e=>{if(!R(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=L(e);return s(e,{resource:`${t}${w("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${I.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function gr(r){return async e=>{if(!R(r))return s(e,{error:"not_found"},404);const t=L(e);return s(e,{issuer:t,authorization_endpoint:`${t}${w(I.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${w(I.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${w(I.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...F],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function Cr(r){return async e=>{if(!R(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:q.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...F],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}const He=new Set(["127.0.0.1","localhost","[::1]"]),be=new Set(["http:","https:","ws:","wss:","ftp:","file:"]),qe=new Set(["javascript:","data:","blob:","about:","vbscript:","intent:","chrome:","view-source:","ms-msdt:","ms-appx-web:"]),je=2048,ze=/[\s\p{Cc}]/u;function Y(r){return r.protocol==="http:"&&He.has(r.hostname)}function Z(r){if(!r||r.length>je||r.includes("#")||ze.test(r))return;const e=URL.parse(r);if(e){if(e.protocol==="https:"||Y(e))return r.toLowerCase().startsWith(`${e.protocol}//`)?e:void 0;if(!(be.has(e.protocol)||qe.has(e.protocol)))return e}}function X(r){const e=r||{},t=Object.keys(e),n=t.find(a=>P(e[a])&&se(e[a])),i=t.find(a=>P(e[a]));return n||i||t[0]}function Tr(r){return async e=>{if(!R(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),a=n.get("client_id"),c=n.get("code_challenge"),o=n.get("code_challenge_method"),d=ie();if(!i||!Z(i))return s(e,{error:"invalid_request",error_description:"redirect_uri must be an https URL, a loopback http URL, or a private-use scheme"},400);if(!c)return s(e,{error:"invalid_request",error_description:"code_challenge is required"},400);if(!$.has(o||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...$].join(", ")}`},400);let p;if(z(a))try{const u=await Ie(a);p=u.client_name;const _=a===Ue;if(!we(i,u.redirect_uris,{ignorePort:_}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(u){const _=u instanceof Error?u.message:String(u);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${_}`},400)}y.sendMcpAuthorizationStartedMessage([{...M(d),redirect_uri:i||null}]);const l=L(e),k={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:a,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:c,mcpCodeChallengeMethod:o,mcpClientName:p,timestamp:Date.now()};try{const u=await Ne(k),_=new URL(w(I.IDP_LOGIN),l);_.searchParams.set("redirectTo",`${I.MCP_CALLBACK}/${u}`);const h=X(r.getConfig().ssoDirect);return h?(_.searchParams.set("idpId",h),e.redirect(_.toString())):s(e,{error:"not_found"},404)}catch(u){const _=u instanceof Error?u.message:String(u),h=u instanceof Error?u.stack:String(u);y.sendMcpAuthorizationFailedMessage([{...M(d),error:_,error_details:h}]);const S=new URL(w(`${he}/oauth2/auth`));return S.search=n.toString(),e.redirect(S.toString())}}}async function Q(r){const e=Math.floor(Date.now()/1e3),t=r.ttlSec?e+r.ttlSec:Math.min(typeof r.claims.exp=="number"?r.claims.exp:1/0,e+b);return{accessToken:await C.sign({...r.claims,[U.CLAIM]:U.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e}:{},exp:t},m,A.HS256,r.kid),expiresIn:Math.max(t-e,0)}}async function $e(r,e,t){const n=T=>s(r,{error:"invalid_grant",error_description:T},400),i=r.get("logger"),a=t.get("refresh_token"),c=t.get("client_id")||void 0;if(!a)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let o;try{o=await de(a)}catch(T){return i?.warn("MCP refresh token rejected: %s",T instanceof Error?T.message:String(T)),n("Invalid refresh token")}if(c&&c!==o.client_id)return n("Client mismatch");const d=o.idp_id?e.getConfig().ssoDirect?.[o.idp_id]:void 0;let p,l,k=o.idp_access_token_enc,u=o.idp_refresh_token_enc;if(o.idp_refresh_token_enc){let T;try{T=Se(o.idp_refresh_token_enc,m)}catch{return n("Invalid refresh token")}if(!o.idp_id||!P(d))return n("Unknown identity provider");let f;try{const g=await ue(o.idp_id,d);if(!g?.token_endpoint)return n("Identity provider has no token endpoint");f=await pe(g.token_endpoint,T,d)}catch(g){return i?.warn("MCP refresh failed to reach the IdP token endpoint: %s",g instanceof Error?g.message:String(g)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(f.error||!(f.id_token||f.access_token))return i?.warn("MCP refresh rejected by IdP: %s",f.error||"empty token response"),n("Refresh rejected by identity provider");if(f.refresh_token&&(u=V(f.refresh_token,m)),f.access_token&&(k=J(f.access_token,m)),f.id_token){try{const g=C.decode(f.id_token);p=g.payload,l=g.header.kid}catch{return n("Invalid token response from identity provider")}if(p.iss!==o.claims.iss||p.sub!==o.claims.sub)return i?.warn("MCP refresh rejected: the id_token names another issuer or subject"),n("Invalid refresh token")}}const _=Math.min(P(d)&&d.tokenExpirationTime||b,b),h=p??o.claims;if(!_e(h,d))return i?.warn("MCP refresh rejected: the id_token was issued for another project"),n("Invalid refresh token");if(j()&&!await ve(await e.getKv(),a,o.exp))return i?.warn("MCP refresh rejected: the refresh token was already used"),n("Refresh token already used");const S=await Q({claims:h,kid:l,idpId:o.idp_id,idpAccessTokenEnc:k,ttlSec:p?void 0:_}),D=await K({clientId:o.client_id,idpId:o.idp_id,claims:h,idpAccessTokenEnc:k,idpRefreshTokenEnc:u,sessionExp:o.session_exp});return s(r,{access_token:S.accessToken,token_type:"Bearer",expires_in:S.expiresIn,refresh_token:D,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function kr(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),a=t.get("redirect_uri")||void 0,c=t.get("code_verifier")||void 0;if(!F.includes(n))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(n==="refresh_token")return await $e(e,r,t);if(!i)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const o=await ce(i);if(a&&a!==o.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(!c)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!Le(o.code_challenge,o.code_challenge_method,c))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400);if(q.OAUTH_CLIENT_ID&&o.client_id&&!z(o.client_id)&&o.client_id!==q.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=o.id_token;if(typeof d!="string"||d.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const p=await x(d,r.getConfig().ssoDirect);if(!p)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);if(j()&&!await Re(await r.getKv(),i,o.exp))return s(e,{error:"invalid_grant",error_description:"Authorization code already used"},400);const l=o.idp_access_token_enc,{accessToken:k,expiresIn:u}=await Q({claims:p.payload,kid:p.kid,idpId:o.idp_id,idpAccessTokenEnc:l}),h=Math.floor(Date.now()/1e3)+B,S=await K({clientId:o.client_id,idpId:o.idp_id,claims:p.payload,idpAccessTokenEnc:l,idpRefreshTokenEnc:o.idp_refresh_token_enc,sessionExp:h}),D={access_token:k,token_type:"Bearer",expires_in:u,refresh_token:S,scope:E.join(" ")};return s(e,D,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(o){return e.get("logger")?.warn("MCP token exchange failed: %s",o instanceof Error?o.message:String(o)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const n=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}async function x(r,e){const t=await le(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:n,header:i}=C.decode(r);if(n[U.CLAIM]!==U.VALUE)return{payload:n,kid:i.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function N(r){return G("sha256").update(r).digest("base64url")}async function Fe(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONSENT,session_hash:N(r.sessionToken),context_hash:N(r.contextToken),iat:e,exp:e+Me},m,A.HS256)}async function Ge(r,e){try{await C.verify(r,m,A.HS256)}catch{return!1}const{payload:t}=C.decode(r);return t.type===O.MCP_CONSENT&&t.session_hash===N(e.sessionToken)&&t.context_hash===N(e.contextToken)}function ee(r,e){return async t=>{const n=new URL(t.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(w(`${I.MCP_CALLBACK}/`))){const c=n.pathname.split("/");i=c[c.length-1]}if(!i)return t.text("Missing context parameter",400);let a=null;try{const c=await De(i);a=c.mcpSessionId||null;const o=c.originalRedirectUri,d=Z(o);if(!c.isMcpFlow||!o||!d)throw new Error("Invalid MCP context");const p=H(t,v.AUTHORIZATION),l=p?await x(p,r.getConfig().ssoDirect):void 0;return!p||!l?(y.sendMcpAuthorizationFailedMessage([{...M(a),error:"Authentication required",error_details:null}]),t.text("Authentication required",401)):await e(t,{contextToken:i,mcpContext:c,redirectUri:o,redirectUrl:d,sessionToken:p,session:l})}catch(c){const o=Je(c);return y.sendMcpAuthorizationFailedMessage([{...M(a),...o}]),t.text(`Invalid MCP callback: ${o.error}`,400)}}}async function re(r,e,{mcpContext:t,redirectUri:n,redirectUrl:i,sessionToken:a,session:c}){const o=H(r,v.IDP_ACCESS_TOKEN),d=H(r,v.IDP_REFRESH_TOKEN),p=await ae({idToken:a,idpId:c.idpId??X(e.getConfig().ssoDirect),idpAccessTokenEnc:o?J(o,m):void 0,idpRefreshTokenEnc:d?V(d,m):void 0,clientId:t.mcpClientId||"",redirectUri:n,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(i);return l.searchParams.set("code",p),t.mcpState&&l.searchParams.set("state",t.mcpState),y.sendMcpAuthorizationCompletedMessage([{...M(t.mcpSessionId||null),redirect_uri:n}]),r.redirect(l.toString())}function Ke(r,{mcpContext:e,redirectUrl:t}){const n=new URL(t);return n.searchParams.set("error","access_denied"),e.mcpState&&n.searchParams.set("state",e.mcpState),y.sendMcpAuthorizationFailedMessage([{...M(e.mcpSessionId||null),error:"access_denied",error_details:null}]),r.redirect(n.toString())}function Be({payload:r}){return[r.email,r.name,r.sub].find(e=>typeof e=="string"&&e.length>0)}function te({mcpContext:r,redirectUri:e,session:t}){const{sub:n}=t.payload;if(!(!j()||typeof n!="string"||n.length===0))return{idpId:t.idpId,subject:n,clientId:r.mcpClientId||"",redirectUri:e}}function ne(r,e){const{seo:t,colorMode:n}=e.getConfig(),{logo:i}=e.getGlobalData();return{...fe({seo:t,logo:i,colorMode:n}),host:new URL(L(r)).host}}function Sr(r){return ee(r,async(e,t)=>{const n=te(t);if(Y(t.redirectUrl)||n&&await Pe(await r.getKv(),n))return re(e,r,t);const{mcpClientId:i,mcpClientName:a}=t.mcpContext,c=ye({site:ne(e,r),user:Be(t.session),clientName:a||void 0,clientIdHost:z(i)?new URL(i).host:void 0,redirectUrl:t.redirectUrl,scopes:E,accessLifetimeHours:B/3600,rememberDays:n?Ae:void 0,consentToken:await Fe(t)});return new Response(c,{status:200,headers:W})})}function Ir(r){return ee(r,async(e,t)=>{const n=await e.req.formData(),i=n.get("consent");if(typeof i!="string"||!await Ge(i,t))return new Response(Ee(ne(e,r)),{status:403,headers:W});if(n.get("decision")==="allow"){const a=te(t);return a&&await Oe(await r.getKv(),a),re(e,r,t)}return Ke(e,t)})}function Je(r){return r instanceof Error&&[me,ge,Ce,Te,ke].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{F as SUPPORTED_MCP_GRANT_TYPES,Ne as createMcpContextToken,Tr as mcpAuthorizationHandler,Sr as mcpCallbackHandler,Ir as mcpConsentHandler,Cr as mcpDynamicClientRegistrationHandler,gr as mcpOAuthAuthorizationServerHandler,mr as mcpOAuthProtectedResourceHandler,kr as mcpTokenPortalHandler,X as resolveMcpIdpId,De as verifyAndParseMcpContextToken,Le as verifyPkce};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/revel",
3
- "version": "0.138.0",
3
+ "version": "0.138.2",
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",
36
+ "@redocly/api-docs": "0.3.1",
37
37
  "@redocly/config": "0.65.0",
38
38
  "@redocly/openapi-core": "2.62.1",
39
39
  "@redocly/portal-legacy-ui": "0.19.0",
40
40
  "@redocly/portal-plugin-mock-server": "0.23.0",
41
41
  "@redocly/realm-asyncapi-sdk": "0.16.0",
42
- "@redocly/theme": "0.70.0",
42
+ "@redocly/theme": "0.70.1",
43
43
  "@shikijs/transformers": "3.21.0",
44
44
  "@tanstack/react-query": "5.62.3",
45
45
  "@tanstack/react-table": "8.21.3",
@@ -50,7 +50,7 @@
50
50
  "ajv-formats": "^3.0.1",
51
51
  "anser": "^2.3.2",
52
52
  "babel-plugin-styled-components": "2.1.4",
53
- "chokidar": "3.6.0",
53
+ "chokidar": "npm:@redocly/chokidar@3.6.0",
54
54
  "colorette": "2.0.20",
55
55
  "copy-to-clipboard": "3.3.3",
56
56
  "dotenv": "16.4.5",