@redocly/redoc-reef 0.136.0-next.4 → 0.136.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 +47 -0
- package/dist/client/ErrorBoundary.js +1 -1
- package/dist/client/app/Sidebar/helpers/hide-empty-separators.d.ts +13 -0
- package/dist/client/app/Sidebar/helpers/hide-empty-separators.js +1 -0
- package/dist/client/app/Sidebar/utils.js +1 -1
- package/dist/client/app/l10n/hooks/useTranslate.js +1 -1
- package/dist/client/app/l10n/initL10n.js +1 -1
- package/dist/client/app/utils/same-resolved-route.d.ts +5 -0
- package/dist/client/app/utils/same-resolved-route.js +1 -0
- package/dist/client/providers/post-message/PostMessageProvider.js +1 -1
- package/dist/client/templates/openapi-docs/helpers.d.ts +1 -1
- package/dist/client/templates/openapi-docs/helpers.js +2 -2
- package/dist/constants/common.d.ts +13 -1
- package/dist/constants/common.js +1 -1
- package/dist/server/config/env-config.d.ts +2 -4
- package/dist/server/config/env-schema.d.ts +5 -10
- package/dist/server/config/env-schemas/feature-flags.d.ts +4 -8
- package/dist/server/config/env-schemas/feature-flags.js +1 -1
- package/dist/server/constants/common.d.ts +2 -0
- package/dist/server/constants/common.js +1 -1
- package/dist/server/constants/entitlements.js +1 -1
- package/dist/server/plugins/config-parser/loaders/utils/read-and-validate-config.js +1 -1
- package/dist/server/plugins/markdown/search/create-render-tag-fn.d.ts +2 -1
- package/dist/server/plugins/markdown/search/create-render-tag-fn.js +1 -1
- package/dist/server/plugins/markdown/search/get-ai-search-documents.js +9 -9
- package/dist/server/plugins/mcp/auth/auth-handlers.d.ts +1 -0
- package/dist/server/plugins/mcp/auth/auth-handlers.js +1 -1
- package/dist/server/plugins/mcp/codemode/tools/execute.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +27 -0
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +2 -0
- package/dist/server/plugins/mcp/gateway-mcp/eligibility.d.ts +4 -15
- package/dist/server/plugins/mcp/gateway-mcp/eligibility.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/allowed-hosts.d.ts +8 -8
- package/dist/server/plugins/mcp/gateway-mcp/fetch/allowed-hosts.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.d.ts +17 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-bridge.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-errors.d.ts +11 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-errors.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.d.ts +13 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/fetch-telemetry.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.d.ts +4 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/redact.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-blocklist.d.ts +22 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-blocklist.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-guard.d.ts +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-guard.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-safe-lookup.d.ts +13 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch/ssrf-safe-lookup.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/fetch-capability.d.ts +8 -1
- package/dist/server/plugins/mcp/gateway-mcp/fetch-capability.js +1 -1
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.d.ts +3 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.d.ts +16 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/edge-limiter.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.d.ts +2 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/ip-key.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.d.ts +7 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/limits.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.d.ts +15 -0
- package/dist/server/plugins/mcp/gateway-mcp/rate-limit/window-counter.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +24 -0
- package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.d.ts +18 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth-scheme.js +1 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.d.ts +10 -0
- package/dist/server/plugins/mcp/gateway-mcp/upstream-auth.js +1 -0
- package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
- package/dist/server/plugins/mcp/handlers/errors.d.ts +3 -3
- package/dist/server/plugins/mcp/handlers/errors.js +1 -1
- package/dist/server/plugins/mcp/handlers/handle-mcp-request.js +1 -1
- package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.d.ts +5 -0
- package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +101 -0
- package/dist/server/plugins/mcp/index.d.ts +1 -0
- package/dist/server/plugins/mcp/index.js +1 -1
- package/dist/server/plugins/mcp/servers/docs-server.d.ts +5 -2
- package/dist/server/plugins/mcp/servers/docs-server.js +1 -1
- package/dist/server/plugins/mcp/servers/mcp-server.d.ts +13 -2
- package/dist/server/plugins/mcp/servers/mcp-server.js +1 -1
- package/dist/server/plugins/mcp/servers/session-manager.d.ts +15 -0
- package/dist/server/plugins/mcp/servers/session-manager.js +1 -0
- package/dist/server/plugins/mcp/types.d.ts +9 -12
- package/dist/server/plugins/mcp/utils/jwt.d.ts +0 -9
- package/dist/server/plugins/mcp/utils/jwt.js +1 -1
- package/dist/server/plugins/mcp/utils/mode.d.ts +12 -2
- package/dist/server/plugins/mcp/utils/mode.js +1 -1
- package/dist/server/plugins/mcp/utils/telemetry-attributes.d.ts +8 -0
- package/dist/server/plugins/mcp/utils/telemetry-attributes.js +1 -0
- package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
- package/dist/server/plugins/mcp/workers/mcp-tool-telemetry.js +1 -1
- package/dist/server/plugins/openapi-docs/search/get-ai-search-documents.js +16 -16
- package/dist/server/plugins/openapi-docs/utils.js +1 -1
- package/dist/server/plugins/sso/index.js +1 -1
- package/dist/server/sandbox/types.d.ts +3 -2
- package/dist/server/sandbox/utils/execution-budget.d.ts +4 -3
- package/dist/server/sandbox/utils/execution-budget.js +1 -1
- package/dist/server/types/plugins/markdown.d.ts +2 -2
- package/dist/server/utils/crypto/constants.d.ts +5 -0
- package/dist/server/utils/crypto/constants.js +1 -0
- package/dist/server/utils/crypto/decrypt-aes-gcm.d.ts +6 -0
- package/dist/server/utils/crypto/decrypt-aes-gcm.js +1 -0
- package/dist/server/utils/crypto/decrypt-claim-value.d.ts +6 -0
- package/dist/server/utils/crypto/decrypt-claim-value.js +1 -0
- package/dist/server/utils/crypto/derive-key.d.ts +5 -0
- package/dist/server/utils/crypto/derive-key.js +1 -0
- package/dist/server/utils/crypto/encrypt-aes-gcm.d.ts +6 -0
- package/dist/server/utils/crypto/encrypt-aes-gcm.js +1 -0
- package/dist/server/utils/crypto/encrypt-claim-value.d.ts +6 -0
- package/dist/server/utils/crypto/encrypt-claim-value.js +1 -0
- package/dist/server/utils/l10n/get-build-translate-context.d.ts +7 -0
- package/dist/server/utils/l10n/get-build-translate-context.js +1 -0
- package/dist/server/utils/rbac.d.ts +4 -0
- package/dist/server/utils/rbac.js +1 -1
- package/dist/server/web-server/auth.d.ts +33 -3
- package/dist/server/web-server/auth.js +3 -3
- package/dist/server/web-server/handle-api-route-request.js +1 -1
- package/dist/server/web-server/routes/api-routes/api-routes.js +1 -1
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +5 -2
- package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
- package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
- package/dist/server/workers/worker-pool.js +1 -1
- package/dist/types/entitlements.d.ts +6 -1
- package/dist/utils/l10n/build-i18n-config.d.ts +24 -0
- package/dist/utils/l10n/build-i18n-config.js +1 -0
- package/dist/utils/l10n/create-translate.d.ts +19 -0
- package/dist/utils/l10n/create-translate.js +1 -0
- package/package.json +11 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
import y from"path";import
|
|
1
|
+
import y from"path";import P from"picomatch";import"../node-crypto-polyfill.js";import{REDOCLY_TEAMS_RBAC as _,REDOCLY_ROUTE_RBAC as A}from"@redocly/config";import{ServerRoutes as k}from"../../constants/api.js";import{DEFAULT_ANONYMOUS_VISITOR_TEAM as x,PUBLIC_RBAC_SCOPE_ITEM as R,RBAC_ALL_OTHER_TEAMS as l,DEFAULT_RBAC_SCOPE as L}from"../../constants/common.js";import{DEPRECATED_PUBLIC_API_DEFINITIONS_FOLDER as j,PUBLIC_API_DEFINITIONS_FOLDER as I,PUBLIC_ASSETS_FOLDER as w}from"../constants/common.js";import{removeTrailingSlash as B}from"../../utils/url/remove-trailing-slash.js";import{removeLeadingSlash as b}from"../../utils/url/remove-leading-slash.js";import{parsePathVersions as M}from"../../utils/path/parse-path-versions.js";import{reporter as U}from"../tools/notifiers/reporter.js";import{bold as W}from"../tools/notifiers/helpers/colors.js";import{shaDirPathShort as $}from"../utils/crypto/sha-dir-path-short.js";import{isTruthy as v}from"../../utils/guards/is-truthy.js";import{canExpandConfig as Y,expandRbacConfig as z,getTeamFolderDefaults as K,parseTeamFoldersTemplate as H,parseTeamNameTemplate as G}from"./rbac-expand.js";import{getUserParamsFromCookies as V}from"../web-server/auth.js";import{getDeeperGlobPattern as J}from"./globs.js";import{EntitlementsProvider as Q}from"../entitlements/entitlements-provider.js";const C=["NONE","READ","TRIAGE","WRITE","MAINTAIN","ADMIN"],X=new Set(["x-parsed-md-description","x-parsed-md-summary"]);function Pt(t,e){const r=C.indexOf(t.toUpperCase()),n=C.indexOf(e.toUpperCase());return r>n?t:e}const E={};function T(t,e){if(!t?.content)return R;const r=t.content,{slug:n,fsPath:s}=e;if(!n&&!s)return R;const o=a=>{const d=`slug:${a}`,p=E[d]??P(a);E[d]=p;const g=`fsPath:${a}`,S=E[g]??P(b(a));return E[g]=S,!!(n&&p(n))||!!(s&&S(s))};if(tt(n||s||"")&&Object.keys(r).filter(p=>o(p)).length===0)return r[L]||R;const f=Object.keys(r).filter(a=>o(a));if(f.length==0)return R;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=J(h,u[a]);return r[h.input]}function F(t,e,r){const n=r?[...e,r]:e,s=[];for(const o of n)t[o]?s.push(t[o]):t[l]&&o!==r&&s.push(t[l]);return s.some(o=>o.toLowerCase()!=="none")}function xt(t,e,r={},n=!1){if(n&&Object.keys(r).length===0)return e.isAuthenticated;const s=r.features?.[t];return s?F(s,e.teams):!0}function Dt(t,e){return O(t,{isAuthenticated:!1,teams:[x]},e.access?.rbac||{},e.access?.requiresLogin||!1)}function O(t,e={},r={},n=!1){if(t.slug&&typeof t.slug=="string"&&Object.values(k).some(c=>{const i=c.split(":")[0].replace(/\/$/,"");return t.slug===i||t.slug?.startsWith(c)})||t.slug==="/mcp"&&Q.instance().canAccessFeature("mcp"))return!0;if(n&&Object.keys(r).length===0)return!!e.isAuthenticated;const s=z(r,e.teams||[]),o=t[_]||T(s,t[A]||{});return Object.keys(o||{}).length===0?!1:Object.keys(o).length===1&&o[l]&&o[l].toLowerCase()!=="none"?!0:F(o,e?.teams||[],e?.email)}function gt(t,e,r,n){if(!t.startsWith(I)&&!t.startsWith(j))return!0;const s=t.replace(new RegExp(`^${I}/`),"").replace(new RegExp(`^${j}/`),""),c=s==="."?"":s,i={[A]:{slug:t,fsPath:c},slug:t};return O(i,n,e,r)}function St(t,e,r,n,s){if(!t.startsWith(w))return!0;const o=t.match(/.*\..{64}\.([A-Fa-f0-9]{8})\.[^\.]+$/)?.[1];if(!o)return!0;const c=n[o];if(!c)return!0;const{base:i,ext:f}=y.parse(t),u=i.split(".")[0],h=f.split(".").join(""),d=c==="."?"":c,p={[A]:{slug:t,fsPath:y.posix.join(d,`${u}.${h}`)},slug:t};return O(p,s,e,r)}async function _t(t,e){const{isAuthenticated:r=!1,idpAccessToken:n,federatedAccessToken:s,federatedIdToken:o,...c}=await V(t,e),{teams:i=[]}=c;let f;return r?f=i.filter(u=>u!==x):f=[x],{isAuthenticated:r,idpAccessToken:n,teams:f,claims:c}}function N(t,e,r={},n=!1){if(!t)return t;if(Array.isArray(t)){const s=[];for(const o of t){const c=N(o,e,r,n);c!==void 0&&s.push(c)}return s}if(typeof t=="object"){if(!O(t,e,r,n))return;let s=!1;const o={};for(const c in t){if(c===_||c===A)continue;if(X.has(c)){o[c]=t[c];continue}const i=N(t[c],e,r,n);if(c==="items"&&Array.isArray(i)&&i.length===0&&t[c].length!==0){s=!0;continue}i!==void 0&&(o[c]=i),c==="paths"&&Z(i)&&q(i)}return s?void 0:o}return t}function Lt(t){return typeof t=="string"?t.split(" ").filter(Boolean):Array.isArray(t)?t.map(e=>e.toString()):[]}function jt(t,e){if(!e)return;const r=e.content;if(!r)return e;const n=Object.entries(r).flatMap(([o,c])=>o===L?[[o,c]]:[[o,c],...t.localeFolders.map(i=>[o.startsWith("/")?`/${i.toLocaleLowerCase()}${o}`:y.posix.join(t.localizationFolder,i,o),c])]),s=Object.fromEntries(n);return{...e,content:s}}async function It(t,e){if(!e)return{};const r={},n=new Set((await t.scan()).flatMap(({relativePath:s})=>{const{versionFolderPath:o}=M(s)||{},c=y.dirname(s);return o?[o,c]:c}));for(const s of n)r[$(s)]=s;return r}const m=t=>typeof t=="object"&&t!==null&&!Array.isArray(t);function Z(t){return m(t)&&Object.keys(t).length>0}function q(t){for(const e of Object.keys(t)){const r=t[e];m(r)&&Object.keys(r).length===0&&delete t[e]}}function tt(t){return t?t.split("/").filter(Boolean).some(r=>r.startsWith(".")):!1}const et=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},Ct=async t=>{if(t){if(Object.keys(t).length===0)return{};if(et(t))return rt(t);await U.panicOnContentError(`You are using an incorrect format of ${W("rbac:")} configuration. See: https://redocly.com/docs/realm/access`)}},rt=t=>{const e={...t};if(e.content){const r={};for(const n in e.content)if(e.content[n]!==void 0){const s=B(n);r[s]=e.content[n]}e.content=r}return e};function Ft(t,e){const r=t.fsPath,n=t.slug,s=[];if(Y(e)&&(r||n)){const o=[r,n].filter(v),c=H(e,o);if(c){const i=e?.teamNamePatterns?.map(u=>u.replace("{teamPathSegment}",c.teamPathSegment).replace("{projectRole}","read"))??[];s.push(...i);const f=T({content:{...K(e),...e.content}},t);s.push(...D(f))}else{const i=T(e,t);s.push(...D(i))}}else{const o=T(e,t);s.push(...D(o))}return nt(e,s)}function D(t){if(!t)return[];const e=[],r=l in t?{authenticated:t[l],anonymous:t[l]}:{};for(const[n,s]of Object.entries({...r,...t}))s.toLowerCase()!=="none"&&n!==l&&e.push(n);return e}function nt(t,e){return e.map(n=>G(t,n)??{teamName:n}).map(n=>n.projectRole&&n.projectRole!=="READ"?n.teamName?.toLowerCase().replace(n.projectRole?.toLowerCase?.()??"","read")??"":n.teamName?.toLowerCase()??"")}export{C as PROJECT_ROLES_ORDERED_BY_ACCESS_LEVEL,jt as applyL10nToRbacConfig,St as canAccessAsset,xt as canAccessFeature,O as canAccessResource,gt as canDownloadApiDefinition,nt as expandTeamsForRead,D as extractTeamsFromScopeItems,N as filterDataByAccessDeep,_t as getAuthDetailsFromCookies,Pt as getHigherRole,Ft as getRbacTeamsListForResource,T as getScopeItemsForResource,F as hasScopeAccess,et as isRbacConfigValid,Dt as isResourcePubliclyAccessible,rt as normalizeRbacConfig,Ct as parseRbacConfig,Lt as parseTeamClaimToArray,It as resolveDirectoryHashes};
|
|
@@ -35,12 +35,16 @@ export declare function isSaml2ProviderConfig(providerConfig: AuthProviderConfig
|
|
|
35
35
|
export declare function getAuthProviderLoginParams(idpId: string, providerConfig: AuthProviderConfig): Promise<AuthProviderLoginParams | undefined>;
|
|
36
36
|
export declare function getOidcLoginParams(idpId: string, providerConfig: OidcProviderConfig): Promise<OidcLoginParams>;
|
|
37
37
|
export declare function getSaml2LoginParams(idpId: string, providerConfig: Saml2ProviderConfig): Saml2LoginParams;
|
|
38
|
-
export
|
|
38
|
+
export type OidcTokenResponse = {
|
|
39
39
|
access_token?: string;
|
|
40
40
|
id_token?: string;
|
|
41
|
+
refresh_token?: string;
|
|
42
|
+
expires_in?: number;
|
|
41
43
|
error?: string;
|
|
42
44
|
error_description?: string;
|
|
43
|
-
}
|
|
45
|
+
};
|
|
46
|
+
export declare function oidcExchangeCodeForToken(tokenUrl: string, code: string, redirectUri: string, idpConfig: OidcProviderConfig, extraParams?: Record<string, string>): Promise<OidcTokenResponse>;
|
|
47
|
+
export declare function oidcRefreshTokenGrant(tokenUrl: string, refreshToken: string, idpConfig: OidcProviderConfig): Promise<OidcTokenResponse>;
|
|
44
48
|
export declare function buildOidcLoginUrl(origin: string, { authorizationEndpoint, clientId, responseType, scope, extraParams, idpId, pkce, }: Partial<OidcLoginParams>, redirectTo: string | null, inviteCode?: string, options?: {
|
|
45
49
|
redirectUriOverride?: string;
|
|
46
50
|
sourceOverride?: 'portal' | 'mcp';
|
|
@@ -58,7 +62,10 @@ type McpAuthorizationCodePayload = {
|
|
|
58
62
|
client_id: string;
|
|
59
63
|
redirect_uri: string;
|
|
60
64
|
id_token: string;
|
|
65
|
+
idp_id?: string;
|
|
61
66
|
idp_access_token?: string;
|
|
67
|
+
idp_access_token_enc?: string;
|
|
68
|
+
idp_refresh_token_enc?: string;
|
|
62
69
|
code_challenge?: string;
|
|
63
70
|
code_challenge_method?: string;
|
|
64
71
|
iat: number;
|
|
@@ -66,7 +73,9 @@ type McpAuthorizationCodePayload = {
|
|
|
66
73
|
};
|
|
67
74
|
export declare function createMcpAuthorizationCode(params: {
|
|
68
75
|
idToken: string;
|
|
69
|
-
|
|
76
|
+
idpId?: string;
|
|
77
|
+
idpAccessTokenEnc?: string;
|
|
78
|
+
idpRefreshTokenEnc?: string;
|
|
70
79
|
clientId: string;
|
|
71
80
|
redirectUri: string;
|
|
72
81
|
codeChallenge?: string;
|
|
@@ -74,6 +83,26 @@ export declare function createMcpAuthorizationCode(params: {
|
|
|
74
83
|
ttlSec?: number;
|
|
75
84
|
}): Promise<string>;
|
|
76
85
|
export declare function verifyMcpAuthorizationCode(code: string): Promise<McpAuthorizationCodePayload>;
|
|
86
|
+
export type McpRefreshTokenPayload = {
|
|
87
|
+
type: 'mcp_refresh_token';
|
|
88
|
+
client_id: string;
|
|
89
|
+
idp_id?: string;
|
|
90
|
+
claims: Record<string, unknown>;
|
|
91
|
+
idp_access_token_enc?: string;
|
|
92
|
+
idp_refresh_token_enc?: string;
|
|
93
|
+
session_exp: number;
|
|
94
|
+
iat: number;
|
|
95
|
+
exp: number;
|
|
96
|
+
};
|
|
97
|
+
export declare function createMcpRefreshToken(params: {
|
|
98
|
+
clientId: string;
|
|
99
|
+
idpId?: string;
|
|
100
|
+
claims: Record<string, unknown>;
|
|
101
|
+
idpAccessTokenEnc?: string;
|
|
102
|
+
idpRefreshTokenEnc?: string;
|
|
103
|
+
sessionExp: number;
|
|
104
|
+
}): Promise<string>;
|
|
105
|
+
export declare function verifyMcpRefreshToken(token: string): Promise<McpRefreshTokenPayload>;
|
|
77
106
|
export declare function createMcpSessionResource(sessionId: string | null | undefined): {
|
|
78
107
|
id: string;
|
|
79
108
|
object: 'mcpSession';
|
|
@@ -114,6 +143,7 @@ export declare const oidcJwksCache: {
|
|
|
114
143
|
}) | null>;
|
|
115
144
|
};
|
|
116
145
|
export declare function getOidcMetadata(idpId: string, providerConfig: OidcProviderConfig): Promise<OidcIssuerMetadata>;
|
|
146
|
+
export declare function discoverJwks(ssoConfig: SsoConfig): Promise<void>;
|
|
117
147
|
export declare function getRedoclyTokenPayload(token: string): Promise<Record<string, unknown>>;
|
|
118
148
|
export declare function isRedoclySso(config: OidcProviderConfig): boolean;
|
|
119
149
|
export declare function verifySAMLResponse(responseXml: string, cert: string): Promise<boolean>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import"../node-crypto-polyfill.js";import{DOMParser as
|
|
1
|
+
import"../node-crypto-polyfill.js";import{DOMParser as D}from"@xmldom/xmldom";import{SignedXml as Y}from"xml-crypto";import G from"xpath";import{deflateSync as Q,inflateSync as X}from"fflate";import{createHash as Z}from"crypto";import{ulid as ee}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as b}from"@redocly/config";import{AUTH_URL as te,JWT_SECRET_KEY as y}from"../constants/common.js";import{envConfig as ne}from"../config/env-config.js";import{decryptClaimValue as re}from"../utils/crypto/decrypt-claim-value.js";import{getPathPrefix as oe,withPathPrefix as se}from"@redocly/theme/core/utils";import{ServerRoutes as P}from"../../constants/api.js";import{AuthCookieNames as _,DEFAULT_AUTHENTICATED_TEAM as ae,REQUIRED_OIDC_SCOPES as v}from"../../constants/common.js";import{appendQueryParams as ie}from"../../utils/url/append-query-params.js";import{parseHttpsUrl as N}from"../../utils/url/parse-https-url.js";import{logger as ce}from"../tools/notifiers/logger.js";import{randomString as ue}from"../utils/crypto/random-string.js";import{randomUUID as U}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as h,JwtTokenExpired as le}from"./jwt/types.js";import*as l from"./jwt/jwt.js";import{parseTeamClaimToArray as de}from"../utils/index.js";import{arrayBufferToBase64 as me,decodeBase64 as j,encodeBase64URL as pe,urlSafeBase64 as V}from"./jwt/encode.js";import{formatSamlCertificate as fe}from"./utils/format-saml-certificate.js";function $(e){return e?.type===u.OIDC}function he(e){return e?.type===u.SAML2}async function nt(e,t){if($(t))return ye(e,t);if(he(t))return we(e,t)}async function ye(e,t){const n=await q(e,t),r=new Set((t.scopes||[]).concat(v)),o=t.authorizationRequestCustomParams||{};return{type:u.OIDC,idpId:e,name:"OAuth provider",authorizationEndpoint:n.authorization_endpoint,clientId:t.clientId,responseType:"code",scope:Array.from(r).join(" "),extraParams:o,pkce:t.pkce}}function we(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}function H(e){return Array.from(new Set((e.scopes||[]).concat(v))).join(" ")}async function B(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 rt(e,t,n,r,o={}){return await B(e,r,{scope:H(r),code:t,redirect_uri:F(n),grant_type:"authorization_code",...o})}async function ot(e,t,n){return await B(e,n,{scope:H(n),grant_type:"refresh_token",refresh_token:t})}function Se(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:s,idpId:a,pkce:d},p,x,f){if(!t||!n||!r||!o)return{loginUrl:void 0};const i=new URL(t),w=f?.redirectUriOverride??`${e}${se(P.OIDC_CALLBACK)}`,g={state:U(),idpId:a,redirectUri:w,redirectTo:p,branch:f?.branchOverride??Ae(e),inviteCode:x,source:f?.sourceOverride??"portal",uiLocales:f?.uiLocales},S={};if(d){const m=V(ue(50)),k=V(Z("sha256").update(m).digest("base64")),I="S256";i.searchParams.append("code_challenge",k),i.searchParams.append("code_challenge_method",I),S.code_verifier={value:m,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:oe()||"/"}}}i.searchParams.append("client_id",n),i.searchParams.append("scope",o),i.searchParams.append("response_type",r),i.searchParams.append("redirect_uri",F(w)),i.searchParams.append("state",pe(JSON.stringify(g))),f?.uiLocales&&i.searchParams.append("ui_locales",f.uiLocales);for(const m in s)s[m]!==void 0&&i.searchParams.append(m,s[m]);return{loginUrl:i.toString(),cookies:S}}function st(e,t,n,r){const o=new URL(e);return o.searchParams.append("post_logout_redirect_uri",t),r&&o.searchParams.append("state",r),o.searchParams.append("id_token_hint",n),o.toString()}async function at(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return l.sign({type:"mcp_auth_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},y,h.HS256)}async function it(e){await l.verify(e,y,h.HS256);const{payload:t}=l.decode(e);if(t.type!=="mcp_auth_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 ct(e){const t=Math.floor(Date.now()/1e3);return l.sign({type:"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},y,h.HS256)}async function ut(e){await l.verify(e,y,h.HS256);const{payload:t}=l.decode(e);if(t.type!=="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 lt(e){const t=e||ee(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcpSession",uri:`urn:redocly:realm:mcp:session:${n}`}}function F(e){const t=N(e);if(!t)return e;const n=_e(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,z(e,t);const r=W(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,z(e,t)):e}function Ae(e){const t=N(e);if(t)return W(t.hostname)?.previewBranch}function z(e,t){return e.replace(/^https:\/\/[^/?#]+/i,`https://${t.host}`)}function W(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),a=n.slice(o+2);return!s||!a||a.includes(".")?null:{projectSlug:s,previewBranch:a,after:r}}function _e(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 xe(e){return e.type===u.OIDC}function ge(e){return e.type===u.SAML2}function dt(e,t,n,r,o){return xe(e)?Se(t,e,n,r,{uiLocales:o}):ge(e)?ke(t,e,n,r,o):{}}function ke(e,t,n,r,o){const a=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
|
2
2
|
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
|
3
3
|
Version="2.0"
|
|
4
4
|
ID="_${U()}"
|
|
5
5
|
IssueInstant="${new Date().toISOString()}"
|
|
6
|
-
AssertionConsumerServiceURL="${e}${
|
|
6
|
+
AssertionConsumerServiceURL="${e}${P.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>`,d=Ie(a);return{loginUrl:ie(t.ssoUrl,{SAMLRequest:d,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function Ie(e){return me(Q(new TextEncoder().encode(e)).buffer)}function mt(e){const t=j(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=X(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function pt(e){try{return JSON.parse(j(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function ft(e){const t=new D().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,s=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",a=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],d=a&&a.nodeValue||void 0,p=c(t,"//*[local-name(.)='Audience']/text()")[0],x=p&&p.nodeValue||void 0,i=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",w=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],g=w&&w.nodeValue||"",S=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],m=S&&S.nodeValue||"",k=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],I=Te(k),C={},L=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(L.length)for(const R of L){const M=c(R,"./@Name")[0];if(M.nodeValue){const O=c(R,"./*[local-name(.)='AttributeValue']/text()")[0];O?.nodeValue&&(C[M.nodeValue]=O.nodeValue)}}return{uid:g,success:r,expiresAt:I,issuerId:d,entityId:x,attrs:C,cert:i,nameFormat:m,destination:s}}function Te(e){const t=typeof e?.nodeValue=="string"&&T(Date.parse(e.nodeValue)),n=T(Date.now()),r=T(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function T(e){return Math.floor(e/1e3)}const E={},A={jwks:{}};async function q(e,t){if(!E[e]){const n=t.configurationUrl?await J(t.configurationUrl):t.configuration;E[e]=Ee()?Ce(n):n}return E[e]}function Ee(){const e=ne.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}function Ce(e){if(typeof e!="object"||e===null)return e;const t={...e};for(const n of Object.keys(t)){const r=t[n];typeof r=="string"&&r.includes("://localhost")&&(t[n]=r.replace("://localhost","://host.docker.internal"))}return t}async function Le(e){for(const t of Object.keys(e)){const n=e[t];if(!$(n))continue;const r=await q(t,n);if(r.jwks_uri){const o=await J(r.jwks_uri);for(const s of o.keys)A.jwks[s.kid]={...s,idpId:t}}}}async function J(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function ht(e){return fetch(`${te}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function yt(e){if(!e.configurationUrl)return!1;const t=new URL(e.configurationUrl);return["localhost","127.0.0.1","blueharvest.cloud","bhstage.cloud","cloud.redocly.com","beta.redocly.com","cloud.eu.redocly.com","beta.eu.redocly.com","cba.au.redocly.com"].some(r=>Re(t.hostname,r))}function Re(e,t){return e===t||e.endsWith(`.${t}`)}async function wt(e,t){const n=new D().parseFromString(e,"application/xml"),r=c(n,"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];if(!r)throw new Error("Cannot find Signature in the SAML response");const o=fe(t),s=new Y({publicCert:o});s.loadSignature(r);try{return s.checkSignature(e)}catch{return!1}}function St(e,t,n,r){t==="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"&&(e=n["http://schemas.microsoft.com/identity/claims/objectidentifier"]);let o;(t==="urn:oasis:names:tc:SAML:2.0:nameid-format:email"||t==="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")&&(o=e),t==="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"&&e?.match(/.+@.+/)&&(o=e);const s=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],a=s?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(a?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?de(n[r]):[]}}function K(e,t={}){return e.map(n=>t[n]||n)}async function At(e,t){if(!t)return{};const n=t[_.AUTHORIZATION];if(!n)return{};try{const r=l.decode(n);if(r.header.alg===h.RS256){A.jwks[r.header.kid]===void 0&&await Le(e);const p=A.jwks[r.header.kid];if(!p)return A.jwks[r.header.kid]=null,{};await l.verify(n,p,h.RS256)}else await l.verify(n,y,h.HS256);const o=r.payload.idpId||A.jwks[r.header.kid]?.idpId,s=e[o]||{},a=De(s),d=Oe(s);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...K(r.payload.teams||[],d),..."defaultTeams"in s&&s.defaultTeams||[],...K("teamsClaimName"in s&&r.payload[a||""]||[],d),ae])),name:Me(r.payload),isAuthenticated:!0,idpAccessToken:re(r.payload.idp_access_token,y)||t[_.IDP_ACCESS_TOKEN],federatedAccessToken:t[_.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[_.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof le||ce.error("Malformed JWT token: %s",r.message)}return{}}function Me(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Oe(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function De(e){switch(e.type){case u.SAML2:return e.teamsAttributeName??b;case u.OIDC:return e.teamsClaimName;default:return b}}function c(e,t){return G.select(t,e)||[]}export{dt as buildLoginUrl,Se as buildOidcLoginUrl,st as buildOidcLogoutUrl,ke as buildSAML2LoginUrl,at as createMcpAuthorizationCode,ct as createMcpRefreshToken,lt as createMcpSessionResource,mt as decodeSamlResponse,Le as discoverJwks,Ie as encodeSAML2,St as extractUserClaims,nt as getAuthProviderLoginParams,ye as getOidcLoginParams,q as getOidcMetadata,ht as getRedoclyTokenPayload,we as getSaml2LoginParams,At as getUserParamsFromCookies,Me as getUsernameFromPayload,$ as isOidcProviderConfig,yt as isRedoclySso,he as isSaml2ProviderConfig,rt as oidcExchangeCodeForToken,A as oidcJwksCache,E as oidcMetadataCache,ot as oidcRefreshTokenGrant,pt as parseOidcState,Ae as parsePreviewBranch,ft as parseSamlResponse,F as rewritePreviewAuthRedirectUri,it as verifyMcpAuthorizationCode,ut as verifyMcpRefreshToken,wt as verifySAMLResponse};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{logger as n}from"../tools/notifiers/logger.js";import{envConfig as o}from"../config/env-config.js";import{runApiRoutesWorker as m}from"../api-routes/run-api-routes-worker.js";import{MCP_DOCS_SERVER_HANDLER_ID as
|
|
1
|
+
import{logger as n}from"../tools/notifiers/logger.js";import{envConfig as o}from"../config/env-config.js";import{runApiRoutesWorker as m}from"../api-routes/run-api-routes-worker.js";import{MCP_CREDENTIALS_HANDLER_ID as i,MCP_DOCS_SERVER_HANDLER_ID as u}from"../plugins/mcp/index.js";import{handleMcpRequest as d}from"../plugins/mcp/handlers/handle-mcp-request.js";async function l(r,s,a){try{const e=n.startTiming(),t=r.requestHandlerId===u||r.requestHandlerId===i?await d(r,s,a):await m(r,s,a);return n.infoTime(e,`API request handled "${new URL(s.req.url).pathname}" with status "${t.status}" and content type "${t.headers?.get("content-type")}"`),t.headers.set("X-Source","REALM_API_FUNCTION"),t}catch(e){return n.error(`[${r.requestHandlerId}] ${e.stack}`),e.name==="RequestBodySizeLimitError"?s.json({message:e.message},{status:413}):e.name==="ResponseSizeLimitError"?s.json({message:e.message},{status:500}):e.name==="MemoryUsageLimitError"?s.json({message:e.message},{status:502}):e.name==="TimeoutExceededError"?s.json({message:e.message},{status:504}):o.isDevelopMode?s.json({message:e.message},{status:500}):s.json({message:"Internal server error"},{status:500})}}export{l as handleApiRouteRequest};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TrieRouter as
|
|
1
|
+
import{TrieRouter as _}from"hono/router/trie-router";import{REDOCLY_ROUTE_RBAC as u}from"@redocly/config";import{withPathPrefix as L,withoutPathPrefix as O}from"@redocly/theme/core/utils";import{CACHE_CONTROL_NO_STORE_HEADER_VALUE as w}from"../../../constants/common.js";import{canAccessResource as P}from"../../../utils/rbac.js";import{handleUnauthorizedApiRequest as U}from"../../utils.js";import{handleApiRouteRequest as q}from"../../handle-api-route-request.js";import{sortApiFunctionRoutes as T}from"../../utils/sort-api-function-routes.js";function h(t){return t.headers.set("Cache-Control",w),t}function F(t,o){t.all("*",async(e,m)=>{const{method:p,path:c}=e.req,R=o.getAllApiRoutes(),f=T(R),i=new _;for(const n of f){const C=n.httpMethod?.toUpperCase()||"ALL",E=L(n.slug);i.add(C,E,n)}const r=i.match(p.toUpperCase(),c);if(!r||r[0].length===0)return m();const l=r[0],[s]=l[0],{isAuthenticated:A,claims:{email:d},teams:g}=e.get("auth"),a=O(c);return P({...s,slug:a,[u]:{...s[u],slug:a}},{isAuthenticated:A,email:d,teams:g},o.config.access?.rbac,o.config.access?.requiresLogin)?h(await q(s,e,o)):h(U(e))})}export{F as installApiRoutes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setCookie as R,deleteCookie as Y}from"hono/cookie";import{AuthProviderType as Q,DEFAULT_TEAM_CLAIM_NAME as x}from"@redocly/config";import{withPathPrefix as _,getPathPrefix as y}from"@redocly/theme/core/utils";import{compareURIs as ee}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as F,JWT_SECRET_KEY as v,ORG_SLUG as re,ORG_ID as oe}from"../../constants/common.js";import{AUTH_SEGMENT as ne,ServerRoutes as P}from"../../../constants/api.js";import{AuthCookieNames as L,DEFAULT_COOKIE_EXPIRATION as H}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as T}from"../../telemetry/index.js";import{envConfig as j}from"../../config/env-config.js";import{fetchWithClient as te}from"../../utils/fetch-with-client.js";import{clearAuthCookies as K,clearPostLogoutCookie as $,hasPostLogoutCookie as se,setPostLogoutCookie as z}from"../../utils/cookie.js";import{getAuthProviderLoginParams as ie,isOidcProviderConfig as E,isSaml2ProviderConfig as ae,oidcExchangeCodeForToken as de,buildLoginUrl as ce,decodeSamlResponse as le,extractUserClaims as ue,parseSamlResponse as pe,parseOidcState as ge,verifySAMLResponse as fe,getUsernameFromPayload as me,buildOidcLogoutUrl as he,getOidcMetadata as G,getRedoclyTokenPayload as Ce,isRedoclySso as Ae,rewritePreviewAuthRedirectUri as Ie,parsePreviewBranch as J,buildOidcLoginUrl as we,createMcpSessionResource as D}from"../auth.js";import*as k from"../jwt/jwt.js";import{AlgorithmTypes as U}from"../jwt/types.js";import{handleErrorPageRender as Le}from"../utils.js";import{encodeBase64URL as Re}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as _e}from"./helpers/resolve-ui-locales-for-idp-login.js";async function je(s){if(j.isProductionEnv)return s.newResponse(null,404,{});const{password:e,...r}=await s.req.json(),a=await k.sign({...r,name:r.username||r.email||"Unknown"},v,U.HS256);return R(s,L.AUTHORIZATION,a,{path:y()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),$(s),s.newResponse(null,200,{})}function Ke(){return async s=>{const e=s.get("logger"),r=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${P.LOGIN}/?error=${encodeURIComponent(r)}`;return s.newResponse(null,301,{Location:a})}}function Z(s){if(!s||!s.includes(P.MCP_CALLBACK))return null;try{const e=s.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function ze(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=ge(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK),c=t?Z(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,i=a?.[m];if(!E(i))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await G(m,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,u=e.req.query("code"),h=e.req.query("error");if(h)return t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),Le(e,s,{slug:"/"},403,"403OIDC");if(!u){const A="Code is expected but not present";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:A,error_details:null}]),new Response(`Forbidden: ${A}`,{status:403})}const w=typeof o.redirectUri=="string"?o.redirectUri:new URL(_(P.OIDC_CALLBACK),e.req.url).toString(),C=e.get("cookies")?.code_verifier,l=await de(p,u,w,i,{...i.tokenRequestCustomParams,...C?{code_verifier:C}:{}});if(l.error)return r.error(`Error from OIDC provider: "${l.error}"`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:`Token exchange error: ${l.error}`,error_details:l.error_description||null}]),e.text(`Forbidden: ${l.error_description||l.error}`,403);if(!l?.id_token){const A="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:A,error_details:null}]),new Response(`Forbidden: ${A}`,{status:403})}const{payload:f,header:M}=k.decode(l.id_token),n=M.alg===U.RS256;if(i.audience?.length&&![...q(f.aud||[]),...q(f[F]||[])].some(O=>i.audience?.includes(O))){const O="No valid audience found in id_token";return r.error(`OIDC login error: ${O}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:O,error_details:null}]),new Response(`Forbidden: ${O}`)}const g=n?l.id_token:await k.sign({...f,idpId:m},v,U.HS256);me(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const S=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+H*1e3;if(i.introspectEndpoint){const A=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:l.access_token})});if(A.ok){const b=(await A.json()).ext?.federatedIdentity;b&&(R(e,L.FEDERATED_ACCESS_TOKEN,b.access_token||"",{path:y()||"/",httpOnly:!1,expires:new Date(S)}),R(e,L.FEDERATED_ID_TOKEN,b.id_token||"",{path:y()||"/",httpOnly:!1,expires:new Date(S)}))}else r.warn(`OIDC introspect error: ${A.statusText}`)}if(R(e,L.AUTHORIZATION,g,{path:y()||"/",httpOnly:!0,expires:new Date(S)}),g!==l.id_token&&R(e,L.IDP_ID_TOKEN,l.id_token||"",{path:y()||"/",httpOnly:!0,expires:new Date(S)}),R(e,L.IDP_ACCESS_TOKEN,l.access_token||"",{path:y()||"/",httpOnly:!0,expires:new Date(S)}),Y(e,"code_verifier",{path:y()||"/"}),$(e),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK)){const O=`${e.req.url.split("?")[0].replace(P.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:O})}const V=typeof o.redirectTo=="string"?o.redirectTo:void 0;let W=B(new URL(V||"/",e.req.url).pathname);const X=e.newResponse(null,302,{Location:W});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),X}catch(p){const u=p instanceof Error?p.message:String(p),h=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${u}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:u,error_details:h}]),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const I="Something went wrong";return r.error(`OIDC login error: ${I}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:I,error_details:null}]),e.text(I,500)}}function Ge(s){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return E(t)||(K(e),z(e)),r.info("Logout successful"),e.newResponse(null,200,{});let c;if(E(t)){const i=(await G(o,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),I=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${I}://${p}`,h=J(u),w=h?Re(JSON.stringify({branch:J(u)})):void 0,C=h?`${Ie(u)}/${ne}/logout`:`${u}${_(P.POST_LOGOUT)}`;c=he(i,C,e.get("cookies")?.[L.IDP_ID_TOKEN]||e.get("cookies")?.[L.AUTHORIZATION]||"",w)}}return r.info("Logout successful"),K(e),z(e),e.newResponse(null,302,{Location:c||_("/")})}}function Je(s){return async e=>{const r=s.getConfig().access?.logoutReturnUrl,a=r||_("/");return e.newResponse(null,302,{Location:a})}}function Ze(s){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=j.BH_API_URL,m=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!o)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(_("/"));const c=await te(`${o}/public-user-invites/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(_("/"))):(r.error("Invite error",await c.text()),e.redirect(_("/")));const i=await c.json(),d=new URL(_("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",m(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Ve(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=new URL(e.req.url),m=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||o.host,i=`${t}://${c}`;let d=o.searchParams.get("idpId");const I=o.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const u=o.searchParams.get("mcp_redirect_uri"),h=!!u;if(!a?.[d]){const g="Invalid idpId";if(r.error(`IdP login error: ${g}`),h){const N=Z(I||void 0);T.sendMcpAuthorizationFailedMessage([{...D(N),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const C=_e({localePrefixParam:o.searchParams.get("localePrefix"),l10n:s.getGlobalData()?.l10n}),l=d&&a?await ie(d,a[d]):void 0,f={};for(const g of Object.keys(l?.extraParams||{}))f[g]=o.searchParams.get(g)||l?.extraParams?.[g]||void 0;se(e)&&(f.prompt="login",f.post_logout="1");let M,n={};if(h&&u&&l&&l.type===Q.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=we("",{...l,extraParams:f},I,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:C});M=g.loginUrl,n=g.cookies||{}}else if(l){const g=ce({...l,extraParams:f},i,I,m,C);M=g.loginUrl,n=g.cookies||{}}return Object.keys(n).forEach(g=>{R(e,g,n[g].value,n[g].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:M||new URL(e.req.url).pathname})}}function We(s){return async e=>{const r=e.get("logger"),a=await e.req.formData(),o=a.get("SAMLResponse"),m=a.get("RelayState");if(typeof o!="string"||typeof m!="string"){const n="SAMLResponse is required";return r.error(`SAML2 login error: ${n}`),e.text(`Bad request: ${n}`,400)}const t=le(o),{success:c,uid:i,nameFormat:d,attrs:I,issuerId:p,expiresAt:u}=pe(t),{idpId:h,redirectTo:w}=JSON.parse(m);if(!c){const n="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!u||Math.ceil(Date.now()/1e3)>=u){const n="SAML2 Token Expired";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const C=s.getConfig().ssoDirect?.[h];if(!C||!ae(C)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(C.issuerId&&p&&ee(C.issuerId,p))){const n="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!await fe(t,C.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=ue(i,d,I,C.teamsAttributeName??x);if(!f.sub){const n="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}if(!f.email){const n="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}const M=await k.sign({...f,idpId:h},v,U.HS256);return R(e,L.AUTHORIZATION,M,{path:y()||"/",httpOnly:!0,expires:new Date(u*1e3)}),$(e),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:w||"/"})}}function Xe(s){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=_(B(a.pathname)),m=s.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,w])=>E(w)&&Ae(w));if(!(m&&t))return e.newResponse(null,302,{Location:o});const i=e.req.query("token"),d=i&&await Ce(i);if(!d)return e.newResponse(null,302,{Location:o});if(!q(d[F]||[]).some(w=>w===re||w===oe))return e.newResponse(null,302,{Location:o});const u=await k.sign({...d,idpId:t?.at(0)},v,U.HS256),h=Date.now()+H*1e3;return R(e,L.AUTHORIZATION,u,{path:y()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),$(e),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{je as authorizeHandler,Ve as idpLoginHandler,Ze as inviteHandler,Ge as logoutHandler,ze as oidcCallbackHandler,Je as postLogoutHandler,Ke as redoclyLoginCallbackHandler,Xe as redoclyTokenLoginHandler,We as samlCallbackHandler};
|
|
1
|
+
import{setCookie as L,deleteCookie as U}from"hono/cookie";import{AuthProviderType as Q,DEFAULT_TEAM_CLAIM_NAME as x}from"@redocly/config";import{withPathPrefix as O,getPathPrefix as C}from"@redocly/theme/core/utils";import{compareURIs as ee}from"../../../utils/url/compare-uris.js";import{ensureArray as N}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as H,JWT_SECRET_KEY as v,ORG_SLUG as re,ORG_ID as oe}from"../../constants/common.js";import{AUTH_SEGMENT as ne,ServerRoutes as P}from"../../../constants/api.js";import{AuthCookieNames as _,DEFAULT_COOKIE_EXPIRATION as B}from"../../../constants/common.js";import{sanitizeRedirectPathname as K}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as T}from"../../telemetry/index.js";import{envConfig as j}from"../../config/env-config.js";import{fetchWithClient as te}from"../../utils/fetch-with-client.js";import{clearAuthCookies as z,clearPostLogoutCookie as $,hasPostLogoutCookie as se,setPostLogoutCookie as G}from"../../utils/cookie.js";import{getAuthProviderLoginParams as ie,isOidcProviderConfig as b,isSaml2ProviderConfig as ae,oidcExchangeCodeForToken as de,buildLoginUrl as ce,decodeSamlResponse as le,extractUserClaims as ue,parseSamlResponse as pe,parseOidcState as ge,verifySAMLResponse as fe,getUsernameFromPayload as me,buildOidcLogoutUrl as he,getOidcMetadata as J,getRedoclyTokenPayload as Re,isRedoclySso as _e,rewritePreviewAuthRedirectUri as Ie,parsePreviewBranch as Z,buildOidcLoginUrl as Ce,createMcpSessionResource as D}from"../auth.js";import*as k from"../jwt/jwt.js";import{AlgorithmTypes as E}from"../jwt/types.js";import{handleErrorPageRender as Ae}from"../utils.js";import{encodeBase64URL as we}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as Le}from"./helpers/resolve-ui-locales-for-idp-login.js";async function Ke(s){if(j.isProductionEnv)return s.newResponse(null,404,{});const{password:e,...r}=await s.req.json(),a=await k.sign({...r,name:r.username||r.email||"Unknown"},v,E.HS256);return L(s,_.AUTHORIZATION,a,{path:C()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),$(s),s.newResponse(null,200,{})}function je(){return async s=>{const e=s.get("logger"),r=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${P.LOGIN}/?error=${encodeURIComponent(r)}`;return s.newResponse(null,301,{Location:a})}}function V(s){if(!s||!s.includes(P.MCP_CALLBACK))return null;try{const e=s.split("/"),r=e[e.length-1];if(r){const a=Buffer.from(r,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function ze(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=ge(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK),l=t?V(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,i=a?.[m];if(!b(i))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await J(m,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return r.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,u=e.req.query("code"),h=e.req.query("error");if(h)return t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),Ae(e,s,{slug:"/"},403,"403OIDC");if(!u){const I="Code is expected but not present";return r.error(`OIDC login error: ${I}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:I,error_details:null}]),new Response(`Forbidden: ${I}`,{status:403})}const w=typeof o.redirectUri=="string"?o.redirectUri:new URL(O(P.OIDC_CALLBACK),e.req.url).toString(),R=e.get("cookies")?.code_verifier,c=await de(p,u,w,i,{...i.tokenRequestCustomParams,...R?{code_verifier:R}:{}});if(c.error)return r.error(`Error from OIDC provider: "${c.error}"`),t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:`Token exchange error: ${c.error}`,error_details:c.error_description||null}]),e.text(`Forbidden: ${c.error_description||c.error}`,403);if(!c?.id_token){const I="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${I}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:I,error_details:null}]),new Response(`Forbidden: ${I}`,{status:403})}const{payload:f,header:M}=k.decode(c.id_token),n=M.alg===E.RS256;if(i.audience?.length&&![...N(f.aud||[]),...N(f[H]||[])].some(y=>i.audience?.includes(y))){const y="No valid audience found in id_token";return r.error(`OIDC login error: ${y}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:y,error_details:null}]),new Response(`Forbidden: ${y}`)}const g=n?c.id_token:await k.sign({...f,idpId:m},v,E.HS256);me(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const S=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+B*1e3;if(i.introspectEndpoint){const I=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:c.access_token})});if(I.ok){const q=(await I.json()).ext?.federatedIdentity;q&&(L(e,_.FEDERATED_ACCESS_TOKEN,q.access_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(S)}),L(e,_.FEDERATED_ID_TOKEN,q.id_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(S)}))}else r.warn(`OIDC introspect error: ${I.statusText}`)}if(L(e,_.AUTHORIZATION,g,{path:C()||"/",httpOnly:!0,expires:new Date(S)}),g!==c.id_token&&L(e,_.IDP_ID_TOKEN,c.id_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(S)}),L(e,_.IDP_ACCESS_TOKEN,c.access_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(S)}),c.refresh_token?L(e,_.IDP_REFRESH_TOKEN,c.refresh_token,{path:C()||"/",httpOnly:!0,expires:new Date(S)}):U(e,_.IDP_REFRESH_TOKEN,{path:C()||"/"}),U(e,"code_verifier",{path:C()||"/"}),$(e),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(P.MCP_CALLBACK)){const y=`${e.req.url.split("?")[0].replace(P.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:y})}const W=typeof o.redirectTo=="string"?o.redirectTo:void 0;let X=K(new URL(W||"/",e.req.url).pathname);const Y=e.newResponse(null,302,{Location:X});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),Y}catch(p){const u=p instanceof Error?p.message:String(p),h=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${u}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:u,error_details:h}]),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const A="Something went wrong";return r.error(`OIDC login error: ${A}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(l),error:A,error_details:null}]),e.text(A,500)}}function Ge(s){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return b(t)||(z(e),G(e)),r.info("Logout successful"),e.newResponse(null,200,{});let l;if(b(t)){const i=(await J(o,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),A=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${A}://${p}`,h=Z(u),w=h?we(JSON.stringify({branch:Z(u)})):void 0,R=h?`${Ie(u)}/${ne}/logout`:`${u}${O(P.POST_LOGOUT)}`;l=he(i,R,e.get("cookies")?.[_.IDP_ID_TOKEN]||e.get("cookies")?.[_.AUTHORIZATION]||"",w)}}return r.info("Logout successful"),z(e),G(e),e.newResponse(null,302,{Location:l||O("/")})}}function Je(s){return async e=>{const r=s.getConfig().access?.logoutReturnUrl,a=r||O("/");return e.newResponse(null,302,{Location:a})}}function Ze(s){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=j.BH_API_URL,m=(t,l,i)=>t&&l?`${t} ${l.charAt(0)}`:i;try{if(!o)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(O("/"));const l=await te(`${o}/public-user-invites/${a}`);if(!l.ok)return l.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(O("/"))):(r.error("Invite error",await l.text()),e.redirect(O("/")));const i=await l.json(),d=new URL(O("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",m(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return r.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Ve(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=new URL(e.req.url),m=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||o.protocol.slice(0,-1)||"https",l=e.req.header("x-forwarded-host")||o.host,i=`${t}://${l}`;let d=o.searchParams.get("idpId");const A=o.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const u=o.searchParams.get("mcp_redirect_uri"),h=!!u;if(!a?.[d]){const g="Invalid idpId";if(r.error(`IdP login error: ${g}`),h){const F=V(A||void 0);T.sendMcpAuthorizationFailedMessage([{...D(F),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const R=Le({localePrefixParam:o.searchParams.get("localePrefix"),l10n:s.getGlobalData()?.l10n}),c=d&&a?await ie(d,a[d]):void 0,f={};for(const g of Object.keys(c?.extraParams||{}))f[g]=o.searchParams.get(g)||c?.extraParams?.[g]||void 0;se(e)&&(f.prompt="login",f.post_logout="1");let M,n={};if(h&&u&&c&&c.type===Q.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=Ce("",{...c,extraParams:f},A,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:R});M=g.loginUrl,n=g.cookies||{}}else if(c){const g=ce({...c,extraParams:f},i,A,m,R);M=g.loginUrl,n=g.cookies||{}}return Object.keys(n).forEach(g=>{L(e,g,n[g].value,n[g].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:M||new URL(e.req.url).pathname})}}function We(s){return async e=>{const r=e.get("logger"),a=await e.req.formData(),o=a.get("SAMLResponse"),m=a.get("RelayState");if(typeof o!="string"||typeof m!="string"){const n="SAMLResponse is required";return r.error(`SAML2 login error: ${n}`),e.text(`Bad request: ${n}`,400)}const t=le(o),{success:l,uid:i,nameFormat:d,attrs:A,issuerId:p,expiresAt:u}=pe(t),{idpId:h,redirectTo:w}=JSON.parse(m);if(!l){const n="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!u||Math.ceil(Date.now()/1e3)>=u){const n="SAML2 Token Expired";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const R=s.getConfig().ssoDirect?.[h];if(!R||!ae(R)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(R.issuerId&&p&&ee(R.issuerId,p))){const n="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!await fe(t,R.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=ue(i,d,A,R.teamsAttributeName??x);if(!f.sub){const n="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}if(!f.email){const n="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${n}`),e.text(n,400)}const M=await k.sign({...f,idpId:h},v,E.HS256);return L(e,_.AUTHORIZATION,M,{path:C()||"/",httpOnly:!0,expires:new Date(u*1e3)}),U(e,_.IDP_REFRESH_TOKEN,{path:C()||"/"}),$(e),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:w||"/"})}}function Xe(s){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=O(K(a.pathname)),m=s.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,w])=>b(w)&&_e(w));if(!(m&&t))return e.newResponse(null,302,{Location:o});const i=e.req.query("token"),d=i&&await Re(i);if(!d)return e.newResponse(null,302,{Location:o});if(!N(d[H]||[]).some(w=>w===re||w===oe))return e.newResponse(null,302,{Location:o});const u=await k.sign({...d,idpId:t?.at(0)},v,E.HS256),h=Date.now()+B*1e3;return L(e,_.AUTHORIZATION,u,{path:C()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),U(e,_.IDP_REFRESH_TOKEN,{path:C()||"/"}),$(e),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{Ke as authorizeHandler,Ve as idpLoginHandler,Ze as inviteHandler,Ge as logoutHandler,ze as oidcCallbackHandler,Je as postLogoutHandler,je as redoclyLoginCallbackHandler,Xe as redoclyTokenLoginHandler,We as samlCallbackHandler};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Handler } from 'hono';
|
|
2
|
+
import type { SsoConfig } from '@redocly/config';
|
|
2
3
|
import type { Store } from '../../../store.js';
|
|
3
4
|
export type McpContextPayload = {
|
|
4
5
|
isMcpFlow: boolean;
|
|
@@ -10,6 +11,7 @@ export type McpContextPayload = {
|
|
|
10
11
|
mcpCodeChallengeMethod?: string | null;
|
|
11
12
|
timestamp: number;
|
|
12
13
|
};
|
|
14
|
+
export declare const SUPPORTED_MCP_GRANT_TYPES: readonly ['authorization_code', 'refresh_token'];
|
|
13
15
|
export declare function verifyPkce(codeChallenge: string, codeChallengeMethod: string | undefined, codeVerifier: string): boolean;
|
|
14
16
|
export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
|
|
15
17
|
export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
|
|
@@ -20,7 +22,8 @@ export declare function mcpOAuthAuthorizationServerHandler(store: Store): Handle
|
|
|
20
22
|
* Also it is simulating the client registration process.
|
|
21
23
|
*/
|
|
22
24
|
export declare function mcpDynamicClientRegistrationHandler(store: Store): Handler;
|
|
25
|
+
export declare function resolveMcpIdpId(ssoDirect: SsoConfig | undefined): string | undefined;
|
|
23
26
|
export declare function mcpAuthorizationHandler(store: Store): Handler;
|
|
24
|
-
export declare function mcpTokenPortalHandler(): Handler;
|
|
25
|
-
export declare function mcpCallbackHandler(): Handler;
|
|
27
|
+
export declare function mcpTokenPortalHandler(store: Store): Handler;
|
|
28
|
+
export declare function mcpCallbackHandler(store: Store): Handler;
|
|
26
29
|
//# sourceMappingURL=mcp-oauth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCookie as S}from"hono/cookie";import{createHash as D,timingSafeEqual as L}from"node:crypto";import{ulid as H}from"ulid";import{isMcpContentProtected as C}from"../../../plugins/mcp/auth/auth-handlers.js";import{isRedoclySso as q,isOidcProviderConfig as z}from"../../auth.js";import{AUTH_URL as M,JWT_SECRET_KEY as A}from"../../../constants/common.js";import{ServerRoutes as u}from"../../../../constants/api.js";import{AuthCookieNames as k}from"../../../../constants/common.js";import{withPathPrefix as _}from"@redocly/theme/core/utils";import{telemetry as m}from"../../../telemetry/index.js";import{envConfig as I}from"../../../config/env-config.js";import{createMcpAuthorizationCode as $,verifyMcpAuthorizationCode as N,createMcpSessionResource as f}from"../../auth.js";import*as h from"../../jwt/jwt.js";import{AlgorithmTypes as w}from"../../jwt/types.js";import{getRequestOrigin as E}from"../../utils/get-request-origin.js";import{fetchClientMetadataDocument as b,isClientMetadataDocumentUrl as P,isRedirectUriRegistered as j}from"./mcp-client-metadata.js";const o=(r,e,n=200,a)=>r.json(e,n,{"Content-Type":"application/json",...a??{}}),U=new Set(["S256"]),B="https://claude.ai/oauth/claude-code-client-metadata";function F(r,e,n){if(!U.has(e||""))return!1;const a=D("sha256").update(n).digest("base64url"),t=Buffer.from(a),s=Buffer.from(r);return t.length!==s.length?!1:L(t,s)}async function K(r){const e=Math.floor(Date.now()/1e3);return h.sign({type:"mcp_context",...r,iat:e,exp:e+600},A,w.HS256)}async function G(r){await h.verify(r,A,w.HS256);const{payload:e}=h.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function se(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);if(e.req.method!=="GET")return o(e,{error:"Method not allowed"},405,{Allow:"GET"});const n=E(e);return o(e,{resource:`${n}${_("/mcp")}`,authorization_servers:[n,M].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${n}${u.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function ce(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);const n=E(e);return o(e,{issuer:M||"",authorization_endpoint:`${n}${_(u.MCP_AUTHORIZATION)}`,token_endpoint:`${n}${_(u.MCP_TOKEN_PORTAL)}`,jwks_uri:`${M||""}/.well-known/jwks.json`,scopes_supported:["openid","profile","email","offline_access"],registration_endpoint:`${n}${_(u.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:["authorization_code","refresh_token","client_credentials"],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0})}}function de(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{return o(e,{client_id:I.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:["authorization_code","refresh_token"],response_types:["code"],scope:"openid offline email",subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(n){return o(e,{error:"invalid_request",error_description:n?.message||"Unable to register client"},500)}}}function le(r){return async e=>{if(!C(r))return o(e,{error:"not_found"},404);const n=new URL(e.req.url),{searchParams:a}=n,t=a.get("redirect_uri"),s=a.get("client_id"),i=a.get("code_challenge"),d=a.get("code_challenge_method"),l=H();if(i&&!U.has(d||""))return o(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...U].join(", ")}`},400);if(P(s)&&!i)return o(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(P(s)&&t)try{const c=await b(s),p=s===B;if(!j(t,c.redirect_uris,{ignorePort:p}))return o(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(c){const p=c instanceof Error?c.message:String(c);return o(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${p}`},400)}m.sendMcpAuthorizationStartedMessage([{...f(l),redirect_uri:t||null}]);const v=E(e),T={isMcpFlow:!0,originalRedirectUri:t,mcpClientId:s,mcpState:a.get("state"),mcpSessionId:l,mcpCodeChallenge:i,mcpCodeChallengeMethod:i?d:null,timestamp:Date.now()};try{const c=await K(T),p=new URL(_(u.IDP_LOGIN),v);p.searchParams.set("redirectTo",`${u.MCP_CALLBACK}/${c}`);const g=r.getConfig().ssoDirect||{},R=Object.entries(g).find(([,O])=>z(O)&&q(O))?.[0]??Object.keys(g)[0];return R?(p.searchParams.set("idpId",R),e.redirect(p.toString())):o(e,{error:"not_found"},404)}catch(c){const p=c instanceof Error?c.message:String(c),g=c instanceof Error?c.stack:String(c);m.sendMcpAuthorizationFailedMessage([{...f(l),error:p,error_details:g}]);const y=new URL(_(`${M}/oauth2/auth`));return y.search=a.toString(),e.redirect(y.toString())}}}function pe(){return async r=>{if(r.req.method!=="POST")return o(r,{error:"Method not allowed"},405);try{const e=await r.req.formData(),n=e.get("grant_type"),a=e.get("code"),t=e.get("redirect_uri")||void 0,s=e.get("code_verifier")||void 0;if(n!=="authorization_code"||!a)return o(r,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await N(a);if(t&&t!==i.redirect_uri)return o(r,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(i.code_challenge){if(!s)return o(r,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!F(i.code_challenge,i.code_challenge_method,s))return o(r,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(I.OAUTH_CLIENT_ID&&i.client_id&&!P(i.client_id)&&i.client_id!==I.OAUTH_CLIENT_ID)return o(r,{error:"invalid_client",error_description:"Client mismatch"},400);const d=i.id_token;if(typeof d!="string"||d.length===0)return o(r,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let l=d;if(i.idp_access_token){const{payload:T,header:{kid:c}}=h.decode(d);l=await h.sign({...T,idp_access_token:i.idp_access_token},A,w.HS256,c)}return o(r,{access_token:l,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:d},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return o(r,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(e){const n=e instanceof Error?e.message:String(e);return o(r,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}function ue(){return async r=>{const e=new URL(r.req.url);let n=e.searchParams.get("context");if(!n&&e.pathname.startsWith(_(`${u.MCP_CALLBACK}/`))){const t=e.pathname.split("/");n=t[t.length-1]}if(!n)return m.sendMcpAuthorizationFailedMessage([{...f(null),error:"Missing context parameter",error_details:null}]),r.text("Missing context parameter",400);let a=null;try{const t=await G(n);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=S(r,k.IDP_ID_TOKEN)||S(r,k.AUTHORIZATION),i=S(r,k.IDP_ACCESS_TOKEN);if(!s)return m.sendMcpAuthorizationFailedMessage([{...f(a),error:"Authentication required",error_details:null}]),r.text("Authentication required",401);const d=await $({idToken:s,idpAccessToken:i||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(t.originalRedirectUri);return l.searchParams.set("code",d),t.mcpState&&l.searchParams.set("state",t.mcpState),m.sendMcpAuthorizationCompletedMessage([{...f(a),redirect_uri:t.originalRedirectUri||null}]),r.redirect(l.toString())}catch(t){const s=t instanceof Error?t.message:String(t),i=t instanceof Error?t.stack:String(t);return m.sendMcpAuthorizationFailedMessage([{...f(a),error:s,error_details:i}]),r.text(`Invalid MCP callback: ${s}`,400)}}}export{K as createMcpContextToken,le as mcpAuthorizationHandler,ue as mcpCallbackHandler,de as mcpDynamicClientRegistrationHandler,ce as mcpOAuthAuthorizationServerHandler,se as mcpOAuthProtectedResourceHandler,pe as mcpTokenPortalHandler,G as verifyAndParseMcpContextToken,F as verifyPkce};
|
|
1
|
+
import{getCookie as A}from"hono/cookie";import{createHash as J,timingSafeEqual as V}from"node:crypto";import{ulid as Y}from"ulid";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{isRedoclySso as Z,isOidcProviderConfig as v}from"../../auth.js";import{AUTH_URL as R,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as W}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as U}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as M}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import{createMcpAuthorizationCode as x,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as z,verifyMcpRefreshToken as X,createMcpSessionResource as y,oidcJwksCache as F,oidcRefreshTokenGrant as ee,getOidcMetadata as re,discoverJwks as te}from"../../auth.js";import*as S from"../../jwt/jwt.js";import{AlgorithmTypes as q}from"../../jwt/types.js";import{getRequestOrigin as L}from"../../utils/get-request-origin.js";import{encryptClaimValue as H}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as ne}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as oe,isClientMetadataDocumentUrl as N,isRedirectUriRegistered as ie}from"./mcp-client-metadata.js";const s=(r,e,n=200,i)=>r.json(e,n,{"Content-Type":"application/json",...i??{}}),j=new Set(["S256"]),$=["authorization_code","refresh_token"],E=["openid","profile","email","offline_access"],se="https://claude.ai/oauth/claude-code-client-metadata";function ce(r,e,n){if(!j.has(e||""))return!1;const i=J("sha256").update(n).digest("base64url"),c=Buffer.from(i),o=Buffer.from(r);return c.length!==o.length?!1:V(c,o)}async function de(r){const e=Math.floor(Date.now()/1e3);return S.sign({type:"mcp_context",...r,iat:e,exp:e+600},m,q.HS256)}async function ae(r){await S.verify(r,m,q.HS256);const{payload:e}=S.decode(r);if(e.type!=="mcp_context")throw new Error("Invalid context token type");return e}function Re(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 n=L(e);return s(e,{resource:`${n}${I("/mcp")}`,authorization_servers:[n,R].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${n}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]})}}function Ue(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=L(e);return s(e,{issuer:R||"",authorization_endpoint:`${n}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${n}${I(T.MCP_TOKEN_PORTAL)}`,jwks_uri:`${R||""}/.well-known/jwks.json`,scopes_supported:[...E],registration_endpoint:`${n}${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})}}function Oe(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:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201)}catch(n){return s(e,{error:"invalid_request",error_description:n?.message||"Unable to register client"},500)}}}function B(r){const e=r||{},n=Object.keys(e),i=n.find(o=>v(e[o])&&Z(e[o])),c=n.find(o=>v(e[o]));return i||c||n[0]}function De(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=new URL(e.req.url),{searchParams:i}=n,c=i.get("redirect_uri"),o=i.get("client_id"),a=i.get("code_challenge"),t=i.get("code_challenge_method"),p=Y();if(a&&!j.has(t||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...j].join(", ")}`},400);if(N(o)&&!a)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(N(o)&&c)try{const d=await oe(o),l=o===se;if(!ie(c,d.redirect_uris,{ignorePort:l}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const l=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${l}`},400)}M.sendMcpAuthorizationStartedMessage([{...y(p),redirect_uri:c||null}]);const f=L(e),_={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:o,mcpState:i.get("state"),mcpSessionId:p,mcpCodeChallenge:a,mcpCodeChallengeMethod:a?t:null,timestamp:Date.now()};try{const d=await de(_),l=new URL(I(T.IDP_LOGIN),f);l.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const k=B(r.getConfig().ssoDirect);return k?(l.searchParams.set("idpId",k),e.redirect(l.toString())):s(e,{error:"not_found"},404)}catch(d){const l=d instanceof Error?d.message:String(d),k=d instanceof Error?d.stack:String(d);M.sendMcpAuthorizationFailedMessage([{...y(p),error:l,error_details:k}]);const C=new URL(I(`${R}/oauth2/auth`));return C.search=i.toString(),e.redirect(C.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),n=await S.sign({...r.claims,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,q.HS256,r.kid),i=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):b);return{accessToken:n,expiresIn:i}}async function pe(r,e,n){const i=g=>s(r,{error:"invalid_grant",error_description:g},400),c=r.get("logger"),o=n.get("refresh_token"),a=n.get("client_id")||void 0;if(!o)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let t;try{t=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(a&&a!==t.client_id)return i("Client mismatch");const p=t.idp_id?e.getConfig().ssoDirect?.[t.idp_id]:void 0;let f,_,d=t.idp_access_token_enc,l=t.idp_refresh_token_enc;if(t.idp_refresh_token_enc){let g;try{g=ne(t.idp_refresh_token_enc,m)}catch{return i("Invalid refresh token")}if(!t.idp_id||!v(p))return i("Unknown identity provider");let u;try{const h=await re(t.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&&(l=G(u.refresh_token,m)),u.access_token&&(d=H(u.access_token,m)),u.id_token)try{const h=S.decode(u.id_token);f=h.payload,_=h.header.kid}catch{return i("Invalid token response from identity provider")}}const k=v(p)&&p.tokenExpirationTime||b,C=f??t.claims,w=await K({claims:C,kid:_,idpId:t.idp_id,idpAccessTokenEnc:d,ttlSec:f?void 0:k}),O=await z({clientId:t.client_id,idpId:t.idp_id,claims:C,idpAccessTokenEnc:d,idpRefreshTokenEnc:l,sessionExp:t.session_exp});return s(r,{access_token:w.accessToken,token_type:"Bearer",expires_in:w.expiresIn,refresh_token:O,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function qe(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const n=await e.req.formData(),i=n.get("grant_type"),c=n.get("code"),o=n.get("redirect_uri")||void 0,a=n.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 pe(e,r,n);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const t=await Q(c);if(o&&o!==t.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(t.code_challenge){if(!a)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!ce(t.code_challenge,t.code_challenge_method,a))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&t.client_id&&!N(t.client_id)&&t.client_id!==D.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const p=t.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 f=t.idp_access_token_enc??(t.idp_access_token?H(t.idp_access_token,m):void 0),_=S.decode(p),{accessToken:d,expiresIn:l}=await K({claims:_.payload,kid:_.header.kid,idpId:t.idp_id,idpAccessTokenEnc:f}),C=Math.floor(Date.now()/1e3)+W,w=await z({clientId:t.client_id,idpId:t.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:t.idp_refresh_token_enc,sessionExp:C}),O={access_token:d,token_type:"Bearer",expires_in:l,refresh_token:w,scope:E.join(" "),id_token:p};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(t){return e.get("logger")?.warn("MCP token exchange failed: %s",t instanceof Error?t.message:String(t)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(n){const i=n instanceof Error?n.message:String(n);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:i},500)}}}async function le(r,e){try{const{payload:n,header:i}=S.decode(r);if(typeof n.idpId=="string"&&n.idpId)return n.idpId;if(i.kid){F.jwks[i.kid]==null&&await te(e||{});const c=F.jwks[i.kid];if(c?.idpId)return c.idpId}}catch{}return B(e)}function Le(r){return async e=>{const n=new URL(e.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const o=n.pathname.split("/");i=o[o.length-1]}if(!i)return M.sendMcpAuthorizationFailedMessage([{...y(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let c=null;try{const o=await ae(i);if(c=o.mcpSessionId||null,!o.isMcpFlow||!o.originalRedirectUri)throw new Error("Invalid MCP context");const a=A(e,U.IDP_ID_TOKEN)||A(e,U.AUTHORIZATION),t=A(e,U.IDP_ACCESS_TOKEN),p=A(e,U.IDP_REFRESH_TOKEN);if(!a)return M.sendMcpAuthorizationFailedMessage([{...y(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const f=await x({idToken:a,idpId:await le(a,r.getConfig().ssoDirect),idpAccessTokenEnc:t?H(t,m):void 0,idpRefreshTokenEnc:p?G(p,m):void 0,clientId:o.mcpClientId||"",redirectUri:o.originalRedirectUri,codeChallenge:o.mcpCodeChallenge||void 0,codeChallengeMethod:o.mcpCodeChallengeMethod||void 0,ttlSec:600}),_=new URL(o.originalRedirectUri);return _.searchParams.set("code",f),o.mcpState&&_.searchParams.set("state",o.mcpState),M.sendMcpAuthorizationCompletedMessage([{...y(c),redirect_uri:o.originalRedirectUri||null}]),e.redirect(_.toString())}catch(o){const a=o instanceof Error?o.message:String(o),t=o instanceof Error?o.stack:String(o);return M.sendMcpAuthorizationFailedMessage([{...y(c),error:a,error_details:t}]),e.text(`Invalid MCP callback: ${a}`,400)}}}export{$ as SUPPORTED_MCP_GRANT_TYPES,de as createMcpContextToken,De as mcpAuthorizationHandler,Le as mcpCallbackHandler,Oe as mcpDynamicClientRegistrationHandler,Ue as mcpOAuthAuthorizationServerHandler,Re as mcpOAuthProtectedResourceHandler,qe as mcpTokenPortalHandler,B as resolveMcpIdpId,ae as verifyAndParseMcpContextToken,ce as verifyPkce};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{corsMiddleware as
|
|
1
|
+
import{corsMiddleware as o}from"../../middleware/corsMiddleware.js";import{ServerRoutes as a}from"../../../../constants/api.js";import{EntitlementsProvider as C}from"../../../entitlements/entitlements-provider.js";import{withPathPrefix as i}from"@redocly/theme/core/utils";import{AGENT_CARD_SLUG as l,A2A_SLUG as t,MCP_SERVER_CARD_SLUG as A}from"../../../../constants/common.js";import{mcpOAuthProtectedResourceHandler as d,mcpOAuthAuthorizationServerHandler as _,mcpDynamicClientRegistrationHandler as r,mcpAuthorizationHandler as R,mcpTokenPortalHandler as E,mcpCallbackHandler as c}from"./mcp-oauth.js";import{mcpServerCardHandler as P}from"./mcp-server-card.js";import{agentCardHandler as T}from"./agent-card.js";import{a2aHandler as s}from"./a2a.js";function N(e,n){const m=n.getConfig().mcp,p=C.instance().canAccessFeature("mcp");!m?.hide&&!m?.docs?.hide&&p&&(e.get(i(A),P(n)),e.get(i(l),T(n)),e.use(i(t),o({allowMethods:["POST"],exposeHeaders:["Link"]})),e.post(i(t),s(n)),e.get(`${a.MCP_OAUTH_PROTECTED_RESOURCE}${i("/mcp")}`,d(n)),e.get(a.MCP_OAUTH_AUTHORIZATION_SERVER,_(n)),e.post(i(a.MCP_DYNAMIC_CLIENT_REGISTRATION),r(n)),e.get(i(a.MCP_AUTHORIZATION),R(n)),e.post(i(a.MCP_TOKEN_PORTAL),E(n)),e.get(i(a.MCP_CALLBACK),c(n)),e.get(i(`${a.MCP_CALLBACK}/*`),c(n)))}export{N as installMcpRoutes};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import l from"workerpool";import{mkdirSync as m}from"node:fs";import{basename as u,dirname as c,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";import{envConfig as n}from"../config/env-config.js";import{SandboxContext as v}from"../utils/sandbox-context.js";const h=(()=>{const o=c(p(import.meta.url));return u(o)==="chunks"?c(o):o})();class
|
|
1
|
+
import l from"workerpool";import{mkdirSync as m}from"node:fs";import{basename as u,dirname as c,resolve as f}from"node:path";import{fileURLToPath as p}from"node:url";import{envConfig as n}from"../config/env-config.js";import{SandboxContext as v}from"../utils/sandbox-context.js";const h=(()=>{const o=c(p(import.meta.url));return u(o)==="chunks"?c(o):o})();class P{#t=null;#r;constructor(r){this.#r=r,this.#r.lazy||(this.#t=this.#e())}#e(){const{workerScript:r,lazy:t,sandbox:i,resolveForkEnv:s,...e}=this.#r,a=this.#s(r);return s&&e.forkOpts&&(e.forkOpts={...e.forkOpts,env:s()}),this.#o(i)?l.pool(a,{...e,forkOpts:this.#i(e.forkOpts)}):l.pool(a,{...e})}#o(r){return!!r&&n.isReunite&&n.REDOCLY_DISABLE_API_FUNCTION_SANDBOX!==!0}#i(r){const t=v.resolve(h);try{m(t.tmpDir,{recursive:!0})}catch{}return{...r,cwd:t.serverOutDir,execArgv:[...t.execArgv,...r?.execArgv??[]],env:{...r?.env,...t.env}}}#s(r){let t=r;return!t.endsWith(".js")&&!t.endsWith(".mjs")&&(t=`${t}.${n.isDevelopMode?"js":"mjs"}`),t.startsWith(".")?f(h,t):t}#n(){return this.#t||(this.#t=this.#e()),this.#t}async exec(r,t,i){let e=this.#n().exec(r,t);return i?.timeout&&(e=e.timeout(i.timeout)),await e}async terminate(){this.#t&&(await this.#t.terminate(),this.#t=null)}}export{P as WorkerPool};
|
|
@@ -35,9 +35,14 @@ export type PlanGates = {
|
|
|
35
35
|
[FEATURE.aiSearchLimit]: number;
|
|
36
36
|
[FEATURE.kvStorageLimit]: number;
|
|
37
37
|
[FEATURE.mcp]: boolean;
|
|
38
|
+
[FEATURE.mcpGateway]: boolean;
|
|
39
|
+
[FEATURE.mcpMode]: 'tools' | 'code';
|
|
40
|
+
[FEATURE.gatewayFetchRatePerMin]: number;
|
|
41
|
+
[FEATURE.gatewayFetchQuotaPerDay]: number;
|
|
42
|
+
[FEATURE.gatewayFetchBudgetPerExec]: number;
|
|
38
43
|
};
|
|
39
44
|
export type Entitlements = {
|
|
40
|
-
[key in Feature]: boolean | number;
|
|
45
|
+
[key in Feature]: boolean | number | string;
|
|
41
46
|
};
|
|
42
47
|
export type ProductPlan = 'pro' | 'enterprise';
|
|
43
48
|
//# sourceMappingURL=entitlements.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { InitOptions } from 'i18next';
|
|
2
|
+
import type { GlobalData } from '../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the i18next init config shared by the client runtime (`initL10n`) and the
|
|
5
|
+
* build-time translate used in `renderForLlms` (`createBuildTranslate`), so both
|
|
6
|
+
* resolve translations identically: same default-translation merge, same locale
|
|
7
|
+
* fallback chain.
|
|
8
|
+
*
|
|
9
|
+
* @param l10n - The project's l10n data (`defaultLocale`, configured `locales`, and
|
|
10
|
+
* `translations` overrides), or `undefined` for a project with no l10n config.
|
|
11
|
+
* @param locale - The `lng` to set on the resulting config (the instance's `resources`
|
|
12
|
+
* still cover every configured locale, so a fixed translator for any other locale can
|
|
13
|
+
* still be derived from the same config via `i18next.getFixedT(otherLocale)`).
|
|
14
|
+
* @returns An i18next `InitOptions` object with merged default + project translations
|
|
15
|
+
* and a `code → langPart → defaultLocale` fallback chain.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const config = await buildI18nConfig(l10n, 'ja');
|
|
20
|
+
* await i18next.init(config);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildI18nConfig(l10n: GlobalData['l10n'], locale: InitOptions['lng']): Promise<InitOptions>;
|
|
24
|
+
//# sourceMappingURL=build-i18n-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{DEFAULT_LOCALE_PLACEHOLDER as u}from"../../constants/common.js";import{defaultTranslationImports as c}from"../../constants/l10n/default-translations.js";async function L(s,r){const a=s?.defaultLocale||u,e=s?.translations??{},i=s?.locales?[...new Set(s.locales.flatMap(t=>[t.code,t.code.split("-")[0]]))]:[a],l={};for(const t of i){const n=await c[t]?.()||{};l[t]={translation:{...n,...e[t]?.translation}}}return{load:"currentOnly",lng:r,nonExplicitSupportedLngs:!0,fallbackLng:t=>{if(!t)return[a];const n=t.split("-")[0];if(t===a)return n!==t?[n,a]:[a];const o=[t];return n!==t&&o.push(n),o.push(a),o},interpolation:{escapeValue:!1},debug:!1,resources:{...l}}}export{L as buildI18nConfig};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TFunction, TOptions } from '@redocly/theme/core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a raw i18next `t`-like function (`useTranslate`'s `i18next.t`, or a
|
|
4
|
+
* build-time `instance.getFixedT(locale)`) with the shared value/defaultValue/options
|
|
5
|
+
* resolution used across the client and build-time translate paths.
|
|
6
|
+
*
|
|
7
|
+
* @param rawT - The underlying i18next translate function to resolve keys against.
|
|
8
|
+
* @returns A `TFunction` that resolves `value` via `rawT`, falling back to
|
|
9
|
+
* `defaultValue` (when passed as the second argument or via `options.defaultValue`)
|
|
10
|
+
* and then to `value` itself when the key is untranslated.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const translate = createTranslate(i18next.t);
|
|
15
|
+
* translate('greeting.hello', 'Hello'); // translated string, or 'Hello' if missing
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function createTranslate(rawT: (key: string, opts?: TOptions) => string): TFunction;
|
|
19
|
+
//# sourceMappingURL=create-translate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function u(r){return(n,t)=>{const e=typeof t=="string"?t:void 0,f=e?void 0:t;return n?r(n,{defaultValue:e,...f})||e||n||"":e??""}}export{u as createTranslate};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/redoc-reef",
|
|
3
|
-
"version": "0.136.0-next.
|
|
3
|
+
"version": "0.136.0-next.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@opentelemetry/semantic-conventions": "1.40.0",
|
|
34
34
|
"@redocly/ajv": "8.18.0",
|
|
35
35
|
"@redocly/mcp-typescript-sdk": "1.18.1",
|
|
36
|
-
"@redocly/openapi-core": "2.
|
|
36
|
+
"@redocly/openapi-core": "2.40.0",
|
|
37
37
|
"@shikijs/transformers": "3.21.0",
|
|
38
38
|
"@tanstack/react-query": "5.62.3",
|
|
39
39
|
"@tanstack/react-table": "8.21.3",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"fflate": "0.7.4",
|
|
56
56
|
"flexsearch": "0.7.43",
|
|
57
57
|
"graphql": "16.12.0",
|
|
58
|
-
"hono": "4.12.
|
|
58
|
+
"hono": "4.12.27",
|
|
59
59
|
"htmlparser2": "8.0.2",
|
|
60
60
|
"i18next": "22.4.15",
|
|
61
61
|
"is-glob": "4.0.3",
|
|
62
|
-
"js-yaml": "4.
|
|
62
|
+
"js-yaml": "4.3.0",
|
|
63
63
|
"lru-cache": "11.1.0",
|
|
64
64
|
"minimatch": "10.2.4",
|
|
65
65
|
"mri": "1.2.0",
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"tty-browserify": "0.0.1",
|
|
87
87
|
"typesense": "2.1.0",
|
|
88
88
|
"ulid": "^2.3.0",
|
|
89
|
+
"undici": "^6.27.0",
|
|
89
90
|
"web-vitals": "3.3.1",
|
|
90
91
|
"workerpool": "9.2.0",
|
|
91
92
|
"ws": "^8.21.0",
|
|
@@ -93,14 +94,14 @@
|
|
|
93
94
|
"xpath": "0.0.34",
|
|
94
95
|
"yaml-ast-parser": "0.0.43",
|
|
95
96
|
"zod": "^3.25.76",
|
|
96
|
-
"@redocly/asyncapi-docs": "1.13.0-next.
|
|
97
|
-
"@redocly/config": "0.
|
|
98
|
-
"@redocly/graphql-docs": "1.13.0-next.
|
|
99
|
-
"@redocly/openapi-docs": "3.24.0-next.
|
|
97
|
+
"@redocly/asyncapi-docs": "1.13.0-next.6",
|
|
98
|
+
"@redocly/config": "0.53.0",
|
|
99
|
+
"@redocly/graphql-docs": "1.13.0-next.6",
|
|
100
|
+
"@redocly/openapi-docs": "3.24.0-next.6",
|
|
100
101
|
"@redocly/portal-legacy-ui": "0.18.0",
|
|
101
|
-
"@redocly/portal-plugin-mock-server": "0.21.0-next.
|
|
102
|
+
"@redocly/portal-plugin-mock-server": "0.21.0-next.6",
|
|
102
103
|
"@redocly/realm-asyncapi-sdk": "0.14.0-next.0",
|
|
103
|
-
"@redocly/theme": "0.68.0-next.
|
|
104
|
+
"@redocly/theme": "0.68.0-next.4"
|
|
104
105
|
},
|
|
105
106
|
"peerDependencies": {
|
|
106
107
|
"react": "^19.2.4",
|