@redocly/realm 0.138.0-next.12 → 0.138.0-next.13

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 (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/client/app/pages/Invite/Invite.js +1 -1
  3. package/dist/constants/l10n/langs/ar.js +1 -1
  4. package/dist/constants/l10n/langs/de.js +1 -1
  5. package/dist/constants/l10n/langs/en.js +1 -1
  6. package/dist/constants/l10n/langs/es.js +1 -1
  7. package/dist/constants/l10n/langs/fr.js +1 -1
  8. package/dist/constants/l10n/langs/hi.js +1 -1
  9. package/dist/constants/l10n/langs/it.js +1 -1
  10. package/dist/constants/l10n/langs/ja.js +1 -1
  11. package/dist/constants/l10n/langs/ko.js +1 -1
  12. package/dist/constants/l10n/langs/pl.js +1 -1
  13. package/dist/constants/l10n/langs/pt-BR.js +1 -1
  14. package/dist/constants/l10n/langs/pt.js +1 -1
  15. package/dist/constants/l10n/langs/ru.js +1 -1
  16. package/dist/constants/l10n/langs/uk.js +1 -1
  17. package/dist/constants/l10n/langs/zh.js +1 -1
  18. package/dist/server/plugins/markdown/attribute-resolvers/code-walkthrough/file-chunk-parser.d.ts +1 -1
  19. package/dist/server/plugins/markdown/attribute-resolvers/resolve-link.js +1 -1
  20. package/dist/server/plugins/mcp/gateway-mcp/elicitation.d.ts +1 -0
  21. package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +5 -5
  22. package/dist/server/plugins/mcp/gateway-mcp/rate-limit/apply-edge-rate-limit.js +1 -1
  23. package/dist/server/plugins/mcp/gateway-mcp/secret-store.d.ts +9 -3
  24. package/dist/server/plugins/mcp/gateway-mcp/secret-store.js +1 -1
  25. package/dist/server/plugins/mcp/gateway-mcp/subject.js +1 -1
  26. package/dist/server/plugins/mcp/handlers/credentials-page/constants.d.ts +7 -0
  27. package/dist/server/plugins/mcp/handlers/credentials-page/constants.js +1 -0
  28. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.d.ts +5 -0
  29. package/dist/server/plugins/mcp/handlers/credentials-page/form-contract.js +1 -0
  30. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.d.ts +14 -0
  31. package/dist/server/plugins/mcp/handlers/credentials-page/form-page.js +62 -0
  32. package/dist/server/plugins/mcp/handlers/credentials-page/layout.d.ts +17 -0
  33. package/dist/server/plugins/mcp/handlers/credentials-page/layout.js +11 -0
  34. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.d.ts +10 -0
  35. package/dist/server/plugins/mcp/handlers/credentials-page/outcome-pages.js +11 -0
  36. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.d.ts +10 -0
  37. package/dist/server/plugins/mcp/handlers/credentials-page/site-branding.js +1 -0
  38. package/dist/server/plugins/mcp/handlers/credentials-page/styles.d.ts +4 -0
  39. package/dist/server/plugins/mcp/handlers/credentials-page/styles.js +186 -0
  40. package/dist/server/plugins/mcp/handlers/credentials-page/types.d.ts +32 -0
  41. package/dist/server/plugins/mcp/handlers/credentials-page/types.js +0 -0
  42. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.d.ts +0 -1
  43. package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +1 -1
  44. package/dist/server/plugins/mcp/index.js +1 -1
  45. package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
  46. package/dist/server/plugins/mcp/types.d.ts +6 -0
  47. package/dist/server/plugins/mcp/utils/mode.d.ts +4 -1
  48. package/dist/server/plugins/mcp/utils/mode.js +1 -1
  49. package/dist/server/plugins/scorecard-classic/template/components.js +1 -1
  50. package/dist/server/providers/database/client.d.ts +5 -0
  51. package/dist/server/providers/database/client.js +1 -1
  52. package/dist/server/providers/database/copy-migrations.js +1 -1
  53. package/dist/server/providers/database/database-connections-manager.js +1 -1
  54. package/dist/server/providers/database/has-project-database.d.ts +3 -0
  55. package/dist/server/providers/database/has-project-database.js +1 -0
  56. package/dist/server/web-server/auth.d.ts +0 -1
  57. package/dist/server/web-server/auth.js +4 -4
  58. package/dist/server/web-server/jwt/token-types.d.ts +1 -0
  59. package/dist/server/web-server/jwt/token-types.js +1 -1
  60. package/dist/server/web-server/routes/catalog/helpers/create-entity-schema.d.ts +1756 -1756
  61. package/dist/server/web-server/routes/catalog/helpers/create-entity-update-schema.d.ts +295 -295
  62. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.d.ts +24 -0
  63. package/dist/server/web-server/routes/mcp-routes/mcp-consent-page.js +35 -0
  64. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.d.ts +15 -0
  65. package/dist/server/web-server/routes/mcp-routes/mcp-oauth-store.js +1 -0
  66. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.d.ts +3 -1
  67. package/dist/server/web-server/routes/mcp-routes/mcp-oauth.js +1 -1
  68. package/dist/server/web-server/routes/mcp-routes/mcp-routes.js +1 -1
  69. package/package.json +5 -5
  70. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.d.ts +0 -12
  71. package/dist/server/plugins/mcp/handlers/mcp-credentials-page.js +0 -225
@@ -0,0 +1,24 @@
1
+ import type { PageSite } from '../../../plugins/mcp/handlers/credentials-page/types.js';
2
+ /** How long the signed consent value on the page stays valid. */
3
+ export declare const MCP_CONSENT_TTL_SEC: number;
4
+ export declare const CONSENT_PAGE_HEADERS: {
5
+ 'Content-Type': string;
6
+ 'Cache-Control': string;
7
+ 'X-Frame-Options': string;
8
+ 'Referrer-Policy': string;
9
+ 'Content-Security-Policy': string;
10
+ };
11
+ export type ConsentPageProps = {
12
+ site: PageSite;
13
+ user: string | undefined;
14
+ clientName: string | undefined;
15
+ clientIdHost: string | undefined;
16
+ redirectUrl: URL;
17
+ scopes: readonly string[];
18
+ accessLifetimeHours: number;
19
+ rememberDays: number | undefined;
20
+ consentToken: string;
21
+ };
22
+ export declare function consentPage({ site, user, clientName, clientIdHost, redirectUrl, scopes, accessLifetimeHours, rememberDays, consentToken, }: ConsentPageProps): string;
23
+ export declare function consentInvalidPage(site: PageSite): string;
24
+ //# sourceMappingURL=mcp-consent-page.d.ts.map
@@ -0,0 +1,35 @@
1
+ import{ICONS as d,code as E,escapeHtml as a,htmlDocument as p,siteHeader as N}from"../../../plugins/mcp/handlers/credentials-page/layout.js";import{statusPage as L}from"../../../plugins/mcp/handlers/credentials-page/outcome-pages.js";const $=300,e={TITLE:"Allow MCP access",INTRO:"An MCP client asks to use this portal with your account.",WARNING:"Allow access only if you started this connection in your MCP client.",REDIRECT_LABEL:"Sends access to",REDIRECT_HINT:"The app receives access at this address.",USER_LABEL:"Signed in as",NOT_PROVIDED:"Not provided",CLIENT_LABEL:"App",CLIENT_UNKNOWN:"The app did not send a name. Continue only if you just started a connection yourself.",CLIENT_NAME_HINT:"The app chose this name itself. We do not check it.",CLIENT_ID_HOST:s=>`App details loaded from ${E(s)}`,PANEL_TITLE:"What the client gets",ACCOUNT_DETAILS:"Your account details, including your name and email address",ACT_AS_YOU:"Use of the MCP server of this portal as you",ACCESS_LIFETIME:s=>`Access that it can renew for up to ${s} hours`,REMEMBER:s=>`If you allow access, we do not ask again for this client for ${s} days.`,SCOPES_LABEL:"Scopes:",ALLOW:"Allow access",DENY:"Deny",INVALID_TITLE:"This request is no longer valid",INVALID_BODY:s=>`An access request works for ${s} minutes. This one has expired or can no longer be used.`,INVALID_NEXT:"Go back to your MCP client and start the connection again."},f={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; img-src 'self'; frame-ancestors 'none'; base-uri 'none'"};function _({protocol:s,host:n,pathname:t,search:o}){return`${s}${n?`//${n}`:""}${t}${o}`}function r(s,n,t){return`<section aria-labelledby="${s}">
2
+ <h2 id="${s}">${a(n)}</h2>
3
+ ${t.filter(Boolean).join(`
4
+ `)}
5
+ </section>`}function m(s,n){return r("consent-client",e.CLIENT_LABEL,[s?`<p>${a(s)}</p><p class="muted small">${e.CLIENT_NAME_HINT}</p>`:`<p>${e.CLIENT_UNKNOWN}</p>`,n?`<p class="muted small">${e.CLIENT_ID_HOST(n)}</p>`:""])}function A({scopes:s,accessLifetimeHours:n,rememberDays:t}){const o=`${d.shield}${e.PANEL_TITLE}`,i=`<ol class="steps">
6
+ <li>${e.ACCOUNT_DETAILS}</li>
7
+ <li>${e.ACT_AS_YOU}</li>
8
+ <li>${e.ACCESS_LIFETIME(n)}</li>
9
+ </ol>`,c=t?`<p>${e.REMEMBER(t)}</p>`:"";return`<aside class="panel" aria-labelledby="panel-title">
10
+ <div class="panel-steps"><h2 id="panel-title">${o}</h2>${i}</div>
11
+ <details class="panel-steps-toggle"><summary><h2>${o}</h2></summary>${i}</details>
12
+ <div class="link-facts">
13
+ ${c}
14
+ <div class="tags"><span>${e.SCOPES_LABEL}</span>${s.map(l=>`<span class="tag">${a(l)}</span>`).join("")}</div>
15
+ </div>
16
+ </aside>`}function S({site:s,user:n,clientName:t,clientIdHost:o,redirectUrl:i,scopes:c,accessLifetimeHours:l,rememberDays:T,consentToken:u}){const I=`<div class="page">
17
+ <main class="card">
18
+ ${N(s)}
19
+ <h1>${e.TITLE}</h1>
20
+ <p class="lede">${e.INTRO}</p>
21
+ <div class="banner warning" role="note">${d.warning}<p>${e.WARNING}</p></div>
22
+ <div class="info-card">
23
+ ${m(t,o)}
24
+ ${r("consent-user",e.USER_LABEL,[`<p>${a(n||e.NOT_PROVIDED)}</p>`])}
25
+ ${r("consent-redirect",e.REDIRECT_LABEL,[`<p>${E(_(i))}</p>`,`<p class="muted small">${e.REDIRECT_HINT}</p>`])}
26
+ </div>
27
+ <form class="credentials" method="post" autocomplete="off">
28
+ <input type="hidden" name="consent" value="${a(u)}">
29
+ <div class="actions">
30
+ <div class="actions-row"><button class="secondary" type="submit" name="decision" value="deny">${e.DENY}</button><button class="primary" type="submit" name="decision" value="allow">${e.ALLOW}</button></div>
31
+ </div>
32
+ </form>
33
+ </main>
34
+ ${A({scopes:c,accessLifetimeHours:l,rememberDays:T})}
35
+ </div>`;return p({title:e.TITLE,body:I},s.colorScheme)}function y(s){return p(L("warning",e.INVALID_TITLE,[e.INVALID_BODY($/60),e.INVALID_NEXT]),s.colorScheme)}export{f as CONSENT_PAGE_HEADERS,$ as MCP_CONSENT_TTL_SEC,y as consentInvalidPage,S as consentPage};
@@ -0,0 +1,15 @@
1
+ import type { KvService } from '@redocly/config';
2
+ export declare const MCP_CONSENT_MEMORY_DAYS = 30;
3
+ export type ConsentGrant = {
4
+ idpId: string | undefined;
5
+ subject: string;
6
+ clientId: string;
7
+ redirectUri: string;
8
+ };
9
+ export declare function hasConsent(kv: KvService, grant: ConsentGrant): Promise<boolean>;
10
+ export declare function saveConsent(kv: KvService, grant: ConsentGrant): Promise<void>;
11
+ /** Returns false when the code was already redeemed. */
12
+ export declare function redeemAuthorizationCode(kv: KvService, code: string, expiresAtSec: number): Promise<boolean>;
13
+ /** Returns false when the refresh token was already redeemed. */
14
+ export declare function redeemRefreshToken(kv: KvService, refreshToken: string, expiresAtSec: number): Promise<boolean>;
15
+ //# sourceMappingURL=mcp-oauth-store.d.ts.map
@@ -0,0 +1 @@
1
+ import{createHash as i}from"node:crypto";const u=30;function r(e){return i("sha256").update(e).digest("hex")}function s({idpId:e,subject:t,clientId:n,redirectUri:o}){return["mcp","consent",r(JSON.stringify([e??null,t])),r(JSON.stringify([n,o]))]}async function f(e,t){return await e.get(s(t))!==null}async function m(e,t){await e.set(s(t),{approvedAt:Date.now()},{ttlInSeconds:u*24*60*60})}function p(e,t,n){return c(e,["mcp","redeemed-code",r(t)],n)}function h(e,t,n){return c(e,["mcp","redeemed-refresh-token",r(t)],n)}async function c(e,t,n){const o=Math.max(n-Math.floor(Date.now()/1e3),1);return e.transaction(async a=>await a.get(t)?!1:(await a.set(t,{redeemedAt:Date.now()},{ttlInSeconds:o}),!0))}export{u as MCP_CONSENT_MEMORY_DAYS,f as hasConsent,p as redeemAuthorizationCode,h as redeemRefreshToken,m as saveConsent};
@@ -9,10 +9,11 @@ export type McpContextPayload = {
9
9
  mcpSessionId: string;
10
10
  mcpCodeChallenge?: string | null;
11
11
  mcpCodeChallengeMethod?: string | null;
12
+ mcpClientName?: string | null;
12
13
  timestamp: number;
13
14
  };
14
15
  export declare const SUPPORTED_MCP_GRANT_TYPES: readonly ['authorization_code', 'refresh_token'];
15
- export declare function verifyPkce(codeChallenge: string, codeChallengeMethod: string | undefined, codeVerifier: string): boolean;
16
+ export declare function verifyPkce(codeChallenge: string | undefined, codeChallengeMethod: string | undefined, codeVerifier: string): boolean;
16
17
  export declare function createMcpContextToken(context: McpContextPayload): Promise<string>;
17
18
  export declare function verifyAndParseMcpContextToken(token: string): Promise<McpContextPayload>;
18
19
  export declare function mcpOAuthProtectedResourceHandler(store: Store): Handler;
@@ -26,4 +27,5 @@ export declare function resolveMcpIdpId(ssoDirect: SsoConfig | undefined): strin
26
27
  export declare function mcpAuthorizationHandler(store: Store): Handler;
27
28
  export declare function mcpTokenPortalHandler(store: Store): Handler;
28
29
  export declare function mcpCallbackHandler(store: Store): Handler;
30
+ export declare function mcpConsentHandler(store: Store): Handler;
29
31
  //# sourceMappingURL=mcp-oauth.d.ts.map
@@ -1 +1 @@
1
- import{getCookie as U}from"hono/cookie";import{createHash as V,timingSafeEqual as Z}from"node:crypto";import{ulid as Y}from"ulid";import{isRedoclySso as W,isOidcProviderConfig as w,createMcpAuthorizationCode as X,verifyMcpAuthorizationCode as Q,createMcpRefreshToken as $,verifyMcpRefreshToken as x,createMcpSessionResource as S,oidcRefreshTokenGrant as ee,getOidcMetadata as re,getUserParamsFromCookies as te,idTokenIsForThisProject as ne}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{SUPPORTED_MCP_SCOPES as y}from"../../../plugins/mcp/constants.js";import{AUTH_URL as oe,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as z,MCP_REFRESH_SESSION_LIFETIME_SEC as ie}from"../../../constants/common.js";import{ServerRoutes as T}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as I}from"@redocly/theme/core/utils";import{telemetry as A}from"../../../telemetry/index.js";import{envConfig as D}from"../../../config/env-config.js";import*as M from"../../jwt/jwt.js";import{InternalTokenTypes as F,McpAccessTokenMarker as R}from"../../jwt/token-types.js";import{AlgorithmTypes as L,JwtAlgorithmNotImplemented as se,JwtTokenExpired as ce,JwtTokenInvalid as de,JwtTokenNotBefore as ae,JwtTokenSignatureMismatched as le}from"../../jwt/types.js";import{getRequestOrigin as N}from"../../utils/get-request-origin.js";import{encryptClaimValue as q}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as pe}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as G}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as _e,isClientMetadataDocumentUrl as H,isRedirectUriRegistered as ue}from"./mcp-client-metadata.js";const s=(r,e,n=200,o)=>r.json(e,n,{"Content-Type":"application/json",...o??{}}),b=new Set(["S256"]),j=["authorization_code","refresh_token"],fe="https://claude.ai/oauth/claude-code-client-metadata";function he(r,e,n){if(!b.has(e||""))return!1;const o=V("sha256").update(n).digest("base64url"),c=Buffer.from(o),i=Buffer.from(r);return c.length!==i.length?!1:Z(c,i)}async function me(r){const e=Math.floor(Date.now()/1e3);return M.sign({type:F.MCP_CONTEXT,...r,iat:e,exp:e+600},m,L.HS256)}async function ge(r){await M.verify(r,m,L.HS256);const{payload:e}=M.decode(r);if(e.type!==F.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function je(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const n=N(e);return s(e,{resource:`${n}${I("/mcp")}`,authorization_servers:[n],bearer_methods_supported:["header"],resource_documentation:`${n}${T.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...y],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function $e(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=N(e);return s(e,{issuer:n,authorization_endpoint:`${n}${I(T.MCP_AUTHORIZATION)}`,token_endpoint:`${n}${I(T.MCP_TOKEN_PORTAL)}`,scopes_supported:[...y],registration_endpoint:`${n}${I(T.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...j],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function ze(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:D.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...j],response_types:["code"],scope:y.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(n){return s(e,{error:"invalid_request",error_description:n?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}function B(r){const e=r||{},n=Object.keys(e),o=n.find(i=>w(e[i])&&W(e[i])),c=n.find(i=>w(e[i]));return o||c||n[0]}function Fe(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const n=new URL(e.req.url),{searchParams:o}=n,c=o.get("redirect_uri"),i=o.get("client_id"),p=o.get("code_challenge"),t=o.get("code_challenge_method"),l=Y();if(p&&!b.has(t||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...b].join(", ")}`},400);if(H(i)&&!p)return s(e,{error:"invalid_request",error_description:"code_challenge is required for client_id_metadata_document clients"},400);if(H(i)&&c)try{const d=await _e(i),a=i===fe;if(!ue(c,d.redirect_uris,{ignorePort:a}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(d){const a=d instanceof Error?d.message:String(d);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${a}`},400)}A.sendMcpAuthorizationStartedMessage([{...S(l),redirect_uri:c||null}]);const _=N(e),f={isMcpFlow:!0,originalRedirectUri:c,mcpClientId:i,mcpState:o.get("state"),mcpSessionId:l,mcpCodeChallenge:p,mcpCodeChallengeMethod:p?t:null,timestamp:Date.now()};try{const d=await me(f),a=new URL(I(T.IDP_LOGIN),_);a.searchParams.set("redirectTo",`${T.MCP_CALLBACK}/${d}`);const C=B(r.getConfig().ssoDirect);return C?(a.searchParams.set("idpId",C),e.redirect(a.toString())):s(e,{error:"not_found"},404)}catch(d){const a=d instanceof Error?d.message:String(d),C=d instanceof Error?d.stack:String(d);A.sendMcpAuthorizationFailedMessage([{...S(l),error:a,error_details:C}]);const g=new URL(I(`${oe}/oauth2/auth`));return g.search=o.toString(),e.redirect(g.toString())}}}async function K(r){const e=Math.floor(Date.now()/1e3),n=await M.sign({...r.claims,[R.CLAIM]:R.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e,exp:e+r.ttlSec}:{}},m,L.HS256,r.kid),o=r.ttlSec??(typeof r.claims.exp=="number"?Math.max(r.claims.exp-e,0):z);return{accessToken:n,expiresIn:o}}async function ke(r,e,n){const o=k=>s(r,{error:"invalid_grant",error_description:k},400),c=r.get("logger"),i=n.get("refresh_token"),p=n.get("client_id")||void 0;if(!i)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let t;try{t=await x(i)}catch(k){return c?.warn("MCP refresh token rejected: %s",k instanceof Error?k.message:String(k)),o("Invalid refresh token")}if(p&&p!==t.client_id)return o("Client mismatch");const l=t.idp_id?e.getConfig().ssoDirect?.[t.idp_id]:void 0;let _,f,d=t.idp_access_token_enc,a=t.idp_refresh_token_enc;if(t.idp_refresh_token_enc){let k;try{k=pe(t.idp_refresh_token_enc,m)}catch{return o("Invalid refresh token")}if(!t.idp_id||!w(l))return o("Unknown identity provider");let u;try{const h=await re(t.idp_id,l);if(!h?.token_endpoint)return o("Identity provider has no token endpoint");u=await ee(h.token_endpoint,k,l)}catch(h){return c?.warn("MCP refresh failed to reach the IdP token endpoint: %s",h instanceof Error?h.message:String(h)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(u.error||!(u.id_token||u.access_token))return c?.warn("MCP refresh rejected by IdP: %s",u.error||"empty token response"),o("Refresh rejected by identity provider");if(u.refresh_token&&(a=G(u.refresh_token,m)),u.access_token&&(d=q(u.access_token,m)),u.id_token){try{const h=M.decode(u.id_token);_=h.payload,f=h.header.kid}catch{return o("Invalid token response from identity provider")}if(_.iss!==t.claims.iss||_.sub!==t.claims.sub)return c?.warn("MCP refresh rejected: the id_token names another issuer or subject"),o("Invalid refresh token")}}const C=w(l)&&l.tokenExpirationTime||z,g=_??t.claims;if(!ne(g,l))return c?.warn("MCP refresh rejected: the id_token was issued for another project"),o("Invalid refresh token");const E=await K({claims:g,kid:f,idpId:t.idp_id,idpAccessTokenEnc:d,ttlSec:_?void 0:C}),O=await $({clientId:t.client_id,idpId:t.idp_id,claims:g,idpAccessTokenEnc:d,idpRefreshTokenEnc:a,sessionExp:t.session_exp});return s(r,{access_token:E.accessToken,token_type:"Bearer",expires_in:E.expiresIn,refresh_token:O,scope:y.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Ge(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const n=await e.req.formData(),o=n.get("grant_type"),c=n.get("code"),i=n.get("redirect_uri")||void 0,p=n.get("code_verifier")||void 0;if(!j.includes(o))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(o==="refresh_token")return await ke(e,r,n);if(!c)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const t=await Q(c);if(i&&i!==t.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(t.code_challenge){if(!p)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!he(t.code_challenge,t.code_challenge_method,p))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400)}if(D.OAUTH_CLIENT_ID&&t.client_id&&!H(t.client_id)&&t.client_id!==D.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const l=t.id_token;if(typeof l!="string"||l.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const _=await J(l,r.getConfig().ssoDirect);if(!_)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);const f=t.idp_access_token_enc??(t.idp_access_token?q(t.idp_access_token,m):void 0),{accessToken:d,expiresIn:a}=await K({claims:_.payload,kid:_.kid,idpId:t.idp_id,idpAccessTokenEnc:f}),g=Math.floor(Date.now()/1e3)+ie,E=await $({clientId:t.client_id,idpId:t.idp_id,claims:_.payload,idpAccessTokenEnc:f,idpRefreshTokenEnc:t.idp_refresh_token_enc,sessionExp:g}),O={access_token:d,token_type:"Bearer",expires_in:a,refresh_token:E,scope:y.join(" ")};return s(e,O,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(t){return e.get("logger")?.warn("MCP token exchange failed: %s",t instanceof Error?t.message:String(t)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(n){const o=n instanceof Error?n.message:String(n);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:o},500)}}}async function J(r,e){const n=await te(e||{},{[v.AUTHORIZATION]:r});if(n.isAuthenticated!==!0)return;const{payload:o,header:c}=M.decode(r);if(o[R.CLAIM]!==R.VALUE)return{payload:o,kid:c.kid,idpId:typeof n.idpId=="string"?n.idpId:void 0}}function Be(r){return async e=>{const n=new URL(e.req.url);let o=n.searchParams.get("context");if(!o&&n.pathname.startsWith(I(`${T.MCP_CALLBACK}/`))){const i=n.pathname.split("/");o=i[i.length-1]}if(!o)return e.text("Missing context parameter",400);let c=null;try{const i=await ge(o);if(c=i.mcpSessionId||null,!i.isMcpFlow||!i.originalRedirectUri)throw new Error("Invalid MCP context");const p=r.getConfig().ssoDirect,t=U(e,v.AUTHORIZATION),l=t?await J(t,p):void 0,_=U(e,v.IDP_ACCESS_TOKEN),f=U(e,v.IDP_REFRESH_TOKEN);if(!t||!l)return A.sendMcpAuthorizationFailedMessage([{...S(c),error:"Authentication required",error_details:null}]),e.text("Authentication required",401);const d=await X({idToken:t,idpId:l.idpId??B(p),idpAccessTokenEnc:_?q(_,m):void 0,idpRefreshTokenEnc:f?G(f,m):void 0,clientId:i.mcpClientId||"",redirectUri:i.originalRedirectUri,codeChallenge:i.mcpCodeChallenge||void 0,codeChallengeMethod:i.mcpCodeChallengeMethod||void 0,ttlSec:600}),a=new URL(i.originalRedirectUri);return a.searchParams.set("code",d),i.mcpState&&a.searchParams.set("state",i.mcpState),A.sendMcpAuthorizationCompletedMessage([{...S(c),redirect_uri:i.originalRedirectUri||null}]),e.redirect(a.toString())}catch(i){const p=Ce(i);return A.sendMcpAuthorizationFailedMessage([{...S(c),...p}]),e.text(`Invalid MCP callback: ${p.error}`,400)}}}function Ce(r){return r instanceof Error&&[se,ce,de,ae,le].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{j as SUPPORTED_MCP_GRANT_TYPES,me as createMcpContextToken,Fe as mcpAuthorizationHandler,Be as mcpCallbackHandler,ze as mcpDynamicClientRegistrationHandler,$e as mcpOAuthAuthorizationServerHandler,je as mcpOAuthProtectedResourceHandler,Ge as mcpTokenPortalHandler,B as resolveMcpIdpId,ge as verifyAndParseMcpContextToken,he as verifyPkce};
1
+ import{getCookie as H}from"hono/cookie";import{createHash as F,timingSafeEqual as ne}from"node:crypto";import{ulid as oe}from"ulid";import{isRedoclySso as ie,isOidcProviderConfig as R,createMcpAuthorizationCode as se,verifyMcpAuthorizationCode as ae,createMcpRefreshToken as G,verifyMcpRefreshToken as ce,createMcpSessionResource as M,oidcRefreshTokenGrant as de,getOidcMetadata as pe,getUserParamsFromCookies as ue,idTokenIsForThisProject as le}from"../../auth.js";import{isMcpContentProtected as P}from"../../../plugins/mcp/auth/auth-handlers.js";import{resolveSiteBranding as _e}from"../../../plugins/mcp/handlers/credentials-page/site-branding.js";import{SUPPORTED_MCP_SCOPES as E}from"../../../plugins/mcp/constants.js";import{AUTH_URL as fe,JWT_SECRET_KEY as m,MCP_ACCESS_TOKEN_TTL_SEC as b,MCP_REFRESH_SESSION_LIFETIME_SEC as K}from"../../../constants/common.js";import{ServerRoutes as I}from"../../../../constants/api.js";import{AuthCookieNames as v}from"../../../../constants/common.js";import{withPathPrefix as w}from"@redocly/theme/core/utils";import{telemetry as y}from"../../../telemetry/index.js";import{envConfig as q}from"../../../config/env-config.js";import*as C from"../../jwt/jwt.js";import{InternalTokenTypes as O,McpAccessTokenMarker as U}from"../../jwt/token-types.js";import{AlgorithmTypes as A,JwtAlgorithmNotImplemented as he,JwtTokenExpired as me,JwtTokenInvalid as ge,JwtTokenNotBefore as Ce,JwtTokenSignatureMismatched as Te}from"../../jwt/types.js";import{getRequestOrigin as L}from"../../utils/get-request-origin.js";import{encryptClaimValue as B}from"../../../utils/crypto/encrypt-claim-value.js";import{decryptAesGcm as ke}from"../../../utils/crypto/decrypt-aes-gcm.js";import{encryptAesGcm as J}from"../../../utils/crypto/encrypt-aes-gcm.js";import{fetchClientMetadataDocument as Se,isClientMetadataDocumentUrl as j,isRedirectUriRegistered as Ie}from"./mcp-client-metadata.js";import{CONSENT_PAGE_HEADERS as V,MCP_CONSENT_TTL_SEC as we,consentInvalidPage as Me,consentPage as Ee}from"./mcp-consent-page.js";import{MCP_CONSENT_MEMORY_DAYS as ye,hasConsent as Ae,redeemAuthorizationCode as Re,redeemRefreshToken as Pe,saveConsent as ve}from"./mcp-oauth-store.js";const s=(r,e,t=200,n)=>r.json(e,t,{"Content-Type":"application/json",...n??{}}),z=new Set(["S256"]),$=["authorization_code","refresh_token"],Oe="https://claude.ai/oauth/claude-code-client-metadata";function Ue(r,e,t){if(!r||!z.has(e||""))return!1;const n=F("sha256").update(t).digest("base64url"),i=Buffer.from(n),a=Buffer.from(r);return i.length!==a.length?!1:ne(i,a)}async function Le(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONTEXT,...r,iat:e,exp:e+600},m,A.HS256)}async function Ne(r){await C.verify(r,m,A.HS256);const{payload:e}=C.decode(r);if(e.type!==O.MCP_CONTEXT)throw new Error("Invalid context token type");return e}function fr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="GET")return s(e,{error:"Method not allowed"},405,{Allow:"GET"});const t=L(e);return s(e,{resource:`${t}${w("/mcp")}`,authorization_servers:[t],bearer_methods_supported:["header"],resource_documentation:`${t}${I.MCP_OAUTH_AUTHORIZATION_SERVER}`,scopes_supported:[...E],bearer_token_types_supported:["Bearer"]},200,{"Access-Control-Allow-Origin":"*"})}}function hr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=L(e);return s(e,{issuer:t,authorization_endpoint:`${t}${w(I.MCP_AUTHORIZATION)}`,token_endpoint:`${t}${w(I.MCP_TOKEN_PORTAL)}`,scopes_supported:[...E],registration_endpoint:`${t}${w(I.MCP_DYNAMIC_CLIENT_REGISTRATION)}`,response_types_supported:["code"],grant_types_supported:[...$],subject_types_supported:["public"],id_token_signing_alg_values_supported:["RS256"],code_challenge_methods_supported:["S256"],token_endpoint_auth_methods_supported:["none"],client_id_metadata_document_supported:!0},200,{"Access-Control-Allow-Origin":"*"})}}function mr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{return s(e,{client_id:q.OAUTH_CLIENT_ID||"",client_name:"MCP Client",redirect_uris:[],grant_types:[...$],response_types:["code"],scope:E.join(" "),subject_type:"public",token_endpoint_auth_method:"none",created_at:new Date().toISOString(),updated_at:new Date().toISOString()},201,{"Access-Control-Allow-Origin":"*"})}catch(t){return s(e,{error:"invalid_request",error_description:t?.message||"Unable to register client"},500,{"Access-Control-Allow-Origin":"*"})}}}const De=new Set(["127.0.0.1","localhost","[::1]"]),He=new Set(["http:","https:","ws:","wss:","ftp:","file:"]),be=new Set(["javascript:","data:","blob:","about:","vbscript:","intent:","chrome:","view-source:","ms-msdt:","ms-appx-web:"]),qe=2048,je=/[\s\p{Cc}]/u;function W(r){return r.protocol==="http:"&&De.has(r.hostname)}function Y(r){if(!r||r.length>qe||r.includes("#")||je.test(r))return;const e=URL.parse(r);if(e){if(e.protocol==="https:"||W(e))return r.toLowerCase().startsWith(`${e.protocol}//`)?e:void 0;if(!(He.has(e.protocol)||be.has(e.protocol)))return e}}function Z(r){const e=r||{},t=Object.keys(e),n=t.find(a=>R(e[a])&&ie(e[a])),i=t.find(a=>R(e[a]));return n||i||t[0]}function gr(r){return async e=>{if(!P(r))return s(e,{error:"not_found"},404);const t=new URL(e.req.url),{searchParams:n}=t,i=n.get("redirect_uri"),a=n.get("client_id"),c=n.get("code_challenge"),o=n.get("code_challenge_method"),d=oe();if(!i||!Y(i))return s(e,{error:"invalid_request",error_description:"redirect_uri must be an https URL, a loopback http URL, or a private-use scheme"},400);if(!c)return s(e,{error:"invalid_request",error_description:"code_challenge is required"},400);if(!z.has(o||""))return s(e,{error:"invalid_request",error_description:`Unsupported code_challenge_method. Supported: ${[...z].join(", ")}`},400);let p;if(j(a))try{const u=await Se(a);p=u.client_name;const _=a===Oe;if(!Ie(i,u.redirect_uris,{ignorePort:_}))return s(e,{error:"invalid_request",error_description:"redirect_uri is not registered for this client"},400)}catch(u){const _=u instanceof Error?u.message:String(u);return s(e,{error:"invalid_client",error_description:`Unable to resolve client metadata document: ${_}`},400)}y.sendMcpAuthorizationStartedMessage([{...M(d),redirect_uri:i||null}]);const l=L(e),k={isMcpFlow:!0,originalRedirectUri:i,mcpClientId:a,mcpState:n.get("state"),mcpSessionId:d,mcpCodeChallenge:c,mcpCodeChallengeMethod:o,mcpClientName:p,timestamp:Date.now()};try{const u=await Le(k),_=new URL(w(I.IDP_LOGIN),l);_.searchParams.set("redirectTo",`${I.MCP_CALLBACK}/${u}`);const h=Z(r.getConfig().ssoDirect);return h?(_.searchParams.set("idpId",h),e.redirect(_.toString())):s(e,{error:"not_found"},404)}catch(u){const _=u instanceof Error?u.message:String(u),h=u instanceof Error?u.stack:String(u);y.sendMcpAuthorizationFailedMessage([{...M(d),error:_,error_details:h}]);const S=new URL(w(`${fe}/oauth2/auth`));return S.search=n.toString(),e.redirect(S.toString())}}}async function X(r){const e=Math.floor(Date.now()/1e3),t=r.ttlSec?e+r.ttlSec:Math.min(typeof r.claims.exp=="number"?r.claims.exp:1/0,e+b);return{accessToken:await C.sign({...r.claims,[U.CLAIM]:U.VALUE,...r.idpId?{idpId:r.idpId}:{},...r.idpAccessTokenEnc?{idp_access_token:r.idpAccessTokenEnc}:{},...r.ttlSec?{iat:e}:{},exp:t},m,A.HS256,r.kid),expiresIn:Math.max(t-e,0)}}async function ze(r,e,t){const n=T=>s(r,{error:"invalid_grant",error_description:T},400),i=r.get("logger"),a=t.get("refresh_token"),c=t.get("client_id")||void 0;if(!a)return s(r,{error:"invalid_request",error_description:"refresh_token is required"},400);let o;try{o=await ce(a)}catch(T){return i?.warn("MCP refresh token rejected: %s",T instanceof Error?T.message:String(T)),n("Invalid refresh token")}if(c&&c!==o.client_id)return n("Client mismatch");const d=o.idp_id?e.getConfig().ssoDirect?.[o.idp_id]:void 0;let p,l,k=o.idp_access_token_enc,u=o.idp_refresh_token_enc;if(o.idp_refresh_token_enc){let T;try{T=ke(o.idp_refresh_token_enc,m)}catch{return n("Invalid refresh token")}if(!o.idp_id||!R(d))return n("Unknown identity provider");let f;try{const g=await pe(o.idp_id,d);if(!g?.token_endpoint)return n("Identity provider has no token endpoint");f=await de(g.token_endpoint,T,d)}catch(g){return i?.warn("MCP refresh failed to reach the IdP token endpoint: %s",g instanceof Error?g.message:String(g)),s(r,{error:"server_error",error_description:"Failed to refresh session"},500)}if(f.error||!(f.id_token||f.access_token))return i?.warn("MCP refresh rejected by IdP: %s",f.error||"empty token response"),n("Refresh rejected by identity provider");if(f.refresh_token&&(u=J(f.refresh_token,m)),f.access_token&&(k=B(f.access_token,m)),f.id_token){try{const g=C.decode(f.id_token);p=g.payload,l=g.header.kid}catch{return n("Invalid token response from identity provider")}if(p.iss!==o.claims.iss||p.sub!==o.claims.sub)return i?.warn("MCP refresh rejected: the id_token names another issuer or subject"),n("Invalid refresh token")}}const _=Math.min(R(d)&&d.tokenExpirationTime||b,b),h=p??o.claims;if(!le(h,d))return i?.warn("MCP refresh rejected: the id_token was issued for another project"),n("Invalid refresh token");if(!await Pe(await e.getKv(),a,o.exp))return i?.warn("MCP refresh rejected: the refresh token was already used"),n("Refresh token already used");const S=await X({claims:h,kid:l,idpId:o.idp_id,idpAccessTokenEnc:k,ttlSec:p?void 0:_}),D=await G({clientId:o.client_id,idpId:o.idp_id,claims:h,idpAccessTokenEnc:k,idpRefreshTokenEnc:u,sessionExp:o.session_exp});return s(r,{access_token:S.accessToken,token_type:"Bearer",expires_in:S.expiresIn,refresh_token:D,scope:E.join(" ")},200,{"Cache-Control":"no-store",Pragma:"no-cache"})}function Cr(r){return async e=>{if(e.req.method!=="POST")return s(e,{error:"Method not allowed"},405);try{const t=await e.req.formData(),n=t.get("grant_type"),i=t.get("code"),a=t.get("redirect_uri")||void 0,c=t.get("code_verifier")||void 0;if(!$.includes(n))return s(e,{error:"invalid_request",error_description:"Invalid grant type"},400);if(n==="refresh_token")return await ze(e,r,t);if(!i)return s(e,{error:"invalid_request",error_description:"Missing authorization code"},400);try{const o=await ae(i);if(a&&a!==o.redirect_uri)return s(e,{error:"invalid_grant",error_description:"redirect_uri mismatch"},400);if(!c)return s(e,{error:"invalid_grant",error_description:"code_verifier required"},400);if(!Ue(o.code_challenge,o.code_challenge_method,c))return s(e,{error:"invalid_grant",error_description:"code_verifier mismatch"},400);if(q.OAUTH_CLIENT_ID&&o.client_id&&!j(o.client_id)&&o.client_id!==q.OAUTH_CLIENT_ID)return s(e,{error:"invalid_client",error_description:"Client mismatch"},400);const d=o.id_token;if(typeof d!="string"||d.length===0)return s(e,{error:"invalid_grant",error_description:"Missing id_token in authorization code"},400);const p=await Q(d,r.getConfig().ssoDirect);if(!p)return s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400);if(!await Re(await r.getKv(),i,o.exp))return s(e,{error:"invalid_grant",error_description:"Authorization code already used"},400);const l=o.idp_access_token_enc,{accessToken:k,expiresIn:u}=await X({claims:p.payload,kid:p.kid,idpId:o.idp_id,idpAccessTokenEnc:l}),h=Math.floor(Date.now()/1e3)+K,S=await G({clientId:o.client_id,idpId:o.idp_id,claims:p.payload,idpAccessTokenEnc:l,idpRefreshTokenEnc:o.idp_refresh_token_enc,sessionExp:h}),D={access_token:k,token_type:"Bearer",expires_in:u,refresh_token:S,scope:E.join(" ")};return s(e,D,200,{"Cache-Control":"no-store",Pragma:"no-cache"})}catch(o){return e.get("logger")?.warn("MCP token exchange failed: %s",o instanceof Error?o.message:String(o)),s(e,{error:"invalid_grant",error_description:"Invalid authorization code"},400)}}catch(t){const n=t instanceof Error?t.message:String(t);return s(e,{error:"server_error",error_description:"Failed to process token request",error_details:n},500)}}}async function Q(r,e){const t=await ue(e||{},{[v.AUTHORIZATION]:r});if(t.isAuthenticated!==!0)return;const{payload:n,header:i}=C.decode(r);if(n[U.CLAIM]!==U.VALUE)return{payload:n,kid:i.kid,idpId:typeof t.idpId=="string"?t.idpId:void 0}}function N(r){return F("sha256").update(r).digest("base64url")}async function $e(r){const e=Math.floor(Date.now()/1e3);return C.sign({type:O.MCP_CONSENT,session_hash:N(r.sessionToken),context_hash:N(r.contextToken),iat:e,exp:e+we},m,A.HS256)}async function Fe(r,e){try{await C.verify(r,m,A.HS256)}catch{return!1}const{payload:t}=C.decode(r);return t.type===O.MCP_CONSENT&&t.session_hash===N(e.sessionToken)&&t.context_hash===N(e.contextToken)}function x(r,e){return async t=>{const n=new URL(t.req.url);let i=n.searchParams.get("context");if(!i&&n.pathname.startsWith(w(`${I.MCP_CALLBACK}/`))){const c=n.pathname.split("/");i=c[c.length-1]}if(!i)return t.text("Missing context parameter",400);let a=null;try{const c=await Ne(i);a=c.mcpSessionId||null;const o=c.originalRedirectUri,d=Y(o);if(!c.isMcpFlow||!o||!d)throw new Error("Invalid MCP context");const p=H(t,v.AUTHORIZATION),l=p?await Q(p,r.getConfig().ssoDirect):void 0;return!p||!l?(y.sendMcpAuthorizationFailedMessage([{...M(a),error:"Authentication required",error_details:null}]),t.text("Authentication required",401)):await e(t,{contextToken:i,mcpContext:c,redirectUri:o,redirectUrl:d,sessionToken:p,session:l})}catch(c){const o=Be(c);return y.sendMcpAuthorizationFailedMessage([{...M(a),...o}]),t.text(`Invalid MCP callback: ${o.error}`,400)}}}async function ee(r,e,{mcpContext:t,redirectUri:n,redirectUrl:i,sessionToken:a,session:c}){const o=H(r,v.IDP_ACCESS_TOKEN),d=H(r,v.IDP_REFRESH_TOKEN),p=await se({idToken:a,idpId:c.idpId??Z(e.getConfig().ssoDirect),idpAccessTokenEnc:o?B(o,m):void 0,idpRefreshTokenEnc:d?J(d,m):void 0,clientId:t.mcpClientId||"",redirectUri:n,codeChallenge:t.mcpCodeChallenge||void 0,codeChallengeMethod:t.mcpCodeChallengeMethod||void 0,ttlSec:600}),l=new URL(i);return l.searchParams.set("code",p),t.mcpState&&l.searchParams.set("state",t.mcpState),y.sendMcpAuthorizationCompletedMessage([{...M(t.mcpSessionId||null),redirect_uri:n}]),r.redirect(l.toString())}function Ge(r,{mcpContext:e,redirectUrl:t}){const n=new URL(t);return n.searchParams.set("error","access_denied"),e.mcpState&&n.searchParams.set("state",e.mcpState),y.sendMcpAuthorizationFailedMessage([{...M(e.mcpSessionId||null),error:"access_denied",error_details:null}]),r.redirect(n.toString())}function Ke({payload:r}){return[r.email,r.name,r.sub].find(e=>typeof e=="string"&&e.length>0)}function re({mcpContext:r,redirectUri:e,session:t}){const{sub:n}=t.payload;if(!(typeof n!="string"||n.length===0))return{idpId:t.idpId,subject:n,clientId:r.mcpClientId||"",redirectUri:e}}function te(r,e){const{seo:t,colorMode:n}=e.getConfig(),{logo:i}=e.getGlobalData();return{..._e({seo:t,logo:i,colorMode:n}),host:new URL(L(r)).host}}function Tr(r){return x(r,async(e,t)=>{const n=re(t);if(W(t.redirectUrl)||n&&await Ae(await r.getKv(),n))return ee(e,r,t);const{mcpClientId:i,mcpClientName:a}=t.mcpContext,c=Ee({site:te(e,r),user:Ke(t.session),clientName:a||void 0,clientIdHost:j(i)?new URL(i).host:void 0,redirectUrl:t.redirectUrl,scopes:E,accessLifetimeHours:K/3600,rememberDays:n?ye:void 0,consentToken:await $e(t)});return new Response(c,{status:200,headers:V})})}function kr(r){return x(r,async(e,t)=>{const n=await e.req.formData(),i=n.get("consent");if(typeof i!="string"||!await Fe(i,t))return new Response(Me(te(e,r)),{status:403,headers:V});if(n.get("decision")==="allow"){const a=re(t);return a&&await ve(await r.getKv(),a),ee(e,r,t)}return Ge(e,t)})}function Be(r){return r instanceof Error&&[he,me,ge,Ce,Te].some(e=>r instanceof e)?{error:r.name,error_details:null}:r instanceof Error?{error:r.message,error_details:r.stack??null}:{error:String(r),error_details:null}}export{$ as SUPPORTED_MCP_GRANT_TYPES,Le as createMcpContextToken,gr as mcpAuthorizationHandler,Tr as mcpCallbackHandler,kr as mcpConsentHandler,mr as mcpDynamicClientRegistrationHandler,hr as mcpOAuthAuthorizationServerHandler,fr as mcpOAuthProtectedResourceHandler,Cr as mcpTokenPortalHandler,Z as resolveMcpIdpId,Ne as verifyAndParseMcpContextToken,Ue as verifyPkce};
@@ -1 +1 @@
1
- import{corsMiddleware as o}from"../../middleware/corsMiddleware.js";import{ServerRoutes as a}from"../../../../constants/api.js";import{EntitlementsProvider as C}from"../../../entitlements/entitlements-provider.js";import{withPathPrefix as i}from"@redocly/theme/core/utils";import{AGENT_CARD_SLUG as l,A2A_SLUG as t,MCP_SERVER_CARD_SLUG as A}from"../../../../constants/common.js";import{mcpOAuthProtectedResourceHandler as d,mcpOAuthAuthorizationServerHandler as _,mcpDynamicClientRegistrationHandler as r,mcpAuthorizationHandler as R,mcpTokenPortalHandler as E,mcpCallbackHandler as c}from"./mcp-oauth.js";import{mcpServerCardHandler as P}from"./mcp-server-card.js";import{agentCardHandler as T}from"./agent-card.js";import{a2aHandler as s}from"./a2a.js";function N(e,n){const m=n.getConfig().mcp,p=C.instance().canAccessFeature("mcp");!m?.hide&&!m?.docs?.hide&&p&&(e.get(i(A),P(n)),e.get(i(l),T(n)),e.use(i(t),o({allowMethods:["POST"],exposeHeaders:["Link"]})),e.post(i(t),s(n)),e.get(`${a.MCP_OAUTH_PROTECTED_RESOURCE}${i("/mcp")}`,d(n)),e.get(a.MCP_OAUTH_AUTHORIZATION_SERVER,_(n)),e.post(i(a.MCP_DYNAMIC_CLIENT_REGISTRATION),r(n)),e.get(i(a.MCP_AUTHORIZATION),R(n)),e.post(i(a.MCP_TOKEN_PORTAL),E(n)),e.get(i(a.MCP_CALLBACK),c(n)),e.get(i(`${a.MCP_CALLBACK}/*`),c(n)))}export{N as installMcpRoutes};
1
+ import{corsMiddleware as A}from"../../middleware/corsMiddleware.js";import{ServerRoutes as i}from"../../../../constants/api.js";import{EntitlementsProvider as o}from"../../../entitlements/entitlements-provider.js";import{withPathPrefix as a}from"@redocly/theme/core/utils";import{AGENT_CARD_SLUG as l,A2A_SLUG as p,MCP_SERVER_CARD_SLUG as d}from"../../../../constants/common.js";import{mcpOAuthProtectedResourceHandler as _,mcpOAuthAuthorizationServerHandler as P,mcpDynamicClientRegistrationHandler as R,mcpAuthorizationHandler as M,mcpTokenPortalHandler as s,mcpCallbackHandler as t,mcpConsentHandler as C}from"./mcp-oauth.js";import{mcpServerCardHandler as E}from"./mcp-server-card.js";import{agentCardHandler as T}from"./agent-card.js";import{a2aHandler as H}from"./a2a.js";function B(n,e){const m=e.getConfig().mcp,c=o.instance().canAccessFeature("mcp");!m?.hide&&!m?.docs?.hide&&c&&(n.get(a(d),E(e)),n.get(a(l),T(e)),n.use(a(p),A({allowMethods:["POST"],exposeHeaders:["Link"]})),n.post(a(p),H(e)),n.get(`${i.MCP_OAUTH_PROTECTED_RESOURCE}${a("/mcp")}`,_(e)),n.get(i.MCP_OAUTH_AUTHORIZATION_SERVER,P(e)),n.post(a(i.MCP_DYNAMIC_CLIENT_REGISTRATION),R(e)),n.get(a(i.MCP_AUTHORIZATION),M(e)),n.post(a(i.MCP_TOKEN_PORTAL),s(e)),n.get(a(i.MCP_CALLBACK),t(e)),n.get(a(`${i.MCP_CALLBACK}/*`),t(e)),n.post(a(i.MCP_CALLBACK),C(e)),n.post(a(`${i.MCP_CALLBACK}/*`),C(e)))}export{B as installMcpRoutes};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/realm",
3
- "version": "0.138.0-next.12",
3
+ "version": "0.138.0-next.13",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,13 +33,13 @@
33
33
  "@opentelemetry/sdk-trace-web": "2.8.0",
34
34
  "@opentelemetry/semantic-conventions": "1.40.0",
35
35
  "@redocly/ajv": "8.18.0",
36
- "@redocly/api-docs": "0.3.0-next.9",
37
- "@redocly/config": "0.64.0",
36
+ "@redocly/api-docs": "0.3.0-next.10",
37
+ "@redocly/config": "0.65.0",
38
38
  "@redocly/openapi-core": "2.58.1",
39
39
  "@redocly/portal-legacy-ui": "0.19.0",
40
40
  "@redocly/portal-plugin-mock-server": "0.23.0-next.1",
41
- "@redocly/realm-asyncapi-sdk": "0.16.0-next.3",
42
- "@redocly/theme": "0.70.0-next.7",
41
+ "@redocly/realm-asyncapi-sdk": "0.16.0-next.4",
42
+ "@redocly/theme": "0.70.0-next.8",
43
43
  "@shikijs/transformers": "3.21.0",
44
44
  "@tanstack/react-query": "5.62.3",
45
45
  "@tanstack/react-table": "8.21.3",
@@ -1,12 +0,0 @@
1
- import type { ElicitationTarget } from '../gateway-mcp/secret-store.js';
2
- export declare function formPage(target: ElicitationTarget, token: string, error?: string): string;
3
- export declare function clearedPage(target: ElicitationTarget): string;
4
- export type MultiOutcome = {
5
- saved: readonly string[];
6
- removed: readonly string[];
7
- stored: readonly string[];
8
- };
9
- export declare function multiOutcomePage(target: ElicitationTarget, outcome: MultiOutcome): string;
10
- export declare function successPage(target: ElicitationTarget): string;
11
- export declare function notFoundPage(): string;
12
- //# sourceMappingURL=mcp-credentials-page.d.ts.map
@@ -1,225 +0,0 @@
1
- import{withPathPrefix as g}from"@redocly/theme/core/utils";import{MCP_CREDENTIALS_ROUTE as C}from"../../../constants/mcp.js";import{ELICITATION_TTL_MS as f}from"../gateway-mcp/secret-store.js";const b=6,v=`<svg class="logo" width="193" height="56" viewBox="0 0 193 56" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Redocly">
2
- <g clip-path="url(#redocly-logo-clip)">
3
- <path d="M84.3634 40.7533H80.0357L73.199 30.9849H69.8119V40.8155H66.1738V14.5592H76.3353C78.9067 14.5592 80.9139 15.368 82.2308 16.9857C83.5483 18.5412 84.1756 20.5322 84.1756 22.8343C84.1756 25.0119 83.6107 26.7541 82.4193 28.3095C81.2902 29.865 79.5339 30.7361 77.2134 30.8605L84.3634 40.7533ZM69.8743 17.8568V27.6251H76.2098C79.22 27.6251 80.5999 25.4475 80.5999 22.7098C80.5999 20.0344 79.22 17.7945 76.3353 17.7945H69.8743V17.8568Z" fill="currentColor"/>
4
- <path d="M103.117 33.225H88.5654C89.0042 36.1492 91.2623 38.078 94.3355 38.078C96.5312 38.078 98.5384 37.0203 99.4789 35.3403C99.8553 35.4648 100.357 35.7137 101.047 35.9626L102.553 36.4603C101.047 39.5091 97.9111 41.1889 94.2731 41.1889C91.701 41.1889 89.506 40.3179 87.6873 38.638C85.931 36.8959 85.1152 34.7182 85.1152 31.9184C85.1152 29.1185 85.9934 26.9409 87.6873 25.2609C89.4436 23.5188 91.6386 22.71 94.2731 22.71C96.8445 22.71 98.9771 23.581 100.608 25.2609C102.302 26.9409 103.18 29.1807 103.18 31.9184C103.117 32.5405 103.117 32.9139 103.117 33.225ZM99.6674 30.4873C99.228 27.563 97.0954 25.7587 94.21 25.7587C91.1999 25.7587 88.9418 27.6252 88.5654 30.4873H99.6674Z" fill="currentColor"/>
5
- <path d="M120.994 40.7532V38.0155C119.802 40.0066 117.356 41.1265 114.659 41.1265C112.087 41.1265 110.018 40.2555 108.198 38.5755C106.442 36.8334 105.627 34.6557 105.627 31.9181C105.627 29.1183 106.505 26.9406 108.198 25.2607C109.892 23.5808 112.087 22.7098 114.596 22.7098C117.293 22.7098 119.74 23.7675 120.931 25.7585V13.377H124.444V40.8154L120.994 40.7532ZM110.833 27.6873C109.704 28.745 109.202 30.176 109.202 31.9181C109.202 33.6603 109.704 35.0913 110.833 36.2112C111.962 37.3312 113.342 37.8911 114.973 37.8911C116.604 37.8911 117.983 37.3934 119.175 36.3357C120.367 35.2157 120.931 33.7847 120.931 31.9803C120.931 30.1138 120.304 28.745 119.113 27.6873C117.92 26.6295 116.54 26.1318 114.973 26.1318C113.342 26.0696 111.962 26.5673 110.833 27.6873Z" fill="currentColor"/>
6
- <path d="M129.461 25.1985C131.279 23.4564 133.475 22.6476 136.172 22.6476C138.806 22.6476 141.064 23.5186 142.883 25.1985C144.702 26.8784 145.581 29.1183 145.581 31.8559C145.581 34.5935 144.702 36.8334 142.821 38.5756C141.002 40.3177 138.806 41.1266 136.172 41.1266C133.6 41.1266 131.342 40.2555 129.523 38.5756C127.704 36.8957 126.826 34.6558 126.826 31.8559C126.826 29.0561 127.704 26.8784 129.461 25.1985ZM140.375 27.6251C139.246 26.5673 137.803 26.0696 136.172 26.0696C134.541 26.0696 133.099 26.5673 131.907 27.6251C130.778 28.6828 130.213 30.1138 130.213 31.8559C130.213 33.6603 130.778 35.0913 131.907 36.2112C133.099 37.3312 134.479 37.8289 136.172 37.8289C137.803 37.8289 139.246 37.3312 140.375 36.2112C141.566 35.0913 142.13 33.6603 142.13 31.8559C142.13 30.1138 141.503 28.6828 140.375 27.6251Z" fill="currentColor"/>
7
- <path d="M163.896 25.4474L161.262 27.6873C160.133 26.5674 158.69 26.0074 156.997 26.0074C155.366 26.0074 153.923 26.5051 152.732 27.6251C151.54 28.6828 150.976 30.1138 150.976 31.856C150.976 33.5981 151.54 35.0913 152.732 36.149C153.923 37.269 155.303 37.829 156.997 37.829C158.753 37.829 160.133 37.269 161.262 36.0869L163.959 38.3267C162.14 40.2555 159.882 41.251 156.997 41.251C154.362 41.251 152.105 40.3799 150.285 38.7C148.404 36.9579 147.525 34.718 147.525 31.9804C147.525 29.2428 148.404 27.0651 150.285 25.323C152.167 23.5808 154.362 22.772 156.997 22.772C159.819 22.6476 162.14 23.5808 163.896 25.4474Z" fill="currentColor"/>
8
- <path d="M171.987 13.377H168.475V40.8154H171.987V13.377Z" fill="currentColor"/>
9
- <path d="M181.898 49.4639H178.26L182.024 40.3177L174.873 23.0209H178.574L183.968 35.9624L189.3 23.0209H193L181.898 49.4639Z" fill="currentColor"/>
10
- <path d="M47.2308 43.491C45.8508 42.5576 44.3455 41.6866 42.8401 41.0644C45.4745 39.9445 47.9207 38.389 49.8651 36.398C53.754 32.5404 56.2002 27.1896 56.2002 21.341C56.2002 15.4303 53.8167 10.1417 49.8651 6.28409C45.9763 2.36431 40.6448 0 34.6861 0H1.63081C1.19174 0 0.752681 0.186656 0.501787 0.497751C0.18817 0.746628 0 1.18215 0 1.61769C0 2.05322 0.18817 2.48875 0.501787 2.73763C0.752681 3.04872 1.19174 3.23538 1.63081 3.23538C6.64866 3.23538 11.2275 5.2886 14.5518 8.52397C17.8134 11.8216 19.8833 16.3635 19.8833 21.341C19.8833 26.3185 17.8134 30.8605 14.5518 34.1581C11.2275 37.3935 6.64866 39.4467 1.63081 39.4467C1.44264 39.4467 1.37992 39.5089 1.19174 39.5089C0.501787 39.6956 0 40.2556 0 41.0022V41.1888C0 42.0599 0.689956 42.6821 1.50536 42.6821H2.57166C6.20962 42.8687 9.59667 44.0509 12.3565 46.0419C15.3672 48.2195 17.688 51.2061 18.9425 54.7526C19.1307 55.3747 19.8206 55.8102 20.4478 55.8102H53.5658C54.0676 55.8102 54.5694 55.5614 54.8203 55.1258C55.1339 54.6903 55.1966 54.1926 55.0085 53.6948C53.5031 49.5884 50.806 46.0419 47.2308 43.491ZM16.8098 36.398C20.6987 32.5404 23.1449 27.1896 23.1449 21.341C23.1449 15.4303 20.7615 10.1417 16.8098 6.28409C15.6181 5.10194 14.3009 4.04422 12.8583 3.17316H34.6861C39.704 3.17316 44.2828 5.22638 47.6071 8.46173C50.8687 11.7594 52.9385 16.3013 52.9385 21.2788C52.9385 26.2563 50.8687 30.7983 47.6071 34.0959C44.2828 37.3313 39.704 39.3845 34.6861 39.3845C34.5606 39.3845 34.4979 39.3845 34.3725 39.4467H12.7956C14.2382 38.7001 15.6181 37.6424 16.8098 36.398ZM14.1755 43.491C13.7992 43.242 13.3601 42.9932 12.9838 42.6821H35.6896C39.3276 42.8687 42.7146 44.0509 45.4745 46.0419C47.858 47.7218 49.8024 49.9617 51.1196 52.6371H21.4514C19.946 48.9662 17.3744 45.8552 14.1755 43.491Z" fill="currentColor"/>
11
- <path d="M29.668 21.3412C29.668 22.2122 30.3579 22.8966 31.2361 22.8966H47.1679C48.0459 22.8966 48.7359 22.2122 48.7359 21.3412C48.7359 20.4701 48.0459 19.7856 47.1679 19.7856H31.2988C30.3579 19.7856 29.668 20.5323 29.668 21.3412Z" fill="currentColor"/>
12
- <path d="M28.5385 29.2427H44.4703C45.3484 29.2427 46.0383 28.5583 46.0383 27.6873C46.0383 26.8162 45.3484 26.1318 44.4703 26.1318H28.5385C27.6604 26.1318 26.9705 26.8162 26.9705 27.6873C26.9077 28.5583 27.6604 29.2427 28.5385 29.2427Z" fill="currentColor"/>
13
- <path d="M28.5385 16.6127H44.4703C45.3484 16.6127 46.0383 15.9282 46.0383 15.0572C46.0383 14.1861 45.3484 13.5017 44.4703 13.5017H28.5385C27.6604 13.5017 26.9705 14.1861 26.9705 15.0572C26.9077 15.8661 27.6604 16.6127 28.5385 16.6127Z" fill="currentColor"/>
14
- </g>
15
- <defs><clipPath id="redocly-logo-clip"><rect width="193" height="56" fill="white"/></clipPath></defs>
16
- </svg>`;function r(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}function c(){return r(g(C))}function s(e){return`<!doctype html>
17
- <html lang="en">
18
- <head>
19
- <meta charset="utf-8">
20
- <meta name="viewport" content="width=device-width, initial-scale=1">
21
- <meta name="robots" content="noindex">
22
- <title>API credentials</title>
23
- <style>
24
- :root{
25
- color-scheme:light dark;
26
- --font:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
27
- --font-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
28
- --page-bg:#ededf2;
29
- --card-bg:#fbfbfc;
30
- --border:#dcdde5;
31
- --border-strong:#c4c6d1;
32
- --text:#1a1c21;
33
- --text-muted:#6e6f7a;
34
- --brand:#2467f2;
35
- --action:#1f7cff;
36
- --action-hover:#3072db;
37
- --action-ring:rgba(31,124,255,.25);
38
- --code-bg:#ededf2;
39
- --danger:#c74b55;
40
- --danger-hover:#a9434c;
41
- --error-bg:#fef2f2;
42
- --error-border:#f5a3aa;
43
- --error-text:#8b3c42;
44
- }
45
- @media (prefers-color-scheme:dark){:root{
46
- --page-bg:#12141a;
47
- --card-bg:#22242b;
48
- --border:#2a2b33;
49
- --border-strong:#3b3c45;
50
- --text:#fbfbfc;
51
- --text-muted:#9b9ca8;
52
- --brand:#2467f2;
53
- --action:#1f7cff;
54
- --action-hover:#3072db;
55
- --action-ring:rgba(31,124,255,.35);
56
- --code-bg:#2a2b33;
57
- --danger:#e5535f;
58
- --danger-hover:#ef858d;
59
- --error-bg:#2a1517;
60
- --error-border:#8b3c42;
61
- --error-text:#f5a3aa;
62
- }}
63
- *{box-sizing:border-box}
64
- body{font-family:var(--font);margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--page-bg);color:var(--text);padding:1.5rem;font-size:14px;line-height:1.5}
65
- .card{width:100%;max-width:26rem;background:var(--card-bg);border:1px solid var(--border);border-radius:8px;box-shadow:0 1px 2px rgba(26,28,33,.04),0 8px 24px -12px rgba(26,28,33,.18);padding:2rem}
66
- .logo{color:var(--brand);height:28px;width:auto;display:block;margin:0 0 1.5rem}
67
- h1{font-size:1.25rem;line-height:1.3;font-weight:600;margin:0 0 .5rem;letter-spacing:-.01em}
68
- .section{margin-top:1.5rem;padding-top:1.5rem;border-top:1px solid var(--border)}
69
- .section h2{font-size:.9375rem;font-weight:600;margin:0 0 .5rem;display:flex;align-items:center;gap:.5rem}
70
- .badge{font-size:.6875rem;font-weight:600;line-height:1;padding:.25rem .5rem;border-radius:999px;border:1px solid var(--border-strong);color:var(--text-muted);background:var(--code-bg)}
71
- .badge.stored{color:var(--action);border-color:var(--action)}
72
- p{margin:.75rem 0 0}
73
- p.hint{color:var(--text-muted);font-size:.8125rem;line-height:1.45}
74
- p.error{margin:1rem 0 0;padding:.75rem;border:1px solid var(--error-border);border-radius:6px;background:var(--error-bg);color:var(--error-text);font-size:.8125rem;line-height:1.45}
75
- label{display:block;margin:1rem 0 .25rem;font-weight:600;font-size:.8125rem}
76
- input[type=password],input[type=text]{width:100%;padding:.625rem .75rem;font-size:.9375rem;font-family:var(--font);border:1px solid var(--border-strong);border-radius:6px;background:var(--card-bg);color:var(--text)}
77
- input::placeholder{color:var(--text-muted)}
78
- input:focus{outline:none;border-color:var(--action);box-shadow:0 0 0 3px var(--action-ring)}
79
- button{margin-top:1.25rem;padding:.625rem 1.25rem;font-size:.9375rem;font-weight:600;font-family:var(--font);color:#fff;background:var(--action);border:1px solid transparent;border-radius:6px;cursor:pointer}
80
- button:hover{background:var(--action-hover)}
81
- button:focus-visible{outline:2px solid var(--action);outline-offset:2px}
82
- button.danger{background:transparent;color:var(--danger);border-color:var(--border-strong)}
83
- button.danger:hover{background:var(--danger);color:#fff;border-color:var(--danger)}
84
- button.danger:focus-visible{outline-color:var(--danger)}
85
- code{font-family:var(--font-mono);background:var(--code-bg);padding:.125rem .375rem;border-radius:4px;font-size:.8125em}
86
- label.check{display:flex;align-items:center;gap:.5rem;margin-top:.75rem;font-weight:400;font-size:.8125rem;color:var(--text-muted)}
87
- label.check input{width:auto;margin:0}
88
- footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--border);font-size:.75rem;color:var(--text-muted);text-align:center;line-height:1.45}
89
- </style>
90
- </head>
91
- <body><main class="card">${v}${e}</main></body>
92
- </html>`}function l(e){return`<h1>\u{1F512} ${r(e)}</h1>`}function d(){return"<footer>Secured by Redocly &middot; Credentials never enter the AI conversation</footer>"}function u(){return`<p class="hint">This link expires ${Math.round(f/6e4)} minutes after it was issued. If it stops working, request a new one in your AI client.</p>`}function h({name:e,schemeLabel:t,schemes:o}){return o&&o.length>1?e:t?`${e} (${t})`:e}function y(e){return`
93
- <div class="section">
94
- <h2>Remove stored credentials</h2>
95
- <p class="hint">This API already has credentials stored. Enter new credentials to replace them, or remove them to make calls anonymous again.</p>
96
- <form method="post" action="${c()}" autocomplete="off">
97
- <input type="hidden" name="token" value="${r(e)}">
98
- <input type="hidden" name="mode" value="clear">
99
- <button class="danger" type="submit">Remove credentials</button>
100
- </form>
101
- </div>`}function Z(e,t,o){const n=o?`<p class="error">${r(o)}</p>`:"";if(e.schemes&&e.schemes.length>1)return I(e,e.schemes,t,n);const a=e.hasExistingSecret?y(t):"",i=h(e);switch(e.auth.kind){case"bearer":return $(i,t,a,n);case"apiKey":return x(i,t,e.auth.header,a,n);case"cookie":return k(i,t,e.auth.name,a,n);case"oauth2ClientCredentials":return w(i,t,e.auth.scopes,a,n);default:return A(i,t,a,n)}}function $(e,t,o,n){return s(`
102
- ${l(`Credentials for ${e}`)}
103
- ${n}
104
- <p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>Authorization header</code>. It never passes through the AI conversation.</p>
105
- <form method="post" action="${c()}" autocomplete="off">
106
- <input type="hidden" name="token" value="${r(t)}">
107
- <input type="hidden" name="mode" value="token">
108
- <label for="secret">API token</label>
109
- <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
110
- <p class="hint">Paste your token with or without the <code>Bearer prefix</code>. The prefix is added automatically, if missing.</p>
111
- <button type="submit">Save credentials</button>
112
- </form>
113
- ${o}
114
- ${u()}
115
- ${d()}`)}function x(e,t,o,n,a){return s(`
116
- ${l(`Credentials for ${e}`)}
117
- ${a}
118
- <p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${r(o)}</code> header. It never passes through the AI conversation.</p>
119
- <form method="post" action="${c()}" autocomplete="off">
120
- <input type="hidden" name="token" value="${r(t)}">
121
- <input type="hidden" name="mode" value="token">
122
- <label for="secret">${r(o)} value</label>
123
- <input type="password" id="secret" name="secret" placeholder="value" required autofocus>
124
- <button type="submit">Save credentials</button>
125
- </form>
126
- ${n}
127
- ${u()}
128
- ${d()}`)}function k(e,t,o,n,a){return s(`
129
- ${l(`Credentials for ${e}`)}
130
- ${a}
131
- <p class="hint">Encrypted and visible only to you for 24 hours (anonymous) or 30 days (signed in).<br />Sent to the API as the <code>${r(o)}</code> cookie. It never passes through the AI conversation.</p>
132
- <form method="post" action="${c()}" autocomplete="off">
133
- <input type="hidden" name="token" value="${r(t)}">
134
- <input type="hidden" name="mode" value="token">
135
- <label for="secret">${r(o)} cookie value</label>
136
- <input type="password" id="secret" name="secret" placeholder="value" required autofocus>
137
- <p class="hint">Paste only the value. It's sent as <code>${r(o)}=&lt;value&gt;</code>.</p>
138
- <button type="submit">Save credentials</button>
139
- </form>
140
- ${n}
141
- ${u()}
142
- ${d()}`)}function w(e,t,o,n,a){const i=o.length>0?o.map(r).join(", "):"default scopes";return s(`
143
- ${l(`Credentials for ${e}`)}
144
- ${a}
145
- <p class="hint">Paste your token, or provide your OAuth2 client credentials.</p>
146
- <div class="section">
147
- <h2>I have a token</h2>
148
- <form method="post" action="${c()}" autocomplete="off">
149
- <input type="hidden" name="token" value="${r(t)}">
150
- <input type="hidden" name="mode" value="token">
151
- <label for="secret">API token</label>
152
- <input type="password" id="secret" name="secret" placeholder="your API token" required autofocus>
153
- <p class="hint"><code>Bearer</code> prefix is added automatically, if missing.</p>
154
- <button type="submit">Save token</button>
155
- </form>
156
- </div>
157
- <div class="section">
158
- <h2>Exchange client credentials</h2>
159
- <p class="hint">We request a token from the API on your behalf. Scopes: ${i}.</p>
160
- <form method="post" action="${c()}" autocomplete="off">
161
- <input type="hidden" name="token" value="${r(t)}">
162
- <input type="hidden" name="mode" value="client-credentials">
163
- <label for="client_id">Client ID</label>
164
- <input type="text" id="client_id" name="client_id" required>
165
- <label for="client_secret">Client secret</label>
166
- <input type="password" id="client_secret" name="client_secret" required>
167
- <button type="submit">Exchange &amp; save</button>
168
- </form>
169
- </div>
170
- ${n}
171
- ${u()}
172
- ${d()}`)}function A(e,t,o,n){return s(`
173
- ${l(`Credentials for ${e}`)}
174
- ${n}
175
- <p class="hint">The value is stored and encrypted only for you for 24 hours (anonymous) or 30 days (signed in).<br />It is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
176
- <form method="post" action="${c()}" autocomplete="off">
177
- <input type="hidden" name="token" value="${r(t)}">
178
- <input type="hidden" name="mode" value="token">
179
- <label for="secret">Authorization header value</label>
180
- <input type="password" id="secret" name="secret" placeholder="Bearer &lt;your token&gt;" required autofocus>
181
- <button type="submit">Save credentials</button>
182
- </form>
183
- ${o}
184
- ${u()}
185
- ${d()}`)}function I(e,t,o,n){const a=t.map((i,p)=>P(i,p)).join(`
186
- `);return s(`
187
- ${l(`Credentials for ${e.name}`)}
188
- ${n}
189
- <p class="hint">This API accepts more than one kind of credential. Fill in the one(s) you have and leave the rest blank - each value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent straight to the API. It never passes through the AI conversation.</p>
190
- <form method="post" action="${c()}" autocomplete="off">
191
- <input type="hidden" name="token" value="${r(o)}">
192
- <input type="hidden" name="mode" value="multi">
193
- ${a}
194
- <button type="submit">Save credentials</button>
195
- </form>
196
- ${u()}
197
- ${d()}`)}function P(e,t){const o=M(e.auth),n=e.label?`<p class="hint">${r(e.label)}</p>`:"",a=S(e.operations),i=e.hasExistingSecret?`<label class="check"><input type="checkbox" name="remove__${t}" value="1"> Remove the stored credential</label>`:"",p=e.hasExistingSecret?'<span class="badge stored">Stored</span>':'<span class="badge">Not set</span>';return`
198
- <div class="section">
199
- <h2>${r(e.name)} ${p}</h2>
200
- <p class="hint">Sent to the API as the <code>${r(o.code)}</code> ${o.suffix}.</p>
201
- ${n}
202
- ${a}
203
- ${_(e.auth,t)}
204
- ${i}
205
- </div>`}function _(e,t){const o=`secret__${t}`;if(e.kind==="oauth2ClientCredentials"){const a=e.scopes.length>0?e.scopes.map(r).join(", "):"default scopes";return`<label for="${o}">API token</label>
206
- <input type="password" id="${o}" name="${o}" placeholder="paste a token you already have">
207
- <p class="hint">Or exchange client credentials for one (scopes: ${a}). We add <code>Bearer</code> automatically.</p>
208
- <label for="client_id__${t}">Client ID</label>
209
- <input type="text" id="client_id__${t}" name="client_id__${t}">
210
- <label for="client_secret__${t}">Client secret</label>
211
- <input type="password" id="client_secret__${t}" name="client_secret__${t}">`}const n=H(e);return`<label for="${o}">${r(n)}</label>
212
- <input type="password" id="${o}" name="${o}" placeholder="value">`}function H(e){switch(e.kind){case"apiKey":return`${e.header} value`;case"cookie":return`${e.name} cookie value`;case"bearer":return"API token";default:return"Authorization header value"}}function M(e){switch(e.kind){case"apiKey":return{code:e.header,suffix:"header"};case"cookie":return{code:e.name,suffix:"cookie"};case"bearer":return{code:"Authorization",suffix:"header (bearer token)"};case"oauth2ClientCredentials":return{code:"Authorization",suffix:"header (OAuth2 bearer token)"};default:return{code:"Authorization",suffix:"header"}}}function S(e){if(!e||e.length===0)return"";const t=e.slice(0,b).map(r).join(", "),o=e.length-b,n=o>0?`, and ${o} more`:"";return`<p class="hint">Used by ${t}${n}.</p>`}function E(e){return s(`
213
- ${l("Credentials removed")}
214
- <p>Credentials for ${r(h(e))} are removed. Calls to the API are anonymous again until you add new credentials.</p>
215
- ${d()}`)}function R(e,t){const o=t.saved.length===0?"Credentials removed":t.removed.length===0?"Credentials saved":"Credentials updated",n=[t.saved.length>0?`<p>Saved: ${m(t.saved)}.</p>`:"",t.removed.length>0?`<p>Removed: ${m(t.removed)}.</p>`:"",t.stored.length>0?`<p>${r(e.name)} now has stored credentials for ${m(t.stored)}.</p>`:`<p>${r(e.name)} has no stored credentials. Calls to the API are anonymous until you add new ones.</p>`];return s(`
216
- ${l(o)}
217
- ${n.join(`
218
- `)}
219
- <p>Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
220
- <p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
221
- ${d()}`)}function m(e){return e.map(t=>`<code>${r(t)}</code>`).join(", ")}function F(e){return s(`
222
- ${l("Credentials saved")}
223
- <p>Credentials for ${r(h(e))} are saved. Retry in your AI client, or confirm any prompt it left open. You can close this tab.</p>
224
- <p class="hint">This link is single-use and is now spent. To change anything, request a new one in your AI client.</p>
225
- ${d()}`)}function q(){return s(`${l("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(f/6e4)} minutes. Retry in your AI client to get a fresh link.</p>${d()}`)}export{E as clearedPage,Z as formPage,R as multiOutcomePage,q as notFoundPage,F as successPage};