@redocly/redoc-reef 0.138.0-next.4 → 0.138.0-next.6
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 +51 -0
- package/dist/cli/build/analytics/collect-analytics.js +1 -1
- package/dist/cli/build/analytics/collectors/get-sidebars-usage.d.ts +12 -0
- package/dist/cli/build/analytics/collectors/get-sidebars-usage.js +1 -0
- package/dist/cli/build/index.js +1 -1
- package/dist/cli/eject/resolveEjectParams.js +1 -1
- package/dist/cli/generate/string-utils.js +1 -1
- package/dist/cli/stats/collectors/extension-sampling.js +1 -1
- package/dist/cli/translations/collect-config-translations.js +1 -1
- package/dist/client/TestProvider.js +1 -1
- package/dist/client/app/Link.js +1 -1
- package/dist/client/app/Sidebar/useSidebarItems.js +1 -1
- package/dist/client/app/hooks/codeHighlight/treeview-transformer.js +1 -1
- package/dist/client/app/hooks/markdown/useMarkdownText.js +1 -1
- package/dist/client/app/search/index.d.ts +1 -1
- package/dist/client/app/search/index.js +1 -1
- package/dist/client/app/search/useSearch.d.ts +3 -0
- package/dist/client/app/search/useSearch.js +1 -1
- package/dist/client/providers/page-data/hooks.d.ts +2 -0
- package/dist/client/providers/page-data/hooks.js +1 -1
- package/dist/client/providers/theme/ThemeDataProvider.js +1 -1
- package/dist/client/runtime/loader.js +1 -1
- package/dist/client/templates/api-docs/helpers.js +1 -1
- package/dist/client/templates/api-docs/template.js +7 -3
- package/dist/client/utils/catalog/parse-filter-query.js +1 -1
- package/dist/client/{app/Sidebar/helpers → utils}/filter-out-versioned-items.d.ts +12 -2
- package/dist/client/utils/filter-out-versioned-items.js +1 -0
- package/dist/client/utils/navigation-tabs.d.ts +14 -0
- package/dist/client/utils/navigation-tabs.js +1 -0
- package/dist/constants/l10n/langs/ar.js +1 -1
- package/dist/constants/l10n/langs/de.js +1 -1
- package/dist/constants/l10n/langs/en.js +1 -1
- package/dist/constants/l10n/langs/es.js +1 -1
- package/dist/constants/l10n/langs/fr.js +1 -1
- package/dist/constants/l10n/langs/hi.js +1 -1
- package/dist/constants/l10n/langs/it.js +1 -1
- package/dist/constants/l10n/langs/ja.js +1 -1
- package/dist/constants/l10n/langs/ko.js +1 -1
- package/dist/constants/l10n/langs/pl.js +1 -1
- package/dist/constants/l10n/langs/pt-BR.js +1 -1
- package/dist/constants/l10n/langs/pt.js +1 -1
- package/dist/constants/l10n/langs/ru.js +1 -1
- package/dist/constants/l10n/langs/uk.js +1 -1
- package/dist/constants/l10n/langs/zh.js +1 -1
- package/dist/server/config/env-config.d.ts +0 -2
- package/dist/server/config/env-schema.d.ts +0 -5
- package/dist/server/config/env-schemas/feature-flags.d.ts +0 -4
- package/dist/server/config/env-schemas/feature-flags.js +1 -1
- package/dist/server/constants/common.d.ts +0 -1
- package/dist/server/constants/common.js +1 -1
- package/dist/server/plugins/api-docs/index.js +1 -1
- package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.d.ts +9 -0
- package/dist/server/plugins/api-docs/utils/apply-nav-item-to-root.js +1 -0
- package/dist/server/plugins/api-docs/utils/decorators.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/ajv-validator.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/extract-parts-from-components-ref.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/is-valid-tag-name.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/openapi-component-content-extractor.js +1 -1
- package/dist/server/plugins/default-theme/extract-team-names.js +1 -1
- package/dist/server/plugins/get-reserved-routes.d.ts +4 -2
- package/dist/server/plugins/get-reserved-routes.js +1 -1
- package/dist/server/plugins/l10n/index.js +1 -1
- package/dist/server/plugins/markdown/search/to-markdown.js +1 -1
- package/dist/server/plugins/markdown/utils/sanitize-malformed-md-content.js +1 -1
- package/dist/server/plugins/mcp/audit/secret-patterns.js +1 -1
- package/dist/server/plugins/mcp/auth/assistant-anonymous-token.js +1 -1
- package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
- package/dist/server/plugins/mcp/codemode/build-tool-list.d.ts +6 -1
- package/dist/server/plugins/mcp/codemode/build-tool-list.js +2 -2
- package/dist/server/plugins/mcp/codemode/code-execution-audit.d.ts +3 -2
- package/dist/server/plugins/mcp/codemode/code-execution-audit.js +1 -1
- package/dist/server/plugins/mcp/codemode/code-execution-telemetry.d.ts +3 -4
- package/dist/server/plugins/mcp/codemode/code-execution-telemetry.js +1 -1
- package/dist/server/plugins/mcp/codemode/prompts.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/describe-tools.js +2 -2
- package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/manage-credentials.js +2 -2
- package/dist/server/plugins/mcp/custom-tools/scan-custom-tools.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tool-schemas.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/endpoint-suggestions.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-endpoint-info.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/get-security-schemes.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.d.ts +3 -4
- package/dist/server/plugins/mcp/docs-mcp/tools/openapi/utils.js +1 -1
- package/dist/server/plugins/mcp/docs-mcp/utils/resolve-refs.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +15 -3
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +6 -5
- package/dist/server/plugins/mcp/gateway-mcp/fetch/allowed-hosts.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/url-placeholders.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +2 -2
- package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +35 -35
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.js +3 -3
- package/dist/server/plugins/mcp/types.d.ts +5 -11
- package/dist/server/plugins/mcp/utils/json-schema-validator.d.ts +2 -2
- package/dist/server/plugins/mcp/utils/json-schema-validator.js +1 -1
- package/dist/server/plugins/mcp/utils/mcp-client.js +1 -1
- package/dist/server/plugins/mcp/utils/public-endpoint.d.ts +9 -0
- package/dist/server/plugins/mcp/utils/public-endpoint.js +1 -0
- package/dist/server/plugins/mcp/utils/telemetry-attributes.d.ts +2 -1
- package/dist/server/plugins/mcp/utils/telemetry-attributes.js +1 -1
- package/dist/server/plugins/mcp/utils/xmcp-utils.d.ts +0 -7
- package/dist/server/plugins/mcp/utils/xmcp-utils.js +1 -1
- package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
- package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.d.ts +5 -7
- package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
- package/dist/server/plugins/nav-utils.js +1 -1
- package/dist/server/plugins/scorecard-classic/index.js +1 -1
- package/dist/server/plugins/scorecard-classic/template/useData.js +1 -1
- package/dist/server/plugins/search/engines/flexsearch/index.js +2 -1
- package/dist/server/plugins/search/engines/flexsearch/search-index.d.ts +2 -2
- package/dist/server/plugins/search/engines/flexsearch/search-index.js +1 -1
- package/dist/server/plugins/search/llmstxt/index.js +1 -1
- package/dist/server/plugins/sidebars/index.js +7 -3
- package/dist/server/plugins/sidebars/tabs.d.ts +19 -0
- package/dist/server/plugins/sidebars/tabs.js +1 -0
- package/dist/server/plugins/sso/index.js +1 -1
- package/dist/server/store.js +1 -1
- package/dist/server/tools/notifiers/helpers/privacy/mask-subject.js +1 -1
- package/dist/server/tools/notifiers/helpers/privacy/remove-pii.js +1 -1
- package/dist/server/types/plugins/search.d.ts +6 -4
- package/dist/server/utils/catalog-classic/get-catalog-shared-data-id.d.ts +3 -0
- package/dist/server/utils/catalog-classic/get-catalog-shared-data-id.js +1 -0
- package/dist/server/utils/envs/interpolate-env-variables.js +1 -1
- package/dist/server/utils/rbac.js +1 -1
- package/dist/server/utils/report-all-errors.js +1 -1
- package/dist/server/utils/sandbox-context.js +1 -1
- package/dist/server/utils/write-problems-report.d.ts +3 -0
- package/dist/server/utils/write-problems-report.js +1 -0
- package/dist/server/web-server/auth.d.ts +9 -3
- package/dist/server/web-server/auth.js +4 -4
- package/dist/server/web-server/middleware/ensureSearchData.js +1 -1
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
- package/dist/utils/path/parse-path-versions.js +1 -1
- package/dist/utils/string/to-kebab-case.js +1 -1
- package/package.json +7 -7
- package/dist/client/app/Sidebar/helpers/filter-out-versioned-items.js +0 -1
- package/dist/server/plugins/mcp/auth/token-audience-telemetry.d.ts +0 -7
- package/dist/server/plugins/mcp/auth/token-audience-telemetry.js +0 -1
- package/dist/server/plugins/mcp/auth/token-audience.d.ts +0 -6
- package/dist/server/plugins/mcp/auth/token-audience.js +0 -1
package/dist/server/store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import A from"@markdoc/markdoc";import{parseAndResolveMarkdoc as T}from"./plugins/markdown/compiler.js";import{getPathnameForLocale as M}from"@redocly/theme/core/utils";import{DEFAULT_LOCALE_PLACEHOLDER as g}from"../constants/common.js";import{DEFAULT_TITLE as O,MARKDOC_PARTIALS_DATA_KEY as m,MARKDOC_PARTIALS_DEPS_KEY as k}from"./constants/common.js";import{GATED_MARKDOC_TAGS as R}from"./constants/entitlements.js";import{isObject as L}from"../utils/guards/is-object.js";import{mapObject as H}from"../utils/object/map-object.js";import{getValueDeep as y}from"../utils/object/get-value-deep.js";import{removeTrailingSlash as D}from"../utils/url/remove-trailing-slash.js";import{normalizeRouteSlug as h}from"../utils/path/normalize-route-slug.js";import{isLocalLink as S}from"../utils/path/is-local-link.js";import{reporter as w}from"./tools/notifiers/reporter.js";import{logger as u}from"./tools/notifiers/logger.js";import{sha1 as G}from"./utils/crypto/sha1.js";import{envConfig as _,setEnv as B}from"./config/env-config.js";import{KvService as F}from"./persistence/kv/services/kv-service.js";import{SandboxContext as I}from"./utils/sandbox-context.js";import{writeSharedData as N}from"./utils/index.js";import{renderComponents as V}from"./ssr/render.js";import{readStaticData as q,writeStaticData as U}from"./utils/static-data.js";import{getMarkdocOptions as j}from"./plugins/markdown/markdoc/markdoc-options.js";import{EntitlementsProvider as v}from"./entitlements/entitlements-provider.js";import{isL10nPath as x}from"./fs/utils/is-l10n-path.js";import{resolveGlobMapValue as K}from"./utils/globs.js";import{replaceEnvVariablesDeep as J}from"./utils/envs/replace-env-variables-deep.js";import{findRedirect as W}from"./utils/redirects/find-redirect.js";import{followRedirectChain as $}from"./utils/redirects/follow-redirect-chain.js";import{addWildcardRedirectToTree as z}from"./utils/redirects/add-wildcard-redirect-to-tree.js";import{telemetryTraceStep as Y}from"../cli/telemetry/helpers/trace-step.js";const E={routesBySlug:"map",apiRoutes:"object",middleware:"object",routesByFsPath:"map",routesSharedData:"map",globalData:"object",config:"object",ssr:"object",searchFacets:"map",routesPartials:"map",mcpToolHandlers:"map",skills:"map"},P="PLAN_GATES",Z=["OAUTH_CLIENT_ID","OAUTH_CLIENT_SECRET","ORGANIZATION_SLUG","ORGANIZATION_ID","ORG_ID"],Me="userDefinedApiFunctions";class b{routesBySlug=new Map;replacedEnvVars={};unsetEnvVars=new Set;lifecycleContext;newRoutes=[];#t={};routesByFsPath=new Map;apiRoutes=[];middleware=[];routesSharedData=new Map;sharedDataDeps=new Map;sharedDataMarkdocComponents=new Map;routesDynamicComponents=new Map;routesPartials=new Map;ssr={preBodyTags:[],postBodyTags:[],headTags:[]};searchFacets=new Map;searchEngine;templates=new Map;browserPlugins=new Set;apiRoutesRequestHandlers=new Map;mcpToolHandlers=new Map;skills=new Map;serverPropsGetters=new Map;pagePropsGetters=new Map;listeners=new Map;globalData={};#o=void 0;config={configFilePath:"",redirects:{},wildcardRedirectsTree:{},access:{rbac:{},requiresLogin:!1},directoryPermissions:{},devLogin:!1,ssoDirect:{}};#i;serverMode;serverOutDir;outdir;buildRevision=0;hasSitemap=!1;compilationErrors=[];#n;userCodeReady;#s=Promise.resolve();#c;#l=Promise.resolve();#d;#u=Promise.resolve();#h;#e=!1;#r=0;#f=0;#a=new Map;constructor({outdir:e,contentDir:s,serverMode:t=!1,serverOutDir:r}){this.#i=s,this.outdir=e,this.serverMode=t,this.serverOutDir=r,I.register({serverOutDir:r,readRoots:[e,process.cwd()]}),this.userCodeReady=new Promise(a=>{this.#n=a})}on(e,s){const t=this.listeners.get(e);t?t.add(s):this.listeners.set(e,new Set([s]))}queueEvent=(e,s,...t)=>{this.#a.set(e+String(s),[e,s,...t])};runListeners=(e,s,...t)=>{for(const r of this.listeners.get(e)||new Set)s?r(s,...t):r(...t)};startPluginsRun(){this.clear(),this.#s=new Promise(e=>{this.#c=e})}waitForPluginsLifecycle(){return Promise.all([this.#s,this.#l,this.#u])}waitForPluginsRun(){return this.#s}finishPluginsRun(){this.#c?.();for(const e of this.#a.values())this.runListeners(...e);this.#a.clear()}startEsbuildRun(){this.#l=new Promise(e=>{this.#d=e})}finishEsbuildRun(){this.#d?.()}markRequestHandlersEntryOutdated(){this.#r++,!this.#e&&(this.#e=!0,this.#u=new Promise(e=>{this.#h=e}))}noteRequestHandlersEntryBuildStart(){this.#f=this.#r}markRequestHandlersEntryUpdated(){this.#e&&(this.#r>this.#f||(this.#e=!1,this.#h?.()))}get contentDir(){if(this.serverMode)throw new Error("contentDir should not be used in server mode");return this.#i}markUserCodeReady(){this.#n?.(!0)}async reloadMarkdocOptions(){await Y("com.redocly.build.reloadMarkdocOptions",async()=>{const e=v.instance(),s=await j(this.serverOutDir),t=Object.fromEntries(Object.entries(s.tags).filter(([r])=>R[r]!=null?e.canAccessFeature(R[r]):!0));this.#o={...s,tags:t}})}get markdocOptions(){return{...this.#o,partials:this.getGlobalConfig(m),themeConfig:this.config.markdown}}setGlobalData=e=>{const s=this.globalData,t={...this.globalData,...e};this.globalData=t,JSON.stringify(t)!==JSON.stringify(s)&&this.queueEvent("global-data-updated",void 0,t)};getGlobalData=()=>this.globalData;getKv=async()=>F.getInstance({baseDbDir:this.serverOutDir});parseMarkdoc=async({input:e,context:s,deps:t,resource:r})=>{const{data:{info:a,ast:i},compoundHash:l}=await T(e,this.markdocOptions,{actions:this,context:s},r);for(const o of a.sharedDataDeps||[]){for(const n of t?.routeSlugs||[])this.addRouteSharedData(n,o,o);for(const n of t?.sharedDataIds||[]){const c=this.sharedDataDeps.get(n)||new Set;c.add(o),this.sharedDataDeps.set(n,c)}}for(const o of a.dynamicMarkdocComponents||[]){for(const n of t?.routeSlugs||[]){const c=this.routesDynamicComponents.get(n)||new Set;c.add(o),this.routesDynamicComponents.set(n,c)}for(const n of t?.sharedDataIds||[]){const c=this.sharedDataMarkdocComponents.get(n)||new Set;c.add(o),this.sharedDataMarkdocComponents.set(n,c)}}if(t?.routeSlugs&&a.partials?.length)for(const o of t.routeSlugs){const n=this.routesPartials.get(o)||[];for(const c of a.partials)n.includes(c)||n.push(c);this.routesPartials.set(o,n)}return{info:a,ast:i,compoundHash:l}};async loadApiDocuments(e){return(await e.cache.load(".","api-docs")).data}setSearchEngine(e){this.searchEngine=e}setSearchFacets=e=>{this.searchFacets=e};setGlobalConfig=e=>{const s=Object.keys(e);for(const l of s)for(const o in this.replacedEnvVars)if(o===l||o.startsWith(`${l}:`)){const n=o.split(":"),{error:c,value:d}=y(e,n);(c||d!==this.replacedEnvVars[o].replaced)&&delete this.replacedEnvVars[o]}const{resolvedObj:t,unsetEnvVars:r,replacedValues:a}=J(e);for(const l of r)this.unsetEnvVars.add(l);Object.assign(this.replacedEnvVars,a);const i=t.seo?.siteUrl?.trim();i&&(t.seo={...t.seo,siteUrl:URL.canParse(i)?new URL(i).origin:D(i)}),Object.assign(this.config,t)};getConfig=()=>this.config;getGlobalConfig=e=>this.config[e];getSearchFacets=()=>this.searchFacets;addRedirect=(e,s,t={})=>{if(!v.instance().canAccessFeature("redirects")&&e!=="/")return;this.config.redirects||(this.config.redirects={});const i=h(e).toLowerCase();this.config.redirects[i]=s;const{trackOriginalSource:l=!0}=t,o=this.getGlobalConfig("originalRedirectSources");l&&Array.isArray(o)&&!o.includes(i)&&o.push(i),i.endsWith("*")&&z(this.config.wildcardRedirectsTree,i)};getRedirect=e=>{const s=h(e).toLowerCase(),t=W(s,this.config.redirects,this.config.wildcardRedirectsTree);if(!t)return null;if(S(t.to)){const r=h(t.to).toLowerCase();if(!r.endsWith("*")&&$(r,[s],this.config.redirects,this.config.wildcardRedirectsTree).type==="cycle")return null}return{to:t.to,type:t.type}};createSharedData=async(e,s,t)=>{if(t&&this.#t[e]===t)return e;const r=JSON.stringify(s),a=t??G(r);return this.#t[e]===a||(this.#t[e]=a,await N(e,r,this.outdir),this.queueEvent("shared-data-updated",e)),e};addRouteSharedData=(e,s,t)=>{const r=D(e),a=this.routesSharedData.get(r)||{};a[s]=t,this.routesSharedData.set(r,a),u.verbose(`Adding shared data to ${e}, ${s}, ${t}`)};getRouteSharedDataByFsPath=e=>{const s=this.routesByFsPath.get(e);return s?this.routesSharedData.get(s)||{}:{}};getPartialsForRoute=e=>{const s=this.getGlobalConfig(m)||{},t=this.routesPartials.get(e);if(!t||t.length===0)return{};const r=this.getGlobalConfig(k)||{},a=new Set(t),i=Array.from(t);for(let o=0;o<i.length;o++){const n=i[o],c=r[n]?.partials??[];for(const d of c)a.has(d)||(a.add(d),i.push(d))}const l={};for(const o of a)s[o]&&(l[o]=s[o]);return l};addRoute=e=>{const t={...K(e.fsPath,this.config.metadataGlobs),...e.metadata||{}};this.newRoutes.push({...e,metadata:t}),u.verbose("Created route %s",e.slug)};addRouteSharedDataToAllLocales=(e,s,t)=>{const r=[g,...this.lifecycleContext?.fs.localeFolders||[]].map(a=>({code:a,name:a}));for(const a of r){const i=M(e,g,a.code,r);this.addRouteSharedData(i,s,t)}};addApiRoute=e=>{this.apiRoutes.push(e),u.verbose("Created API route %s",e.slug)};addMcpTools=(e,s)=>{for(const t of s){const r=this.mcpToolHandlers.get(t.name);r&&r.importPath!==e&&u.warn('MCP tool "%s" from %s overwrites the tool registered from %s',t.name,e,r.importPath),this.mcpToolHandlers.set(t.name,{...t,importPath:e}),u.verbose("Created MCP tool %s",t.name)}};getMcpTools=()=>Array.from(this.mcpToolHandlers.values());markGatedMcpTools=e=>{for(const s of e){const t=this.mcpToolHandlers.get(s);t&&this.mcpToolHandlers.set(s,{...t,hasAvailabilityGate:!0})}};addSkills=e=>{for(const s of e)this.skills.set(s.slug,s),u.verbose("Discovered skill %s",s.slug)};getSkills=()=>Array.from(this.skills.values());removeMcpTools=e=>{for(const s of e)this.mcpToolHandlers.delete(s)};addMiddleware=e=>{this.middleware.push(e),u.verbose("Created middleware %s",e.id)};setResourceResponseHeaders=(e,s)=>{this.config.responseHeaders||(this.config.responseHeaders={});const t=new Set(s.map(i=>i.name.toLowerCase())),a=[...(this.config.responseHeaders[e]??[]).filter(i=>!t.has(i.name.toLowerCase())),...s];this.config.responseHeaders[e]=a};getRouteByFsPath=e=>{const s=this.routesByFsPath.get(e);return s?this.getRouteBySlug(s):void 0};getRouteBySlug=(e,s={})=>{const{followRedirect:t=!0}=s,r=this.getRedirect(e);return t&&r?this.routesBySlug.get(h(r.to)):this.routesBySlug.get(e)};hasRouteOrRedirectBySlug=e=>{if(this.routesBySlug.has(e))return!0;const s=this.getRedirect(e);if(!s)return!1;if(!S(s.to))return!0;const t=h(s.to);return this.routesBySlug.has(t)};getRoutesByTemplateId=e=>this.newRoutes.filter(s=>s.templateId===e);getAllRoutesForLocale=(e=g)=>{const s=Array.from(this.routesBySlug.values()),t=e.toLowerCase();return s.filter(r=>e===g?!x(r.fsPath):r.slug.startsWith(`/${t}`))};getAllRoutes=()=>Array.from(this.routesBySlug.values());getAllApiRoutes=()=>this.apiRoutes;getAllMiddleware=()=>this.middleware;getTemplate=e=>this.templates.get(e);getRequestHandler=e=>this.apiRoutesRequestHandlers.get(e);createTemplate=(e,s)=>(this.templates.set(e,s),e);addBrowserPlugin=e=>{this.browserPlugins.add(e)};createRequestHandler=(e,s)=>(this.apiRoutesRequestHandlers.set(e,s),e);clearRequestHandlersByPrefix=e=>{for(const s of this.apiRoutesRequestHandlers.keys())s.startsWith(e)&&this.apiRoutesRequestHandlers.delete(s)};registerServerPropsGetter=(e,s)=>(this.serverPropsGetters.set(e,s),e);registerPagePropsGetter=(e,s)=>{this.pagePropsGetters.set(e,s)};async writeRouteStaticData(e,s){const t=await this.resolveRouteStaticData(e,s,!1);t&&U(e.slug,t,this.outdir)}async resolveRouteStaticData(e,s,t){if(this.serverMode)return q(e.slug,this.outdir);const r={...this,contentDir:this.contentDir,parseMarkdoc:({input:c,context:d,resource:f})=>this.parseMarkdoc({input:c,context:d,deps:{routeSlugs:[e.slug]},resource:f})},a=await e.getStaticData?.(e,r)||{},i=new Set(this.routesDynamicComponents.get(e.slug)),l=this.routesSharedData.get(e.slug)||{};for(const c of Object.values(l)){const d=this.sharedDataMarkdocComponents.get(c);d&&d.forEach(p=>i.add(p));const f=this.sharedDataDeps.get(c);f&&f.forEach(p=>this.addRouteSharedData(e.slug,p,p))}const o=this.getGlobalConfig("seo"),n=a?.frontmatter||{};return{...a,frontmatter:{...n,seo:{...n?.seo,title:n?.seo?.title||await e.getNavText?.()}},props:{...a.props,dynamicMarkdocComponents:Array.from(i),metadata:{...a?.props?.metadata,...e.metadata},seo:{title:O,...o,...a.props?.seo,siteUrl:o?.siteUrl},compilationErrors:this.compilationErrors},lastModified:t||!e.fsPath?null:await this.lifecycleContext?.fs.getLastModified(e.fsPath)}}addSsrComponents(e,s){if(!e?.length)return;const t=typeof e[0]=="string"?e.join(""):V(e);t&&(s==="head"?this.ssr.headTags.push(t):s==="preBody"?this.ssr.preBodyTags.push(t):this.ssr.postBodyTags.push(t))}clear=()=>{this.routesByFsPath.clear(),this.templates.clear(),this.newRoutes=[],this.routesBySlug.clear(),this.apiRoutes=[],this.middleware=[],this.routesSharedData.clear(),this.sharedDataDeps.clear(),this.sharedDataMarkdocComponents.clear(),this.routesDynamicComponents.clear(),this.routesPartials.clear(),this.skills.clear(),this.config.redirects={},this.config.wildcardRedirectsTree={},this.config.directoryPermissions={},this.ssr={preBodyTags:[],postBodyTags:[],headTags:[]}};async toJson(){const e=[];for(const[t,r]of Object.entries(E))switch(r){case"map":{const a=Array.from(this[t].entries());e.push([t,a]);break}case"object":if(t==="config"){e.push([t,await this.getConfigWithEnvPlaceholders()]);break}e.push([t,this[t]]);break;default:throw new Error("Invalid format")}const s=Object.fromEntries(e);return s[P]=_.PLAN_GATES,s}static fromJson(e,s){const t=new b(s);for(const[a,i]of Object.entries(E))switch(i){case"map":t[a]=new Map(e[a]);break;case"object":if(a==="config"){t.setGlobalConfig(e[a]);break}t[a]=e[a];break;default:throw new Error("Invalid format")}t.config[m]=Q(t.config[m]||{});const r=e[P];return r&&B("PLAN_GATES",r),t}async getConfigWithEnvPlaceholders(){const e=JSON.parse(JSON.stringify(this.config));for(const s in this.replacedEnvVars){const{original:t}=this.replacedEnvVars[s],r=s.split(":"),a=r.pop(),{error:i,value:l}=y(e,r);if(i||!L(l)&&!Array.isArray(l)){await w.panicOnBuild(`Failed to replace env var with env name for ${s}`);continue}l[a]=t}return e}async reportUnsetEnvVars(){if(this.unsetEnvVars.size===0)return;const e=Array.from(this.unsetEnvVars).filter(t=>!Z.includes(t));if(e.length===0)return;const s=`Failed to resolve config. The following environment variables are not set: ${e.join(", ")}`;await w.panicOnBuildContentError(s)}}function Q(C){return H(C,e=>A.Ast.fromJSON(JSON.stringify(e)))}export{b as Store,Me as USER_DEFINED_API_FUNCTIONS_COUNTER_KEY};
|
|
1
|
+
import A from"@markdoc/markdoc";import{parseAndResolveMarkdoc as T}from"./plugins/markdown/compiler.js";import{getPathnameForLocale as M}from"@redocly/theme/core/utils";import{DEFAULT_LOCALE_PLACEHOLDER as g}from"../constants/common.js";import{DEFAULT_TITLE as O,MARKDOC_PARTIALS_DATA_KEY as m,MARKDOC_PARTIALS_DEPS_KEY as k}from"./constants/common.js";import{GATED_MARKDOC_TAGS as R}from"./constants/entitlements.js";import{isObject as L}from"../utils/guards/is-object.js";import{mapObject as H}from"../utils/object/map-object.js";import{getValueDeep as y}from"../utils/object/get-value-deep.js";import{removeTrailingSlash as D}from"../utils/url/remove-trailing-slash.js";import{normalizeRouteSlug as h}from"../utils/path/normalize-route-slug.js";import{isLocalLink as S}from"../utils/path/is-local-link.js";import{reporter as w}from"./tools/notifiers/reporter.js";import{logger as u}from"./tools/notifiers/logger.js";import{sha1 as G}from"./utils/crypto/sha1.js";import{envConfig as _,setEnv as B}from"./config/env-config.js";import{KvService as F}from"./persistence/kv/services/kv-service.js";import{SandboxContext as I}from"./utils/sandbox-context.js";import{writeSharedData as N}from"./utils/index.js";import{renderComponents as V}from"./ssr/render.js";import{readStaticData as q,writeStaticData as U}from"./utils/static-data.js";import{getMarkdocOptions as j}from"./plugins/markdown/markdoc/markdoc-options.js";import{EntitlementsProvider as v}from"./entitlements/entitlements-provider.js";import{isRouteReserved as x}from"./plugins/get-reserved-routes.js";import{isL10nPath as K}from"./fs/utils/is-l10n-path.js";import{resolveGlobMapValue as J}from"./utils/globs.js";import{replaceEnvVariablesDeep as W}from"./utils/envs/replace-env-variables-deep.js";import{findRedirect as $}from"./utils/redirects/find-redirect.js";import{followRedirectChain as z}from"./utils/redirects/follow-redirect-chain.js";import{addWildcardRedirectToTree as Y}from"./utils/redirects/add-wildcard-redirect-to-tree.js";import{telemetryTraceStep as Z}from"../cli/telemetry/helpers/trace-step.js";const E={routesBySlug:"map",apiRoutes:"object",middleware:"object",routesByFsPath:"map",routesSharedData:"map",globalData:"object",config:"object",ssr:"object",searchFacets:"map",routesPartials:"map",mcpToolHandlers:"map",skills:"map"},P="PLAN_GATES",Q=["OAUTH_CLIENT_ID","OAUTH_CLIENT_SECRET","ORGANIZATION_SLUG","ORGANIZATION_ID","ORG_ID"],ke="userDefinedApiFunctions";class b{routesBySlug=new Map;replacedEnvVars={};unsetEnvVars=new Set;lifecycleContext;newRoutes=[];#t={};routesByFsPath=new Map;apiRoutes=[];middleware=[];routesSharedData=new Map;sharedDataDeps=new Map;sharedDataMarkdocComponents=new Map;routesDynamicComponents=new Map;routesPartials=new Map;ssr={preBodyTags:[],postBodyTags:[],headTags:[]};searchFacets=new Map;searchEngine;templates=new Map;browserPlugins=new Set;apiRoutesRequestHandlers=new Map;mcpToolHandlers=new Map;skills=new Map;serverPropsGetters=new Map;pagePropsGetters=new Map;listeners=new Map;globalData={};#a=void 0;config={configFilePath:"",redirects:{},wildcardRedirectsTree:{},access:{rbac:{},requiresLogin:!1},directoryPermissions:{},devLogin:!1,ssoDirect:{}};#i;serverMode;serverOutDir;outdir;buildRevision=0;hasSitemap=!1;compilationErrors=[];#n;userCodeReady;#s=Promise.resolve();#c;#l=Promise.resolve();#d;#u=Promise.resolve();#h;#e=!1;#r=0;#f=0;#o=new Map;constructor({outdir:e,contentDir:s,serverMode:t=!1,serverOutDir:r}){this.#i=s,this.outdir=e,this.serverMode=t,this.serverOutDir=r,I.register({serverOutDir:r,readRoots:[e,process.cwd()]}),this.userCodeReady=new Promise(o=>{this.#n=o})}on(e,s){const t=this.listeners.get(e);t?t.add(s):this.listeners.set(e,new Set([s]))}queueEvent=(e,s,...t)=>{this.#o.set(e+String(s),[e,s,...t])};runListeners=(e,s,...t)=>{for(const r of this.listeners.get(e)||new Set)s?r(s,...t):r(...t)};startPluginsRun(){this.clear(),this.#s=new Promise(e=>{this.#c=e})}waitForPluginsLifecycle(){return Promise.all([this.#s,this.#l,this.#u])}waitForPluginsRun(){return this.#s}finishPluginsRun(){this.#c?.();for(const e of this.#o.values())this.runListeners(...e);this.#o.clear()}startEsbuildRun(){this.#l=new Promise(e=>{this.#d=e})}finishEsbuildRun(){this.#d?.()}markRequestHandlersEntryOutdated(){this.#r++,!this.#e&&(this.#e=!0,this.#u=new Promise(e=>{this.#h=e}))}noteRequestHandlersEntryBuildStart(){this.#f=this.#r}markRequestHandlersEntryUpdated(){this.#e&&(this.#r>this.#f||(this.#e=!1,this.#h?.()))}get contentDir(){if(this.serverMode)throw new Error("contentDir should not be used in server mode");return this.#i}markUserCodeReady(){this.#n?.(!0)}async reloadMarkdocOptions(){await Z("com.redocly.build.reloadMarkdocOptions",async()=>{const e=v.instance(),s=await j(this.serverOutDir),t=Object.fromEntries(Object.entries(s.tags).filter(([r])=>R[r]!=null?e.canAccessFeature(R[r]):!0));this.#a={...s,tags:t}})}get markdocOptions(){return{...this.#a,partials:this.getGlobalConfig(m),themeConfig:this.config.markdown}}setGlobalData=e=>{const s=this.globalData,t={...this.globalData,...e};this.globalData=t,JSON.stringify(t)!==JSON.stringify(s)&&this.queueEvent("global-data-updated",void 0,t)};getGlobalData=()=>this.globalData;getKv=async()=>F.getInstance({baseDbDir:this.serverOutDir});parseMarkdoc=async({input:e,context:s,deps:t,resource:r})=>{const{data:{info:o,ast:i},compoundHash:l}=await T(e,this.markdocOptions,{actions:this,context:s},r);for(const a of o.sharedDataDeps||[]){for(const n of t?.routeSlugs||[])this.addRouteSharedData(n,a,a);for(const n of t?.sharedDataIds||[]){const c=this.sharedDataDeps.get(n)||new Set;c.add(a),this.sharedDataDeps.set(n,c)}}for(const a of o.dynamicMarkdocComponents||[]){for(const n of t?.routeSlugs||[]){const c=this.routesDynamicComponents.get(n)||new Set;c.add(a),this.routesDynamicComponents.set(n,c)}for(const n of t?.sharedDataIds||[]){const c=this.sharedDataMarkdocComponents.get(n)||new Set;c.add(a),this.sharedDataMarkdocComponents.set(n,c)}}if(t?.routeSlugs&&o.partials?.length)for(const a of t.routeSlugs){const n=this.routesPartials.get(a)||[];for(const c of o.partials)n.includes(c)||n.push(c);this.routesPartials.set(a,n)}return{info:o,ast:i,compoundHash:l}};async loadApiDocuments(e){return(await e.cache.load(".","api-docs")).data}setSearchEngine(e){this.searchEngine=e}setSearchFacets=e=>{this.searchFacets=e};setGlobalConfig=e=>{const s=Object.keys(e);for(const l of s)for(const a in this.replacedEnvVars)if(a===l||a.startsWith(`${l}:`)){const n=a.split(":"),{error:c,value:d}=y(e,n);(c||d!==this.replacedEnvVars[a].replaced)&&delete this.replacedEnvVars[a]}const{resolvedObj:t,unsetEnvVars:r,replacedValues:o}=W(e);for(const l of r)this.unsetEnvVars.add(l);Object.assign(this.replacedEnvVars,o);const i=t.seo?.siteUrl?.trim();i&&(t.seo={...t.seo,siteUrl:URL.canParse(i)?new URL(i).origin:D(i)}),Object.assign(this.config,t)};getConfig=()=>this.config;getGlobalConfig=e=>this.config[e];getSearchFacets=()=>this.searchFacets;addRedirect=(e,s,t={})=>{if(!v.instance().canAccessFeature("redirects")&&e!=="/")return;this.config.redirects||(this.config.redirects={});const i=h(e).toLowerCase();this.config.redirects[i]=s;const{trackOriginalSource:l=!0}=t,a=this.getGlobalConfig("originalRedirectSources");l&&Array.isArray(a)&&!a.includes(i)&&a.push(i),i.endsWith("*")&&Y(this.config.wildcardRedirectsTree,i)};getRedirect=e=>{const s=h(e).toLowerCase(),t=$(s,this.config.redirects,this.config.wildcardRedirectsTree);if(!t)return null;if(S(t.to)){const r=h(t.to).toLowerCase();if(!r.endsWith("*")&&z(r,[s],this.config.redirects,this.config.wildcardRedirectsTree).type==="cycle")return null}return{to:t.to,type:t.type}};createSharedData=async(e,s,t)=>{if(t&&this.#t[e]===t)return e;const r=JSON.stringify(s),o=t??G(r);return this.#t[e]===o||(this.#t[e]=o,await N(e,r,this.outdir),this.queueEvent("shared-data-updated",e)),e};addRouteSharedData=(e,s,t)=>{const r=D(e),o=this.routesSharedData.get(r)||{};o[s]=t,this.routesSharedData.set(r,o),u.verbose(`Adding shared data to ${e}, ${s}, ${t}`)};getRouteSharedDataByFsPath=e=>{const s=this.routesByFsPath.get(e);return s?this.routesSharedData.get(s)||{}:{}};getPartialsForRoute=e=>{const s=this.getGlobalConfig(m)||{},t=this.routesPartials.get(e);if(!t||t.length===0)return{};const r=this.getGlobalConfig(k)||{},o=new Set(t),i=Array.from(t);for(let a=0;a<i.length;a++){const n=i[a],c=r[n]?.partials??[];for(const d of c)o.has(d)||(o.add(d),i.push(d))}const l={};for(const a of o)s[a]&&(l[a]=s[a]);return l};addRoute=e=>{const t={...J(e.fsPath,this.config.metadataGlobs),...e.metadata||{}};this.newRoutes.push({...e,metadata:t}),u.verbose("Created route %s",e.slug)};addRouteSharedDataToAllLocales=(e,s,t)=>{const r=[g,...this.lifecycleContext?.fs.localeFolders||[]].map(o=>({code:o,name:o}));for(const o of r){const i=M(e,g,o.code,r);this.addRouteSharedData(i,s,t)}};addApiRoute=e=>{this.apiRoutes.push(e),u.verbose("Created API route %s",e.slug)};addMcpTools=(e,s)=>{for(const t of s){const r=this.mcpToolHandlers.get(t.name);r&&r.importPath!==e&&u.warn('MCP tool "%s" from %s overwrites the tool registered from %s',t.name,e,r.importPath),this.mcpToolHandlers.set(t.name,{...t,importPath:e}),u.verbose("Created MCP tool %s",t.name)}};getMcpTools=()=>Array.from(this.mcpToolHandlers.values());markGatedMcpTools=e=>{for(const s of e){const t=this.mcpToolHandlers.get(s);t&&this.mcpToolHandlers.set(s,{...t,hasAvailabilityGate:!0})}};addSkills=e=>{for(const s of e)this.skills.set(s.slug,s),u.verbose("Discovered skill %s",s.slug)};getSkills=()=>Array.from(this.skills.values());removeMcpTools=e=>{for(const s of e)this.mcpToolHandlers.delete(s)};addMiddleware=e=>{this.middleware.push(e),u.verbose("Created middleware %s",e.id)};setResourceResponseHeaders=(e,s)=>{this.config.responseHeaders||(this.config.responseHeaders={});const t=new Set(s.map(i=>i.name.toLowerCase())),o=[...(this.config.responseHeaders[e]??[]).filter(i=>!t.has(i.name.toLowerCase())),...s];this.config.responseHeaders[e]=o};getRouteByFsPath=e=>{const s=this.routesByFsPath.get(e);return s?this.getRouteBySlug(s):void 0};getRouteBySlug=(e,s={})=>{const{followRedirect:t=!0}=s,r=this.getRedirect(e);return t&&r?this.routesBySlug.get(h(r.to)):this.routesBySlug.get(e)};hasRouteOrRedirectBySlug=e=>{if(this.routesBySlug.has(e)||x(e,this.getConfig()))return!0;const s=this.getRedirect(e);if(!s)return!1;if(!S(s.to))return!0;const t=h(s.to);return this.routesBySlug.has(t)};getRoutesByTemplateId=e=>this.newRoutes.filter(s=>s.templateId===e);getAllRoutesForLocale=(e=g)=>{const s=Array.from(this.routesBySlug.values()),t=e.toLowerCase();return s.filter(r=>e===g?!K(r.fsPath):r.slug.startsWith(`/${t}`))};getAllRoutes=()=>Array.from(this.routesBySlug.values());getAllApiRoutes=()=>this.apiRoutes;getAllMiddleware=()=>this.middleware;getTemplate=e=>this.templates.get(e);getRequestHandler=e=>this.apiRoutesRequestHandlers.get(e);createTemplate=(e,s)=>(this.templates.set(e,s),e);addBrowserPlugin=e=>{this.browserPlugins.add(e)};createRequestHandler=(e,s)=>(this.apiRoutesRequestHandlers.set(e,s),e);clearRequestHandlersByPrefix=e=>{for(const s of this.apiRoutesRequestHandlers.keys())s.startsWith(e)&&this.apiRoutesRequestHandlers.delete(s)};registerServerPropsGetter=(e,s)=>(this.serverPropsGetters.set(e,s),e);registerPagePropsGetter=(e,s)=>{this.pagePropsGetters.set(e,s)};async writeRouteStaticData(e,s){const t=await this.resolveRouteStaticData(e,s,!1);t&&U(e.slug,t,this.outdir)}async resolveRouteStaticData(e,s,t){if(this.serverMode)return q(e.slug,this.outdir);const r={...this,contentDir:this.contentDir,parseMarkdoc:({input:c,context:d,resource:f})=>this.parseMarkdoc({input:c,context:d,deps:{routeSlugs:[e.slug]},resource:f})},o=await e.getStaticData?.(e,r)||{},i=new Set(this.routesDynamicComponents.get(e.slug)),l=this.routesSharedData.get(e.slug)||{};for(const c of Object.values(l)){const d=this.sharedDataMarkdocComponents.get(c);d&&d.forEach(p=>i.add(p));const f=this.sharedDataDeps.get(c);f&&f.forEach(p=>this.addRouteSharedData(e.slug,p,p))}const a=this.getGlobalConfig("seo"),n=o?.frontmatter||{};return{...o,frontmatter:{...n,seo:{...n?.seo,title:n?.seo?.title||await e.getNavText?.()}},props:{...o.props,dynamicMarkdocComponents:Array.from(i),metadata:{...o?.props?.metadata,...e.metadata},seo:{title:O,...a,...o.props?.seo,siteUrl:a?.siteUrl},compilationErrors:this.compilationErrors},lastModified:t||!e.fsPath?null:await this.lifecycleContext?.fs.getLastModified(e.fsPath)}}addSsrComponents(e,s){if(!e?.length)return;const t=typeof e[0]=="string"?e.join(""):V(e);t&&(s==="head"?this.ssr.headTags.push(t):s==="preBody"?this.ssr.preBodyTags.push(t):this.ssr.postBodyTags.push(t))}clear=()=>{this.routesByFsPath.clear(),this.templates.clear(),this.newRoutes=[],this.routesBySlug.clear(),this.apiRoutes=[],this.middleware=[],this.routesSharedData.clear(),this.sharedDataDeps.clear(),this.sharedDataMarkdocComponents.clear(),this.routesDynamicComponents.clear(),this.routesPartials.clear(),this.skills.clear(),this.config.redirects={},this.config.wildcardRedirectsTree={},this.config.directoryPermissions={},this.ssr={preBodyTags:[],postBodyTags:[],headTags:[]}};async toJson(){const e=[];for(const[t,r]of Object.entries(E))switch(r){case"map":{const o=Array.from(this[t].entries());e.push([t,o]);break}case"object":if(t==="config"){e.push([t,await this.getConfigWithEnvPlaceholders()]);break}e.push([t,this[t]]);break;default:throw new Error("Invalid format")}const s=Object.fromEntries(e);return s[P]=_.PLAN_GATES,s}static fromJson(e,s){const t=new b(s);for(const[o,i]of Object.entries(E))switch(i){case"map":t[o]=new Map(e[o]);break;case"object":if(o==="config"){t.setGlobalConfig(e[o]);break}t[o]=e[o];break;default:throw new Error("Invalid format")}t.config[m]=X(t.config[m]||{});const r=e[P];return r&&B("PLAN_GATES",r),t}async getConfigWithEnvPlaceholders(){const e=JSON.parse(JSON.stringify(this.config));for(const s in this.replacedEnvVars){const{original:t}=this.replacedEnvVars[s],r=s.split(":"),o=r.pop(),{error:i,value:l}=y(e,r);if(i||!L(l)&&!Array.isArray(l)){await w.panicOnBuild(`Failed to replace env var with env name for ${s}`);continue}l[o]=t}return e}async reportUnsetEnvVars(){if(this.unsetEnvVars.size===0)return;const e=Array.from(this.unsetEnvVars).filter(t=>!Q.includes(t));if(e.length===0)return;const s=`Failed to resolve config. The following environment variables are not set: ${e.join(", ")}`;await w.panicOnBuildContentError(s)}}function X(C){return H(C,e=>A.Ast.fromJSON(JSON.stringify(e)))}export{b as Store,ke as USER_DEFINED_API_FUNCTIONS_COUNTER_KEY};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{maskEmail as
|
|
1
|
+
import{maskEmail as c}from"./mask-email.js";const r=/[a-zA-Z0-9._%+-]/,m=/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/,d=e=>{let o="",i=0,a=0;for(;a<e.length;){const n=e.indexOf("@",a);if(n===-1)break;let t=n;for(;t>i&&r.test(e.charAt(t-1));)t--;const s=t<n?m.exec(e.slice(n+1))?.[0]:void 0;if(!s){a=n+1;continue}const l=n+1+s.length;o+=e.slice(i,t)+c(e.slice(t,l)),i=l,a=l}return o+e.slice(i)};export{d as maskSubject};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isString as m}from"../../../../../utils/string/is-string.js";const s={email:{pattern:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,replacement:"[email_removed]"},phone:{pattern:/(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})(?:\s?(?:ext|x|extension)\.?\s?(\d+))?/g,replacement:"[phone_removed]"},ssn:{pattern:/\b(?!000|666|9\d{2})\d{3}-?(?!00)\d{2}-?(?!0000)\d{4}\b/g,replacement:"[SSN_removed]"},creditCard:{pattern:/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,replacement:"[credit_card_removed]"},address:{pattern:/\b\d+\s
|
|
1
|
+
import{isString as m}from"../../../../../utils/string/is-string.js";const s={email:{pattern:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,replacement:"[email_removed]"},phone:{pattern:/(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})(?:\s?(?:ext|x|extension)\.?\s?(\d+))?/g,replacement:"[phone_removed]"},ssn:{pattern:/\b(?!000|666|9\d{2})\d{3}-?(?!00)\d{2}-?(?!0000)\d{4}\b/g,replacement:"[SSN_removed]"},creditCard:{pattern:/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,replacement:"[credit_card_removed]"},address:{pattern:/\b\d+\s[A-Za-z0-9\s,.-]+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Court|Ct|Circle|Cir|Way|Place|Pl|Parkway|Pkwy|Terrace|Ter)\b/gi,replacement:"[address_removed]"},passport:{pattern:/\b[A-Z]{2}[0-9]{7}\b/g,replacement:"[passport_number_removed]"},driversLicense:{pattern:/\b[A-Z]{1,2}[0-9]{6,9}\b/g,replacement:"[driver_license_removed]"},zipCode:{pattern:/(?<!\d-)\b\d{5}(?:-\d{4})?\b(?!-\d)/g,replacement:"[zip_code_removed]"},bankAccount:{pattern:/\b\d{8,17}\b/g,replacement:"[bank_account_removed]"},dateOfBirth:{pattern:/\b(?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01])[-/](?:19|20)\d{2}\b/g,replacement:"[date_removed]"}};function l(n={}){const e={email:{enabled:!0,replacement:"[email_removed]"},phone:{enabled:!0,replacement:"[phone_removed]"},ssn:{enabled:!0,replacement:"[SSN_removed]"},creditCard:{enabled:!0,replacement:"[credit_card_removed]"},address:{enabled:!0,replacement:"[address_removed]"},passport:{enabled:!0,replacement:"[passport_number_removed]"},driversLicense:{enabled:!0,replacement:"[driver_license_removed]"},zipCode:{enabled:!0,replacement:"[zip_code_removed]"},bankAccount:{enabled:!0,replacement:"[bank_account_removed]"},dateOfBirth:{enabled:!0,replacement:"[date_removed]"}};for(const[r,t]of Object.entries(n))e[r]?e[r]={...e[r],...t}:e[r]=t;return e}function b(n,e={}){if(!m(n))return n;const r=l(e);let t=n;for(const[c,a]of Object.entries(r)){if(!a.enabled)continue;const d=s[c],o=d?.pattern||a.pattern,p=a.replacement||d?.replacement||"[PII removed]";o&&(t=t.replace(o,p))}return t}export{b as removePii};
|
|
@@ -31,7 +31,7 @@ export type SearchIndex = {
|
|
|
31
31
|
add(document: SearchDocument): void;
|
|
32
32
|
search(params: SearchParameters): Promise<SearchResponse>;
|
|
33
33
|
export(handler: ExportHandler<string | IndexedSearchDocument>): void;
|
|
34
|
-
import(
|
|
34
|
+
import(entries: AsyncIterable<FlexSearchDataEntry>): Promise<void>;
|
|
35
35
|
clearDocuments(): void;
|
|
36
36
|
};
|
|
37
37
|
export type AddDocumentsParams = {
|
|
@@ -39,9 +39,11 @@ export type AddDocumentsParams = {
|
|
|
39
39
|
locale: string;
|
|
40
40
|
outDir: string;
|
|
41
41
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/** One line of an exported FlexSearch group file: a stored document or one FlexSearch export key. */
|
|
43
|
+
export type FlexSearchDataEntry = {
|
|
44
|
+
document: [number, IndexedSearchDocument];
|
|
45
|
+
} | {
|
|
46
|
+
index: [string, string];
|
|
45
47
|
};
|
|
46
48
|
export type TypesenseSearchData = {
|
|
47
49
|
documents: SearchDocument[];
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Id of the shared data that holds a classic catalog's resolved items; `localeIdSuffix` is empty for the default locale. */
|
|
2
|
+
export declare function getCatalogSharedDataId(catalogId: string, localeIdSuffix?: string): string;
|
|
3
|
+
//# sourceMappingURL=get-catalog-shared-data-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function e(a,t=""){return`catalog-${a}${t}`}export{e as getCatalogSharedDataId};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readEnvVariable as d}from"./read-env-variable.js";const i=/{{\s*process\.env\.([A-Z0-9_]+)\s*(?:\|\|?\s*(?:"([^"]*)"|'([^']*)')
|
|
1
|
+
import{readEnvVariable as d}from"./read-env-variable.js";const i=/{{\s*process\.env\.([A-Z0-9_]+)\s*(?:\|\|?\s*(?:"([^"]*)"|'([^']*)')\s*)?}}/gi;function V(s){let e=s;const t=new Set,r=new Set,c=[...s.matchAll(i)];for(const n of c){const a=n[1].toUpperCase(),o=n[2]??n[3],l=d(a);l!==void 0?(e=e.replace(n[0],l),r.add(a)):o!==void 0?(e=e.replace(n[0],o),r.add(a)):t.add(a)}return{interpolatedValue:e,unsetVars:Array.from(t),interpolatedVars:Array.from(r)}}export{V as interpolateEnvVariables};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import R from"path";import P from"picomatch";import"../node-crypto-polyfill.js";import{REDOCLY_TEAMS_RBAC as L,REDOCLY_ROUTE_RBAC as
|
|
1
|
+
import R from"path";import P from"picomatch";import"../node-crypto-polyfill.js";import{REDOCLY_TEAMS_RBAC as L,REDOCLY_ROUTE_RBAC as y}from"@redocly/config";import{MCP_ROUTE as w,PUBLIC_MCP_ROUTE as U}from"@redocly/theme/core/constants/mcp";import{ServerRoutes as M}from"../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as g,PUBLIC_RBAC_SCOPE_ITEM as E,RBAC_ALL_OTHER_TEAMS as l,DEFAULT_RBAC_SCOPE as C}from"../../constants/common.js";import{DEPRECATED_PUBLIC_API_DEFINITIONS_FOLDER as j,PUBLIC_API_DEFINITIONS_FOLDER as I,PUBLIC_ASSETS_FOLDER as B}from"../constants/common.js";import{MCP_CREDENTIALS_ROUTE as b}from"../constants/mcp.js";import{ANONYMOUS_AUTH_DETAILS as W}from"../constants/rbac.js";import{removeTrailingSlash as _}from"../../utils/url/remove-trailing-slash.js";import{removeLeadingSlash as $}from"../../utils/url/remove-leading-slash.js";import{parsePathVersions as v}from"../../utils/path/parse-path-versions.js";import{reporter as Y}from"../tools/notifiers/reporter.js";import{bold as z}from"../tools/notifiers/helpers/colors.js";import{shaDirPathShort as H}from"../utils/crypto/sha-dir-path-short.js";import{isTruthy as K}from"../../utils/guards/is-truthy.js";import{canExpandConfig as G,expandRbacConfig as V,getTeamFolderDefaults as J,parseTeamFoldersTemplate as Q,parseTeamNameTemplate as X}from"./rbac-expand.js";import{getUserParamsFromCookies as Z}from"../web-server/auth.js";import{getDeeperGlobPattern as q}from"./globs.js";import{EntitlementsProvider as tt}from"../entitlements/entitlements-provider.js";const F=["NONE","READ","TRIAGE","WRITE","MAINTAIN","ADMIN"],et=new Set(["x-parsed-md-description","x-parsed-md-summary"]),rt=new Set(Object.values(M).map(t=>_(t.split(":")[0])));function jt(t,e){const r=F.indexOf(t.toUpperCase()),n=F.indexOf(e.toUpperCase());return r>n?t:e}const A={};function T(t,e){if(!t?.content)return E;const r=t.content,{slug:n,fsPath:o}=e;if(!n&&!o)return E;const s=a=>{const d=`slug:${a}`,p=A[d]??P(a);A[d]=p;const S=`fsPath:${a}`,D=A[S]??P($(a));return A[S]=D,!!(n&&p(n))||!!(o&&D(o))};if(st(n||o||"")&&Object.keys(r).filter(p=>s(p)).length===0)return r[C]||E;const f=Object.keys(r).filter(a=>s(a));if(f.length==0)return E;const u=f.map(a=>P.scan(a,{tokens:!0,parts:!0}));let h=u[0];for(let a=1;a<u.length;a++)h=q(h,u[a]);return r[h.input]}function N(t,e,r){const n=r?[...e,r]:e,o=[];for(const s of n)t[s]?o.push(t[s]):t[l]&&s!==r&&o.push(t[l]);return o.some(s=>s.toLowerCase()!=="none")}function It(t,e,r={},n=!1){if(n&&Object.keys(r).length===0)return e.isAuthenticated;const o=r.features?.[t];return o?N(o,e.teams):!0}function Ft(t,e){return O(t,W,e.access?.rbac||{},e.access?.requiresLogin||!1)}function O(t,e={},r={},n=!1){if(t.slug&&typeof t.slug=="string"&&rt.has(_(t.slug))||(t.slug===w||t.slug===U||t.slug===b)&&tt.instance().canAccessFeature("mcp"))return!0;if(n&&Object.keys(r).length===0)return!!e.isAuthenticated;const o=V(r,e.teams||[]),s=t[L]||T(o,t[y]||{});return Object.keys(s||{}).length===0?!1:Object.keys(s).length===1&&s[l]&&s[l].toLowerCase()!=="none"?!0:N(s,e?.teams||[],e?.email)}function Nt(t,e,r,n){if(!t.startsWith(I)&&!t.startsWith(j))return!0;const o=t.replace(new RegExp(`^${I}/`),"").replace(new RegExp(`^${j}/`),""),c=o==="."?"":o,i={[y]:{slug:t,fsPath:c},slug:t};return O(i,n,e,r)}function kt(t,e,r,n,o){if(!t.startsWith(B))return!0;const s=t.match(/\..{64}\.([A-Fa-f0-9]{8})\.[^.]+$/)?.[1];if(!s)return!0;const c=n[s];if(!c)return!0;const{base:i,ext:f}=R.parse(t),u=i.split(".")[0],h=f.split(".").join(""),d=c==="."?"":c,p={[y]:{slug:t,fsPath:R.posix.join(d,`${u}.${h}`)},slug:t};return O(p,o,e,r)}async function wt(t,e){const{isAuthenticated:r=!1,idpAccessToken:n,federatedAccessToken:o,federatedIdToken:s,...c}=await Z(t,e),{teams:i=[]}=c;let f;return r?f=i.filter(u=>u!==g):f=[g],{isAuthenticated:r,idpAccessToken:n,teams:f,claims:c}}function k(t,e,r={},n=!1){if(!t)return t;if(Array.isArray(t)){const o=[];for(const s of t){const c=k(s,e,r,n);c!==void 0&&o.push(c)}return o}if(typeof t=="object"){if(!O(t,e,r,n))return;let o=!1;const s={};for(const c in t){if(c===L||c===y)continue;if(et.has(c)){s[c]=t[c];continue}const i=k(t[c],e,r,n);if(!t.link&&(c==="items"||c==="menu"&&t.type==="tab")&&Array.isArray(i)&&i.length===0&&t[c].length!==0){o=!0;continue}i!==void 0&&(s[c]=i),c==="paths"&&nt(i)&&ot(i)}return o?void 0:s}return t}function Ut(t){return typeof t=="string"?t.split(" ").filter(Boolean):Array.isArray(t)?t.map(e=>e.toString()):[]}function Mt(t,e){if(!e)return;const r=e.content;if(!r)return e;const n=Object.entries(r).flatMap(([s,c])=>s===C?[[s,c]]:[[s,c],...t.localeFolders.map(i=>[s.startsWith("/")?`/${i.toLocaleLowerCase()}${s}`:R.posix.join(t.localizationFolder,i,s),c])]),o=Object.fromEntries(n);return{...e,content:o}}async function Bt(t,e){if(!e)return{};const r={},n=new Set((await t.scan()).flatMap(({relativePath:o})=>{const{versionFolderPath:s}=v(o)||{},c=R.dirname(o);return s?[s,c]:c}));for(const o of n)r[H(o)]=o;return r}const m=t=>typeof t=="object"&&t!==null&&!Array.isArray(t);function nt(t){return m(t)&&Object.keys(t).length>0}function ot(t){for(const e of Object.keys(t)){const r=t[e];m(r)&&Object.keys(r).length===0&&delete t[e]}}function st(t){return t?t.split("/").filter(Boolean).some(r=>r.startsWith(".")):!1}const ct=t=>{if(t&&m(t)&&("content"in t&&m(t.content)||"reunite"in t&&m(t.reunite)||"features"in t&&m(t.features)||t.teamFolders&&t.teamNamePatterns)){const e=Object.values(t.content||{});if(e.length===0)return!0;if(e.every(m))return e.every(r=>Object.values(r).every(n=>typeof n=="string"))}return!1},bt=async t=>{if(t){if(Object.keys(t).length===0)return{};if(ct(t))return it(t);await Y.panicOnContentError(`You are using an incorrect format of ${z("rbac:")} configuration. See: https://redocly.com/docs/realm/access`)}},it=t=>{const e={...t};if(e.content){const r={};for(const n in e.content)if(e.content[n]!==void 0){const o=_(n);r[o]=e.content[n]}e.content=r}return e};function Wt(t,e){const r=t.fsPath,n=t.slug,o=[];if(G(e)&&(r||n)){const s=[r,n].filter(K),c=Q(e,s);if(c){const i=e?.teamNamePatterns?.map(u=>u.replace("{teamPathSegment}",c.teamPathSegment).replace("{projectRole}","read"))??[];o.push(...i);const f=T({content:{...J(e),...e.content}},t);o.push(...x(f))}else{const i=T(e,t);o.push(...x(i))}}else{const s=T(e,t);o.push(...x(s))}return at(e,o)}function x(t){if(!t)return[];const e=[],r=l in t?{authenticated:t[l],anonymous:t[l]}:{};for(const[n,o]of Object.entries({...r,...t}))o.toLowerCase()!=="none"&&n!==l&&e.push(n);return e}function at(t,e){return e.map(n=>X(t,n)??{teamName:n}).map(n=>n.projectRole&&n.projectRole!=="READ"?n.teamName?.toLowerCase().replace(n.projectRole?.toLowerCase?.()??"","read")??"":n.teamName?.toLowerCase()??"")}export{F as PROJECT_ROLES_ORDERED_BY_ACCESS_LEVEL,Mt as applyL10nToRbacConfig,kt as canAccessAsset,It as canAccessFeature,O as canAccessResource,Nt as canDownloadApiDefinition,at as expandTeamsForRead,x as extractTeamsFromScopeItems,k as filterDataByAccessDeep,wt as getAuthDetailsFromCookies,jt as getHigherRole,Wt as getRbacTeamsListForResource,T as getScopeItemsForResource,N as hasScopeAccess,ct as isRbacConfigValid,Ft as isResourcePubliclyAccessible,it as normalizeRbacConfig,bt as parseRbacConfig,Ut as parseTeamClaimToArray,Bt as resolveDirectoryHashes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{envConfig as
|
|
1
|
+
import{envConfig as p}from"../config/env-config.js";import{logger as l}from"../tools/notifiers/logger.js";import{reporter as r}from"../tools/notifiers/reporter.js";import{isMarkdocError as d}from"../plugins/markdown/errors.js";import{telemetryTraceStep as g}from"../../cli/telemetry/helpers/trace-step.js";import{writeProblemsReport as u}from"./write-problems-report.js";async function M(e){await g("com.redocly.build.validationErrors",async o=>{let t=0;for(const[c,f]of e.cache.errors.entries()){const m=c.split(":")[1];for(const i of f)d(i)?(r.reportMarkdocProblem(i),i.type!=="BROKEN_LINK"&&t++):await r.panicOnBuild(i.message,m)}const a=await e.getConfig();if(!p.isBuildMode)return;const n=r.getPageRenderProblems().length;o?.setAttribute("markdocErrors",t.toString()),o?.setAttribute("pageRenderErrors",n.toString()),t>0&&!a?.reunite?.ignoreMarkdocErrors?await s(`Need to fix ${t} Markdoc issue(s) or set the \`ignoreMarkdocErrors\` option to \`true\` in the \`reunite\` section of your \`redocly.yaml\``):n>0&&await s(`Need to fix ${n} runtime error(s) in React pages`)})}async function s(e){r.printErrors();try{u()}catch(o){l.warn("Failed to write the problems report: "+o.message)}await r.panic(e)}export{M as reportAllErrors};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as a}from"node:fs";import{dirname as l,join as m}from"node:path";class s{serverOutDir;static#e;static register({serverOutDir:e,readRoots:t}){s.#e=new s(e,t)}static resolve(e){return s.#e??new s(e)}#t;constructor(e,t=[]){this.serverOutDir=e,this.#t=s.#n(e,t)}get tmpDir(){return`${this.serverOutDir}/.data/tmp`}get execArgv(){return["--permission",...this.#t.map(e=>`--allow-fs-read=${e}/*`),`--allow-fs-write=${this.serverOutDir}/.data/*`,"--allow-addons"]}get env(){return{TMPDIR:this.tmpDir,TMP:this.tmpDir,TEMP:this.tmpDir}}static#r(e){if(!e)return e;const r=e.indexOf("/node_modules/");return r!==-1?e.slice(0,r):s.#s(e)??s.#i(e)??e}static#s(e){let t=e;for(;;){if(a(m(t,"node_modules",".pnpm")))return t;const r=l(t);if(r===t)return;t=r}}static#i(e){let t=e;for(;;){if(a(m(t,"node_modules")))return t;const r=l(t);if(r===t)return;t=r}}static#n(e,t){const r=[s.#r(e),...t].filter(Boolean).map(i=>i.replace(
|
|
1
|
+
import{existsSync as a}from"node:fs";import{dirname as l,join as m}from"node:path";class s{serverOutDir;static#e;static register({serverOutDir:e,readRoots:t}){s.#e=new s(e,t)}static resolve(e){return s.#e??new s(e)}#t;constructor(e,t=[]){this.serverOutDir=e,this.#t=s.#n(e,t)}get tmpDir(){return`${this.serverOutDir}/.data/tmp`}get execArgv(){return["--permission",...this.#t.map(e=>`--allow-fs-read=${e}/*`),`--allow-fs-write=${this.serverOutDir}/.data/*`,"--allow-addons"]}get env(){return{TMPDIR:this.tmpDir,TMP:this.tmpDir,TEMP:this.tmpDir}}static#r(e){if(!e)return e;const r=e.indexOf("/node_modules/");return r!==-1?e.slice(0,r):s.#s(e)??s.#i(e)??e}static#s(e){let t=e;for(;;){if(a(m(t,"node_modules",".pnpm")))return t;const r=l(t);if(r===t)return;t=r}}static#i(e){let t=e;for(;;){if(a(m(t,"node_modules")))return t;const r=l(t);if(r===t)return;t=r}}static#n(e,t){const r=[s.#r(e),...t].filter(Boolean).map(i=>i.replace(/(?<!\/)\/+$/,"")).filter(Boolean),n=[...new Set(r)];return n.filter(i=>!n.some(o=>o!==i&&i.startsWith(`${o}/`)))}}export{s as SandboxContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{writeFileSync as e}from"node:fs";import{envConfig as t}from"../config/env-config.js";import{reporter as o}from"../tools/notifiers/reporter.js";import{ensureDir as i}from"./fs.js";function u(){const r=t.REDOCLY_PROBLEMS_OUTPUT_FILE;return r?(e(i(r),JSON.stringify(o.getProblems()),"utf-8"),!0):!1}export{u as writeProblemsReport};
|
|
@@ -5,8 +5,12 @@ import type { UserIDToken } from '../types';
|
|
|
5
5
|
import { InternalTokenTypes } from './jwt/token-types.js';
|
|
6
6
|
export type AuthProviderLoginParams = OidcLoginParams | Saml2LoginParams;
|
|
7
7
|
export type OidcJwk = {
|
|
8
|
+
kty?: string;
|
|
8
9
|
n: string;
|
|
9
10
|
e: string;
|
|
11
|
+
crv?: string;
|
|
12
|
+
x?: string;
|
|
13
|
+
y?: string;
|
|
10
14
|
kid: string;
|
|
11
15
|
alg: string;
|
|
12
16
|
use: string;
|
|
@@ -61,11 +65,13 @@ export declare function buildOidcLoginUrl(origin: string, { authorizationEndpoin
|
|
|
61
65
|
export declare function buildOidcLogoutUrl(endSessionEndpoint: string, postLogoutUrl: string, idTokenHint: string | undefined, state?: string): string;
|
|
62
66
|
/**
|
|
63
67
|
* True when the provider sets no `audience`, or the token's `aud` or alternative
|
|
64
|
-
* audience claim names one of its values.
|
|
68
|
+
* audience claim names one of its non-empty values.
|
|
65
69
|
*/
|
|
66
70
|
export declare function idTokenAudienceIsAllowed(tokenPayload: Record<string, unknown>, idpConfig: {
|
|
67
|
-
audience?: string[];
|
|
71
|
+
audience?: string | string[];
|
|
68
72
|
} | undefined): boolean;
|
|
73
|
+
/** False when an OIDC token fails the `audience` or the Redocly SSO `clientId` check of its IdP. */
|
|
74
|
+
export declare function idTokenIsForThisProject(tokenPayload: Record<string, unknown>, idpConfig: AuthProviderConfig | undefined): boolean;
|
|
69
75
|
export declare function isIdpIssuedToken(token: string | undefined): boolean;
|
|
70
76
|
type McpAuthorizationCodePayload = {
|
|
71
77
|
client_id: string;
|
|
@@ -148,7 +154,7 @@ export declare function parseSamlResponse(responseXml: string): {
|
|
|
148
154
|
export declare const oidcMetadataCache: Record<string, OidcIssuerMetadata>;
|
|
149
155
|
export declare const oidcJwksCache: {
|
|
150
156
|
jwks: Record<string, (OidcJwk & {
|
|
151
|
-
|
|
157
|
+
idpIds: string[];
|
|
152
158
|
}) | null>;
|
|
153
159
|
};
|
|
154
160
|
export declare function getOidcMetadata(idpId: string, providerConfig: OidcProviderConfig): Promise<OidcIssuerMetadata>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import"../node-crypto-polyfill.js";import{DOMParser as v}from"@xmldom/xmldom";import{SignedXml as
|
|
1
|
+
import"../node-crypto-polyfill.js";import{DOMParser as v}from"@xmldom/xmldom";import{SignedXml as Q}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 S,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}from"@redocly/theme/core/utils";import{ServerRoutes as U}from"../../constants/api.js";import{AuthCookieNames as g,DEFAULT_AUTHENTICATED_TEAM as me,REQUIRED_OIDC_SCOPES as V}from"../../constants/common.js";import{appendQueryParams as pe}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 fe}from"../utils/crypto/random-string.js";import{randomUUID as F}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as d,JwtTokenExpired as he}from"./jwt/types.js";import*as m from"./jwt/jwt.js";import{InternalTokenTypes as T}from"./jwt/token-types.js";import{parseTeamClaimToArray as ye}from"../utils/index.js";import{parseTeamNameTemplate as Ae}from"../utils/rbac-expand.js";import{arrayBufferToBase64 as Se,decodeBase64 as H,encodeBase64URL as we,urlSafeBase64 as $}from"./jwt/encode.js";import{formatSamlCertificate as _e}from"./utils/format-saml-certificate.js";const Te=new Set(Object.values(T));function x(e){return e?.type===f.OIDC}function xe(e){return e?.type===f.SAML2}async function xt(e,t){if(x(t))return Ie(e,t);if(xe(t))return ge(e,t)}async function Ie(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 ge(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 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 It(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 gt(e,t,n){return await z(e,n,{scope:K(n),grant_type:"refresh_token",refresh_token:t})}function Ee(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)}`,w={state:F(),idpId:i,redirectUri:A,redirectTo:u,branch:p?.branchOverride??Me(e),inviteCode:h,source:p?.sourceOverride??"portal",uiLocales:p?.uiLocales},_={};if(c){const y=$(fe(50)),E=$(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",we(JSON.stringify(w))),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 Et(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 ke(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 Re(e,t){return!t||!x(t)?!0:ke(e,t)&&Oe(e,t)}function Oe(e,t){return!x(t)||!$e(t)||!t.clientId?!0:R(e.aud||[]).includes(t.clientId)}function kt(e){if(!e)return!1;try{return m.decode(e).header.alg===d.RS256}catch{return!1}}async function Rt(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},S,d.HS256)}async function Ot(e){await m.verify(e,S,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 Mt(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},S,d.HS256)}async function Lt(e){await m.verify(e,S,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 Nt(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=Le(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 Me(e){const t=B(e);if(t)return Y(t.hostname)?.previewBranch}function q(e,t){return e.replace(/^https?:\/\/[^/?#]+/i,`${t.protocol}//${t.host}`)}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 Le(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 Ne(e){return e.type===f.OIDC}function Ce(e){return e.type===f.SAML2}function Ct(e,t,n,r,o){return Ne(e)?Ee(t,e,n,r,{uiLocales:o}):Ce(e)?De(t,e,n,r,o):{}}function De(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="_${F()}"
|
|
5
5
|
IssueInstant="${new Date().toISOString()}"
|
|
6
|
-
AssertionConsumerServiceURL="${e}${
|
|
6
|
+
AssertionConsumerServiceURL="${e}${U.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>`,
|
|
12
|
+
</samlp:AuthnRequest>`,c=be(i);return{loginUrl:pe(t.ssoUrl,{SAMLRequest:c,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function be(e){return Se(te(new TextEncoder().encode(e)).buffer)}function Dt(e){const t=H(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 bt(e){try{return JSON.parse(H(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function Pt(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],w=A&&A.nodeValue||"",_=l(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],y=_&&_.nodeValue||"",E=l(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],k=Pe(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:w,success:r,expiresAt:k,issuerId:c,entityId:h,attrs:N,cert:a,nameFormat:y,destination:s}}function Pe(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 X(t.configurationUrl):t.configuration;L[e]=ve()?Ue(n):n}return L[e]}function ve(){const e=ce.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}const je=["authorization_endpoint","end_session_endpoint"];function Ue(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const n of Object.keys(t)){if(je.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 Ve=["kty","n","e","crv","x","y"];function Be(e,t){return Ve.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 X(o.jwks_uri);for(const i of s.keys){const c=t[i.kid],u=c&&Be(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 W(t,i.length?i:[s.clientId])})??r.at(-1)}async function X(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function vt(e){return fetch(`${ie}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function $e(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=>Ke(t.hostname,r))}function Ke(e,t){return e===t||e.endsWith(`.${t}`)}async function jt(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=_e(t),s=new Q({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 We(e,t,n){if(!t)return e;const r=ze(t),o=s=>{const i=n?.[s]||s;return r.has(i)||!!Ae(t,i)};return[...e.filter(o),...e.filter(s=>!o(s))]}async function Ut(e,t,n,r){const o=n?e[n]:void 0,s=Array.isArray(o)?We(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,S,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))),w=Math.max(1,A/i*4/3);i=Math.max(0,i-Math.max(1,Math.ceil(a/w)))}return null}function Vt(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?ye(n[r]):[]}}function Z(e,t={}){return e.map(n=>t[n]||n)}async function Bt(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"&&Te.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,S,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&&!Re(r.payload,i))return O.warn("Rejected id_token: it was issued for another project"),{};const c=Ye(i),u=qe(i);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:s,teams:Array.from(new Set([...Z(r.payload.teams||[],u),..."defaultTeams"in i&&i.defaultTeams||[],...Z("teamsClaimName"in i&&r.payload[c||""]||[],u),me])),name:Je(r.payload),isAuthenticated:!0,idpAccessToken:ue(r.payload.idp_access_token,S)||t[g.IDP_ACCESS_TOKEN],federatedAccessToken:t[g.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[g.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof he||O.error("Malformed JWT token: %s",r.message)}return{}}function Je(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 Ye(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{Ct as buildLoginUrl,Ee as buildOidcLoginUrl,Et as buildOidcLogoutUrl,De as buildSAML2LoginUrl,Rt as createMcpAuthorizationCode,Mt as createMcpRefreshToken,Nt as createMcpSessionResource,Dt as decodeSamlResponse,Fe as discoverJwks,be as encodeSAML2,Vt as extractUserClaims,xt as getAuthProviderLoginParams,Ie as getOidcLoginParams,G as getOidcMetadata,vt as getRedoclyTokenPayload,ge as getSaml2LoginParams,Bt as getUserParamsFromCookies,Je as getUsernameFromPayload,ke as idTokenAudienceIsAllowed,Re as idTokenIsForThisProject,kt as isIdpIssuedToken,x as isOidcProviderConfig,$e as isRedoclySso,xe as isSaml2ProviderConfig,It as oidcExchangeCodeForToken,I as oidcJwksCache,L as oidcMetadataCache,gt as oidcRefreshTokenGrant,We as orderTeamsRbacFirst,bt as parseOidcState,Me as parsePreviewBranch,Pt as parseSamlResponse,J as rewritePreviewAuthRedirectUri,Ut as signTokenWithinCookieSizeLimit,Ot as verifyMcpAuthorizationCode,Lt as verifyMcpRefreshToken,jt as verifySAMLResponse};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_SEARCH_ENGINE as
|
|
1
|
+
import{DEFAULT_SEARCH_ENGINE as o}from"@redocly/config";import{logger as e}from"../../tools/notifiers/logger.js";import{SearchEngine as h}from"../../plugins/search/engines/search-engine.js";function l(a){let i;const c=async()=>{e.info("Loading search data...");const t=e.startTiming();try{const n=a.config?.search?.engine??o,r=new h(n,a.globalData.l10n);await r.initIndexSchema(a.searchFacets),await r.import(a.outdir),a.setSearchEngine(r),e.infoTime(t,"Search data loaded")}catch(n){throw e.error("Failed to load search data: %s",n),n}};return async(t,n)=>{!a.searchEngine&&!a.config?.search?.hide&&(i??=c().finally(()=>{i=void 0}),await i),await n()}}export{l as ensureSearchData};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setCookie as w,deleteCookie as P}from"hono/cookie";import{AuthProviderType as Q,DEFAULT_TEAM_CLAIM_NAME as x}from"@redocly/config";import{withPathPrefix as y,getPathPrefix as A}from"@redocly/theme/core/utils";import{compareURIs as ee}from"../../../utils/url/compare-uris.js";import{ensureArray as re}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as oe,JWT_SECRET_KEY as U,MAX_AUTH_TOKEN_COOKIE_BYTES as N,ORG_SLUG as te,ORG_ID as ne}from"../../constants/common.js";import{AUTH_SEGMENT as ie,ServerRoutes as k}from"../../../constants/api.js";import{AuthCookieNames as _,DEFAULT_COOKIE_EXPIRATION as B}from"../../../constants/common.js";import{sanitizeRedirectPathname as H}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as L}from"../../telemetry/index.js";import{envConfig as K}from"../../config/env-config.js";import{fetchWithClient as se}from"../../utils/fetch-with-client.js";import{clearAuthCookies as z,clearPostLogoutCookie as v,hasPostLogoutCookie as ae,setPostLogoutCookie as j}from"../../utils/cookie.js";import{getAuthProviderLoginParams as de,isOidcProviderConfig as b,isSaml2ProviderConfig as le,oidcExchangeCodeForToken as ce,buildLoginUrl as ue,decodeSamlResponse as pe,extractUserClaims as ge,parseSamlResponse as fe,parseOidcState as me,verifySAMLResponse as he,getUsernameFromPayload as Ce,buildOidcLogoutUrl as Ie,isIdpIssuedToken as _e,getOidcMetadata as J,getRedoclyTokenPayload as Ae,idTokenAudienceIsAllowed as Re,isRedoclySso as Oe,rewritePreviewAuthRedirectUri as Te,parsePreviewBranch as G,buildOidcLoginUrl as we,createMcpSessionResource as M,signTokenWithinCookieSizeLimit as Z}from"../auth.js";import*as E from"../jwt/jwt.js";import{AlgorithmTypes as $}from"../jwt/types.js";import{handleErrorPageRender as ye}from"../utils.js";import{encodeBase64URL as Le}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as Me}from"./helpers/resolve-ui-locales-for-idp-login.js";async function Ge(n){if(K.isProductionEnv)return n.newResponse(null,404,{});const{password:e,...r}=await n.req.json(),d=await E.sign({...r,name:r.username||r.email||"Unknown"},U,$.HS256);return w(n,_.AUTHORIZATION,d,{path:A()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),v(n),n.newResponse(null,200,{})}function Ze(){return async n=>{const e=n.get("logger"),r=encodeURIComponent(n.req.query("message")||"");e.error(`Login error: ${r}`);const d=`${k.LOGIN}/?error=${encodeURIComponent(r)}`;return n.newResponse(null,301,{Location:d})}}function V(n){if(!n||!n.includes(k.MCP_CALLBACK))return null;try{const e=n.split("/"),r=e[e.length-1];if(r){const d=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(d).mcpSessionId||null}}catch{}return null}function Ve(n){return async e=>{const r=e.get("logger"),d=n.getConfig().ssoDirect;let o;try{o=me(e.req.query("state"))}catch{return r.warn("OIDC login error: Invalid OAuth2 state"),e.text("Bad request: Invalid OAuth2 state",400)}const h=o.idpId,i=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(k.MCP_CALLBACK),u=i?V(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,s=d?.[h];if(!b(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const p=await J(h,s);if(d&&!p.token_endpoint){const m="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${m}`),e.text(m,500)}try{const m=p.token_endpoint,f=e.req.query("code"),C=e.req.query("error");if(C)return i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:`OIDC error: ${C}`,error_details:e.req.query("error_description")||null}]),ye(e,n,{slug:"/"},403,"403OIDC");if(!f){const g="Code is expected but not present";return r.error(`OIDC login error: ${g}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:g,error_details:null}]),new Response(`Forbidden: ${g}`,{status:403})}const O=typeof o.redirectUri=="string"?o.redirectUri:new URL(y(k.OIDC_CALLBACK),e.req.url).toString(),I=e.get("cookies")?.code_verifier,a=await ce(m,f,O,s,{...s.tokenRequestCustomParams,...I?{code_verifier:I}:{}});if(a.error)return r.error(`Error from OIDC provider: "${a.error}"`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:`Token exchange error: ${a.error}`,error_details:a.error_description||null}]),e.text(`Forbidden: ${a.error_description||a.error}`,403);if(!a?.id_token){const g="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${g}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:g,error_details:null}]),new Response(`Forbidden: ${g}`,{status:403})}const{payload:l,header:T}=E.decode(a.id_token),t=T.alg===$.RS256;if(s.audience?.length&&!Re(l,s)){const g="No valid audience found in id_token";return r.error(`OIDC login error: ${g}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:g,error_details:null}]),new Response(`Forbidden: ${g}`)}let c=t?a.id_token:await E.sign({...l,idpId:h},U,$.HS256);if(Buffer.byteLength(c)>N){const g=await Z(l,h,s.teamsClaimName,{rbacConfig:n.getConfig().access?.rbac,teamsMapping:s.teamsClaimMap});if(!g){const S=`The id_token claims are too large for the session cookie: ${Buffer.byteLength(JSON.stringify(l))} bytes of claims`;return r.error(`OIDC login error: ${S}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:S,error_details:null}]),e.text(`Forbidden: ${S}`,403)}g.teamsKept<g.teamsTotal&&r.warn(`OIDC login: the "${s.teamsClaimName}" claim is too large for the session cookie size limit; kept ${g.teamsKept} of ${g.teamsTotal} values, with teams from the RBAC configuration first; RBAC ignores the dropped teams: ${JSON.stringify(g.droppedTeams)}`),c=g.token}Ce(l)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const D=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:l.exp*1e3||Date.now()+B*1e3;if(s.introspectEndpoint){const g=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:a.access_token})});if(g.ok){const q=(await g.json()).ext?.federatedIdentity;q&&(w(e,_.FEDERATED_ACCESS_TOKEN,q.access_token||"",{path:A()||"/",httpOnly:!1,expires:new Date(D)}),w(e,_.FEDERATED_ID_TOKEN,q.id_token||"",{path:A()||"/",httpOnly:!1,expires:new Date(D)}))}else r.warn(`OIDC introspect error: ${g.statusText}`)}if(w(e,_.AUTHORIZATION,c,{path:A()||"/",httpOnly:!0,expires:new Date(D)}),c!==a.id_token&&Buffer.byteLength(a.id_token)<=N?w(e,_.IDP_ID_TOKEN,a.id_token||"",{path:A()||"/",httpOnly:!0,expires:new Date(D)}):P(e,_.IDP_ID_TOKEN,{path:A()||"/"}),w(e,_.IDP_ACCESS_TOKEN,a.access_token||"",{path:A()||"/",httpOnly:!0,expires:new Date(D)}),a.refresh_token?w(e,_.IDP_REFRESH_TOKEN,a.refresh_token,{path:A()||"/",httpOnly:!0,expires:new Date(D)}):P(e,_.IDP_REFRESH_TOKEN,{path:A()||"/"}),P(e,"code_verifier",{path:A()||"/"}),v(e),i&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(k.MCP_CALLBACK)){const S=`${e.req.url.split("?")[0].replace(k.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:S})}const W=typeof o.redirectTo=="string"?o.redirectTo:void 0;let X=H(new URL(W||"/",e.req.url).pathname);const Y=e.newResponse(null,302,{Location:X});return r.updateContext({email:l.email,subject:l.sub}),r.info("OIDC login successful"),Y}catch(m){const f=m instanceof Error?m.message:String(m),C=m instanceof Error?m.stack:String(m);if(r.error(`OIDC login error: ${f}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:f,error_details:C}]),m.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const R="Something went wrong";return r.error(`OIDC login error: ${R}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:R,error_details:null}]),e.text(R,500)}}function We(n){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,i=n.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return b(i)||(z(e),j(e)),r.info("Logout successful"),e.newResponse(null,200,{});let u;if(b(i)){const s=(await J(o,i)).end_session_endpoint;if(s){const p=new URL(e.req.url),R=e.req.header("x-forwarded-proto")||p.protocol.slice(0,-1)||"https",m=e.req.header("x-forwarded-host")||p.host,f=`${R}://${m}`,C=G(f),O=C?Le(JSON.stringify({branch:G(f)})):void 0,I=C?`${Te(f)}/${ie}/logout`:`${f}${y(k.POST_LOGOUT)}`,a=e.get("cookies"),l=a?.[_.AUTHORIZATION],T=a?.[_.IDP_ID_TOKEN]||(_e(l)?l:void 0);u=Ie(s,I,T,O)}}return r.info("Logout successful"),z(e),j(e),e.newResponse(null,302,{Location:u||y("/")})}}function Xe(n){return async e=>{const r=n.getConfig().access?.logoutReturnUrl,d=r||y("/");return e.newResponse(null,302,{Location:d})}}function Ye(n){return async e=>{const r=e.get("logger"),d=e.req.param("code"),o=K.BH_API_URL,h=(i,u,s)=>i&&u?`${i} ${u.charAt(0)}`:s;try{if(!o)throw new Error("BH_API_URL is not set");const i=n.getConfig().ssoDirect;if(!i||!Object.keys(i).length)return r.warn("Invite no sso configured to handle"),e.redirect(y("/"));const u=await se(`${o}/public-user-invites/${d}`);if(!u.ok)return u.status===404?(r.warn(`Invite ${d} not found redirect to homepage`),e.redirect(y("/"))):(r.error("Invite error",await u.text()),e.redirect(y("/")));const s=await u.json(),p=new URL(y("/invite"),e.req.url);return p.searchParams.set("code",d),p.searchParams.set("org",s.organization.name),p.searchParams.set("invitedBy",h(s.invitedBy.firstName,s.invitedBy.lastName,s.invitedBy.name)),e.newResponse(null,302,{Location:p.toString()})}catch(i){return r.error("Error processing invite",{error:i,inviteCode:d}),e.text(i.message||"Failed to process invite",400)}}}function Qe(n){return async e=>{const r=e.get("logger"),d=n.getConfig().ssoDirect,o=new URL(e.req.url),h=e.req.query("inviteCode"),i=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",u=e.req.header("x-forwarded-host")||o.host,s=`${i}://${u}`;let p=o.searchParams.get("idpId");const R=o.searchParams.get("redirectTo"),m=Object.keys(d||{})[0];p=p||m;const f=o.searchParams.get("mcp_redirect_uri"),C=!!f;if(!d?.[p]){const c="Invalid idpId";if(r.error(`IdP login error: ${c}`),C){const F=V(R||void 0);L.sendMcpAuthorizationFailedMessage([{...M(F),error:c,error_details:null}])}return e.text(`Forbidden: ${c}`,403)}const I=Me({localePrefixParam:o.searchParams.get("localePrefix"),l10n:n.getGlobalData()?.l10n}),a=p&&d?await de(p,d[p]):void 0,l={};for(const c of Object.keys(a?.extraParams||{}))l[c]=o.searchParams.get(c)||a?.extraParams?.[c]||void 0;ae(e)&&(l.prompt="login",l.post_logout="1");let T,t={};if(C&&f&&a&&a.type===Q.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${f}`);const c=we("",{...a,extraParams:l},R,h,{redirectUriOverride:f,sourceOverride:"mcp",branchOverride:void 0,uiLocales:I});T=c.loginUrl,t=c.cookies||{}}else if(a){const c=ue({...a,extraParams:l},s,R,h,I);T=c.loginUrl,t=c.cookies||{}}return Object.keys(t).forEach(c=>{w(e,c,t[c].value,t[c].options)}),r.info(`IdP login initiated for ID '${p}'`),e.newResponse(null,302,{Location:T||new URL(e.req.url).pathname})}}function xe(n){return async e=>{const r=e.get("logger"),d=await e.req.formData(),o=d.get("SAMLResponse"),h=d.get("RelayState");if(typeof o!="string"||typeof h!="string"){const t="SAMLResponse is required";return r.error(`SAML2 login error: ${t}`),e.text(`Bad request: ${t}`,400)}const i=pe(o),{success:u,uid:s,nameFormat:p,attrs:R,issuerId:m,expiresAt:f}=fe(i),{idpId:C,redirectTo:O}=JSON.parse(h);if(!u){const t="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${t}`),e.text(`Permission denied: ${t}`,401)}if(!f||Math.ceil(Date.now()/1e3)>=f){const t="SAML2 Token Expired";return r.error(`SAML2 login error: ${t}`),e.text(t,401)}const I=n.getConfig().ssoDirect?.[C];if(!I||!le(I)){const t="Cannot find valid IdP";return r.error(`SAML2 login error: ${t}`),e.text(`Permission denied: ${t}`,401)}if(!(I.issuerId&&m&&ee(I.issuerId,m))){const t="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${t}`),e.text(`Permission denied: ${t}`,401)}if(!await he(i,I.x509PublicCert)){const t="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${t}`),e.text(t,401)}const l=ge(s,p,R,I.teamsAttributeName??x);if(!l.sub){const t="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${t}`),e.text(t,400)}if(!l.email){const t="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${t}`),e.text(t,400)}let T=await E.sign({...l,idpId:C},U,$.HS256);if(Buffer.byteLength(T)>N){const t=await Z({...l},C,"teams",{rbacConfig:n.getConfig().access?.rbac,teamsMapping:I.teamsAttributeMap});if(!t){const c=`The SAML2 claims are too large for the session cookie: ${Buffer.byteLength(JSON.stringify(l))} bytes of claims`;return r.error(`SAML2 login error: ${c}`),e.text(`Forbidden: ${c}`,403)}t.teamsKept<t.teamsTotal&&r.warn(`SAML2 login: the "teams" claim is too large for the session cookie size limit; kept ${t.teamsKept} of ${t.teamsTotal} values, with teams from the RBAC configuration first; RBAC ignores the dropped teams: ${JSON.stringify(t.droppedTeams)}`),T=t.token}return w(e,_.AUTHORIZATION,T,{path:A()||"/",httpOnly:!0,expires:new Date(f*1e3)}),P(e,_.IDP_REFRESH_TOKEN,{path:A()||"/"}),v(e),r.updateContext({email:l.email,subject:l.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:O||"/"})}}function er(n){return async e=>{const r=e.get("logger"),d=new URL(e.req.query("redirectTo")||"/",e.req.url),o=y(H(d.pathname)),h=n.getConfig().ssoDirect,i=Object.entries(h||{}).find(([,O])=>b(O)&&Oe(O));if(!(h&&i))return e.newResponse(null,302,{Location:o});const s=e.req.query("token"),p=s&&await Ae(s);if(!p)return e.newResponse(null,302,{Location:o});if(!re(p[oe]||[]).some(O=>O===te||O===ne))return e.newResponse(null,302,{Location:o});const f=await E.sign({...p,idpId:i?.at(0)},U,$.HS256),C=Date.now()+B*1e3;return w(e,_.AUTHORIZATION,f,{path:A()||"/",httpOnly:!0,expires:new Date(C),sameSite:"None",secure:!0}),P(e,_.IDP_REFRESH_TOKEN,{path:A()||"/"}),v(e),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{Ge as authorizeHandler,Qe as idpLoginHandler,Ye as inviteHandler,We as logoutHandler,Ve as oidcCallbackHandler,Xe as postLogoutHandler,Ze as redoclyLoginCallbackHandler,er as redoclyTokenLoginHandler,xe as samlCallbackHandler};
|
|
1
|
+
import{setCookie as w,deleteCookie as P}from"hono/cookie";import{AuthProviderType as Q,DEFAULT_TEAM_CLAIM_NAME as x}from"@redocly/config";import{withPathPrefix as y,getPathPrefix as A}from"@redocly/theme/core/utils";import{compareURIs as ee}from"../../../utils/url/compare-uris.js";import{ensureArray as re}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as oe,JWT_SECRET_KEY as U,MAX_AUTH_TOKEN_COOKIE_BYTES as N,ORG_SLUG as te,ORG_ID as ne}from"../../constants/common.js";import{AUTH_SEGMENT as ie,ServerRoutes as k}from"../../../constants/api.js";import{AuthCookieNames as _,DEFAULT_COOKIE_EXPIRATION as B}from"../../../constants/common.js";import{sanitizeRedirectPathname as H}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as L}from"../../telemetry/index.js";import{envConfig as K}from"../../config/env-config.js";import{fetchWithClient as se}from"../../utils/fetch-with-client.js";import{clearAuthCookies as z,clearPostLogoutCookie as v,hasPostLogoutCookie as ae,setPostLogoutCookie as j}from"../../utils/cookie.js";import{getAuthProviderLoginParams as de,isOidcProviderConfig as b,isSaml2ProviderConfig as le,oidcExchangeCodeForToken as ce,buildLoginUrl as ue,decodeSamlResponse as pe,extractUserClaims as ge,parseSamlResponse as fe,parseOidcState as me,verifySAMLResponse as he,getUsernameFromPayload as Ce,buildOidcLogoutUrl as Ie,isIdpIssuedToken as _e,getOidcMetadata as J,getRedoclyTokenPayload as Ae,idTokenAudienceIsAllowed as Re,isRedoclySso as Oe,rewritePreviewAuthRedirectUri as Te,parsePreviewBranch as G,buildOidcLoginUrl as we,createMcpSessionResource as M,signTokenWithinCookieSizeLimit as Z}from"../auth.js";import*as E from"../jwt/jwt.js";import{AlgorithmTypes as $}from"../jwt/types.js";import{handleErrorPageRender as ye}from"../utils.js";import{encodeBase64URL as Le}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as Me}from"./helpers/resolve-ui-locales-for-idp-login.js";async function Ge(n){if(K.isProductionEnv)return n.newResponse(null,404,{});const{password:e,...r}=await n.req.json(),d=await E.sign({...r,name:r.username||r.email||"Unknown"},U,$.HS256);return w(n,_.AUTHORIZATION,d,{path:A()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),v(n),n.newResponse(null,200,{})}function Ze(){return async n=>{const e=n.get("logger"),r=encodeURIComponent(n.req.query("message")||"");e.error(`Login error: ${r}`);const d=`${k.LOGIN}/?error=${encodeURIComponent(r)}`;return n.newResponse(null,301,{Location:d})}}function V(n){if(!n||!n.includes(k.MCP_CALLBACK))return null;try{const e=n.split("/"),r=e[e.length-1];if(r){const d=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(d).mcpSessionId||null}}catch{}return null}function Ve(n){return async e=>{const r=e.get("logger"),d=n.getConfig().ssoDirect;let o;try{o=me(e.req.query("state"))}catch{return r.warn("OIDC login error: Invalid OAuth2 state"),e.text("Bad request: Invalid OAuth2 state",400)}const h=o.idpId,i=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(k.MCP_CALLBACK),u=i?V(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,s=d?.[h];if(!b(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const p=await J(h,s);if(d&&!p.token_endpoint){const m="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${m}`),e.text(m,500)}try{const m=p.token_endpoint,f=e.req.query("code"),C=e.req.query("error");if(C)return i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:`OIDC error: ${C}`,error_details:e.req.query("error_description")||null}]),ye(e,n,{slug:"/"},403,"403OIDC");if(!f){const g="Code is expected but not present";return r.error(`OIDC login error: ${g}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:g,error_details:null}]),new Response(`Forbidden: ${g}`,{status:403})}const O=typeof o.redirectUri=="string"?o.redirectUri:new URL(y(k.OIDC_CALLBACK),e.req.url).toString(),I=e.get("cookies")?.code_verifier,a=await ce(m,f,O,s,{...s.tokenRequestCustomParams,...I?{code_verifier:I}:{}});if(a.error)return r.error(`Error from OIDC provider: "${a.error}"`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:`Token exchange error: ${a.error}`,error_details:a.error_description||null}]),e.text(`Forbidden: ${a.error_description||a.error}`,403);if(!a?.id_token){const g="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${g}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:g,error_details:null}]),new Response(`Forbidden: ${g}`,{status:403})}const{payload:l,header:T}=E.decode(a.id_token),t=T.alg===$.RS256;if(!Re(l,s)){const g="No valid audience found in id_token";return r.error(`OIDC login error: ${g}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:g,error_details:null}]),new Response(`Forbidden: ${g}`,{status:403})}let c=t?a.id_token:await E.sign({...l,idpId:h},U,$.HS256);if(Buffer.byteLength(c)>N){const g=await Z(l,h,s.teamsClaimName,{rbacConfig:n.getConfig().access?.rbac,teamsMapping:s.teamsClaimMap});if(!g){const S=`The id_token claims are too large for the session cookie: ${Buffer.byteLength(JSON.stringify(l))} bytes of claims`;return r.error(`OIDC login error: ${S}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:S,error_details:null}]),e.text(`Forbidden: ${S}`,403)}g.teamsKept<g.teamsTotal&&r.warn(`OIDC login: the "${s.teamsClaimName}" claim is too large for the session cookie size limit; kept ${g.teamsKept} of ${g.teamsTotal} values, with teams from the RBAC configuration first; RBAC ignores the dropped teams: ${JSON.stringify(g.droppedTeams)}`),c=g.token}Ce(l)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const D=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:l.exp*1e3||Date.now()+B*1e3;if(s.introspectEndpoint){const g=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:a.access_token})});if(g.ok){const q=(await g.json()).ext?.federatedIdentity;q&&(w(e,_.FEDERATED_ACCESS_TOKEN,q.access_token||"",{path:A()||"/",httpOnly:!1,expires:new Date(D)}),w(e,_.FEDERATED_ID_TOKEN,q.id_token||"",{path:A()||"/",httpOnly:!1,expires:new Date(D)}))}else r.warn(`OIDC introspect error: ${g.statusText}`)}if(w(e,_.AUTHORIZATION,c,{path:A()||"/",httpOnly:!0,expires:new Date(D)}),c!==a.id_token&&Buffer.byteLength(a.id_token)<=N?w(e,_.IDP_ID_TOKEN,a.id_token||"",{path:A()||"/",httpOnly:!0,expires:new Date(D)}):P(e,_.IDP_ID_TOKEN,{path:A()||"/"}),w(e,_.IDP_ACCESS_TOKEN,a.access_token||"",{path:A()||"/",httpOnly:!0,expires:new Date(D)}),a.refresh_token?w(e,_.IDP_REFRESH_TOKEN,a.refresh_token,{path:A()||"/",httpOnly:!0,expires:new Date(D)}):P(e,_.IDP_REFRESH_TOKEN,{path:A()||"/"}),P(e,"code_verifier",{path:A()||"/"}),v(e),i&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(k.MCP_CALLBACK)){const S=`${e.req.url.split("?")[0].replace(k.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:S})}const W=typeof o.redirectTo=="string"?o.redirectTo:void 0;let X=H(new URL(W||"/",e.req.url).pathname);const Y=e.newResponse(null,302,{Location:X});return r.updateContext({email:l.email,subject:l.sub}),r.info("OIDC login successful"),Y}catch(m){const f=m instanceof Error?m.message:String(m),C=m instanceof Error?m.stack:String(m);if(r.error(`OIDC login error: ${f}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:f,error_details:C}]),m.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const R="Something went wrong";return r.error(`OIDC login error: ${R}`),i&&L.sendMcpAuthorizationFailedMessage([{...M(u),error:R,error_details:null}]),e.text(R,500)}}function We(n){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,i=n.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return b(i)||(z(e),j(e)),r.info("Logout successful"),e.newResponse(null,200,{});let u;if(b(i)){const s=(await J(o,i)).end_session_endpoint;if(s){const p=new URL(e.req.url),R=e.req.header("x-forwarded-proto")||p.protocol.slice(0,-1)||"https",m=e.req.header("x-forwarded-host")||p.host,f=`${R}://${m}`,C=G(f),O=C?Le(JSON.stringify({branch:G(f)})):void 0,I=C?`${Te(f)}/${ie}/logout`:`${f}${y(k.POST_LOGOUT)}`,a=e.get("cookies"),l=a?.[_.AUTHORIZATION],T=a?.[_.IDP_ID_TOKEN]||(_e(l)?l:void 0);u=Ie(s,I,T,O)}}return r.info("Logout successful"),z(e),j(e),e.newResponse(null,302,{Location:u||y("/")})}}function Xe(n){return async e=>{const r=n.getConfig().access?.logoutReturnUrl,d=r||y("/");return e.newResponse(null,302,{Location:d})}}function Ye(n){return async e=>{const r=e.get("logger"),d=e.req.param("code"),o=K.BH_API_URL,h=(i,u,s)=>i&&u?`${i} ${u.charAt(0)}`:s;try{if(!o)throw new Error("BH_API_URL is not set");const i=n.getConfig().ssoDirect;if(!i||!Object.keys(i).length)return r.warn("Invite no sso configured to handle"),e.redirect(y("/"));const u=await se(`${o}/public-user-invites/${d}`);if(!u.ok)return u.status===404?(r.warn(`Invite ${d} not found redirect to homepage`),e.redirect(y("/"))):(r.error("Invite error",await u.text()),e.redirect(y("/")));const s=await u.json(),p=new URL(y("/invite"),e.req.url);return p.searchParams.set("code",d),p.searchParams.set("org",s.organization.name),p.searchParams.set("invitedBy",h(s.invitedBy.firstName,s.invitedBy.lastName,s.invitedBy.name)),e.newResponse(null,302,{Location:p.toString()})}catch(i){return r.error("Error processing invite",{error:i,inviteCode:d}),e.text(i.message||"Failed to process invite",400)}}}function Qe(n){return async e=>{const r=e.get("logger"),d=n.getConfig().ssoDirect,o=new URL(e.req.url),h=e.req.query("inviteCode"),i=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",u=e.req.header("x-forwarded-host")||o.host,s=`${i}://${u}`;let p=o.searchParams.get("idpId");const R=o.searchParams.get("redirectTo"),m=Object.keys(d||{})[0];p=p||m;const f=o.searchParams.get("mcp_redirect_uri"),C=!!f;if(!d?.[p]){const c="Invalid idpId";if(r.error(`IdP login error: ${c}`),C){const F=V(R||void 0);L.sendMcpAuthorizationFailedMessage([{...M(F),error:c,error_details:null}])}return e.text(`Forbidden: ${c}`,403)}const I=Me({localePrefixParam:o.searchParams.get("localePrefix"),l10n:n.getGlobalData()?.l10n}),a=p&&d?await de(p,d[p]):void 0,l={};for(const c of Object.keys(a?.extraParams||{}))l[c]=o.searchParams.get(c)||a?.extraParams?.[c]||void 0;ae(e)&&(l.prompt="login",l.post_logout="1");let T,t={};if(C&&f&&a&&a.type===Q.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${f}`);const c=we("",{...a,extraParams:l},R,h,{redirectUriOverride:f,sourceOverride:"mcp",branchOverride:void 0,uiLocales:I});T=c.loginUrl,t=c.cookies||{}}else if(a){const c=ue({...a,extraParams:l},s,R,h,I);T=c.loginUrl,t=c.cookies||{}}return Object.keys(t).forEach(c=>{w(e,c,t[c].value,t[c].options)}),r.info(`IdP login initiated for ID '${p}'`),e.newResponse(null,302,{Location:T||new URL(e.req.url).pathname})}}function xe(n){return async e=>{const r=e.get("logger"),d=await e.req.formData(),o=d.get("SAMLResponse"),h=d.get("RelayState");if(typeof o!="string"||typeof h!="string"){const t="SAMLResponse is required";return r.error(`SAML2 login error: ${t}`),e.text(`Bad request: ${t}`,400)}const i=pe(o),{success:u,uid:s,nameFormat:p,attrs:R,issuerId:m,expiresAt:f}=fe(i),{idpId:C,redirectTo:O}=JSON.parse(h);if(!u){const t="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${t}`),e.text(`Permission denied: ${t}`,401)}if(!f||Math.ceil(Date.now()/1e3)>=f){const t="SAML2 Token Expired";return r.error(`SAML2 login error: ${t}`),e.text(t,401)}const I=n.getConfig().ssoDirect?.[C];if(!I||!le(I)){const t="Cannot find valid IdP";return r.error(`SAML2 login error: ${t}`),e.text(`Permission denied: ${t}`,401)}if(!(I.issuerId&&m&&ee(I.issuerId,m))){const t="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${t}`),e.text(`Permission denied: ${t}`,401)}if(!await he(i,I.x509PublicCert)){const t="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${t}`),e.text(t,401)}const l=ge(s,p,R,I.teamsAttributeName??x);if(!l.sub){const t="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${t}`),e.text(t,400)}if(!l.email){const t="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${t}`),e.text(t,400)}let T=await E.sign({...l,idpId:C},U,$.HS256);if(Buffer.byteLength(T)>N){const t=await Z({...l},C,"teams",{rbacConfig:n.getConfig().access?.rbac,teamsMapping:I.teamsAttributeMap});if(!t){const c=`The SAML2 claims are too large for the session cookie: ${Buffer.byteLength(JSON.stringify(l))} bytes of claims`;return r.error(`SAML2 login error: ${c}`),e.text(`Forbidden: ${c}`,403)}t.teamsKept<t.teamsTotal&&r.warn(`SAML2 login: the "teams" claim is too large for the session cookie size limit; kept ${t.teamsKept} of ${t.teamsTotal} values, with teams from the RBAC configuration first; RBAC ignores the dropped teams: ${JSON.stringify(t.droppedTeams)}`),T=t.token}return w(e,_.AUTHORIZATION,T,{path:A()||"/",httpOnly:!0,expires:new Date(f*1e3)}),P(e,_.IDP_REFRESH_TOKEN,{path:A()||"/"}),v(e),r.updateContext({email:l.email,subject:l.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:O||"/"})}}function er(n){return async e=>{const r=e.get("logger"),d=new URL(e.req.query("redirectTo")||"/",e.req.url),o=y(H(d.pathname)),h=n.getConfig().ssoDirect,i=Object.entries(h||{}).find(([,O])=>b(O)&&Oe(O));if(!(h&&i))return e.newResponse(null,302,{Location:o});const s=e.req.query("token"),p=s&&await Ae(s);if(!p)return e.newResponse(null,302,{Location:o});if(!re(p[oe]||[]).some(O=>O===te||O===ne))return e.newResponse(null,302,{Location:o});const f=await E.sign({...p,idpId:i?.at(0)},U,$.HS256),C=Date.now()+B*1e3;return w(e,_.AUTHORIZATION,f,{path:A()||"/",httpOnly:!0,expires:new Date(C),sameSite:"None",secure:!0}),P(e,_.IDP_REFRESH_TOKEN,{path:A()||"/"}),v(e),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{Ge as authorizeHandler,Qe as idpLoginHandler,Ye as inviteHandler,We as logoutHandler,Ve as oidcCallbackHandler,Xe as postLogoutHandler,Ze as redoclyLoginCallbackHandler,er as redoclyTokenLoginHandler,xe as samlCallbackHandler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCookie as U}from"hono/cookie";import{createHash as V,timingSafeEqual as Z}from"node:crypto";import{ulid as Y}from"ulid";import{isRedoclySso as W,isOidcProviderConfig as w,createMcpAuthorizationCode as X,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as z,verifyMcpRefreshToken as x,createMcpSessionResource as S,oidcRefreshTokenGrant as ee,getOidcMetadata as re,getUserParamsFromCookies as te}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as y}from"../../../plugins/mcp/constants.js";import{AUTH_URL as ne,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as j,MCP_REFRESH_SESSION_LIFETIME_SEC as oe}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as A}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import*as M from"../../jwt/jwt.js";import{InternalTokenTypes as F,McpAccessTokenMarker as R}from"../../jwt/token-types.js";import{AlgorithmTypes as L,JwtAlgorithmNotImplemented as ie,JwtTokenExpired as se,JwtTokenInvalid as ce,JwtTokenNotBefore as de,JwtTokenSignatureMismatched as ae}from"../../jwt/types.js";import{getRequestOrigin as N}from"../../utils/get-request-origin.js";import{encryptClaimValue as q}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as le}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as pe,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as _e}from"./mcp-client-metadata.js";const s=(r,e,t=200,i)=>r.json(e,t,{"Content-Type":"application/json",...i??{}}),b=new Set(["S256"]),$=["authorization_code","refresh_token"],ue="https://claude.ai/oauth/claude-code-client-metadata";function fe(r,e,t){if(!b.has(e||""))return!1;const i=V("sha256").update(t).digest("base64url"),c=Buffer.from(i),o=Buffer.from(r);return c.length!==o.length?!1:Z(c,o)}async function he(r){const e=Math.floor(Date.now()/1e3);return M.sign({type:F.MCP_CONTEXT,...r,iat:e,exp:e+600},m,L.HS256)}async function me(r){await M.verify(r,m,L.HS256);const{payload:e}=M.decode(r);if(e.type!==F.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function be(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=N(e);return s(e,{resource:`${t}${I("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...y],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function $e(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=N(e);return s(e,{issuer:t,authorization_endpoint:`${t}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...y],registration_endpoint:`${t}${I(T.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 ze(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:D.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...$],response_types:["code"],scope:y.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":"*"})}}}function B(r){const e=r||{},t=Object.keys(e),i=t.find(o=>w(e[o])&&W(e[o])),c=t.find(o=>w(e[o]));return i||c||t[0]}function je(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:i}=t,c=i.get("redirect_uri"),o=i.get("client_id"),l=i.get("code_challenge"),n=i.get("code_challenge_method"),p=Y();if(l&&!b.has(n||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...b].join(", ")}`},400);if(H(o)&&!l)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(o)&&c)try{const d=await pe(o),a=o===ue;if(!_e(c,d.redirect_uris,{ignorePort:a}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const a=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${a}`},400)}A.sendMcpAuthorizationStartedMessage([{...S(p),redirect_uri:c||null}]);const _=N(e),f={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:o,mcpState:i.get("state"),mcpSessionId:p,mcpCodeChallenge:l,mcpCodeChallengeMethod:l?n:null,timestamp:Date.now()};try{const d=await he(f),a=new URL(I(T.IDP_LOGIN),_);a.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const k=B(r.getConfig().ssoDirect);return k?(a.searchParams.set("idpId",k),e.redirect(a.toString())):s(e,{error:"not_found"},404)}catch(d){const a=d instanceof Error?d.message:String(d),k=d instanceof Error?d.stack:String(d);A.sendMcpAuthorizationFailedMessage([{...S(p),error:a,error_details:k}]);const C=new URL(I(`${ne}/oauth2/auth`));return C.search=i.toString(),e.redirect(C.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),t=await M.sign({...r.claims,[R.CLAIM]:R.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,L.HS256,r.kid),i=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):j);return{accessToken:t,expiresIn:i}}async function ge(r,e,t){const i=g=>s(r,{error:"invalid_grant",error_description:g},400),c=r.get("logger"),o=t.get("refresh_token"),l=t.get("client_id")||void 0;if(!o)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let n;try{n=await x(o)}catch(g){return c?.warn("MCP refresh token rejected: %s",g instanceof Error?g.message:String(g)),i("Invalid refresh token")}if(l&&l!==n.client_id)return i("Client mismatch");const p=n.idp_id?e.getConfig().ssoDirect?.[n.idp_id]:void 0;let _,f,d=n.idp_access_token_enc,a=n.idp_refresh_token_enc;if(n.idp_refresh_token_enc){let g;try{g=le(n.idp_refresh_token_enc,m)}catch{return i("Invalid refresh token")}if(!n.idp_id||!w(p))return i("Unknown identity provider");let u;try{const h=await re(n.idp_id,p);if(!h?.token_endpoint)return i("Identity provider has no token endpoint");u=await ee(h.token_endpoint,g,p)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),i("Refresh rejected by identity provider");if(u.refresh_token&&(a=G(u.refresh_token,m)),u.access_token&&(d=q(u.access_token,m)),u.id_token)try{const h=M.decode(u.id_token);_=h.payload,f=h.header.kid}catch{return i("Invalid token response from identity provider")}}const k=w(p)&&p.tokenExpirationTime||j,C=_??n.claims,E=await K({claims:C,kid:f,idpId:n.idp_id,idpAccessTokenEnc:d,ttlSec:_?void 0:k}),O=await z({clientId:n.client_id,idpId:n.idp_id,claims:C,idpAccessTokenEnc:d,idpRefreshTokenEnc:a,sessionExp:n.session_exp});return s(r,{access_token:E.accessToken,token_type:"Bearer",expires_in:E.expiresIn,refresh_token:O,scope:y.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Fe(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),i=t.get("grant_type"),c=t.get("code"),o=t.get("redirect_uri")||void 0,l=t.get("code_verifier")||void 0;if(!$.includes(i))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(i==="refresh_token")return await ge(e,r,t);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const n=await Q(c);if(o&&o!==n.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(n.code_challenge){if(!l)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!fe(n.code_challenge,n.code_challenge_method,l))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&n.client_id&&!H(n.client_id)&&n.client_id!==D.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const p=n.id_token;if(typeof p!="string"||p.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const _=await J(p,r.getConfig().ssoDirect);if(!_)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);const f=n.idp_access_token_enc??(n.idp_access_token?q(n.idp_access_token,m):void 0),{accessToken:d,expiresIn:a}=await K({claims:_.payload,kid:_.kid,idpId:n.idp_id,idpAccessTokenEnc:f}),C=Math.floor(Date.now()/1e3)+oe,E=await z({clientId:n.client_id,idpId:n.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:n.idp_refresh_token_enc,sessionExp:C}),O={access_token:d,token_type:"Bearer",expires_in:a,refresh_token:E,scope:y.join(" ")};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(n){return e.get("logger")?.warn("MCP token exchange failed: %s",n instanceof Error?n.message:String(n)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const i=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:i},500)}}}async function J(r,e){const t=await te(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:i,header:c}=M.decode(r);if(i[R.CLAIM]!==R.VALUE)return{payload:i,kid:c.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function Ge(r){return async e=>{const t=new URL(e.req.url);let i=t.searchParams.get("context");if(!i&&t.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const o=t.pathname.split("/");i=o[o.length-1]}if(!i)return e.text("Missing context parameter",400);let c=null;try{const o=await me(i);if(c=o.mcpSessionId||null,!o.isMcpFlow||!o.originalRedirectUri)throw new Error("Invalid MCP context");const l=r.getConfig().ssoDirect,n=U(e,v.AUTHORIZATION),p=n?await J(n,l):void 0,_=U(e,v.IDP_ACCESS_TOKEN),f=U(e,v.IDP_REFRESH_TOKEN);if(!n||!p)return A.sendMcpAuthorizationFailedMessage([{...S(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await X({idToken:n,idpId:p.idpId??B(l),idpAccessTokenEnc:_?q(_,m):void 0,idpRefreshTokenEnc:f?G(f,m):void 0,clientId:o.mcpClientId||"",redirectUri:o.originalRedirectUri,codeChallenge:o.mcpCodeChallenge||void 0,codeChallengeMethod:o.mcpCodeChallengeMethod||void 0,ttlSec:600}),a=new URL(o.originalRedirectUri);return a.searchParams.set("code",d),o.mcpState&&a.searchParams.set("state",o.mcpState),A.sendMcpAuthorizationCompletedMessage([{...S(c),redirect_uri:o.originalRedirectUri||null}]),e.redirect(a.toString())}catch(o){const l=ke(o);return A.sendMcpAuthorizationFailedMessage([{...S(c),...l}]),e.text(`Invalid MCP callback: ${l.error}`,400)}}}function ke(r){return r instanceof Error&&[ie,se,ce,de,ae].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,he as createMcpContextToken,je as mcpAuthorizationHandler,Ge as mcpCallbackHandler,ze as mcpDynamicClientRegistrationHandler,$e as mcpOAuthAuthorizationServerHandler,be as mcpOAuthProtectedResourceHandler,Fe as mcpTokenPortalHandler,B as resolveMcpIdpId,me as verifyAndParseMcpContextToken,fe as verifyPkce};
|
|
1
|
+
import{getCookie as U}from"hono/cookie";import{createHash as V,timingSafeEqual as Z}from"node:crypto";import{ulid as Y}from"ulid";import{isRedoclySso as W,isOidcProviderConfig as w,createMcpAuthorizationCode as X,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as $,verifyMcpRefreshToken as x,createMcpSessionResource as S,oidcRefreshTokenGrant as ee,getOidcMetadata as re,getUserParamsFromCookies as te,idTokenIsForThisProject as ne}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as y}from"../../../plugins/mcp/constants.js";import{AUTH_URL as oe,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as z,MCP_REFRESH_SESSION_LIFETIME_SEC as ie}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as A}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import*as M from"../../jwt/jwt.js";import{InternalTokenTypes as F,McpAccessTokenMarker as R}from"../../jwt/token-types.js";import{AlgorithmTypes as L,JwtAlgorithmNotImplemented as se,JwtTokenExpired as ce,JwtTokenInvalid as de,JwtTokenNotBefore as ae,JwtTokenSignatureMismatched as le}from"../../jwt/types.js";import{getRequestOrigin as N}from"../../utils/get-request-origin.js";import{encryptClaimValue as q}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as pe}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as _e,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as ue}from"./mcp-client-metadata.js";const s=(r,e,t=200,o)=>r.json(e,t,{"Content-Type":"application/json",...o??{}}),b=new Set(["S256"]),j=["authorization_code","refresh_token"],fe="https://claude.ai/oauth/claude-code-client-metadata";function he(r,e,t){if(!b.has(e||""))return!1;const o=V("sha256").update(t).digest("base64url"),c=Buffer.from(o),i=Buffer.from(r);return c.length!==i.length?!1:Z(c,i)}async function me(r){const e=Math.floor(Date.now()/1e3);return M.sign({type:F.MCP_CONTEXT,...r,iat:e,exp:e+600},m,L.HS256)}async function ge(r){await M.verify(r,m,L.HS256);const{payload:e}=M.decode(r);if(e.type!==F.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function je(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=N(e);return s(e,{resource:`${t}${I("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...y],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function $e(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=N(e);return s(e,{issuer:t,authorization_endpoint:`${t}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...y],registration_endpoint:`${t}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...j],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 ze(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:D.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...j],response_types:["code"],scope:y.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":"*"})}}}function B(r){const e=r||{},t=Object.keys(e),o=t.find(i=>w(e[i])&&W(e[i])),c=t.find(i=>w(e[i]));return o||c||t[0]}function Fe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:o}=t,c=o.get("redirect_uri"),i=o.get("client_id"),p=o.get("code_challenge"),n=o.get("code_challenge_method"),l=Y();if(p&&!b.has(n||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...b].join(", ")}`},400);if(H(i)&&!p)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(i)&&c)try{const d=await _e(i),a=i===fe;if(!ue(c,d.redirect_uris,{ignorePort:a}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const a=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${a}`},400)}A.sendMcpAuthorizationStartedMessage([{...S(l),redirect_uri:c||null}]);const _=N(e),f={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:i,mcpState:o.get("state"),mcpSessionId:l,mcpCodeChallenge:p,mcpCodeChallengeMethod:p?n:null,timestamp:Date.now()};try{const d=await me(f),a=new URL(I(T.IDP_LOGIN),_);a.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const C=B(r.getConfig().ssoDirect);return C?(a.searchParams.set("idpId",C),e.redirect(a.toString())):s(e,{error:"not_found"},404)}catch(d){const a=d instanceof Error?d.message:String(d),C=d instanceof Error?d.stack:String(d);A.sendMcpAuthorizationFailedMessage([{...S(l),error:a,error_details:C}]);const g=new URL(I(`${oe}/oauth2/auth`));return g.search=o.toString(),e.redirect(g.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),t=await M.sign({...r.claims,[R.CLAIM]:R.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,L.HS256,r.kid),o=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):z);return{accessToken:t,expiresIn:o}}async function ke(r,e,t){const o=k=>s(r,{error:"invalid_grant",error_description:k},400),c=r.get("logger"),i=t.get("refresh_token"),p=t.get("client_id")||void 0;if(!i)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let n;try{n=await x(i)}catch(k){return c?.warn("MCP refresh token rejected: %s",k instanceof Error?k.message:String(k)),o("Invalid refresh token")}if(p&&p!==n.client_id)return o("Client mismatch");const l=n.idp_id?e.getConfig().ssoDirect?.[n.idp_id]:void 0;let _,f,d=n.idp_access_token_enc,a=n.idp_refresh_token_enc;if(n.idp_refresh_token_enc){let k;try{k=pe(n.idp_refresh_token_enc,m)}catch{return o("Invalid refresh token")}if(!n.idp_id||!w(l))return o("Unknown identity provider");let u;try{const h=await re(n.idp_id,l);if(!h?.token_endpoint)return o("Identity provider has no token endpoint");u=await ee(h.token_endpoint,k,l)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),o("Refresh rejected by identity provider");if(u.refresh_token&&(a=G(u.refresh_token,m)),u.access_token&&(d=q(u.access_token,m)),u.id_token)try{const h=M.decode(u.id_token);_=h.payload,f=h.header.kid}catch{return o("Invalid token response from identity provider")}}const C=w(l)&&l.tokenExpirationTime||z,g=_??n.claims;if(!ne(g,l))return c?.warn("MCP refresh rejected: the id_token was issued for another project"),o("Invalid refresh token");const E=await K({claims:g,kid:f,idpId:n.idp_id,idpAccessTokenEnc:d,ttlSec:_?void 0:C}),O=await $({clientId:n.client_id,idpId:n.idp_id,claims:g,idpAccessTokenEnc:d,idpRefreshTokenEnc:a,sessionExp:n.session_exp});return s(r,{access_token:E.accessToken,token_type:"Bearer",expires_in:E.expiresIn,refresh_token:O,scope:y.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Ge(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),o=t.get("grant_type"),c=t.get("code"),i=t.get("redirect_uri")||void 0,p=t.get("code_verifier")||void 0;if(!j.includes(o))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(o==="refresh_token")return await ke(e,r,t);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const n=await Q(c);if(i&&i!==n.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(n.code_challenge){if(!p)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!he(n.code_challenge,n.code_challenge_method,p))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&n.client_id&&!H(n.client_id)&&n.client_id!==D.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const l=n.id_token;if(typeof l!="string"||l.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const _=await J(l,r.getConfig().ssoDirect);if(!_)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);const f=n.idp_access_token_enc??(n.idp_access_token?q(n.idp_access_token,m):void 0),{accessToken:d,expiresIn:a}=await K({claims:_.payload,kid:_.kid,idpId:n.idp_id,idpAccessTokenEnc:f}),g=Math.floor(Date.now()/1e3)+ie,E=await $({clientId:n.client_id,idpId:n.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:n.idp_refresh_token_enc,sessionExp:g}),O={access_token:d,token_type:"Bearer",expires_in:a,refresh_token:E,scope:y.join(" ")};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(n){return e.get("logger")?.warn("MCP token exchange failed: %s",n instanceof Error?n.message:String(n)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const o=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}async function J(r,e){const t=await te(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:o,header:c}=M.decode(r);if(o[R.CLAIM]!==R.VALUE)return{payload:o,kid:c.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function Be(r){return async e=>{const t=new URL(e.req.url);let o=t.searchParams.get("context");if(!o&&t.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const i=t.pathname.split("/");o=i[i.length-1]}if(!o)return e.text("Missing context parameter",400);let c=null;try{const i=await ge(o);if(c=i.mcpSessionId||null,!i.isMcpFlow||!i.originalRedirectUri)throw new Error("Invalid MCP context");const p=r.getConfig().ssoDirect,n=U(e,v.AUTHORIZATION),l=n?await J(n,p):void 0,_=U(e,v.IDP_ACCESS_TOKEN),f=U(e,v.IDP_REFRESH_TOKEN);if(!n||!l)return A.sendMcpAuthorizationFailedMessage([{...S(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await X({idToken:n,idpId:l.idpId??B(p),idpAccessTokenEnc:_?q(_,m):void 0,idpRefreshTokenEnc:f?G(f,m):void 0,clientId:i.mcpClientId||"",redirectUri:i.originalRedirectUri,codeChallenge:i.mcpCodeChallenge||void 0,codeChallengeMethod:i.mcpCodeChallengeMethod||void 0,ttlSec:600}),a=new URL(i.originalRedirectUri);return a.searchParams.set("code",d),i.mcpState&&a.searchParams.set("state",i.mcpState),A.sendMcpAuthorizationCompletedMessage([{...S(c),redirect_uri:i.originalRedirectUri||null}]),e.redirect(a.toString())}catch(i){const p=Ce(i);return A.sendMcpAuthorizationFailedMessage([{...S(c),...p}]),e.text(`Invalid MCP callback: ${p.error}`,400)}}}function Ce(r){return r instanceof Error&&[se,ce,de,ae,le].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{j as SUPPORTED_MCP_GRANT_TYPES,me as createMcpContextToken,Fe as mcpAuthorizationHandler,Be as mcpCallbackHandler,ze as mcpDynamicClientRegistrationHandler,$e as mcpOAuthAuthorizationServerHandler,je as mcpOAuthProtectedResourceHandler,Ge as mcpTokenPortalHandler,B as resolveMcpIdpId,ge as verifyAndParseMcpContextToken,he as verifyPkce};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const s=new RegExp("((?!@i18n\\/|@l10n\\/)\\/?@.*?(?=\\/))","g");function l(n){if(n.match(s)?.length!==1)return null;const[i,e,t]=n.split(new RegExp("(?:^@(?:i18n|l10n)\\/)?(
|
|
1
|
+
const s=new RegExp("((?!@i18n\\/|@l10n\\/)\\/?@.*?(?=\\/))","g");function l(n){if(n.match(s)?.length!==1)return null;const[i,e,t]=n.split(new RegExp("(?<![^\\n])(?:^@(?:i18n|l10n)\\/)?([^@\\n]*)@(.*)")),[o,r]=t.split(new RegExp("\\/(.*)","s"));return{versionFolderPath:e||"/",filePathInVersion:r,versionName:o}}export{l as parsePathVersions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function r(e){return e?e.trim().replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").replace(/[^\w\s-]+/g," ").toLowerCase().replace(/[\s_]+/g,"-").replace(/-+/g,"-").replace(
|
|
1
|
+
function r(e){return e?e.trim().replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([A-Z])([A-Z][a-z])/g,"$1-$2").replace(/[^\w\s-]+/g," ").toLowerCase().replace(/[\s_]+/g,"-").replace(/-+/g,"-").replace(/^-+|(?<!-)-+$/g,""):""}export{r as toKebabCase};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/redoc-reef",
|
|
3
|
-
"version": "0.138.0-next.
|
|
3
|
+
"version": "0.138.0-next.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"@opentelemetry/sdk-trace-web": "2.8.0",
|
|
34
34
|
"@opentelemetry/semantic-conventions": "1.40.0",
|
|
35
35
|
"@redocly/ajv": "8.18.0",
|
|
36
|
-
"@redocly/api-docs": "0.3.0-next.
|
|
37
|
-
"@redocly/config": "0.
|
|
38
|
-
"@redocly/openapi-core": "2.
|
|
36
|
+
"@redocly/api-docs": "0.3.0-next.4",
|
|
37
|
+
"@redocly/config": "0.61.0",
|
|
38
|
+
"@redocly/openapi-core": "2.55.0",
|
|
39
39
|
"@redocly/portal-legacy-ui": "0.19.0",
|
|
40
|
-
"@redocly/portal-plugin-mock-server": "0.
|
|
41
|
-
"@redocly/realm-asyncapi-sdk": "0.16.0-next.
|
|
42
|
-
"@redocly/theme": "0.70.0-next.
|
|
40
|
+
"@redocly/portal-plugin-mock-server": "0.23.0-next.0",
|
|
41
|
+
"@redocly/realm-asyncapi-sdk": "0.16.0-next.2",
|
|
42
|
+
"@redocly/theme": "0.70.0-next.4",
|
|
43
43
|
"@shikijs/transformers": "3.21.0",
|
|
44
44
|
"@tanstack/react-query": "5.62.3",
|
|
45
45
|
"@tanstack/react-table": "8.21.3",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function f(o,n,e={}){for(const r of o)r.versionFolderId&&r.version&&(e[r.versionFolderId]=e[r.versionFolderId]??new Set).add(r.version);return o.filter(r=>r.version==null?r:!n||r.versionFolderId!==n.folderId?r.isDefault:r.versionFolderId&&e[r.versionFolderId]?.has(n.version)?r.version===n.version:r.isDefault).map(r=>{if(Array.isArray(r.items)&&r.items.length){const s=f(r.items,n,e);return s.length===0?null:{...r,items:s}}return r}).filter(r=>r!==null)}export{f as filterOutVersionedItems};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
type TokenAudienceMismatchEvent = {
|
|
2
|
-
audience: string;
|
|
3
|
-
enforced: boolean;
|
|
4
|
-
};
|
|
5
|
-
export declare function reportMcpTokenAudienceMismatch({ audience, enforced, }: TokenAudienceMismatchEvent): void;
|
|
6
|
-
export {};
|
|
7
|
-
//# sourceMappingURL=token-audience-telemetry.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{telemetry as t}from"../../../telemetry/index.js";import{logger as o}from"../../../tools/notifiers/logger.js";import{McpServerType as c}from"../constants.js";import{serverAttributes as m}from"../utils/telemetry-attributes.js";function a({audience:e,enforced:r}){try{t.sendMcpTokenAudienceMismatchMessage([{...m(c.Docs),audience:e,enforced:r}])}catch{}o.warn(`MCP token audience mismatch [audience=${e}] enforced=${r}`)}export{a as reportMcpTokenAudienceMismatch};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { McpUserInfo } from '../types.js';
|
|
2
|
-
export type TokenAudienceResult = 'match' | 'mismatch' | 'skipped';
|
|
3
|
-
export declare function toAudienceList(aud: McpUserInfo['aud']): string[];
|
|
4
|
-
export declare function checkTokenAudience(aud: McpUserInfo['aud']): TokenAudienceResult;
|
|
5
|
-
export declare function isTokenAudienceEnforced(): boolean;
|
|
6
|
-
//# sourceMappingURL=token-audience.d.ts.map
|