@redocly/realm 0.134.0-next.4 → 0.134.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/client/app/hooks/useUserClaims.js +1 -1
  3. package/dist/client/app/search/useSearch.d.ts +2 -0
  4. package/dist/client/app/search/useSearch.js +1 -1
  5. package/dist/constants/common.d.ts +7 -0
  6. package/dist/constants/common.js +1 -1
  7. package/dist/constants/l10n/langs/ar.js +1 -1
  8. package/dist/constants/l10n/langs/de.js +1 -1
  9. package/dist/constants/l10n/langs/en.js +1 -1
  10. package/dist/constants/l10n/langs/es.js +1 -1
  11. package/dist/constants/l10n/langs/fr.js +1 -1
  12. package/dist/constants/l10n/langs/hi.js +1 -1
  13. package/dist/constants/l10n/langs/it.js +1 -1
  14. package/dist/constants/l10n/langs/ja.js +1 -1
  15. package/dist/constants/l10n/langs/ko.js +1 -1
  16. package/dist/constants/l10n/langs/pl.js +1 -1
  17. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  18. package/dist/constants/l10n/langs/pt.js +1 -1
  19. package/dist/constants/l10n/langs/ru.js +1 -1
  20. package/dist/constants/l10n/langs/uk.js +1 -1
  21. package/dist/constants/l10n/langs/zh.js +1 -1
  22. package/dist/server/plugins/markdown/search/to-markdown.js +14 -14
  23. package/dist/server/plugins/markdown/search/walk-sections.js +1 -1
  24. package/dist/server/plugins/mcp/constants.d.ts +1 -0
  25. package/dist/server/plugins/mcp/constants.js +1 -1
  26. package/dist/server/plugins/mcp/handlers/docs-mcp-handler.js +1 -1
  27. package/dist/server/plugins/mcp/handlers/errors.js +1 -1
  28. package/dist/server/plugins/mcp/handlers/sse-keepalive.d.ts +15 -0
  29. package/dist/server/plugins/mcp/handlers/sse-keepalive.js +3 -0
  30. package/dist/server/plugins/mcp/utils/jwt.js +1 -1
  31. package/dist/server/plugins/mcp/workers/execute-mcp-tool.js +1 -1
  32. package/dist/server/utils/cookie.d.ts +1 -0
  33. package/dist/server/utils/cookie.js +1 -1
  34. package/dist/server/utils/envs/interpolate-env-variables.d.ts +3 -1
  35. package/dist/server/utils/envs/interpolate-env-variables.js +1 -1
  36. package/dist/server/web-server/auth.d.ts +1 -1
  37. package/dist/server/web-server/auth.js +4 -4
  38. package/dist/server/web-server/routes/ask-ai.js +1 -1
  39. package/dist/server/web-server/routes/auth.js +1 -1
  40. package/dist/server/web-server/routes/error.js +1 -1
  41. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  42. package/dist/types/markdoc.d.ts +2 -0
  43. package/dist/utils/path/sanitize-path.js +1 -1
  44. package/package.json +7 -7
@@ -1 +1 @@
1
- import*as n from"../../../web-server/jwt/jwt.js";function e(t){if(!t)return null;const r=Array.isArray(t)?t[0]:t;return!r||!r.startsWith("Bearer ")?null:r.substring(7)}function u(t){try{const{payload:r}=n.decode(t),o=Math.floor(Date.now()/1e3);return typeof r.exp=="number"&&r.exp<o?null:r}catch(r){return console.error("Failed to decode JWT token:",r),null}}function i(t){const r=t.authorization||t.Authorization,o=e(r);return o?u(o):null}export{e as extractTokenFromAuthHeader,i as getUserInfoFromHeaders,u as getUserInfoFromToken};
1
+ import*as t from"../../../web-server/jwt/jwt.js";function n(e){if(!e)return null;const r=Array.isArray(e)?e[0]:e;return!r||!r.startsWith("Bearer ")?null:r.replace(/^Bearer /,"")}function u(e){try{const{payload:r}=t.decode(e),o=Math.floor(Date.now()/1e3);return typeof r.exp=="number"&&r.exp<o?null:r}catch(r){return console.error("Failed to decode JWT token:",r),null}}function a(e){const r=e.authorization||e.Authorization,o=n(r);return o?u(o):null}export{n as extractTokenFromAuthHeader,a as getUserInfoFromHeaders,u as getUserInfoFromToken};
@@ -1 +1 @@
1
- import{telemetry as s}from"../../../telemetry/index.js";import{mcpToolHandlers as l}from"../../../../client/mcp-tool-handlers-entry.js";async function y(r){s.initialize();const{toolName:e,args:c,context:o,extra:i}=r;try{const t=l[e];if(!t)throw new Error(`Unknown MCP tool: ${e}`);const{default:f}=await t(),n=f[e];if(!n)throw new Error(`MCP tool module does not export a handler for "${e}". Expected \`export default { '${e}': handler }\`.`);const a=await(typeof n=="function"?n:n.execute)(c,o,i);if(!d(a))throw new Error(`MCP tool "${e}" returned an invalid result.`);return a.isError?s.sendMcpErrorMessage([{object:"mcp_server",server_type:"docs",tool:e,message:`${a.content.map(({text:p})=>p).join(" ")}`,stack:""}]):s.sendMcpToolCalledMessage([{object:"mcp_server",server_type:"docs",tool:e}]),a}catch(t){throw s.sendMcpErrorMessage([{object:"mcp_server",server_type:"docs",tool:e,message:t instanceof Error?t.message:String(t),stack:t instanceof Error&&t.stack||""}]),t}}function d(r){return!r||typeof r!="object"?!1:Array.isArray(r.content)}async function h(r){s.initialize();const{toolName:e,context:c}=r;try{const o=l[e];if(!o)return!1;const{default:i}=await o(),t=i[e];return t?typeof t=="object"&&typeof t.isAvailable=="function"?await t.isAvailable(c):!0:!1}catch(o){throw s.sendMcpErrorMessage([{object:"mcp_server",server_type:"docs",tool:e,message:"Failed to check if MCP tool is available: "+(o instanceof Error?o.message:String(o)),stack:o instanceof Error&&o.stack||""}]),o}}export{y as executeMcpTool,h as isMcpToolAvailable};
1
+ import{telemetry as s}from"../../../telemetry/index.js";import{mcpToolHandlers as p}from"../../../../client/mcp-tool-handlers-entry.js";import{McpServerType as n}from"../constants.js";const i={id:n.Docs,object:"mcpServer",uri:`urn:redocly:realm:mcp:server:${n.Docs}`};async function g(r){s.initialize();const{toolName:e,args:l,context:o,extra:f}=r;try{const t=p[e];if(!t)throw new Error(`Unknown MCP tool: ${e}`);const{default:u}=await t(),a=u[e];if(!a)throw new Error(`MCP tool module does not export a handler for "${e}". Expected \`export default { '${e}': handler }\`.`);const c=await(typeof a=="function"?a:a.execute)(l,o,f);if(!m(c))throw new Error(`MCP tool "${e}" returned an invalid result.`);return c.isError?s.sendMcpErrorMessage([{...i,server_type:n.Docs,tool:e,message:`${c.content.map(({text:d})=>d).join(" ")}`,stack:""}]):s.sendMcpToolCalledMessage([{...i,server_type:n.Docs,tool:e}]),c}catch(t){throw s.sendMcpErrorMessage([{...i,server_type:n.Docs,tool:e,message:t instanceof Error?t.message:String(t),stack:t instanceof Error&&t.stack||""}]),t}}function m(r){return!r||typeof r!="object"?!1:Array.isArray(r.content)}async function x(r){s.initialize();const{toolName:e,context:l}=r;try{const o=p[e];if(!o)return!1;const{default:f}=await o(),t=f[e];return t?typeof t=="object"&&typeof t.isAvailable=="function"?await t.isAvailable(l):!0:!1}catch(o){throw s.sendMcpErrorMessage([{...i,server_type:n.Docs,tool:e,message:"Failed to check if MCP tool is available: "+(o instanceof Error?o.message:String(o)),stack:o instanceof Error&&o.stack||""}]),o}}export{g as executeMcpTool,x as isMcpToolAvailable};
@@ -1,4 +1,5 @@
1
1
  import type { Context } from 'hono';
2
+ export declare function clearAuthCookies(ctx: Context): void;
2
3
  export declare function safeDecodeURIComponent(value: string): string;
3
4
  /**
4
5
  * Parses cookie header string according to RFC 6265
@@ -1 +1 @@
1
- import{getCookie as p}from"hono/cookie";function r(e){try{return decodeURIComponent(e)}catch{return e}}function m(e){const o=new Map,n=e.req.header("cookie")||"";return n&&n.split(";").forEach(i=>{const[a,...s]=i.split("="),t=a?.trim();if(!t)return;const u=s.join("=").trim();try{const c=p(e,t);if(!c)return;o.set(r(t),c)}catch{o.set(r(t),r(u))}}),o}export{m as parseCookieHeader,r as safeDecodeURIComponent};
1
+ import{deleteCookie as m,getCookie as u}from"hono/cookie";import{getPathPrefix as p}from"@redocly/theme/core/utils";import{AuthCookieNames as k}from"../../constants/common.js";function C(e){const o=p()||"/";for(const t of Object.values(k))m(e,t,{path:o})}function n(e){try{return decodeURIComponent(e)}catch{return e}}function x(e){const o=new Map,t=e.req.header("cookie")||"";return t&&t.split(";").forEach(c=>{const[a,...s]=c.split("="),r=a?.trim();if(!r)return;const f=s.join("=").trim();try{const i=u(e,r);if(!i)return;o.set(n(r),i)}catch{o.set(n(r),n(f))}}),o}export{C as clearAuthCookies,x as parseCookieHeader,n as safeDecodeURIComponent};
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Interpolates environment variables in a string using the format {{ process.env.VARIABLE_NAME }}.
3
+ * Supports optional quoted default values applied when the variable is not set,
4
+ * e.g. {{ process.env.VARIABLE_NAME || "default" }} (both `|` and `||` separators are accepted).
3
5
  *
4
6
  * @param value - The string containing environment variable templates to interpolate
5
7
  * @returns An object containing:
6
8
  * - interpolatedValue: The string with all environment variables replaced with their values
7
- * - unsetVars: Array of environment variable names that were not found
9
+ * - unsetVars: Array of environment variable names that were not found and had no default
8
10
  * - interpolatedVars: Array of environment variable names that were successfully resolved
9
11
  */
10
12
  export declare function interpolateEnvVariables(value: string): {
@@ -1 +1 @@
1
- import{readEnvVariable as i}from"./read-env-variable.js";const l=/{{\s*process\.env\.([A-Z0-9_]+)\s*}}/gi;function V(r){let e=r;const t=new Set,a=new Set,c=[...r.matchAll(l)];for(const s of c){const n=s[1].toUpperCase(),o=i(n);o===void 0?t.add(n):(e=e.replace(s[0],o),a.add(n))}return{interpolatedValue:e,unsetVars:Array.from(t),interpolatedVars:Array.from(a)}}export{V as interpolateEnvVariables};
1
+ import{readEnvVariable as d}from"./read-env-variable.js";const i=/{{\s*process\.env\.([A-Z0-9_]+)\s*(?:\|\|?\s*(?:"([^"]*)"|'([^']*)'))?\s*}}/gi;function V(s){let e=s;const t=new Set,r=new Set,c=[...s.matchAll(i)];for(const n of c){const a=n[1].toUpperCase(),o=n[2]??n[3],l=d(a);l!==void 0?(e=e.replace(n[0],l),r.add(a)):o!==void 0?(e=e.replace(n[0],o),r.add(a)):t.add(a)}return{interpolatedValue:e,unsetVars:Array.from(t),interpolatedVars:Array.from(r)}}export{V as interpolateEnvVariables};
@@ -76,7 +76,7 @@ export declare function createMcpAuthorizationCode(params: {
76
76
  export declare function verifyMcpAuthorizationCode(code: string): Promise<McpAuthorizationCodePayload>;
77
77
  export declare function createMcpSessionResource(sessionId: string | null | undefined): {
78
78
  id: string;
79
- object: 'mcp_session';
79
+ object: 'mcpSession';
80
80
  uri: string;
81
81
  };
82
82
  export declare function rewritePreviewAuthRedirectUri(redirectUri: string): string;
@@ -1,12 +1,12 @@
1
- import"../node-crypto-polyfill.js";import{DOMParser as b}from"@xmldom/xmldom";import{SignedXml as W}from"xml-crypto";import J from"xpath";import{deflateSync as q,inflateSync as K}from"fflate";import{createHash as Y}from"crypto";import{ulid as Q}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as X}from"@redocly/config";import{AUTH_URL as G,JWT_SECRET_KEY as L}from"../constants/common.js";import{envConfig as Z}from"../config/env-config.js";import{getPathPrefix as ee,withPathPrefix as te}from"@redocly/theme/core/utils";import{DEFAULT_AUTHENTICATED_TEAM as ne,REQUIRED_OIDC_SCOPES as D,ServerRoutes as v}from"../../constants/common.js";import{appendQueryParams as re}from"../../utils/url/append-query-params.js";import{parseHttpsUrl as R}from"../../utils/url/parse-https-url.js";import{logger as oe}from"../tools/notifiers/logger.js";import{randomString as ae}from"../utils/crypto/random-string.js";import{randomUUID as U}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as w,JwtTokenExpired as se}from"./jwt/types.js";import*as f from"./jwt/jwt.js";import{parseTeamClaimToArray as ie}from"../utils/index.js";import{arrayBufferToBase64 as ce,decodeBase64 as N,encodeBase64URL as ue,urlSafeBase64 as j}from"./jwt/encode.js";import{formatSamlCertificate as le}from"./utils/format-saml-certificate.js";function E(e){return e?.type===u.OIDC}function de(e){return e?.type===u.SAML2}async function Qe(e,t){if(E(t))return me(e,t);if(de(t))return pe(e,t)}async function me(e,t){const n=await H(e,t),r=new Set((t.scopes||[]).concat(D)),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 pe(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function Xe(e,t,n,r,o={}){const a=new Set((r.scopes||[]).concat(D));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:r.clientId,scope:Array.from(a).join(" "),code:t,redirect_uri:V(n),grant_type:"authorization_code",...r.clientSecret?{client_secret:r.clientSecret}:{},...o}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function fe(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:a,idpId:s,pkce:l},m,A,p){if(!t||!n||!r||!o)return{loginUrl:void 0};const i=new URL(t),h=p?.redirectUriOverride??`${e}${te(v.OIDC_CALLBACK)}`,_={state:U(),idpId:s,redirectUri:h,redirectTo:m,branch:p?.branchOverride??he(e),inviteCode:A,source:p?.sourceOverride??"portal",uiLocales:p?.uiLocales},y={};if(l){const d=j(ae(50)),g=j(Y("sha256").update(d).digest("base64")),x="S256";i.searchParams.append("code_challenge",g),i.searchParams.append("code_challenge_method",x),y.code_verifier={value:d,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:ee()||"/"}}}i.searchParams.append("client_id",n),i.searchParams.append("scope",o),i.searchParams.append("response_type",r),i.searchParams.append("redirect_uri",V(h)),i.searchParams.append("state",ue(JSON.stringify(_))),p?.uiLocales&&i.searchParams.append("ui_locales",p.uiLocales);for(const d in a)a[d]!==void 0&&i.searchParams.append(d,a[d]);return{loginUrl:i.toString(),cookies:y}}function Ge(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 Ze(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return f.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpAccessToken?{idp_access_token:e.idpAccessToken}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:n},L,w.HS256)}async function et(e){await f.verify(e,L,w.HS256);const{payload:t}=f.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}function tt(e){const t=e||Q(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcp_session",uri:`urn:redocly:realm:mcp:session:${n}`}}function V(e){const t=R(e);if(!t)return e;const n=ye(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,$(e,t);const r=B(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,$(e,t)):e}function he(e){const t=R(e);if(t)return B(t.hostname)?.previewBranch}function $(e,t){return e.replace(/^https:\/\/[^/?#]+/i,`https://${t.host}`)}function B(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 a=n.slice(0,o),s=n.slice(o+2);return!a||!s||s.includes(".")?null:{projectSlug:a,previewBranch:s,after:r}}function ye(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 a=t===-1?"":e.slice(t);return{previewBranch:o,after:a}}function we(e){return e.type===u.OIDC}function Se(e){return e.type===u.SAML2}function nt(e,t,n,r,o){return we(e)?fe(t,e,n,r,{uiLocales:o}):Se(e)?Ae(t,e,n,r,o):{}}function Ae(e,t,n,r,o){const s=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
1
+ import"../node-crypto-polyfill.js";import{DOMParser as P}from"@xmldom/xmldom";import{SignedXml as J}from"xml-crypto";import q from"xpath";import{deflateSync as K,inflateSync as Y}from"fflate";import{createHash as Q}from"crypto";import{ulid as X}from"ulid";import{AuthProviderType as u,DEFAULT_TEAM_CLAIM_NAME as Z}from"@redocly/config";import{AUTH_URL as G,JWT_SECRET_KEY as C}from"../constants/common.js";import{envConfig as ee}from"../config/env-config.js";import{getPathPrefix as te,withPathPrefix as ne}from"@redocly/theme/core/utils";import{AuthCookieNames as A,DEFAULT_AUTHENTICATED_TEAM as re,REQUIRED_OIDC_SCOPES as N,ServerRoutes as R}from"../../constants/common.js";import{appendQueryParams as oe}from"../../utils/url/append-query-params.js";import{parseHttpsUrl as b}from"../../utils/url/parse-https-url.js";import{logger as ae}from"../tools/notifiers/logger.js";import{randomString as se}from"../utils/crypto/random-string.js";import{randomUUID as v}from"../utils/crypto/random-uuid.js";import{AlgorithmTypes as w,JwtTokenExpired as ie}from"./jwt/types.js";import*as f from"./jwt/jwt.js";import{parseTeamClaimToArray as ce}from"../utils/index.js";import{arrayBufferToBase64 as ue,decodeBase64 as U,encodeBase64URL as le,urlSafeBase64 as j}from"./jwt/encode.js";import{formatSamlCertificate as de}from"./utils/format-saml-certificate.js";function V(e){return e?.type===u.OIDC}function me(e){return e?.type===u.SAML2}async function Xe(e,t){if(V(t))return pe(e,t);if(me(t))return fe(e,t)}async function pe(e,t){const n=await F(e,t),r=new Set((t.scopes||[]).concat(N)),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 fe(e,t){return{type:u.SAML2,idpId:e,name:"SAML2 provider",ssoUrl:t.ssoUrl,issuerId:t.issuerId,entityId:t.entityId||t.issuerId}}async function Ze(e,t,n,r,o={}){const a=new Set((r.scopes||[]).concat(N));return await fetch(e,{method:"POST",body:new URLSearchParams({client_id:r.clientId,scope:Array.from(a).join(" "),code:t,redirect_uri:$(n),grant_type:"authorization_code",...r.clientSecret?{client_secret:r.clientSecret}:{},...o}).toString(),headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"}}).then(s=>s.json())}function he(e,{authorizationEndpoint:t,clientId:n,responseType:r,scope:o,extraParams:a,idpId:s,pkce:l},m,g,p){if(!t||!n||!r||!o)return{loginUrl:void 0};const i=new URL(t),h=p?.redirectUriOverride??`${e}${ne(R.OIDC_CALLBACK)}`,_={state:v(),idpId:s,redirectUri:h,redirectTo:m,branch:p?.branchOverride??ye(e),inviteCode:g,source:p?.sourceOverride??"portal",uiLocales:p?.uiLocales},y={};if(l){const d=j(se(50)),x=j(Q("sha256").update(d).digest("base64")),I="S256";i.searchParams.append("code_challenge",x),i.searchParams.append("code_challenge_method",I),y.code_verifier={value:d,options:{secure:!0,httpOnly:!0,expires:new Date(Date.now()+1e3*60*10),path:te()||"/"}}}i.searchParams.append("client_id",n),i.searchParams.append("scope",o),i.searchParams.append("response_type",r),i.searchParams.append("redirect_uri",$(h)),i.searchParams.append("state",le(JSON.stringify(_))),p?.uiLocales&&i.searchParams.append("ui_locales",p.uiLocales);for(const d in a)a[d]!==void 0&&i.searchParams.append(d,a[d]);return{loginUrl:i.toString(),cookies:y}}function Ge(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 et(e){const t=Math.floor(Date.now()/1e3),n=t+(e.ttlSec??600);return f.sign({type:"mcp_auth_code",client_id:e.clientId,redirect_uri:e.redirectUri,id_token:e.idToken,...e.idpAccessToken?{idp_access_token:e.idpAccessToken}:{},...e.codeChallenge?{code_challenge:e.codeChallenge}:{},...e.codeChallengeMethod?{code_challenge_method:e.codeChallengeMethod}:{},iat:t,exp:n},C,w.HS256)}async function tt(e){await f.verify(e,C,w.HS256);const{payload:t}=f.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}function nt(e){const t=e||X(),n=t.startsWith("mcp_")?t:`mcp_${t}`;return{id:n,object:"mcpSession",uri:`urn:redocly:realm:mcp:session:${n}`}}function $(e){const t=b(e);if(!t)return e;const n=we(t.hostname);if(n)return t.hostname=`previewauth--${n.previewBranch}${n.after}`,H(e,t);const r=B(t.hostname);return r?(t.hostname=`${r.projectSlug}.previewauth.${r.after}`,H(e,t)):e}function ye(e){const t=b(e);if(t)return B(t.hostname)?.previewBranch}function H(e,t){return e.replace(/^https:\/\/[^/?#]+/i,`https://${t.host}`)}function B(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 a=n.slice(0,o),s=n.slice(o+2);return!a||!s||s.includes(".")?null:{projectSlug:a,previewBranch:s,after:r}}function we(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 a=t===-1?"":e.slice(t);return{previewBranch:o,after:a}}function Se(e){return e.type===u.OIDC}function Ae(e){return e.type===u.SAML2}function rt(e,t,n,r,o){return Se(e)?he(t,e,n,r,{uiLocales:o}):Ae(e)?ge(t,e,n,r,o):{}}function ge(e,t,n,r,o){const s=`<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
2
2
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
3
3
  Version="2.0"
4
- ID="_${U()}"
4
+ ID="_${v()}"
5
5
  IssueInstant="${new Date().toISOString()}"
6
- AssertionConsumerServiceURL="${e}${v.SAML_CALLBACK}"
6
+ AssertionConsumerServiceURL="${e}${R.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>`,l=_e(s);return{loginUrl:re(t.ssoUrl,{SAMLRequest:l,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function _e(e){return ce(q(new TextEncoder().encode(e)).buffer)}function rt(e){const t=N(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=K(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function ot(e){try{return JSON.parse(N(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function at(e){const t=new b().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,a=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],l=s&&s.nodeValue||void 0,m=c(t,"//*[local-name(.)='Audience']/text()")[0],A=m&&m.nodeValue||void 0,i=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",h=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],_=h&&h.nodeValue||"",y=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],d=y&&y.nodeValue||"",g=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],x=ge(g),C={},M=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(M.length)for(const O of M){const P=c(O,"./@Name")[0];if(P.nodeValue){const T=c(O,"./*[local-name(.)='AttributeValue']/text()")[0];T?.nodeValue&&(C[P.nodeValue]=T.nodeValue)}}return{uid:_,success:r,expiresAt:x,issuerId:l,entityId:A,attrs:C,cert:i,nameFormat:d,destination:a}}function ge(e){const t=typeof e?.nodeValue=="string"&&I(Date.parse(e.nodeValue)),n=I(Date.now()),r=I(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function I(e){return Math.floor(e/1e3)}const k={},S={jwks:{}};async function H(e,t){if(!k[e]){const n=t.configurationUrl?await z(t.configurationUrl):t.configuration;k[e]=xe()?Le(n):n}return k[e]}function xe(){const e=Z.REDOCLY_ENFORCE_RESIDENCY;return!!e&&e.includes("host.docker.internal")}function Le(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 Ie(e){for(const t of Object.keys(e)){const n=e[t];if(!E(n))continue;const r=await H(t,n);if(r.jwks_uri){const o=await z(r.jwks_uri);for(const a of o.keys)S.jwks[a.kid]={...a,idpId:t}}}}async function z(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function st(e){return fetch(`${G}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function it(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=>ke(t.hostname,r))}function ke(e,t){return e===t||e.endsWith(`.${t}`)}async function ct(e,t){const n=new b().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=le(t),a=new W({publicCert:o});a.loadSignature(r);try{return a.checkSignature(e)}catch{return!1}}function ut(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 a=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=a?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?a: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"]||a,email:o,email_verified:!0,teams:r?ie(n[r]):[]}}function F(e,t={}){return e.map(n=>t[n]||n)}async function lt(e,t){if(!t)return{};const n=t.authorization;if(!n)return{};try{const r=f.decode(n);if(r.header.alg===w.RS256){S.jwks[r.header.kid]===void 0&&await Ie(e);const m=S.jwks[r.header.kid];if(!m)return S.jwks[r.header.kid]=null,{};await f.verify(n,m,w.RS256)}else await f.verify(n,L,w.HS256);const o=r.payload.idpId||S.jwks[r.header.kid]?.idpId,a=e[o]||{},s=Oe(a),l=Me(a);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...F(r.payload.teams||[],l),..."defaultTeams"in a&&a.defaultTeams||[],...F("teamsClaimName"in a&&r.payload[s||""]||[],l),ne])),name:Ce(r.payload),isAuthenticated:!0,idpAccessToken:r.payload.idp_access_token||t.idp_access_token,federatedAccessToken:t.federated_access_token,federatedIdToken:t.federated_id_token,authCookie:n}}catch(r){r instanceof se||oe.error("Malformed JWT token: %s",r.message)}return{}}function Ce(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function Me(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function Oe(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return X}}function c(e,t){return J.select(t,e)||[]}export{nt as buildLoginUrl,fe as buildOidcLoginUrl,Ge as buildOidcLogoutUrl,Ae as buildSAML2LoginUrl,Ze as createMcpAuthorizationCode,tt as createMcpSessionResource,rt as decodeSamlResponse,_e as encodeSAML2,ut as extractUserClaims,Qe as getAuthProviderLoginParams,me as getOidcLoginParams,H as getOidcMetadata,st as getRedoclyTokenPayload,pe as getSaml2LoginParams,lt as getUserParamsFromCookies,Ce as getUsernameFromPayload,E as isOidcProviderConfig,it as isRedoclySso,de as isSaml2ProviderConfig,Xe as oidcExchangeCodeForToken,S as oidcJwksCache,k as oidcMetadataCache,ot as parseOidcState,he as parsePreviewBranch,at as parseSamlResponse,V as rewritePreviewAuthRedirectUri,et as verifyMcpAuthorizationCode,ct as verifySAMLResponse};
12
+ </samlp:AuthnRequest>`,l=_e(s);return{loginUrl:oe(t.ssoUrl,{SAMLRequest:l,RelayState:JSON.stringify({idpId:t.idpId,redirectTo:n,inviteCode:r,source:"portal",uiLocales:o})})}}function _e(e){return ue(K(new TextEncoder().encode(e)).buffer)}function ot(e){const t=U(e);if(t.startsWith("<samlp:Response")||t.indexOf("<saml2p:Response")>-1)return t;const n=Y(new Uint8Array(atob(e).split("").map(r=>r.charCodeAt(0))));return new TextDecoder().decode(n)}function at(e){try{return JSON.parse(U(e||""))}catch{throw new Error("Invalid OAuth2 state")}}function st(e){const t=new P().parseFromString(e,"application/xml"),r=c(t,"//*[local-name(.)='StatusCode']/@Value")[0]?.nodeValue?.endsWith("Success")||!1,a=c(t,"//*[local-name(.)='Response']/@Destination")[0]?.nodeValue||"",s=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='Issuer']/text()")[0],l=s&&s.nodeValue||void 0,m=c(t,"//*[local-name(.)='Audience']/text()")[0],g=m&&m.nodeValue||void 0,i=c(t,"//*[local-name(.)='Assertion']//*[local-name(.)='X509Certificate']/text()")[0]?.nodeValue||"",h=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/text()")[0],_=h&&h.nodeValue||"",y=c(t,"//*[local-name(.)='Subject']//*[local-name(.)='NameID']/@Format")[0],d=y&&y.nodeValue||"",x=c(t,"//*[local-name(.)='Conditions']/@NotOnOrAfter")[0],I=xe(x),O={},D=c(t,"//*[local-name(.)='AttributeStatement']//*[local-name(.)='Attribute']");if(D.length)for(const M of D){const k=c(M,"./@Name")[0];if(k.nodeValue){const E=c(M,"./*[local-name(.)='AttributeValue']/text()")[0];E?.nodeValue&&(O[k.nodeValue]=E.nodeValue)}}return{uid:_,success:r,expiresAt:I,issuerId:l,entityId:g,attrs:O,cert:i,nameFormat:d,destination:a}}function xe(e){const t=typeof e?.nodeValue=="string"&&L(Date.parse(e.nodeValue)),n=L(Date.now()),r=L(Date.now()+720*60*1e3);return t?t>n&&t<r?r:t:n}function L(e){return Math.floor(e/1e3)}const T={},S={jwks:{}};async function F(e,t){if(!T[e]){const n=t.configurationUrl?await z(t.configurationUrl):t.configuration;T[e]=Ie()?Ce(n):n}return T[e]}function Ie(){const e=ee.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(!V(n))continue;const r=await F(t,n);if(r.jwks_uri){const o=await z(r.jwks_uri);for(const a of o.keys)S.jwks[a.kid]={...a,idpId:t}}}}async function z(e){return fetch(e,{headers:{Accept:"application/json"}}).then(t=>t.json())}async function it(e){return fetch(`${G}/oidc/userinfo`,{headers:{Accept:"application/json",Authorization:`Bearer ${e}`}}).then(t=>t.status===200?t.json():void 0).catch(()=>{})}function ct(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=>Te(t.hostname,r))}function Te(e,t){return e===t||e.endsWith(`.${t}`)}async function ut(e,t){const n=new P().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=de(t),a=new J({publicCert:o});a.loadSignature(r);try{return a.checkSignature(e)}catch{return!1}}function lt(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 a=n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],s=a?.match(/.+@.+/);return o=o||n["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]||(s?a: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"]||a,email:o,email_verified:!0,teams:r?ce(n[r]):[]}}function W(e,t={}){return e.map(n=>t[n]||n)}async function dt(e,t){if(!t)return{};const n=t[A.AUTHORIZATION];if(!n)return{};try{const r=f.decode(n);if(r.header.alg===w.RS256){S.jwks[r.header.kid]===void 0&&await Le(e);const m=S.jwks[r.header.kid];if(!m)return S.jwks[r.header.kid]=null,{};await f.verify(n,m,w.RS256)}else await f.verify(n,C,w.HS256);const o=r.payload.idpId||S.jwks[r.header.kid]?.idpId,a=e[o]||{},s=Me(a),l=De(a);return{...r.payload,email:r.payload.email?.toLowerCase(),idpId:o,teams:Array.from(new Set([...W(r.payload.teams||[],l),..."defaultTeams"in a&&a.defaultTeams||[],...W("teamsClaimName"in a&&r.payload[s||""]||[],l),re])),name:Oe(r.payload),isAuthenticated:!0,idpAccessToken:r.payload.idp_access_token||t[A.IDP_ACCESS_TOKEN],federatedAccessToken:t[A.FEDERATED_ACCESS_TOKEN],federatedIdToken:t[A.FEDERATED_ID_TOKEN],authCookie:n}}catch(r){r instanceof ie||ae.error("Malformed JWT token: %s",r.message)}return{}}function Oe(e){return(e.firstName&&e.lastName?`${e.firstName} ${e.lastName}`:e.name||e.given_name||e.firstName||e.lastName)||e.email}function De(e){switch(e.type){case u.SAML2:return e.teamsAttributeMap;case u.OIDC:return e.teamsClaimMap;default:return}}function Me(e){switch(e.type){case u.SAML2:return e.teamsAttributeName;case u.OIDC:return e.teamsClaimName;default:return Z}}function c(e,t){return q.select(t,e)||[]}export{rt as buildLoginUrl,he as buildOidcLoginUrl,Ge as buildOidcLogoutUrl,ge as buildSAML2LoginUrl,et as createMcpAuthorizationCode,nt as createMcpSessionResource,ot as decodeSamlResponse,_e as encodeSAML2,lt as extractUserClaims,Xe as getAuthProviderLoginParams,pe as getOidcLoginParams,F as getOidcMetadata,it as getRedoclyTokenPayload,fe as getSaml2LoginParams,dt as getUserParamsFromCookies,Oe as getUsernameFromPayload,V as isOidcProviderConfig,ct as isRedoclySso,me as isSaml2ProviderConfig,Ze as oidcExchangeCodeForToken,S as oidcJwksCache,T as oidcMetadataCache,at as parseOidcState,ye as parsePreviewBranch,st as parseSamlResponse,$ as rewritePreviewAuthRedirectUri,tt as verifyMcpAuthorizationCode,ut as verifySAMLResponse};
@@ -1 +1 @@
1
- import{RbacFeatures as p}from"../../../constants/common.js";import{ASK_AI_API_URL as r}from"../../constants/common.js";import{canAccessFeature as u}from"../../utils/rbac.js";import{handleUnauthorizedApiRequest as f}from"../utils.js";import{isAiSearchEnabled as h}from"../../plugins/search/utils.js";function C(a){return async e=>{if(!r)return e.newResponse(null,404);const t=a.getConfig();if(!h(t))return e.newResponse(null,403,{});const o=e.get("auth");if(!u(p.AI_SEARCH,o,t.access?.rbac,t.access?.requiresLogin))return f(e);const i={"Content-Type":"application/json"},n=[];o.claims?.authCookie&&n.push(`authorization=${o.claims.authCookie}`),o.idpAccessToken&&n.push(`accessToken=${o.idpAccessToken}`),n.length>0&&(i.Cookie=n.join("; "));const c=await e.req.json(),s=await fetch(r,{method:"POST",body:JSON.stringify(c),headers:i});return s.ok?e.newResponse(s.body,200,{"Content-Type":"text/event-stream"}):e.newResponse(s.body,s.status,{"Content-Type":"application/json"})}}export{C as askAiHandler};
1
+ import{RbacFeatures as p}from"../../../constants/common.js";import{ASK_AI_API_URL as a}from"../../constants/common.js";import{canAccessFeature as h}from"../../utils/rbac.js";import{handleUnauthorizedApiRequest as f}from"../utils.js";import{isAiSearchEnabled as A}from"../../plugins/search/utils.js";function R(c){return async e=>{if(!a)return e.newResponse(null,404);const t=c.getConfig();if(!A(t))return e.newResponse(null,403,{});const o=e.get("auth");if(!h(p.AI_SEARCH,o,t.access?.rbac,t.access?.requiresLogin))return f(e);const i={"Content-Type":"application/json"},n=[];o.claims?.authCookie&&n.push(`authorization=${o.claims.authCookie}`),o.idpAccessToken&&n.push(`accessToken=${o.idpAccessToken}`),n.length>0&&(i.Cookie=n.join("; "));const r=e.req.header("Authorization");r&&(i.Authorization=r);const u=await e.req.json(),s=await fetch(a,{method:"POST",body:JSON.stringify(u),headers:i});return s.ok?e.newResponse(s.body,200,{"Content-Type":"text/event-stream"}):e.newResponse(s.body,s.status,{"Content-Type":"application/json"})}}export{R as askAiHandler};
@@ -1 +1 @@
1
- import{setCookie as R,deleteCookie as b}from"hono/cookie";import{AuthProviderType as X}from"@redocly/config";import{withPathPrefix as I,getPathPrefix as _}from"@redocly/theme/core/utils";import{compareURIs as W}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 U,ORG_SLUG as Y,ORG_ID as Q}from"../../constants/common.js";import{AUTH_SEGMENT as Z,DEFAULT_COOKIE_EXPIRATION as B,ServerRoutes as O}from"../../../constants/common.js";import{sanitizeRedirectPathname as z}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{envConfig as H}from"../../config/env-config.js";import{getAuthProviderLoginParams as x,isOidcProviderConfig as $,isSaml2ProviderConfig as ee,oidcExchangeCodeForToken as re,buildLoginUrl as oe,decodeSamlResponse as ne,extractUserClaims as te,parseSamlResponse as ie,parseOidcState as se,verifySAMLResponse as ae,getUsernameFromPayload as de,buildOidcLogoutUrl as ce,getOidcMetadata as N,getRedoclyTokenPayload as le,isRedoclySso as ue,rewritePreviewAuthRedirectUri as pe,parsePreviewBranch as j,buildOidcLoginUrl as ge,createMcpSessionResource as A}from"../auth.js";import*as D from"../jwt/jwt.js";import{AlgorithmTypes as v}from"../jwt/types.js";import{handleErrorPageRender as fe}from"../utils.js";import{encodeBase64URL as me}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as he}from"./helpers/resolve-ui-locales-for-idp-login.js";async function Ue(i){if(H.isProductionEnv)return i.newResponse(null,404,{});const{password:e,...r}=await i.req.json(),a=await D.sign({...r,name:r.username||r.email||"Unknown"},U,v.HS256);return R(i,"authorization",a,{path:_()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),i.newResponse(null,200,{})}function $e(){return async i=>{const e=i.get("logger"),r=encodeURIComponent(i.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${O.LOGIN}/?error=${encodeURIComponent(r)}`;return i.newResponse(null,301,{Location:a})}}function G(i){if(!i||!i.includes(O.MCP_CALLBACK))return null;try{const e=i.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 Te(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,o=se(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(O.MCP_CALLBACK),c=t?G(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,s=a?.[m];if(!$(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await N(m,s);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&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:`OIDC error: ${h}`,error_details:e.req.query("error_description")||null}]),fe(e,i,{slug:"/"},403,"403OIDC");if(!u){const y="Code is expected but not present";return r.error(`OIDC login error: ${y}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:y,error_details:null}]),new Response(`Forbidden: ${y}`,{status:403})}const C=typeof o.redirectUri=="string"?o.redirectUri:new URL(I(O.OIDC_CALLBACK),e.req.url).toString(),w=e.get("cookies")?.code_verifier,l=await re(p,u,C,s,{...s.tokenRequestCustomParams,...w?{code_verifier:w}:{}});if(l.error)return r.error(`Error from OIDC provider: "${l.error}"`),t&&k.sendMcpAuthorizationFailedMessage([{...A(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 y="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${y}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:y,error_details:null}]),new Response(`Forbidden: ${y}`,{status:403})}const{payload:f,header:S}=D.decode(l.id_token),n=S.alg===v.RS256;if(s.audience?.length&&![...q(f.aud||[]),...q(f[F]||[])].some(M=>s.audience?.includes(M))){const M="No valid audience found in id_token";return r.error(`OIDC login error: ${M}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:M,error_details:null}]),new Response(`Forbidden: ${M}`)}const g=n?l.id_token:await D.sign({...f,idpId:m},U,v.HS256);de(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const P=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+B*1e3;if(s.introspectEndpoint){const y=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:l.access_token})});if(y.ok){const T=(await y.json()).ext?.federatedIdentity;T&&(R(e,"federated_access_token",T.access_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(P)}),R(e,"federated_id_token",T.id_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(P)}))}else r.warn(`OIDC introspect error: ${y.statusText}`)}if(R(e,"authorization",g,{path:_()||"/",httpOnly:!0,expires:new Date(P)}),g!==l.id_token&&R(e,"idp_id_token",l.id_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(P)}),R(e,"idp_access_token",l.access_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(P)}),b(e,"code_verifier",{path:_()||"/"}),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(O.MCP_CALLBACK)){const M=`${e.req.url.split("?")[0].replace(O.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:M})}const K=typeof o.redirectTo=="string"?o.redirectTo:void 0;let J=z(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),V}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&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:u,error_details:h}]),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const L="Something went wrong";return r.error(`OIDC login error: ${L}`),t&&k.sendMcpAuthorizationFailedMessage([{...A(c),error:L,error_details:null}]),e.text(L,500)}}function be(i){return async e=>{const r=e.get("logger"),o=e.get("auth").claims?.idpId,t=i.getConfig().ssoDirect?.[o];if(e.req.method==="POST")return $(t)||b(e,"authorization",{path:_()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const s=(await N(o,t)).end_session_endpoint;if(s){const d=new URL(e.req.url),L=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${L}://${p}`,h=j(u),C=h?me(JSON.stringify({branch:j(u)})):void 0,w=h?`${pe(u)}/${Z}/logout`:`${u}${I(O.POST_LOGOUT)}`;c=ce(s,w,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",C)}}return r.info("Logout successful"),b(e,"authorization",{path:_()||"/"}),e.newResponse(null,302,{Location:c||I("/")})}}function qe(i){return async e=>{const r=i.getConfig().access?.logoutReturnUrl,a=r||I("/");return e.newResponse(null,302,{Location:a})}}function Ee(i){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=H.BH_API_URL,m=(t,c,s)=>t&&c?`${t} ${c.charAt(0)}`:s;try{if(!o)throw new Error("BH_API_URL is not set");const t=i.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return r.warn("Invite no sso configured to handle"),e.redirect(I("/"));const c=await fetch(`${o}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(I("/"))):(r.error("Invite error",await c.text()),e.redirect(I("/")));const s=await c.json(),d=new URL(I("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",s.organization.name),d.searchParams.set("invitedBy",m(s.invitedBy.firstName,s.invitedBy.lastName,s.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 Fe(i){return async e=>{const r=e.get("logger"),a=i.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,s=`${t}://${c}`;let d=o.searchParams.get("idpId");const L=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 E=G(L||void 0);k.sendMcpAuthorizationFailedMessage([{...A(E),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const w=he({localePrefixParam:o.searchParams.get("localePrefix"),l10n:i.getGlobalData()?.l10n}),l=d&&a?await x(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;let S,n={};if(h&&u&&l&&l.type===X.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=ge("",{...l,extraParams:f},L,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:w});S=g.loginUrl,n=g.cookies||{}}else if(l){const g=oe({...l,extraParams:f},s,L,m,w);S=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:S||new URL(e.req.url).pathname})}}function Be(i){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=ne(o),{success:c,uid:s,nameFormat:d,attrs:L,issuerId:p,expiresAt:u}=ie(t),{idpId:h,redirectTo:C}=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 w=i.getConfig().ssoDirect?.[h];if(!w||!ee(w)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(w.issuerId&&p&&W(w.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 ae(t,w.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=te(s,d,L,w.teamsAttributeName);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 S=await D.sign({...f,idpId:h},U,v.HS256);return R(e,"authorization",S,{path:_()||"/",httpOnly:!0,expires:new Date(u*1e3)}),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:C||"/"})}}function ze(i){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=I(z(a.pathname)),m=i.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,C])=>$(C)&&ue(C));if(!(m&&t))return e.newResponse(null,302,{Location:o});const s=e.req.query("token"),d=s&&await le(s);if(!d)return e.newResponse(null,302,{Location:o});if(!q(d[F]||[]).some(C=>C===Y||C===Q))return e.newResponse(null,302,{Location:o});const u=await D.sign({...d,idpId:t?.at(0)},U,v.HS256),h=Date.now()+B*1e3;return R(e,"authorization",u,{path:_()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{Ue as authorizeHandler,Fe as idpLoginHandler,Ee as inviteHandler,be as logoutHandler,Te as oidcCallbackHandler,qe as postLogoutHandler,$e as redoclyLoginCallbackHandler,ze as redoclyTokenLoginHandler,Be as samlCallbackHandler};
1
+ import{setCookie as y,deleteCookie as X}from"hono/cookie";import{AuthProviderType as W}from"@redocly/config";import{withPathPrefix as L,getPathPrefix as O}from"@redocly/theme/core/utils";import{compareURIs as Y}from"../../../utils/url/compare-uris.js";import{ensureArray as b}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as N,JWT_SECRET_KEY as v,ORG_SLUG as Q,ORG_ID as x}from"../../constants/common.js";import{AUTH_SEGMENT as ee,AuthCookieNames as A,DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as S}from"../../../constants/common.js";import{sanitizeRedirectPathname as H}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as T}from"../../telemetry/index.js";import{envConfig as B}from"../../config/env-config.js";import{clearAuthCookies as j}from"../../utils/cookie.js";import{getAuthProviderLoginParams as re,isOidcProviderConfig as $,isSaml2ProviderConfig as oe,oidcExchangeCodeForToken as ne,buildLoginUrl as te,decodeSamlResponse as se,extractUserClaims as ie,parseSamlResponse as ae,parseOidcState as de,verifySAMLResponse as ce,getUsernameFromPayload as le,buildOidcLogoutUrl as ue,getOidcMetadata as K,getRedoclyTokenPayload as pe,isRedoclySso as ge,rewritePreviewAuthRedirectUri as fe,parsePreviewBranch as z,buildOidcLoginUrl as me,createMcpSessionResource as D}from"../auth.js";import*as k from"../jwt/jwt.js";import{AlgorithmTypes as U}from"../jwt/types.js";import{handleErrorPageRender as he}from"../utils.js";import{encodeBase64URL as Ie}from"../jwt/encode.js";import{resolveUiLocalesForIdpLogin as we}from"./helpers/resolve-ui-locales-for-idp-login.js";async function Ee(s){if(B.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 y(s,A.AUTHORIZATION,a,{path:O()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),s.newResponse(null,200,{})}function be(){return async s=>{const e=s.get("logger"),r=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${S.LOGIN}/?error=${encodeURIComponent(r)}`;return s.newResponse(null,301,{Location:a})}}function G(s){if(!s||!s.includes(S.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 qe(s){return async e=>{const r=e.get("logger"),a=s.getConfig().ssoDirect,o=de(e.req.query("state")),m=o.idpId,t=o.source==="mcp"||o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(S.MCP_CALLBACK),c=t?G(typeof o.redirectTo=="string"?o.redirectTo:void 0):null,i=a?.[m];if(!$(i))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await K(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}]),he(e,s,{slug:"/"},403,"403OIDC");if(!u){const w="Code is expected but not present";return r.error(`OIDC login error: ${w}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:w,error_details:null}]),new Response(`Forbidden: ${w}`,{status:403})}const R=typeof o.redirectUri=="string"?o.redirectUri:new URL(L(S.OIDC_CALLBACK),e.req.url).toString(),I=e.get("cookies")?.code_verifier,l=await ne(p,u,R,i,{...i.tokenRequestCustomParams,...I?{code_verifier:I}:{}});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 w="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${w}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:w,error_details:null}]),new Response(`Forbidden: ${w}`,{status:403})}const{payload:f,header:M}=k.decode(l.id_token),n=M.alg===U.RS256;if(i.audience?.length&&![...b(f.aud||[]),...b(f[N]||[])].some(_=>i.audience?.includes(_))){const _="No valid audience found in id_token";return r.error(`OIDC login error: ${_}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:_,error_details:null}]),new Response(`Forbidden: ${_}`)}const g=n?l.id_token:await k.sign({...f,idpId:m},v,U.HS256);le(f)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const P=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:f.exp*1e3||Date.now()+F*1e3;if(i.introspectEndpoint){const w=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:l.access_token})});if(w.ok){const E=(await w.json()).ext?.federatedIdentity;E&&(y(e,A.FEDERATED_ACCESS_TOKEN,E.access_token||"",{path:O()||"/",httpOnly:!1,expires:new Date(P)}),y(e,A.FEDERATED_ID_TOKEN,E.id_token||"",{path:O()||"/",httpOnly:!1,expires:new Date(P)}))}else r.warn(`OIDC introspect error: ${w.statusText}`)}if(y(e,A.AUTHORIZATION,g,{path:O()||"/",httpOnly:!0,expires:new Date(P)}),g!==l.id_token&&y(e,A.IDP_ID_TOKEN,l.id_token||"",{path:O()||"/",httpOnly:!0,expires:new Date(P)}),y(e,A.IDP_ACCESS_TOKEN,l.access_token||"",{path:O()||"/",httpOnly:!0,expires:new Date(P)}),X(e,"code_verifier",{path:O()||"/"}),t&&o.redirectTo&&typeof o.redirectTo=="string"&&o.redirectTo.includes(S.MCP_CALLBACK)){const _=`${e.req.url.split("?")[0].replace(S.OIDC_CALLBACK,"")}${o.redirectTo}`;return e.newResponse(null,302,{Location:_})}const J=typeof o.redirectTo=="string"?o.redirectTo:void 0;let Z=H(new URL(J||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:Z});return r.updateContext({email:f.email,subject:f.sub}),r.info("OIDC login successful"),V}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 C="Something went wrong";return r.error(`OIDC login error: ${C}`),t&&T.sendMcpAuthorizationFailedMessage([{...D(c),error:C,error_details:null}]),e.text(C,500)}}function Ne(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 $(t)||j(e),r.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const i=(await K(o,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),C=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,u=`${C}://${p}`,h=z(u),R=h?Ie(JSON.stringify({branch:z(u)})):void 0,I=h?`${fe(u)}/${ee}/logout`:`${u}${L(S.POST_LOGOUT)}`;c=ue(i,I,e.get("cookies")?.[A.IDP_ID_TOKEN]||e.get("cookies")?.[A.AUTHORIZATION]||"",R)}}return r.info("Logout successful"),j(e),e.newResponse(null,302,{Location:c||L("/")})}}function Fe(s){return async e=>{const r=s.getConfig().access?.logoutReturnUrl,a=r||L("/");return e.newResponse(null,302,{Location:a})}}function He(s){return async e=>{const r=e.get("logger"),a=e.req.param("code"),o=B.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(L("/"));const c=await fetch(`${o}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(L("/"))):(r.error("Invite error",await c.text()),e.redirect(L("/")));const i=await c.json(),d=new URL(L("/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 Be(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 C=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 q=G(C||void 0);T.sendMcpAuthorizationFailedMessage([{...D(q),error:g,error_details:null}])}return e.text(`Forbidden: ${g}`,403)}const I=we({localePrefixParam:o.searchParams.get("localePrefix"),l10n:s.getGlobalData()?.l10n}),l=d&&a?await re(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;let M,n={};if(h&&u&&l&&l.type===W.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${u}`);const g=me("",{...l,extraParams:f},C,m,{redirectUriOverride:u,sourceOverride:"mcp",branchOverride:void 0,uiLocales:I});M=g.loginUrl,n=g.cookies||{}}else if(l){const g=te({...l,extraParams:f},i,C,m,I);M=g.loginUrl,n=g.cookies||{}}return Object.keys(n).forEach(g=>{y(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 je(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=se(o),{success:c,uid:i,nameFormat:d,attrs:C,issuerId:p,expiresAt:u}=ae(t),{idpId:h,redirectTo:R}=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 I=s.getConfig().ssoDirect?.[h];if(!I||!oe(I)){const n="Cannot find valid IdP";return r.error(`SAML2 login error: ${n}`),e.text(`Permission denied: ${n}`,401)}if(!(I.issuerId&&p&&Y(I.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 ce(t,I.x509PublicCert)){const n="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${n}`),e.text(n,401)}const f=ie(i,d,C,I.teamsAttributeName);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 y(e,A.AUTHORIZATION,M,{path:O()||"/",httpOnly:!0,expires:new Date(u*1e3)}),r.updateContext({email:f.email,subject:f.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:R||"/"})}}function Ke(s){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),o=L(H(a.pathname)),m=s.getConfig().ssoDirect,t=Object.entries(m||{}).find(([,R])=>$(R)&&ge(R));if(!(m&&t))return e.newResponse(null,302,{Location:o});const i=e.req.query("token"),d=i&&await pe(i);if(!d)return e.newResponse(null,302,{Location:o});if(!b(d[N]||[]).some(R=>R===Q||R===x))return e.newResponse(null,302,{Location:o});const u=await k.sign({...d,idpId:t?.at(0)},v,U.HS256),h=Date.now()+F*1e3;return y(e,A.AUTHORIZATION,u,{path:O()||"/",httpOnly:!0,expires:new Date(h),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:o})}}export{Ee as authorizeHandler,Be as idpLoginHandler,He as inviteHandler,Ne as logoutHandler,qe as oidcCallbackHandler,Fe as postLogoutHandler,be as redoclyLoginCallbackHandler,Ke as redoclyTokenLoginHandler,je as samlCallbackHandler};
@@ -1 +1 @@
1
- import{context as n,SpanStatusCode as s,trace as o}from"@opentelemetry/api";import{envConfig as a}from"../../config/env-config.js";const m=e=>{const t=o?.getSpan(n.active());return t&&(t.setStatus({code:s.ERROR,message:e.message}),t.recordException(e),t.end()),e instanceof Error&&"name"in e&&e.name==="URIError"?new Response("Malformed URL",{status:400}):a.isDevelopMode&&e?.name==="PageMissingDefaultExportError"?new Response(e.message,{status:200}):new Response("Internal Server Error",{status:500})};export{m as errorHandler};
1
+ import{context as s,SpanStatusCode as n,trace as a}from"@opentelemetry/api";import{envConfig as o}from"../../config/env-config.js";const c=t=>{const e=a?.getSpan(s.active());return t instanceof Error&&"name"in t&&t.name==="URIError"?(e&&(e.setStatus({code:n.OK}),e.end()),new Response("Malformed URL",{status:400})):t instanceof SyntaxError?(e&&(e.setStatus({code:n.OK}),e.end()),new Response("Invalid JSON",{status:400})):(e&&(e.setStatus({code:n.ERROR,message:t.message}),e.recordException(t),e.end()),o.isDevelopMode&&t?.name==="PageMissingDefaultExportError"?new Response(t.message,{status:200}):new Response("Internal Server Error",{status:500}))};export{c as errorHandler};
@@ -1 +1 @@
1
- import{getCookie as C}from"hono/cookie";import{createHash as P,timingSafeEqual as v}from"node:crypto";import{ulid as E}from"ulid";import{AUTH_URL as f,JWT_SECRET_KEY as M}from"../../../constants/common.js";import{ServerRoutes as _}from"../../../../constants/common.js";import{withPathPrefix as u}from"@redocly/theme/core/utils";import{telemetry as m}from"../../../telemetry/index.js";import{envConfig as T}from"../../../config/env-config.js";import{createMcpAuthorizationCode as R,verifyMcpAuthorizationCode as D,createMcpSessionResource as h}from"../../auth.js";import*as g from"../../jwt/jwt.js";import{AlgorithmTypes as S}from"../../jwt/types.js";import{getRequestOrigin as k}from"../../utils/get-request-origin.js";import{fetchClientMetadataDocument as O,isClientMetadataDocumentUrl as y,isRedirectUriRegistered as L}from"./mcp-client-metadata.js";const o=(e,r,n=200,a)=>e.json(r,n,{"Content-Type":"application/json",...a??{}}),A=new Set(["S256"]),q="https://claude.ai/oauth/claude-code-client-metadata";function z(e,r,n){if(!A.has(r||""))return!1;const a=P("sha256").update(n).digest("base64url"),t=Buffer.from(a),s=Buffer.from(e);return t.length!==s.length?!1:v(t,s)}async function H(e){const r=Math.floor(Date.now()/1e3);return g.sign({type:"mcp_context",...e,iat:r,exp:r+600},M,S.HS256)}async function $(e){await g.verify(e,M,S.HS256);const{payload:r}=g.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function Q(){return async e=>{if(e.req.method!=="GET")return o(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=k(e);return o(e,{resource:`${r}${u("/mcp")}`,authorization_servers:[r,f].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${r}${_.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function X(){return async e=>{const r=k(e);return o(e,{issuer:f||"",authorization_endpoint:`${r}${u(_.MCP_AUTHORIZATION)}`,token_endpoint:`${r}${u(_.MCP_TOKEN_PORTAL)}`,jwks_uri:`${f||""}/.well-known/jwks.json`,scopes_supported:["openid","profile","email","offline_access"],registration_endpoint:`${r}${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 x(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{return o(e,{client_id:T.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(r){return o(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function ee(){return async e=>{const r=new URL(e.req.url),{searchParams:n}=r,a=n.get("redirect_uri"),t=n.get("client_id"),s=n.get("code_challenge"),i=n.get("code_challenge_method"),d=E();if(s&&!A.has(i||""))return o(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...A].join(", ")}`},400);if(y(t)&&!s)return o(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(y(t)&&a)try{const c=await O(t),l=t===q;if(!L(a,c.redirect_uris,{ignorePort:l}))return o(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(c){const l=c instanceof Error?c.message:String(c);return o(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${l}`},400)}m.sendMcpAuthorizationStartedMessage([{...h(d),redirect_uri:a||null}]);const p=k(e),w={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:t,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:s,mcpCodeChallengeMethod:s?i:null,timestamp:Date.now()};try{const c=await H(w),l=new URL(u(_.IDP_LOGIN),p);return l.searchParams.set("redirectTo",`${_.MCP_CALLBACK}/${c}`),l.searchParams.set("idpId","oidc"),e.redirect(l.toString())}catch(c){const l=c instanceof Error?c.message:String(c),U=c instanceof Error?c.stack:String(c);m.sendMcpAuthorizationFailedMessage([{...h(d),error:l,error_details:U}]);const I=new URL(u(`${f}/oauth2/auth`));return I.search=n.toString(),e.redirect(I.toString())}}}function re(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),n=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0,s=r.get("code_verifier")||void 0;if(n!=="authorization_code"||!a)return o(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await D(a);if(t&&t!==i.redirect_uri)return o(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(i.code_challenge){if(!s)return o(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!z(i.code_challenge,i.code_challenge_method,s))return o(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(T.OAUTH_CLIENT_ID&&i.client_id&&!y(i.client_id)&&i.client_id!==T.OAUTH_CLIENT_ID)return o(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=i.id_token;if(typeof d!="string"||d.length===0)return o(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let p=d;if(i.idp_access_token){const{payload:c,header:{kid:l}}=g.decode(d);p=await g.sign({...c,idp_access_token:i.idp_access_token},M,S.HS256,l)}return o(e,{access_token:p,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:d},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return o(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const n=r instanceof Error?r.message:String(r);return o(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}function te(){return async e=>{const r=new URL(e.req.url);let n=r.searchParams.get("context");if(!n&&r.pathname.startsWith(u(`${_.MCP_CALLBACK}/`))){const t=r.pathname.split("/");n=t[t.length-1]}if(!n)return m.sendMcpAuthorizationFailedMessage([{...h(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let a=null;try{const t=await $(n);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=C(e,"idp_id_token")||C(e,"authorization"),i=C(e,"idp_access_token");if(!s)return m.sendMcpAuthorizationFailedMessage([{...h(a),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await R({idToken:s,idpAccessToken:i||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),p=new URL(t.originalRedirectUri);return p.searchParams.set("code",d),t.mcpState&&p.searchParams.set("state",t.mcpState),m.sendMcpAuthorizationCompletedMessage([{...h(a),redirect_uri:t.originalRedirectUri||null}]),e.redirect(p.toString())}catch(t){const s=t instanceof Error?t.message:String(t),i=t instanceof Error?t.stack:String(t);return m.sendMcpAuthorizationFailedMessage([{...h(a),error:s,error_details:i}]),e.text(`Invalid MCP callback: ${s}`,400)}}}export{H as createMcpContextToken,ee as mcpAuthorizationHandler,te as mcpCallbackHandler,x as mcpDynamicClientRegistrationHandler,X as mcpOAuthAuthorizationServerHandler,Q as mcpOAuthProtectedResourceHandler,re as mcpTokenPortalHandler,$ as verifyAndParseMcpContextToken,z as verifyPkce};
1
+ import{getCookie as C}from"hono/cookie";import{createHash as U,timingSafeEqual as R}from"node:crypto";import{ulid as v}from"ulid";import{AUTH_URL as f,JWT_SECRET_KEY as M}from"../../../constants/common.js";import{AuthCookieNames as T,ServerRoutes as _}from"../../../../constants/common.js";import{withPathPrefix as u}from"@redocly/theme/core/utils";import{telemetry as m}from"../../../telemetry/index.js";import{envConfig as A}from"../../../config/env-config.js";import{createMcpAuthorizationCode as O,verifyMcpAuthorizationCode as D,createMcpSessionResource as h}from"../../auth.js";import*as g from"../../jwt/jwt.js";import{AlgorithmTypes as S}from"../../jwt/types.js";import{getRequestOrigin as y}from"../../utils/get-request-origin.js";import{fetchClientMetadataDocument as L,isClientMetadataDocumentUrl as k,isRedirectUriRegistered as H}from"./mcp-client-metadata.js";const o=(e,r,n=200,a)=>e.json(r,n,{"Content-Type":"application/json",...a??{}}),I=new Set(["S256"]),q="https://claude.ai/oauth/claude-code-client-metadata";function z(e,r,n){if(!I.has(r||""))return!1;const a=U("sha256").update(n).digest("base64url"),t=Buffer.from(a),s=Buffer.from(e);return t.length!==s.length?!1:R(t,s)}async function $(e){const r=Math.floor(Date.now()/1e3);return g.sign({type:"mcp_context",...e,iat:r,exp:r+600},M,S.HS256)}async function N(e){await g.verify(e,M,S.HS256);const{payload:r}=g.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function X(){return async e=>{if(e.req.method!=="GET")return o(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=y(e);return o(e,{resource:`${r}${u("/mcp")}`,authorization_servers:[r,f].filter(Boolean),bearer_methods_supported:["header"],resource_documentation:`${r}${_.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function x(){return async e=>{const r=y(e);return o(e,{issuer:f||"",authorization_endpoint:`${r}${u(_.MCP_AUTHORIZATION)}`,token_endpoint:`${r}${u(_.MCP_TOKEN_PORTAL)}`,jwks_uri:`${f||""}/.well-known/jwks.json`,scopes_supported:["openid","profile","email","offline_access"],registration_endpoint:`${r}${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 ee(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{return o(e,{client_id:A.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(r){return o(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function re(){return async e=>{const r=new URL(e.req.url),{searchParams:n}=r,a=n.get("redirect_uri"),t=n.get("client_id"),s=n.get("code_challenge"),i=n.get("code_challenge_method"),d=v();if(s&&!I.has(i||""))return o(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...I].join(", ")}`},400);if(k(t)&&!s)return o(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(k(t)&&a)try{const c=await L(t),l=t===q;if(!H(a,c.redirect_uris,{ignorePort:l}))return o(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(c){const l=c instanceof Error?c.message:String(c);return o(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${l}`},400)}m.sendMcpAuthorizationStartedMessage([{...h(d),redirect_uri:a||null}]);const p=y(e),w={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:t,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:s,mcpCodeChallengeMethod:s?i:null,timestamp:Date.now()};try{const c=await $(w),l=new URL(u(_.IDP_LOGIN),p);return l.searchParams.set("redirectTo",`${_.MCP_CALLBACK}/${c}`),l.searchParams.set("idpId","oidc"),e.redirect(l.toString())}catch(c){const l=c instanceof Error?c.message:String(c),P=c instanceof Error?c.stack:String(c);m.sendMcpAuthorizationFailedMessage([{...h(d),error:l,error_details:P}]);const E=new URL(u(`${f}/oauth2/auth`));return E.search=n.toString(),e.redirect(E.toString())}}}function te(){return async e=>{if(e.req.method!=="POST")return o(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),n=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0,s=r.get("code_verifier")||void 0;if(n!=="authorization_code"||!a)return o(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const i=await D(a);if(t&&t!==i.redirect_uri)return o(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(i.code_challenge){if(!s)return o(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!z(i.code_challenge,i.code_challenge_method,s))return o(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(A.OAUTH_CLIENT_ID&&i.client_id&&!k(i.client_id)&&i.client_id!==A.OAUTH_CLIENT_ID)return o(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=i.id_token;if(typeof d!="string"||d.length===0)return o(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);let p=d;if(i.idp_access_token){const{payload:c,header:{kid:l}}=g.decode(d);p=await g.sign({...c,idp_access_token:i.idp_access_token},M,S.HS256,l)}return o(e,{access_token:p,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:d},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return o(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const n=r instanceof Error?r.message:String(r);return o(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}function oe(){return async e=>{const r=new URL(e.req.url);let n=r.searchParams.get("context");if(!n&&r.pathname.startsWith(u(`${_.MCP_CALLBACK}/`))){const t=r.pathname.split("/");n=t[t.length-1]}if(!n)return m.sendMcpAuthorizationFailedMessage([{...h(null),error:"Missing context parameter",error_details:null}]),e.text("Missing context parameter",400);let a=null;try{const t=await N(n);if(a=t.mcpSessionId||null,!t.isMcpFlow||!t.originalRedirectUri)throw new Error("Invalid MCP context");const s=C(e,T.IDP_ID_TOKEN)||C(e,T.AUTHORIZATION),i=C(e,T.IDP_ACCESS_TOKEN);if(!s)return m.sendMcpAuthorizationFailedMessage([{...h(a),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await O({idToken:s,idpAccessToken:i||void 0,clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),p=new URL(t.originalRedirectUri);return p.searchParams.set("code",d),t.mcpState&&p.searchParams.set("state",t.mcpState),m.sendMcpAuthorizationCompletedMessage([{...h(a),redirect_uri:t.originalRedirectUri||null}]),e.redirect(p.toString())}catch(t){const s=t instanceof Error?t.message:String(t),i=t instanceof Error?t.stack:String(t);return m.sendMcpAuthorizationFailedMessage([{...h(a),error:s,error_details:i}]),e.text(`Invalid MCP callback: ${s}`,400)}}}export{$ as createMcpContextToken,re as mcpAuthorizationHandler,oe as mcpCallbackHandler,ee as mcpDynamicClientRegistrationHandler,x as mcpOAuthAuthorizationServerHandler,X as mcpOAuthProtectedResourceHandler,te as mcpTokenPortalHandler,N as verifyAndParseMcpContextToken,z as verifyPkce};
@@ -1,9 +1,11 @@
1
1
  import type { Node, Variable, RenderableTreeNode, Function } from '@markdoc/markdoc';
2
+ import type { RenderTagFn } from '../server/types/plugins/markdown.js';
2
3
  export type GetInnerContentFnOptions = {
3
4
  indent?: number;
4
5
  isTrim?: boolean;
5
6
  skipConditionals?: boolean;
6
7
  variables?: Record<string, unknown>;
8
+ renderTag?: RenderTagFn;
7
9
  };
8
10
  export type GetInnerContentFn = (children: (RenderableTreeNode | Node | Variable | Function)[], options?: GetInnerContentFnOptions) => string;
9
11
  //# sourceMappingURL=markdoc.d.ts.map
@@ -1 +1 @@
1
- function t(e){return e.replace(/\0/g,"")}export{t as sanitizePath};
1
+ function n(r){return r.split("\0").join("")}export{n as sanitizePath};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/realm",
3
- "version": "0.134.0-next.4",
3
+ "version": "0.134.0-next.6",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  "@opentelemetry/sdk-trace-web": "2.6.1",
31
31
  "@opentelemetry/semantic-conventions": "1.40.0",
32
32
  "@redocly/ajv": "8.18.0",
33
- "@redocly/openapi-core": "2.31.2",
33
+ "@redocly/openapi-core": "2.31.6",
34
34
  "@shikijs/transformers": "3.21.0",
35
35
  "@tanstack/react-query": "5.62.3",
36
36
  "@tanstack/react-table": "8.21.3",
@@ -91,14 +91,14 @@
91
91
  "xpath": "0.0.34",
92
92
  "yaml-ast-parser": "0.0.43",
93
93
  "zod": "^3.25.76",
94
- "@redocly/asyncapi-docs": "1.11.0-next.3",
94
+ "@redocly/asyncapi-docs": "1.11.0-next.5",
95
95
  "@redocly/config": "0.49.0",
96
- "@redocly/graphql-docs": "1.11.0-next.3",
97
- "@redocly/openapi-docs": "3.22.0-next.3",
96
+ "@redocly/graphql-docs": "1.11.0-next.5",
97
+ "@redocly/openapi-docs": "3.22.0-next.5",
98
98
  "@redocly/portal-legacy-ui": "0.17.0-next.1",
99
- "@redocly/portal-plugin-mock-server": "0.19.0-next.3",
99
+ "@redocly/portal-plugin-mock-server": "0.19.0-next.5",
100
100
  "@redocly/realm-asyncapi-sdk": "0.12.0-next.3",
101
- "@redocly/theme": "0.66.0-next.3"
101
+ "@redocly/theme": "0.66.0-next.4"
102
102
  },
103
103
  "peerDependencies": {
104
104
  "react": "^19.2.4",