@redocly/realm 0.129.0-next.5 → 0.129.0
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 +58 -1
- package/dist/constants/common.d.ts +1 -0
- package/dist/constants/common.js +1 -1
- package/dist/server/plugins/catalog-entities/database/constants/relation-normalization.d.ts +3 -0
- package/dist/server/plugins/catalog-entities/database/constants/relation-normalization.js +1 -0
- package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-dto.js +1 -1
- package/dist/server/plugins/catalog-entities/database/mappers/create-entity-relation-db-record-from-file-schema.js +1 -1
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-bff-repository.d.ts +14 -0
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-bff-repository.js +112 -0
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-read-repository.js +12 -130
- package/dist/server/plugins/catalog-entities/database/repositories/local/catalog-entities-local-write-repository.js +1 -1
- package/dist/server/plugins/catalog-entities/utils/normalize-relation.d.ts +77 -0
- package/dist/server/plugins/catalog-entities/utils/normalize-relation.js +1 -0
- package/dist/server/plugins/config-parser/format-error.js +13 -5
- package/dist/server/plugins/config-parser/loaders/utils/read-and-validate-config.js +1 -1
- package/dist/server/providers/database/databases/catalog-sqlite/migrations/0003_catalog_versions_and_revisions_relations.sql +12 -6
- package/dist/server/providers/database/databases/catalog-sqlite/migrations/0004_normalize_relation_types.sql +147 -0
- package/dist/server/providers/database/databases/catalog-sqlite/migrations/meta/0004_snapshot.json +392 -0
- package/dist/server/providers/database/databases/catalog-sqlite/migrations/meta/_journal.json +7 -0
- package/dist/server/providers/database/databases/sqld-sqlite/migrations/0006_catalog-versions-and-revisions-relations.sql +12 -6
- package/dist/server/providers/database/pagination/filter.d.ts +1 -0
- package/dist/server/providers/database/pagination/filter.js +1 -1
- package/dist/server/web-server/auth.js +2 -2
- package/dist/server/web-server/routes/auth.d.ts +1 -0
- package/dist/server/web-server/routes/auth.js +1 -1
- package/dist/server/web-server/routes/index.js +1 -1
- package/dist/server/web-server/routes/mcp-oauth.d.ts +10 -0
- package/dist/server/web-server/routes/mcp-oauth.js +1 -1
- package/dist/server/web-server/utils/get-request-origin.d.ts +3 -0
- package/dist/server/web-server/utils/get-request-origin.js +1 -0
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setCookie as L,deleteCookie as q}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as M,getPathPrefix as C}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as U}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(s){if(process.env.NODE_ENV==="production")return s.newResponse(null,404,{});const{password:e,...o}=await s.req.json(),a=await S.sign({...o,name:o.username||o.email||"Unknown"},v);return L(s,"authorization",a,{path:C()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),s.newResponse(null,200,{})}function Pe(){return async s=>{const e=s.get("logger"),o=encodeURIComponent(s.req.query("message")||"");e.error(`Login error: ${o}`);const a=`${D.LOGIN}/?error=${encodeURIComponent(o)}`;return s.newResponse(null,301,{Location:a})}}function j(s){if(!s||!s.includes(D.MCP_CALLBACK))return null;try{const e=s.split("/"),o=e[e.length-1];if(o){const a=Buffer.from(o,"base64url").toString("utf-8");return JSON.parse(a).mcpSessionId||null}}catch{}return null}function Se(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?j(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,i=a?.[f];if(!$(i))return o.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,i);if(a&&!d.token_endpoint){const p="Invalid OIDC configuration: token_endpoint is required";return o.error(`OIDC login error: ${p}`),e.text(p,500)}try{const p=d.token_endpoint,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,s,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(M(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),_=e.get("cookies")?.code_verifier,u=await Z(p,l,A,i,{...i.tokenRequestCustomParams,..._?{code_verifier:_}:{}});if(u.error)return o.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return o.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:r,header:T}=S.decode(u.id_token),H=T.alg===ue.RS256;if(i.audience?.length&&![...U(r.aud||[]),...U(r[E]||[])].some(R=>i.audience?.includes(R))){const R="No valid audience found in id_token";return o.error(`OIDC login error: ${R}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:R,error_details:null}),new Response(`Forbidden: ${R}`)}const b=H?u.id_token:await S.sign({...r,idpId:f},v);ie(r)||o.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const O=i?.tokenExpirationTime?Date.now()+i.tokenExpirationTime*1e3:r.exp*1e3||Date.now()+F*1e3;if(i.introspectEndpoint){const h=await fetch(i.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:C()||"/",httpOnly:!1,expires:new Date(O)}))}else o.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:C()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:C()||"/",httpOnly:!0,expires:new Date(O)}),q(e,"code_verifier",{path:C()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),R=M(n.redirectTo),P=`${h}${R}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return o.updateContext({email:r.email,subject:r.sub}),o.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(o.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return o.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return o.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(s){return async e=>{const o=e.get("logger"),n=e.get("auth").claims?.idpId,t=s.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(t)||q(e,"authorization",{path:C()||"/"}),o.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const i=(await z(n,t)).end_session_endpoint;if(i){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:l;c=se(i,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return o.info("Logout successful"),q(e,"authorization",{path:C()||"/"}),e.newResponse(null,302,{Location:c||M("/")})}}function $e(s){return async e=>{const o=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(t,c,i)=>t&&c?`${t} ${c.charAt(0)}`:i;try{if(!n)throw new Error("BH_API_URL is not set");const t=s.getConfig().ssoDirect;if(!t||!Object.keys(t).length)return o.warn("Invite no sso configured to handle"),e.redirect(M("/"));const c=await fetch(`${n}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(o.warn(`Invite ${a} not found redirect to homepage`),e.redirect(M("/"))):(o.error("Invite error",await c.text()),e.redirect(M("/")));const i=await c.json(),d=new URL(M("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",i.organization.name),d.searchParams.set("invitedBy",f(i.invitedBy.firstName,i.invitedBy.lastName,i.invitedBy.name)),e.newResponse(null,302,{Location:d.toString()})}catch(t){return o.error("Error processing invite",{error:t,inviteCode:a}),e.text(t.message||"Failed to process invite",400)}}}function Te(s){return async e=>{const o=e.get("logger"),a=s.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,i=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const r="Invalid idpId";if(o.error(`IdP login error: ${r}`),m){const T=j(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(T),error:r,error_details:null})}return e.text(`Forbidden: ${r}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const r of Object.keys(g?.extraParams||{}))A[r]=n.searchParams.get(r)||g?.extraParams?.[r]||void 0;let _,u={};if(m&&l&&g&&g.type===G.OIDC){o.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const r=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});_=r.loginUrl,u=r.cookies||{}}else if(g){const r=x({...g,extraParams:A},i,w,f);_=r.loginUrl,u=r.cookies||{}}return Object.keys(u).forEach(r=>{L(e,r,u[r].value,u[r].options)}),o.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:_||new URL(e.req.url).pathname})}}function qe(s){return async e=>{const o=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const r="SAMLResponse is required";return o.error(`SAML2 login error: ${r}`),e.text(`Bad request: ${r}`,400)}const t=ee(n),{success:c,uid:i,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);if(!c){const r="SAML2 assertion is not successful";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const r="SAML2 Token Expired";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const g=s.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const r="Cannot find valid IdP";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){const r="IssuerID is misconfigured or untrusted assertions issuer received";return o.error(`SAML2 login error: ${r}`),e.text(`Permission denied: ${r}`,401)}if(!await te(t,g.x509PublicCert)){const r="SAMLResponse signature invalid";return o.error(`SAML2 login error: ${r}`),e.text(r,401)}const _=re(i,d,w,g.teamsAttributeName);if(!_.sub){const r="The provider did not return a valid user identity.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}if(!_.email){const r="The provider did not return a valid user email.";return o.error(`SAML2 login error: ${r}`),e.text(r,400)}const u=await S.sign({..._,idpId:m},v);return L(e,"authorization",u,{path:C()||"/",httpOnly:!0,expires:new Date(l*1e3)}),o.updateContext({email:_.email,subject:_.sub}),o.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function Ue(s){return async e=>{const o=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=M(B(a.pathname)),f=s.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const i=e.req.query("token"),d=i&&await ae(i);if(!d)return e.newResponse(null,302,{Location:n});if(!U(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:C()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),o.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,$e as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,Pe as redoclyLoginCallbackHandler,Ue as redoclyTokenLoginHandler,qe as samlCallbackHandler};
|
|
1
|
+
import{setCookie as L,deleteCookie as T}from"hono/cookie";import{AuthProviderType as G}from"@redocly/config";import{withPathPrefix as R,getPathPrefix as _}from"@redocly/theme/core/utils";import{compareURIs as X}from"../../../utils/url/compare-uris.js";import{ensureArray as q}from"../../../utils/array/ensure-array.js";import{ALTERNATIVE_AUD_CLAIM_NAME as E,JWT_SECRET_KEY as v,ORG_SLUG as W}from"../../constants/common.js";import{DEFAULT_COOKIE_EXPIRATION as F,ServerRoutes as D}from"../../../constants/common.js";import{sanitizeRedirectPathname as B}from"../../../utils/url/sanitize-redirect-pathname.js";import{telemetry as k}from"../../telemetry/index.js";import{getAuthProviderLoginParams as Y,isOidcProviderConfig as $,isSaml2ProviderConfig as Q,oidcExchangeCodeForToken as Z,buildLoginUrl as x,decodeSamlResponse as ee,extractUserClaims as re,parseSamlResponse as oe,parseOidcState as ne,verifySAMLResponse as te,getUsernameFromPayload as ie,buildOidcLogoutUrl as se,getOidcMetadata as z,getRedoclyTokenPayload as ae,isRedoclySso as de,rewritePreviewAuthRedirectUri as ce,parsePreviewBranch as N,buildOidcLoginUrl as le,createMcpSessionResource as I}from"../auth.js";import*as S from"../jwt/jwt.js";import{AlgorithmTypes as ue}from"../jwt/types.js";import{handleErrorPageRender as pe}from"../utils.js";import{encodeBase64URL as ge}from"../jwt/encode.js";async function Oe(i){if(process.env.NODE_ENV==="production")return i.newResponse(null,404,{});const{password:e,...r}=await i.req.json(),a=await S.sign({...r,name:r.username||r.email||"Unknown"},v);return L(i,"authorization",a,{path:_()||"/",httpOnly:!0,secure:!0,sameSite:"none"}),i.newResponse(null,200,{})}function Pe(){return async i=>{const e=i.get("logger"),r=encodeURIComponent(i.req.query("message")||"");e.error(`Login error: ${r}`);const a=`${D.LOGIN}/?error=${encodeURIComponent(r)}`;return i.newResponse(null,301,{Location:a})}}function H(i){if(!i||!i.includes(D.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 Se(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=ne(e.req.query("state")),f=n.idpId,t=n.source==="mcp"||n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK),c=t?H(typeof n.redirectTo=="string"?n.redirectTo:void 0):null,s=a?.[f];if(!$(s))return r.error("OIDC login error: missing OIDC provider config"),e.text("Forbidden",403);const d=await z(f,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,l=e.req.query("code"),m=e.req.query("error");if(m)return t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`OIDC error: ${m}`,error_details:e.req.query("error_description")||null}),pe(e,i,{slug:"/"},403,"403OIDC");if(!l){const h="Code is expected but not present";return r.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const y=e.req.header("x-forwarded-host"),g=e.req.header("x-forwarded-proto")||"https",A=t&&typeof n.redirectUri=="string"?n.redirectUri:new URL(R(D.OIDC_CALLBACK),y?`${g}://${y}`:e.req.url).toString(),C=e.get("cookies")?.code_verifier,u=await Z(p,l,A,s,{...s.tokenRequestCustomParams,...C?{code_verifier:C}:{}});if(u.error)return r.error(`Error from OIDC provider: "${u.error}"`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:`Token exchange error: ${u.error}`,error_details:u.error_description||null}),e.text(`Forbidden: ${u.error_description||u.error}`,403);if(!u?.id_token){const h="No id_token, please, add openid to scopes";return r.error(`OIDC login error: ${h}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:h,error_details:null}),new Response(`Forbidden: ${h}`,{status:403})}const{payload:o,header:U}=S.decode(u.id_token),j=U.alg===ue.RS256;if(s.audience?.length&&![...q(o.aud||[]),...q(o[E]||[])].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({...I(c),error:M,error_details:null}),new Response(`Forbidden: ${M}`)}const b=j?u.id_token:await S.sign({...o,idpId:f},v);ie(o)||r.warn("To display your username, the required 'email' or 'full_profile' scope must be added to the identity provider configuration");const O=s?.tokenExpirationTime?Date.now()+s.tokenExpirationTime*1e3:o.exp*1e3||Date.now()+F*1e3;if(s.introspectEndpoint){const h=await fetch(s.introspectEndpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({access_token:u.access_token})});if(h.ok){const P=(await h.json()).ext?.federatedIdentity;P&&(L(e,"federated_access_token",P.access_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}),L(e,"federated_id_token",P.id_token||"",{path:_()||"/",httpOnly:!1,expires:new Date(O)}))}else r.warn(`OIDC introspect error: ${h.statusText}`)}if(L(e,"authorization",b,{path:_()||"/",httpOnly:!0,expires:new Date(O)}),b!==u.id_token&&L(e,"idp_id_token",u.id_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),L(e,"idp_access_token",u.access_token||"",{path:_()||"/",httpOnly:!0,expires:new Date(O)}),T(e,"code_verifier",{path:_()||"/"}),t&&n.redirectTo&&typeof n.redirectTo=="string"&&n.redirectTo.includes(D.MCP_CALLBACK)){const h=e.req.url.split("?")[0].replace(D.OIDC_CALLBACK,""),M=R(n.redirectTo),P=`${h}${M}`;return e.newResponse(null,302,{Location:P})}const K=typeof n.redirectTo=="string"?n.redirectTo:void 0;let J=B(new URL(K||"/",e.req.url).pathname);const V=e.newResponse(null,302,{Location:J});return r.updateContext({email:o.email,subject:o.sub}),r.info("OIDC login successful"),V}catch(p){const l=p instanceof Error?p.message:String(p),m=p instanceof Error?p.stack:String(p);if(r.error(`OIDC login error: ${l}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:l,error_details:m}),p.error==="access_denied")return r.info("Access denied"),e.text("Forbidden",403)}const w="Something went wrong";return r.error(`OIDC login error: ${w}`),t&&k.sendMcpAuthorizationFailedMessage({...I(c),error:w,error_details:null}),e.text(w,500)}}function ve(i){return async e=>{const r=e.get("logger"),n=e.get("auth").claims?.idpId,t=i.getConfig().ssoDirect?.[n];if(e.req.method==="POST")return $(t)||T(e,"authorization",{path:_()||"/"}),r.info("Logout successful"),e.newResponse(null,200,{});let c;if($(t)){const s=(await z(n,t)).end_session_endpoint;if(s){const d=new URL(e.req.url),w=e.req.header("x-forwarded-proto")||d.protocol.slice(0,-1)||"https",p=e.req.header("x-forwarded-host")||d.host,l=`${w}://${p}`,m=N(l),y=m?ge(JSON.stringify({branch:N(l)})):void 0,g=m?`${ce(l)}/_auth/logout`:`${l}/post-logout`;c=se(s,g,e.get("cookies")?.idp_id_token||e.get("cookies")?.authorization||"",y)}}return r.info("Logout successful"),T(e,"authorization",{path:_()||"/"}),e.newResponse(null,302,{Location:c||R("/")})}}function $e(i){return async e=>{const r=i.getConfig().logoutReturnUrl,a=r||R("/");return e.newResponse(null,302,{Location:a})}}function Ue(i){return async e=>{const r=e.get("logger"),a=e.req.param("code"),n=process.env.BH_API_URL,f=(t,c,s)=>t&&c?`${t} ${c.charAt(0)}`:s;try{if(!n)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(R("/"));const c=await fetch(`${n}/user-invites/public/${a}`);if(!c.ok)return c.status===404?(r.warn(`Invite ${a} not found redirect to homepage`),e.redirect(R("/"))):(r.error("Invite error",await c.text()),e.redirect(R("/")));const s=await c.json(),d=new URL(R("/invite"),e.req.url);return d.searchParams.set("code",a),d.searchParams.set("org",s.organization.name),d.searchParams.set("invitedBy",f(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 Te(i){return async e=>{const r=e.get("logger"),a=i.getConfig().ssoDirect,n=new URL(e.req.url),f=e.req.query("inviteCode"),t=e.req.header("x-forwarded-proto")||n.protocol.slice(0,-1)||"https",c=e.req.header("x-forwarded-host")||n.host,s=`${t}://${c}`;let d=n.searchParams.get("idpId");const w=n.searchParams.get("redirectTo"),p=Object.keys(a||{})[0];d=d||p;const l=n.searchParams.get("mcp_redirect_uri"),m=!!l;if(!a?.[d]){const o="Invalid idpId";if(r.error(`IdP login error: ${o}`),m){const U=H(w||void 0);k.sendMcpAuthorizationFailedMessage({...I(U),error:o,error_details:null})}return e.text(`Forbidden: ${o}`,403)}const g=d&&a?await Y(d,a[d]):void 0,A={};for(const o of Object.keys(g?.extraParams||{}))A[o]=n.searchParams.get(o)||g?.extraParams?.[o]||void 0;let C,u={};if(m&&l&&g&&g.type===G.OIDC){r.info(`Building MCP OAuth login URL with redirect_uri: ${l}`);const o=le("",{...g,extraParams:A},w,f,{redirectUriOverride:l,sourceOverride:"mcp",branchOverride:void 0});C=o.loginUrl,u=o.cookies||{}}else if(g){const o=x({...g,extraParams:A},s,w,f);C=o.loginUrl,u=o.cookies||{}}return Object.keys(u).forEach(o=>{L(e,o,u[o].value,u[o].options)}),r.info(`IdP login initiated for ID '${d}'`),e.newResponse(null,302,{Location:C||new URL(e.req.url).pathname})}}function qe(i){return async e=>{const r=e.get("logger"),a=await e.req.formData(),n=a.get("SAMLResponse"),f=a.get("RelayState");if(typeof n!="string"||typeof f!="string"){const o="SAMLResponse is required";return r.error(`SAML2 login error: ${o}`),e.text(`Bad request: ${o}`,400)}const t=ee(n),{success:c,uid:s,nameFormat:d,attrs:w,issuerId:p,expiresAt:l}=oe(t),{idpId:m,redirectTo:y}=JSON.parse(f);if(!c){const o="SAML2 assertion is not successful";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!l||Math.ceil(Date.now()/1e3)>=l){const o="SAML2 Token Expired";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const g=i.getConfig().ssoDirect?.[m];if(!g||!Q(g)){const o="Cannot find valid IdP";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!(g.issuerId&&p&&X(g.issuerId,p))){const o="IssuerID is misconfigured or untrusted assertions issuer received";return r.error(`SAML2 login error: ${o}`),e.text(`Permission denied: ${o}`,401)}if(!await te(t,g.x509PublicCert)){const o="SAMLResponse signature invalid";return r.error(`SAML2 login error: ${o}`),e.text(o,401)}const C=re(s,d,w,g.teamsAttributeName);if(!C.sub){const o="The provider did not return a valid user identity.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}if(!C.email){const o="The provider did not return a valid user email.";return r.error(`SAML2 login error: ${o}`),e.text(o,400)}const u=await S.sign({...C,idpId:m},v);return L(e,"authorization",u,{path:_()||"/",httpOnly:!0,expires:new Date(l*1e3)}),r.updateContext({email:C.email,subject:C.sub}),r.info("SAML2 login successful"),e.newResponse(null,302,{Location:y||"/"})}}function be(i){return async e=>{const r=e.get("logger"),a=new URL(e.req.query("redirectTo")||"/",e.req.url),n=R(B(a.pathname)),f=i.getConfig().ssoDirect,t=Object.entries(f||{}).find(([,y])=>$(y)&&de(y));if(!(f&&t))return e.newResponse(null,302,{Location:n});const s=e.req.query("token"),d=s&&await ae(s);if(!d)return e.newResponse(null,302,{Location:n});if(!q(d[E]||[]).some(y=>y===W))return e.newResponse(null,302,{Location:n});const l=await S.sign({...d,idpId:t?.at(0)},v),m=Date.now()+F*1e3;return L(e,"authorization",l,{path:_()||"/",httpOnly:!0,expires:new Date(m),sameSite:"None",secure:!0}),r.info("Token login successful"),e.newResponse(null,302,{Location:n})}}export{Oe as authorizeHandler,Te as idpLoginHandler,Ue as inviteHandler,ve as logoutHandler,Se as oidcCallbackHandler,$e as postLogoutHandler,Pe as redoclyLoginCallbackHandler,be as redoclyTokenLoginHandler,qe as samlCallbackHandler};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{serveStatic as f}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as s}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as R}from"../../constants/common.js";import{authMiddleware as c}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as S}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as
|
|
1
|
+
import{serveStatic as f}from"hono/serve-static";import{withPathPrefix as e,withoutPathPrefix as s}from"@redocly/theme/core/utils";import{ServerRoutes as i}from"../../../constants/common.js";import{PUBLIC_STATIC_FOLDER as R}from"../../constants/common.js";import{authMiddleware as c}from"../middleware/authMiddleware.js";import{ensureSearchData as g}from"../middleware/ensureSearchData.js";import{dynamicMiddleware as S}from"../middleware/dynamic-middleware/dynamic-middleware.js";import{installRoutes as P}from"../../plugins/dev-onboarding/api/routes/index.js";import{authorizeHandler as I,oidcCallbackHandler as r,logoutHandler as d,postLogoutHandler as h,idpLoginHandler as D,redoclyLoginCallbackHandler as N,samlCallbackHandler as M,redoclyTokenLoginHandler as U,inviteHandler as B}from"./auth.js";import{appDataHandler as G}from"./app-data.js";import{searchFacetsHandler as w,searchHandler as F}from"./search.js";import{dynamicRouteHandler as v}from"./dynamic-route.js";import{pageDataHandler as K,sharedPageDataHandler as y}from"./page-data.js";import{pathPrefixRedirectHandler as k}from"./path-prefix-redirect.js";import{getRoutesByLineHandler as E,resolvePathHandler as o,resolvePathsHandler as Y,resolveSlugHandler as p}from"./resolve-route.js";import{feedbackHandler as b}from"./feedback.js";import{loggerMiddleware as V}from"../middleware/loggerMiddleware.js";import{responseHeadersMiddleware as x}from"../middleware/responseHeadersMiddleware.js";import{idleTimeoutMiddleware as z}from"../middleware/idleTimeoutMiddleware.js";import{otelTracesHandler as Z}from"./otel/otel.js";import{healthCheckHandler as $}from"./health.js";import{askAiHandler as q}from"./ask-ai.js";import{replayOauth2RedirectCallbackHandler as W}from"./replay-oauth2-redirect.js";import{mcpOAuthProtectedResourceHandler as j,mcpOAuthAuthorizationServerHandler as J,mcpDynamicClientRegistrationHandler as Q,mcpAuthorizationHandler as X,mcpTokenPortalHandler as u,mcpCallbackHandler as _}from"./mcp-oauth.js";import{corsMiddleware as O}from"../middleware/corsMiddleware.js";import{installApiRoutes as aa}from"./api-routes/api-routes.js";import{cookieMiddleware as ea}from"../middleware/cookieMiddleware.js";import{staticContentHandler as ia}from"../routes/static-content.js";import{infoHandler as L}from"./info.js";import{catalogHandler as la}from"./catalog/catalog.js";import{catalogRelationsHandler as ta}from"./catalog/catalog-relations.js";import{bffCatalogHandler as ma}from"./catalog/bff-catalog.js";import{bffCatalogRevisionsHandler as na}from"./catalog/bff-catalog-revisions.js";import{bffCatalogRelatedEntitiesHandler as Aa}from"./catalog/bff-catalog-related-entities.js";import{catalogAuthMiddleware as C}from"../middleware/catalogAuthMiddleware.js";import{telemetryMiddleware as Ta}from"../middleware/telemetry-middleware.js";import{errorHandler as da}from"./error.js";function Wa(a,l,t){const{resolveRouteData:n,readStaticAsset:A}=t;a.use("*",z()),a.use("*",ea()),a.use("*",S(l)),a.use("*",c(l)),a.use("*",V()),a.use("*",x(l)),a.use("*",Ta()),a.use(e("*"),f({root:`./${R}`,getContent:(m,H)=>ia(m,H,l,A),rewriteRequestPath:m=>s(m)})),a.use(e(i.FEEDBACK),O({allowMethods:["POST"]})),a.use(e(i.ASK_AI),O({allowMethods:["POST"]})),a.use("*",Ea(l));const T=g(l);a.use(e(i.INFO),L()),process.env.NEW_CATALOG_ENABLED==="true"&&(a.use(e(i.CATALOG_ENTITIES),C(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES_RELATIONS),C(l.serverOutDir,{secureMethods:["POST","PUT","DELETE","PATCH"]})),a.use(e(i.CATALOG_ENTITIES),la(l)),a.use(e(i.CATALOG_ENTITIES_RELATIONS),ta(l)),a.get(e(i.BFF_CATALOG_ENTITIES),ma(l)),a.get(e(i.BFF_CATALOG_RELATED_ENTITIES),Aa(l)),a.get(e(i.BFF_CATALOG_REVISIONS),na(l))),a.get(e(i.SHARED_PAGE_DATA),y(l)),a.get(e(i.PAGE_DATA),K(l,n)),a.get(e(i.APP_DATA),G(l)),a.post(e(i.SEARCH),T,F(l)),a.post(e(i.SEARCH_FACETS),T,w(l)),a.post(e(i.AUTHORIZATION),I),a.post(e(i.LOGOUT),d(l)),a.get(e(i.LOGOUT),d(l)),a.get(e(i.POST_LOGOUT),h(l)),a.get(e(i.OIDC_CALLBACK),r(l)),a.get(e(i.REDOCLY_TOKEN_LOGIN),U(l)),a.get(e(i.REDOCLY_LOGIN_CALLBACK),N()),a.get(e(i.IDP_LOGIN),D(l)),a.post(e(i.SAML_CALLBACK),M(l)),a.get(e(i.INVITE),B(l)),a.get(e(i.HEALTH),$),a.get(e(i.MCP_OAUTH_PROTECTED_RESOURCE),j()),a.get(e(i.MCP_OAUTH_AUTHORIZATION_SERVER),J()),a.post(e(i.MCP_DYNAMIC_CLIENT_REGISTRATION),Q()),a.get(e(i.MCP_AUTHORIZATION),X()),a.post(e(i.MCP_TOKEN_PORTAL),u()),a.get(e(i.MCP_CALLBACK),_()),a.get(e(`${i.MCP_CALLBACK}/*`),_()),P(a,l),aa(a,l),a.post(e(i.FEEDBACK),b(l)),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTES_BY_PATHS),Y(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.post(e(i.ASK_AI),q(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l)),a.post(e(i.OTEL_TRACES),Z),a.get(e(i.REPLAY_OAUTH2_CALLBACK),W),a.all(e("/*"),v(l,n,A)),a.get("*",k),a.onError(da)}function Ea(a){return async(l,t)=>{await a.waitForPluginsLifecycle(),await t()}}function ja(a,l){a.get(e(i.INFO),L()),a.post(e(i.RESOLVE_ROUTE_BY_PATH),o(l)),a.post(e(i.RESOLVE_ROUTE_BY_SLUG),p(l)),a.get(e(i.GET_ROUTES_BY_LINE),E(l))}export{ja as installDevRoutes,Wa as installProdRoutes,Ea as waitForPluginsLifecycle};
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { Handler } from 'hono';
|
|
2
|
+
export type McpContextPayload = {
|
|
3
|
+
isMcpFlow: boolean;
|
|
4
|
+
originalRedirectUri: string | null;
|
|
5
|
+
mcpClientId: string | null;
|
|
6
|
+
mcpState: string | null;
|
|
7
|
+
mcpSessionId: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
|
|
11
|
+
export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
|
|
2
12
|
export declare function mcpOAuthProtectedResourceHandler(): Handler;
|
|
3
13
|
export declare function mcpOAuthAuthorizationServerHandler(): Handler;
|
|
4
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getCookie as
|
|
1
|
+
import{getCookie as C}from"hono/cookie";import{ulid as w}from"ulid";import{AUTH_URL as _,JWT_SECRET_KEY as y}from"../../constants/common.js";import{ServerRoutes as d}from"../../../constants/common.js";import{withPathPrefix as m}from"@redocly/theme/core/utils";import{telemetry as l}from"../../telemetry/index.js";import{createMcpAuthorizationCode as k,verifyMcpAuthorizationCode as S,createMcpSessionResource as u}from"../auth.js";import*as h from"../jwt/jwt.js";import{getRequestOrigin as g}from"../utils/get-request-origin.js";const n=(e,r,o=200,a)=>e.json(r,o,{"Content-Type":"application/json",...a??{}});async function T(e){const r=Math.floor(Date.now()/1e3);return h.sign({type:"mcp_context",...e,iat:r,exp:r+600},y)}async function I(e){await h.verify(e,y);const{payload:r}=h.decode(e);if(r.type!=="mcp_context")throw new Error("Invalid context token type");return r}function O(){return async e=>{if(e.req.method!=="GET")return n(e,{error:"Method not allowed"},405,{Allow:"GET"});const r=g(e);return n(e,{resource:`${r}/mcp`,authorization_servers:[r],bearer_methods_supported:["header"],resource_documentation:`${r}/.well-known/oauth-authorization-server`,scopes_supported:["openid","profile","email","offline_access"],bearer_token_types_supported:["Bearer"]})}}function D(){return async e=>{const r=g(e);return n(e,{issuer:_||"",authorization_endpoint:`${r}${d.MCP_AUTHORIZATION}`,token_endpoint:`${r}${d.MCP_TOKEN_PORTAL}`,jwks_uri:`${_||""}/.well-known/jwks.json`,registration_endpoint:`${r}${d.MCP_DYNAMIC_CLIENT_REGISTRATION}`,scopes_supported:["openid","profile","email","offline_access"],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"]})}}function $(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{return n(e,{client_id:process.env.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 n(e,{error:"invalid_request",error_description:r?.message||"Unable to register client"},500)}}}function q(){return async e=>{const r=new URL(e.req.url),{searchParams:o}=r,a=o.get("redirect_uri"),t=w();l.sendMcpAuthorizationStartedMessage({...u(t),redirect_uri:a||null});const s=g(e),c={isMcpFlow:!0,originalRedirectUri:a,mcpClientId:o.get("client_id"),mcpState:o.get("state"),mcpSessionId:t,timestamp:Date.now()};try{const i=await T(c),p=new URL(d.IDP_LOGIN,s);return p.searchParams.set("redirectTo",`${m(d.MCP_CALLBACK)}/${i}`),p.searchParams.set("idpId","oidc"),e.redirect(p.toString())}catch(i){const p=i instanceof Error?i.message:String(i),M=i instanceof Error?i.stack:String(i);l.sendMcpAuthorizationFailedMessage({...u(t),error:p,error_details:M});const f=new URL(m(`${_}/oauth2/auth`));return f.search=o.toString(),e.redirect(f.toString())}}}function H(){return async e=>{if(e.req.method!=="POST")return n(e,{error:"Method not allowed"},405);try{const r=await e.req.formData(),o=r.get("grant_type"),a=r.get("code"),t=r.get("redirect_uri")||void 0;if(o!=="authorization_code"||!a)return n(e,{error:"invalid_request",error_description:"Invalid grant type or missing authorization code"},400);try{const s=await S(a);if(t&&t!==s.redirect_uri)return n(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(process.env.OAUTH_CLIENT_ID&&s.client_id&&s.client_id!==process.env.OAUTH_CLIENT_ID)return n(e,{error:"invalid_client",error_description:"Client mismatch"},400);const c=s.id_token;return typeof c!="string"||c.length===0?n(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400):n(e,{access_token:c,token_type:"Bearer",expires_in:3600,scope:"openid profile email",id_token:c},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch{return n(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(r){const o=r instanceof Error?r.message:String(r);return n(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}function b(){return async e=>{const r=new URL(e.req.url);let o=r.searchParams.get("context");if(!o&&r.pathname.startsWith(m(`${d.MCP_CALLBACK}/`))){const t=r.pathname.split("/");o=t[t.length-1]}if(!o)return l.sendMcpAuthorizationFailedMessage({...u(null),error:"Missing context parameter",error_details:null}),e.text("Missing context parameter",400);let a=null;try{const t=await I(o);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"),c=await k({idToken:s||"",clientId:t.mcpClientId||"",redirectUri:t.originalRedirectUri,ttlSec:600}),i=new URL(t.originalRedirectUri);return i.searchParams.set("code",c),t.mcpState&&i.searchParams.set("state",t.mcpState),l.sendMcpAuthorizationCompletedMessage({...u(a),redirect_uri:t.originalRedirectUri||null}),e.redirect(i.toString())}catch(t){const s=t instanceof Error?t.message:String(t),c=t instanceof Error?t.stack:String(t);return l.sendMcpAuthorizationFailedMessage({...u(a),error:s,error_details:c}),e.text(`Invalid MCP callback: ${s}`,400)}}}export{T as createMcpContextToken,q as mcpAuthorizationHandler,b as mcpCallbackHandler,$ as mcpDynamicClientRegistrationHandler,D as mcpOAuthAuthorizationServerHandler,O as mcpOAuthProtectedResourceHandler,H as mcpTokenPortalHandler,I as verifyAndParseMcpContextToken};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function s(r){const e=new URL(r.req.url),o=r.req.header("x-forwarded-proto")||e.protocol.slice(0,-1)||"https",t=r.req.header("x-forwarded-host")||e.host;return`${o}://${t}`}export{s as getRequestOrigin};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/realm",
|
|
3
|
-
"version": "0.129.0
|
|
3
|
+
"version": "0.129.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -93,14 +93,14 @@
|
|
|
93
93
|
"xml-crypto": "6.0.1",
|
|
94
94
|
"xpath": "0.0.34",
|
|
95
95
|
"yaml-ast-parser": "0.0.43",
|
|
96
|
-
"@redocly/
|
|
97
|
-
"@redocly/
|
|
98
|
-
"@redocly/
|
|
99
|
-
"@redocly/
|
|
100
|
-
"@redocly/
|
|
101
|
-
"@redocly/
|
|
102
|
-
"@redocly/
|
|
103
|
-
"@redocly/
|
|
96
|
+
"@redocly/config": "0.41.2",
|
|
97
|
+
"@redocly/asyncapi-docs": "1.6.0",
|
|
98
|
+
"@redocly/openapi-docs": "3.17.0",
|
|
99
|
+
"@redocly/realm-asyncapi-sdk": "0.7.0",
|
|
100
|
+
"@redocly/theme": "0.61.0",
|
|
101
|
+
"@redocly/graphql-docs": "1.6.0",
|
|
102
|
+
"@redocly/portal-plugin-mock-server": "0.14.0",
|
|
103
|
+
"@redocly/portal-legacy-ui": "0.12.0"
|
|
104
104
|
},
|
|
105
105
|
"peerDependencies": {
|
|
106
106
|
"react": "19.2.3",
|