@frontmcp/auth 1.11.1 → 1.11.3
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/README.md +2 -2
- package/authorities/authorities.context.d.ts.map +1 -1
- package/authorities/authorities.engine.d.ts +1 -1
- package/authorities/authorities.engine.d.ts.map +1 -1
- package/authorities/authorities.evaluator.d.ts.map +1 -1
- package/authorities/authorities.profiles.d.ts +1 -1
- package/authorities/authorities.profiles.d.ts.map +1 -1
- package/authorities/authorities.registry.d.ts +1 -1
- package/authorities/authorities.registry.d.ts.map +1 -1
- package/authorities/authorities.scope-mapping.d.ts +1 -1
- package/authorities/authorities.scope-mapping.d.ts.map +1 -1
- package/authorities/authorities.types.d.ts +4 -0
- package/authorities/authorities.types.d.ts.map +1 -1
- package/authorization/orchestrated.accessor.d.ts.map +1 -1
- package/authorization/public.authorization.d.ts.map +1 -1
- package/authorization/transparent.authorization.d.ts +1 -1
- package/authorization/transparent.authorization.d.ts.map +1 -1
- package/browser/authorities/authorities.context.mjs +3 -1
- package/browser/authorities/authorities.engine.mjs +32 -33
- package/browser/authorities/authorities.evaluator.mjs +42 -16
- package/browser/authorization/dcr-client.registry.mjs +1 -1
- package/browser/authorization/public.authorization.mjs +1 -1
- package/browser/authorization/transparent.authorization.mjs +1 -1
- package/browser/cimd/cimd-client-assertion.mjs +108 -0
- package/browser/cimd/cimd-redis.cache.mjs +5 -3
- package/browser/cimd/cimd.errors.mjs +11 -0
- package/browser/cimd/cimd.service.mjs +127 -31
- package/browser/cimd/cimd.types.mjs +15 -10
- package/browser/cimd/index.mjs +13 -1
- package/browser/errors/auth-internal.error.mjs +3 -2
- package/browser/errors/auth-internal.errors.mjs +8 -1
- package/browser/errors/index.mjs +2 -0
- package/browser/index.mjs +21 -1
- package/browser/jwks/index.mjs +2 -0
- package/browser/jwks/jwks.service.mjs +22 -103
- package/browser/jwks/local-token-keys.mjs +155 -0
- package/browser/options/public.schema.mjs +7 -4
- package/browser/options/shared.schemas.mjs +38 -13
- package/browser/session/federated-auth.session.mjs +2 -0
- package/browser/session/incremental-auth-ticket.mjs +24 -1
- package/browser/session/index.mjs +4 -0
- package/browser/session/redis-session.store.mjs +3 -3
- package/browser/session/session-crypto.mjs +4 -4
- package/browser/session/storage/storage-authorization-vault.mjs +3 -1
- package/browser/session/token.vault.mjs +1 -1
- package/browser/session/utils/index.mjs +9 -1
- package/browser/session/utils/session-crypto.utils.mjs +4 -0
- package/browser/ui/index.mjs +2 -0
- package/browser/ui/templates.mjs +65 -11
- package/browser/upstream/index.mjs +6 -0
- package/browser/upstream/upstream-client-registration.mjs +273 -0
- package/cimd/cimd-client-assertion.d.ts +43 -0
- package/cimd/cimd-client-assertion.d.ts.map +1 -0
- package/cimd/cimd-redis.cache.d.ts +2 -2
- package/cimd/cimd-redis.cache.d.ts.map +1 -1
- package/cimd/cimd.cache.d.ts +1 -1
- package/cimd/cimd.cache.d.ts.map +1 -1
- package/cimd/cimd.errors.d.ts +14 -0
- package/cimd/cimd.errors.d.ts.map +1 -1
- package/cimd/cimd.service.d.ts +37 -2
- package/cimd/cimd.service.d.ts.map +1 -1
- package/cimd/cimd.types.d.ts +6 -0
- package/cimd/cimd.types.d.ts.map +1 -1
- package/cimd/index.d.ts +3 -2
- package/cimd/index.d.ts.map +1 -1
- package/errors/auth-internal.error.d.ts +4 -5
- package/errors/auth-internal.error.d.ts.map +1 -1
- package/errors/auth-internal.errors.d.ts +8 -0
- package/errors/auth-internal.errors.d.ts.map +1 -1
- package/errors/index.d.ts +1 -1
- package/errors/index.d.ts.map +1 -1
- package/esm/index.mjs +54 -14
- package/esm/package.json +5 -5
- package/index.d.ts +12 -10
- package/index.d.ts.map +1 -1
- package/index.js +58 -18
- package/jwks/index.d.ts +2 -0
- package/jwks/index.d.ts.map +1 -1
- package/jwks/jwks.service.d.ts +12 -12
- package/jwks/jwks.service.d.ts.map +1 -1
- package/jwks/jwks.types.d.ts +2 -0
- package/jwks/jwks.types.d.ts.map +1 -1
- package/jwks/local-token-keys.d.ts +41 -0
- package/jwks/local-token-keys.d.ts.map +1 -0
- package/options/interfaces.d.ts +64 -15
- package/options/interfaces.d.ts.map +1 -1
- package/options/public.schema.d.ts.map +1 -1
- package/options/shared.schemas.d.ts.map +1 -1
- package/package.json +5 -5
- package/session/authorization.store.d.ts +9 -2
- package/session/authorization.store.d.ts.map +1 -1
- package/session/federated-auth.session.d.ts +8 -0
- package/session/federated-auth.session.d.ts.map +1 -1
- package/session/incremental-auth-ticket.d.ts +10 -0
- package/session/incremental-auth-ticket.d.ts.map +1 -1
- package/session/index.d.ts +2 -2
- package/session/index.d.ts.map +1 -1
- package/session/redis-session.store.d.ts +1 -1
- package/session/redis-session.store.d.ts.map +1 -1
- package/session/session-crypto.d.ts.map +1 -1
- package/session/storage/in-memory-authorization-vault.d.ts.map +1 -1
- package/session/storage/storage-authorization-vault.d.ts +2 -2
- package/session/storage/storage-authorization-vault.d.ts.map +1 -1
- package/session/storage/storage-token-store.d.ts +2 -2
- package/session/storage/storage-token-store.d.ts.map +1 -1
- package/session/token.refresh.d.ts.map +1 -1
- package/session/utils/index.d.ts +1 -1
- package/session/utils/index.d.ts.map +1 -1
- package/session/utils/session-crypto.utils.d.ts +5 -0
- package/session/utils/session-crypto.utils.d.ts.map +1 -1
- package/ui/index.d.ts +2 -2
- package/ui/index.d.ts.map +1 -1
- package/ui/templates.d.ts +33 -0
- package/ui/templates.d.ts.map +1 -1
- package/upstream/index.d.ts +3 -0
- package/upstream/index.d.ts.map +1 -0
- package/upstream/upstream-client-registration.d.ts +50 -0
- package/upstream/upstream-client-registration.d.ts.map +1 -0
- package/vault/auth-providers.accessor.d.ts +1 -1
- package/vault/auth-providers.accessor.d.ts.map +1 -1
- package/vault/auth-providers.registry.d.ts +1 -1
- package/vault/auth-providers.registry.d.ts.map +1 -1
- package/vault/credential-cache.d.ts +1 -1
- package/vault/credential-cache.d.ts.map +1 -1
- package/vault/credential-loaders/eager-loader.d.ts +2 -2
- package/vault/credential-loaders/eager-loader.d.ts.map +1 -1
package/esm/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
2
|
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
3
|
// __frontmcpCreateRequire
|
|
4
|
-
var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyNames;var __require=(x=>typeof require!=="undefined"?require:typeof Proxy!=="undefined"?new Proxy(x,{get:(a,b)=>(typeof require!=="undefined"?require:a)[b]}):x)(function(x){if(typeof require!=="undefined")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var __esm=(fn,res,err)=>function __init(){if(err)throw err[0];try{return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res}catch(e){throw err=[e],e}};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var cimd_redis_cache_exports={};__export(cimd_redis_cache_exports,{RedisCimdCache:()=>RedisCimdCache});import{RedisStorageAdapter as RedisStorageAdapter2}from"@frontmcp/utils";import{sha256Hex as sha256Hex3}from"@frontmcp/utils";var RedisCimdCache;var init_cimd_redis_cache=__esm({"libs/auth/src/cimd/cimd-redis.cache.ts"(){"use strict";init_cimd_cache();RedisCimdCache=class{redis;keyPrefix;config;constructor(config){if(!config.redis){throw new Error("Redis configuration is required for RedisCimdCache")}this.config={defaultTtlMs:config.defaultTtlMs??36e5,maxTtlMs:config.maxTtlMs??864e5,minTtlMs:config.minTtlMs??6e4};this.keyPrefix=config.redis.keyPrefix??"cimd:";const redisOptions={};if(config.redis.url){redisOptions.url=config.redis.url}else if(config.redis.host){redisOptions.config={host:config.redis.host,port:config.redis.port,password:config.redis.password,db:config.redis.db,tls:config.redis.tls}}this.redis=new RedisStorageAdapter2(redisOptions)}async connect(){await this.redis.connect()}cacheKey(clientId){const hash=sha256Hex3(clientId);return`${this.keyPrefix}${hash}`}async get(clientId){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return void 0}try{const entry=JSON.parse(data);if(entry.expiresAt<Date.now()){return void 0}return entry}catch{await this.redis.delete(key);return void 0}}async getStale(clientId){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return void 0}try{return JSON.parse(data)}catch{return void 0}}async set(clientId,document,headers){const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);const now=Date.now();const entry={document,expiresAt:now+ttlMs,etag,lastModified,cachedAt:now};const key=this.cacheKey(clientId);const value=JSON.stringify(entry);const redisTtlSeconds=Math.ceil((ttlMs+this.config.maxTtlMs*2)/1e3);await this.redis.set(key,value,{ttlSeconds:redisTtlSeconds})}async revalidate(clientId,headers){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return false}try{const existing=JSON.parse(data);const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);existing.expiresAt=Date.now()+ttlMs;if(etag)existing.etag=etag;if(lastModified)existing.lastModified=lastModified;const value=JSON.stringify(existing);const redisTtlSeconds=Math.ceil((ttlMs+this.config.maxTtlMs*2)/1e3);await this.redis.set(key,value,{ttlSeconds:redisTtlSeconds});return true}catch{return false}}async delete(clientId){const key=this.cacheKey(clientId);return this.redis.delete(key)}async getConditionalHeaders(clientId){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return void 0}try{const entry=JSON.parse(data);const headers={};if(entry.etag){headers["If-None-Match"]=entry.etag}if(entry.lastModified){headers["If-Modified-Since"]=entry.lastModified}return Object.keys(headers).length>0?headers:void 0}catch{return void 0}}async clear(){const keys=await this.redis.keys(`${this.keyPrefix}*`);if(keys.length>0){await this.redis.mdelete(keys)}}async size(){const keys=await this.redis.keys(`${this.keyPrefix}*`);return keys.length}async cleanup(){const now=Date.now();let removed=0;const keys=await this.redis.keys(`${this.keyPrefix}*`);for(const key of keys){const data=await this.redis.get(key);if(!data)continue;try{const entry=JSON.parse(data);if(entry.expiresAt+this.config.maxTtlMs*2<now){await this.redis.delete(key);removed++}}catch{await this.redis.delete(key);removed++}}return removed}async close(){await this.redis.disconnect()}}}});function extractCacheHeaders(headers){return{"cache-control":headers.get("cache-control")??void 0,expires:headers.get("expires")??void 0,etag:headers.get("etag")??void 0,"last-modified":headers.get("last-modified")??void 0,age:headers.get("age")??void 0}}function parseCacheHeaders(headers,config){let ttlMs=config.defaultTtlMs;if(headers["cache-control"]){const cacheControl=parseCacheControlHeader(headers["cache-control"]);if(cacheControl["no-store"]||cacheControl["no-cache"]){ttlMs=config.minTtlMs}else if(typeof cacheControl["max-age"]==="number"){let maxAgeSecs=cacheControl["max-age"];if(headers.age){const ageSeconds=parseInt(headers.age,10);if(!isNaN(ageSeconds)){maxAgeSecs=Math.max(0,maxAgeSecs-ageSeconds)}}ttlMs=maxAgeSecs*1e3}if(typeof cacheControl["s-maxage"]==="number"){let sMaxAgeSecs=cacheControl["s-maxage"];if(headers.age){const ageSeconds=parseInt(headers.age,10);if(!isNaN(ageSeconds)){sMaxAgeSecs=Math.max(0,sMaxAgeSecs-ageSeconds)}}ttlMs=sMaxAgeSecs*1e3}}else if(headers.expires){const expiresDate=new Date(headers.expires);if(!isNaN(expiresDate.getTime())){ttlMs=Math.max(0,expiresDate.getTime()-Date.now())}}ttlMs=Math.max(config.minTtlMs,Math.min(config.maxTtlMs,ttlMs));return{ttlMs,etag:headers.etag,lastModified:headers["last-modified"]}}function parseCacheControlHeader(value){const result={};const directives=value.toLowerCase().split(",").map(d=>d.trim());for(const directive of directives){const[key,val]=directive.split("=").map(s=>s.trim());if(val!==void 0){const numVal=parseInt(val,10);if(!isNaN(numVal)){result[key]=numVal}}else{result[key]=true}}return result}async function createCimdCache(config){const cacheType=config?.type??"memory";if(cacheType==="redis"){if(!config?.redis){throw new Error('Redis configuration is required when cache type is "redis"')}const{RedisCimdCache:RedisCimdCache2}=await Promise.resolve().then(()=>(init_cimd_redis_cache(),cimd_redis_cache_exports));const cache=new RedisCimdCache2(config);await cache.connect();return cache}return new InMemoryCimdCache(config)}var InMemoryCimdCache;var init_cimd_cache=__esm({"libs/auth/src/cimd/cimd.cache.ts"(){"use strict";InMemoryCimdCache=class{cache=new Map;config;constructor(config){this.config={defaultTtlMs:config?.defaultTtlMs??36e5,maxTtlMs:config?.maxTtlMs??864e5,minTtlMs:config?.minTtlMs??6e4}}async get(clientId){const entry=this.cache.get(clientId);if(!entry){return void 0}if(entry.expiresAt<Date.now()){return void 0}return entry}async getStale(clientId){return this.cache.get(clientId)}async set(clientId,document,headers){const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);const now=Date.now();const entry={document,expiresAt:now+ttlMs,etag,lastModified,cachedAt:now};this.cache.set(clientId,entry)}async revalidate(clientId,headers){const existing=this.cache.get(clientId);if(!existing){return false}const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);existing.expiresAt=Date.now()+ttlMs;if(etag)existing.etag=etag;if(lastModified)existing.lastModified=lastModified;return true}async delete(clientId){return this.cache.delete(clientId)}async getConditionalHeaders(clientId){const entry=this.cache.get(clientId);if(!entry){return void 0}const headers={};if(entry.etag){headers["If-None-Match"]=entry.etag}if(entry.lastModified){headers["If-Modified-Since"]=entry.lastModified}return Object.keys(headers).length>0?headers:void 0}async clear(){this.cache.clear()}async size(){return this.cache.size}async cleanup(){const now=Date.now();let removed=0;for(const[clientId,entry]of this.cache){if(entry.expiresAt+this.config.maxTtlMs*2<now){this.cache.delete(clientId);removed++}}return removed}}}});import{bytesToHex,createKeyPersistence,isProduction,isRedirectResponse,randomBytes,rsaVerify}from"@frontmcp/utils";import{classifyIpAddress}from"@frontmcp/utils";var CimdError=class extends Error{code;statusCode;clientIdUrl;constructor(message,code,statusCode,clientIdUrl){super(message);this.name=this.constructor.name;this.code=code;this.statusCode=statusCode;this.clientIdUrl=clientIdUrl;if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}getPublicMessage(){return this.message}};var InvalidClientIdUrlError=class extends CimdError{reason;constructor(clientIdUrl,reason){super(`Invalid CIMD client_id URL: ${reason}`,"INVALID_CLIENT_ID_URL",400,clientIdUrl);this.reason=reason}getPublicMessage(){return`Invalid client_id URL: ${this.reason}`}};var CimdFetchError=class extends CimdError{httpStatus;originalError;constructor(clientIdUrl,message,options){super(`Failed to fetch CIMD document from ${clientIdUrl}: ${message}`,"CIMD_FETCH_ERROR",502,clientIdUrl);this.httpStatus=options?.httpStatus;this.originalError=options?.originalError}getPublicMessage(){if(this.httpStatus){return`Failed to fetch client metadata: HTTP ${this.httpStatus}`}return"Failed to fetch client metadata document"}};var CimdValidationError=class extends CimdError{validationErrors;constructor(clientIdUrl,errors){super(`CIMD document validation failed: ${errors.join("; ")}`,"CIMD_VALIDATION_ERROR",400,clientIdUrl);this.validationErrors=errors}getPublicMessage(){return`Client metadata document validation failed: ${this.validationErrors.join("; ")}`}};var CimdClientIdMismatchError=class extends CimdError{documentClientId;constructor(urlClientId,documentClientId){super(`CIMD client_id mismatch: URL is "${urlClientId}" but document contains "${documentClientId}"`,"CIMD_CLIENT_ID_MISMATCH",400,urlClientId);this.documentClientId=documentClientId}getPublicMessage(){return"Client ID in metadata document does not match the request"}};var CimdSecurityError=class extends CimdError{securityReason;constructor(clientIdUrl,reason){super(`CIMD security check failed for ${clientIdUrl}: ${reason}`,"CIMD_SECURITY_ERROR",403,clientIdUrl);this.securityReason=reason}getPublicMessage(){return"Client ID URL is not allowed by security policy"}};var RedirectUriMismatchError=class extends CimdError{requestedRedirectUri;allowedRedirectUris;constructor(clientIdUrl,requestedRedirectUri,allowedRedirectUris){super(`Redirect URI "${requestedRedirectUri}" is not registered for client "${clientIdUrl}"`,"REDIRECT_URI_MISMATCH",400,clientIdUrl);this.requestedRedirectUri=requestedRedirectUri;this.allowedRedirectUris=allowedRedirectUris}getPublicMessage(){return"The redirect_uri is not registered for this client"}};var CimdResponseTooLargeError=class extends CimdError{maxBytes;actualBytes;constructor(clientIdUrl,maxBytes,actualBytes){super(`CIMD response from ${clientIdUrl} exceeds maximum size of ${maxBytes} bytes${actualBytes?` (received ${actualBytes} bytes)`:""}`,"CIMD_RESPONSE_TOO_LARGE",502,clientIdUrl);this.maxBytes=maxBytes;this.actualBytes=actualBytes}getPublicMessage(){return"Client metadata document is too large"}};var CimdDisabledError=class extends CimdError{constructor(){super("CIMD (Client ID Metadata Documents) is disabled on this server","CIMD_DISABLED",400)}};function isCimdClientId(clientId,allowInsecure=false){if(!clientId||typeof clientId!=="string"){return false}try{const url=new URL(clientId);if(url.protocol==="https:"){}else if(url.protocol==="http:"&&allowInsecure&&isLocalhostHost(url.hostname)){}else{return false}if(!url.pathname||url.pathname==="/"){return false}return true}catch{return false}}function isLocalhostHost(hostname){const lower=hostname.toLowerCase();return lower==="localhost"||lower==="127.0.0.1"||lower==="[::1]"||lower.endsWith(".localhost")}function validateClientIdUrl(clientId,securityConfig){if(!clientId||typeof clientId!=="string"){throw new InvalidClientIdUrlError(clientId||"","client_id must be a non-empty string")}let url;try{url=new URL(clientId)}catch{throw new InvalidClientIdUrlError(clientId,"Invalid URL format")}const allowInsecure=securityConfig?.allowInsecureForTesting??false;if(url.protocol==="https:"){}else if(url.protocol==="http:"&&allowInsecure&&isLocalhostHost(url.hostname)){}else{throw new InvalidClientIdUrlError(clientId,`CIMD requires HTTPS, got ${url.protocol.replace(":","")}`)}if(!url.pathname||url.pathname==="/"){throw new InvalidClientIdUrlError(clientId,"CIMD client_id URL must have a path component (e.g., /oauth/client-metadata.json)")}const config={blockPrivateIPs:securityConfig?.blockPrivateIPs??true,allowedDomains:securityConfig?.allowedDomains,blockedDomains:securityConfig?.blockedDomains};if(config.allowedDomains?.length){if(!isDomainInList(url.hostname,config.allowedDomains)){throw new CimdSecurityError(clientId,`Domain "${url.hostname}" is not in the allowed domains list`)}}if(config.blockedDomains?.length){if(isDomainInList(url.hostname,config.blockedDomains)){throw new CimdSecurityError(clientId,`Domain "${url.hostname}" is blocked`)}}if(config.blockPrivateIPs&&!allowInsecure){const ssrfCheck=checkSsrfProtection(url.hostname);if(!ssrfCheck.allowed){throw new CimdSecurityError(clientId,ssrfCheck.reason)}}return url}function checkSsrfProtection(hostname){const lowercaseHostname=hostname.toLowerCase();if(lowercaseHostname==="localhost"||lowercaseHostname==="localhost.localdomain"||lowercaseHostname.endsWith(".localhost")){return{allowed:false,reason:"Localhost addresses are not allowed"}}if(isIpAddress(hostname)){const ipCheck=checkIpAddress(hostname);if(!ipCheck.allowed){return ipCheck}}return{allowed:true,reason:""}}async function resolveAndCheckHostname(hostname){const literal=checkSsrfProtection(hostname);if(!literal.allowed)return literal;if(isIpAddress(hostname))return{allowed:true,reason:""};let dns;try{dns=__require("node:dns")}catch{return{allowed:true,reason:""}}let addresses;try{const result=await dns.promises.lookup(hostname,{all:true,verbatim:true});addresses=Array.isArray(result)?result:[result]}catch{return{allowed:false,reason:`DNS resolution failed for "${hostname}"; refusing to fetch an unvalidated host`}}if(addresses.length===0){return{allowed:false,reason:`Host "${hostname}" resolved to no addresses`}}for(const{address}of addresses){if(!address)continue;const ipCheck=checkIpAddress(address);if(!ipCheck.allowed){return{allowed:false,reason:`Host "${hostname}" resolves to a blocked address (${address}): ${ipCheck.reason}`}}}return{allowed:true,reason:""}}async function assertHostNotSsrf(hostname,clientId){const check=await resolveAndCheckHostname(hostname);if(!check.allowed){throw new CimdSecurityError(clientId,check.reason)}}function isIpAddress(hostname){const ipv4Pattern=/^(\d{1,3}\.){3}\d{1,3}$/;if(ipv4Pattern.test(hostname)){return true}const cleanHostname=hostname.replace(/^\[|\]$/g,"");if(cleanHostname.includes(":")){return true}return false}var BLOCKED_RANGE_REASONS=new Map([["0.0.0.0/8","Current network addresses (0.x.x.x) are not allowed"],["10.0.0.0/8","Private IP addresses (10.x.x.x) are not allowed"],["100.64.0.0/10","Carrier-grade NAT addresses (100.64-127.x.x) are not allowed"],["127.0.0.0/8","Loopback addresses (127.x.x.x) are not allowed"],["169.254.0.0/16","Link-local addresses (169.254.x.x) are not allowed"],["172.16.0.0/12","Private IP addresses (172.16-31.x.x) are not allowed"],["192.0.0.0/24","IETF protocol-assignment addresses (192.0.0.0/24) are not allowed"],["192.168.0.0/16","Private IP addresses (192.168.x.x) are not allowed"],["224.0.0.0/4","Multicast addresses are not allowed"],["255.255.255.255/32","Broadcast address is not allowed"],["::/128","IPv6 unspecified address (::) is not allowed"],["::1/128","IPv6 loopback address (::1) is not allowed"],["fc00::/7","IPv6 unique local addresses (fc00::/7) are not allowed"],["fe80::/10","IPv6 link-local addresses (fe80::/10) are not allowed"]]);function checkIpAddress(ip){const classification=classifyIpAddress(ip);if(!classification){const bareIp=ip.replace(/^\[|\]$/g,"");return{allowed:false,reason:bareIp.includes(":")?`Malformed IPv6 address: ${bareIp}`:"Invalid IPv4 address"}}if(classification.range==="public"){return{allowed:true,reason:""}}return{allowed:false,reason:BLOCKED_RANGE_REASONS.get(classification.cidr)??`Addresses in the ${classification.range} range (${classification.cidr}) are not allowed`}}function isDomainInList(hostname,domainList){const lowerHostname=hostname.toLowerCase();for(const domain of domainList){const lowerDomain=domain.toLowerCase();if(lowerHostname===lowerDomain){return true}if(lowerHostname.endsWith("."+lowerDomain)){return true}if(lowerDomain.startsWith("*.")){const baseDomain=lowerDomain.slice(2);if(lowerHostname===baseDomain||lowerHostname.endsWith("."+baseDomain)){return true}}}return false}function hasOnlyLocalhostRedirectUris(redirectUris){if(!redirectUris.length){return false}return redirectUris.every(uri=>{try{const url=new URL(uri);const hostname=url.hostname.toLowerCase();return hostname==="localhost"||hostname==="127.0.0.1"||hostname==="[::1]"||hostname.endsWith(".localhost")}catch{return false}})}var noopAuthLogger={info(){},warn(){},error(){},debug(){}};var jose;function loadJose(){jose??=import("jose").catch(importError=>{try{return __require("jose")}catch{throw importError}}).catch(error=>{jose=void 0;throw error});return jose}function trimSlash(s){const str=s??"";let end=str.length;while(end>0&&str[end-1]==="/"){end--}return str.slice(0,end)}function normalizeIssuer(u){return trimSlash(String(u??""))}function decodeJwtPayloadSafe(token){if(!token)return void 0;const parts=token.split(".");if(parts.length<2)return void 0;try{const b64=parts[1].replace(/-/g,"+").replace(/_/g,"/");const json=typeof Buffer!=="undefined"?Buffer.from(b64,"base64").toString("utf8"):atob(b64);const obj=JSON.parse(json);return obj&&typeof obj==="object"&&!Array.isArray(obj)?obj:void 0}catch{return void 0}}var TRANSPARENT_JWT_ALGS=["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA"];var MAX_JWKS_FETCH_BYTES=1048576;var MAX_JWKS_FETCH_HOPS=3;var UNKNOWN_KID_REFETCH_COOLDOWN_MS=6e4;var MISSING_EXP_ERROR='missing required "exp" claim';function isMissingExpError(error){if(typeof error!=="object"||error===null)return false;const{code,claim,reason}=error;return code==="ERR_JWT_CLAIM_VALIDATION_FAILED"&&claim==="exp"&&reason==="missing"}var FAILURE_DEPTH=["jwks_unavailable","malformed","unsupported_algorithm","unknown_key","bad_signature","missing_claim","wrong_issuer","not_yet_valid","expired"];function tokenVerifyFailureOf(error){const{code,claim,reason}=typeof error==="object"&&error!==null?error:{};switch(code){case"ERR_JWT_EXPIRED":return"expired";case"ERR_JWS_SIGNATURE_VERIFICATION_FAILED":return"bad_signature";case"ERR_JWKS_NO_MATCHING_KEY":return"unknown_key";case"ERR_JOSE_ALG_NOT_ALLOWED":case"ERR_JOSE_NOT_SUPPORTED":return"unsupported_algorithm";case"ERR_JWT_CLAIM_VALIDATION_FAILED":if(claim==="iss")return"wrong_issuer";if(reason==="missing")return"missing_claim";return claim==="nbf"&&reason==="check_failed"?"not_yet_valid":"malformed";default:return"malformed"}}function deeperFailure(current,next){return current&&FAILURE_DEPTH.indexOf(current)>=FAILURE_DEPTH.indexOf(next)?current:next}var WEAK_KEY_WARNING=`
|
|
4
|
+
var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyNames;var __require=(x=>typeof require!=="undefined"?require:typeof Proxy!=="undefined"?new Proxy(x,{get:(a,b)=>(typeof require!=="undefined"?require:a)[b]}):x)(function(x){if(typeof require!=="undefined")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var __esm=(fn,res,err)=>function __init(){if(err)throw err[0];try{return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res}catch(e){throw err=[e],e}};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var cimd_redis_cache_exports={};__export(cimd_redis_cache_exports,{RedisCimdCache:()=>RedisCimdCache});import{RedisStorageAdapter as RedisStorageAdapter2,sha256Hex as sha256Hex4}from"@frontmcp/utils";var RedisCimdCache;var init_cimd_redis_cache=__esm({"libs/auth/src/cimd/cimd-redis.cache.ts"(){"use strict";init_cimd_cache();RedisCimdCache=class{redis;keyPrefix;config;constructor(config){if(!config.redis){throw new Error("Redis configuration is required for RedisCimdCache")}this.config={defaultTtlMs:config.defaultTtlMs??36e5,maxTtlMs:config.maxTtlMs??864e5,minTtlMs:config.minTtlMs??6e4};this.keyPrefix=config.redis.keyPrefix??"cimd:";const redisOptions={};if(config.redis.url){redisOptions.url=config.redis.url}else if(config.redis.host){redisOptions.config={host:config.redis.host,port:config.redis.port,password:config.redis.password,db:config.redis.db,tls:config.redis.tls}}this.redis=new RedisStorageAdapter2(redisOptions)}async connect(){await this.redis.connect()}cacheKey(clientId){const hash=sha256Hex4(clientId);return`${this.keyPrefix}${hash}`}async get(clientId){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return void 0}try{const entry=JSON.parse(data);if(entry.expiresAt<Date.now()){return void 0}return entry}catch{await this.redis.delete(key);return void 0}}async getStale(clientId){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return void 0}try{return JSON.parse(data)}catch{return void 0}}async set(clientId,document,headers){const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);const now=Date.now();const entry={document,expiresAt:now+ttlMs,etag,lastModified,cachedAt:now};const key=this.cacheKey(clientId);const value=JSON.stringify(entry);const redisTtlSeconds=Math.ceil((ttlMs+this.config.maxTtlMs*2)/1e3);await this.redis.set(key,value,{ttlSeconds:redisTtlSeconds})}async revalidate(clientId,headers){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return false}try{const existing=JSON.parse(data);const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);existing.expiresAt=Date.now()+ttlMs;if(etag)existing.etag=etag;if(lastModified)existing.lastModified=lastModified;const value=JSON.stringify(existing);const redisTtlSeconds=Math.ceil((ttlMs+this.config.maxTtlMs*2)/1e3);await this.redis.set(key,value,{ttlSeconds:redisTtlSeconds});return true}catch{return false}}async delete(clientId){const key=this.cacheKey(clientId);return this.redis.delete(key)}async getConditionalHeaders(clientId){const key=this.cacheKey(clientId);const data=await this.redis.get(key);if(!data){return void 0}try{const entry=JSON.parse(data);const headers={};if(entry.etag){headers["If-None-Match"]=entry.etag}if(entry.lastModified){headers["If-Modified-Since"]=entry.lastModified}return Object.keys(headers).length>0?headers:void 0}catch{return void 0}}async clear(){const keys=await this.redis.keys(`${this.keyPrefix}*`);if(keys.length>0){await this.redis.mdelete(keys)}}async size(){const keys=await this.redis.keys(`${this.keyPrefix}*`);return keys.length}async cleanup(){const now=Date.now();let removed=0;const keys=await this.redis.keys(`${this.keyPrefix}*`);for(const key of keys){const data=await this.redis.get(key);if(!data)continue;try{const entry=JSON.parse(data);if(entry.expiresAt+this.config.maxTtlMs*2<now){await this.redis.delete(key);removed++}}catch{await this.redis.delete(key);removed++}}return removed}async close(){await this.redis.disconnect()}}}});function extractCacheHeaders(headers){return{"cache-control":headers.get("cache-control")??void 0,expires:headers.get("expires")??void 0,etag:headers.get("etag")??void 0,"last-modified":headers.get("last-modified")??void 0,age:headers.get("age")??void 0}}function parseCacheHeaders(headers,config){let ttlMs=config.defaultTtlMs;if(headers["cache-control"]){const cacheControl=parseCacheControlHeader(headers["cache-control"]);if(cacheControl["no-store"]||cacheControl["no-cache"]){ttlMs=config.minTtlMs}else if(typeof cacheControl["max-age"]==="number"){let maxAgeSecs=cacheControl["max-age"];if(headers.age){const ageSeconds=parseInt(headers.age,10);if(!isNaN(ageSeconds)){maxAgeSecs=Math.max(0,maxAgeSecs-ageSeconds)}}ttlMs=maxAgeSecs*1e3}if(typeof cacheControl["s-maxage"]==="number"){let sMaxAgeSecs=cacheControl["s-maxage"];if(headers.age){const ageSeconds=parseInt(headers.age,10);if(!isNaN(ageSeconds)){sMaxAgeSecs=Math.max(0,sMaxAgeSecs-ageSeconds)}}ttlMs=sMaxAgeSecs*1e3}}else if(headers.expires){const expiresDate=new Date(headers.expires);if(!isNaN(expiresDate.getTime())){ttlMs=Math.max(0,expiresDate.getTime()-Date.now())}}ttlMs=Math.max(config.minTtlMs,Math.min(config.maxTtlMs,ttlMs));return{ttlMs,etag:headers.etag,lastModified:headers["last-modified"]}}function parseCacheControlHeader(value){const result={};const directives=value.toLowerCase().split(",").map(d=>d.trim());for(const directive of directives){const[key,val]=directive.split("=").map(s=>s.trim());if(val!==void 0){const numVal=parseInt(val,10);if(!isNaN(numVal)){result[key]=numVal}}else{result[key]=true}}return result}async function createCimdCache(config){const cacheType=config?.type??"memory";if(cacheType==="redis"){if(!config?.redis){throw new Error('Redis configuration is required when cache type is "redis"')}const{RedisCimdCache:RedisCimdCache2}=await Promise.resolve().then(()=>(init_cimd_redis_cache(),cimd_redis_cache_exports));const cache=new RedisCimdCache2(config);await cache.connect();return cache}return new InMemoryCimdCache(config)}var InMemoryCimdCache;var init_cimd_cache=__esm({"libs/auth/src/cimd/cimd.cache.ts"(){"use strict";InMemoryCimdCache=class{cache=new Map;config;constructor(config){this.config={defaultTtlMs:config?.defaultTtlMs??36e5,maxTtlMs:config?.maxTtlMs??864e5,minTtlMs:config?.minTtlMs??6e4}}async get(clientId){const entry=this.cache.get(clientId);if(!entry){return void 0}if(entry.expiresAt<Date.now()){return void 0}return entry}async getStale(clientId){return this.cache.get(clientId)}async set(clientId,document,headers){const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);const now=Date.now();const entry={document,expiresAt:now+ttlMs,etag,lastModified,cachedAt:now};this.cache.set(clientId,entry)}async revalidate(clientId,headers){const existing=this.cache.get(clientId);if(!existing){return false}const cacheHeaders=extractCacheHeaders(headers);const{ttlMs,etag,lastModified}=parseCacheHeaders(cacheHeaders,this.config);existing.expiresAt=Date.now()+ttlMs;if(etag)existing.etag=etag;if(lastModified)existing.lastModified=lastModified;return true}async delete(clientId){return this.cache.delete(clientId)}async getConditionalHeaders(clientId){const entry=this.cache.get(clientId);if(!entry){return void 0}const headers={};if(entry.etag){headers["If-None-Match"]=entry.etag}if(entry.lastModified){headers["If-Modified-Since"]=entry.lastModified}return Object.keys(headers).length>0?headers:void 0}async clear(){this.cache.clear()}async size(){return this.cache.size}async cleanup(){const now=Date.now();let removed=0;for(const[clientId,entry]of this.cache){if(entry.expiresAt+this.config.maxTtlMs*2<now){this.cache.delete(clientId);removed++}}return removed}}}});import{bytesToHex,isProduction,isRedirectResponse,randomBytes,rsaVerify}from"@frontmcp/utils";import{classifyIpAddress}from"@frontmcp/utils";var CimdError=class extends Error{code;statusCode;clientIdUrl;constructor(message,code,statusCode,clientIdUrl){super(message);this.name=this.constructor.name;this.code=code;this.statusCode=statusCode;this.clientIdUrl=clientIdUrl;if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}getPublicMessage(){return this.message}};var InvalidClientIdUrlError=class extends CimdError{reason;constructor(clientIdUrl,reason){super(`Invalid CIMD client_id URL: ${reason}`,"INVALID_CLIENT_ID_URL",400,clientIdUrl);this.reason=reason}getPublicMessage(){return`Invalid client_id URL: ${this.reason}`}};var CimdFetchError=class extends CimdError{httpStatus;originalError;constructor(clientIdUrl,message,options){super(`Failed to fetch CIMD document from ${clientIdUrl}: ${message}`,"CIMD_FETCH_ERROR",502,clientIdUrl);this.httpStatus=options?.httpStatus;this.originalError=options?.originalError}getPublicMessage(){if(this.httpStatus){return`Failed to fetch client metadata: HTTP ${this.httpStatus}`}return"Failed to fetch client metadata document"}};var CimdValidationError=class extends CimdError{validationErrors;constructor(clientIdUrl,errors){super(`CIMD document validation failed: ${errors.join("; ")}`,"CIMD_VALIDATION_ERROR",400,clientIdUrl);this.validationErrors=errors}getPublicMessage(){return`Client metadata document validation failed: ${this.validationErrors.join("; ")}`}};var CimdClientIdMismatchError=class extends CimdError{documentClientId;constructor(urlClientId,documentClientId){super(`CIMD client_id mismatch: URL is "${urlClientId}" but document contains "${documentClientId}"`,"CIMD_CLIENT_ID_MISMATCH",400,urlClientId);this.documentClientId=documentClientId}getPublicMessage(){return"Client ID in metadata document does not match the request"}};var CimdSecurityError=class extends CimdError{securityReason;constructor(clientIdUrl,reason){super(`CIMD security check failed for ${clientIdUrl}: ${reason}`,"CIMD_SECURITY_ERROR",403,clientIdUrl);this.securityReason=reason}getPublicMessage(){return"Client ID URL is not allowed by security policy"}};var RedirectUriMismatchError=class extends CimdError{requestedRedirectUri;allowedRedirectUris;constructor(clientIdUrl,requestedRedirectUri,allowedRedirectUris){super(`Redirect URI "${requestedRedirectUri}" is not registered for client "${clientIdUrl}"`,"REDIRECT_URI_MISMATCH",400,clientIdUrl);this.requestedRedirectUri=requestedRedirectUri;this.allowedRedirectUris=allowedRedirectUris}getPublicMessage(){return"The redirect_uri is not registered for this client"}};var CimdResponseTooLargeError=class extends CimdError{maxBytes;actualBytes;constructor(clientIdUrl,maxBytes,actualBytes){super(`CIMD response from ${clientIdUrl} exceeds maximum size of ${maxBytes} bytes${actualBytes?` (received ${actualBytes} bytes)`:""}`,"CIMD_RESPONSE_TOO_LARGE",502,clientIdUrl);this.maxBytes=maxBytes;this.actualBytes=actualBytes}getPublicMessage(){return"Client metadata document is too large"}};var CimdClientAssertionError=class extends CimdError{reason;constructor(clientIdUrl,reason){super(`Client assertion refused for ${clientIdUrl}: ${reason}`,"CIMD_CLIENT_ASSERTION_INVALID",401,clientIdUrl);this.reason=reason}getPublicMessage(){return"Client authentication failed"}};var CimdDisabledError=class extends CimdError{constructor(){super("CIMD (Client ID Metadata Documents) is disabled on this server","CIMD_DISABLED",400)}};function isCimdClientId(clientId,allowInsecure=false){if(!clientId||typeof clientId!=="string"){return false}try{const url=new URL(clientId);if(url.protocol==="https:"){}else if(url.protocol==="http:"&&allowInsecure&&isLocalhostHost(url.hostname)){}else{return false}if(!url.pathname||url.pathname==="/"){return false}return true}catch{return false}}function isLocalhostHost(hostname){const lower=hostname.toLowerCase();return lower==="localhost"||lower==="127.0.0.1"||lower==="[::1]"||lower.endsWith(".localhost")}function validateClientIdUrl(clientId,securityConfig){if(!clientId||typeof clientId!=="string"){throw new InvalidClientIdUrlError(clientId||"","client_id must be a non-empty string")}let url;try{url=new URL(clientId)}catch{throw new InvalidClientIdUrlError(clientId,"Invalid URL format")}const allowInsecure=securityConfig?.allowInsecureForTesting??false;if(url.protocol==="https:"){}else if(url.protocol==="http:"&&allowInsecure&&isLocalhostHost(url.hostname)){}else{throw new InvalidClientIdUrlError(clientId,`CIMD requires HTTPS, got ${url.protocol.replace(":","")}`)}if(!url.pathname||url.pathname==="/"){throw new InvalidClientIdUrlError(clientId,"CIMD client_id URL must have a path component (e.g., /oauth/client-metadata.json)")}const config={blockPrivateIPs:securityConfig?.blockPrivateIPs??true,allowedDomains:securityConfig?.allowedDomains,blockedDomains:securityConfig?.blockedDomains};if(config.allowedDomains?.length){if(!isDomainInList(url.hostname,config.allowedDomains)){throw new CimdSecurityError(clientId,`Domain "${url.hostname}" is not in the allowed domains list`)}}if(config.blockedDomains?.length){if(isDomainInList(url.hostname,config.blockedDomains)){throw new CimdSecurityError(clientId,`Domain "${url.hostname}" is blocked`)}}if(config.blockPrivateIPs&&!allowInsecure){const ssrfCheck=checkSsrfProtection(url.hostname);if(!ssrfCheck.allowed){throw new CimdSecurityError(clientId,ssrfCheck.reason)}}return url}function checkSsrfProtection(hostname){const lowercaseHostname=hostname.toLowerCase();if(lowercaseHostname==="localhost"||lowercaseHostname==="localhost.localdomain"||lowercaseHostname.endsWith(".localhost")){return{allowed:false,reason:"Localhost addresses are not allowed"}}if(isIpAddress(hostname)){const ipCheck=checkIpAddress(hostname);if(!ipCheck.allowed){return ipCheck}}return{allowed:true,reason:""}}async function resolveAndCheckHostname(hostname){const literal=checkSsrfProtection(hostname);if(!literal.allowed)return literal;if(isIpAddress(hostname))return{allowed:true,reason:""};let dns;try{dns=__require("node:dns")}catch{return{allowed:true,reason:""}}let addresses;try{const result=await dns.promises.lookup(hostname,{all:true,verbatim:true});addresses=Array.isArray(result)?result:[result]}catch{return{allowed:false,reason:`DNS resolution failed for "${hostname}"; refusing to fetch an unvalidated host`}}if(addresses.length===0){return{allowed:false,reason:`Host "${hostname}" resolved to no addresses`}}for(const{address}of addresses){if(!address)continue;const ipCheck=checkIpAddress(address);if(!ipCheck.allowed){return{allowed:false,reason:`Host "${hostname}" resolves to a blocked address (${address}): ${ipCheck.reason}`}}}return{allowed:true,reason:""}}async function assertHostNotSsrf(hostname,clientId){const check=await resolveAndCheckHostname(hostname);if(!check.allowed){throw new CimdSecurityError(clientId,check.reason)}}function isIpAddress(hostname){const ipv4Pattern=/^(\d{1,3}\.){3}\d{1,3}$/;if(ipv4Pattern.test(hostname)){return true}const cleanHostname=hostname.replace(/^\[|\]$/g,"");if(cleanHostname.includes(":")){return true}return false}var BLOCKED_RANGE_REASONS=new Map([["0.0.0.0/8","Current network addresses (0.x.x.x) are not allowed"],["10.0.0.0/8","Private IP addresses (10.x.x.x) are not allowed"],["100.64.0.0/10","Carrier-grade NAT addresses (100.64-127.x.x) are not allowed"],["127.0.0.0/8","Loopback addresses (127.x.x.x) are not allowed"],["169.254.0.0/16","Link-local addresses (169.254.x.x) are not allowed"],["172.16.0.0/12","Private IP addresses (172.16-31.x.x) are not allowed"],["192.0.0.0/24","IETF protocol-assignment addresses (192.0.0.0/24) are not allowed"],["192.168.0.0/16","Private IP addresses (192.168.x.x) are not allowed"],["224.0.0.0/4","Multicast addresses are not allowed"],["255.255.255.255/32","Broadcast address is not allowed"],["::/128","IPv6 unspecified address (::) is not allowed"],["::1/128","IPv6 loopback address (::1) is not allowed"],["fc00::/7","IPv6 unique local addresses (fc00::/7) are not allowed"],["fe80::/10","IPv6 link-local addresses (fe80::/10) are not allowed"]]);function checkIpAddress(ip){const classification=classifyIpAddress(ip);if(!classification){const bareIp=ip.replace(/^\[|\]$/g,"");return{allowed:false,reason:bareIp.includes(":")?`Malformed IPv6 address: ${bareIp}`:"Invalid IPv4 address"}}if(classification.range==="public"){return{allowed:true,reason:""}}return{allowed:false,reason:BLOCKED_RANGE_REASONS.get(classification.cidr)??`Addresses in the ${classification.range} range (${classification.cidr}) are not allowed`}}function isDomainInList(hostname,domainList){const lowerHostname=hostname.toLowerCase();for(const domain of domainList){const lowerDomain=domain.toLowerCase();if(lowerHostname===lowerDomain){return true}if(lowerHostname.endsWith("."+lowerDomain)){return true}if(lowerDomain.startsWith("*.")){const baseDomain=lowerDomain.slice(2);if(lowerHostname===baseDomain||lowerHostname.endsWith("."+baseDomain)){return true}}}return false}function hasOnlyLocalhostRedirectUris(redirectUris){if(!redirectUris.length){return false}return redirectUris.every(uri=>{try{const url=new URL(uri);const hostname=url.hostname.toLowerCase();return hostname==="localhost"||hostname==="127.0.0.1"||hostname==="[::1]"||hostname.endsWith(".localhost")}catch{return false}})}var noopAuthLogger={info(){},warn(){},error(){},debug(){}};var jose;function loadJose(){jose??=import("jose").catch(importError=>{try{return __require("jose")}catch{throw importError}}).catch(error=>{jose=void 0;throw error});return jose}function trimSlash(s){const str=s??"";let end=str.length;while(end>0&&str[end-1]==="/"){end--}return str.slice(0,end)}function normalizeIssuer(u){return trimSlash(String(u??""))}function decodeJwtPayloadSafe(token){if(!token)return void 0;const parts=token.split(".");if(parts.length<2)return void 0;try{const b64=parts[1].replace(/-/g,"+").replace(/_/g,"/");const json=typeof Buffer!=="undefined"?Buffer.from(b64,"base64").toString("utf8"):atob(b64);const obj=JSON.parse(json);return obj&&typeof obj==="object"&&!Array.isArray(obj)?obj:void 0}catch{return void 0}}var TRANSPARENT_JWT_ALGS=["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA"];var MAX_JWKS_FETCH_BYTES=1048576;var MAX_JWKS_FETCH_HOPS=3;var UNKNOWN_KID_REFETCH_COOLDOWN_MS=6e4;var MISSING_EXP_ERROR='missing required "exp" claim';function isMissingExpError(error){if(typeof error!=="object"||error===null)return false;const{code,claim,reason}=error;return code==="ERR_JWT_CLAIM_VALIDATION_FAILED"&&claim==="exp"&&reason==="missing"}var FAILURE_DEPTH=["jwks_unavailable","malformed","unsupported_algorithm","unknown_key","bad_signature","missing_claim","wrong_issuer","not_yet_valid","expired"];function tokenVerifyFailureOf(error){const{code,claim,reason}=typeof error==="object"&&error!==null?error:{};switch(code){case"ERR_JWT_EXPIRED":return"expired";case"ERR_JWS_SIGNATURE_VERIFICATION_FAILED":return"bad_signature";case"ERR_JWKS_NO_MATCHING_KEY":return"unknown_key";case"ERR_JOSE_ALG_NOT_ALLOWED":case"ERR_JOSE_NOT_SUPPORTED":return"unsupported_algorithm";case"ERR_JWT_CLAIM_VALIDATION_FAILED":if(claim==="iss")return"wrong_issuer";if(reason==="missing")return"missing_claim";return claim==="nbf"&&reason==="check_failed"?"not_yet_valid":"malformed";default:return"malformed"}}function deeperFailure(current,next){return current&&FAILURE_DEPTH.indexOf(current)>=FAILURE_DEPTH.indexOf(next)?current:next}var WEAK_KEY_WARNING=`
|
|
5
5
|
\u26A0\uFE0F SECURITY WARNING: OAuth provider is using an RSA key smaller than 2048 bits.
|
|
6
6
|
This is considered insecure and should be updated.
|
|
7
7
|
Please contact your OAuth provider to upgrade their signing keys.
|
|
8
8
|
Verification will proceed but with reduced security guarantees.
|
|
9
|
-
`;var JwksService=class{opts;logger;warnedProviders=new Set;orchestratorKey;providerJwks=new Map;unknownKidRefetches=new Map;keyInitialized=false;keyInitPromise;keyPersistence;constructor(opts){this.logger=opts?.logger??noopAuthLogger;this.opts={orchestratorAlg:opts?.orchestratorAlg??"RS256",rotateDays:opts?.rotateDays??30,providerJwksTtlMs:opts?.providerJwksTtlMs??6*60*60*1e3,networkTimeoutMs:opts?.networkTimeoutMs??5e3}}async getKeyPersistence(){if(isProduction())return null;if(!this.keyPersistence){this.keyPersistence=await createKeyPersistence({baseDir:".frontmcp/keys"})}return this.keyPersistence}async getPublicJwks(){return this.getOrchestratorJwks()}async verifyTransparentToken(token,candidates){if(!candidates?.length)return{ok:false,error:"no_providers"};const{createLocalJWKSet,decodeProtectedHeader,jwtVerify}=await loadJose();let kid;try{const header=decodeProtectedHeader(token);kid=typeof header?.kid==="string"?header.kid:void 0}catch{}let missingExp=false;let failure;for(const p of candidates){let jwks;try{jwks=await this.getJwksForProvider(p);if(kid&&!p.jwks?.keys?.length&&!jwks?.keys?.some(key=>key.kid===kid)){jwks=await this.refetchForUnknownKid(p)??jwks}if(!jwks?.keys?.length){failure=deeperFailure(failure,"jwks_unavailable");continue}const JWKS=createLocalJWKSet(jwks);const issuerConstraint=this.issuerConstraintFor(p);const{payload,protectedHeader}=await jwtVerify(token,JWKS,{algorithms:[...TRANSPARENT_JWT_ALGS],...issuerConstraint?{issuer:issuerConstraint}:{},requiredClaims:["exp"]});return{ok:true,issuer:payload?.iss,sub:payload?.sub,providerId:p.id,header:protectedHeader,payload}}catch(e){if(this.isWeakKeyError(e)){const fallbackJwks=jwks??await this.getJwksForProvider(p);if(fallbackJwks?.keys?.length){const fallbackResult=await this.verifyWithWeakKey(token,fallbackJwks,p);if(fallbackResult.ok){return fallbackResult}if(fallbackResult.error==="missing_exp")missingExp=true;if(fallbackResult.failure)failure=deeperFailure(failure,fallbackResult.failure)}}if(isMissingExpError(e))missingExp=true;failure=deeperFailure(failure,tokenVerifyFailureOf(e));this.logger.debug("[JwksService] Failed to verify token for provider: %s",p.id,e)}}if(missingExp)return{ok:false,error:MISSING_EXP_ERROR,failure};return{ok:false,error:`no_provider_verified${kid?` (kid=${kid})`:""}`,failure}}issuerConstraintFor(p){if(p.verifyIssuer===false)return void 0;const issuers=new Set;for(const candidate of[p.issuerUrl,...p.additionalIssuers??[]]){const normalized=normalizeIssuer(candidate);if(!normalized)continue;issuers.add(normalized);issuers.add(`${normalized}/`)}return[...issuers]}isWeakKeyError(error){const message=typeof error==="object"&&error!==null&&"message"in error&&typeof error.message==="string"?error.message:String(error);return message.includes("modulusLength")&&message.includes("2048")}async verifyWithWeakKey(token,jwks,provider){try{const parts=token.split(".");if(parts.length!==3){return{ok:false,error:"invalid_token_format",failure:"malformed"}}const[headerB64,payloadB64,signatureB64]=parts;const header=JSON.parse(Buffer.from(headerB64,"base64url").toString("utf8"));const payload=JSON.parse(Buffer.from(payloadB64,"base64url").toString("utf8"));const matchingKey=this.findMatchingKey(jwks,header);if(!matchingKey){return{ok:false,error:"no_matching_key",failure:"unknown_key"}}const signatureInput=`${headerB64}.${payloadB64}`;const signature=Buffer.from(signatureB64,"base64url");const jwtAlg=typeof header.alg==="string"?header.alg:void 0;if(!jwtAlg){return{ok:false,error:"missing_alg",failure:"malformed"}}const isValid=await rsaVerify(jwtAlg,Buffer.from(signatureInput),matchingKey,signature);if(!isValid){return{ok:false,error:"signature_invalid",failure:"bad_signature"}}if(provider.verifyIssuer!==false){const payloadIssuerRaw=typeof payload.iss==="string"?payload.iss:void 0;if(!payloadIssuerRaw){return{ok:false,error:"issuer_mismatch",failure:"wrong_issuer"}}const trustedIssuers=new Set([provider.issuerUrl,...provider.additionalIssuers??[]].map(issuer=>normalizeIssuer(issuer)));const payloadIssuer=normalizeIssuer(payloadIssuerRaw);if(!trustedIssuers.has(payloadIssuer)){return{ok:false,error:"issuer_mismatch",failure:"wrong_issuer"}}}const nowSec=Math.floor(Date.now()/1e3);if(payload.exp===void 0){return{ok:false,error:"missing_exp",failure:"missing_claim"}}if(typeof payload.exp!=="number"||!Number.isFinite(payload.exp)||payload.exp<=nowSec){return{ok:false,error:"token_expired",failure:"expired"}}if(payload.nbf!==void 0&&(typeof payload.nbf!=="number"||!Number.isFinite(payload.nbf)||payload.nbf>nowSec)){return{ok:false,error:"token_not_yet_valid",failure:"not_yet_valid"}}if(!this.warnedProviders.has(provider.id)){this.warnedProviders.add(provider.id);this.logger.warn(WEAK_KEY_WARNING);this.logger.warn(" Provider: %s (%s)",provider.id,provider.issuerUrl)}return{ok:true,issuer:payload.iss,sub:payload.sub,providerId:provider.id,header,payload}}catch(err){const message=err instanceof Error?err.message:"unknown";return{ok:false,error:`weak_key_verification_failed: ${message}`,failure:"malformed"}}}findMatchingKey(jwks,header){if(header.kid){return jwks.keys.find(k=>k.kid===header.kid)}if(header.alg){const byAlg=jwks.keys.find(k=>k.alg===header.alg||k.kty==="RSA"&&header.alg?.startsWith("RS"));if(byAlg)return byAlg}return jwks.keys.find(k=>k.kty==="RSA")}setProviderJwks(providerId,jwks){this.providerJwks.set(providerId,{jwks,fetchedAt:Date.now()})}async getJwksForProvider(ref){if(ref.jwks?.keys?.length){this.setProviderJwks(ref.id,ref.jwks);return ref.jwks}const cached=this.providerJwks.get(ref.id);if(cached&&Date.now()-cached.fetchedAt<this.opts.providerJwksTtlMs){return cached.jwks}const fetched=await this.fetchProviderJwks(ref);if(fetched)return fetched;const maxStaleMs=Math.max(this.opts.providerJwksTtlMs*4,6e4);if(cached&&Date.now()-cached.fetchedAt<maxStaleMs){return cached.jwks}return void 0}refetchForUnknownKid(ref){let refetch=this.unknownKidRefetches.get(ref.id);if(!refetch||Date.now()-refetch.startedAt>=UNKNOWN_KID_REFETCH_COOLDOWN_MS){refetch={startedAt:Date.now(),done:this.fetchProviderJwks(ref)};this.unknownKidRefetches.set(ref.id,refetch)}return refetch.done}async fetchProviderJwks(ref){if(ref.jwksUri){const fromUri=await this.tryFetchJwks(ref.id,ref.jwksUri);if(fromUri?.keys?.length)return fromUri}const issuer=trimSlash(ref.issuerUrl);const fromWellKnown=await this.tryFetchJwks(ref.id,`${issuer}/.well-known/jwks.json`);if(fromWellKnown?.keys?.length)return fromWellKnown;for(const document of["oauth-authorization-server","openid-configuration"]){const meta=await this.tryFetchAsMeta(`${issuer}/.well-known/${document}`);const uri=meta&&typeof meta==="object"&&meta["jwks_uri"]?String(meta["jwks_uri"]):void 0;if(uri){const fromMeta=await this.tryFetchJwks(ref.id,uri);if(fromMeta?.keys?.length)return fromMeta}}return void 0}async getOrchestratorJwks(){await this.ensureOrchestratorKey();return this.orchestratorKey.publicJwk}async getOrchestratorSigningKey(){await this.ensureOrchestratorKey();return{kid:this.orchestratorKey.kid,key:this.orchestratorKey.privateKey,alg:this.opts.orchestratorAlg}}async tryFetchJwks(providerId,uri){try{const jwks=await this.fetchJson(uri);if(jwks?.keys?.length){this.setProviderJwks(providerId,jwks);return jwks}}catch{}return void 0}async tryFetchAsMeta(url){try{return await this.fetchJson(url)}catch{return void 0}}async fetchJson(url){const ctl=typeof AbortController!=="undefined"?new AbortController:void 0;const timer=setTimeout(()=>ctl?.abort(),this.opts.networkTimeoutMs);try{let currentUrl=url;for(let hop=0;;hop++){const parsed=new URL(currentUrl);const host=parsed.hostname.toLowerCase();const isLocalhost=host==="localhost"||host==="127.0.0.1"||host==="::1"||host==="[::1]"||host.endsWith(".localhost");if(parsed.protocol!=="https:"&&!(parsed.protocol==="http:"&&(isLocalhost||!isProduction()))){throw new Error(`insecure scheme "${parsed.protocol}"`)}if(!(isLocalhost&&!isProduction())){const ssrf=await resolveAndCheckHostname(parsed.hostname);if(!ssrf.allowed)throw new Error(`blocked host: ${ssrf.reason}`)}const res=await fetch(currentUrl,{method:"GET",headers:{accept:"application/json"},signal:ctl?.signal,redirect:"manual"});if(isRedirectResponse(res)){const location=res.headers.get("location");if(!location)throw new Error("redirect without a readable Location header");if(hop>=MAX_JWKS_FETCH_HOPS)throw new Error("too many redirects");currentUrl=new URL(location,currentUrl).toString();continue}if(!res.ok)throw new Error(`HTTP ${res.status}`);const contentLength=res.headers.get("content-length");if(contentLength&&Number(contentLength)>MAX_JWKS_FETCH_BYTES){throw new Error("response too large")}const text=await res.text();if(text.length>MAX_JWKS_FETCH_BYTES){throw new Error("response too large")}return JSON.parse(text)}}finally{clearTimeout(timer)}}async ensureOrchestratorKey(){const now=Date.now();const maxAge=this.opts.rotateDays*24*60*60*1e3;if(this.orchestratorKey&&now-this.orchestratorKey.createdAt<=maxAge){return}if(this.keyInitPromise){await this.keyInitPromise;return}this.keyInitPromise=this.initializeOrchestratorKey(now,maxAge);try{await this.keyInitPromise}finally{this.keyInitPromise=void 0}}async initializeOrchestratorKey(now,maxAge){const persistence=await this.getKeyPersistence();if(persistence&&!this.keyInitialized){this.keyInitialized=true;const loaded=await persistence.getAsymmetric("jwks-orchestrator");if(loaded&&now-loaded.createdAt<=maxAge){if(loaded.alg!==this.opts.orchestratorAlg){this.logger.warn("[JwksService] Persisted key algorithm (%s) doesn't match config (%s), generating new key",loaded.alg,this.opts.orchestratorAlg)}else{try{const{createPrivateKey}=__require("node:crypto");const privateKey=createPrivateKey({key:loaded.privateKey,format:"jwk"});this.orchestratorKey={kid:loaded.kid,privateKey,publicJwk:loaded.publicJwk,createdAt:loaded.createdAt};return}catch(error){this.logger.warn("[JwksService] Failed to load persisted key: %s, generating new key",error.message)}}}}this.orchestratorKey=this.generateKey(this.opts.orchestratorAlg);this.keyInitialized=true;if(persistence){try{await persistence.set({type:"asymmetric",kid:this.orchestratorKey.kid,alg:this.opts.orchestratorAlg,privateKey:this.orchestratorKey.privateKey.export({format:"jwk"}),publicJwk:this.orchestratorKey.publicJwk,createdAt:this.orchestratorKey.createdAt,version:1})}catch(error){this.logger.warn("[JwksService] Failed to persist dev key: %s",error.message)}}}generateKey(alg){const{generateKeyPairSync}=__require("node:crypto");if(alg==="RS256"){const{privateKey,publicKey}=generateKeyPairSync("rsa",{modulusLength:2048});const kid=bytesToHex(randomBytes(8));const publicJwk=publicKey.export({format:"jwk"});Object.assign(publicJwk,{kid,alg:"RS256",use:"sig",kty:"RSA"});return{kid,privateKey,publicJwk:{keys:[publicJwk]},createdAt:Date.now()}}else{const{privateKey,publicKey}=generateKeyPairSync("ec",{namedCurve:"P-256"});const kid=bytesToHex(randomBytes(8));const publicJwk=publicKey.export({format:"jwk"});Object.assign(publicJwk,{kid,alg:"ES256",use:"sig",kty:"EC"});return{kid,privateKey,publicJwk:{keys:[publicJwk]},createdAt:Date.now()}}}};import{escapeHtml}from"@frontmcp/utils";import{escapeHtml as escapeHtml2}from"@frontmcp/utils";var CDN={tailwind:"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4",fonts:{preconnect:["https://fonts.googleapis.com","https://fonts.gstatic.com"],stylesheet:"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"}};var BUILT_IN_AUTH_PAGE_CSP=["default-src 'self'","script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net","style-src 'self' 'unsafe-inline' https://fonts.googleapis.com","font-src 'self' https://fonts.gstatic.com","img-src 'self' data: https:","connect-src 'self'","object-src 'none'","base-uri 'none'","frame-ancestors 'none'"].join("; ");function builtInAuthPageHeaders(options={}){return{...options.csp===false?{}:{"Content-Security-Policy":BUILT_IN_AUTH_PAGE_CSP},"X-Frame-Options":"DENY","X-Content-Type-Options":"nosniff","Referrer-Policy":"same-origin","Cache-Control":"no-store"}}var DEFAULT_THEME={colors:{primary:"#3b82f6","primary-dark":"#2563eb",secondary:"#8b5cf6",accent:"#06b6d4",success:"#22c55e",warning:"#f59e0b",danger:"#ef4444"},fonts:{sans:'Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'}};function buildThemeCss(theme){const lines=[];if(theme.colors){for(const[key,value]of Object.entries(theme.colors)){if(value){lines.push(`--color-${key}: ${value};`)}}}if(theme.fonts){for(const[key,value]of Object.entries(theme.fonts)){if(value){lines.push(`--font-${key}: ${value};`)}}}if(theme.customVars){lines.push(theme.customVars)}return lines.join("\n ")}function baseLayout(content,options){const{title,description,includeTailwind=true,includeFonts=true,headExtra="",bodyClass="bg-gray-50 min-h-screen font-sans antialiased",theme}=options;const mergedTheme={colors:{...DEFAULT_THEME.colors,...theme?.colors},fonts:{...DEFAULT_THEME.fonts,...theme?.fonts},customVars:theme?.customVars,customCss:theme?.customCss};const fontPreconnect=includeFonts?CDN.fonts.preconnect.map((url,i)=>`<link rel="preconnect" href="${url}"${i>0?" crossorigin":""}>`).join("\n "):"";const fontStylesheet=includeFonts?`<link href="${CDN.fonts.stylesheet}" rel="stylesheet">`:"";const themeCss=buildThemeCss(mergedTheme);const customCss=mergedTheme.customCss||"";const tailwindBlock=includeTailwind?`<script src="${CDN.tailwind}"></script>
|
|
9
|
+
`;var JwksService=class{opts;logger;warnedProviders=new Set;orchestratorKey;publicJwks={keys:[]};providerJwks=new Map;unknownKidRefetches=new Map;constructor(opts){this.logger=opts?.logger??noopAuthLogger;this.opts={orchestratorAlg:opts?.orchestratorAlg??"RS256",rotateDays:opts?.rotateDays??30,providerJwksTtlMs:opts?.providerJwksTtlMs??6*60*60*1e3,networkTimeoutMs:opts?.networkTimeoutMs??5e3}}async getPublicJwks(){return{keys:[...this.publicJwks.keys]}}setPublicJwks(jwks){this.publicJwks={keys:[...jwks.keys]}}async verifyTransparentToken(token,candidates){if(!candidates?.length)return{ok:false,error:"no_providers"};const{createLocalJWKSet,decodeProtectedHeader,jwtVerify}=await loadJose();let kid;try{const header=decodeProtectedHeader(token);kid=typeof header?.kid==="string"?header.kid:void 0}catch{}let missingExp=false;let failure;for(const p of candidates){let jwks;try{jwks=await this.getJwksForProvider(p);if(kid&&!p.jwks?.keys?.length&&!jwks?.keys?.some(key=>key.kid===kid)){jwks=await this.refetchForUnknownKid(p)??jwks}if(!jwks?.keys?.length){failure=deeperFailure(failure,"jwks_unavailable");continue}const JWKS=createLocalJWKSet(jwks);const issuerConstraint=this.issuerConstraintFor(p);const{payload,protectedHeader}=await jwtVerify(token,JWKS,{algorithms:[...TRANSPARENT_JWT_ALGS],...issuerConstraint?{issuer:issuerConstraint}:{},requiredClaims:["exp"]});return{ok:true,issuer:payload?.iss,sub:payload?.sub,providerId:p.id,header:protectedHeader,payload}}catch(e){if(this.isWeakKeyError(e)){const fallbackJwks=jwks??await this.getJwksForProvider(p);if(fallbackJwks?.keys?.length){const fallbackResult=await this.verifyWithWeakKey(token,fallbackJwks,p);if(fallbackResult.ok){return fallbackResult}if(fallbackResult.error==="missing_exp")missingExp=true;if(fallbackResult.failure)failure=deeperFailure(failure,fallbackResult.failure)}}if(isMissingExpError(e))missingExp=true;failure=deeperFailure(failure,tokenVerifyFailureOf(e));this.logger.debug("[JwksService] Failed to verify token for provider: %s",p.id,e)}}if(missingExp)return{ok:false,error:MISSING_EXP_ERROR,failure};return{ok:false,error:`no_provider_verified${kid?` (kid=${kid})`:""}`,failure}}issuerConstraintFor(p){if(p.verifyIssuer===false)return void 0;const issuers=new Set;for(const candidate of[p.issuerUrl,...p.additionalIssuers??[]]){const normalized=normalizeIssuer(candidate);if(!normalized)continue;issuers.add(normalized);issuers.add(`${normalized}/`)}return[...issuers]}isWeakKeyError(error){const message=typeof error==="object"&&error!==null&&"message"in error&&typeof error.message==="string"?error.message:String(error);return message.includes("modulusLength")&&message.includes("2048")}async verifyWithWeakKey(token,jwks,provider){try{const parts=token.split(".");if(parts.length!==3){return{ok:false,error:"invalid_token_format",failure:"malformed"}}const[headerB64,payloadB64,signatureB64]=parts;const header=JSON.parse(Buffer.from(headerB64,"base64url").toString("utf8"));const payload=JSON.parse(Buffer.from(payloadB64,"base64url").toString("utf8"));const matchingKey=this.findMatchingKey(jwks,header);if(!matchingKey){return{ok:false,error:"no_matching_key",failure:"unknown_key"}}const signatureInput=`${headerB64}.${payloadB64}`;const signature=Buffer.from(signatureB64,"base64url");const jwtAlg=typeof header.alg==="string"?header.alg:void 0;if(!jwtAlg){return{ok:false,error:"missing_alg",failure:"malformed"}}const isValid=await rsaVerify(jwtAlg,Buffer.from(signatureInput),matchingKey,signature);if(!isValid){return{ok:false,error:"signature_invalid",failure:"bad_signature"}}if(provider.verifyIssuer!==false){const payloadIssuerRaw=typeof payload.iss==="string"?payload.iss:void 0;if(!payloadIssuerRaw){return{ok:false,error:"issuer_mismatch",failure:"wrong_issuer"}}const trustedIssuers=new Set([provider.issuerUrl,...provider.additionalIssuers??[]].map(issuer=>normalizeIssuer(issuer)));const payloadIssuer=normalizeIssuer(payloadIssuerRaw);if(!trustedIssuers.has(payloadIssuer)){return{ok:false,error:"issuer_mismatch",failure:"wrong_issuer"}}}const nowSec=Math.floor(Date.now()/1e3);if(payload.exp===void 0){return{ok:false,error:"missing_exp",failure:"missing_claim"}}if(typeof payload.exp!=="number"||!Number.isFinite(payload.exp)||payload.exp<=nowSec){return{ok:false,error:"token_expired",failure:"expired"}}if(payload.nbf!==void 0&&(typeof payload.nbf!=="number"||!Number.isFinite(payload.nbf)||payload.nbf>nowSec)){return{ok:false,error:"token_not_yet_valid",failure:"not_yet_valid"}}if(!this.warnedProviders.has(provider.id)){this.warnedProviders.add(provider.id);this.logger.warn(WEAK_KEY_WARNING);this.logger.warn(" Provider: %s (%s)",provider.id,provider.issuerUrl)}return{ok:true,issuer:payload.iss,sub:payload.sub,providerId:provider.id,header,payload}}catch(err){const message=err instanceof Error?err.message:"unknown";return{ok:false,error:`weak_key_verification_failed: ${message}`,failure:"malformed"}}}findMatchingKey(jwks,header){if(header.kid){return jwks.keys.find(k=>k.kid===header.kid)}if(header.alg){const byAlg=jwks.keys.find(k=>k.alg===header.alg||k.kty==="RSA"&&header.alg?.startsWith("RS"));if(byAlg)return byAlg}return jwks.keys.find(k=>k.kty==="RSA")}setProviderJwks(providerId,jwks){this.providerJwks.set(providerId,{jwks,fetchedAt:Date.now()})}async getJwksForProvider(ref){if(ref.jwks?.keys?.length){this.setProviderJwks(ref.id,ref.jwks);return ref.jwks}const cached=this.providerJwks.get(ref.id);if(cached&&Date.now()-cached.fetchedAt<this.opts.providerJwksTtlMs){return cached.jwks}const fetched=await this.fetchProviderJwks(ref);if(fetched)return fetched;const maxStaleMs=Math.max(this.opts.providerJwksTtlMs*4,6e4);if(cached&&Date.now()-cached.fetchedAt<maxStaleMs){return cached.jwks}return void 0}refetchForUnknownKid(ref){let refetch=this.unknownKidRefetches.get(ref.id);if(!refetch||Date.now()-refetch.startedAt>=UNKNOWN_KID_REFETCH_COOLDOWN_MS){refetch={startedAt:Date.now(),done:this.fetchProviderJwks(ref)};this.unknownKidRefetches.set(ref.id,refetch)}return refetch.done}async fetchProviderJwks(ref){if(ref.jwksUri){const fromUri=await this.tryFetchJwks(ref.id,ref.jwksUri);if(fromUri?.keys?.length)return fromUri}const issuer=trimSlash(ref.issuerUrl);const fromWellKnown=await this.tryFetchJwks(ref.id,`${issuer}/.well-known/jwks.json`);if(fromWellKnown?.keys?.length)return fromWellKnown;for(const document of["oauth-authorization-server","openid-configuration"]){const meta=await this.tryFetchAsMeta(`${issuer}/.well-known/${document}`);const uri=meta&&typeof meta==="object"&&meta["jwks_uri"]?String(meta["jwks_uri"]):void 0;if(uri){const fromMeta=await this.tryFetchJwks(ref.id,uri);if(fromMeta?.keys?.length)return fromMeta}}return void 0}async getOrchestratorJwks(){this.ensureOrchestratorKey();return this.orchestratorKey.publicJwk}async getOrchestratorSigningKey(){this.ensureOrchestratorKey();return{kid:this.orchestratorKey.kid,key:this.orchestratorKey.privateKey,alg:this.opts.orchestratorAlg}}async tryFetchJwks(providerId,uri){try{const jwks=await this.fetchJson(uri);if(jwks?.keys?.length){this.setProviderJwks(providerId,jwks);return jwks}}catch{}return void 0}async tryFetchAsMeta(url){try{return await this.fetchJson(url)}catch{return void 0}}async fetchJson(url){const ctl=typeof AbortController!=="undefined"?new AbortController:void 0;const timer=setTimeout(()=>ctl?.abort(),this.opts.networkTimeoutMs);try{let currentUrl=url;for(let hop=0;;hop++){const parsed=new URL(currentUrl);const host=parsed.hostname.toLowerCase();const isLocalhost=host==="localhost"||host==="127.0.0.1"||host==="::1"||host==="[::1]"||host.endsWith(".localhost");if(parsed.protocol!=="https:"&&!(parsed.protocol==="http:"&&(isLocalhost||!isProduction()))){throw new Error(`insecure scheme "${parsed.protocol}"`)}if(!(isLocalhost&&!isProduction())){const ssrf=await resolveAndCheckHostname(parsed.hostname);if(!ssrf.allowed)throw new Error(`blocked host: ${ssrf.reason}`)}const res=await fetch(currentUrl,{method:"GET",headers:{accept:"application/json"},signal:ctl?.signal,redirect:"manual"});if(isRedirectResponse(res)){const location=res.headers.get("location");if(!location)throw new Error("redirect without a readable Location header");if(hop>=MAX_JWKS_FETCH_HOPS)throw new Error("too many redirects");currentUrl=new URL(location,currentUrl).toString();continue}if(!res.ok)throw new Error(`HTTP ${res.status}`);const contentLength=res.headers.get("content-length");if(contentLength&&Number(contentLength)>MAX_JWKS_FETCH_BYTES){throw new Error("response too large")}const text=await res.text();if(text.length>MAX_JWKS_FETCH_BYTES){throw new Error("response too large")}return JSON.parse(text)}}finally{clearTimeout(timer)}}ensureOrchestratorKey(){const maxAge=this.opts.rotateDays*24*60*60*1e3;if(!this.orchestratorKey||Date.now()-this.orchestratorKey.createdAt>maxAge){this.orchestratorKey=this.generateKey(this.opts.orchestratorAlg)}}generateKey(alg){const{generateKeyPairSync}=__require("node:crypto");if(alg==="RS256"){const{privateKey,publicKey}=generateKeyPairSync("rsa",{modulusLength:2048});const kid=bytesToHex(randomBytes(8));const publicJwk=publicKey.export({format:"jwk"});Object.assign(publicJwk,{kid,alg:"RS256",use:"sig",kty:"RSA"});return{kid,privateKey,publicJwk:{keys:[publicJwk]},createdAt:Date.now()}}else{const{privateKey,publicKey}=generateKeyPairSync("ec",{namedCurve:"P-256"});const kid=bytesToHex(randomBytes(8));const publicJwk=publicKey.export({format:"jwk"});Object.assign(publicJwk,{kid,alg:"ES256",use:"sig",kty:"EC"});return{kid,privateKey,publicJwk:{keys:[publicJwk]},createdAt:Date.now()}}}};import{base64urlDecode,sha256Base64url}from"@frontmcp/utils";import{bytesToHex as bytesToHex2,randomBytes as randomBytes2}from"@frontmcp/utils";var AuthInternalError=class extends Error{errorId;isPublic=false;statusCode=500;code;constructor(message,code="AUTH_INTERNAL_ERROR"){super(message);this.name=this.constructor.name;this.code=code;this.errorId=`err_${bytesToHex2(randomBytes2(8))}`;Error.captureStackTrace(this,this.constructor)}getPublicMessage(){if(this.isPublic)return this.message;return`Internal auth error. Please contact support with error ID: ${this.errorId}`}getInternalMessage(){return this.message}};var EncryptionContextNotSetError=class extends AuthInternalError{constructor(){super("Encryption context is not set","ENCRYPTION_CONTEXT_NOT_SET")}};var VaultLoadError=class extends AuthInternalError{originalError;constructor(vaultId,originalError){super(`Failed to load vault "${vaultId}"${originalError?`: ${originalError.message}`:""}`,"VAULT_LOAD_ERROR");this.originalError=originalError}};var VaultNotFoundError=class extends AuthInternalError{constructor(entityType,id){super(`${entityType} "${id}" not found in vault`,"VAULT_NOT_FOUND")}};var TokenNotAvailableError=class extends AuthInternalError{constructor(message){super(message,"TOKEN_NOT_AVAILABLE")}};var TokenStoreRequiredError=class extends AuthInternalError{constructor(context){super(`Token store is required for ${context}`,"TOKEN_STORE_REQUIRED")}};var NoProviderIdError=class extends AuthInternalError{constructor(message){super(message,"NO_PROVIDER_ID")}};var TokenLeakDetectedError=class extends AuthInternalError{constructor(detail){super(`Token leak detected: ${detail}`,"TOKEN_LEAK_DETECTED")}};var SessionSecretRequiredError=class extends AuthInternalError{constructor(component){super(`Session secret is required for ${component}`,"SESSION_SECRET_REQUIRED")}};var JwtSecretRequiredError=class extends AuthInternalError{constructor(mode){super(`JWT_SECRET is required in production for auth.mode "${mode}". Without it the server signs tokens with a random per-process secret, so every restart invalidates outstanding tokens and any second instance or isolate rejects tokens the first one minted.`,"JWT_SECRET_REQUIRED")}};var JwtSecretWeakError=class extends AuthInternalError{constructor(byteLength){super(`JWT_SECRET is ${byteLength} bytes; HS256 requires at least 32 (RFC 7518 \xA73.2). A shorter secret is guessable, and whoever guesses it can forge tokens for this server. Generate one with \`openssl rand -hex 32\`.`,"JWT_SECRET_INVALID")}};var SigningKeyInvalidError=class extends AuthInternalError{constructor(message){super(message,"SIGNING_KEY_INVALID")}};var CredentialProviderAlreadyRegisteredError=class extends AuthInternalError{constructor(name){super(`Credential provider "${name}" is already registered`,"CREDENTIAL_PROVIDER_ALREADY_REGISTERED")}};var AuthProvidersNotConfiguredError=class extends AuthInternalError{isPublic=true;constructor(providerName){const register="Register it in @FrontMcp({ authProvidersVault: { providers: [...] } }).";super(providerName===void 0?"Auth providers are not configured: this server registers no auth providers. Register them in @FrontMcp({ authProvidersVault: { providers: [...] } }).":`Auth provider "${providerName}" is not configured: this server registers no auth providers. ${register}`,"AUTH_PROVIDERS_NOT_CONFIGURED")}};var OrchestratedAuthNotAvailableError=class extends AuthInternalError{constructor(){super("Orchestrated auth is not available","ORCHESTRATED_AUTH_NOT_AVAILABLE")}};var EncryptionKeyNotConfiguredError=class extends AuthInternalError{constructor(){super("Encryption key is not configured","ENCRYPTION_KEY_NOT_CONFIGURED")}};var SessionIdEmptyError=class extends AuthInternalError{constructor(storeName){super(`Session ID must not be empty (${storeName})`,"SESSION_ID_EMPTY")}};var ElicitationSecretRequiredError=class extends AuthInternalError{constructor(){super("Elicitation secret is required","ELICITATION_SECRET_REQUIRED")}};var ScopeDeniedError=class extends AuthInternalError{constructor(providerId){super(`Scope access denied for provider "${providerId}"`,"SCOPE_DENIED")}};var InMemoryStoreRequiredError=class extends AuthInternalError{constructor(component){super(`In-memory store is required for ${component}`,"INMEMORY_STORE_REQUIRED")}};var OrchestratorJwksNotAvailableError=class extends AuthInternalError{constructor(){super("Orchestrator JWKS is not available","ORCHESTRATOR_JWKS_NOT_AVAILABLE")}};var SecureStoreConfigError=class extends AuthInternalError{constructor(message){super(`Invalid auth.secureStore: ${message}`,"SECURE_STORE_CONFIG_INVALID")}};var AuthInvalidInputError=class extends AuthInternalError{constructor(message){super(message,"AUTH_INVALID_INPUT")}};var CredentialStorageError=class extends AuthInternalError{constructor(message){super(message,"CREDENTIAL_STORAGE_ERROR")}};var AuthFlowError=class extends AuthInternalError{constructor(message,code="AUTH_FLOW_ERROR"){super(message,code)}};var HMAC_MIN_BYTES={HS256:32,HS384:48,HS512:64};var ALGS_BY_KTY={RSA:["RS256","RS384","RS512","PS256","PS384","PS512"],EC:["ES256","ES384","ES512"],OKP:["EdDSA","Ed25519"]};var EC_ALG_BY_CURVE={"P-256":"ES256","P-384":"ES384","P-521":"ES512"};var PUBLIC_MEMBERS={RSA:["e","kty","n"],EC:["crv","kty","x","y"],OKP:["crv","kty","x"]};var MIN_RSA_MODULUS_BYTES=256;function isAsymmetricKty(kty){return kty==="RSA"||kty==="EC"||kty==="OKP"}var LocalTokenKeys=class _LocalTokenKeys{constructor(alg,kid,publicJwks,algorithms,hmacKey,privateJwk){this.alg=alg;this.kid=kid;this.publicJwks=publicJwks;this.algorithms=algorithms;this.hmacKey=hmacKey;this.privateJwk=privateJwk}alg;kid;publicJwks;algorithms;hmacKey;privateJwk;importedKey;publicKeySet;static fromConfig(config,fallbackSecret,optionPath){const{signKey,jwks}=config;if(signKey!==void 0&&!(signKey instanceof Uint8Array)&&signKey.kty!=="oct"){return _LocalTokenKeys.asymmetric(signKey,jwks?.keys??[],optionPath)}if(jwks?.keys?.length){const hmacSource=signKey?`${optionPath}.signKey`:"JWT_SECRET";throw new SigningKeyInvalidError(`${optionPath}.jwks is set, but tokens are signed with an HMAC key (${hmacSource}), which is never published. Set an RSA, EC or Ed25519 private key as ${optionPath}.signKey.`)}const hmac=_LocalTokenKeys.hmacKeyOf(signKey,fallbackSecret,`${optionPath}.signKey`);return new _LocalTokenKeys(hmac.alg,hmac.kid,{keys:[]},[hmac.alg],hmac.key,void 0)}protectedHeader(){return this.kid?{alg:this.alg,typ:"JWT",kid:this.kid}:{alg:this.alg,typ:"JWT"}}signingKey(){if(this.hmacKey)return Promise.resolve(this.hmacKey);this.importedKey??=loadJose().then(({importJWK})=>importJWK(this.privateJwk,this.alg)).catch(error=>{this.importedKey=void 0;const reason=error instanceof Error?error.message:String(error);throw new SigningKeyInvalidError(`The configured signKey can't be imported: ${reason}`)});return this.importedKey}async verify(token,options){const{createLocalJWKSet,jwtVerify}=await loadJose();const verifyOptions={...options,algorithms:this.algorithms};if(this.hmacKey)return jwtVerify(token,this.hmacKey,verifyOptions);this.publicKeySet??=createLocalJWKSet(this.publicJwks);return jwtVerify(token,this.publicKeySet,verifyOptions)}static asymmetric(signKey,jwksKeys,optionPath){const signing=_LocalTokenKeys.publicKeyOf(signKey,`${optionPath}.signKey`,"private");const published=[signing];jwksKeys.forEach((key,index)=>{const publicKey=_LocalTokenKeys.publicKeyOf(key,`${optionPath}.jwks.keys[${index}]`,"public");if(!published.some(existing=>existing.kid===publicKey.kid))published.push(publicKey)});const algorithms=[...new Set(published.map(key=>key.alg))];return new _LocalTokenKeys(signing.alg,signing.kid,{keys:published},algorithms,void 0,signKey)}static hmacKeyOf(signKey,fallbackSecret,path){if(signKey===void 0)return{alg:"HS256",key:fallbackSecret};if(signKey instanceof Uint8Array)return{alg:"HS256",key:_LocalTokenKeys.longEnough(signKey,"HS256",path)};const alg=signKey.alg??"HS256";if(!(alg in HMAC_MIN_BYTES)){throw new SigningKeyInvalidError(`${path} is an oct key with alg "${alg}"; use HS256, HS384 or HS512.`)}if(typeof signKey.k!=="string"||signKey.k.length===0){throw new SigningKeyInvalidError(`${path} is an oct key without "k".`)}return{alg,kid:signKey.kid,key:_LocalTokenKeys.longEnough(base64urlDecode(signKey.k),alg,path)}}static longEnough(key,alg,path){const minBytes=HMAC_MIN_BYTES[alg];if(key.length<minBytes){throw new SigningKeyInvalidError(`${path} is ${key.length} bytes; ${alg} requires at least ${minBytes} (RFC 7518 \xA73.2).`)}return key}static publicKeyOf(key,path,expected){if(!isAsymmetricKty(key.kty)){throw new SigningKeyInvalidError(`${path} has kty "${String(key.kty)}"; use an RSA, EC or OKP key.`)}const isPrivate=typeof key.d==="string";if(expected==="private"&&!isPrivate){throw new SigningKeyInvalidError(`${path} is a public key (it has no "d"); it must be the private key.`)}if(expected==="public"&&isPrivate){throw new SigningKeyInvalidError(`${path} holds a private key; the jwks are published, so list public keys only.`)}if(key.use!==void 0&&key.use!=="sig"){throw new SigningKeyInvalidError(`${path} has use "${key.use}"; a signing key needs use "sig" or none.`)}const alg=key.alg??_LocalTokenKeys.defaultAlg(key);if(!ALGS_BY_KTY[key.kty].includes(alg)){throw new SigningKeyInvalidError(`${path} has alg "${alg}", which ${key.kty} keys can't sign with.`)}if(key.kty==="EC"&&EC_ALG_BY_CURVE[key.crv??""]!==alg){throw new SigningKeyInvalidError(`${path} has curve "${String(key.crv)}" and alg "${alg}"; use P-256/ES256, P-384/ES384 or P-521/ES512.`)}if(key.kty==="OKP"&&key.crv!=="Ed25519"){throw new SigningKeyInvalidError(`${path} is an OKP key on "${String(key.crv)}"; only Ed25519 signs.`)}const members=PUBLIC_MEMBERS[key.kty];const missing=members.filter(member=>typeof key[member]!=="string");if(missing.length>0){throw new SigningKeyInvalidError(`${path} is missing ${missing.map(member=>`"${member}"`).join(", ")}.`)}if(key.kty==="RSA"&&base64urlDecode(key.n).length<MIN_RSA_MODULUS_BYTES){throw new SigningKeyInvalidError(`${path} is an RSA key shorter than 2048 bits.`)}const publicMembers=Object.fromEntries(members.map(member=>[member,key[member]]));const kid=key.kid??sha256Base64url(JSON.stringify(publicMembers));return{...publicMembers,kid,alg,use:"sig"}}static defaultAlg(key){if(key.kty==="RSA")return"RS256";if(key.kty==="OKP")return"EdDSA";return EC_ALG_BY_CURVE[key.crv??""]??"ES256"}};import{escapeHtml}from"@frontmcp/utils";import{escapeHtml as escapeHtml2}from"@frontmcp/utils";var CDN={tailwind:"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4",fonts:{preconnect:["https://fonts.googleapis.com","https://fonts.gstatic.com"],stylesheet:"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"}};var BUILT_IN_AUTH_PAGE_CSP=["default-src 'self'","script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net","style-src 'self' 'unsafe-inline' https://fonts.googleapis.com","font-src 'self' https://fonts.gstatic.com","img-src 'self' data: https:","connect-src 'self'","object-src 'none'","base-uri 'none'","frame-ancestors 'none'"].join("; ");function builtInAuthPageHeaders(options={}){return{...options.csp===false?{}:{"Content-Security-Policy":BUILT_IN_AUTH_PAGE_CSP},"X-Frame-Options":"DENY","X-Content-Type-Options":"nosniff","Referrer-Policy":"same-origin","Cache-Control":"no-store"}}var DEFAULT_THEME={colors:{primary:"#3b82f6","primary-dark":"#2563eb",secondary:"#8b5cf6",accent:"#06b6d4",success:"#22c55e",warning:"#f59e0b",danger:"#ef4444"},fonts:{sans:'Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'}};function buildThemeCss(theme){const lines=[];if(theme.colors){for(const[key,value]of Object.entries(theme.colors)){if(value){lines.push(`--color-${key}: ${value};`)}}}if(theme.fonts){for(const[key,value]of Object.entries(theme.fonts)){if(value){lines.push(`--font-${key}: ${value};`)}}}if(theme.customVars){lines.push(theme.customVars)}return lines.join("\n ")}function baseLayout(content,options){const{title,description,includeTailwind=true,includeFonts=true,headExtra="",bodyClass="bg-gray-50 min-h-screen font-sans antialiased",theme}=options;const mergedTheme={colors:{...DEFAULT_THEME.colors,...theme?.colors},fonts:{...DEFAULT_THEME.fonts,...theme?.fonts},customVars:theme?.customVars,customCss:theme?.customCss};const fontPreconnect=includeFonts?CDN.fonts.preconnect.map((url,i)=>`<link rel="preconnect" href="${url}"${i>0?" crossorigin":""}>`).join("\n "):"";const fontStylesheet=includeFonts?`<link href="${CDN.fonts.stylesheet}" rel="stylesheet">`:"";const themeCss=buildThemeCss(mergedTheme);const customCss=mergedTheme.customCss||"";const tailwindBlock=includeTailwind?`<script src="${CDN.tailwind}"></script>
|
|
10
10
|
<style type="text/tailwindcss">
|
|
11
11
|
@theme {
|
|
12
12
|
${themeCss}
|
|
@@ -129,9 +129,9 @@ var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyN
|
|
|
129
129
|
|
|
130
130
|
<div class="p-4 bg-amber-50 border border-amber-200 rounded-lg text-sm text-amber-800 text-center">
|
|
131
131
|
This is an incremental authorization. Your existing session will be expanded to include ${escapeHtml3(app.appName)}.
|
|
132
|
-
</div>`;return centeredCardLayout(content,{title:`Authorize ${app.appName}`})}function buildFederatedLoginPage(params){const{providers,clientName,pendingAuthId,callbackPath}=params;const providerCards=providers.map(provider=>{const isPrimaryBadge=provider.isPrimary?`<span class="px-2 py-0.5 text-xs font-medium bg-blue-600 text-white rounded-full">Primary</span>`:"";const providerUrl=provider.providerUrl?`<p class="text-xs text-gray-400 font-mono truncate">${escapeHtml3(provider.providerUrl)}</p>`:"";const appIds=provider.appIds.length>0?`<p class="text-xs text-gray-500 mt-1">Apps: ${provider.appIds.map(id=>escapeHtml3(id)).join(", ")}</p>`:"";return`<label class="flex items-start gap-4 p-4 bg-white border-2 border-gray-200 rounded-xl cursor-pointer hover:border-blue-300 transition-colors has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50">
|
|
132
|
+
</div>`;return centeredCardLayout(content,{title:`Authorize ${app.appName}`})}function buildFederatedLoginPage(params){const{providers,clientName,pendingAuthId,callbackPath,allowSkip=true}=params;const providerCards=providers.map(provider=>{const isPrimaryBadge=provider.isPrimary?`<span class="px-2 py-0.5 text-xs font-medium bg-blue-600 text-white rounded-full">Primary</span>`:"";const providerUrl=provider.providerUrl?`<p class="text-xs text-gray-400 font-mono truncate">${escapeHtml3(provider.providerUrl)}</p>`:"";const appIds=provider.appIds.length>0?`<p class="text-xs text-gray-500 mt-1">Apps: ${provider.appIds.map(id=>escapeHtml3(id)).join(", ")}</p>`:"";return`<label class="flex items-start gap-4 p-4 bg-white border-2 border-gray-200 rounded-xl cursor-pointer hover:border-blue-300 transition-colors has-[:checked]:border-blue-500 has-[:checked]:bg-blue-50">
|
|
133
133
|
<input type="checkbox" name="providers" value="${escapeHtml3(provider.providerId)}"
|
|
134
|
-
class="mt-1 w-5 h-5 rounded border-gray-300" ${provider.isPrimary?"checked":""}>
|
|
134
|
+
class="mt-1 w-5 h-5 rounded border-gray-300" ${provider.isPrimary||provider.required?"checked":""}${provider.required?" required":""}>
|
|
135
135
|
<div class="flex-1">
|
|
136
136
|
<div class="flex items-center gap-2 mb-1">
|
|
137
137
|
<span class="font-semibold text-gray-900">${escapeHtml3(provider.providerName)}</span>
|
|
@@ -141,7 +141,13 @@ var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyN
|
|
|
141
141
|
${providerUrl}
|
|
142
142
|
${appIds}
|
|
143
143
|
</div>
|
|
144
|
-
</label>`}).join("\n");const
|
|
144
|
+
</label>`}).join("\n");const skipAllButton=allowSkip?`<button type="button"
|
|
145
|
+
class="flex-1 px-6 py-3 text-gray-700 bg-gray-100 hover:bg-gray-200 font-medium rounded-lg transition-colors"
|
|
146
|
+
onclick="document.querySelectorAll('input[name=providers]').forEach(cb => cb.checked = false); document.getElementById('federated-form').submit();">
|
|
147
|
+
Skip All
|
|
148
|
+
</button>`:"";const skipNote=allowSkip?`<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-800">
|
|
149
|
+
Skipped providers can be authorized later when you try to use their tools (progressive authorization).
|
|
150
|
+
</div>`:"";const content=`
|
|
145
151
|
<h1 class="text-3xl font-bold text-gray-900 mb-4">Select Authorization Providers</h1>
|
|
146
152
|
<p class="text-gray-600 mb-8">
|
|
147
153
|
${escapeHtml3(clientName)} uses multiple authentication providers. Select which ones you want to authorize.
|
|
@@ -172,21 +178,52 @@ var __defProp=Object.defineProperty;var __getOwnPropNames=Object.getOwnPropertyN
|
|
|
172
178
|
|
|
173
179
|
<!-- Buttons -->
|
|
174
180
|
<div class="flex gap-4">
|
|
175
|
-
|
|
176
|
-
class="flex-1 px-6 py-3 text-gray-700 bg-gray-100 hover:bg-gray-200 font-medium rounded-lg transition-colors"
|
|
177
|
-
onclick="document.querySelectorAll('input[name=providers]').forEach(cb => cb.checked = false); document.getElementById('federated-form').submit();">
|
|
178
|
-
Skip All
|
|
179
|
-
</button>
|
|
181
|
+
${skipAllButton}
|
|
180
182
|
<button type="submit"
|
|
181
183
|
class="flex-1 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
|
|
182
184
|
Continue
|
|
183
185
|
</button>
|
|
184
186
|
</div>
|
|
185
187
|
</form>
|
|
188
|
+
${skipNote}`;return wideLayout(content,{title:"Select Providers"})}function buildFederatedStepPage(params){const{provider,step,clientName,pendingAuthId,callbackPath,csrfToken,error}=params;const primaryBadge=provider.isPrimary?`<span class="px-2 py-0.5 text-xs font-medium bg-blue-600 text-white rounded-full">Primary</span>`:"";const providerUrl=provider.providerUrl?`<p class="text-xs text-gray-400 font-mono truncate">${escapeHtml3(provider.providerUrl)}</p>`:"";const appIds=provider.appIds.length>0?`<p class="text-xs text-gray-500 mt-1">Apps: ${provider.appIds.map(id=>escapeHtml3(id)).join(", ")}</p>`:"";const errorBanner=error?`<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800">${escapeHtml3(error)}</div>`:"";const emailField=step.last?`<div class="mb-6">
|
|
189
|
+
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">Email</label>
|
|
190
|
+
<input type="email" id="email" name="email" required placeholder="you@example.com"
|
|
191
|
+
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
192
|
+
</div>`:"";const secondaryButton="flex-1 px-6 py-3 text-gray-700 bg-gray-100 hover:bg-gray-200 font-medium rounded-lg transition-colors";const skipButton=step.canSkip?`<button type="submit" name="action" value="skip" class="${secondaryButton}">Skip</button>`:"";const backButton=step.canGoBack?`<button type="submit" name="action" value="back" formnovalidate class="${secondaryButton}">Back</button>`:"";const skipNote=step.canSkip?`<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-800">
|
|
193
|
+
A skipped provider can be authorized later when you try to use its tools (progressive authorization).
|
|
194
|
+
</div>`:"";const content=`
|
|
195
|
+
<p class="text-sm font-medium text-blue-600 mb-2">Step ${step.index+1} of ${step.count}</p>
|
|
196
|
+
<h1 class="text-3xl font-bold text-gray-900 mb-4">Authorize ${escapeHtml3(provider.providerName)}</h1>
|
|
197
|
+
<p class="text-gray-600 mb-8">
|
|
198
|
+
${escapeHtml3(clientName)} uses multiple authentication providers. Authorize them one at a time.
|
|
199
|
+
</p>
|
|
200
|
+
${errorBanner}
|
|
201
|
+
<form method="POST" action="${escapeHtml3(callbackPath)}" id="federated-step-form">
|
|
202
|
+
<input type="hidden" name="pending_auth_id" value="${escapeHtml3(pendingAuthId)}">
|
|
203
|
+
<input type="hidden" name="federated" value="true">
|
|
204
|
+
<input type="hidden" name="federated_step" value="${step.index}">
|
|
205
|
+
<input type="hidden" name="csrf" value="${escapeHtml3(csrfToken)}">
|
|
186
206
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
207
|
+
<div class="p-4 bg-white border-2 border-blue-500 rounded-xl mb-8">
|
|
208
|
+
<div class="flex items-center gap-2 mb-1">
|
|
209
|
+
<span class="font-semibold text-gray-900">${escapeHtml3(provider.providerName)}</span>
|
|
210
|
+
${primaryBadge}
|
|
211
|
+
</div>
|
|
212
|
+
<p class="text-sm text-gray-500">Mode: ${escapeHtml3(provider.mode)}</p>
|
|
213
|
+
${providerUrl}
|
|
214
|
+
${appIds}
|
|
215
|
+
</div>
|
|
216
|
+
${emailField}
|
|
217
|
+
<div class="flex flex-row-reverse gap-4">
|
|
218
|
+
<button type="submit" name="action" value="continue"
|
|
219
|
+
class="flex-1 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
|
|
220
|
+
${step.last?"Finish":"Continue"}
|
|
221
|
+
</button>
|
|
222
|
+
${skipButton}
|
|
223
|
+
${backButton}
|
|
224
|
+
</div>
|
|
225
|
+
</form>
|
|
226
|
+
${skipNote}`;return wideLayout(content,{title:`Authorize ${provider.providerName}`})}function buildToolConsentPage(params){const{tools,clientName,pendingAuthId,csrfToken,callbackPath,userName,userEmail,groupByApp=true,showDescriptions=true,customMessage,allowSelectAll=true,requireSelection=true,defaultSelectedTools,error,hiddenFields=[]}=params;const defaultSet=defaultSelectedTools?new Set(defaultSelectedTools):void 0;const isChecked=toolId=>defaultSet?defaultSet.has(toolId):true;const checkedCount=tools.filter(t=>isChecked(t.toolId)).length;const renderTool=tool=>{const desc=showDescriptions&&tool.description?`<p class="text-sm text-gray-500 mt-0.5">${escapeHtml3(tool.description)}</p>`:"";return`<label class="flex items-start gap-3 p-3 bg-white rounded-lg cursor-pointer hover:bg-gray-50">
|
|
190
227
|
<input type="checkbox" name="tools" value="${escapeHtml3(tool.toolId)}" class="mt-0.5 w-5 h-5 rounded border-gray-300"${isChecked(tool.toolId)?" checked":""}>
|
|
191
228
|
<div>
|
|
192
229
|
<span class="font-medium text-gray-900">${escapeHtml3(tool.toolName)}</span>
|
|
@@ -354,4 +391,7 @@ ${buildLoginFieldHtml(field)}
|
|
|
354
391
|
<code class="px-2 py-1 bg-gray-100 rounded text-red-600 font-mono text-sm">${escapeHtml3(error)}</code>
|
|
355
392
|
</p>
|
|
356
393
|
<p class="text-gray-600">${escapeHtml3(description)}</p>
|
|
357
|
-
</div>`;return centeredCardLayout(content,{title:"Error"})}function renderToHtml(html,_options){return html}function toLoginExtraFields(fields,values){if(!fields)return[];return Object.entries(fields).map(([name,def])=>({name,type:def.type,label:def.label,required:def.required,placeholder:def.placeholder,options:def.options,value:values?.[name]}))}function withoutSecretFieldValues(values,fields){if(!values)return void 0;const kept={};for(const[name,value]of Object.entries(values)){if(fields?.[name]?.type==="password")continue;kept[name]=value}return kept}function renderLocalLoginPage(login,ctx,submitted){const values=withoutSecretFieldValues(submitted,login?.fields);if(login?.render){return login.render({...ctx,values})}const extraFields=login?.fields?toLoginExtraFields(login.fields,values):void 0;return buildLoginPage({clientName:ctx.clientName,scope:ctx.scopes.join(" "),pendingAuthId:ctx.pendingAuthId,callbackPath:ctx.callbackPath,title:login?.title,subtitle:login?.subtitle,logoUri:login?.logoUri??ctx.logoUri,error:ctx.error,extraFields})}import{internalZ as z}from"@frontmcp/lazy-zod";import{randomUUID,sha256Base64url}from"@frontmcp/utils";var pkceChallengeSchema=z.object({challenge:z.string().min(43).max(128),method:z.literal("S256")});var authorizationCodeRecordSchema=z.object({code:z.string().min(1),clientId:z.string().min(1),redirectUri:z.string().url(),scopes:z.array(z.string()),pkce:pkceChallengeSchema,userSub:z.string().min(1),userEmail:z.string().email().optional(),userName:z.string().optional(),state:z.string().optional(),createdAt:z.number(),expiresAt:z.number(),used:z.boolean(),resource:z.string().url().optional(),selectedToolIds:z.array(z.string()).optional(),selectedProviderIds:z.array(z.string()).optional(),skippedProviderIds:z.array(z.string()).optional(),consentEnabled:z.boolean().optional(),federatedLoginUsed:z.boolean().optional(),pendingAuthId:z.string().optional(),authorizedAppIds:z.array(z.string()).optional(),customClaims:z.record(z.string(),z.unknown()).optional()});var AUTH_CODE_TTL_MS=60*1e3;var PENDING_AUTH_TTL_MS=10*60*1e3;var REFRESH_TOKEN_TTL_MS=30*24*60*60*1e3;function generateAuthorizationCode(){return randomUUID().replace(/-/g,"")+randomUUID().replace(/-/g,"")}function generateRefreshTokenValue(){return randomUUID()+"-"+randomUUID()}function buildCodeRecord(params){const now=Date.now();return{code:generateAuthorizationCode(),clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,pkce:params.pkce,userSub:params.userSub,userEmail:params.userEmail,userName:params.userName,state:params.state,resource:params.resource,createdAt:now,expiresAt:now+AUTH_CODE_TTL_MS,used:false,selectedToolIds:params.selectedToolIds,selectedProviderIds:params.selectedProviderIds,skippedProviderIds:params.skippedProviderIds,consentEnabled:params.consentEnabled,federatedLoginUsed:params.federatedLoginUsed,pendingAuthId:params.pendingAuthId,authorizedAppIds:params.authorizedAppIds,customClaims:params.customClaims}}function buildPendingRecord(params){const now=Date.now();return{id:randomUUID(),clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,pkce:params.pkce,state:params.state,resource:params.resource,createdAt:now,expiresAt:now+PENDING_AUTH_TTL_MS,isIncremental:params.isIncremental,incrementalSub:params.incrementalSub,targetAppId:params.targetAppId,targetToolId:params.targetToolId,existingSessionId:params.existingSessionId,existingAuthorizationId:params.existingAuthorizationId,priorAuthorizedAppIds:params.priorAuthorizedAppIds,federatedLogin:params.federatedLogin,consent:params.consent,authUiCsrf:params.authUiCsrf,signinBinding:params.signinBinding}}function buildRefreshTokenRecord(params){const now=Date.now();return{token:generateRefreshTokenValue(),clientId:params.clientId,userSub:params.userSub,scopes:params.scopes,resource:params.resource,createdAt:now,expiresAt:now+REFRESH_TOKEN_TTL_MS,revoked:false,userEmail:params.userEmail,userName:params.userName,consentEnabled:params.consentEnabled,selectedToolIds:params.selectedToolIds,authorizedAppIds:params.authorizedAppIds,customClaims:params.customClaims,federatedLoginUsed:params.federatedLoginUsed,selectedProviderIds:params.selectedProviderIds,skippedProviderIds:params.skippedProviderIds,providerTokensId:params.providerTokensId}}function verifyPkce(codeVerifier,challenge){if(challenge.method!=="S256"){return false}const hash=sha256Base64url(codeVerifier);return hash===challenge.challenge}function generatePkceChallenge(codeVerifier){const challenge=sha256Base64url(codeVerifier);return{challenge,method:"S256"}}var InMemoryAuthorizationStore=class{codes=new Map;pending=new Map;refreshTokens=new Map;generateCode(){return generateAuthorizationCode()}generateRefreshToken(){return generateRefreshTokenValue()}async storeAuthorizationCode(record){this.codes.set(record.code,record)}async getAuthorizationCode(code){const record=this.codes.get(code);if(!record)return null;if(Date.now()>record.expiresAt){this.codes.delete(code);return null}return record}async markCodeUsed(code,issuedRefreshToken){const record=this.codes.get(code);if(record){record.used=true;if(issuedRefreshToken)record.issuedRefreshToken=issuedRefreshToken}}async deleteAuthorizationCode(code){this.codes.delete(code)}async storePendingAuthorization(record){this.pending.set(record.id,record)}async getPendingAuthorization(id){const record=this.pending.get(id);if(!record)return null;if(Date.now()>record.expiresAt){this.pending.delete(id);return null}return record}async deletePendingAuthorization(id){this.pending.delete(id)}async storeRefreshToken(record){this.refreshTokens.set(record.token,record)}async getRefreshToken(token){const record=this.refreshTokens.get(token);if(!record)return null;if(Date.now()>record.expiresAt||record.revoked){return null}return record}async revokeRefreshToken(token){const record=this.refreshTokens.get(token);if(record){record.revoked=true}}async rotateRefreshToken(oldToken,newRecord){await this.revokeRefreshToken(oldToken);newRecord.previousToken=oldToken;await this.storeRefreshToken(newRecord)}async cleanup(){const now=Date.now();for(const[code,record]of this.codes){if(now>record.expiresAt){this.codes.delete(code)}}for(const[id,record]of this.pending){if(now>record.expiresAt){this.pending.delete(id)}}for(const[token,record]of this.refreshTokens){if(now>record.expiresAt||record.revoked){this.refreshTokens.delete(token)}}}createCodeRecord(params){return buildCodeRecord(params)}createPendingRecord(params){return buildPendingRecord(params)}createRefreshTokenRecord(params){return buildRefreshTokenRecord(params)}};var RedisAuthorizationStore=class{constructor(redis,namespace="oauth:"){this.redis=redis;this.namespace=namespace}redis;namespace;key(type,id){return`${this.namespace}${type}:${id}`}generateCode(){return generateAuthorizationCode()}generateRefreshToken(){return generateRefreshTokenValue()}createCodeRecord(params){return buildCodeRecord(params)}createPendingRecord(params){return buildPendingRecord(params)}createRefreshTokenRecord(params){return buildRefreshTokenRecord(params)}async storeAuthorizationCode(record){const ttl=Math.max(Math.ceil((record.expiresAt-Date.now())/1e3),1);await this.redis.set(this.key("code",record.code),JSON.stringify(record),"EX",Math.max(ttl,1))}async getAuthorizationCode(code){const data=await this.redis.get(this.key("code",code));if(!data)return null;return JSON.parse(data)}async markCodeUsed(code,issuedRefreshToken){const record=await this.getAuthorizationCode(code);if(record){record.used=true;if(issuedRefreshToken)record.issuedRefreshToken=issuedRefreshToken;const ttl=Math.ceil((record.expiresAt-Date.now())/1e3);await this.redis.set(this.key("code",code),JSON.stringify(record),"EX",Math.max(ttl,1))}}async deleteAuthorizationCode(code){await this.redis.del(this.key("code",code))}async storePendingAuthorization(record){const ttl=Math.max(Math.ceil((record.expiresAt-Date.now())/1e3),1);await this.redis.set(this.key("pending",record.id),JSON.stringify(record),"EX",ttl)}async getPendingAuthorization(id){const data=await this.redis.get(this.key("pending",id));if(!data)return null;return JSON.parse(data)}async deletePendingAuthorization(id){await this.redis.del(this.key("pending",id))}async storeRefreshToken(record){const ttl=Math.ceil((record.expiresAt-Date.now())/1e3);await this.redis.set(this.key("refresh",record.token),JSON.stringify(record),"EX",ttl)}async getRefreshToken(token){const data=await this.redis.get(this.key("refresh",token));if(!data)return null;const record=JSON.parse(data);if(record.revoked)return null;return record}async revokeRefreshToken(token){const record=await this.getRefreshToken(token);if(record){record.revoked=true;const ttl=Math.ceil((record.expiresAt-Date.now())/1e3);await this.redis.set(this.key("refresh",token),JSON.stringify(record),"EX",Math.max(ttl,1))}}async rotateRefreshToken(oldToken,newRecord){await this.revokeRefreshToken(oldToken);newRecord.previousToken=oldToken;await this.storeRefreshToken(newRecord)}async cleanup(){}};import{base64urlDecode,base64urlEncode,decryptAesGcm,encryptAesGcm,hkdfSha256,randomBytes as randomBytes2}from"@frontmcp/utils";var SALT=new TextEncoder().encode("frontmcp-pending-login");function keyFor(secret,pendingAuthId){return hkdfSha256(secret,SALT,new TextEncoder().encode(`pending-login:${pendingAuthId}`),32)}function sealPendingLogin(secret,pendingAuthId,state){const iv=randomBytes2(12);const{ciphertext,tag}=encryptAesGcm(keyFor(secret,pendingAuthId),new TextEncoder().encode(JSON.stringify(state)),iv);return[iv,tag,ciphertext].map(part=>base64urlEncode(part)).join(".")}function openPendingLogin(secret,pendingAuthId,sealed){try{const[iv,tag,ciphertext]=sealed.split(".").map(part=>base64urlDecode(part));if(!iv||!tag||!ciphertext)return void 0;const plaintext=decryptAesGcm(keyFor(secret,pendingAuthId),ciphertext,iv,tag);const state=JSON.parse(new TextDecoder().decode(plaintext));return typeof state?.sub==="string"&&state.sub.length>0?state:void 0}catch{return void 0}}import{base64urlEncode as base64urlEncode2,getCookie,randomBytes as randomBytes3,sha256Hex,timingSafeEqual}from"@frontmcp/utils";var SIGNIN_BINDING_COOKIE_PREFIX="frontmcp_signin_";var SIGNIN_BINDING_SECURE_COOKIE_PREFIX=`__Host-${SIGNIN_BINDING_COOKIE_PREFIX}`;var SIGNIN_BINDING_MAX_AGE_SECONDS=30*60;function signinBindingCookieName(pendingAuthId,options={}){const prefix=options.secure?SIGNIN_BINDING_SECURE_COOKIE_PREFIX:SIGNIN_BINDING_COOKIE_PREFIX;return`${prefix}${sha256Hex(`signin:${pendingAuthId}`).slice(0,16)}`}function createSigninBinding(pendingAuthId,options={}){const value=base64urlEncode2(randomBytes3(32));return{cookieName:signinBindingCookieName(pendingAuthId,options),value,hash:sha256Hex(value)}}function signinBindingMatches(cookieHeader,pendingAuthId,expectedHash,options={}){if(!expectedHash)return false;const value=getCookie(cookieHeader,signinBindingCookieName(pendingAuthId,options));if(!value)return false;const encoder2=new TextEncoder;const actual=encoder2.encode(sha256Hex(value));const expected=encoder2.encode(expectedHash);return actual.length===expected.length&&timingSafeEqual(actual,expected)}import{TypedStorage}from"@frontmcp/utils";var StorageAuthorizationStore=class{codeStore;pendingStore;refreshStore;namespace;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"oauth";this.storageIsNamespaced=this.isNamespacedStorage(storage);const base=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.codeStore=new TypedStorage(base);this.pendingStore=new TypedStorage(base);this.refreshStore=new TypedStorage(base)}generateCode(){return generateAuthorizationCode()}generateRefreshToken(){return generateRefreshTokenValue()}createCodeRecord(params){return buildCodeRecord(params)}createPendingRecord(params){return buildPendingRecord(params)}createRefreshTokenRecord(params){return buildRefreshTokenRecord(params)}async storeAuthorizationCode(record){await this.codeStore.set(this.key("code",record.code),record,this.ttl(record.expiresAt))}async getAuthorizationCode(code){const record=await this.codeStore.get(this.key("code",code));if(!record)return null;if(Date.now()>record.expiresAt){await this.codeStore.delete(this.key("code",code));return null}return record}async markCodeUsed(code,issuedRefreshToken){const record=await this.getAuthorizationCode(code);if(record){record.used=true;if(issuedRefreshToken)record.issuedRefreshToken=issuedRefreshToken;await this.codeStore.set(this.key("code",code),record,this.ttl(record.expiresAt))}}async deleteAuthorizationCode(code){await this.codeStore.delete(this.key("code",code))}async storePendingAuthorization(record){await this.pendingStore.set(this.key("pending",record.id),record,this.ttl(record.expiresAt))}async getPendingAuthorization(id){const record=await this.pendingStore.get(this.key("pending",id));if(!record)return null;if(Date.now()>record.expiresAt){await this.pendingStore.delete(this.key("pending",id));return null}return record}async deletePendingAuthorization(id){await this.pendingStore.delete(this.key("pending",id))}async storeRefreshToken(record){await this.refreshStore.set(this.key("refresh",record.token),record,this.ttl(record.expiresAt))}async getRefreshToken(token){const record=await this.refreshStore.get(this.key("refresh",token));if(!record)return null;if(Date.now()>record.expiresAt||record.revoked){return null}return record}async revokeRefreshToken(token){const record=await this.refreshStore.get(this.key("refresh",token));if(record){record.revoked=true;await this.refreshStore.set(this.key("refresh",token),record,this.ttl(record.expiresAt))}}async rotateRefreshToken(oldToken,newRecord){await this.revokeRefreshToken(oldToken);newRecord.previousToken=oldToken;await this.storeRefreshToken(newRecord)}async cleanup(){}ttl(expiresAtMs){const ttl=Math.ceil((expiresAtMs-Date.now())/1e3);return{ttlSeconds:ttl>0?ttl:1}}key(type,id){const composite=`${type}:${id}`;return this.storageIsNamespaced?composite:`${this.namespace}:${composite}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{internalZ as z2}from"@frontmcp/lazy-zod";var credentialTypeSchema=z2.enum(["oauth","api_key","basic","bearer","private_key","mtls","custom","ssh_key","service_account","oauth_pkce"]);var oauthCredentialSchema=z2.object({type:z2.literal("oauth"),accessToken:z2.string(),refreshToken:z2.string().optional(),tokenType:z2.string().default("Bearer"),expiresAt:z2.number().optional(),scopes:z2.array(z2.string()).default([]),idToken:z2.string().optional()});var apiKeyCredentialSchema=z2.object({type:z2.literal("api_key"),key:z2.string().min(1),headerName:z2.string().default("X-API-Key"),headerPrefix:z2.string().optional(),queryParam:z2.string().optional()});var basicAuthCredentialSchema=z2.object({type:z2.literal("basic"),username:z2.string().min(1),password:z2.string(),encodedValue:z2.string().optional()});var bearerCredentialSchema=z2.object({type:z2.literal("bearer"),token:z2.string().min(1),expiresAt:z2.number().optional()});var privateKeyCredentialSchema=z2.object({type:z2.literal("private_key"),format:z2.enum(["pem","jwk","pkcs8","pkcs12"]),keyData:z2.string(),keyId:z2.string().optional(),algorithm:z2.string().optional(),passphrase:z2.string().optional(),certificate:z2.string().optional()});var mtlsCredentialSchema=z2.object({type:z2.literal("mtls"),certificate:z2.string(),privateKey:z2.string(),passphrase:z2.string().optional(),caCertificate:z2.string().optional()});var customCredentialSchema=z2.object({type:z2.literal("custom"),customType:z2.string().min(1),data:z2.record(z2.string(),z2.unknown()),headers:z2.record(z2.string(),z2.string()).optional()});var sshKeyCredentialSchema=z2.object({type:z2.literal("ssh_key"),privateKey:z2.string().min(1),publicKey:z2.string().optional(),passphrase:z2.string().optional(),keyType:z2.enum(["rsa","ed25519","ecdsa","dsa"]).default("ed25519"),fingerprint:z2.string().optional(),username:z2.string().optional()});var serviceAccountCredentialSchema=z2.object({type:z2.literal("service_account"),provider:z2.enum(["gcp","aws","azure","custom"]),credentials:z2.record(z2.string(),z2.unknown()),projectId:z2.string().optional(),region:z2.string().optional(),assumeRoleArn:z2.string().optional(),externalId:z2.string().optional(),serviceAccountId:z2.string().optional(),expiresAt:z2.number().optional()});var pkceOAuthCredentialSchema=z2.object({type:z2.literal("oauth_pkce"),accessToken:z2.string(),refreshToken:z2.string().optional(),tokenType:z2.string().default("Bearer"),expiresAt:z2.number().optional(),scopes:z2.array(z2.string()).default([]),idToken:z2.string().optional(),issuer:z2.string().optional()});var credentialSchema=z2.discriminatedUnion("type",[oauthCredentialSchema,apiKeyCredentialSchema,basicAuthCredentialSchema,bearerCredentialSchema,privateKeyCredentialSchema,mtlsCredentialSchema,customCredentialSchema,sshKeyCredentialSchema,serviceAccountCredentialSchema,pkceOAuthCredentialSchema]);var appCredentialSchema=z2.object({appId:z2.string().min(1),providerId:z2.string().min(1),credential:credentialSchema,acquiredAt:z2.number(),lastUsedAt:z2.number().optional(),expiresAt:z2.number().optional(),isValid:z2.boolean().default(true),invalidReason:z2.string().optional(),userInfo:z2.object({sub:z2.string().optional(),email:z2.string().optional(),name:z2.string().optional()}).optional(),metadata:z2.record(z2.string(),z2.unknown()).optional()});var vaultConsentRecordSchema=z2.object({enabled:z2.boolean(),selectedToolIds:z2.array(z2.string()),availableToolIds:z2.array(z2.string()),consentedAt:z2.number(),version:z2.string().default("1.0")});var vaultFederatedRecordSchema=z2.object({selectedProviderIds:z2.array(z2.string()),skippedProviderIds:z2.array(z2.string()),primaryProviderId:z2.string().optional(),completedAt:z2.number()});var pendingIncrementalAuthSchema=z2.object({id:z2.string(),appId:z2.string(),toolId:z2.string().optional(),authUrl:z2.string(),requiredScopes:z2.array(z2.string()).optional(),elicitId:z2.string().optional(),createdAt:z2.number(),expiresAt:z2.number(),status:z2.enum(["pending","completed","cancelled","expired"])});var authorizationVaultEntrySchema=z2.object({id:z2.string(),userSub:z2.string(),userEmail:z2.string().optional(),userName:z2.string().optional(),clientId:z2.string(),createdAt:z2.number(),lastAccessAt:z2.number(),appCredentials:z2.record(z2.string(),appCredentialSchema).default({}),consent:vaultConsentRecordSchema.optional(),federated:vaultFederatedRecordSchema.optional(),pendingAuths:z2.array(pendingIncrementalAuthSchema),authorizedAppIds:z2.array(z2.string()),skippedAppIds:z2.array(z2.string())});import{getEnv,randomUUID as randomUUID2}from"@frontmcp/utils";import{z as z3}from"@frontmcp/lazy-zod";import{base64urlDecode as base64urlDecode2,base64urlEncode as base64urlEncode3,decryptAesGcm as decryptAesGcm2,encryptAesGcm as encryptAesGcm2,hkdfSha256 as hkdfSha2562,randomBytes as randomBytes4}from"@frontmcp/utils";var encryptedDataSchema=z3.object({v:z3.literal(1),alg:z3.literal("aes-256-gcm"),iv:z3.string(),ct:z3.string(),tag:z3.string()});var VaultEncryption=class{pepper;hkdfInfo;constructor(config={}){this.pepper=new TextEncoder().encode(config.pepper??"");this.hkdfInfo=config.hkdfInfo??"frontmcp-vault-v1"}async deriveKey(claims){const ikmParts=[claims.jti,claims.vaultKey??"",claims.sub,claims.iat.toString(),new TextDecoder().decode(this.pepper)];const ikm=new TextEncoder().encode(ikmParts.join(""));const infoBytes=new TextEncoder().encode(this.hkdfInfo);const key=await hkdfSha2562(ikm,new Uint8Array(0),infoBytes,32);return key}async deriveKeyFromToken(token,claims){const parts=token.split(".");const signature=parts[2]??"";const ikmParts=[claims.jti,claims.vaultKey??"",claims.sub,claims.iat.toString(),signature,new TextDecoder().decode(this.pepper)];const ikm=new TextEncoder().encode(ikmParts.join(""));const infoBytes=new TextEncoder().encode(this.hkdfInfo);const key=await hkdfSha2562(ikm,new Uint8Array(0),infoBytes,32);return key}async encrypt(plaintext,key){if(key.length!==32){throw new Error("Encryption key must be 32 bytes")}const iv=randomBytes4(12);const{ciphertext,tag}=await encryptAesGcm2(key,new TextEncoder().encode(plaintext),iv);return{v:1,alg:"aes-256-gcm",iv:base64urlEncode3(iv),ct:base64urlEncode3(ciphertext),tag:base64urlEncode3(tag)}}async decrypt(encrypted,key){if(key.length!==32){throw new Error("Encryption key must be 32 bytes")}const parsed=encryptedDataSchema.safeParse(encrypted);if(!parsed.success){throw new Error("Invalid encrypted data format")}const{iv,ct,tag}=parsed.data;const ivBuffer=base64urlDecode2(iv);const ciphertext=base64urlDecode2(ct);const tagBuffer=base64urlDecode2(tag);try{const plaintext=await decryptAesGcm2(key,ciphertext,ivBuffer,tagBuffer);return new TextDecoder().decode(plaintext)}catch(_error){throw new Error("Decryption failed: invalid key or corrupted data")}}async encryptObject(data,key){return this.encrypt(JSON.stringify(data),key)}async decryptObject(encrypted,key){const plaintext=await this.decrypt(encrypted,key);return JSON.parse(plaintext)}isEncrypted(data){return encryptedDataSchema.safeParse(data).success}};var encryptedVaultEntrySchema=z3.object({id:z3.string(),userSub:z3.string(),userEmail:z3.string().optional(),userName:z3.string().optional(),clientId:z3.string(),createdAt:z3.number(),lastAccessAt:z3.number(),encryptedData:encryptedDataSchema,authorizedAppIds:z3.array(z3.string()),skippedAppIds:z3.array(z3.string()),pendingAuthIds:z3.array(z3.string()).default([])});var DEFAULT_KEY_PREFIX="mcp:cred:";var DEFAULT_PENDING_TTL_MS=6e5;var SessionCredentialVault=class{storage;encryption;keyPrefix;ttlMs;pendingTtlMs;logger;keyCache=new Map;constructor(options){this.storage=options.storage;this.keyPrefix=options.keyPrefix??DEFAULT_KEY_PREFIX;this.ttlMs=options.ttlMs;this.pendingTtlMs=options.pendingTtlMs??DEFAULT_PENDING_TTL_MS;this.logger=options.logger??noopAuthLogger;const pepper=options.pepper??resolveVaultPepper(this.logger);this.encryption=new VaultEncryption({pepper,hkdfInfo:"frontmcp-session-credential-vault-v1"})}indexKey(sub){return`${this.keyPrefix}idx:${sub}`}keysKey(sub,vaultId){return`${this.keyPrefix}keys:${sub}:${vaultId}`}dataKey(sub,vaultId,key){return`${this.keyPrefix}data:${sub}:${vaultId}:${key}`}pendingKey(pendingId){return`${this.keyPrefix}pending:${pendingId}`}get credentialSetOpts(){return this.ttlMs?{ttlSeconds:Math.ceil(this.ttlMs/1e3)}:void 0}deriveKey(sub,vaultId){const cacheKey=`${sub}:${vaultId}`;const cached=this.keyCache.get(cacheKey);if(cached)return cached;const promise=this.encryption.deriveKey({jti:vaultId,vaultKey:vaultId,sub,iat:0});this.keyCache.set(cacheKey,promise);return promise}async getVaultId(sub){const raw=await this.storage.get(this.indexKey(sub));return raw??void 0}async rotateVault(sub){const vaultId=randomUUID2();await this.storage.set(this.indexKey(sub),vaultId,this.credentialSetOpts);return vaultId}async ensureVault(sub){const currentVaultId=await this.getVaultId(sub);if(currentVaultId)return currentVaultId;const createdVaultId=randomUUID2();await this.storage.set(this.indexKey(sub),createdVaultId,{...this.credentialSetOpts,ifNotExists:true});return await this.getVaultId(sub)??createdVaultId}async store(sub,vaultId,key,value){const aesKey=await this.deriveKey(sub,vaultId);const enc=await this.encryption.encryptObject(this.normalize(value),aesKey);const record={enc};await this.storage.set(this.dataKey(sub,vaultId,key),JSON.stringify(record),this.credentialSetOpts);await this.addKeyToSet(sub,vaultId,key)}async get(sub,key){const vaultId=await this.getVaultId(sub);if(!vaultId)return void 0;const raw=await this.storage.get(this.dataKey(sub,vaultId,key));if(!raw)return void 0;try{const record=JSON.parse(raw);const aesKey=await this.deriveKey(sub,vaultId);return await this.encryption.decryptObject(record.enc,aesKey)}catch(err){this.logger.warn(`SessionCredentialVault: failed to read credential "${key}": ${errMsg(err)}`);return void 0}}async list(sub){const vaultId=await this.getVaultId(sub);if(!vaultId)return[];return this.readKeySet(sub,vaultId)}async remove(sub,key){const vaultId=await this.getVaultId(sub);if(!vaultId)return;await this.storage.delete(this.dataKey(sub,vaultId,key));const keys=await this.readKeySet(sub,vaultId);const next=keys.filter(k=>k!==key);if(next.length!==keys.length){await this.writeKeySet(sub,vaultId,next)}}async stage(pendingId,sub,credentials){const vaultId=await this.rotateVault(sub);const creds={};for(const cred of credentials){const aesKey=await this.deriveKey(sub,vaultId);const{key,...value}=cred;creds[key]={enc:await this.encryption.encryptObject(this.normalize(value),aesKey)}}const batch={sub,vaultId,creds};await this.storage.set(this.pendingKey(pendingId),JSON.stringify(batch),{ttlSeconds:Math.ceil(this.pendingTtlMs/1e3)});return vaultId}async commit(pendingId,sub){const raw=await this.storage.get(this.pendingKey(pendingId));if(!raw)return false;let batch;try{batch=JSON.parse(raw)}catch{await this.storage.delete(this.pendingKey(pendingId));return false}if(batch.sub!==sub){this.logger.warn("SessionCredentialVault: pending batch subject mismatch on commit; discarding");await this.storage.delete(this.pendingKey(pendingId));return false}await this.storage.set(this.indexKey(sub),batch.vaultId,this.credentialSetOpts);const keys=[];for(const[key,record]of Object.entries(batch.creds)){await this.storage.set(this.dataKey(sub,batch.vaultId,key),JSON.stringify(record),this.credentialSetOpts);keys.push(key)}await this.writeKeySet(sub,batch.vaultId,keys);await this.storage.delete(this.pendingKey(pendingId));return true}async readKeySet(sub,vaultId){const raw=await this.storage.get(this.keysKey(sub,vaultId));if(!raw)return[];try{const parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter(k=>typeof k==="string"):[]}catch{return[]}}async writeKeySet(sub,vaultId,keys){const unique=[...new Set(keys)];await this.storage.set(this.keysKey(sub,vaultId),JSON.stringify(unique),this.credentialSetOpts)}async addKeyToSet(sub,vaultId,key){const keys=await this.readKeySet(sub,vaultId);if(!keys.includes(key)){keys.push(key);await this.writeKeySet(sub,vaultId,keys)}}normalize(value){return value.metadata===void 0?{secret:value.secret}:{secret:value.secret,metadata:value.metadata}}};function resolveVaultPepper(logger){const fromEnv=getEnv("VAULT_SECRET")??getEnv("JWT_SECRET");if(fromEnv&&fromEnv.trim().length>0)return fromEnv;logger.warn("SessionCredentialVault: neither VAULT_SECRET nor JWT_SECRET is set; using a random per-process pepper. Stored credentials will NOT survive a restart and cannot be shared across instances. Set VAULT_SECRET (or JWT_SECRET) for persistent, multi-instance credential storage.");return randomUUID2()+randomUUID2()}function errMsg(err){return err instanceof Error?err.message:String(err)}import{MemoryStorageAdapter,RedisStorageAdapter}from"@frontmcp/utils";function isRedisTokenStorage(config){return typeof config==="object"&&config!==null&&"redis"in config&&!!config.redis}function isSqliteTokenStorage(config){return typeof config==="object"&&config!==null&&"sqlite"in config&&!!config.sqlite}function isPersistentTokenStorage(config){return config!==void 0&&config!=="memory"}async function createTokenStorageAdapter(config){if(config===void 0||config==="memory"){const adapter=new MemoryStorageAdapter;await adapter.connect();return adapter}if(isSqliteTokenStorage(config)){const{SqliteStorageAdapter}=__require("@frontmcp/storage-sqlite");const adapter=new SqliteStorageAdapter({path:config.sqlite.path,encryption:config.sqlite.encryption,ttlCleanupIntervalMs:config.sqlite.ttlCleanupIntervalMs??6e4,walMode:config.sqlite.walMode??true,busyTimeoutMs:config.sqlite.busyTimeoutMs});await adapter.connect();return adapter}if(isRedisTokenStorage(config)){const{host,port,password,db,tls,keyPrefix}=config.redis;const adapter=new RedisStorageAdapter({config:{host,port,password,db,tls},keyPrefix:keyPrefix??""});await adapter.connect();return adapter}const fallback=new MemoryStorageAdapter;await fallback.connect();return fallback}async function createSessionCredentialVault(options){const storage=options.storage??await createTokenStorageAdapter(options.tokenStorage);const vault=new SessionCredentialVault({storage,pepper:options.pepper,keyPrefix:options.keyPrefix,ttlMs:options.ttlMs,logger:options.logger});const type=options.tokenStorage===void 0||options.tokenStorage==="memory"?"memory":"sqlite"in options.tokenStorage?"sqlite":"redis";return{vault,type}}import{signData,verifyData}from"@frontmcp/utils";var DEFAULT_RESUME_TTL_MS=6e5;function signCredentialResumeToken(input,secret){const payload={sub:input.sub,key:input.key,...input.context!==void 0?{context:input.context}:{},exp:Date.now()+(input.ttlMs??DEFAULT_RESUME_TTL_MS)};const signedJson=signData(payload,{secret});return Buffer.from(signedJson,"utf8").toString("base64url")}function verifyCredentialResumeToken(token,secret,now=Date.now()){let signedJson;try{signedJson=Buffer.from(token,"base64url").toString("utf8")}catch{return null}const payload=verifyData(signedJson,{secret});if(!payload)return null;if(typeof payload.sub!=="string"||typeof payload.key!=="string"||!Number.isFinite(payload.exp)){return null}if(payload.context!==void 0&&typeof payload.context!=="string"){return null}if(now>=payload.exp){return null}return payload}function buildCredentialResumeUrl(basePath,token){const trimmed=basePath.endsWith("/")?basePath.slice(0,-1):basePath;return`${trimmed}/oauth/connect?token=${encodeURIComponent(token)}`}import{randomUUID as randomUUID3,signData as signData2,verifyData as verifyData2}from"@frontmcp/utils";var DEFAULT_INCREMENTAL_TICKET_TTL_MS=3e5;function signIncrementalAuthTicket(input,secret){const payload={sub:input.sub,appId:input.appId,...input.toolId!==void 0?{toolId:input.toolId}:{},...input.priorAppIds!==void 0?{priorAppIds:input.priorAppIds}:{},jti:randomUUID3(),exp:Date.now()+(input.ttlMs??DEFAULT_INCREMENTAL_TICKET_TTL_MS)};const signedJson=signData2(payload,{secret});return Buffer.from(signedJson,"utf8").toString("base64url")}function verifyIncrementalAuthTicket(token,secret,now=Date.now()){let signedJson;try{signedJson=Buffer.from(token,"base64url").toString("utf8")}catch{return null}const payload=verifyData2(signedJson,{secret});if(!payload)return null;if(typeof payload.sub!=="string"||payload.sub.length===0)return null;if(typeof payload.appId!=="string"||payload.appId.length===0)return null;if(typeof payload.jti!=="string"||payload.jti.length===0)return null;if(!Number.isFinite(payload.exp))return null;if(payload.toolId!==void 0&&typeof payload.toolId!=="string")return null;if(payload.priorAppIds!==void 0&&(!Array.isArray(payload.priorAppIds)||payload.priorAppIds.some(id=>typeof id!=="string"))){return null}if(now>=payload.exp){return null}return payload}var CREDENTIALS_ACCESSOR=Symbol.for("frontmcp:CREDENTIALS_ACCESSOR");var CredentialsAccessorImpl=class{vault;resolveSub;signingSecret;basePath;resumeTtlMs;logger;constructor(deps){this.vault=deps.vault;this.resolveSub=deps.resolveSub;this.signingSecret=deps.signingSecret;this.basePath=deps.basePath;this.resumeTtlMs=deps.resumeTtlMs;this.logger=deps.logger??noopAuthLogger}async get(key){const sub=this.resolveSub();if(!sub)return void 0;return this.vault.get(sub,key)}async list(){const sub=this.resolveSub();if(!sub)return[];return this.vault.list(sub)}async requireConnect(options){const{key,context}=options;const sub=this.resolveSub();if(!sub){return{connected:false,key,resumeUrl:"",signInRequired:true,message:`Sign in to this server to connect credential "${key}".`}}const existing=await this.vault.get(sub,key);if(existing){return{connected:true,key,credential:existing}}const token=signCredentialResumeToken({sub,key,context,ttlMs:this.resumeTtlMs},this.signingSecret);const resumeUrl=buildCredentialResumeUrl(this.basePath,token);this.logger.debug(`CredentialsAccessor: credential "${key}" not connected; issued resume link`);return{connected:false,key,resumeUrl,message:`Credential "${key}" is not connected. Open the resume URL to connect it.`}}};var DEFAULT_KEY_PREFIX2="mcp:secret:";var EncryptedStorageSecureStoreBackend=class{storage;encryption;keyPrefix;logger;keyCache=new Map;constructor(options){this.storage=options.storage;this.keyPrefix=options.keyPrefix??DEFAULT_KEY_PREFIX2;this.logger=options.logger??noopAuthLogger;this.encryption=new VaultEncryption({pepper:options.pepper,hkdfInfo:"frontmcp-secure-store-v1"})}keysKey(namespace){return`${this.keyPrefix}keys:${namespace}`}dataKey(namespace,key){return`${this.keyPrefix}data:${namespace}:${key}`}deriveKey(namespace){const cached=this.keyCache.get(namespace);if(cached)return cached;const promise=this.encryption.deriveKey({jti:namespace,vaultKey:namespace,sub:namespace,iat:0});this.keyCache.set(namespace,promise);return promise}setOpts(ttlMs){return ttlMs&&ttlMs>0?{ttlSeconds:Math.ceil(ttlMs/1e3)}:void 0}async get(namespace,key){const raw=await this.storage.get(this.dataKey(namespace,key));if(!raw)return null;try{const record=JSON.parse(raw);const aesKey=await this.deriveKey(namespace);return await this.encryption.decrypt(record.enc,aesKey)}catch(err){this.logger.warn(`SecureStore: failed to read secret "${key}": ${errMsg2(err)}`);return null}}async set(namespace,key,value,ttlMs){const aesKey=await this.deriveKey(namespace);const enc=await this.encryption.encrypt(value,aesKey);const record={enc};const opts=this.setOpts(ttlMs);await this.storage.set(this.dataKey(namespace,key),JSON.stringify(record),opts);await this.addKeyToSet(namespace,key,opts)}async delete(namespace,key){const existed=await this.storage.delete(this.dataKey(namespace,key));const keys=await this.readKeySet(namespace);const next=keys.filter(k=>k!==key);if(next.length!==keys.length){await this.writeKeySet(namespace,next)}return existed}async list(namespace){return this.readKeySet(namespace)}async readKeySet(namespace){const raw=await this.storage.get(this.keysKey(namespace));if(!raw)return[];try{const parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter(k=>typeof k==="string"):[]}catch{return[]}}async writeKeySet(namespace,keys,opts){const unique=[...new Set(keys)];await this.storage.set(this.keysKey(namespace),JSON.stringify(unique),opts)}async addKeyToSet(namespace,key,opts){const keys=await this.readKeySet(namespace);if(!keys.includes(key)){keys.push(key);await this.writeKeySet(namespace,keys,opts)}}};function errMsg2(err){return err instanceof Error?err.message:String(err)}var SECURE_STORE_ACCESSOR=Symbol.for("frontmcp:SECURE_STORE_ACCESSOR");import{sha256Hex as sha256Hex2}from"@frontmcp/utils";var GLOBAL_NAMESPACE="global";var SecureStoreAccessorImpl=class{backend;scope;resolveSub;resolveSessionId;ttlMs;logger;constructor(deps){this.backend=deps.backend;this.scope=deps.scope??"user";this.resolveSub=deps.resolveSub;this.resolveSessionId=deps.resolveSessionId;this.ttlMs=deps.ttlMs;this.logger=deps.logger??noopAuthLogger}resolveNamespace(){switch(this.scope){case"global":return GLOBAL_NAMESPACE;case"session":{const sid=this.resolveSessionId();return sid?`s:${sha256Hex2(sid).slice(0,32)}`:void 0}case"user":default:{const sub=this.resolveSub();return sub?`u:${sha256Hex2(sub).slice(0,32)}`:void 0}}}async get(key){const namespace=this.resolveNamespace();if(!namespace)return void 0;const raw=await this.backend.get(namespace,key);if(raw===null)return void 0;try{return JSON.parse(raw)}catch(err){this.logger.warn(`SecureStore: failed to parse secret "${key}": ${errMsg3(err)}`);return void 0}}async set(key,value,options){const namespace=this.resolveNamespace();if(!namespace){this.logger.debug(`SecureStore: no ${this.scope} scope for request; skipping set("${key}")`);return}const ttlMs=options?.ttlMs??this.ttlMs;await this.backend.set(namespace,key,JSON.stringify(value),ttlMs)}async delete(key){const namespace=this.resolveNamespace();if(!namespace)return false;return this.backend.delete(namespace,key)}async list(){const namespace=this.resolveNamespace();if(!namespace)return[];return this.backend.list(namespace)}};function errMsg3(err){return err instanceof Error?err.message:String(err)}import{MemoryStorageAdapter as MemoryStorageAdapter2}from"@frontmcp/utils";import{randomBytes as randomBytes5,bytesToHex as bytesToHex2}from"@frontmcp/utils";var AuthInternalError=class extends Error{errorId;isPublic=false;statusCode=500;code;constructor(message,code="AUTH_INTERNAL_ERROR"){super(message);this.name=this.constructor.name;this.code=code;this.errorId=`err_${bytesToHex2(randomBytes5(8))}`;Error.captureStackTrace(this,this.constructor)}getPublicMessage(){return`Internal auth error. Please contact support with error ID: ${this.errorId}`}getInternalMessage(){return this.message}};var EncryptionContextNotSetError=class extends AuthInternalError{constructor(){super("Encryption context is not set","ENCRYPTION_CONTEXT_NOT_SET")}};var VaultLoadError=class extends AuthInternalError{originalError;constructor(vaultId,originalError){super(`Failed to load vault "${vaultId}"${originalError?`: ${originalError.message}`:""}`,"VAULT_LOAD_ERROR");this.originalError=originalError}};var VaultNotFoundError=class extends AuthInternalError{constructor(entityType,id){super(`${entityType} "${id}" not found in vault`,"VAULT_NOT_FOUND")}};var TokenNotAvailableError=class extends AuthInternalError{constructor(message){super(message,"TOKEN_NOT_AVAILABLE")}};var TokenStoreRequiredError=class extends AuthInternalError{constructor(context){super(`Token store is required for ${context}`,"TOKEN_STORE_REQUIRED")}};var NoProviderIdError=class extends AuthInternalError{constructor(message){super(message,"NO_PROVIDER_ID")}};var TokenLeakDetectedError=class extends AuthInternalError{constructor(detail){super(`Token leak detected: ${detail}`,"TOKEN_LEAK_DETECTED")}};var SessionSecretRequiredError=class extends AuthInternalError{constructor(component){super(`Session secret is required for ${component}`,"SESSION_SECRET_REQUIRED")}};var JwtSecretRequiredError=class extends AuthInternalError{constructor(mode){super(`JWT_SECRET is required in production for auth.mode "${mode}". Without it the server signs tokens with a random per-process secret, so every restart invalidates outstanding tokens and any second instance or isolate rejects tokens the first one minted.`,"JWT_SECRET_REQUIRED")}};var JwtSecretWeakError=class extends AuthInternalError{constructor(byteLength){super(`JWT_SECRET is ${byteLength} bytes; HS256 requires at least 32 (RFC 7518 \xA73.2). A shorter secret is guessable, and whoever guesses it can forge tokens for this server. Generate one with \`openssl rand -hex 32\`.`,"JWT_SECRET_INVALID")}};var CredentialProviderAlreadyRegisteredError=class extends AuthInternalError{constructor(name){super(`Credential provider "${name}" is already registered`,"CREDENTIAL_PROVIDER_ALREADY_REGISTERED")}};var AuthProvidersNotConfiguredError=class extends AuthInternalError{constructor(providerName){const register="Register it in @FrontMcp({ authProvidersVault: { providers: [...] } }).";super(providerName===void 0?"Auth providers are not configured: this server registers no auth providers.":`Auth provider "${providerName}" is not configured: this server registers no auth providers. ${register}`,"AUTH_PROVIDERS_NOT_CONFIGURED")}};var OrchestratedAuthNotAvailableError=class extends AuthInternalError{constructor(){super("Orchestrated auth is not available","ORCHESTRATED_AUTH_NOT_AVAILABLE")}};var EncryptionKeyNotConfiguredError=class extends AuthInternalError{constructor(){super("Encryption key is not configured","ENCRYPTION_KEY_NOT_CONFIGURED")}};var SessionIdEmptyError=class extends AuthInternalError{constructor(storeName){super(`Session ID must not be empty (${storeName})`,"SESSION_ID_EMPTY")}};var ElicitationSecretRequiredError=class extends AuthInternalError{constructor(){super("Elicitation secret is required","ELICITATION_SECRET_REQUIRED")}};var ScopeDeniedError=class extends AuthInternalError{constructor(providerId){super(`Scope access denied for provider "${providerId}"`,"SCOPE_DENIED")}};var InMemoryStoreRequiredError=class extends AuthInternalError{constructor(component){super(`In-memory store is required for ${component}`,"INMEMORY_STORE_REQUIRED")}};var OrchestratorJwksNotAvailableError=class extends AuthInternalError{constructor(){super("Orchestrator JWKS is not available","ORCHESTRATOR_JWKS_NOT_AVAILABLE")}};var SecureStoreConfigError=class extends AuthInternalError{constructor(message){super(`Invalid auth.secureStore: ${message}`,"SECURE_STORE_CONFIG_INVALID")}};var AuthInvalidInputError=class extends AuthInternalError{constructor(message){super(message,"AUTH_INVALID_INPUT")}};var CredentialStorageError=class extends AuthInternalError{constructor(message){super(message,"CREDENTIAL_STORAGE_ERROR")}};var AuthFlowError=class extends AuthInternalError{constructor(message,code="AUTH_FLOW_ERROR"){super(message,code)}};import{internalZ as z7}from"@frontmcp/lazy-zod";import{internalZ as z4}from"@frontmcp/lazy-zod";var jwkParametersSchema=z4.object({kty:z4.string().optional(),alg:z4.string().optional(),key_ops:z4.array(z4.string()).optional(),ext:z4.boolean().optional(),use:z4.string().optional(),x5c:z4.array(z4.string()).optional(),x5t:z4.string().optional(),"x5t#S256":z4.string().optional(),x5u:z4.string().optional(),kid:z4.string().optional()});var jwkSchema=jwkParametersSchema.extend({crv:z4.string().optional(),d:z4.string().optional(),dp:z4.string().optional(),dq:z4.string().optional(),e:z4.string().optional(),k:z4.string().optional(),n:z4.string().optional(),p:z4.string().optional(),q:z4.string().optional(),qi:z4.string().optional(),x:z4.string().optional(),y:z4.string().optional(),pub:z4.string().optional(),priv:z4.string().optional()});var jsonWebKeySetSchema=z4.object({keys:z4.array(jwkSchema)});import{internalZ as z5}from"@frontmcp/lazy-zod";var transportProtocolSchema=z5.enum(["legacy-sse","sse","streamable-http","stateful-http","stateless-http"]);var sseTransportStateSchema=z5.object({type:z5.literal("sse"),lastEventId:z5.string().optional(),lastPing:z5.number().optional(),connectionState:z5.enum(["connecting","open","closed"]).optional()});var streamableHttpTransportStateSchema=z5.object({type:z5.literal("streamable-http"),requestSeq:z5.number(),activeStreamId:z5.string().optional(),pendingRequests:z5.array(z5.string()).optional()});var statefulHttpTransportStateSchema=z5.object({type:z5.literal("stateful-http"),requestSeq:z5.number(),pendingResponses:z5.array(z5.string()).optional(),lastActivity:z5.number().optional()});var statelessHttpTransportStateSchema=z5.object({type:z5.literal("stateless-http"),requestCount:z5.number(),windowStart:z5.number().optional()});var legacySseTransportStateSchema=z5.object({type:z5.literal("legacy-sse"),messagePath:z5.string(),lastEventId:z5.string().optional(),connectionState:z5.enum(["connecting","open","closed"]).optional()});var transportStateSchema=z5.discriminatedUnion("type",[sseTransportStateSchema,streamableHttpTransportStateSchema,statefulHttpTransportStateSchema,statelessHttpTransportStateSchema,legacySseTransportStateSchema]);var transportSessionSchema=z5.object({id:z5.string(),authorizationId:z5.string(),protocol:transportProtocolSchema,createdAt:z5.number(),expiresAt:z5.number().optional(),nodeId:z5.string(),clientFingerprint:z5.string().optional(),transportState:transportStateSchema.optional()});var sessionJwtPayloadSchema=z5.object({sid:z5.string(),aid:z5.string(),proto:transportProtocolSchema,nid:z5.string(),iat:z5.number(),exp:z5.number().optional()});var encryptedBlobSchema=z5.object({alg:z5.literal("A256GCM"),kid:z5.string().optional(),iv:z5.string(),tag:z5.string(),data:z5.string(),exp:z5.number().optional(),meta:z5.record(z5.string(),z5.unknown()).optional()});var storedSessionSchema=z5.object({session:transportSessionSchema,authorizationId:z5.string(),tokens:z5.record(z5.string(),encryptedBlobSchema).optional(),createdAt:z5.number(),lastAccessedAt:z5.number(),initialized:z5.boolean().optional(),maxLifetimeAt:z5.number().optional(),clientCapabilities:z5.record(z5.string(),z5.unknown()).optional(),reassignedAt:z5.number().optional(),reassignedFrom:z5.string().optional()});var redisConfigSchema=z5.object({host:z5.string().min(1),port:z5.number().int().positive().optional().default(6379),password:z5.string().optional(),db:z5.number().int().nonnegative().optional().default(0),tls:z5.boolean().optional().default(false),keyPrefix:z5.string().optional().default("mcp:session:"),defaultTtlMs:z5.number().int().positive().optional().default(36e5)});var noopLogger={child:()=>noopLogger,debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}};import{internalZ as z6}from"@frontmcp/lazy-zod";var clientMetadataDocumentSchema=z6.object({client_id:z6.string().url(),client_name:z6.string().min(1),redirect_uris:z6.array(z6.string().url()).min(1),token_endpoint_auth_method:z6.enum(["none","client_secret_basic","client_secret_post","private_key_jwt"]).default("none"),grant_types:z6.array(z6.string()).default(["authorization_code"]),response_types:z6.array(z6.string()).default(["code"]),client_uri:z6.string().url().optional(),logo_uri:z6.string().url().optional(),jwks_uri:z6.string().url().optional(),jwks:z6.object({keys:z6.array(z6.record(z6.string(),z6.unknown()))}).optional(),tos_uri:z6.string().url().optional(),policy_uri:z6.string().url().optional(),scope:z6.string().optional(),contacts:z6.array(z6.string().email()).optional(),software_statement:z6.string().optional(),software_id:z6.string().optional(),software_version:z6.string().optional()});var cimdRedisCacheConfigSchema=z6.object({url:z6.string().optional(),host:z6.string().optional(),port:z6.number().optional(),password:z6.string().optional(),db:z6.number().optional(),tls:z6.boolean().optional(),keyPrefix:z6.string().default("cimd:")});var cimdCacheConfigSchema=z6.object({type:z6.enum(["memory","redis"]).default("memory"),defaultTtlMs:z6.number().min(0).default(36e5),maxTtlMs:z6.number().min(0).default(864e5),minTtlMs:z6.number().min(0).default(6e4),redis:cimdRedisCacheConfigSchema.optional()});var cimdSecurityConfigSchema=z6.object({blockPrivateIPs:z6.boolean().default(true),allowedDomains:z6.array(z6.string()).optional(),blockedDomains:z6.array(z6.string()).optional(),warnOnLocalhostRedirects:z6.boolean().default(true),allowInsecureForTesting:z6.boolean().default(false)});var cimdNetworkConfigSchema=z6.object({timeoutMs:z6.number().min(100).default(5e3),maxResponseSizeBytes:z6.number().min(1024).default(65536),redirectPolicy:z6.enum(["deny","same-origin","allow"]).default("deny"),maxRedirects:z6.number().int().min(0).default(5)});var cimdConfigSchema=z6.object({enabled:z6.boolean().default(true),cache:cimdCacheConfigSchema.optional(),security:cimdSecurityConfigSchema.optional(),network:cimdNetworkConfigSchema.optional()});init_cimd_cache();init_cimd_cache();import{isRedirectResponse as isRedirectResponse2}from"@frontmcp/utils";var CimdService=class{config;cacheConfig;securityConfig;networkConfig;cacheBackend;logger;get enabled(){return this.config.enabled}constructor(logger,config){this.logger=(logger??noopLogger).child("CimdService");this.config=cimdConfigSchema.parse(config??{});this.cacheConfig=cimdCacheConfigSchema.parse(this.config.cache??{});this.securityConfig=cimdSecurityConfigSchema.parse(this.config.security??{});this.networkConfig=cimdNetworkConfigSchema.parse(this.config.network??{});this.logger.debug("CimdService initialized",{enabled:this.config.enabled,cacheDefaultTtlMs:this.cacheConfig.defaultTtlMs,networkTimeoutMs:this.networkConfig.timeoutMs});if(this.securityConfig.allowInsecureForTesting){this.logger.warn("CIMD allowInsecureForTesting is enabled. HTTP is allowed for localhost CIMD URLs. This should NEVER be enabled in production!")}}async initialize(){await this.cache()}cache(){if(!this.cacheBackend){this.cacheBackend=createCimdCache(this.cacheConfig).catch(error=>{this.cacheBackend=void 0;throw error})}return this.cacheBackend}async dispose(){const backend=this.cacheBackend;this.cacheBackend=void 0;if(!backend)return;const createdBackend=await backend.catch(()=>void 0);await createdBackend?.close?.()}isCimdClientId(clientId){return isCimdClientId(clientId,this.securityConfig.allowInsecureForTesting)}async resolveClientMetadata(clientId){if(!this.isCimdClientId(clientId)){return{isCimdClient:false,fromCache:false}}validateClientIdUrl(clientId,this.securityConfig);const cache=await this.cache();const cached=await cache.get(clientId);if(cached){this.logger.debug(`Cache hit for CIMD client: ${clientId}`);return{isCimdClient:true,metadata:cached.document,fromCache:true,expiresAt:cached.expiresAt,etag:cached.etag,lastModified:cached.lastModified}}this.logger.info(`Fetching CIMD document: ${clientId}`);const{document,headers}=await this.fetchMetadataDocument(clientId);this.validateDocument(clientId,document);if(this.securityConfig.warnOnLocalhostRedirects&&hasOnlyLocalhostRedirectUris(document.redirect_uris)){this.logger.warn(`CIMD client "${clientId}" has only localhost redirect URIs - this may be a development client`)}await cache.set(clientId,document,headers);const entry=await cache.get(clientId);return{isCimdClient:true,metadata:document,fromCache:false,expiresAt:entry?.expiresAt,etag:entry?.etag,lastModified:entry?.lastModified}}validateRedirectUri(redirectUri,metadata){const normalizedRequestUri=normalizeRedirectUri(redirectUri);const normalizedAllowed=metadata.redirect_uris.map(normalizeRedirectUri);if(!normalizedAllowed.includes(normalizedRequestUri)){throw new RedirectUriMismatchError(metadata.client_id,redirectUri,metadata.redirect_uris)}}async clearCache(clientId){const cache=await this.cache();if(clientId){await cache.delete(clientId);this.logger.debug(`Cache cleared for: ${clientId}`)}else{await cache.clear();this.logger.debug("Cache cleared")}}async getCacheStats(){const cache=await this.cache();return{size:await cache.size()}}async fetchMetadataDocument(clientId){const controller=new AbortController;const timer=setTimeout(()=>controller.abort(),this.networkConfig.timeoutMs);try{const cache=await this.cache();const conditionalHeaders=await cache.getConditionalHeaders(clientId);const originalOrigin=new URL(clientId).origin;const maxRedirects=this.networkConfig.maxRedirects;let currentUrl=clientId;let redirectCount=0;let isFirstRequest=true;while(true){if(this.securityConfig.blockPrivateIPs&&!this.securityConfig.allowInsecureForTesting){await assertHostNotSsrf(new URL(currentUrl).hostname,clientId)}const headers={Accept:"application/json"};if(isFirstRequest&&conditionalHeaders){Object.assign(headers,conditionalHeaders)}const response=await fetch(currentUrl,{method:"GET",headers,signal:controller.signal,redirect:"manual"});if(response.status===304){const staleEntry=await cache.getStale(clientId);if(staleEntry){await cache.revalidate(clientId,response.headers);this.logger.debug(`CIMD document not modified: ${clientId}`);return{document:staleEntry.document,headers:response.headers}}throw new CimdFetchError(clientId,"304 Not Modified but no cached entry",{httpStatus:304})}if(isRedirectResponse2(response)){const location=response.headers.get("location");if(!location){throw new CimdFetchError(clientId,"Redirect response missing Location header",{httpStatus:response.status})}const nextUrl=new URL(location,currentUrl).toString();const redirectPolicy=this.networkConfig.redirectPolicy;if(redirectPolicy==="deny"){throw new CimdFetchError(clientId,`CIMD fetch redirected to "${nextUrl}" but redirects are disabled. Host the metadata at the client_id URL or set auth.cimd.network.redirectPolicy to "same-origin" or "allow".`,{httpStatus:response.status})}if(redirectPolicy==="same-origin"){const nextOrigin=new URL(nextUrl).origin;if(nextOrigin!==originalOrigin){throw new CimdFetchError(clientId,`CIMD fetch redirected to "${nextUrl}" which is not the same origin as "${originalOrigin}". Host the metadata at the client_id URL or set auth.cimd.network.redirectPolicy to "allow".`,{httpStatus:response.status})}}validateClientIdUrl(nextUrl,this.securityConfig);redirectCount+=1;if(redirectCount>maxRedirects){throw new CimdFetchError(clientId,`CIMD fetch exceeded max redirects (${maxRedirects}). Host the metadata at the client_id URL or increase auth.cimd.network.maxRedirects.`)}this.logger.warn(`CIMD redirect ${redirectCount}/${maxRedirects}: ${currentUrl} -> ${nextUrl}`);currentUrl=nextUrl;isFirstRequest=false;continue}if(!response.ok){throw new CimdFetchError(clientId,`HTTP ${response.status} ${response.statusText}`,{httpStatus:response.status})}const contentLength=response.headers.get("content-length");if(contentLength){const length=parseInt(contentLength,10);if(!isNaN(length)&&length>this.networkConfig.maxResponseSizeBytes){throw new CimdResponseTooLargeError(clientId,this.networkConfig.maxResponseSizeBytes,length)}}const text=await this.readResponseWithLimit(response,this.networkConfig.maxResponseSizeBytes,clientId);let document;try{document=JSON.parse(text)}catch(e){throw new CimdFetchError(clientId,"Invalid JSON response",{originalError:e instanceof Error?e:new Error(String(e))})}return{document,headers:response.headers}}}catch(error){if(error instanceof CimdFetchError||error instanceof CimdResponseTooLargeError||error instanceof CimdSecurityError){throw error}if(error instanceof Error&&error.name==="AbortError"){throw new CimdFetchError(clientId,"Request timeout",{originalError:error})}throw new CimdFetchError(clientId,error instanceof Error?error.message:"Unknown error",{originalError:error instanceof Error?error:void 0})}finally{clearTimeout(timer)}}async readResponseWithLimit(response,maxBytes,clientId){const reader=response.body?.getReader();if(!reader){const buffer=await response.arrayBuffer();if(buffer.byteLength>maxBytes){throw new CimdResponseTooLargeError(clientId,maxBytes,buffer.byteLength)}return new TextDecoder().decode(buffer)}const chunks=[];let totalBytes=0;try{while(true){const{done,value}=await reader.read();if(done)break;totalBytes+=value.length;if(totalBytes>maxBytes){throw new CimdResponseTooLargeError(clientId,maxBytes,totalBytes)}chunks.push(value)}}finally{reader.releaseLock()}const combined=new Uint8Array(totalBytes);let offset=0;for(const chunk of chunks){combined.set(chunk,offset);offset+=chunk.length}return new TextDecoder().decode(combined)}validateDocument(clientId,document){const result=clientMetadataDocumentSchema.safeParse(document);if(!result.success){const errors=result.error.issues.map(issue=>{const path=issue.path.length>0?`${issue.path.join(".")}: `:"";return`${path}${issue.message}`});throw new CimdValidationError(clientId,errors)}if(result.data.client_id!==clientId){throw new CimdClientIdMismatchError(clientId,result.data.client_id)}}};var LOOPBACK_HOSTNAMES=new Set(["127.0.0.1","::1","[::1]","localhost"]);function isLoopbackHostname(hostname){return LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())}function normalizeRedirectUri(uri){try{const url=new URL(uri);const host=isLoopbackHostname(url.hostname)?url.hostname.toLowerCase():url.host.toLowerCase();let normalized=`${url.protocol.toLowerCase()}//${host}`;normalized+=url.pathname.replace(/\/+$/,"")||"/";if(url.search)normalized+=url.search;return normalized}catch{return uri}}var publicAccessConfigSchema=z7.object({tools:z7.union([z7.literal("all"),z7.array(z7.string())]).default("all"),prompts:z7.union([z7.literal("all"),z7.array(z7.string())]).default("all"),rateLimit:z7.number().default(60)});var localSigningConfigSchema=z7.object({signKey:jwkSchema.or(z7.instanceof(Uint8Array)).optional(),jwks:jsonWebKeySetSchema.optional(),issuer:z7.string().optional()});var providerConfigSchema=z7.object({name:z7.string().optional(),id:z7.string().optional(),jwks:jsonWebKeySetSchema.optional(),jwksUri:z7.string().url().optional(),additionalIssuers:z7.array(z7.string().trim().min(1,"additionalIssuers entries must be non-empty")).optional(),verifyIssuer:z7.boolean().optional(),dcrEnabled:z7.boolean().default(false),authEndpoint:z7.string().url().optional(),tokenEndpoint:z7.string().url().optional(),registrationEndpoint:z7.string().url().optional(),userInfoEndpoint:z7.string().url().optional()});var remoteProviderConfigSchema=z7.object({provider:z7.string().url(),name:z7.string().optional(),id:z7.string().optional(),jwks:jsonWebKeySetSchema.optional(),jwksUri:z7.string().url().optional(),clientId:z7.string().optional(),clientSecret:z7.string().optional(),scopes:z7.array(z7.string()).optional(),dcrEnabled:z7.boolean().default(false),authEndpoint:z7.string().url().optional(),tokenEndpoint:z7.string().url().optional(),registrationEndpoint:z7.string().url().optional(),userInfoEndpoint:z7.string().url().optional()});var upstreamProviderSchema=z7.object({id:z7.string().min(1),name:z7.string().optional(),authorizationEndpoint:z7.string().url().optional(),authorizeUrl:z7.string().url().optional(),tokenEndpoint:z7.string().url().optional(),tokenUrl:z7.string().url().optional(),clientId:z7.string().min(1),clientSecret:z7.string().optional(),scopes:z7.array(z7.string()).optional(),userInfoEndpoint:z7.string().url().optional(),jwksUri:z7.string().url().optional(),issuer:z7.string().url().optional(),additionalIssuers:z7.array(z7.string().trim().min(1,"additionalIssuers entries must be non-empty")).optional()}).refine(p=>!!(p.authorizationEndpoint??p.authorizeUrl),{message:"authorizationEndpoint (or authorizeUrl) is required",path:["authorizationEndpoint"]}).refine(p=>!!(p.tokenEndpoint??p.tokenUrl),{message:"tokenEndpoint (or tokenUrl) is required",path:["tokenEndpoint"]}).refine(p=>!(p.authorizationEndpoint&&p.authorizeUrl),{message:"provide only one of authorizationEndpoint or authorizeUrl",path:["authorizationEndpoint"]}).refine(p=>!(p.tokenEndpoint&&p.tokenUrl),{message:"provide only one of tokenEndpoint or tokenUrl",path:["tokenEndpoint"]});var localDcrClientSchema=z7.object({clientId:z7.string().min(1),clientSecret:z7.string().min(1).optional(),redirectUris:z7.array(z7.string().url()).min(1,"At least one redirect_uri is required"),clientName:z7.string().optional(),tokenEndpointAuthMethod:z7.enum(["none","client_secret_basic","client_secret_post"]).default("none"),grantTypes:z7.array(z7.enum(["authorization_code","refresh_token"])).default(["authorization_code"]),responseTypes:z7.array(z7.enum(["code"])).default(["code"]),scope:z7.string().optional()});var localDcrConfigSchema=z7.object({enabled:z7.boolean().optional(),allowedRedirectUris:z7.array(z7.string().min(1)).optional(),allowedClientIds:z7.array(z7.string().min(1)).optional(),initialAccessToken:z7.string().min(1).optional(),clients:z7.array(localDcrClientSchema).optional(),maxDynamicClients:z7.number().int().min(0).optional()});var flatRemoteProviderFields={provider:z7.string().url(),clientId:z7.string().optional(),clientSecret:z7.string().optional(),scopes:z7.array(z7.string()).optional(),providerConfig:providerConfigSchema.optional()};var tokenStorageSqliteSchema=z7.object({path:z7.string().min(1),encryption:z7.object({secret:z7.string().min(1)}).optional(),ttlCleanupIntervalMs:z7.number().int().nonnegative().optional(),walMode:z7.boolean().optional(),busyTimeoutMs:z7.number().int().nonnegative().optional()});var tokenStorageConfigSchema=z7.union([z7.literal("memory"),z7.object({redis:redisConfigSchema}),z7.object({sqlite:tokenStorageSqliteSchema})]);var secureStoreScopeSchema=z7.enum(["user","session","global"]);var secureStoreEncryptionSchema=z7.object({pepper:z7.string().min(1).optional()});var secureStoreCustomBackendSchema=z7.custom(v=>typeof v==="object"&&v!==null&&typeof v.get==="function"&&typeof v.set==="function"&&typeof v.delete==="function"&&typeof v.list==="function",{message:"secureStore.backend must implement get/set/delete/list (SecureStoreBackend)"});var secureStoreCommonFields={scope:secureStoreScopeSchema.optional(),ttlMs:z7.number().int().positive().optional(),encryption:secureStoreEncryptionSchema.optional()};var secureStoreBackingSchemas={sqlite:tokenStorageSqliteSchema,redis:redisConfigSchema,backend:secureStoreCustomBackendSchema};function secureStoreBackingError(issue){if(typeof issue.input!=="object"||issue.input===null)return void 0;const config=issue.input;const named=Object.keys(secureStoreBackingSchemas).filter(backing=>config[backing]!==void 0);if(named.length>1)return`secureStore takes one backing, not ${named.join(" and ")}`;for(const[backing,schema2]of Object.entries(secureStoreBackingSchemas)){if(config[backing]===void 0)continue;const nested=schema2.safeParse(config[backing]).error?.issues[0];if(!nested)continue;const where=["secureStore",backing,...nested.path.map(String)].join(".");return nested.message.startsWith(where)?nested.message:`${where}: ${nested.message}`}return void 0}var noBacking={sqlite:z7.undefined().optional(),redis:z7.undefined().optional(),backend:z7.undefined().optional()};var secureStoreConfigSchema=z7.union([z7.literal("memory"),z7.object({...noBacking,sqlite:secureStoreBackingSchemas.sqlite,...secureStoreCommonFields}),z7.object({...noBacking,redis:secureStoreBackingSchemas.redis,...secureStoreCommonFields}),z7.object({...noBacking,backend:secureStoreBackingSchemas.backend,...secureStoreCommonFields}),z7.object({...noBacking,...secureStoreCommonFields})],{error:secureStoreBackingError});var tokenRefreshConfigSchema=z7.object({enabled:z7.boolean().default(true),skewSeconds:z7.number().nonnegative().default(60)});var skippedAppBehaviorSchema=z7.enum(["anonymous","require-auth"]);var consentConfigSchema=z7.object({enabled:z7.boolean().default(false),groupByApp:z7.boolean().default(true),showDescriptions:z7.boolean().default(true),allowSelectAll:z7.boolean().default(true),requireSelection:z7.boolean().default(true),customMessage:z7.string().optional(),rememberConsent:z7.boolean().default(true),excludedTools:z7.array(z7.string()).optional(),defaultSelectedTools:z7.array(z7.string()).optional()});var federatedAuthConfigSchema=z7.object({stateValidation:z7.enum(["strict","format"]).default("strict"),minProviders:z7.number().int().positive().optional(),requiredProviders:z7.array(z7.string()).optional()});var incrementalAuthConfigSchema=z7.object({enabled:z7.boolean().default(true),skippedAppBehavior:skippedAppBehaviorSchema.default("anonymous"),allowSkip:z7.boolean().default(true),showAllAppsAtOnce:z7.boolean().default(true)});function isCustomBackendConfig(config){return typeof config==="object"&&config!==null&&"backend"in config&&!!config.backend}function isSqliteConfig(config){return typeof config==="object"&&config!==null&&"sqlite"in config&&!!config.sqlite}function isRedisConfig(config){return typeof config==="object"&&config!==null&&"redis"in config&&!!config.redis}function readScope(config){if(typeof config==="object"&&config!==null&&"scope"in config&&config.scope){return config.scope}return"user"}function readTtlMs(config){if(typeof config==="object"&&config!==null&&"ttlMs"in config){return config.ttlMs}return void 0}function readPepper(config){if(typeof config==="object"&&config!==null&&"encryption"in config&&config.encryption){return config.encryption.pepper}return void 0}function toTokenStorageConfig(config){if(isSqliteConfig(config)){return{sqlite:config.sqlite}}if(isRedisConfig(config)){return{redis:config.redis}}return"memory"}function refuseUnusableConfig(config){if(config===void 0)return;const issue=secureStoreConfigSchema.safeParse(config).error?.issues[0];if(!issue)return;const where=issue.path.length>0?`secureStore.${issue.path.map(String).join(".")}: `:"";throw new SecureStoreConfigError(`${where}${issue.message}`)}async function createSecureStore(options){const{config,pepper,storage,logger}=options;refuseUnusableConfig(config);const scope=readScope(config);const ttlMs=readTtlMs(config);if(isCustomBackendConfig(config)){return{backend:config.backend,scope,ttlMs,kind:"custom"}}const kind=isSqliteConfig(config)?"sqlite":isRedisConfig(config)?"redis":"memory";let adapter;if(storage){adapter=storage}else if(kind==="memory"){adapter=new MemoryStorageAdapter2;await adapter.connect()}else{adapter=await createTokenStorageAdapter(toTokenStorageConfig(config))}const backend=new EncryptedStorageSecureStoreBackend({storage:adapter,pepper:readPepper(config)??pepper,logger});return{backend,scope,ttlMs,kind,...storage?{}:{close:()=>adapter.disconnect()}}}import{encryptAesGcm as encryptAesGcm3,decryptAesGcm as decryptAesGcm3,randomBytes as randomBytes6,base64urlEncode as base64urlEncode4,base64urlDecode as base64urlDecode3}from"@frontmcp/utils";var TokenVault=class{active;keys=new Map;constructor(keys){if(!Array.isArray(keys)||keys.length===0){throw new Error("TokenVault requires at least one key")}for(const k of keys){if(!(k.key instanceof Uint8Array)||k.key.length!==32){throw new Error(`TokenVault key "${k.kid}" must be a 32-byte Uint8Array for AES-256-GCM`)}if(this.keys.has(k.kid)){throw new Error(`TokenVault duplicate kid: "${k.kid}"`)}this.keys.set(k.kid,k.key)}this.active=keys[0]}rotateTo(k){if(!(k.key instanceof Uint8Array)||k.key.length!==32){throw new Error(`TokenVault key "${k.kid}" must be a 32-byte Uint8Array for AES-256-GCM`)}this.active=k;this.keys.set(k.kid,k.key)}async encrypt(plaintext,opts){const iv=randomBytes6(12);const{ciphertext,tag}=await encryptAesGcm3(this.active.key,new TextEncoder().encode(plaintext),iv);return{alg:"A256GCM",kid:this.active.kid,iv:base64urlEncode4(iv),tag:base64urlEncode4(tag),data:base64urlEncode4(ciphertext),exp:opts?.exp,meta:opts?.meta}}async decrypt(blob){if(blob.exp!==void 0){const nowSeconds=Math.floor(Date.now()/1e3);if(nowSeconds>blob.exp){throw new Error(`vault_expired:${blob.kid}`)}}const key=this.keys.get(blob.kid);if(!key)throw new Error(`vault_unknown_kid:${blob.kid}`);const iv=base64urlDecode3(blob.iv);const tag=base64urlDecode3(blob.tag);const data=base64urlDecode3(blob.data);const plaintext=await decryptAesGcm3(key,data,iv,tag);return new TextDecoder().decode(plaintext)}};import{hkdfSha256 as hkdfSha2565,encryptValue as encryptValue2,decryptValue as decryptValue2,encryptAesGcm as encryptAesGcm6,decryptAesGcm as decryptAesGcm6}from"@frontmcp/utils";import{signData as signData3,verifyData as verifyData3,isSignedData,verifyOrParseData,getEnv as getEnv2,isProduction as isProduction2}from"@frontmcp/utils";function getSigningSecret(config){const secret=config?.secret||getEnv2("MCP_SESSION_SECRET");if(!secret){if(isProduction2()){throw new SessionSecretRequiredError("session signing")}console.warn("[SessionCrypto] MCP_SESSION_SECRET not set. Using insecure default for development only.");return"insecure-dev-secret-do-not-use-in-production"}return secret}function resolveConfig(config){return{secret:getSigningSecret(config)}}function signSession(session,config){return signData3(session,resolveConfig(config))}function verifySession(signedData,config){return verifyData3(signedData,resolveConfig(config))}function verifyOrParseSession(data,config){return verifyOrParseData(data,resolveConfig(config))}var SessionRateLimiter=class{windowMs;maxRequests;requests=new Map;cleanupIntervalMs;cleanupTimer=null;constructor(config={}){this.windowMs=config.windowMs??6e4;this.maxRequests=config.maxRequests??100;this.cleanupIntervalMs=config.cleanupIntervalMs??6e4}ensureCleanupTimer(){if(this.cleanupTimer||this.cleanupIntervalMs<=0)return;this.cleanupTimer=setInterval(()=>this.cleanup(),this.cleanupIntervalMs);if(typeof this.cleanupTimer.unref==="function")this.cleanupTimer.unref()}check(key){this.ensureCleanupTimer();const now=Date.now();const windowStart=now-this.windowMs;let timestamps=this.requests.get(key);if(!timestamps){timestamps=[];this.requests.set(key,timestamps)}const validTimestamps=timestamps.filter(t=>t>windowStart);const oldestInWindow=validTimestamps[0]??now;const resetAt=oldestInWindow+this.windowMs;if(validTimestamps.length>=this.maxRequests){return{allowed:false,remaining:0,resetAt,retryAfterMs:resetAt-now}}validTimestamps.push(now);this.requests.set(key,validTimestamps);return{allowed:true,remaining:this.maxRequests-validTimestamps.length,resetAt}}wouldAllow(key){const now=Date.now();const windowStart=now-this.windowMs;const timestamps=this.requests.get(key);if(!timestamps)return true;const validCount=timestamps.filter(t=>t>windowStart).length;return validCount<this.maxRequests}reset(key){this.requests.delete(key)}cleanup(){const now=Date.now();const windowStart=now-this.windowMs;for(const[key,timestamps]of this.requests.entries()){const valid=timestamps.filter(t=>t>windowStart);if(valid.length===0){this.requests.delete(key)}else if(valid.length<timestamps.length){this.requests.set(key,valid)}}}getStats(){let totalRequests=0;for(const timestamps of this.requests.values()){totalRequests+=timestamps.length}return{totalKeys:this.requests.size,totalRequests}}dispose(){if(this.cleanupTimer){clearInterval(this.cleanupTimer);this.cleanupTimer=null}this.requests.clear()}};var defaultSessionRateLimiter=new SessionRateLimiter;import{randomUUID as randomUUID4}from"@frontmcp/utils";var TransportIdGenerator=class{static createId(){return randomUUID4().replace(/-/g,"")}};var TinyTtlCache=class{constructor(ttlMs){this.ttlMs=ttlMs}ttlMs;map=new Map;get(k){const hit=this.map.get(k);if(!hit)return void 0;if(hit.exp<Date.now()){this.map.delete(k);return void 0}return hit.v}set(k,v){this.map.set(k,{v,exp:Date.now()+this.ttlMs})}delete(k){return this.map.delete(k)}clear(){this.map.clear()}size(){return this.map.size}cleanup(){const now=Date.now();let removed=0;for(const[k,{exp}]of this.map){if(exp<now){this.map.delete(k);removed++}}return removed}};import{sha256Base64url as sha256Base64url2}from"@frontmcp/utils";function isJwt(token){if(!token)return false;return token.split(".").length===3}function getTokenSignatureFingerprint(token){if(isJwt(token)){const sig=token.split(".")[2];if(sig)return sig}return sha256Base64url2(token)}function extractClaimValue(claims,key,validator){const value=claims[key];return validator(value)?value:void 0}var isString=value=>typeof value==="string";var isNumber=value=>typeof value==="number";var isStringOrStringArray=value=>typeof value==="string"||Array.isArray(value);function deriveTypedUser(claims){return{...claims,iss:extractClaimValue(claims,"iss",isString)??"",sid:extractClaimValue(claims,"sid",isString),sub:extractClaimValue(claims,"sub",isString)??extractClaimValue(claims,"client_id",isString)??extractClaimValue(claims,"azp",isString)??"",exp:extractClaimValue(claims,"exp",isNumber),iat:extractClaimValue(claims,"iat",isNumber),aud:extractClaimValue(claims,"aud",isStringOrStringArray),email:extractClaimValue(claims,"email",isString),preferred_username:extractClaimValue(claims,"preferred_username",isString),username:extractClaimValue(claims,"username",isString),name:extractClaimValue(claims,"name",isString),picture:extractClaimValue(claims,"picture",isString)}}function anonymousCallerClaims(options,anonymousId){const{issuer,scopes=["anonymous"],subject}=options;return{sub:subject??`anon:${anonymousId}`,iss:issuer,name:subject?"Static token":"Anonymous",scope:scopes.join(" ")}}function extractBearerToken(header){if(!header)return void 0;const m=header.match(/^\s*Bearer\s+(\S+)\s*$/i);return m?m[1]:void 0}import{decryptValue,encryptValue,getEnv as getEnv3,getMachineId,isProduction as isProduction3,sha256}from"@frontmcp/utils";var cachedKey=null;function getKey(){if(cachedKey)return cachedKey;const secret=getEnv3("MCP_SESSION_SECRET");if(!secret){if(isProduction3()){throw new SessionSecretRequiredError("session ID encryption")}console.warn("[SessionIdUtils] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. Set MCP_SESSION_SECRET environment variable for secure session encryption.")}const base=secret||getMachineId();cachedKey=sha256(new TextEncoder().encode(base));return cachedKey}function encryptJson(obj){const key=getKey();const encrypted=encryptValue(obj,key);return`${encrypted.iv}.${encrypted.tag}.${encrypted.data}`}function decryptSessionJson(sessionId){const parts=sessionId.split(".");if(parts.length!==3)return null;const[ivB64,tagB64,ctB64]=parts;if(!ivB64||!tagB64||!ctB64)return null;const key=getKey();return decryptValue({alg:"A256GCM",iv:ivB64,tag:tagB64,data:ctB64},key)}function safeDecrypt(sessionId){try{return decryptSessionJson(sessionId)}catch{return null}}function resetCachedKey(){cachedKey=null}import{TypedStorage as TypedStorage4}from"@frontmcp/utils";import{EncryptedTypedStorage,EncryptedStorageError}from"@frontmcp/utils";import{randomUUID as randomUUID5,TypedStorage as TypedStorage2}from"@frontmcp/utils";var StorageTokenStore=class{storage;namespace;defaultTtlSeconds;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"tok";this.defaultTtlSeconds=options.defaultTtlSeconds;this.storageIsNamespaced="namespace"in storage&&typeof storage.namespace==="function";const namespacedStorage=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.storage=new TypedStorage2(namespacedStorage)}allocId(){return randomUUID5()}async put(id,blob){const record={id,blob,updatedAt:Date.now()};const ttlSeconds=this.calculateTtl(blob.exp);await this.storage.set(this.key(id),record,ttlSeconds?{ttlSeconds}:void 0)}async get(id){const record=await this.storage.get(this.key(id));return record??void 0}async del(id){await this.storage.delete(this.key(id))}calculateTtl(exp){if(exp){const nowSeconds=Math.floor(Date.now()/1e3);const ttl=exp-nowSeconds;return ttl>0?ttl:1}return this.defaultTtlSeconds}key(id){return this.storageIsNamespaced?id:`${this.namespace}:${id}`}};import{randomUUID as randomUUID6,TypedStorage as TypedStorage3}from"@frontmcp/utils";var StorageAuthorizationVault=class{storage;namespace;pendingAuthTtlMs;constructor(storage,options={}){this.namespace=options.namespace??"vault";this.pendingAuthTtlMs=options.pendingAuthTtlMs??10*60*1e3;const namespacedStorage=this.isNamespacedStorage(storage)?storage.namespace(this.namespace):storage;this.storage=new TypedStorage3(namespacedStorage,{schema:options.validateOnRead?authorizationVaultEntrySchema:void 0,throwOnInvalid:false})}async create(params){const now=Date.now();const entry={id:randomUUID6(),userSub:params.userSub,userEmail:params.userEmail,userName:params.userName,clientId:params.clientId,createdAt:now,lastAccessAt:now,appCredentials:{},consent:params.consent,federated:params.federated,pendingAuths:[],authorizedAppIds:params.authorizedAppIds??[],skippedAppIds:params.skippedAppIds??[]};await this.storage.set(this.key(entry.id),entry);return entry}async get(id){return this.storage.get(this.key(id))}async update(id,updates){const entry=await this.get(id);if(!entry){console.warn(`[StorageAuthorizationVault] Update failed: vault entry not found for id ${id}`);return}const updated={...entry,...updates,lastAccessAt:Date.now()};await this.storage.set(this.key(id),updated)}async delete(id){await this.storage.delete(this.key(id))}async updateConsent(vaultId,consent){const entry=await this.get(vaultId);if(!entry)return;entry.consent=consent;entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async authorizeApp(vaultId,appId){const entry=await this.get(vaultId);if(!entry)return;entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==appId);if(!entry.authorizedAppIds.includes(appId)){entry.authorizedAppIds.push(appId)}entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async isAppAuthorized(vaultId,appId){const entry=await this.get(vaultId);if(!entry)return false;return entry.authorizedAppIds.includes(appId)}async createPendingAuth(vaultId,params){const entry=await this.get(vaultId);if(!entry){throw new Error(`Vault not found: ${vaultId}`)}const now=Date.now();const pendingAuth={id:randomUUID6(),appId:params.appId,toolId:params.toolId,authUrl:params.authUrl,requiredScopes:params.requiredScopes,elicitId:params.elicitId,createdAt:now,expiresAt:now+(params.ttlMs??this.pendingAuthTtlMs),status:"pending"};entry.pendingAuths.push(pendingAuth);entry.lastAccessAt=now;await this.storage.set(this.key(vaultId),entry);return pendingAuth}async getPendingAuth(vaultId,pendingAuthId){const entry=await this.get(vaultId);if(!entry)return null;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(!pendingAuth)return null;if(Date.now()>pendingAuth.expiresAt&&pendingAuth.status==="pending"){pendingAuth.status="expired";await this.storage.set(this.key(vaultId),entry)}return pendingAuth}async completePendingAuth(vaultId,pendingAuthId){const entry=await this.get(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="completed";entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==pendingAuth.appId);if(!entry.authorizedAppIds.includes(pendingAuth.appId)){entry.authorizedAppIds.push(pendingAuth.appId)}entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}}async cancelPendingAuth(vaultId,pendingAuthId){const entry=await this.get(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="cancelled";await this.storage.set(this.key(vaultId),entry)}}async getPendingAuths(vaultId){const entry=await this.get(vaultId);if(!entry)return[];const now=Date.now();let updated=false;const pending=entry.pendingAuths.filter(p=>{if(now>p.expiresAt&&p.status==="pending"){p.status="expired";updated=true}return p.status==="pending"});if(updated){await this.storage.set(this.key(vaultId),entry)}return pending}credentialKey(appId,providerId){return`${appId}:${providerId}`}async addAppCredential(vaultId,credential){const entry=await this.get(vaultId);if(!entry)return;const shouldStore=await this.shouldStoreCredential(vaultId,credential.appId);if(!shouldStore){return}const key=this.credentialKey(credential.appId,credential.providerId);entry.appCredentials[key]=credential;entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async removeAppCredential(vaultId,appId,providerId){const entry=await this.get(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);delete entry.appCredentials[key];entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async getAppCredentials(vaultId,appId){const entry=await this.get(vaultId);if(!entry)return[];const prefix=`${appId}:`;return Object.entries(entry.appCredentials).filter(([key])=>key.startsWith(prefix)).map(([,cred])=>cred)}async getCredential(vaultId,appId,providerId){const entry=await this.get(vaultId);if(!entry)return null;const key=this.credentialKey(appId,providerId);return entry.appCredentials[key]??null}async getAllCredentials(vaultId,filterByConsent=false){const entry=await this.get(vaultId);if(!entry)return[];const allCredentials=Object.values(entry.appCredentials);if(!filterByConsent||!entry.consent?.enabled){return allCredentials}const consentedToolIds=new Set(entry.consent.selectedToolIds);return allCredentials.filter(cred=>{return Array.from(consentedToolIds).some(toolId=>toolId.startsWith(`${cred.appId}:`))})}async updateCredential(vaultId,appId,providerId,updates){const entry=await this.get(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential)return;Object.assign(credential,updates);entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async shouldStoreCredential(vaultId,appId,toolIds){const entry=await this.get(vaultId);if(!entry)return false;const consent=entry.consent;if(!consent?.enabled){return true}if(toolIds&&toolIds.length>0){return toolIds.some(toolId=>consent.selectedToolIds.includes(toolId))}const consentedToolIds=consent.selectedToolIds;return consentedToolIds.some(toolId=>toolId.startsWith(`${appId}:`))}async invalidateCredential(vaultId,appId,providerId,reason){await this.updateCredential(vaultId,appId,providerId,{isValid:false,invalidReason:reason})}async refreshOAuthCredential(vaultId,appId,providerId,tokens){const entry=await this.get(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential||credential.credential.type!=="oauth"&&credential.credential.type!=="oauth_pkce")return;credential.credential.accessToken=tokens.accessToken;if(tokens.refreshToken!==void 0){credential.credential.refreshToken=tokens.refreshToken}if(tokens.expiresAt!==void 0){credential.credential.expiresAt=tokens.expiresAt;credential.expiresAt=tokens.expiresAt}credential.isValid=true;credential.invalidReason=void 0;entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async cleanup(){const keys=await this.storage.keys("*");const now=Date.now();for(const key of keys){const entry=await this.storage.get(key);if(!entry)continue;const originalLength=entry.pendingAuths.length;entry.pendingAuths=entry.pendingAuths.filter(p=>{if(now>p.expiresAt&&p.status==="pending"){p.status="expired"}return p.status==="pending"});if(entry.pendingAuths.length!==originalLength){await this.storage.set(key,entry)}}}key(id){return this.isNamespacedStorage(this.storage.raw)?id:`${this.namespace}:${id}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{MemoryStorageAdapter as MemoryStorageAdapter3}from"@frontmcp/utils";var InMemoryAuthorizationVault=class extends StorageAuthorizationVault{memoryAdapter;constructor(options={}){const memoryAdapter=new MemoryStorageAdapter3;super(memoryAdapter,{namespace:options.namespace??"vault",pendingAuthTtlMs:options.pendingAuthTtlMs});this.memoryAdapter=memoryAdapter;void this.memoryAdapter.connect()}async clear(){const keys=await this.memoryAdapter.keys("*");for(const key of keys){await this.memoryAdapter.delete(key)}}};import{randomUUID as randomUUID7,RedisStorageAdapter as RedisStorageAdapter3}from"@frontmcp/utils";var RedisSessionStore=class{storage;keyPrefix;defaultTtlMs;logger;externalInstance=false;security;rateLimiter;constructor(config,logger){this.defaultTtlMs=("defaultTtlMs"in config?config.defaultTtlMs:void 0)??36e5;this.logger=logger;this.keyPrefix=("keyPrefix"in config?config.keyPrefix:void 0)??"mcp:session:";this.security=("security"in config?config.security:void 0)??{};if(this.security.enableRateLimiting){this.rateLimiter=new SessionRateLimiter({windowMs:this.security.rateLimiting?.windowMs,maxRequests:this.security.rateLimiting?.maxRequests})}if("redis"in config&&config.redis){this.storage=new RedisStorageAdapter3({client:config.redis,keyPrefix:this.keyPrefix});this.externalInstance=true}else{const redisConfig=config;this.storage=new RedisStorageAdapter3({config:{host:redisConfig.host,port:redisConfig.port??6379,password:redisConfig.password,db:redisConfig.db??0,tls:redisConfig.tls},keyPrefix:this.keyPrefix})}}validateSessionId(sessionId){if(!sessionId||sessionId.trim()===""){throw new SessionIdEmptyError("RedisSessionStore")}}async ensureConnected(){await this.storage.connect()}async get(sessionId,options){this.validateSessionId(sessionId);if(this.rateLimiter){const rateLimitKey=options?.clientIdentifier||sessionId;const rateLimitResult=this.rateLimiter.check(rateLimitKey);if(!rateLimitResult.allowed){this.logger?.warn("[RedisSessionStore] Rate limit exceeded for session lookup",{sessionId:sessionId.slice(0,20),clientIdentifier:options?.clientIdentifier?options.clientIdentifier.slice(0,20):void 0,retryAfterMs:rateLimitResult.retryAfterMs});return null}}await this.ensureConnected();let raw;const redis=this.storage.getClient();const ttlSeconds=Math.ceil(this.defaultTtlMs/1e3);if(redis){try{raw=await redis.getex(this.keyPrefix+sessionId,"EX",ttlSeconds)}catch{raw=await this.storage.get(sessionId);if(raw){await this.storage.expire(sessionId,ttlSeconds)}}}else{raw=await this.storage.get(sessionId);if(raw){await this.storage.expire(sessionId,ttlSeconds)}}if(!raw)return null;try{let parsed;if(this.security.enableSigning){parsed=verifyOrParseSession(raw,{secret:this.security.signingSecret});if(!parsed){this.logger?.warn("[RedisSessionStore] Session signature verification failed",{sessionId:sessionId.slice(0,20)});this.delete(sessionId).catch(()=>void 0);return null}}else{parsed=JSON.parse(raw)}const result=storedSessionSchema.safeParse(parsed);if(!result.success){this.logger?.warn("[RedisSessionStore] Invalid session format",{sessionId:sessionId.slice(0,20),errors:result.error.issues.slice(0,3).map(i=>({path:i.path,message:i.message}))});this.delete(sessionId).catch(()=>void 0);return null}const session=result.data;if(session.maxLifetimeAt&&session.maxLifetimeAt<Date.now()){this.logger?.info("[RedisSessionStore] Session exceeded max lifetime",{sessionId:sessionId.slice(0,20),maxLifetimeAt:session.maxLifetimeAt});await this.delete(sessionId);return null}if(session.session.expiresAt&&session.session.expiresAt<Date.now()){await this.delete(sessionId);return null}if(session.session.expiresAt){const ttlMs=Math.min(this.defaultTtlMs,session.session.expiresAt-Date.now());if(ttlMs>0&&ttlMs<this.defaultTtlMs){const boundedTtlSeconds=Math.ceil(ttlMs/1e3);this.storage.expire(sessionId,boundedTtlSeconds).catch(err=>{this.logger?.warn("[RedisSessionStore] TTL extension failed",{sessionId:sessionId.slice(0,20),error:err.message})})}}const updatedSession={...session,lastAccessedAt:Date.now()};return updatedSession}catch(error){this.logger?.warn("[RedisSessionStore] Failed to parse session",{sessionId:sessionId.slice(0,20),error:error.message});this.delete(sessionId).catch(()=>void 0);return null}}async set(sessionId,session,ttlMs){this.validateSessionId(sessionId);await this.ensureConnected();let value;if(this.security.enableSigning){value=signSession(session,{secret:this.security.signingSecret})}else{value=JSON.stringify(session)}if(ttlMs&&ttlMs>0){const ttlSeconds=Math.ceil(ttlMs/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else if(session.session.expiresAt){const ttl=session.session.expiresAt-Date.now();if(ttl>0){const ttlSeconds=Math.ceil(ttl/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else{await this.storage.set(sessionId,value)}}else{await this.storage.set(sessionId,value)}}async delete(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();await this.storage.delete(sessionId)}async exists(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();return this.storage.exists(sessionId)}allocId(){return randomUUID7()}async disconnect(){if(!this.externalInstance){await this.storage.disconnect()}}getRedisClient(){return this.storage.getClient()}async ping(){try{await this.ensureConnected();return this.storage.ping()}catch(error){this.logger?.error("[RedisSessionStore] Connection failed",{error:error.message});return false}}};import{randomUUID as randomUUID8,VercelKvStorageAdapter}from"@frontmcp/utils";var VercelKvSessionStore=class{storage;keyPrefix;defaultTtlMs;logger;security;rateLimiter;constructor(config,logger){this.keyPrefix=config.keyPrefix??"mcp:session:";this.defaultTtlMs=config.defaultTtlMs??36e5;this.logger=logger;this.security=("security"in config?config.security:void 0)??{};if(this.security.enableRateLimiting){this.rateLimiter=new SessionRateLimiter({windowMs:this.security.rateLimiting?.windowMs,maxRequests:this.security.rateLimiting?.maxRequests})}this.storage=new VercelKvStorageAdapter({url:config.url,token:config.token,keyPrefix:this.keyPrefix})}validateSessionId(sessionId){if(!sessionId||sessionId.trim()===""){throw new SessionIdEmptyError("VercelKvSessionStore")}}async connect(){await this.storage.connect()}async ensureConnected(){await this.storage.connect()}async get(sessionId,options){this.validateSessionId(sessionId);if(this.rateLimiter){const rateLimitKey=options?.clientIdentifier||sessionId;const rateLimitResult=this.rateLimiter.check(rateLimitKey);if(!rateLimitResult.allowed){this.logger?.warn("[VercelKvSessionStore] Rate limit exceeded for session lookup",{sessionId:sessionId.slice(0,20),clientIdentifier:options?.clientIdentifier?options.clientIdentifier.slice(0,20):void 0,retryAfterMs:rateLimitResult.retryAfterMs});return null}}await this.ensureConnected();const raw=await this.storage.get(sessionId);if(!raw)return null;const ttlSeconds=Math.ceil(this.defaultTtlMs/1e3);this.storage.expire(sessionId,ttlSeconds).catch(err=>{this.logger?.warn("[VercelKvSessionStore] TTL extension failed",{sessionId:sessionId.slice(0,20),error:err.message})});try{let parsed;if(this.security.enableSigning){parsed=verifyOrParseSession(raw,{secret:this.security.signingSecret});if(!parsed){this.logger?.warn("[VercelKvSessionStore] Session signature verification failed",{sessionId:sessionId.slice(0,20)});this.delete(sessionId).catch(()=>void 0);return null}}else{parsed=JSON.parse(raw)}const result=storedSessionSchema.safeParse(parsed);if(!result.success){this.logger?.warn("[VercelKvSessionStore] Invalid session format",{sessionId:sessionId.slice(0,20),errors:result.error.issues.slice(0,3).map(i=>({path:i.path,message:i.message}))});this.delete(sessionId).catch(()=>void 0);return null}const session=result.data;if(session.maxLifetimeAt&&session.maxLifetimeAt<Date.now()){this.logger?.info("[VercelKvSessionStore] Session exceeded max lifetime",{sessionId:sessionId.slice(0,20),maxLifetimeAt:session.maxLifetimeAt});await this.delete(sessionId);return null}if(session.session.expiresAt&&session.session.expiresAt<Date.now()){await this.delete(sessionId);return null}if(session.session.expiresAt){const ttlMs=Math.min(this.defaultTtlMs,session.session.expiresAt-Date.now());if(ttlMs>0&&ttlMs<this.defaultTtlMs){const boundedTtlSeconds=Math.ceil(ttlMs/1e3);this.storage.expire(sessionId,boundedTtlSeconds).catch(err=>{this.logger?.warn("[VercelKvSessionStore] TTL bound extension failed",{sessionId:sessionId.slice(0,20),error:err.message})})}}const updatedSession={...session,lastAccessedAt:Date.now()};return updatedSession}catch(error){this.logger?.warn("[VercelKvSessionStore] Failed to parse session",{sessionId:sessionId.slice(0,20),error:error.message});this.delete(sessionId).catch(()=>void 0);return null}}async set(sessionId,session,ttlMs){this.validateSessionId(sessionId);await this.ensureConnected();let value;if(this.security.enableSigning){value=signSession(session,{secret:this.security.signingSecret})}else{value=JSON.stringify(session)}if(ttlMs&&ttlMs>0){const ttlSeconds=Math.ceil(ttlMs/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else if(session.session.expiresAt){const ttl=session.session.expiresAt-Date.now();if(ttl>0){const ttlSeconds=Math.ceil(ttl/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else{await this.storage.set(sessionId,value)}}else{await this.storage.set(sessionId,value)}}async delete(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();await this.storage.delete(sessionId)}async exists(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();return this.storage.exists(sessionId)}allocId(){return randomUUID8()}async disconnect(){await this.storage.disconnect()}async ping(){try{await this.ensureConnected();return this.storage.ping()}catch(error){this.logger?.error("[VercelKvSessionStore] Connection failed",{error:error.message});return false}}};import{decryptAesGcm as decryptAesGcm5,encryptAesGcm as encryptAesGcm5,hkdfSha256 as hkdfSha2564,randomBytes as randomBytes8}from"@frontmcp/utils";import{decryptAesGcm as decryptAesGcm4,encryptAesGcm as encryptAesGcm4,hkdfSha256 as hkdfSha2563,randomBytes as randomBytes7}from"@frontmcp/utils";function recordDropAt(record){if(!record.refreshToken)return record.expiresAt;return Math.max(record.expiresAt??0,record.updatedAt+REFRESH_TOKEN_TTL_MS)}function hasLiveAccessToken(record,refreshSkewMs=0){if(record.expiresAt===void 0)return true;return record.expiresAt-(record.refreshToken?refreshSkewMs:0)>Date.now()}var HKDF_SALT=new TextEncoder().encode("frontmcp-token-store");function deriveKeyForRecord(masterKey,compositeKey,cache){const cached=cache.get(compositeKey);if(cached){return cached}const info=new TextEncoder().encode(`orchestrated-token:${compositeKey}`);const derived=hkdfSha2563(masterKey,HKDF_SALT,info,32);cache.set(compositeKey,derived);return derived}function encryptRecord(key,record){const plaintext=JSON.stringify(record);const iv=randomBytes7(12);const{ciphertext,tag}=encryptAesGcm4(key,new TextEncoder().encode(plaintext),iv);return JSON.stringify({iv:Buffer.from(iv).toString("base64url"),tag:Buffer.from(tag).toString("base64url"),data:Buffer.from(ciphertext).toString("base64url")})}function decryptRecord(key,encrypted){const{iv,tag,data}=JSON.parse(encrypted);const ivBytes=Buffer.from(iv,"base64url");const tagBytes=Buffer.from(tag,"base64url");const ciphertextBytes=Buffer.from(data,"base64url");const plaintext=decryptAesGcm4(key,ciphertextBytes,ivBytes,tagBytes);return JSON.parse(new TextDecoder().decode(plaintext))}var InMemoryOrchestratedTokenStore=class{tokens=new Map;encryptionKey;derivedKeys=new Map;cleanupTimer;defaultTtlMs;refreshSkewMs;constructor(options={}){this.encryptionKey=options.encryptionKey;this.defaultTtlMs=options.defaultTtlMs;this.refreshSkewMs=options.refreshSkewMs??0;const cleanupIntervalMs=options.cleanupIntervalMs??6e4;this.cleanupTimer=setInterval(()=>{void this.cleanup()},cleanupIntervalMs);if(this.cleanupTimer.unref){this.cleanupTimer.unref()}}buildKey(authorizationId,providerId){return`${authorizationId}:${providerId}`}async deriveKeyForRecord(compositeKey){if(!this.encryptionKey){throw new EncryptionKeyNotConfiguredError}const cached=this.derivedKeys.get(compositeKey);if(cached){return cached}const info=new TextEncoder().encode(`orchestrated-token:${compositeKey}`);const salt=new TextEncoder().encode("frontmcp-token-store");const derivedKey=hkdfSha2564(this.encryptionKey,salt,info,32);this.derivedKeys.set(compositeKey,derivedKey);return derivedKey}async encryptRecord(compositeKey,record){const key=await this.deriveKeyForRecord(compositeKey);const plaintext=JSON.stringify(record);const iv=randomBytes8(12);const{ciphertext,tag}=encryptAesGcm5(key,new TextEncoder().encode(plaintext),iv);return JSON.stringify({iv:Buffer.from(iv).toString("base64url"),tag:Buffer.from(tag).toString("base64url"),data:Buffer.from(ciphertext).toString("base64url")})}async decryptRecord(compositeKey,encrypted){const key=await this.deriveKeyForRecord(compositeKey);const{iv,tag,data}=JSON.parse(encrypted);const ivBytes=Buffer.from(iv,"base64url");const tagBytes=Buffer.from(tag,"base64url");const ciphertextBytes=Buffer.from(data,"base64url");const plaintext=decryptAesGcm5(key,ciphertextBytes,ivBytes,tagBytes);return JSON.parse(new TextDecoder().decode(plaintext))}async getRecord(authorizationId,providerId){const key=this.buildKey(authorizationId,providerId);const stored=this.tokens.get(key);if(!stored){return null}let record;if(this.encryptionKey){if(typeof stored!=="string"){return null}try{record=await this.decryptRecord(key,stored)}catch{this.tokens.delete(key);return null}}else{record=stored}const dropAt=recordDropAt(record);if(dropAt&&dropAt<Date.now()){this.tokens.delete(key);return null}return record}async getAccessToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record&&hasLiveAccessToken(record,this.refreshSkewMs)?record.accessToken:null}async getRefreshToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record?.refreshToken??null}async storeTokens(authorizationId,providerId,tokens){const key=this.buildKey(authorizationId,providerId);const now=Date.now();const record={accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt:tokens.expiresAt??(this.defaultTtlMs?now+this.defaultTtlMs:void 0),createdAt:now,updatedAt:now};if(this.encryptionKey){const encrypted=await this.encryptRecord(key,record);this.tokens.set(key,encrypted)}else{this.tokens.set(key,record)}}async deleteTokens(authorizationId,providerId){const key=this.buildKey(authorizationId,providerId);this.tokens.delete(key);this.derivedKeys.delete(key)}async hasTokens(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record!==null}async deleteAllForAuthorization(authorizationId){const prefix=`${authorizationId}:`;for(const key of this.tokens.keys()){if(key.startsWith(prefix)){this.tokens.delete(key);this.derivedKeys.delete(key)}}}async getProviderIds(authorizationId){const prefix=`${authorizationId}:`;const providerIds=[];for(const key of this.tokens.keys()){if(key.startsWith(prefix)){const providerId=key.slice(prefix.length);const record=await this.getRecord(authorizationId,providerId);if(record){providerIds.push(providerId)}}}return providerIds}async cleanup(){const now=Date.now();const keysToDelete=[];for(const[key,stored]of this.tokens.entries()){let record=null;if(this.encryptionKey){if(typeof stored==="string"){try{record=await this.decryptRecord(key,stored)}catch{keysToDelete.push(key);continue}}}else{record=stored}const dropAt=record?recordDropAt(record):void 0;if(dropAt&&dropAt<now){keysToDelete.push(key)}}for(const key of keysToDelete){this.tokens.delete(key);this.derivedKeys.delete(key)}}dispose(){if(this.cleanupTimer){clearInterval(this.cleanupTimer);this.cleanupTimer=void 0}}get size(){return this.tokens.size}clear(){this.tokens.clear();this.derivedKeys.clear()}async migrateTokens(fromAuthId,toAuthId){for(const oldKey of await this.copyRecords(fromAuthId,toAuthId)){this.tokens.delete(oldKey);this.derivedKeys.delete(oldKey)}}async copyTokens(fromAuthId,toAuthId){await this.copyRecords(fromAuthId,toAuthId)}async copyRecords(fromAuthId,toAuthId){const prefix=`${fromAuthId}:`;const sourceKeys=[...this.tokens.keys()].filter(key=>key.startsWith(prefix));const copiedKeys=[];for(const oldKey of sourceKeys){const stored=this.tokens.get(oldKey);if(!stored)continue;const newKey=this.buildKey(toAuthId,oldKey.slice(prefix.length));if(this.encryptionKey){try{const record=await this.decryptRecord(oldKey,stored);this.tokens.set(newKey,await this.encryptRecord(newKey,record))}catch{continue}}else{this.tokens.set(newKey,stored)}copiedKeys.push(oldKey)}return copiedKeys}};var StorageOrchestratedTokenStore=class{storage;encryptionKey;defaultTtlMs;refreshSkewMs;namespace;storageIsNamespaced;derivedKeys=new Map;constructor(storage,options={}){this.encryptionKey=options.encryptionKey;this.defaultTtlMs=options.defaultTtlMs;this.refreshSkewMs=options.refreshSkewMs??0;this.namespace=options.namespace??"otok";this.storageIsNamespaced=this.isNamespacedStorage(storage);this.storage=this.storageIsNamespaced?storage.namespace(this.namespace):storage}async getAccessToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record&&hasLiveAccessToken(record,this.refreshSkewMs)?record.accessToken:null}async getRefreshToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record?.refreshToken??null}async storeTokens(authorizationId,providerId,tokens){const compositeKey=this.compositeKey(authorizationId,providerId);const now=Date.now();const record={accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt:tokens.expiresAt??(this.defaultTtlMs?now+this.defaultTtlMs:void 0),createdAt:now,updatedAt:now};const value=this.encryptionKey?encryptRecord(this.keyFor(compositeKey),record):JSON.stringify(record);await this.storage.set(this.storageKey(compositeKey),value,this.ttlOptions(recordDropAt(record)))}async deleteTokens(authorizationId,providerId){const compositeKey=this.compositeKey(authorizationId,providerId);await this.storage.delete(this.storageKey(compositeKey));this.derivedKeys.delete(compositeKey)}async hasTokens(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record!==null}async getProviderIds(authorizationId){const prefix=`${authorizationId}:`;const compositeKeys=await this.listCompositeKeys(`${prefix}*`);const providerIds=[];for(const compositeKey of compositeKeys){if(!compositeKey.startsWith(prefix))continue;const providerId=compositeKey.slice(prefix.length);const record=await this.getRecord(authorizationId,providerId);if(record){providerIds.push(providerId)}}return providerIds}async migrateTokens(fromAuthId,toAuthId){for(const oldComposite of await this.copyRecords(fromAuthId,toAuthId)){await this.storage.delete(this.storageKey(oldComposite));this.derivedKeys.delete(oldComposite)}}async copyTokens(fromAuthId,toAuthId){await this.copyRecords(fromAuthId,toAuthId)}async deleteAllForAuthorization(authorizationId){const prefix=`${authorizationId}:`;const compositeKeys=await this.listCompositeKeys(`${prefix}*`);for(const compositeKey of compositeKeys){if(!compositeKey.startsWith(prefix))continue;await this.storage.delete(this.storageKey(compositeKey));this.derivedKeys.delete(compositeKey)}}async copyRecords(fromAuthId,toAuthId){const prefix=`${fromAuthId}:`;const compositeKeys=await this.listCompositeKeys(`${prefix}*`);const copiedKeys=[];for(const oldComposite of compositeKeys){if(!oldComposite.startsWith(prefix))continue;const newComposite=this.compositeKey(toAuthId,oldComposite.slice(prefix.length));const stored=await this.storage.get(this.storageKey(oldComposite));if(stored===null)continue;if(this.encryptionKey){let record;try{record=decryptRecord(this.keyFor(oldComposite),stored)}catch{continue}const value=encryptRecord(this.keyFor(newComposite),record);await this.storage.set(this.storageKey(newComposite),value,this.ttlOptions(recordDropAt(record)))}else{let dropAt;try{dropAt=recordDropAt(JSON.parse(stored))}catch{dropAt=void 0}await this.storage.set(this.storageKey(newComposite),stored,this.ttlOptions(dropAt))}copiedKeys.push(oldComposite)}return copiedKeys}async getRecord(authorizationId,providerId){const compositeKey=this.compositeKey(authorizationId,providerId);const stored=await this.storage.get(this.storageKey(compositeKey));if(stored===null){return null}let record;if(this.encryptionKey){try{record=decryptRecord(this.keyFor(compositeKey),stored)}catch{await this.storage.delete(this.storageKey(compositeKey));return null}}else{try{record=JSON.parse(stored)}catch{await this.storage.delete(this.storageKey(compositeKey));return null}}const dropAt=recordDropAt(record);if(dropAt&&dropAt<Date.now()){await this.storage.delete(this.storageKey(compositeKey));return null}return record}keyFor(compositeKey){if(!this.encryptionKey){throw new EncryptionKeyNotConfiguredError}return deriveKeyForRecord(this.encryptionKey,compositeKey,this.derivedKeys)}compositeKey(authorizationId,providerId){return`${authorizationId}:${providerId}`}storageKey(compositeKey){return this.storageIsNamespaced?compositeKey:`${this.namespace}:${compositeKey}`}async listCompositeKeys(compositeGlob){const pattern=this.storageIsNamespaced?compositeGlob:`${this.namespace}:${compositeGlob}`;const raw=await this.storage.keys(pattern);if(this.storageIsNamespaced){return raw}const stripPrefix=`${this.namespace}:`;return raw.map(k=>k.startsWith(stripPrefix)?k.slice(stripPrefix.length):k)}ttlOptions(expiresAtMs){if(!expiresAtMs){return void 0}const ttl=Math.ceil((expiresAtMs-Date.now())/1e3);return{ttlSeconds:ttl>0?ttl:1}}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{randomUUID as randomUUID9}from"@frontmcp/utils";function toSessionRecord(session){return{...session,completedProviders:Array.from(session.completedProviders.entries())}}function fromSessionRecord(record){return{...record,completedProviders:new Map(record.completedProviders)}}var InMemoryFederatedAuthSessionStore=class{sessions=new Map;sessionTtlMs=15*60*1e3;cleanupTimer;constructor(){this.cleanupTimer=setInterval(()=>{void this.cleanup()},6e4);if(this.cleanupTimer.unref){this.cleanupTimer.unref()}}async store(session){const record=toSessionRecord(session);this.sessions.set(session.id,record)}async get(id){const record=this.sessions.get(id);if(!record){return null}if(Date.now()>record.expiresAt){this.sessions.delete(id);return null}return fromSessionRecord(record)}async delete(id){this.sessions.delete(id)}async update(session){const record=toSessionRecord(session);this.sessions.set(session.id,record)}async cleanup(){const now=Date.now();for(const[id,record]of this.sessions){if(now>record.expiresAt){this.sessions.delete(id)}}}dispose(){if(this.cleanupTimer){clearInterval(this.cleanupTimer);this.cleanupTimer=void 0}}createSession(params){const now=Date.now();return{id:randomUUID9(),pendingAuthId:params.pendingAuthId,clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,state:params.state,resource:params.resource,userInfo:params.userInfo,frontmcpPkce:params.frontmcpPkce,providerQueue:[...params.providerIds],completedProviders:new Map,skippedProviders:[],signinBinding:params.signinBinding,createdAt:now,expiresAt:now+this.sessionTtlMs}}get size(){return this.sessions.size}clear(){this.sessions.clear()}};function createFederatedAuthSession(params,ttlMs=15*60*1e3){const now=Date.now();return{id:randomUUID9(),pendingAuthId:params.pendingAuthId,clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,state:params.state,resource:params.resource,userInfo:params.userInfo,frontmcpPkce:params.frontmcpPkce,providerQueue:[...params.providerIds],completedProviders:new Map,skippedProviders:[],signinBinding:params.signinBinding,createdAt:now,expiresAt:now+ttlMs}}function isSessionComplete(session){return session.providerQueue.length===0&&!session.currentProviderId}function getNextProvider(session){if(session.currentProviderId){return session.currentProviderId}return session.providerQueue[0]}function completeCurrentProvider(session,tokens,userInfo){if(!session.currentProviderId){throw new AuthFlowError("No current provider to complete","AUTH_FLOW_ERROR")}session.completedProviders.set(session.currentProviderId,{providerId:session.currentProviderId,tokens,userInfo,completedAt:Date.now()});session.currentProviderId=void 0;session.currentProviderPkce=void 0;session.currentProviderState=void 0}function startNextProvider(session,pkce,state){if(session.currentProviderId){throw new AuthFlowError("Cannot start next provider while current is in progress","AUTH_FLOW_ERROR")}if(session.providerQueue.length===0){throw new AuthFlowError("No more providers in queue","AUTH_FLOW_ERROR")}const providerId=session.providerQueue.shift();if(!providerId){throw new AuthFlowError("No more providers in queue","AUTH_FLOW_ERROR")}session.currentProviderId=providerId;session.currentProviderPkce=pkce;session.currentProviderState=state;return providerId}import{TypedStorage as TypedStorage5}from"@frontmcp/utils";var StorageFederatedAuthSessionStore=class{storage;namespace;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"fed";this.storageIsNamespaced=this.isNamespacedStorage(storage);const namespacedStorage=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.storage=new TypedStorage5(namespacedStorage)}async store(session){await this.put(session)}async update(session){await this.put(session)}async get(id){const record=await this.storage.get(this.key(id));if(!record){return null}if(Date.now()>record.expiresAt){await this.storage.delete(this.key(id));return null}return fromSessionRecord(record)}async delete(id){await this.storage.delete(this.key(id))}async put(session){const record=toSessionRecord(session);const ttlSeconds=this.calculateTtl(record.expiresAt);await this.storage.set(this.key(session.id),record,ttlSeconds?{ttlSeconds}:void 0)}calculateTtl(expiresAtMs){const ttl=Math.ceil((expiresAtMs-Date.now())/1e3);return ttl>0?ttl:1}key(id){return this.storageIsNamespaced?id:`${this.namespace}:${id}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};function consentRecordKey(userSub,clientId){return`consent:${userSub}:${clientId}`}var InMemoryConsentStore=class{records=new Map;async get(userSub,clientId){const record=this.records.get(consentRecordKey(userSub,clientId));if(!record)return null;return{...record,selectedToolIds:[...record.selectedToolIds],seenToolIds:[...record.seenToolIds]}}async set(record){this.records.set(consentRecordKey(record.userSub,record.clientId),{...record,selectedToolIds:[...record.selectedToolIds],seenToolIds:[...record.seenToolIds]})}async delete(userSub,clientId){this.records.delete(consentRecordKey(userSub,clientId))}get size(){return this.records.size}clear(){this.records.clear()}};import{TypedStorage as TypedStorage6}from"@frontmcp/utils";var StorageConsentStore=class{storage;namespace;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"remembered-consent";this.storageIsNamespaced=this.isNamespacedStorage(storage);const namespacedStorage=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.storage=new TypedStorage6(namespacedStorage)}async get(userSub,clientId){return this.storage.get(this.key(userSub,clientId))}async set(record){await this.storage.set(this.key(record.userSub,record.clientId),record)}async delete(userSub,clientId){await this.storage.delete(this.key(userSub,clientId))}key(userSub,clientId){const base=consentRecordKey(userSub,clientId);return this.storageIsNamespaced?base:`${this.namespace}:${base}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{internalZ as z8}from"@frontmcp/lazy-zod";import{AsyncLocalStorage,randomUUID as randomUUID10}from"@frontmcp/utils";var redisVaultEntrySchema=z8.object({id:z8.string(),userSub:z8.string(),userEmail:z8.string().optional(),userName:z8.string().optional(),clientId:z8.string(),createdAt:z8.number(),lastAccessAt:z8.number(),authorizedAppIds:z8.array(z8.string()),skippedAppIds:z8.array(z8.string()),pendingAuthIds:z8.array(z8.string()),encrypted:encryptedDataSchema});var encryptionContextStorage=new AsyncLocalStorage;var EncryptedRedisVault=class{constructor(redis,encryption,namespace="vault:"){this.redis=redis;this.encryption=encryption;this.namespace=namespace}redis;encryption;namespace;runWithContext(context,fn){return encryptionContextStorage.run(context,fn)}getKey(){const asyncContext=encryptionContextStorage.getStore();if(asyncContext){return asyncContext.key}throw new EncryptionContextNotSetError}redisKey(id){return`${this.namespace}${id}`}credentialKey(appId,providerId){return`${appId}:${providerId}`}async encryptSensitive(data){return this.encryption.encryptObject(data,this.getKey())}async decryptSensitive(encrypted){return this.encryption.decryptObject(encrypted,this.getKey())}async toVaultEntry(redisEntry){const sensitive=await this.decryptSensitive(redisEntry.encrypted);return{id:redisEntry.id,userSub:redisEntry.userSub,userEmail:redisEntry.userEmail,userName:redisEntry.userName,clientId:redisEntry.clientId,createdAt:redisEntry.createdAt,lastAccessAt:redisEntry.lastAccessAt,appCredentials:sensitive.appCredentials,consent:sensitive.consent,federated:sensitive.federated,pendingAuths:sensitive.pendingAuths,authorizedAppIds:redisEntry.authorizedAppIds,skippedAppIds:redisEntry.skippedAppIds}}async toRedisEntry(entry){const sensitive={appCredentials:entry.appCredentials,consent:entry.consent,federated:entry.federated,pendingAuths:entry.pendingAuths};return{id:entry.id,userSub:entry.userSub,userEmail:entry.userEmail,userName:entry.userName,clientId:entry.clientId,createdAt:entry.createdAt,lastAccessAt:entry.lastAccessAt,authorizedAppIds:entry.authorizedAppIds,skippedAppIds:entry.skippedAppIds,pendingAuthIds:entry.pendingAuths.map(p=>p.id),encrypted:await this.encryptSensitive(sensitive)}}async saveEntry(entry){const redisEntry=await this.toRedisEntry(entry);await this.redis.set(this.redisKey(entry.id),JSON.stringify(redisEntry))}async loadEntry(id){const data=await this.redis.get(this.redisKey(id));if(!data)return null;try{const redisEntry=redisVaultEntrySchema.parse(JSON.parse(data));return await this.toVaultEntry(redisEntry)}catch(error){throw new VaultLoadError(id,error instanceof Error?error:void 0)}}async create(params){const now=Date.now();const entry={id:randomUUID10(),userSub:params.userSub,userEmail:params.userEmail,userName:params.userName,clientId:params.clientId,createdAt:now,lastAccessAt:now,appCredentials:{},consent:params.consent,federated:params.federated,pendingAuths:[],authorizedAppIds:params.authorizedAppIds??[],skippedAppIds:params.skippedAppIds??[]};await this.saveEntry(entry);return entry}async get(id){const entry=await this.loadEntry(id);if(!entry)return null;entry.lastAccessAt=Date.now();await this.saveEntry(entry);return entry}async update(id,updates){const entry=await this.loadEntry(id);if(!entry){throw new VaultNotFoundError("Vault entry",id)}Object.assign(entry,updates,{lastAccessAt:Date.now()});await this.saveEntry(entry)}async delete(id){await this.redis.del(this.redisKey(id))}async updateConsent(vaultId,consent){const entry=await this.loadEntry(vaultId);if(!entry)return;entry.consent=consent;entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async authorizeApp(vaultId,appId){const entry=await this.loadEntry(vaultId);if(!entry)return;entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==appId);if(!entry.authorizedAppIds.includes(appId)){entry.authorizedAppIds.push(appId)}entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async createPendingAuth(vaultId,params){const entry=await this.loadEntry(vaultId);if(!entry){throw new VaultNotFoundError("Vault",vaultId)}const now=Date.now();const pendingAuth={id:randomUUID10(),appId:params.appId,toolId:params.toolId,authUrl:params.authUrl,requiredScopes:params.requiredScopes,elicitId:params.elicitId,createdAt:now,expiresAt:now+(params.ttlMs??10*60*1e3),status:"pending"};entry.pendingAuths.push(pendingAuth);entry.lastAccessAt=now;await this.saveEntry(entry);return pendingAuth}async getPendingAuth(vaultId,pendingAuthId){const entry=await this.loadEntry(vaultId);if(!entry)return null;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(!pendingAuth)return null;if(Date.now()>pendingAuth.expiresAt&&pendingAuth.status==="pending"){pendingAuth.status="expired";await this.saveEntry(entry)}return pendingAuth}async completePendingAuth(vaultId,pendingAuthId){const entry=await this.loadEntry(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="completed";entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==pendingAuth.appId);if(!entry.authorizedAppIds.includes(pendingAuth.appId)){entry.authorizedAppIds.push(pendingAuth.appId)}entry.lastAccessAt=Date.now();await this.saveEntry(entry)}}async cancelPendingAuth(vaultId,pendingAuthId){const entry=await this.loadEntry(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="cancelled";await this.saveEntry(entry)}}async isAppAuthorized(vaultId,appId){const data=await this.redis.get(this.redisKey(vaultId));if(!data)return false;try{const parsed=JSON.parse(data);return Array.isArray(parsed.authorizedAppIds)&&parsed.authorizedAppIds.includes(appId)}catch{return false}}async getPendingAuths(vaultId){const entry=await this.loadEntry(vaultId);if(!entry)return[];const now=Date.now();let updated=false;const pending=entry.pendingAuths.filter(p=>{if(now>p.expiresAt&&p.status==="pending"){p.status="expired";updated=true}return p.status==="pending"});if(updated){await this.saveEntry(entry)}return pending}async addAppCredential(vaultId,credential){const entry=await this.loadEntry(vaultId);if(!entry)return;const shouldStore=await this.shouldStoreCredential(vaultId,credential.appId);if(!shouldStore)return;const key=this.credentialKey(credential.appId,credential.providerId);entry.appCredentials[key]=credential;entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async removeAppCredential(vaultId,appId,providerId){const entry=await this.loadEntry(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);delete entry.appCredentials[key];entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async getAppCredentials(vaultId,appId){const entry=await this.loadEntry(vaultId);if(!entry)return[];const prefix=`${appId}:`;return Object.entries(entry.appCredentials).filter(([key])=>key.startsWith(prefix)).map(([,cred])=>cred)}async getCredential(vaultId,appId,providerId){const entry=await this.loadEntry(vaultId);if(!entry)return null;const key=this.credentialKey(appId,providerId);return entry.appCredentials[key]??null}async getAllCredentials(vaultId,filterByConsent=false){const entry=await this.loadEntry(vaultId);if(!entry)return[];const allCredentials=Object.values(entry.appCredentials);if(!filterByConsent||!entry.consent?.enabled){return allCredentials}const consentedToolIds=new Set(entry.consent.selectedToolIds);return allCredentials.filter(cred=>{return Array.from(consentedToolIds).some(toolId=>toolId.startsWith(`${cred.appId}:`))})}async updateCredential(vaultId,appId,providerId,updates){const entry=await this.loadEntry(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential)return;Object.assign(credential,updates);entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async shouldStoreCredential(vaultId,appId,toolIds){const entry=await this.loadEntry(vaultId);if(!entry)return false;if(!entry.consent?.enabled){return true}if(toolIds&&toolIds.length>0){return toolIds.some(toolId=>entry.consent.selectedToolIds.includes(toolId))}const consentedToolIds=entry.consent.selectedToolIds;return consentedToolIds.some(toolId=>toolId.startsWith(`${appId}:`))}async invalidateCredential(vaultId,appId,providerId,reason){await this.updateCredential(vaultId,appId,providerId,{isValid:false,invalidReason:reason})}async refreshOAuthCredential(vaultId,appId,providerId,tokens){const entry=await this.loadEntry(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential||credential.credential.type!=="oauth")return;credential.credential.accessToken=tokens.accessToken;if(tokens.refreshToken!==void 0){credential.credential.refreshToken=tokens.refreshToken}if(tokens.expiresAt!==void 0){credential.credential.expiresAt=tokens.expiresAt;credential.expiresAt=tokens.expiresAt}credential.isValid=true;credential.invalidReason=void 0;entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async cleanup(){}};function createEncryptedVault(redis,config={}){const encryption=new VaultEncryption({pepper:config.pepper});const vault=new EncryptedRedisVault(redis,encryption,config.namespace);return{vault,encryption}}import{base64urlDecode as base64urlDecode4}from"@frontmcp/utils";function toEpochSeconds(exp){if(exp==null)return void 0;if(exp instanceof Date)return Math.floor(exp.getTime()/1e3);return exp>1e12?Math.floor(exp/1e3):Math.floor(exp)}function isSoonExpiring(exp,skewSec=60){const expSec=toEpochSeconds(exp);if(expSec==null)return false;const now=Math.floor(Date.now()/1e3);return expSec<=now+Math.max(0,skewSec)}function isSoonExpiringProvider(sessionLike,providerId,skewSec=60){const snap=sessionLike.authorizedProviders[providerId];if(!snap)return false;return isSoonExpiring(snap.exp,skewSec)}function tryJwtExp(token){if(!token)return void 0;const parts=token.split(".");if(parts.length<2)return void 0;try{const json=JSON.parse(new TextDecoder().decode(base64urlDecode4(parts[1])));const e=json?.exp;return typeof e==="number"?toEpochSeconds(e):void 0}catch{return void 0}}import{internalZ as z9}from"@frontmcp/lazy-zod";var authModeSchema=z9.enum(["public","transparent","orchestrated"]);var authUserSchema=z9.object({sub:z9.string(),name:z9.string().optional(),email:z9.string().email().optional(),picture:z9.string().url().optional(),anonymous:z9.boolean().optional()});var authorizedToolSchema=z9.object({executionPath:z9.tuple([z9.string(),z9.string()]),scopes:z9.array(z9.string()).optional(),details:z9.record(z9.string(),z9.unknown()).optional()});var authorizedPromptSchema=z9.object({executionPath:z9.tuple([z9.string(),z9.string()]),scopes:z9.array(z9.string()).optional(),details:z9.record(z9.string(),z9.unknown()).optional()});var llmSafeAuthContextSchema=z9.object({authorizationId:z9.string(),sessionId:z9.string(),mode:authModeSchema,isAnonymous:z9.boolean(),user:z9.object({sub:z9.string(),name:z9.string().optional()}),scopes:z9.array(z9.string()),authorizedToolIds:z9.array(z9.string()),authorizedPromptIds:z9.array(z9.string())});var AppAuthState=(AppAuthState2=>{AppAuthState2["AUTHORIZED"]="authorized";AppAuthState2["SKIPPED"]="skipped";AppAuthState2["PENDING"]="pending";return AppAuthState2})(AppAuthState||{});var appAuthStateSchema=z9.nativeEnum(AppAuthState);var appAuthorizationRecordSchema=z9.object({appId:z9.string(),state:appAuthStateSchema,stateChangedAt:z9.number(),grantedScopes:z9.array(z9.string()).optional(),authProviderId:z9.string().optional(),toolIds:z9.array(z9.string())});var progressiveAuthStateSchema=z9.object({apps:z9.record(z9.string(),appAuthorizationRecordSchema),initiallyAuthorized:z9.array(z9.string()),initiallySkipped:z9.array(z9.string())});import{base64urlDecode as base64urlDecode5,getMachineId as getMachineId2,randomUUID as randomUUID11}from"@frontmcp/utils";var AuthorizationBase=class _AuthorizationBase{id;isAnonymous;user;claims;expiresAt;scopes;authorizedProviders;authorizedProviderIds;authorizedApps;authorizedAppIds;authorizedTools;authorizedToolIds;authorizedPrompts;authorizedPromptIds;authorizedResources;token;#sessions=new Map;createdAt;constructor(ctx){this.id=ctx.id;this.isAnonymous=ctx.isAnonymous;this.user=ctx.user;this.claims=ctx.claims;this.expiresAt=ctx.expiresAt;this.scopes=ctx.scopes??[];this.token=ctx.token;this.createdAt=Date.now();this.authorizedProviders=ctx.authorizedProviders??{};this.authorizedProviderIds=ctx.authorizedProviderIds??Object.keys(this.authorizedProviders);this.authorizedApps=ctx.authorizedApps??{};this.authorizedAppIds=ctx.authorizedAppIds??Object.keys(this.authorizedApps);this.authorizedTools=ctx.authorizedTools??{};this.authorizedToolIds=ctx.authorizedToolIds??Object.keys(this.authorizedTools);this.authorizedPrompts=ctx.authorizedPrompts??{};this.authorizedPromptIds=ctx.authorizedPromptIds??Object.keys(this.authorizedPrompts);this.authorizedResources=ctx.authorizedResources??[]}createTransportSession(protocol,fingerprint){const sessionId=randomUUID11();const session={id:sessionId,authorizationId:this.id,protocol,createdAt:Date.now(),expiresAt:this.expiresAt,nodeId:getMachineId2(),clientFingerprint:fingerprint};this.#sessions.set(session.id,session);return session}getTransportSession(sessionId){return this.#sessions.get(sessionId)}getAllSessions(){return Array.from(this.#sessions.values())}removeTransportSession(sessionId){return this.#sessions.delete(sessionId)}get sessionCount(){return this.#sessions.size}hasScope(scope){return this.scopes.includes(scope)}hasAllScopes(scopes){return scopes.every(s=>this.scopes.includes(s))}hasAnyScope(scopes){return scopes.some(s=>this.scopes.includes(s))}canAccessTool(toolId){return toolId in this.authorizedTools||this.authorizedToolIds.includes(toolId)}canAccessPrompt(promptId){return promptId in this.authorizedPrompts||this.authorizedPromptIds.includes(promptId)}isAppAuthorized(appId){return appId in this.authorizedApps||this.authorizedAppIds.includes(appId)}getProgressiveAuthUrl(appId,baseUrl,ticket){const params=new URLSearchParams({app:appId});if(ticket)params.set("ticket",ticket);return`${baseUrl}/oauth/authorize?${params.toString()}`}isExpired(){if(!this.expiresAt)return false;return Date.now()>this.expiresAt}getTimeToExpiry(){if(!this.expiresAt)return void 0;return this.expiresAt-Date.now()}toSessionJwt(session){const payload={sid:session.id,aid:this.id,proto:session.protocol,nid:session.nodeId,iat:Math.floor(Date.now()/1e3),exp:this.expiresAt?Math.floor(this.expiresAt/1e3):void 0};return encryptJson(payload)}toLLMSafeContext(session){return{authorizationId:this.id,sessionId:session.id,mode:this.mode,isAnonymous:this.isAnonymous,user:{sub:this.user.sub,name:this.user.name},scopes:this.scopes,authorizedToolIds:this.authorizedToolIds,authorizedPromptIds:this.authorizedPromptIds}}static isJwtHeader(segment){try{const bytes=base64urlDecode5(segment);const text=new TextDecoder().decode(bytes);const parsed=JSON.parse(text);return typeof parsed==="object"&&parsed!==null&&typeof parsed.alg==="string"}catch{return false}}static validateNoTokenLeakage(data){const json=JSON.stringify(data);const B64URL=/^[A-Za-z0-9_-]+$/;const MIN_SEG=2;let dotIdx=json.indexOf(".");while(dotIdx!==-1){let seg1Start=dotIdx;while(seg1Start>0&&/[A-Za-z0-9_-]/.test(json[seg1Start-1]))seg1Start--;const seg1Len=dotIdx-seg1Start;if(seg1Len>=MIN_SEG){let seg2End=dotIdx+1;while(seg2End<json.length&&/[A-Za-z0-9_-]/.test(json[seg2End]))seg2End++;const seg2Len=seg2End-(dotIdx+1);if(seg2Len>=MIN_SEG&&seg2End<json.length&&json[seg2End]==="."){const dot2=seg2End;let seg3End=dot2+1;while(seg3End<json.length&&/[A-Za-z0-9_-]/.test(json[seg3End]))seg3End++;const seg3Len=seg3End-(dot2+1);if(seg3Len>=MIN_SEG){const seg1=json.substring(seg1Start,dotIdx);const seg2=json.substring(dotIdx+1,dot2);const seg3=json.substring(dot2+1,seg3End);if(B64URL.test(seg1)&&B64URL.test(seg2)&&B64URL.test(seg3)&&_AuthorizationBase.isJwtHeader(seg1)){throw new TokenLeakDetectedError("JWT pattern detected in LLM context data")}}}}dotIdx=json.indexOf(".",dotIdx+1)}const sensitiveFields=["access_token","refresh_token","id_token","tokenEnc","secretRefId"];for(const field of sensitiveFields){if(json.includes(`"${field}"`)){throw new TokenLeakDetectedError(`Sensitive field "${field}" found in data`)}}}};import{randomUUID as randomUUID12}from"@frontmcp/utils";var PublicAuthorization=class _PublicAuthorization extends AuthorizationBase{mode="public";issuer;constructor(ctx){super(ctx);this.issuer=ctx.issuer}static create(ctx={}){const{prefix="anon",scopes=["anonymous"],ttlMs=36e5,issuer,allowedTools="all",allowedPrompts="all"}=ctx;const uuid=randomUUID12();const sub=`${prefix}:${uuid}`;const user={sub,name:"Anonymous",anonymous:true};const expiresAt=ttlMs?Date.now()+ttlMs:void 0;const authorizedTools={};const authorizedToolIds=[];if(allowedTools!=="all"&&Array.isArray(allowedTools)){for(const toolId of allowedTools){authorizedTools[toolId]={executionPath:["public",toolId]};authorizedToolIds.push(toolId)}}const authorizedPrompts={};const authorizedPromptIds=[];if(allowedPrompts!=="all"&&Array.isArray(allowedPrompts)){for(const promptId of allowedPrompts){authorizedPrompts[promptId]={executionPath:["public",promptId]};authorizedPromptIds.push(promptId)}}return new _PublicAuthorization({id:sub,isAnonymous:true,user,scopes,expiresAt,issuer,authorizedTools:allowedTools==="all"?void 0:authorizedTools,authorizedToolIds:allowedTools==="all"?void 0:authorizedToolIds,authorizedPrompts:allowedPrompts==="all"?void 0:authorizedPrompts,authorizedPromptIds:allowedPrompts==="all"?void 0:authorizedPromptIds})}async getToken(_providerId){throw new TokenNotAvailableError("PublicAuthorization: Anonymous users cannot access provider tokens. Use transparent or orchestrated mode for token access.")}get allowsAllTools(){return this.authorizedToolIds.length===0&&Object.keys(this.authorizedTools).length===0}get allowsAllPrompts(){return this.authorizedPromptIds.length===0&&Object.keys(this.authorizedPrompts).length===0}canAccessTool(toolId){if(this.allowsAllTools){return true}return super.canAccessTool(toolId)}canAccessPrompt(promptId){if(this.allowsAllPrompts){return true}return super.canAccessPrompt(promptId)}};import{sha256Hex as sha256Hex4}from"@frontmcp/utils";var TransparentAuthorization=class _TransparentAuthorization extends AuthorizationBase{mode="transparent";providerId;providerName;constructor(ctx){super(ctx);this.providerId=ctx.providerId;this.providerName=ctx.providerName}static fromVerifiedToken(ctx){const{token,payload,providerId,providerName,...projections}=ctx;const user={sub:payload.sub,name:payload.name,email:payload.email,picture:payload.picture,anonymous:false};const scopes=_TransparentAuthorization.parseScopes(payload.scope);const expiresAt=payload.exp?payload.exp*1e3:void 0;const id=_TransparentAuthorization.generateAuthorizationId(token);const providerSnapshot={id:providerId,exp:expiresAt,payload,embedMode:"plain",token};return new _TransparentAuthorization({id,isAnonymous:false,user,claims:payload,expiresAt,scopes,token,providerId,providerName,authorizedProviders:{[providerId]:providerSnapshot},authorizedProviderIds:[providerId],...projections})}async getToken(_providerId){if(!this.token){throw new TokenNotAvailableError("TransparentAuthorization: Token not available")}return this.token}static parseScopes(scope){if(!scope)return[];if(Array.isArray(scope))return scope;return scope.split(/\s+/).filter(Boolean)}static generateAuthorizationId(token){const parts=token.split(".");const signature=parts[2]||token;return sha256Hex4(signature).substring(0,16)}get issuer(){return this.claims?.["iss"]}get audience(){return this.claims?.["aud"]}hasAudience(aud){const tokenAud=this.audience;if(!tokenAud)return false;if(Array.isArray(tokenAud))return tokenAud.includes(aud);return tokenAud===aud}};import{sha256Hex as sha256Hex5}from"@frontmcp/utils";function deriveAuthorizationId(token){const parts=token.split(".");const signature=parts[2]||token;return sha256Hex5(signature).substring(0,16)}var OrchestratedAuthorization=class _OrchestratedAuthorization extends AuthorizationBase{mode="orchestrated";primaryProviderId;#tokenStore;#onTokenRefresh;#providerStates;constructor(ctx){super(ctx);this.primaryProviderId=ctx.primaryProviderId;this.#tokenStore=ctx.tokenStore;this.#onTokenRefresh=ctx.onTokenRefresh;this.#providerStates=ctx.providerStates??new Map}static create(ctx){const{token,user,scopes=[],claims,expiresAt,primaryProviderId,tokenStore,onTokenRefresh,providers={},authorizedProviderIds,...projections}=ctx;const id=deriveAuthorizationId(token);const providerStates=new Map;const authorizedProviders={};const providerIdsFromState=[];for(const[providerId,state]of Object.entries(providers)){providerStates.set(providerId,state);providerIdsFromState.push(providerId);authorizedProviders[providerId]={id:providerId,exp:state.expiresAt,embedMode:state.secretRefId?"ref":"store-only",secretRefId:state.secretRefId,refreshRefId:state.refreshRefId}}const finalAuthorizedProviderIds=authorizedProviderIds??(providerIdsFromState.length>0?providerIdsFromState:void 0);return new _OrchestratedAuthorization({id,isAnonymous:false,user,claims,expiresAt,scopes,token,primaryProviderId,tokenStore,onTokenRefresh,providerStates,authorizedProviders,...projections,authorizedProviderIds:finalAuthorizedProviderIds})}async getToken(providerId){const targetProviderId=providerId??this.primaryProviderId;if(!targetProviderId){throw new NoProviderIdError("OrchestratedAuthorization: No provider ID specified and no primary provider set")}if(!this.#tokenStore){throw new TokenStoreRequiredError("orchestrated token retrieval")}const hasToken=await this.#tokenStore.hasTokens(this.id,targetProviderId);if(!hasToken){throw new TokenNotAvailableError(`OrchestratedAuthorization: No tokens available for provider "${targetProviderId}"`)}const accessToken=await this.#tokenStore.getAccessToken(this.id,targetProviderId);if(accessToken){const providerState=this.#providerStates.get(targetProviderId);if(providerState?.expiresAt&&providerState.expiresAt<Date.now()){return this.refreshAndGetToken(targetProviderId)}return accessToken}return this.refreshAndGetToken(targetProviderId)}async refreshAndGetToken(providerId){if(!this.#tokenStore||!this.#onTokenRefresh){throw new TokenNotAvailableError(`OrchestratedAuthorization: Token expired for provider "${providerId}" and refresh not available`)}const refreshToken=await this.#tokenStore.getRefreshToken(this.id,providerId);if(!refreshToken){throw new TokenNotAvailableError(`OrchestratedAuthorization: No refresh token available for provider "${providerId}"`)}const result=await this.#onTokenRefresh(providerId,refreshToken);await this.#tokenStore.storeTokens(this.id,providerId,{accessToken:result.accessToken,refreshToken:result.refreshToken??refreshToken,expiresAt:result.expiresIn?Date.now()+result.expiresIn*1e3:void 0});const currentState=this.#providerStates.get(providerId);if(currentState){currentState.expiresAt=result.expiresIn?Date.now()+result.expiresIn*1e3:void 0}return result.accessToken}hasProvider(providerId){return this.#providerStates.has(providerId)}getProviderIds(){return Array.from(this.#providerStates.keys())}async addProvider(providerId,tokens){if(!this.#tokenStore){throw new TokenStoreRequiredError("adding providers")}const expiresAt=tokens.expiresIn?Date.now()+tokens.expiresIn*1e3:void 0;await this.#tokenStore.storeTokens(this.id,providerId,{accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt});this.#providerStates.set(providerId,{id:providerId,expiresAt,secretRefId:`${this.id}:${providerId}`})}#mutableAuthorizedApps=new Map(Object.entries(this.authorizedApps??{}));async addAppAuthorization(appId,toolIds,tokens){if(!this.#tokenStore){throw new TokenStoreRequiredError("progressive authorization")}const providerId=`app:${appId}`;await this.addProvider(providerId,tokens);this.#mutableAuthorizedApps.set(appId,{id:appId,toolIds})}async getAppToken(appId){if(!this.#mutableAuthorizedApps.has(appId)){return null}const providerId=`app:${appId}`;try{return await this.getToken(providerId)}catch{return null}}isAppAuthorized(appId){return this.#mutableAuthorizedApps.has(appId)||super.isAppAuthorized(appId)}getAllAuthorizedAppIds(){const baseIds=new Set(this.authorizedAppIds??[]);for(const appId of this.#mutableAuthorizedApps.keys()){baseIds.add(appId)}return Array.from(baseIds)}getAppToolIds(appId){return this.#mutableAuthorizedApps.get(appId)?.toolIds??this.authorizedApps?.[appId]?.toolIds}async removeProvider(providerId){if(this.#tokenStore){await this.#tokenStore.deleteTokens(this.id,providerId)}this.#providerStates.delete(providerId)}get issuer(){return this.claims?.["iss"]}};var ORCHESTRATED_AUTH_ACCESSOR=Symbol.for("frontmcp:ORCHESTRATED_AUTH_ACCESSOR");var NullOrchestratedAuthAccessor=class{primaryProviderId=void 0;issuer=void 0;authorizationId="null";isAuthenticated=false;async getToken(providerId){throw new OrchestratedAuthNotAvailableError}async tryGetToken(providerId){return null}async getAppToken(appId){return null}hasProvider(providerId){return false}getProviderIds(){return[]}isAppAuthorized(appId){return false}getAllAuthorizedAppIds(){return[]}getAppToolIds(appId){return void 0}};var OrchestratedAuthAccessorAdapter=class{constructor(authorization){this.authorization=authorization}authorization;get primaryProviderId(){return this.authorization.primaryProviderId}get issuer(){return this.authorization.issuer}get authorizationId(){return this.authorization.id}get isAuthenticated(){return!this.authorization.isAnonymous}async getToken(providerId){return this.authorization.getToken(providerId)}async tryGetToken(providerId){try{return await this.authorization.getToken(providerId)}catch{return null}}async getAppToken(appId){return this.authorization.getAppToken(appId)}hasProvider(providerId){return this.authorization.hasProvider(providerId)}getProviderIds(){return this.authorization.getProviderIds()}isAppAuthorized(appId){return this.authorization.isAppAuthorized(appId)}getAllAuthorizedAppIds(){return this.authorization.getAllAuthorizedAppIds()}getAppToolIds(appId){return this.authorization.getAppToolIds(appId)}};import{sha256 as sha2562,timingSafeEqual as timingSafeEqual2}from"@frontmcp/utils";var DEFAULT_MAX_DYNAMIC_CLIENTS=1e3;function globMatch(pattern,input){let p=0;let i=0;let starIdx=-1;let matchIdx=0;while(i<input.length){if(p<pattern.length&&pattern[p]===input[i]){p++;i++}else if(p<pattern.length&&pattern[p]==="*"){starIdx=p;matchIdx=i;p++}else if(starIdx!==-1){p=starIdx+1;matchIdx++;i=matchIdx}else{return false}}while(p<pattern.length&&pattern[p]==="*")p++;return p===pattern.length}function splitRedirectUri(s){const schemeSep=s.indexOf("://");if(schemeSep===-1)return null;const scheme=s.slice(0,schemeSep);const afterScheme=s.slice(schemeSep+3);let boundary=afterScheme.length;for(let i=0;i<afterScheme.length;i++){const ch=afterScheme[i];if(ch==="/"||ch==="?"||ch==="#"){boundary=i;break}}const authority=afterScheme.slice(0,boundary);const rest=afterScheme.slice(boundary);const at=authority.lastIndexOf("@");const userinfo=at===-1?"":authority.slice(0,at);const hostport=at===-1?authority:authority.slice(at+1);let host=hostport;let port="";if(hostport.startsWith("[")){const close=hostport.indexOf("]");if(close!==-1){host=hostport.slice(0,close+1);if(hostport[close+1]===":")port=hostport.slice(close+2)}}else{const colon=hostport.indexOf(":");if(colon!==-1){host=hostport.slice(0,colon);port=hostport.slice(colon+1)}}return{scheme,userinfo,host,port,rest}}function globMatchRedirectUri(pattern,input){const p=splitRedirectUri(pattern);if(!p)return globMatch(pattern,input);const u=splitRedirectUri(input);if(!u)return false;return globMatch(p.scheme,u.scheme)&&globMatch(p.userinfo,u.userinfo)&&globMatch(p.host,u.host)&&globMatch(p.port,u.port)&&globMatch(p.rest,u.rest)}var DcrClientRegistry=class{constructor(config={}){this.config=config;this.redirectGlobs=config.allowedRedirectUris;this.seedPreRegisteredClients()}config;clients=new Map;redirectGlobs;seedPreRegisteredClients(){const clients=this.config.clients??[];const now=Math.floor(Date.now()/1e3);for(const c of clients){this.clients.set(c.clientId,{client_id:c.clientId,client_secret:c.clientSecret,token_endpoint_auth_method:c.tokenEndpointAuthMethod??"none",grant_types:c.grantTypes??["authorization_code"],response_types:c.responseTypes??["code"],redirect_uris:c.redirectUris,client_name:c.clientName,scope:c.scope,created_at:now,dev:false,preRegistered:true})}}get(clientId){return this.clients.get(clientId)}verifyClientSecret(clientId,presentedSecret){const client=this.clients.get(clientId);if(!client)return"unknown";const method=client.token_endpoint_auth_method;const isConfidential=method==="client_secret_basic"||method==="client_secret_post";if(!isConfidential)return"ok";if(!client.client_secret)return"invalid";if(!presentedSecret)return"invalid";return timingSafeEqual2(sha2562(presentedSecret),sha2562(client.client_secret))?"ok":"invalid"}has(clientId){return this.clients.has(clientId)}register(client){const cap=this.dynamicClientCap();const dynamicCount=this.countDynamicClients();const isNew=!this.clients.has(client.client_id);if(isNew&&dynamicCount>=cap){return false}this.clients.set(client.client_id,client);return true}dynamicClientCap(){const configured=this.config.maxDynamicClients;return Number.isSafeInteger(configured)&&configured>=0?configured:DEFAULT_MAX_DYNAMIC_CLIENTS}countDynamicClients(){let n=0;for(const c of this.clients.values())if(c.dev&&!c.preRegistered)n++;return n}hasRedirectAllowlist(){return!!this.redirectGlobs&&this.redirectGlobs.length>0}isRedirectUriAllowed(redirectUri){if(!this.redirectGlobs||this.redirectGlobs.length===0){return true}return this.redirectGlobs.some(glob=>globMatchRedirectUri(glob,redirectUri))}hasClientIdAllowlist(){return!!this.config.allowedClientIds&&this.config.allowedClientIds.length>0}isClientIdAllowed(clientId){const allow=this.config.allowedClientIds;if(!allow||allow.length===0){return true}return allow.includes(clientId)}requiresInitialAccessToken(){return typeof this.config.initialAccessToken==="string"&&this.config.initialAccessToken.length>0}verifyInitialAccessToken(presented){const expected=this.config.initialAccessToken;if(!expected){return false}if(typeof presented!=="string"||presented.length===0){return false}return timingSafeEqual2(sha2562(presented),sha2562(expected))}};import{internalZ as z10}from"@frontmcp/lazy-zod";var aiPlatformTypeSchema=z10.enum(["openai","claude","claude-code","codex","gemini","cursor","continue","cody","generic-mcp","ext-apps","unknown"]);var userClaimSchema=z10.object({iss:z10.string(),sid:z10.string().optional(),sub:z10.string(),exp:z10.number().optional(),iat:z10.number().optional(),aud:z10.union([z10.string(),z10.array(z10.string())]).optional(),email:z10.string().optional(),username:z10.string().optional(),preferred_username:z10.string().optional(),name:z10.string().optional(),picture:z10.string().optional()}).passthrough();var sessionIdPayloadSchema=z10.object({nodeId:z10.string(),authSig:z10.string(),uuid:z10.string().uuid(),iat:z10.number(),protocol:z10.enum(["legacy-sse","sse","streamable-http","stateful-http","stateless-http"]).optional(),isPublic:z10.boolean().optional(),platformType:aiPlatformTypeSchema.optional(),clientName:z10.string().optional(),clientVersion:z10.string().optional(),supportsElicitation:z10.boolean().optional(),skillsOnlyMode:z10.boolean().optional()});var sessionIdSchema=z10.object({id:z10.string(),payload:sessionIdPayloadSchema.optional()});var authorizationSchema=z10.object({token:z10.string(),session:sessionIdSchema.optional(),user:userClaimSchema});import{internalZ as z11}from"@frontmcp/lazy-zod";var publicAuthOptionsSchema=z11.object({mode:z11.literal("public"),issuer:z11.string().optional(),sessionTtl:z11.number().int().positive().default(3600),anonymousScopes:z11.array(z11.string()).default(["anonymous"]),publicAccess:publicAccessConfigSchema.optional(),jwks:jsonWebKeySetSchema.optional(),signKey:jwkSchema.or(z11.instanceof(Uint8Array)).optional()});import{internalZ as z12}from"@frontmcp/lazy-zod";var staticAuthOptionsSchema=z12.object({mode:z12.literal("static"),tokens:z12.array(z12.string().min(1)).min(1),header:z12.string().min(1).default("authorization"),scheme:z12.string().default("Bearer"),scopes:z12.array(z12.string()).default(["static"]),realm:z12.string().default("mcp"),publicAccess:publicAccessConfigSchema.optional()});import{internalZ as z13}from"@frontmcp/lazy-zod";var transparentAuthOptionsSchema=z13.object({mode:z13.literal("transparent"),...flatRemoteProviderFields,expectedAudience:z13.union([z13.string(),z13.array(z13.string())]).optional(),requireAudience:z13.boolean().optional(),requiredScopes:z13.array(z13.string()).default([]),allowAnonymous:z13.boolean().default(false),anonymousScopes:z13.array(z13.string()).default(["anonymous"]),publicAccess:publicAccessConfigSchema.optional()});import{internalZ as z14}from"@frontmcp/lazy-zod";var authSlotSchema=z14.enum(["login","consent","incremental","federated","error"]);var authUiMapSchema=z14.partialRecord(authSlotSchema,z14.string().min(1));var authExtraHandlerSchema=z14.custom(v=>typeof v==="function",{message:"auth.extras entries must be handler functions (input, ctx) => AuthExtraResult."});var authExtrasMapSchema=z14.record(z14.string().min(1),authExtraHandlerSchema);var sharedAuthFields={local:localSigningConfigSchema.optional(),tokenStorage:tokenStorageConfigSchema.default("memory"),secureStore:secureStoreConfigSchema.optional(),allowDefaultPublic:z14.boolean().default(false),anonymousScopes:z14.array(z14.string()).default(["anonymous"]),publicAccess:publicAccessConfigSchema.optional(),consent:consentConfigSchema.optional(),federatedAuth:federatedAuthConfigSchema.optional(),refresh:tokenRefreshConfigSchema.optional(),expectedAudience:z14.union([z14.string(),z14.array(z14.string())]).optional(),incrementalAuth:incrementalAuthConfigSchema.optional(),requireRegisteredClients:z14.boolean().default(true),allowedScopes:z14.array(z14.string().trim().min(1,"allowedScopes entries must be non-empty")).optional(),cimd:cimdConfigSchema.optional(),ui:authUiMapSchema.optional(),extras:authExtrasMapSchema.optional()};var loginFieldSchema=z14.object({type:z14.enum(["text","password","email","select","hidden"]),label:z14.string().optional(),required:z14.boolean().optional(),placeholder:z14.string().optional(),options:z14.array(z14.object({value:z14.string(),label:z14.string()})).optional()});var loginConfigSchema=z14.object({title:z14.string().optional(),subtitle:z14.string().optional(),logoUri:z14.string().optional(),fields:z14.record(z14.string(),loginFieldSchema).optional(),render:z14.custom(v=>typeof v==="function",{message:"login.render must be a function"}).optional(),subject:z14.object({fromField:z14.string().optional(),strategy:z14.enum(["per-session","per-account"]).optional()}).optional()});var authenticateFnSchema=z14.custom(v=>typeof v==="function",{message:"authenticate must be a function"});var localAuthSchema=z14.object({mode:z14.literal("local"),...sharedAuthFields,requireEmail:z14.boolean().default(true),anonymousSubject:z14.string().min(1).default("local-operator"),login:loginConfigSchema.optional(),authenticate:authenticateFnSchema.optional(),providers:z14.array(upstreamProviderSchema).optional(),dcr:localDcrConfigSchema.optional()});var remoteAuthSchema=z14.object({mode:z14.literal("remote"),...flatRemoteProviderFields,...sharedAuthFields});var orchestratedLocalSchema=localAuthSchema;var orchestratedRemoteSchema=remoteAuthSchema;import{internalZ as z15}from"@frontmcp/lazy-zod";var authOptionsSchema=z15.union([publicAuthOptionsSchema,staticAuthOptionsSchema,transparentAuthOptionsSchema,localAuthSchema,remoteAuthSchema]);import{internalZ as z16}from"@frontmcp/lazy-zod";var standaloneOptionSchema={standalone:z16.boolean().optional(),excludeFromParent:z16.boolean().optional()};var appAuthOptionsSchema=z16.union([publicAuthOptionsSchema.extend(standaloneOptionSchema),transparentAuthOptionsSchema.extend(standaloneOptionSchema),localAuthSchema.extend(standaloneOptionSchema),remoteAuthSchema.extend(standaloneOptionSchema)]);function parseAuthOptions(input){return authOptionsSchema.parse(input)}function isPublicMode(options){return options.mode==="public"}function isStaticMode(options){return options.mode==="static"}function isTransparentMode(options){return options.mode==="transparent"}function isLocalMode(options){return options.mode==="local"}function isRemoteMode(options){return options.mode==="remote"}function isOrchestratedMode(options){return options.mode==="local"||options.mode==="remote"}function isOrchestratedLocal(options){return options.mode==="local"}function isOrchestratedRemote(options){return options.mode==="remote"}function allowsPublicAccess(options){if(options.mode==="public")return true;if(options.mode==="transparent")return options.allowAnonymous;if(options.mode==="local"||options.mode==="remote")return options.allowDefaultPublic;return false}import{internalZ as z17}from"@frontmcp/lazy-zod";var consentToolItemSchema=z17.object({id:z17.string().min(1),name:z17.string().min(1),description:z17.string().optional(),appId:z17.string().min(1),appName:z17.string().min(1),defaultSelected:z17.boolean().default(true),requiredScopes:z17.array(z17.string()).optional(),category:z17.string().optional()});var consentSelectionSchema=z17.object({selectedTools:z17.array(z17.string()),allSelected:z17.boolean(),consentedAt:z17.string().datetime(),consentVersion:z17.string().default("1.0")});var consentStateSchema=z17.object({enabled:z17.boolean(),availableTools:z17.array(consentToolItemSchema),preselectedTools:z17.array(z17.string()).optional(),groupByApp:z17.boolean().default(true),customMessage:z17.string().optional()});var federatedProviderItemSchema=z17.object({id:z17.string().min(1),name:z17.string().min(1),description:z17.string().optional(),icon:z17.string().optional(),type:z17.enum(["local","remote","transparent"]),providerUrl:z17.string().url().optional(),appIds:z17.array(z17.string()),appNames:z17.array(z17.string()),scopes:z17.array(z17.string()),isPrimary:z17.boolean(),isOptional:z17.boolean().default(false)});var federatedLoginStateSchema=z17.object({providers:z17.array(federatedProviderItemSchema),primaryProviderId:z17.string().optional(),allowSkip:z17.boolean().default(true),preselectedProviders:z17.array(z17.string()).optional()});var federatedSelectionSchema=z17.object({selectedProviders:z17.array(z17.string()),skippedProviders:z17.array(z17.string()),providerMetadata:z17.record(z17.string(),z17.unknown()).optional()});import{internalZ as z18}from"@frontmcp/lazy-zod";var detectedAuthProviderSchema=z18.object({id:z18.string(),providerUrl:z18.string().optional(),mode:z18.enum(["public","static","transparent","local","remote"]),appIds:z18.array(z18.string()),scopes:z18.array(z18.string()),isParentProvider:z18.boolean()});var authProviderDetectionResultSchema=z18.object({providers:z18.map(z18.string(),detectedAuthProviderSchema),requiresOrchestration:z18.boolean(),parentProviderId:z18.string().optional(),childProviderIds:z18.array(z18.string()),uniqueProviderCount:z18.number(),validationErrors:z18.array(z18.string()),warnings:z18.array(z18.string())});function deriveProviderId(options){if(isPublicMode(options)){return options.issuer??"public"}if(isTransparentMode(options)){return options.providerConfig?.id??urlToProviderId(options.provider)}if(isOrchestratedMode(options)){if(isOrchestratedRemote(options)){return options.providerConfig?.id??urlToProviderId(options.provider)}return options.local?.issuer??"local"}return"unknown"}function urlToProviderId(url){try{const parsed=new URL(url);return parsed.hostname.replace(/\./g,"_")}catch{return url.replace(/[^a-zA-Z0-9]/g,"_")}}function extractScopes(options){if(isTransparentMode(options)){return options.requiredScopes||[]}if(isOrchestratedMode(options)){if(isOrchestratedRemote(options)){return options.scopes||[]}}return[]}function detectAuthProviders(parentAuth,apps){const providers=new Map;const validationErrors=[];const warnings=[];let parentProviderId;if(parentAuth){parentProviderId=deriveProviderId(parentAuth);providers.set(parentProviderId,{id:parentProviderId,providerUrl:getProviderUrl(parentAuth),mode:parentAuth.mode,appIds:["__parent__"],scopes:extractScopes(parentAuth),isParentProvider:true})}for(const app of apps){if(!app.auth){continue}const providerId=deriveProviderId(app.auth);const existing=providers.get(providerId);if(existing){existing.appIds.push(app.id);const newScopes=extractScopes(app.auth);existing.scopes=[...new Set([...existing.scopes,...newScopes])]}else{providers.set(providerId,{id:providerId,providerUrl:getProviderUrl(app.auth),mode:app.auth.mode,appIds:[app.id],scopes:extractScopes(app.auth),isParentProvider:false})}}const childProviderIds=[...providers.keys()].filter(id=>id!==parentProviderId);const uniqueProviderCount=providers.size;const hasMultipleProviders=uniqueProviderCount>1;const hasChildOnlyProviders=childProviderIds.length>0&&!parentProviderId;const requiresOrchestration=hasMultipleProviders||hasChildOnlyProviders||childProviderIds.length>0&&parentProviderId!==void 0;if(requiresOrchestration&&parentAuth&&isTransparentMode(parentAuth)){validationErrors.push(`Invalid auth configuration: Parent uses transparent mode but apps have their own auth providers. Transparent mode passes tokens through without modification, which is incompatible with multi-provider setups. Change parent auth to local or remote mode to properly manage tokens for each provider. Detected providers: ${[...providers.keys()].join(", ")}`)}const sharedEndpointUnenforced=!parentAuth||isPublicMode(parentAuth)||isStaticMode(parentAuth);const unenforcedAppIds=apps.filter(app=>app.auth&&!isPublicMode(app.auth)&&!staticParentEnforces(parentAuth,app.auth)).map(app=>app.id);if(sharedEndpointUnenforced&&unenforcedAppIds.length>0){validationErrors.push(`App-level auth is not enforced on the shared endpoint of a server in ${parentAuth?.mode??"public"} mode, so the tools of ${unenforcedAppIds.join(", ")} would be served without it. Serve the app on its own endpoint with standalone: true or splitByApp: true, or run the server in local or remote mode with incrementalAuth enabled, which checks each tool call against the apps the caller has authorized (without incrementalAuth, local and remote mode do not check app grants per tool call).`)}const tokenMintingAppIds=apps.filter(app=>app.remoteAuthMode==="forward"&&!(parentAuth&&isTransparentMode(parentAuth))).map(app=>app.id);if(tokenMintingAppIds.length>0){validationErrors.push(`remoteAuth: { mode: 'forward' } on ${tokenMintingAppIds.join(", ")} would send the remote a token this server minted or holds itself: only a server in transparent mode receives its callers' tokens from the identity provider. Use remoteAuth: { mode: 'static', credentials } (or transportOptions.headers) for the remote, or run the server in transparent mode.`)}return{providers,requiresOrchestration,parentProviderId,childProviderIds,uniqueProviderCount,validationErrors,warnings}}function staticParentEnforces(parentAuth,appAuth){if(!parentAuth||!isStaticMode(parentAuth)||!isStaticMode(appAuth))return false;const parent=staticAuthOptionsSchema.safeParse(parentAuth);const app=staticAuthOptionsSchema.safeParse(appAuth);if(!parent.success||!app.success)return false;const sameChallenge=parent.data.header.toLowerCase()===app.data.header.toLowerCase()&&parent.data.scheme===app.data.scheme;return sameChallenge&&parent.data.tokens.every(token=>app.data.tokens.includes(token))}function getProviderUrl(options){if(isTransparentMode(options)){return options.provider}if(isOrchestratedMode(options)&&isOrchestratedRemote(options)){return options.provider}return void 0}function appRequiresOrchestration(appAuth,parentAuth){if(!appAuth){return false}if(!parentAuth){return appAuth.mode!=="public"}const appProviderId=deriveProviderId(appAuth);const parentProviderId=deriveProviderId(parentAuth);return appProviderId!==parentProviderId}function getProviderScopes(detection,providerId){const provider=detection.providers.get(providerId);return provider?.scopes??[]}function getProviderApps(detection,providerId){const provider=detection.providers.get(providerId);return provider?.appIds.filter(id=>id!=="__parent__")??[]}function buildWwwAuthenticate(options={}){const parts=["Bearer"];const params=[];if(options.resourceMetadataUrl){params.push(`resource_metadata="${escapeQuotedString(options.resourceMetadataUrl)}"`)}if(options.realm){params.push(`realm="${escapeQuotedString(options.realm)}"`)}if(options.error){params.push(`error="${options.error}"`)}if(options.errorDescription){params.push(`error_description="${escapeQuotedString(options.errorDescription)}"`)}if(options.errorUri){params.push(`error_uri="${escapeQuotedString(options.errorUri)}"`)}if(options.scope){const scopeValue=Array.isArray(options.scope)?options.scope.join(" "):options.scope;params.push(`scope="${escapeQuotedString(scopeValue)}"`)}if(params.length>0){parts.push(params.join(", "))}return parts.join(" ")}function buildPrmUrl(baseUrl,entryPath,routeBase){const normalizedEntry=normalizePathSegment(entryPath);const normalizedRoute=normalizePathSegment(routeBase);return`${baseUrl}/.well-known/oauth-protected-resource${normalizedEntry}${normalizedRoute}`}function buildUnauthorizedHeader(prmUrl){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl})}function buildInvalidTokenHeader(prmUrl,description){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl,error:"invalid_token",errorDescription:description??"The access token is invalid or expired"})}function buildInsufficientScopeHeader(prmUrl,requiredScopes,description){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl,error:"insufficient_scope",scope:requiredScopes,errorDescription:description??"The request requires higher privileges"})}function buildInvalidRequestHeader(prmUrl,description){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl,error:"invalid_request",errorDescription:description??"The request is missing required parameters"})}function parseWwwAuthenticate(header){const result={};if(!header.toLowerCase().startsWith("bearer")){return result}const paramString=header.substring(6).trim();const params=parseQuotedParams(paramString);for(const[key,value]of params){const unescapedValue=unescapeQuotedString(value);switch(key.toLowerCase()){case"resource_metadata":result.resourceMetadataUrl=unescapedValue;break;case"realm":result.realm=unescapedValue;break;case"error":result.error=unescapedValue;break;case"error_description":result.errorDescription=unescapedValue;break;case"error_uri":result.errorUri=unescapedValue;break;case"scope":result.scope=unescapedValue;break}}return result}function parseQuotedParams(input){const result=[];let i=0;const len=input.length;while(i<len){while(i<len&&(input[i]===" "||input[i]===","||input[i]===" ")){i++}if(i>=len)break;const keyStart=i;while(i<len&&/\w/.test(input[i])){i++}const key=input.slice(keyStart,i);if(!key)break;while(i<len&&(input[i]===" "||input[i]===" ")){i++}if(i>=len||input[i]!=="="){while(i<len&&input[i]!==",")i++;continue}i++;while(i<len&&(input[i]===" "||input[i]===" ")){i++}if(i>=len||input[i]!=='"'){while(i<len&&input[i]!==",")i++;continue}i++;let value="";while(i<len&&input[i]!=='"'){if(input[i]==="\\"&&i+1<len){value+=input[i+1];i+=2}else{value+=input[i];i++}}if(i<len&&input[i]==='"'){i++}result.push([key,value])}return result}function escapeQuotedString(value){return value.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function unescapeQuotedString(value){return value.replace(/\\(.)/g,"$1")}function normalizePathSegment(path){if(!path||path==="/")return"";const normalized=path.startsWith("/")?path:`/${path}`;let end=normalized.length;while(end>0&&normalized[end-1]==="/"){end--}return normalized.slice(0,end)}function validateAudience(tokenAudience,options){const{expectedAudiences,allowNoAudience=false,caseSensitive=true,allowWildcards=false}=options;if(tokenAudience===void 0||tokenAudience===null){if(allowNoAudience){return{valid:true}}return{valid:false,error:"Token is missing audience claim"}}if(expectedAudiences.length===0){return{valid:false,error:"No expected audiences configured - cannot validate token"}}const tokenAuds=Array.isArray(tokenAudience)?tokenAudience:[tokenAudience];for(const tokenAud of tokenAuds){for(const expectedAud of expectedAudiences){if(matchesAudience(tokenAud,expectedAud,caseSensitive,allowWildcards)){return{valid:true,matchedAudience:tokenAud}}}}return{valid:false,error:`Token audience does not match expected audiences. Got: ${tokenAuds.join(", ")}. Expected one of: ${expectedAudiences.join(", ")}`}}function matchesAudience(tokenAud,expectedAud,caseSensitive,allowWildcards){if(caseSensitive){if(tokenAud===expectedAud)return true}else{if(tokenAud.toLowerCase()===expectedAud.toLowerCase())return true}if(allowWildcards&&expectedAud.includes("*")){const wildcardCount=(expectedAud.match(/\*/g)||[]).length;if(wildcardCount>2){return false}const pattern=expectedAud.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^.]*");const regex=new RegExp(`^${pattern}$`,caseSensitive?"":"i");if(regex.test(tokenAud))return true}return false}function createAudienceValidator(options){return audience=>validateAudience(audience,options)}function deriveExpectedAudience(resourceUrl){const audiences=[];try{const url=new URL(resourceUrl);audiences.push(resourceUrl.replace(/\/$/,""));if(url.pathname!=="/"&&url.pathname!==""){audiences.push(url.origin)}}catch{audiences.push(resourceUrl)}return[...new Set(audiences)]}var AudienceValidator=class _AudienceValidator{options;constructor(options={}){this.options={expectedAudiences:[...options.expectedAudiences??[]],allowNoAudience:options.allowNoAudience??false,caseSensitive:options.caseSensitive??true,allowWildcards:options.allowWildcards??false}}validate(audience){return validateAudience(audience,this.options)}addAudiences(...audiences){this.options.expectedAudiences.push(...audiences)}setAudiences(audiences){this.options.expectedAudiences=audiences}static fromResourceUrl(resourceUrl,options={}){return new _AudienceValidator({...options,expectedAudiences:deriveExpectedAudience(resourceUrl)})}};var DEFAULT_ALLOWED_SCOPES=Object.freeze(["openid","profile","email","offline_access"]);function grantScopes(requested,allowed){const patterns=allowed??DEFAULT_ALLOWED_SCOPES;const granted=[];for(const scope of requested){if(!scope||granted.includes(scope))continue;if(patterns.some(pattern=>globMatch(pattern,scope)))granted.push(scope)}return granted}function advertisedScopes(allowed){const scopes=[];for(const scope of allowed??DEFAULT_ALLOWED_SCOPES){if(scope&&!scope.includes("*")&&!scopes.includes(scope))scopes.push(scope)}return scopes}function resourceScopesFor(options){const mode=options?.mode??"public";if(mode==="local"||mode==="remote")return advertisedScopes(options?.allowedScopes);const listed=mode==="public"?options?.anonymousScopes??["anonymous"]:mode==="static"?options?.scopes??["static"]:mode==="transparent"?[...options?.requiredScopes??[],...options?.scopes??[]]:[];return advertisedScopes(listed)}function isLoopbackRedirectUri(uri){let url;try{url=new URL(uri)}catch{return false}if(url.protocol!=="http:"&&url.protocol!=="https:")return false;if(url.username||url.password)return false;const host=url.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(host==="localhost"||host==="::1")return true;const m=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);if(m){const octets=m.slice(1).map(n=>Number(n));return octets.every(o=>o>=0&&o<=255)&&octets[0]===127}return false}function scopesFromClaims(claims){const scopes=[claims?.scope,claims?.scp].flatMap(claim=>{if(typeof claim==="string")return claim.split(/\s+/).filter(Boolean);if(Array.isArray(claim))return claim.filter(scope=>typeof scope==="string");return[]});return[...new Set(scopes)]}import{internalZ as z19}from"@frontmcp/lazy-zod";var credentialScopeSchema=z19.enum(["global","user","session"]);var loadingStrategySchema=z19.enum(["eager","lazy"]);var getCredentialOptionsSchema=z19.object({forceRefresh:z19.boolean().optional(),scopes:z19.array(z19.string()).optional(),timeout:z19.number().positive().optional()}).strict();var credentialProviderConfigSchema=z19.object({name:z19.string().min(1),description:z19.string().optional(),scope:credentialScopeSchema,loading:loadingStrategySchema,cacheTtl:z19.number().nonnegative().optional(),factory:z19.any(),refresh:z19.any().optional(),toHeaders:z19.any().optional(),metadata:z19.record(z19.string(),z19.unknown()).optional(),required:z19.boolean().optional()}).strict();var authProviderMappingSchema=z19.union([z19.string(),z19.object({name:z19.string().min(1),required:z19.boolean().optional().default(true),scopes:z19.array(z19.string()).optional(),alias:z19.string().optional()}).strict()]);var authProvidersVaultOptionsSchema=z19.object({enabled:z19.boolean().optional(),useSharedStorage:z19.boolean().optional().default(true),namespace:z19.string().optional().default("authproviders:"),defaultCacheTtl:z19.number().nonnegative().optional().default(36e5),maxCredentialsPerSession:z19.number().positive().optional().default(100),providers:z19.array(credentialProviderConfigSchema).optional()}).strict();function extractCredentialExpiry(credential){switch(credential.type){case"oauth":case"oauth_pkce":case"bearer":case"service_account":return credential.expiresAt;default:return void 0}}var CredentialCache=class{cache=new Map;maxSize;stats={hits:0,misses:0,evictions:0,size:0};constructor(maxSize=100){this.maxSize=maxSize}get(providerId){const entry=this.cache.get(providerId);if(!entry){this.stats.misses++;return void 0}if(this.isExpired(entry)){this.cache.delete(providerId);this.stats.size=this.cache.size;this.stats.misses++;this.stats.evictions++;return void 0}if(entry.resolved.expiresAt&&Date.now()>=entry.resolved.expiresAt){this.cache.delete(providerId);this.stats.size=this.cache.size;this.stats.misses++;this.stats.evictions++;return void 0}this.stats.hits++;return entry.resolved}set(providerId,resolved,ttl=0){if(this.cache.size>=this.maxSize&&!this.cache.has(providerId)){this.evictOldest()}const entry={resolved,cachedAt:Date.now(),ttl};this.cache.set(providerId,entry);this.stats.size=this.cache.size}has(providerId){const entry=this.cache.get(providerId);if(!entry)return false;if(this.isExpired(entry)){this.cache.delete(providerId);this.stats.size=this.cache.size;this.stats.evictions++;return false}return true}invalidate(providerId){const deleted=this.cache.delete(providerId);if(deleted){this.stats.size=this.cache.size}return deleted}invalidateAll(){this.cache.clear();this.stats.size=0}invalidateByScope(scope){for(const[key,entry]of this.cache){if(entry.resolved.scope===scope){this.cache.delete(key)}}this.stats.size=this.cache.size}keys(){this.cleanup();return[...this.cache.keys()]}get size(){return this.cache.size}getStats(){return{...this.stats}}resetStats(){this.stats={hits:0,misses:0,evictions:0,size:this.cache.size}}cleanup(){const now=Date.now();for(const[key,entry]of this.cache){if(this.isExpiredAt(entry,now)){this.cache.delete(key);this.stats.evictions++}}this.stats.size=this.cache.size}isExpired(entry){return this.isExpiredAt(entry,Date.now())}isExpiredAt(entry,now){if(entry.ttl>0&&now-entry.cachedAt>=entry.ttl){return true}if(entry.resolved.expiresAt&&now>=entry.resolved.expiresAt){return true}if(!entry.resolved.isValid){return true}return false}evictOldest(){let oldestKey;let oldestTime=Infinity;for(const[key,entry]of this.cache){if(entry.cachedAt<oldestTime){oldestTime=entry.cachedAt;oldestKey=key}}if(oldestKey){this.cache.delete(oldestKey);this.stats.evictions++;this.stats.size=this.cache.size}}};var AUTH_PROVIDERS_ACCESSOR=Symbol.for("frontmcp:AUTH_PROVIDERS_ACCESSOR");import{base64Encode}from"@frontmcp/utils";var AuthProvidersAccessorImpl=class{constructor(registry,vault,cache,loader,context,logger){this.registry=registry;this.vault=vault;this.cache=cache;this.loader=loader;this.context=context;this.logger=logger}registry;vault;cache;loader;context;logger;async get(providerName,options){const config=this.registry.get(providerName);if(!config){this.logger?.debug(`Provider "${providerName}" is not registered`);return null}if(!options?.forceRefresh){const cached=this.cache.get(providerName);if(cached&&this.isValid(cached)){this.logger?.debug(`Cache hit for "${providerName}"`);return cached}}if(!options?.forceRefresh){const fromVault=await this.loadFromVault(providerName,config);if(fromVault&&this.isValid(fromVault)){this.cache.set(providerName,fromVault,config.cacheTtl);this.logger?.debug(`Vault hit for "${providerName}"`);return fromVault}}const loaded=await this.loader.load(config,this.context);if(!loaded){return null}await this.storeInVault(providerName,loaded,config);this.cache.set(providerName,loaded,config.cacheTtl);return loaded}async getMany(providerNames,options){const results=new Map;const promises=providerNames.map(async name=>{const result=await this.get(name,options);return{name,result}});const settled=await Promise.allSettled(promises);for(const outcome of settled){if(outcome.status==="fulfilled"){results.set(outcome.value.name,outcome.value.result)}else{this.logger?.warn("Failed to load credential:",outcome.reason)}}return results}async headers(providerName){const resolved=await this.get(providerName);if(!resolved){return{}}const config=this.registry.get(providerName);if(config?.toHeaders){return config.toHeaders(resolved.credential)}return this.defaultHeaders(resolved.credential)}async headersMany(providerNames){const allHeaders={};for(const name of providerNames){const headers=await this.headers(name);Object.assign(allHeaders,headers)}return allHeaders}async refresh(providerName){const config=this.registry.get(providerName);if(!config){this.logger?.debug(`Cannot refresh: provider "${providerName}" not registered`);return null}const existing=this.cache.get(providerName);const existingCredential=existing?.credential;try{const refreshed=await this.loader.refresh(config,{...this.context,existingCredential});if(!refreshed){return null}await this.storeInVault(providerName,refreshed,config);this.cache.set(providerName,refreshed,config.cacheTtl);return refreshed}catch(error){this.logger?.warn(`Failed to refresh credential "${providerName}":`,error);return null}}async has(providerName){const cached=this.cache.get(providerName);if(cached){if(this.isValid(cached)){return true}this.cache.invalidate(providerName)}const config=this.registry.get(providerName);if(!config){return false}const fromVault=await this.loadFromVault(providerName,config);return fromVault!==null&&this.isValid(fromVault)}isRegistered(providerName){return this.registry.has(providerName)}invalidate(providerName){this.cache.invalidate(providerName);this.logger?.debug(`Invalidated cache for "${providerName}"`)}invalidateAll(){this.cache.invalidateAll();this.logger?.debug("Invalidated all cached credentials")}listProviders(){return this.registry.getNames()}async listAvailable(){const available=[];for(const name of this.registry.getNames()){if(await this.has(name)){available.push(name)}}return available}async loadFromVault(providerName,config){try{const credential=await this.vault.getCredential(this.context.sessionId,providerName,config.scope,this.context.userSub);if(!credential){return null}return{credential,providerId:providerName,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope}}catch(error){this.logger?.warn(`Failed to load from vault "${providerName}":`,error);return null}}async storeInVault(providerName,resolved,config){try{await this.vault.storeCredential(this.context.sessionId,providerName,resolved.credential,config.scope,this.context.userSub,config.cacheTtl)}catch(error){this.logger?.warn(`Failed to store in vault "${providerName}":`,error)}}isValid(resolved){if(!resolved.isValid){return false}if(resolved.expiresAt&&Date.now()>=resolved.expiresAt){return false}return true}defaultHeaders(credential){switch(credential.type){case"oauth":case"oauth_pkce":return{Authorization:`${credential.tokenType} ${credential.accessToken}`};case"bearer":return{Authorization:`Bearer ${credential.token}`};case"api_key":if(credential.headerPrefix){return{[credential.headerName]:`${credential.headerPrefix}${credential.key}`}}return{[credential.headerName]:credential.key};case"basic":{const value=`${credential.username}:${credential.password}`;const encoded=credential.encodedValue??base64Encode(new TextEncoder().encode(value));return{Authorization:`Basic ${encoded}`}}case"custom":return credential.headers??{};case"ssh_key":case"mtls":case"private_key":case"service_account":return{};default:return{}}}};var AuthProvidersRegistry=class{providers=new Map;defaultCacheTtl;constructor(options){this.defaultCacheTtl=options?.defaultCacheTtl??36e5;if(options?.providers){for(const provider of options.providers){this.register(provider)}}}register(config){if(this.providers.has(config.name)){throw new CredentialProviderAlreadyRegisteredError(config.name)}const normalized=this.normalize(config);this.providers.set(config.name,normalized)}unregister(name){return this.providers.delete(name)}get(name){return this.providers.get(name)}has(name){return this.providers.has(name)}getNames(){return[...this.providers.keys()]}getAll(){return[...this.providers.values()]}getByScope(scope){return this.getAll().filter(p=>p.scope===scope)}getByLoading(loading){return this.getAll().filter(p=>p.loading===loading)}getRequired(){return this.getAll().filter(p=>p.required)}getEager(){return this.getByLoading("eager")}getLazy(){return this.getByLoading("lazy")}get size(){return this.providers.size}isEmpty(){return this.providers.size===0}clear(){this.providers.clear()}normalize(config){return{name:config.name,description:config.description,scope:config.scope,loading:config.loading,cacheTtl:config.cacheTtl??this.defaultCacheTtl,factory:config.factory,refresh:config.refresh,toHeaders:config.toHeaders,metadata:config.metadata,required:config.required??false}}};var AUTH_PROVIDERS_REGISTRY=Symbol.for("frontmcp:AUTH_PROVIDERS_REGISTRY");import{EncryptedTypedStorage as EncryptedTypedStorage2,hkdfSha256 as hkdfSha2566,MAX_TTL_SECONDS,randomBytes as randomBytes9,sha256Hex as sha256Hex6}from"@frontmcp/utils";var UNIDENTIFIED_SESSION_PREFIX="unidentified:";var DEFAULT_CREDENTIAL_TTL_MS=36e5;var ENCRYPTION_KEY_INFO=new TextEncoder().encode("frontmcp-auth-providers-vault-v1");function positiveOr(value,fallback){return value!==void 0&&value>0?value:fallback}function encryptionKeyFrom(pepper){if(!pepper)return randomBytes9(32);return hkdfSha2566(new TextEncoder().encode(pepper),new Uint8Array(0),ENCRYPTION_KEY_INFO,32)}var AuthProvidersVault=class{credentials;namespace;defaultCacheTtl;logger;constructor(storage,options={}){this.credentials=new EncryptedTypedStorage2(storage,{keys:[{kid:"authproviders-v1",key:encryptionKeyFrom(options.pepper)}]});this.namespace=options.namespace??"authproviders:";this.defaultCacheTtl=positiveOr(options.defaultCacheTtl,DEFAULT_CREDENTIAL_TTL_MS);this.logger=options.logger}async storeCredential(sessionId,providerId,credential,scope,userId,cacheTtl){if(scope==="session"&&sessionId.startsWith(UNIDENTIFIED_SESSION_PREFIX)){this.logger?.debug(`Not storing credential "${providerId}": the caller has no verified session`);return}const key=this.credentialKey(sessionId,providerId,scope,userId);const appCredential={appId:this.namespace,providerId,credential,acquiredAt:Date.now(),isValid:true,expiresAt:extractCredentialExpiry(credential)};try{await this.write(key,appCredential,cacheTtl);this.logger?.debug(`Stored credential "${providerId}" with scope "${scope}"`)}catch(error){this.logger?.warn(`Failed to store credential "${providerId}":`,error);throw error}}async getCredential(sessionId,providerId,scope,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{const appCredential=await this.read(key);if(!appCredential){return null}if(!appCredential.isValid){this.logger?.debug(`Credential "${providerId}" is marked as invalid`);return null}if(appCredential.expiresAt&&Date.now()>=appCredential.expiresAt){this.logger?.debug(`Credential "${providerId}" has expired`);return null}return appCredential.credential}catch(error){this.logger?.warn(`Failed to get credential "${providerId}":`,error);if(error instanceof CredentialStorageError){throw error}throw new CredentialStorageError(`Storage error while retrieving credential "${providerId}": ${error instanceof Error?error.message:String(error)}`)}}async removeCredential(sessionId,providerId,scope,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{await this.credentials.delete(key);this.logger?.debug(`Removed credential "${providerId}"`)}catch(error){this.logger?.warn(`Failed to remove credential "${providerId}":`,error)}}async invalidateCredential(sessionId,providerId,scope,reason,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{const appCredential=await this.read(key);if(!appCredential)return;await this.write(key,{...appCredential,isValid:false,invalidReason:reason});this.logger?.debug(`Invalidated credential "${providerId}": ${reason}`)}catch(error){this.logger?.warn(`Failed to invalidate credential "${providerId}":`,error)}}async refreshOAuthCredential(sessionId,providerId,scope,tokens,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{const appCredential=await this.read(key);if(!appCredential)return;const credential=appCredential.credential;if(credential.type!=="oauth"&&credential.type!=="oauth_pkce")return;const refreshed={...appCredential,credential:{...credential,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??credential.refreshToken,expiresAt:tokens.expiresAt??credential.expiresAt},expiresAt:tokens.expiresAt??appCredential.expiresAt,isValid:true,invalidReason:void 0};await this.write(key,refreshed);this.logger?.debug(`Refreshed OAuth credential "${providerId}"`)}catch(error){this.logger?.warn(`Failed to refresh OAuth credential "${providerId}":`,error);throw error}}async getAllCredentials(sessionId,scope,userId){if(scope){return this.listCredentials(this.buildVaultKey(sessionId,scope,userId))}const scopes=userId?["session","user","global"]:["session","global"];const credentialsPerScope=await Promise.all(scopes.map(eachScope=>this.listCredentials(this.buildVaultKey(sessionId,eachScope,userId))));return credentialsPerScope.flat()}async listCredentials(vaultKey){try{const keys=await this.credentials.keys(`${vaultKey}:*`);const records=await this.credentials.mget(keys);return records.flatMap((record,index)=>record?.storageKey===keys[index]?[record.appCredential]:[])}catch{return[]}}async read(key){const record=await this.credentials.get(key);return record?.storageKey===key?record.appCredential:null}async write(key,appCredential,cacheTtl){const ttlMs=appCredential.expiresAt===void 0?positiveOr(cacheTtl,this.defaultCacheTtl):appCredential.expiresAt-Date.now();if(ttlMs<=0){await this.credentials.delete(key);return}const ttlSeconds=Math.min(Math.ceil(ttlMs/1e3),MAX_TTL_SECONDS);await this.credentials.set(key,{storageKey:key,appCredential},{ttlSeconds})}credentialKey(sessionId,providerId,scope,userId){return`${this.buildVaultKey(sessionId,scope,userId)}:${providerId}`}buildVaultKey(sessionId,scope,userId){switch(scope){case"global":return`${this.namespace}global`;case"user":if(!userId){throw new AuthInvalidInputError(`userId is required for user-scoped credentials (namespace: ${this.namespace})`)}return`${this.namespace}user:${sha256Hex6(userId)}`;case"session":return`${this.namespace}session:${sha256Hex6(sessionId)}`;default:{const _exhaustive=scope;throw new AuthInvalidInputError(`Unknown credential scope: ${_exhaustive}`)}}}};var AUTH_PROVIDERS_VAULT=Symbol.for("frontmcp:AUTH_PROVIDERS_VAULT");var EagerCredentialLoader=class{constructor(registry,cache,logger){this.registry=registry;this.cache=cache;this.logger=logger}registry;cache;logger;async loadForSession(context){const startTime=Date.now();const eagerProviders=this.registry.getEager();if(eagerProviders.length===0){return{loaded:new Map,failed:new Map,duration:Date.now()-startTime}}this.logger?.debug(`Loading ${eagerProviders.length} eager credential providers`);const loaded=new Map;const failed=new Map;const results=await Promise.allSettled(eagerProviders.map(async config=>{const credential=await this.loadOne(config,context);return{name:config.name,credential,config}}));for(const result of results){if(result.status==="fulfilled"){const{name,credential,config}=result.value;if(credential){const resolved=this.wrapCredential(name,credential,config);loaded.set(name,resolved);this.cache.set(name,resolved,config.cacheTtl);this.logger?.debug(`Loaded eager credential: ${name}`)}else if(config.required){failed.set(name,new Error(`Required credential "${name}" returned null`));this.logger?.warn(`Required eager credential "${name}" returned null`)}}else{const providerName=result.reason?.providerName||"unknown";const error=result.reason instanceof Error?result.reason:new Error(String(result.reason));failed.set(providerName,error);this.logger?.warn(`Failed to load eager credential "${providerName}":`,error.message)}}const duration=Date.now()-startTime;this.logger?.debug(`Eager loading complete: ${loaded.size} loaded, ${failed.size} failed, ${duration}ms`);return{loaded,failed,duration}}async loadOne(config,context){try{return await config.factory({...context,metadata:config.metadata})}catch(error){error.providerName=config.name;throw error}}wrapCredential(providerId,credential,config){return{credential,providerId,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope}}};function inFlightKey(providerName,context){return JSON.stringify([providerName,context.sessionId,context.userSub??null])}var LazyCredentialLoader=class{constructor(logger){this.logger=logger}logger;loading=new Map;async load(config,context){const key=inFlightKey(config.name,context);const existing=this.loading.get(key);if(existing){this.logger?.debug(`Waiting for in-flight load: ${config.name}`);return existing}const promise=this.doLoad(config,context);this.loading.set(key,promise);try{return await promise}finally{this.loading.delete(key)}}async doLoad(config,context){const startTime=Date.now();this.logger?.debug(`Loading lazy credential: ${config.name}`);try{const credential=await config.factory({...context,metadata:config.metadata});if(!credential){this.logger?.debug(`Lazy credential "${config.name}" returned null`);return null}const resolved={credential,providerId:config.name,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope};const duration=Date.now()-startTime;this.logger?.debug(`Loaded lazy credential "${config.name}" in ${duration}ms`);return resolved}catch(error){const duration=Date.now()-startTime;this.logger?.warn(`Failed to load lazy credential "${config.name}" after ${duration}ms:`,error);throw error}}async refresh(config,context){const startTime=Date.now();this.logger?.debug(`Refreshing credential: ${config.name}`);try{let credential;if(config.refresh){credential=await config.refresh(context)}else{credential=await config.factory(context)}if(!credential){this.logger?.debug(`Credential refresh "${config.name}" returned null`);return null}const resolved={credential,providerId:config.name,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope};const duration=Date.now()-startTime;this.logger?.debug(`Refreshed credential "${config.name}" in ${duration}ms`);return resolved}catch(error){const duration=Date.now()-startTime;this.logger?.warn(`Failed to refresh credential "${config.name}" after ${duration}ms:`,error);throw error}}isLoading(name,context){return this.loading.has(inFlightKey(name,context))}cancelAll(){this.loading.clear()}};import{getEnv as getEnv4}from"@frontmcp/utils";function resolveDotPath(obj,path){if(path in obj){return obj[path]}const segments=path.split(".");let current=obj;for(const segment of segments){if(current===null||current===void 0||typeof current!=="object"){return void 0}current=current[segment]}return current}function toStringArray(value){if(Array.isArray(value)){return value.filter(v=>typeof v==="string")}if(typeof value==="string"){return value.split(/\s+/).filter(Boolean)}return[]}var noopRelationshipResolver={async check(){return false}};function resolveAuthUser(authInfo){const extraUser=authInfo?.extra?.["user"];if(authInfo?.user)return authInfo.user;return typeof extraUser==="object"&&extraUser!==null?extraUser:{}}function isAnonymousSubject(sub){return typeof sub!=="string"||sub===""||sub.startsWith("anon:")}function signedInSubject(rawSub,mappedSub){if(rawSub!==void 0&&isAnonymousSubject(rawSub))return void 0;return[mappedSub,rawSub].find(candidate=>!isAnonymousSubject(candidate))}var runtimeEnv=new Proxy({},{get:(_target,name)=>typeof name==="string"?getEnv4(name):void 0});var AuthoritiesContextBuilder=class{claimsMapping;claimsResolver;relationshipResolver;constructor(options={}){this.claimsMapping=options.claimsMapping;this.claimsResolver=options.claimsResolver;this.relationshipResolver=options.relationshipResolver??noopRelationshipResolver}build(authInfo,input={},env=runtimeEnv){if(this.claimsResolver){const resolved=this.claimsResolver(authInfo);return{user:{sub:signedInSubject(resolveAuthUser(authInfo).sub),roles:resolved.roles,permissions:resolved.permissions,claims:resolved.claims},input,env,relationships:this.relationshipResolver}}const user=resolveAuthUser(authInfo);const authorization=authInfo?.extra?.["authorization"];const authorizationClaims=authorization?.["claims"]??{};const rawClaims={...authorizationClaims,...user};let roles;if(this.claimsMapping?.roles){roles=toStringArray(resolveDotPath(rawClaims,this.claimsMapping.roles))}else{roles=toStringArray(user["roles"]??authorization?.["scopes"]??[])}let permissions;if(this.claimsMapping?.permissions){permissions=toStringArray(resolveDotPath(rawClaims,this.claimsMapping.permissions))}else{permissions=toStringArray(user["permissions"]??[])}const mappedSub=this.claimsMapping?.userId?resolveDotPath(rawClaims,this.claimsMapping.userId):void 0;return{user:{sub:signedInSubject(user.sub,mappedSub),roles,permissions,claims:rawClaims},input,env,relationships:this.relationshipResolver}}};function toStringArray2(value){if(Array.isArray(value)){return value.filter(v=>typeof v==="string")}if(typeof value==="string"){return value.split(/\s+/).filter(Boolean)}return[]}var FrontMcpAuthContextImpl=class{user;isAnonymous;mode;sessionId;scopes;claims;roles;permissions;constructor(source,claimsMapping,claimsResolver){const rawUser=resolveAuthUser(source);const mappedSub=!claimsResolver&&claimsMapping?.userId?resolveDotPath(this.buildRawClaims(source),claimsMapping.userId):void 0;const signedInSub=signedInSubject(rawUser.sub,mappedSub);const sub=signedInSub??(typeof rawUser.sub==="string"?rawUser.sub:"");this.user=Object.freeze({sub,name:rawUser.name,email:rawUser.email,picture:rawUser.picture});this.isAnonymous=signedInSub===void 0;const extraAuth=source.extra?.["authorization"];this.mode=String(source.extra?.["authMode"]??extraAuth?.["mode"]??(source.extra?.["isPublic"]===true||rawUser["anonymous"]===true?"public":"authenticated"));this.sessionId=typeof source.sessionId==="string"?source.sessionId:"";this.scopes=Object.freeze(Array.isArray(source.scopes)?source.scopes.filter(s=>typeof s==="string"):[]);if(claimsResolver){const resolved=claimsResolver(source);this.claims=Object.freeze({...resolved.claims});this.roles=Object.freeze([...toStringArray2(resolved.roles)]);this.permissions=Object.freeze([...toStringArray2(resolved.permissions)]);return}const rawClaims=this.buildRawClaims(source);this.claims=Object.freeze({...rawClaims});if(claimsMapping?.roles){this.roles=Object.freeze([...toStringArray2(resolveDotPath(rawClaims,claimsMapping.roles))])}else{this.roles=Object.freeze([...toStringArray2(rawUser["roles"]??extraAuth?.["scopes"]??[])])}if(claimsMapping?.permissions){this.permissions=Object.freeze([...toStringArray2(resolveDotPath(rawClaims,claimsMapping.permissions))])}else{this.permissions=Object.freeze([...toStringArray2(rawUser["permissions"]??[])])}}hasRole(role){return this.roles.includes(role)}hasAllRoles(roles){return roles.every(r=>this.roles.includes(r))}hasAnyRole(roles){return roles.some(r=>this.roles.includes(r))}hasPermission(permission){return this.permissions.includes(permission)}hasAllPermissions(permissions){return permissions.every(p=>this.permissions.includes(p))}hasAnyPermission(permissions){return permissions.some(p=>this.permissions.includes(p))}hasScope(scope){return this.scopes.includes(scope)}hasAllScopes(scopes){return scopes.every(s=>this.scopes.includes(s))}hasAnyScope(scopes){return scopes.some(s=>this.scopes.includes(s))}buildRawClaims(source){const rawUser=resolveAuthUser(source);const extraAuth=source.extra?.["authorization"];const authorizationClaims=extraAuth?.["claims"]??{};return{...authorizationClaims,...rawUser}}};function buildAuthContext(authInfo,claimsMapping,pipes,claimsResolver){const base=new FrontMcpAuthContextImpl(authInfo,claimsMapping,claimsResolver);if(!pipes||pipes.length===0){return base}return runPipes(base,pipes)}async function runPipes(base,pipes){const extensions={};for(const pipe of pipes){try{const result=await pipe(base.claims);if(result&&typeof result==="object"){Object.assign(extensions,result)}}catch(error){const message=error instanceof Error?error.message:String(error);console.warn(`[FrontMcpAuth] pipe failed: ${message}`)}}const merged=Object.create(Object.getPrototypeOf(base));Object.assign(merged,base,extensions);return Object.freeze(merged)}function resolveRequiredScopes(denial,mapping,authorities){const scopes=new Set;if(typeof authorities==="string"&&mapping.profiles?.[authorities]){mapping.profiles[authorities].forEach(s=>scopes.add(s))}if(Array.isArray(authorities)){for(const profileName of authorities){if(typeof profileName==="string"&&mapping.profiles?.[profileName]){mapping.profiles[profileName].forEach(s=>scopes.add(s))}}}if(denial.kind==="roles"&&denial.missing&&mapping.roles){for(const role of denial.missing){const mapped=mapping.roles[role];if(mapped)mapped.forEach(s=>scopes.add(s))}}if(denial.kind==="permissions"&&denial.missing&&mapping.permissions){for(const perm of denial.missing){const mapped=mapping.permissions[perm];if(mapped)mapped.forEach(s=>scopes.add(s))}}return scopes.size>0?[...scopes].sort():void 0}import{z as z20}from"@frontmcp/lazy-zod";var rbacRolesPolicySchema=z20.object({all:z20.array(z20.string().min(1)).optional(),any:z20.array(z20.string().min(1)).optional()}).refine(data=>data.all!==void 0||data.any!==void 0,{message:'Roles policy must have at least "all" or "any"'});var rbacPermissionsPolicySchema=z20.object({all:z20.array(z20.string().min(1)).optional(),any:z20.array(z20.string().min(1)).optional()}).refine(data=>data.all!==void 0||data.any!==void 0,{message:'Permissions policy must have at least "all" or "any"'});var abacOperatorSchema=z20.enum(["eq","neq","in","notIn","gt","gte","lt","lte","contains","startsWith","endsWith","exists","matches"]);var abacConditionSchema=z20.object({path:z20.string().min(1),op:abacOperatorSchema,value:z20.unknown()});var abacPolicySchema=z20.object({match:z20.record(z20.string(),z20.unknown()).optional(),conditions:z20.array(abacConditionSchema).optional()}).refine(data=>data.match!==void 0||data.conditions!==void 0,{message:'Attributes policy must have at least "match" or "conditions"'});var resourceIdRefSchema=z20.union([z20.string().min(1),z20.object({fromInput:z20.string().min(1)}).strict(),z20.object({fromClaims:z20.string().min(1)}).strict()]);var rebacPolicySchema=z20.object({type:z20.string().min(1),resource:z20.string().min(1),resourceId:resourceIdRefSchema});var basePolicyShape={roles:rbacRolesPolicySchema.optional(),permissions:rbacPermissionsPolicySchema.optional(),attributes:abacPolicySchema.optional(),relationships:z20.union([rebacPolicySchema,z20.array(rebacPolicySchema)]).optional(),custom:z20.record(z20.string(),z20.unknown()).optional(),guards:z20.array(z20.instanceof(Function)).optional(),operator:z20.enum(["AND","OR"]).optional()};var authoritiesPolicySchema=z20.lazy(()=>z20.object({...basePolicyShape,not:authoritiesPolicySchema.optional(),anyOf:z20.array(authoritiesPolicySchema).optional(),allOf:z20.array(authoritiesPolicySchema).optional()}));var authoritiesMetadataSchema=z20.union([z20.string().min(1),z20.array(z20.string().min(1)).min(1),authoritiesPolicySchema]);var authoritiesClaimsMappingSchema=z20.record(z20.string(),z20.string().optional()).refine(data=>Object.values(data).some(v=>v!==void 0),{message:"Claims mapping must have at least one mapping"});var authoritiesConfigSchema=z20.object({claimsMapping:authoritiesClaimsMappingSchema.optional(),profiles:z20.record(z20.string().min(1),authoritiesPolicySchema).optional()});function evaluateRbacRoles(policy,ctx){const userRoles=ctx.user.roles;if(policy.all){const missing=policy.all.filter(r=>!userRoles.includes(r));if(missing.length>0){return{granted:false,deniedBy:`roles.all: missing ${missing.map(r=>`'${r}'`).join(", ")}`,denial:{kind:"roles",path:"roles.all",missing},evaluatedPolicies:["roles.all"]}}}if(policy.any){const hasAny=policy.any.some(r=>userRoles.includes(r));if(!hasAny){return{granted:false,deniedBy:`roles.any: user has none of ${policy.any.map(r=>`'${r}'`).join(", ")}`,denial:{kind:"roles",path:"roles.any",missing:policy.any},evaluatedPolicies:["roles.any"]}}}return{granted:true,evaluatedPolicies:["roles"]}}function evaluateRbacPermissions(policy,ctx){const userPerms=ctx.user.permissions;if(policy.all){const missing=policy.all.filter(p=>!userPerms.includes(p));if(missing.length>0){return{granted:false,deniedBy:`permissions.all: missing ${missing.map(p=>`'${p}'`).join(", ")}`,denial:{kind:"permissions",path:"permissions.all",missing},evaluatedPolicies:["permissions.all"]}}}if(policy.any){const hasAny=policy.any.some(p=>userPerms.includes(p));if(!hasAny){return{granted:false,deniedBy:`permissions.any: user has none of ${policy.any.map(p=>`'${p}'`).join(", ")}`,denial:{kind:"permissions",path:"permissions.any",missing:policy.any},evaluatedPolicies:["permissions.any"]}}}return{granted:true,evaluatedPolicies:["permissions"]}}function buildContextEnvelope(ctx){return{user:ctx.user,claims:ctx.user.claims,input:ctx.input,env:ctx.env}}function resolveValue(value,ctx){if(value!==null&&typeof value==="object"&&!Array.isArray(value)){const obj=value;if("fromInput"in obj&&typeof obj["fromInput"]==="string"){return ctx.input[obj["fromInput"]]}if("fromClaims"in obj&&typeof obj["fromClaims"]==="string"){return resolveDotPath(ctx.user.claims,obj["fromClaims"])}}return value}function applyOperator(actual,op,expected){switch(op){case"eq":return expected!==void 0&&actual===expected;case"neq":return actual!==expected;case"in":return Array.isArray(expected)&&expected.includes(actual);case"notIn":return Array.isArray(expected)&&!expected.includes(actual);case"gt":return typeof actual==="number"&&typeof expected==="number"&&actual>expected;case"gte":return typeof actual==="number"&&typeof expected==="number"&&actual>=expected;case"lt":return typeof actual==="number"&&typeof expected==="number"&&actual<expected;case"lte":return typeof actual==="number"&&typeof expected==="number"&&actual<=expected;case"contains":if(typeof actual==="string"&&typeof expected==="string")return actual.includes(expected);if(Array.isArray(actual))return actual.includes(expected);return false;case"startsWith":return typeof actual==="string"&&typeof expected==="string"&&actual.startsWith(expected);case"endsWith":return typeof actual==="string"&&typeof expected==="string"&&actual.endsWith(expected);case"exists":return expected?actual!==void 0&&actual!==null:actual===void 0||actual===null;case"matches":if(typeof actual!=="string"||typeof expected!=="string")return false;if(expected.length>256)return false;if(/(\+\+|\*\*|\{\d+,\d*\}\{)/.test(expected))return false;try{return new RegExp(expected).test(actual)}catch{return false}default:return false}}function evaluateCondition(condition,envelope,ctx){const actual=resolveDotPath(envelope,condition.path);const expected=resolveValue(condition.value,ctx);return applyOperator(actual,condition.op,expected)}var REDACTED_ENV_VALUE="[redacted]";function reportableActual(path,actual){return path==="env"||path.startsWith("env.")?REDACTED_ENV_VALUE:actual}function evaluateAbac(policy,ctx){const envelope=buildContextEnvelope(ctx);if(policy.match){for(const[path,expectedRaw]of Object.entries(policy.match)){const actual=resolveDotPath(envelope,path);const expected=resolveValue(expectedRaw,ctx);if(expected===void 0||actual!==expected){const reported=reportableActual(path,actual);return{granted:false,deniedBy:`attributes.match: '${path}' expected '${String(expected)}' but got '${String(reported)}'`,denial:{kind:"attributes",path:"attributes.match",expected,actual:reported},evaluatedPolicies:["attributes.match"]}}}}if(policy.conditions){for(let i=0;i<policy.conditions.length;i++){const condition=policy.conditions[i];if(!evaluateCondition(condition,envelope,ctx)){const expected=resolveValue(condition.value,ctx);const actual=reportableActual(condition.path,resolveDotPath(envelope,condition.path));return{granted:false,deniedBy:`attributes.conditions: '${condition.path}' failed '${condition.op}' check against '${String(expected)}'`,denial:{kind:"attributes",path:`attributes.conditions[${i}]`,expected,actual},evaluatedPolicies:["attributes.conditions"]}}}}return{granted:true,evaluatedPolicies:["attributes"]}}function resolveResourceId(ref,ctx){if(typeof ref==="string")return ref;if("fromInput"in ref){const val=ctx.input[ref.fromInput];return typeof val==="string"?val:void 0}if("fromClaims"in ref){const val=resolveDotPath(ctx.user.claims,ref.fromClaims);return typeof val==="string"?val:void 0}return void 0}async function evaluateSingleRebac(policy,ctx){const userSub=ctx.user.sub;if(userSub===void 0||isAnonymousSubject(userSub)){return{granted:false,deniedBy:`relationships: an anonymous caller cannot be '${policy.type}' of ${policy.resource}`,denial:{kind:"relationships",path:"user.sub",expected:"a signed-in caller",actual:void 0},evaluatedPolicies:["relationships"]}}const resourceId=resolveResourceId(policy.resourceId,ctx);if(resourceId===void 0){return{granted:false,deniedBy:`relationships: could not resolve resourceId for ${policy.resource}`,denial:{kind:"relationships",path:"relationships",expected:`${policy.resource}:resourceId`,actual:void 0},evaluatedPolicies:["relationships"]}}const hasRelationship=await ctx.relationships.check(policy.type,policy.resource,resourceId,userSub,ctx);if(!hasRelationship){return{granted:false,deniedBy:`relationships: user '${userSub}' is not '${policy.type}' of ${policy.resource}:${resourceId}`,denial:{kind:"relationships",path:"relationships",expected:`${policy.type}:${policy.resource}:${resourceId}`},evaluatedPolicies:["relationships"]}}return{granted:true,evaluatedPolicies:["relationships"]}}async function evaluateRebac(policy,ctx){const policies=Array.isArray(policy)?policy:[policy];for(const p of policies){const result=await evaluateSingleRebac(p,ctx);if(!result.granted)return result}return{granted:true,evaluatedPolicies:["relationships"]}}var ABAC_OPERATORS=new Set(["eq","neq","in","notIn","gt","gte","lt","lte","contains","startsWith","endsWith","exists","matches"]);var CHECK_FIELDS=["roles","permissions","attributes","relationships","custom","guards","allOf","anyOf","not"];var RULE_FIELDS=new Set([...CHECK_FIELDS,"operator"]);function isPlainObject(value){return typeof value==="object"&&value!==null&&!Array.isArray(value)}function isNonEmptyString(value){return typeof value==="string"&&value.length>0}function unknownFields(value,known,path){const prefix=path?`${path} has`:"has";return Object.keys(value).filter(key=>!known.has(key)).map(key=>`${prefix} an unknown field "${key}"`)}function checkStringList(value,path){if(!Array.isArray(value))return[`${path} must be a list of names`];if(value.length===0)return[`${path} is empty`];return value.every(isNonEmptyString)?[]:[`${path} must contain only non-empty names`]}var ALL_OR_ANY=new Set(["all","any"]);function checkAllOrAny(value,path){if(!isPlainObject(value))return[`${path} must be an object with "all" or "any"`];const problems=unknownFields(value,ALL_OR_ANY,path);if(value["all"]===void 0&&value["any"]===void 0){problems.push(`${path} needs "all" or "any"`)}if(value["all"]!==void 0)problems.push(...checkStringList(value["all"],`${path}.all`));if(value["any"]!==void 0)problems.push(...checkStringList(value["any"],`${path}.any`));return problems}var ATTRIBUTE_FIELDS=new Set(["match","conditions"]);var CONDITION_FIELDS=new Set(["path","op","value"]);function isValueReference(value){return isPlainObject(value)&&("fromInput"in value||"fromClaims"in value)}function checkValueReference(value,path){const ref=value["fromInput"]??value["fromClaims"];return Object.keys(value).length===1&&isNonEmptyString(ref)?[]:[`${path} must be { fromInput: "<name>" } or { fromClaims: "<path>" }`]}var OPERATOR_VALUES={in:{accepts:Array.isArray,needs:"a list"},notIn:{accepts:Array.isArray,needs:"a list"},gt:{accepts:value=>typeof value==="number",needs:"a number"},gte:{accepts:value=>typeof value==="number",needs:"a number"},lt:{accepts:value=>typeof value==="number",needs:"a number"},lte:{accepts:value=>typeof value==="number",needs:"a number"},startsWith:{accepts:value=>typeof value==="string",needs:"a string"},endsWith:{accepts:value=>typeof value==="string",needs:"a string"},matches:{accepts:value=>typeof value==="string",needs:"a string"}};function checkConditionValue(op,condition,path){const value=condition["value"];if(value===void 0)return[`${path} is missing`];if(op==="exists"){return typeof value==="boolean"?[]:[`${path} must be true or false for "exists"`]}if(isValueReference(value))return checkValueReference(value,path);const expected=OPERATOR_VALUES[op];if(expected&&!expected.accepts(value))return[`${path} must be ${expected.needs} for "${op}"`];if(Array.isArray(value)&&value.length===0&&(op==="in"||op==="notIn"))return[`${path} is empty`];return[]}function checkAttributes(value,path){if(!isPlainObject(value))return[`${path} must be an object with "match" or "conditions"`];const problems=unknownFields(value,ATTRIBUTE_FIELDS,path);const{match,conditions}=value;if(match===void 0&&conditions===void 0){problems.push(`${path} needs "match" or "conditions"`)}if(match!==void 0){if(!isPlainObject(match))problems.push(`${path}.match must be an object`);else if(Object.keys(match).length===0)problems.push(`${path}.match is empty`);else{for(const[attribute,expected]of Object.entries(match)){const at=`${path}.match[${JSON.stringify(attribute)}]`;if(expected===void 0)problems.push(`${at} has no value`);else if(isValueReference(expected))problems.push(...checkValueReference(expected,at))}}}if(conditions!==void 0){if(!Array.isArray(conditions))problems.push(`${path}.conditions must be a list`);else if(conditions.length===0)problems.push(`${path}.conditions is empty`);else{conditions.forEach((condition,index)=>{const at=`${path}.conditions[${index}]`;if(!isPlainObject(condition)){problems.push(`${at} must be an object with "path" and "op"`);return}problems.push(...unknownFields(condition,CONDITION_FIELDS,at));if(!isNonEmptyString(condition["path"]))problems.push(`${at}.path must be a non-empty string`);const op=condition["op"];if(!ABAC_OPERATORS.has(op)){problems.push(`${at}.op "${String(condition["op"])}" is not an operator`)}else{problems.push(...checkConditionValue(op,condition,`${at}.value`))}})}}return problems}var RELATIONSHIP_FIELDS=new Set(["type","resource","resourceId"]);function checkResourceId(value,path){if(isNonEmptyString(value))return[];if(isPlainObject(value)&&Object.keys(value).length===1){const ref=value["fromInput"]??value["fromClaims"];if(isNonEmptyString(ref))return[]}return[`${path} must be a string, { fromInput } or { fromClaims }`]}function checkRelationship(value,path){if(!isPlainObject(value))return[`${path} must be an object with "type", "resource" and "resourceId"`];const problems=unknownFields(value,RELATIONSHIP_FIELDS,path);if(!isNonEmptyString(value["type"]))problems.push(`${path}.type must be a non-empty string`);if(!isNonEmptyString(value["resource"]))problems.push(`${path}.resource must be a non-empty string`);problems.push(...checkResourceId(value["resourceId"],`${path}.resourceId`));return problems}function checkRelationships(value,path){if(!Array.isArray(value))return checkRelationship(value,path);if(value.length===0)return[`${path} is empty`];return value.flatMap((relationship,index)=>checkRelationship(relationship,`${path}[${index}]`))}function checkRuleList(value,path){if(!Array.isArray(value))return[`${path} must be a list of rules`];if(value.length===0)return[`${path} is empty`];return value.flatMap((rule,index)=>checkRule(rule,`${path}[${index}]`))}function checkRule(rule,path){if(!isPlainObject(rule)){return[`${path||"the rule"} must be a rule object${typeof rule==="string"?", not a profile name":""}`]}const problems=unknownFields(rule,RULE_FIELDS,path);if(!CHECK_FIELDS.some(field=>rule[field]!==void 0)){problems.push(`${path?`${path} `:""}checks nothing`)}if(rule["operator"]!==void 0&&rule["operator"]!=="AND"&&rule["operator"]!=="OR"){problems.push(`${path}.operator must be "AND" or "OR"`)}if(rule["roles"]!==void 0)problems.push(...checkAllOrAny(rule["roles"],`${path}.roles`));if(rule["permissions"]!==void 0)problems.push(...checkAllOrAny(rule["permissions"],`${path}.permissions`));if(rule["attributes"]!==void 0)problems.push(...checkAttributes(rule["attributes"],`${path}.attributes`));if(rule["relationships"]!==void 0){problems.push(...checkRelationships(rule["relationships"],`${path}.relationships`))}if(rule["custom"]!==void 0){if(!isPlainObject(rule["custom"]))problems.push(`${path}.custom must be an object`);else if(Object.keys(rule["custom"]).length===0)problems.push(`${path}.custom is empty`)}if(rule["guards"]!==void 0){const guards=rule["guards"];if(!Array.isArray(guards))problems.push(`${path}.guards must be a list of functions`);else if(guards.length===0)problems.push(`${path}.guards is empty`);else if(!guards.every(guard=>typeof guard==="function")){problems.push(`${path}.guards must contain only functions`)}}if(rule["allOf"]!==void 0)problems.push(...checkRuleList(rule["allOf"],`${path}.allOf`));if(rule["anyOf"]!==void 0)problems.push(...checkRuleList(rule["anyOf"],`${path}.anyOf`));if(rule["not"]!==void 0)problems.push(...checkRule(rule["not"],`${path}.not`));return problems}function findAuthoritiesRuleProblems(authorities){if(typeof authorities==="string"){return authorities.length>0?[]:["the profile name is empty"]}if(Array.isArray(authorities)){if(authorities.length===0)return["the list of profiles is empty"];return authorities.every(isNonEmptyString)?[]:["a list of profiles must contain only non-empty profile names"]}if(!isPlainObject(authorities)){return["must be a profile name, a list of profile names, or a rule object"]}return checkRule(authorities,"")}function findAuthoritiesProfileProblems(name,policy){return checkRule(policy,"").map(problem=>`profile "${name}": ${problem.replace(/^\./,"")}`)}function mergeResult(base,...others){const evaluatedPolicies=[...base.evaluatedPolicies];for(const other of others){evaluatedPolicies.push(...other.evaluatedPolicies)}return{...base,evaluatedPolicies}}function invalidRule(problems,policies=[]){return{granted:false,deniedBy:`invalid authorities rule: ${problems.join("; ")}`,evaluatedPolicies:policies}}var AuthoritiesEngine=class{constructor(profiles,evaluators){this.profiles=profiles;this.evaluators=evaluators}profiles;evaluators;ruleProblems=new WeakMap;findRuleProblems(authorities){const problems=findAuthoritiesRuleProblems(authorities);if(problems.length>0)return problems;const names=typeof authorities==="string"?[authorities]:Array.isArray(authorities)?authorities:[];return names.filter(name=>typeof name==="string"&&!this.profiles.has(name)).map(name=>`names an unknown profile "${name}"`)}findProfileProblems(){return Object.entries(this.profiles.getAll()).flatMap(([name,policy])=>findAuthoritiesProfileProblems(name,policy))}async evaluate(authorities,ctx){const problems=this.problemsOf(authorities,findAuthoritiesRuleProblems);if(problems.length>0)return invalidRule(problems);if(typeof authorities==="string"){return this.evaluateProfile(authorities,ctx)}if(Array.isArray(authorities)){return this.evaluateProfileArray(authorities,ctx)}return this.evaluatePolicy(authorities,ctx)}async evaluateProfile(name,ctx){const policy=this.profiles.resolve(name);if(!policy){return{granted:false,deniedBy:`profile '${name}' is not registered`,evaluatedPolicies:[`profile:${name}`]}}const problems=this.problemsOf(policy,rule=>findAuthoritiesProfileProblems(name,rule));if(problems.length>0)return invalidRule(problems,[`profile:${name}`]);const result=await this.evaluatePolicy(policy,ctx);if(!result.granted){return{...result,deniedBy:`profile:${name}: ${result.deniedBy}`,evaluatedPolicies:[`profile:${name}`,...result.evaluatedPolicies]}}return mergeResult(result,{granted:true,evaluatedPolicies:[`profile:${name}`]})}problemsOf(rule,find){if(typeof rule!=="object"||rule===null)return find(rule);let problems=this.ruleProblems.get(rule);if(!problems){problems=find(rule);this.ruleProblems.set(rule,problems)}return problems}async evaluateProfileArray(names,ctx){const allPolicies=[];for(const name of names){const result=await this.evaluateProfile(name,ctx);allPolicies.push(...result.evaluatedPolicies);if(!result.granted){return{...result,evaluatedPolicies:allPolicies}}}return{granted:true,evaluatedPolicies:allPolicies}}async evaluatePolicy(policy,ctx){const operator=policy.operator??"AND";const results=[];if(policy.roles){results.push(evaluateRbacRoles(policy.roles,ctx))}if(policy.permissions){results.push(evaluateRbacPermissions(policy.permissions,ctx))}if(policy.attributes){results.push(evaluateAbac(policy.attributes,ctx))}if(policy.relationships){results.push(await evaluateRebac(policy.relationships,ctx))}if(policy.custom){results.push(await this.evaluateCustom(policy.custom,ctx))}if(policy.guards&&policy.guards.length>0){results.push(await this.evaluateGuards(policy.guards,ctx))}if(policy.allOf){results.push(await this.evaluateAllOf(policy.allOf,ctx))}if(policy.anyOf){results.push(await this.evaluateAnyOf(policy.anyOf,ctx))}if(policy.not){results.push(await this.evaluateNot(policy.not,ctx))}if(results.length===0){return invalidRule(["checks nothing"])}return this.combineResults(results,operator)}combineResults(results,operator){const allPolicies=results.flatMap(r=>r.evaluatedPolicies);if(operator==="AND"){const denied=results.find(r=>!r.granted);if(denied){return{...denied,evaluatedPolicies:allPolicies}}return{granted:true,evaluatedPolicies:allPolicies}}const anyGranted=results.some(r=>r.granted);if(anyGranted){return{granted:true,evaluatedPolicies:allPolicies}}const firstDenied=results.find(r=>!r.granted);return{granted:false,deniedBy:firstDenied?.deniedBy??"all policies denied (OR)",evaluatedPolicies:allPolicies}}async evaluateCustom(custom,ctx){const allPolicies=[];for(const[name,config]of Object.entries(custom)){const evaluator=this.evaluators.get(name);if(!evaluator){return{granted:false,deniedBy:`custom evaluator '${name}' is not registered`,evaluatedPolicies:[...allPolicies,`custom.${name}`]}}const result=await evaluator.evaluate(config,ctx);allPolicies.push(`custom.${name}`,...result.evaluatedPolicies);if(!result.granted){return{...result,evaluatedPolicies:allPolicies}}}return{granted:true,evaluatedPolicies:allPolicies}}async evaluateGuards(guards,ctx){for(let i=0;i<guards.length;i++){const guard=guards[i];const result=await guard(ctx);if(result!==true){const denialMessage=typeof result==="string"?result:result===false?`guard[${i}] denied`:`guard[${i}] did not return true (returned ${result===null?"null":typeof result})`;return{granted:false,deniedBy:`guards[${i}]: ${denialMessage}`,denial:{kind:"custom",path:`guards[${i}]`},evaluatedPolicies:["guards"]}}}return{granted:true,evaluatedPolicies:["guards"]}}async evaluateAllOf(policies,ctx){const allPolicies=["allOf"];for(const policy of policies){const result=await this.evaluatePolicy(policy,ctx);allPolicies.push(...result.evaluatedPolicies);if(!result.granted){return{...result,evaluatedPolicies:allPolicies}}}return{granted:true,evaluatedPolicies:allPolicies}}async evaluateAnyOf(policies,ctx){const allPolicies=["anyOf"];let lastDenied;for(const policy of policies){const result=await this.evaluatePolicy(policy,ctx);allPolicies.push(...result.evaluatedPolicies);if(result.granted){return{granted:true,evaluatedPolicies:allPolicies}}lastDenied=result}return{granted:false,deniedBy:lastDenied?.deniedBy??"no policies in anyOf",denial:lastDenied?.denial??{kind:"anyOf",path:"anyOf"},evaluatedPolicies:allPolicies}}async evaluateNot(policy,ctx){const result=await this.evaluatePolicy(policy,ctx);return{granted:!result.granted,deniedBy:result.granted?"not: inner policy was granted (negated to denied)":void 0,denial:result.granted?{kind:"not",path:"not"}:void 0,evaluatedPolicies:["not",...result.evaluatedPolicies]}}resolveProfile(name){return this.profiles.resolve(name)}};var AuthoritiesProfileRegistry=class{profiles=new Map;register(name,policy){this.profiles.set(name,policy)}registerAll(profiles){for(const[name,policy]of Object.entries(profiles)){this.profiles.set(name,policy)}}resolve(name){return this.profiles.get(name)}has(name){return this.profiles.has(name)}getAll(){const result={};for(const[name,policy]of this.profiles){result[name]=policy}return result}get size(){return this.profiles.size}remove(name){return this.profiles.delete(name)}clear(){this.profiles.clear()}};var AuthoritiesEvaluatorRegistry=class{evaluators=new Map;register(name,evaluator){this.evaluators.set(name,evaluator)}registerAll(evaluators){for(const[name,evaluator]of Object.entries(evaluators)){this.evaluators.set(name,evaluator)}}get(name){return this.evaluators.get(name)}has(name){return this.evaluators.has(name)}get size(){return this.evaluators.size}remove(name){return this.evaluators.delete(name)}clear(){this.evaluators.clear()}};import{brandClass,isBrandedInstance}from"@frontmcp/utils";var FORBIDDEN_CODE=-32003;var AuthorityDeniedError=class extends Error{static[Symbol.hasInstance](value){return isBrandedInstance(this,value)}mcpErrorCode=FORBIDDEN_CODE;statusCode=403;code="AUTHORITY_DENIED";entryType;entryName;deniedBy;denial;requiredScopes;constructor(params){const msg=params.message??`Access denied to ${params.entryType} "${params.entryName}": ${params.deniedBy}`;super(msg);this.name="AuthorityDeniedError";this.entryType=params.entryType;this.entryName=params.entryName;this.deniedBy=params.deniedBy;this.denial=params.denial;this.requiredScopes=params.requiredScopes}toJsonRpcError(){const data={entryType:this.entryType,entryName:this.entryName,deniedBy:this.deniedBy};if(this.denial!==void 0)data["denial"]=this.denial;if(this.requiredScopes!==void 0)data["requiredScopes"]=this.requiredScopes;return{code:this.mcpErrorCode,message:this.message,data}}};brandClass(AuthorityDeniedError,"@frontmcp/auth:AuthorityDeniedError");var bearerApplier={apply(token,_url,init){const headers=new Headers(init.headers);headers.set("Authorization",`Bearer ${token}`);return{init:{...init,headers}}}};function headerApplier(headerName,prefix){return{apply(token,_url,init){const headers=new Headers(init.headers);headers.set(headerName,prefix?`${prefix} ${token}`:token);return{init:{...init,headers}}}}}function queryApplier(paramName){return{apply(token,url,init){const urlObj=new URL(url);urlObj.searchParams.set(paramName,token);return{init,url:urlObj.toString()}}}}var basicApplier={apply(token,_url,init){const headers=new Headers(init.headers);const encoded=typeof btoa==="function"?btoa(token):Buffer.from(token).toString("base64");headers.set("Authorization",`Basic ${encoded}`);return{init:{...init,headers}}}};function isFrontMcpCredentials(creds){return typeof creds==="object"&&creds!==null&&"provider"in creds&&typeof creds.provider==="string"&&creds.provider.length>0}function providerHeadersCredentials(resolveHeaders){return{async applyCredentials(_url,init){const{credentials,...cleanInit}=init;if(!isFrontMcpCredentials(credentials)){return{init:typeof credentials==="string"?init:cleanInit}}const headers=new Headers(cleanInit.headers);for(const[name,value]of Object.entries(await resolveHeaders(credentials.provider))){headers.set(name,value)}return{init:{...cleanInit,headers}}}}}var FetchCredentialMiddleware=class{constructor(tokenAccessor,appliers){this.tokenAccessor=tokenAccessor;this.appliers=new Map(Object.entries(appliers??{}))}tokenAccessor;appliers;async applyCredentials(url,init){const creds=init.credentials;if(!creds||typeof creds==="string"){return{init}}if(!isFrontMcpCredentials(creds)){const{credentials:_removed2,...rest}=init;return{init:rest}}const token=await this.tokenAccessor.getToken(creds.provider);if(!token){const{credentials:_removed2,...rest}=init;return{init:rest}}const applier=this.appliers.get(creds.provider)??bearerApplier;const{credentials:_removed,...cleanInit}=init;return applier.apply(token,url,cleanInit)}};import{internalZ as z21}from"@frontmcp/lazy-zod";var MINUTE_MS=6e4;function milliseconds(field){return z21.number({message:`${field} must be a number of milliseconds`}).int(`${field} must be a whole number of milliseconds`).positive(`${field} must be greater than 0`)}function createPeopleAuthOptionsSchema(){const loopbackToken=z21.object({mode:z21.literal("loopback-token"),codeTtlMs:milliseconds("codeTtlMs").max(10*MINUTE_MS,"codeTtlMs must be at most 600000 (10 minutes): a sign-in code is for one hand-off").optional(),sessionTtlMs:milliseconds("sessionTtlMs").optional(),maxFailedAttempts:z21.number({message:"maxFailedAttempts must be a number"}).int("maxFailedAttempts must be a whole number").positive("maxFailedAttempts must be greater than 0").optional(),lockoutMs:milliseconds("lockoutMs").optional(),cookiePath:z21.string().startsWith("/",'cookiePath must start with "/"').optional()}).strict();return z21.union([z21.literal("loopback-token"),loopbackToken],{message:"http.auth.people must be 'loopback-token' or { mode: 'loopback-token', ... }"}).transform(value=>{const settings=typeof value==="string"?{}:value;return{mode:"loopback-token",codeTtlMs:settings.codeTtlMs??MINUTE_MS,sessionTtlMs:settings.sessionTtlMs??12*60*MINUTE_MS,maxFailedAttempts:settings.maxFailedAttempts??5,lockoutMs:settings.lockoutMs??MINUTE_MS,cookiePath:settings.cookiePath??"/"}})}var schema;function peopleAuthOptionsSchema(){schema??=createPeopleAuthOptionsSchema();return schema}import{base64urlEncode as base64urlEncode5,randomBytes as randomBytes10,sha256 as sha2563,timingSafeEqual as timingSafeEqual3}from"@frontmcp/utils";var PEOPLE_SESSION_COOKIE="frontmcp_people";var INVALID={ok:false,reason:"invalid"};var LOCKED={ok:false,reason:"locked"};var MAX_TRACKED_CLIENTS=1024;function newSecret(){return base64urlEncode5(randomBytes10(32))}function matches(presented,digest){return timingSafeEqual3(sha2563(presented),digest)}function unexpired(secrets){const now=Date.now();return secrets.filter(secret=>secret.expiresAt>now)}var LoopbackTokenAuth=class{constructor(options){this.options=options;const token=newSecret();this.token=token;this.tokenDigest=sha2563(token)}options;token;tokenDigest;codes=[];sessions=[];failures=new Map;takeToken(){const token=this.token;this.token=void 0;return token}createCode(options={}){const code=newSecret();const expiresAt=Date.now()+this.options.codeTtlMs;const{subject}=options;this.codes=[...unexpired(this.codes),{digest:sha2563(code),expiresAt,...subject?{subject}:{}}];return{code,expiresAt}}exchangeToken(presented,client){if(this.isLocked(client))return LOCKED;if(presented===void 0||!matches(presented,this.tokenDigest))return this.refuse(client);this.failures.delete(client);return{ok:true,...this.createCode()}}exchangeCode(presented,client){if(this.isLocked(client))return LOCKED;this.codes=unexpired(this.codes);const index=presented===void 0?-1:this.codes.findIndex(code=>matches(presented,code.digest));if(index===-1)return this.refuse(client);const[{subject}]=this.codes.splice(index,1);this.failures.delete(client);const cookieValue=newSecret();const expiresAt=Date.now()+this.options.sessionTtlMs;this.sessions=[...unexpired(this.sessions),{digest:sha2563(cookieValue),expiresAt,...subject?{subject}:{}}];return{ok:true,cookieValue,expiresAt}}verifySession(presented){if(!presented)return void 0;this.sessions=unexpired(this.sessions);const session=this.sessions.find(stored=>matches(presented,stored.digest));if(!session)return void 0;return session.subject?{expiresAt:session.expiresAt,subject:session.subject}:{expiresAt:session.expiresAt}}endSession(presented){if(!presented)return;this.sessions=this.sessions.filter(stored=>!matches(presented,stored.digest))}isLocked(client){return(this.failures.get(client)?.lockedUntil??0)>Date.now()}refuse(client){const now=Date.now();const{maxFailedAttempts,lockoutMs}=this.options;const previous=this.failures.get(client);const count=previous&&now-previous.lastAt<=lockoutMs?previous.count+1:1;const locked=count>=maxFailedAttempts;if(!previous&&this.failures.size>=MAX_TRACKED_CLIENTS)this.forgetIdleClients(now);this.failures.set(client,{count:locked?0:count,lastAt:now,lockedUntil:locked?now+lockoutMs:0});return locked?LOCKED:INVALID}forgetIdleClients(now){for(const[client,failures]of this.failures){if(failures.lockedUntil<=now&&now-failures.lastAt>this.options.lockoutMs)this.failures.delete(client)}}};function peopleSessionCookie(value,options){return{name:PEOPLE_SESSION_COOKIE,value,path:options.path,httpOnly:true,sameSite:"strict",secure:options.secure,maxAge:Math.floor(options.maxAgeMs/1e3)}}import{base64urlDecode as base64urlDecode6,base64urlEncode as base64urlEncode6,hkdfSha256 as hkdfSha2567,hmacSha256,randomBytes as randomBytes11,timingSafeEqual as timingSafeEqual4}from"@frontmcp/utils";var SECRET_BYTES=32;var BASE64URL_32_BYTES=/^[A-Za-z0-9_-]{43}$/;var KEY_SALT="frontmcp shared server";var PROOF_LABEL="frontmcp shared server identity v1";var encoder=new TextEncoder;function createSharedServerSecret(){return base64urlEncode6(randomBytes11(SECRET_BYTES))}function createSharedServerNonce(){return base64urlEncode6(randomBytes11(SECRET_BYTES))}function isBase64url32Bytes(value){return typeof value==="string"&&BASE64URL_32_BYTES.test(value)}function isSharedServerNonce(value){return isBase64url32Bytes(value)}function deriveSharedServerKeys(secret){if(!isBase64url32Bytes(secret))throw new TypeError("a shared server secret is 32 bytes of base64url");const bytes=base64urlDecode6(secret);const salt=encoder.encode(KEY_SALT);return{proofKey:hkdfSha2567(bytes,salt,encoder.encode("proof"),32),token:base64urlEncode6(hkdfSha2567(bytes,salt,encoder.encode("token"),32))}}function proofBytes(proofKey,{endpoint,nonce,version}){return hmacSha256(proofKey,encoder.encode(JSON.stringify([PROOF_LABEL,endpoint,nonce,version])))}function proveSharedServerIdentity(proofKey,statement){return base64urlEncode6(proofBytes(proofKey,statement))}function verifySharedServerIdentity(proofKey,statement,proof){if(!isBase64url32Bytes(proof))return false;const expected=proofBytes(proofKey,statement);const presented=base64urlDecode6(proof);return presented.length===expected.length&&timingSafeEqual4(presented,expected)}export{AUTH_PROVIDERS_ACCESSOR,AUTH_PROVIDERS_REGISTRY,AUTH_PROVIDERS_VAULT,AppAuthState,AudienceValidator,AuthInternalError,AuthInvalidInputError,AuthProvidersAccessorImpl,AuthProvidersNotConfiguredError,AuthProvidersRegistry,AuthProvidersVault,AuthoritiesContextBuilder,AuthoritiesEngine,AuthoritiesEvaluatorRegistry,AuthoritiesProfileRegistry,AuthorityDeniedError,AuthorizationBase,BUILT_IN_AUTH_PAGE_CSP,CDN,CREDENTIALS_ACCESSOR,CimdClientIdMismatchError,CimdDisabledError,CimdError,CimdFetchError,CimdResponseTooLargeError,CimdSecurityError,CimdService,CimdValidationError,CredentialCache,CredentialProviderAlreadyRegisteredError,CredentialStorageError,CredentialsAccessorImpl,DEFAULT_ALLOWED_SCOPES,DEFAULT_INCREMENTAL_TICKET_TTL_MS,DEFAULT_RESUME_TTL_MS,DEFAULT_THEME,DcrClientRegistry,EagerCredentialLoader,ElicitationSecretRequiredError,EncryptedRedisVault,EncryptedStorageError,EncryptedStorageSecureStoreBackend,EncryptedTypedStorage,EncryptionContextNotSetError,EncryptionKeyNotConfiguredError,FetchCredentialMiddleware,FrontMcpAuthContextImpl,InMemoryAuthorizationStore,InMemoryAuthorizationVault,InMemoryCimdCache,InMemoryConsentStore,InMemoryFederatedAuthSessionStore,InMemoryOrchestratedTokenStore,InMemoryStoreRequiredError,InvalidClientIdUrlError,JwksService,JwtSecretRequiredError,JwtSecretWeakError,LazyCredentialLoader,LoopbackTokenAuth,NoProviderIdError,NullOrchestratedAuthAccessor,ORCHESTRATED_AUTH_ACCESSOR,OrchestratedAuthAccessorAdapter,OrchestratedAuthNotAvailableError,OrchestratedAuthorization,OrchestratorJwksNotAvailableError,PEOPLE_SESSION_COOKIE,PublicAuthorization,RedirectUriMismatchError,RedisAuthorizationStore,RedisSessionStore,SECURE_STORE_ACCESSOR,SIGNIN_BINDING_COOKIE_PREFIX,SIGNIN_BINDING_MAX_AGE_SECONDS,SIGNIN_BINDING_SECURE_COOKIE_PREFIX,ScopeDeniedError,SecureStoreAccessorImpl,SecureStoreConfigError,SessionCredentialVault,SessionIdEmptyError,SessionRateLimiter,SessionSecretRequiredError,StorageAuthorizationStore,StorageAuthorizationVault,StorageConsentStore,StorageFederatedAuthSessionStore,StorageOrchestratedTokenStore,StorageTokenStore,TinyTtlCache,TokenLeakDetectedError,TokenNotAvailableError,TokenStoreRequiredError,TokenVault,TransparentAuthorization,TransportIdGenerator,TypedStorage4 as TypedStorage,UNIDENTIFIED_SESSION_PREFIX,VaultEncryption,VaultLoadError,VaultNotFoundError,VercelKvSessionStore,abacConditionSchema,abacOperatorSchema,abacPolicySchema,advertisedScopes,aiPlatformTypeSchema,allowsPublicAccess,anonymousCallerClaims,apiKeyCredentialSchema,appAuthOptionsSchema,appAuthStateSchema,appAuthorizationRecordSchema,appCredentialSchema,appRequiresOrchestration,authLayout,authModeSchema,authOptionsSchema,authProviderDetectionResultSchema,authProviderMappingSchema,authProvidersVaultOptionsSchema,authUserSchema,authenticateFnSchema,authoritiesClaimsMappingSchema,authoritiesConfigSchema,authoritiesMetadataSchema,authoritiesPolicySchema,authorizationCodeRecordSchema,authorizationVaultEntrySchema,authorizedPromptSchema,authorizedToolSchema,baseLayout,basicApplier,basicAuthCredentialSchema,bearerApplier,bearerCredentialSchema,buildAuthContext,buildCodeRecord,buildConnectPage,buildConnectSuccessPage,buildConsentPage,buildCredentialResumeUrl,buildErrorPage,buildFederatedLoginPage,buildIncrementalAuthPage,buildInsufficientScopeHeader,buildInvalidRequestHeader,buildInvalidTokenHeader,buildLoginPage,buildPendingRecord,buildPrmUrl,buildRefreshTokenRecord,buildToolConsentPage,buildUnauthorizedHeader,buildWwwAuthenticate,builtInAuthPageHeaders,centeredCardLayout,checkSsrfProtection,cimdCacheConfigSchema,cimdConfigSchema,cimdNetworkConfigSchema,cimdSecurityConfigSchema,clientMetadataDocumentSchema,completeCurrentProvider,consentConfigSchema,consentRecordKey,consentSelectionSchema,consentStateSchema,consentToolItemSchema,createAudienceValidator,createEncryptedVault,createFederatedAuthSession,createLayout,createSecureStore,createSessionCredentialVault,createSharedServerNonce,createSharedServerSecret,createSigninBinding,createTokenStorageAdapter,credentialProviderConfigSchema,credentialSchema,credentialScopeSchema,credentialTypeSchema,customCredentialSchema,decodeJwtPayloadSafe,decryptAesGcm6 as decryptAesGcm,decryptSessionJson,decryptValue2 as decryptValue,defaultSessionRateLimiter,deriveAuthorizationId,deriveExpectedAudience,deriveProviderId,deriveSharedServerKeys,deriveTypedUser,detectAuthProviders,detectedAuthProviderSchema,encryptAesGcm6 as encryptAesGcm,encryptJson,encryptValue2 as encryptValue,encryptedBlobSchema,encryptedDataSchema,encryptedVaultEntrySchema,escapeHtml2 as escapeHtml,extraWideLayout,extractBearerToken,extractCacheHeaders,extractCredentialExpiry,federatedAuthConfigSchema,federatedLoginStateSchema,federatedProviderItemSchema,federatedSelectionSchema,findAuthoritiesProfileProblems,findAuthoritiesRuleProblems,flatRemoteProviderFields,fromSessionRecord,generateAuthorizationCode,generatePkceChallenge,generateRefreshTokenValue,getCredentialOptionsSchema,getKey,getNextProvider,getProviderApps,getProviderScopes,getTokenSignatureFingerprint,grantScopes,hasOnlyLocalhostRedirectUris,headerApplier,hkdfSha2565 as hkdfSha256,incrementalAuthConfigSchema,isAnonymousSubject,isCimdClientId,isFrontMcpCredentials,isJwt,isLocalMode,isLoopbackRedirectUri,isOrchestratedLocal,isOrchestratedMode,isOrchestratedRemote,isPersistentTokenStorage,isPublicMode,isRedisTokenStorage,isRemoteMode,isSessionComplete,isSharedServerNonce,isSignedData as isSignedSession,isSoonExpiring,isSoonExpiringProvider,isSqliteTokenStorage,isStaticMode,isTransparentMode,jsonWebKeySetSchema,jwkParametersSchema,jwkSchema,legacySseTransportStateSchema,llmSafeAuthContextSchema,loadingStrategySchema,localAuthSchema,localDcrClientSchema,localDcrConfigSchema,localSigningConfigSchema,loginConfigSchema,mtlsCredentialSchema,noopLogger,normalizeIssuer,oauthCredentialSchema,openPendingLogin,orchestratedLocalSchema,orchestratedRemoteSchema,parseAuthOptions,parseCacheHeaders,parseWwwAuthenticate,pendingIncrementalAuthSchema,peopleAuthOptionsSchema,peopleSessionCookie,pkceChallengeSchema,pkceOAuthCredentialSchema,privateKeyCredentialSchema,progressiveAuthStateSchema,proveSharedServerIdentity,providerConfigSchema,providerHeadersCredentials,publicAccessConfigSchema,publicAuthOptionsSchema,queryApplier,rbacPermissionsPolicySchema,rbacRolesPolicySchema,rebacPolicySchema,redisConfigSchema,redisVaultEntrySchema,remoteAuthSchema,remoteProviderConfigSchema,renderLocalLoginPage,renderToHtml,resetCachedKey,resolveRequiredScopes,resourceIdRefSchema,resourceScopesFor,safeDecrypt,scopesFromClaims,sealPendingLogin,secureStoreConfigSchema,secureStoreCustomBackendSchema,secureStoreEncryptionSchema,secureStoreScopeSchema,serviceAccountCredentialSchema,sessionIdPayloadSchema,sessionJwtPayloadSchema,signCredentialResumeToken,signIncrementalAuthTicket,signSession,signinBindingCookieName,signinBindingMatches,skippedAppBehaviorSchema,sseTransportStateSchema,sshKeyCredentialSchema,startNextProvider,statefulHttpTransportStateSchema,statelessHttpTransportStateSchema,staticAuthOptionsSchema,storedSessionSchema,streamableHttpTransportStateSchema,toEpochSeconds,toLoginExtraFields,toSessionRecord,tokenRefreshConfigSchema,tokenStorageConfigSchema,tokenStorageSqliteSchema,transparentAuthOptionsSchema,transportProtocolSchema,transportSessionSchema,transportStateSchema,trimSlash,tryJwtExp,upstreamProviderSchema,userClaimSchema,validateAudience,validateClientIdUrl,vaultConsentRecordSchema,vaultFederatedRecordSchema,verifyCredentialResumeToken,verifyIncrementalAuthTicket,verifyOrParseSession,verifyPkce,verifySession,verifySharedServerIdentity,wideLayout,withoutSecretFieldValues};
|
|
394
|
+
</div>`;return centeredCardLayout(content,{title:"Error"})}function renderToHtml(html,_options){return html}function toLoginExtraFields(fields,values){if(!fields)return[];return Object.entries(fields).map(([name,def])=>({name,type:def.type,label:def.label,required:def.required,placeholder:def.placeholder,options:def.options,value:values?.[name]}))}function withoutSecretFieldValues(values,fields){if(!values)return void 0;const kept={};for(const[name,value]of Object.entries(values)){if(fields?.[name]?.type==="password")continue;kept[name]=value}return kept}function renderLocalLoginPage(login,ctx,submitted){const values=withoutSecretFieldValues(submitted,login?.fields);if(login?.render){return login.render({...ctx,values})}const extraFields=login?.fields?toLoginExtraFields(login.fields,values):void 0;return buildLoginPage({clientName:ctx.clientName,scope:ctx.scopes.join(" "),pendingAuthId:ctx.pendingAuthId,callbackPath:ctx.callbackPath,title:login?.title,subtitle:login?.subtitle,logoUri:login?.logoUri??ctx.logoUri,error:ctx.error,extraFields})}import{internalZ as z}from"@frontmcp/lazy-zod";import{randomUUID,sha256Base64url as sha256Base64url2}from"@frontmcp/utils";var pkceChallengeSchema=z.object({challenge:z.string().min(43).max(128),method:z.literal("S256")});var authorizationCodeRecordSchema=z.object({code:z.string().min(1),clientId:z.string().min(1),redirectUri:z.string().url(),scopes:z.array(z.string()),pkce:pkceChallengeSchema,userSub:z.string().min(1),userEmail:z.string().email().optional(),userName:z.string().optional(),state:z.string().optional(),createdAt:z.number(),expiresAt:z.number(),used:z.boolean(),resource:z.string().url().optional(),selectedToolIds:z.array(z.string()).optional(),selectedProviderIds:z.array(z.string()).optional(),skippedProviderIds:z.array(z.string()).optional(),consentEnabled:z.boolean().optional(),federatedLoginUsed:z.boolean().optional(),pendingAuthId:z.string().optional(),authorizedAppIds:z.array(z.string()).optional(),customClaims:z.record(z.string(),z.unknown()).optional()});var AUTH_CODE_TTL_MS=60*1e3;var PENDING_AUTH_TTL_MS=10*60*1e3;var REFRESH_TOKEN_TTL_MS=30*24*60*60*1e3;function generateAuthorizationCode(){return randomUUID().replace(/-/g,"")+randomUUID().replace(/-/g,"")}function generateRefreshTokenValue(){return randomUUID()+"-"+randomUUID()}function buildCodeRecord(params){const now=Date.now();return{code:generateAuthorizationCode(),clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,pkce:params.pkce,userSub:params.userSub,userEmail:params.userEmail,userName:params.userName,state:params.state,resource:params.resource,createdAt:now,expiresAt:now+AUTH_CODE_TTL_MS,used:false,selectedToolIds:params.selectedToolIds,selectedProviderIds:params.selectedProviderIds,skippedProviderIds:params.skippedProviderIds,consentEnabled:params.consentEnabled,federatedLoginUsed:params.federatedLoginUsed,pendingAuthId:params.pendingAuthId,authorizedAppIds:params.authorizedAppIds,customClaims:params.customClaims}}function buildPendingRecord(params){const now=Date.now();return{id:randomUUID(),clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,pkce:params.pkce,state:params.state,resource:params.resource,createdAt:now,expiresAt:now+PENDING_AUTH_TTL_MS,isIncremental:params.isIncremental,incrementalSub:params.incrementalSub,targetAppId:params.targetAppId,targetToolId:params.targetToolId,existingSessionId:params.existingSessionId,existingAuthorizationId:params.existingAuthorizationId,priorAuthorizedAppIds:params.priorAuthorizedAppIds,federatedLogin:params.federatedLogin,consent:params.consent,authUiCsrf:params.authUiCsrf,signinBinding:params.signinBinding}}function buildRefreshTokenRecord(params){const now=Date.now();return{token:generateRefreshTokenValue(),clientId:params.clientId,userSub:params.userSub,scopes:params.scopes,resource:params.resource,createdAt:now,expiresAt:now+REFRESH_TOKEN_TTL_MS,revoked:false,userEmail:params.userEmail,userName:params.userName,consentEnabled:params.consentEnabled,selectedToolIds:params.selectedToolIds,authorizedAppIds:params.authorizedAppIds,customClaims:params.customClaims,federatedLoginUsed:params.federatedLoginUsed,selectedProviderIds:params.selectedProviderIds,skippedProviderIds:params.skippedProviderIds,providerTokensId:params.providerTokensId}}function verifyPkce(codeVerifier,challenge){if(challenge.method!=="S256"){return false}const hash=sha256Base64url2(codeVerifier);return hash===challenge.challenge}function generatePkceChallenge(codeVerifier){const challenge=sha256Base64url2(codeVerifier);return{challenge,method:"S256"}}var InMemoryAuthorizationStore=class{codes=new Map;pending=new Map;refreshTokens=new Map;generateCode(){return generateAuthorizationCode()}generateRefreshToken(){return generateRefreshTokenValue()}async storeAuthorizationCode(record){this.codes.set(record.code,record)}async getAuthorizationCode(code){const record=this.codes.get(code);if(!record)return null;if(Date.now()>record.expiresAt){this.codes.delete(code);return null}return record}async markCodeUsed(code,issuedRefreshToken){const record=this.codes.get(code);if(record){record.used=true;if(issuedRefreshToken)record.issuedRefreshToken=issuedRefreshToken}}async deleteAuthorizationCode(code){this.codes.delete(code)}async storePendingAuthorization(record){this.pending.set(record.id,record)}async getPendingAuthorization(id){const record=this.pending.get(id);if(!record)return null;if(Date.now()>record.expiresAt){this.pending.delete(id);return null}return record}async deletePendingAuthorization(id){this.pending.delete(id)}async storeRefreshToken(record){this.refreshTokens.set(record.token,record)}async getRefreshToken(token){const record=this.refreshTokens.get(token);if(!record)return null;if(Date.now()>record.expiresAt||record.revoked){return null}return record}async revokeRefreshToken(token){const record=this.refreshTokens.get(token);if(record){record.revoked=true}}async rotateRefreshToken(oldToken,newRecord){await this.revokeRefreshToken(oldToken);newRecord.previousToken=oldToken;await this.storeRefreshToken(newRecord)}async cleanup(){const now=Date.now();for(const[code,record]of this.codes){if(now>record.expiresAt){this.codes.delete(code)}}for(const[id,record]of this.pending){if(now>record.expiresAt){this.pending.delete(id)}}for(const[token,record]of this.refreshTokens){if(now>record.expiresAt||record.revoked){this.refreshTokens.delete(token)}}}createCodeRecord(params){return buildCodeRecord(params)}createPendingRecord(params){return buildPendingRecord(params)}createRefreshTokenRecord(params){return buildRefreshTokenRecord(params)}};var RedisAuthorizationStore=class{constructor(redis,namespace="oauth:"){this.redis=redis;this.namespace=namespace}redis;namespace;key(type,id){return`${this.namespace}${type}:${id}`}generateCode(){return generateAuthorizationCode()}generateRefreshToken(){return generateRefreshTokenValue()}createCodeRecord(params){return buildCodeRecord(params)}createPendingRecord(params){return buildPendingRecord(params)}createRefreshTokenRecord(params){return buildRefreshTokenRecord(params)}async storeAuthorizationCode(record){const ttl=Math.max(Math.ceil((record.expiresAt-Date.now())/1e3),1);await this.redis.set(this.key("code",record.code),JSON.stringify(record),"EX",Math.max(ttl,1))}async getAuthorizationCode(code){const data=await this.redis.get(this.key("code",code));if(!data)return null;return JSON.parse(data)}async markCodeUsed(code,issuedRefreshToken){const record=await this.getAuthorizationCode(code);if(record){record.used=true;if(issuedRefreshToken)record.issuedRefreshToken=issuedRefreshToken;const ttl=Math.ceil((record.expiresAt-Date.now())/1e3);await this.redis.set(this.key("code",code),JSON.stringify(record),"EX",Math.max(ttl,1))}}async deleteAuthorizationCode(code){await this.redis.del(this.key("code",code))}async storePendingAuthorization(record){const ttl=Math.max(Math.ceil((record.expiresAt-Date.now())/1e3),1);await this.redis.set(this.key("pending",record.id),JSON.stringify(record),"EX",ttl)}async getPendingAuthorization(id){const data=await this.redis.get(this.key("pending",id));if(!data)return null;return JSON.parse(data)}async deletePendingAuthorization(id){await this.redis.del(this.key("pending",id))}async storeRefreshToken(record){const ttl=Math.ceil((record.expiresAt-Date.now())/1e3);await this.redis.set(this.key("refresh",record.token),JSON.stringify(record),"EX",ttl)}async getRefreshToken(token){const data=await this.redis.get(this.key("refresh",token));if(!data)return null;const record=JSON.parse(data);if(record.revoked)return null;return record}async revokeRefreshToken(token){const record=await this.getRefreshToken(token);if(record){record.revoked=true;const ttl=Math.ceil((record.expiresAt-Date.now())/1e3);await this.redis.set(this.key("refresh",token),JSON.stringify(record),"EX",Math.max(ttl,1))}}async rotateRefreshToken(oldToken,newRecord){await this.revokeRefreshToken(oldToken);newRecord.previousToken=oldToken;await this.storeRefreshToken(newRecord)}async cleanup(){}};import{base64urlDecode as base64urlDecode2,base64urlEncode,decryptAesGcm,encryptAesGcm,hkdfSha256,randomBytes as randomBytes3}from"@frontmcp/utils";var SALT=new TextEncoder().encode("frontmcp-pending-login");function keyFor(secret,pendingAuthId){return hkdfSha256(secret,SALT,new TextEncoder().encode(`pending-login:${pendingAuthId}`),32)}function sealPendingLogin(secret,pendingAuthId,state){const iv=randomBytes3(12);const{ciphertext,tag}=encryptAesGcm(keyFor(secret,pendingAuthId),new TextEncoder().encode(JSON.stringify(state)),iv);return[iv,tag,ciphertext].map(part=>base64urlEncode(part)).join(".")}function openPendingLogin(secret,pendingAuthId,sealed){try{const[iv,tag,ciphertext]=sealed.split(".").map(part=>base64urlDecode2(part));if(!iv||!tag||!ciphertext)return void 0;const plaintext=decryptAesGcm(keyFor(secret,pendingAuthId),ciphertext,iv,tag);const state=JSON.parse(new TextDecoder().decode(plaintext));return typeof state?.sub==="string"&&state.sub.length>0?state:void 0}catch{return void 0}}import{base64urlEncode as base64urlEncode2,getCookie,randomBytes as randomBytes4,sha256Hex,timingSafeEqual}from"@frontmcp/utils";var SIGNIN_BINDING_COOKIE_PREFIX="frontmcp_signin_";var SIGNIN_BINDING_SECURE_COOKIE_PREFIX=`__Host-${SIGNIN_BINDING_COOKIE_PREFIX}`;var SIGNIN_BINDING_MAX_AGE_SECONDS=30*60;function signinBindingCookieName(pendingAuthId,options={}){const prefix=options.secure?SIGNIN_BINDING_SECURE_COOKIE_PREFIX:SIGNIN_BINDING_COOKIE_PREFIX;return`${prefix}${sha256Hex(`signin:${pendingAuthId}`).slice(0,16)}`}function createSigninBinding(pendingAuthId,options={}){const value=base64urlEncode2(randomBytes4(32));return{cookieName:signinBindingCookieName(pendingAuthId,options),value,hash:sha256Hex(value)}}function signinBindingMatches(cookieHeader,pendingAuthId,expectedHash,options={}){if(!expectedHash)return false;const value=getCookie(cookieHeader,signinBindingCookieName(pendingAuthId,options));if(!value)return false;const encoder2=new TextEncoder;const actual=encoder2.encode(sha256Hex(value));const expected=encoder2.encode(expectedHash);return actual.length===expected.length&&timingSafeEqual(actual,expected)}import{TypedStorage}from"@frontmcp/utils";var StorageAuthorizationStore=class{codeStore;pendingStore;refreshStore;namespace;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"oauth";this.storageIsNamespaced=this.isNamespacedStorage(storage);const base=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.codeStore=new TypedStorage(base);this.pendingStore=new TypedStorage(base);this.refreshStore=new TypedStorage(base)}generateCode(){return generateAuthorizationCode()}generateRefreshToken(){return generateRefreshTokenValue()}createCodeRecord(params){return buildCodeRecord(params)}createPendingRecord(params){return buildPendingRecord(params)}createRefreshTokenRecord(params){return buildRefreshTokenRecord(params)}async storeAuthorizationCode(record){await this.codeStore.set(this.key("code",record.code),record,this.ttl(record.expiresAt))}async getAuthorizationCode(code){const record=await this.codeStore.get(this.key("code",code));if(!record)return null;if(Date.now()>record.expiresAt){await this.codeStore.delete(this.key("code",code));return null}return record}async markCodeUsed(code,issuedRefreshToken){const record=await this.getAuthorizationCode(code);if(record){record.used=true;if(issuedRefreshToken)record.issuedRefreshToken=issuedRefreshToken;await this.codeStore.set(this.key("code",code),record,this.ttl(record.expiresAt))}}async deleteAuthorizationCode(code){await this.codeStore.delete(this.key("code",code))}async storePendingAuthorization(record){await this.pendingStore.set(this.key("pending",record.id),record,this.ttl(record.expiresAt))}async getPendingAuthorization(id){const record=await this.pendingStore.get(this.key("pending",id));if(!record)return null;if(Date.now()>record.expiresAt){await this.pendingStore.delete(this.key("pending",id));return null}return record}async deletePendingAuthorization(id){await this.pendingStore.delete(this.key("pending",id))}async storeRefreshToken(record){await this.refreshStore.set(this.key("refresh",record.token),record,this.ttl(record.expiresAt))}async getRefreshToken(token){const record=await this.refreshStore.get(this.key("refresh",token));if(!record)return null;if(Date.now()>record.expiresAt||record.revoked){return null}return record}async revokeRefreshToken(token){const record=await this.refreshStore.get(this.key("refresh",token));if(record){record.revoked=true;await this.refreshStore.set(this.key("refresh",token),record,this.ttl(record.expiresAt))}}async rotateRefreshToken(oldToken,newRecord){await this.revokeRefreshToken(oldToken);newRecord.previousToken=oldToken;await this.storeRefreshToken(newRecord)}async cleanup(){}ttl(expiresAtMs){const ttl=Math.ceil((expiresAtMs-Date.now())/1e3);return{ttlSeconds:ttl>0?ttl:1}}key(type,id){const composite=`${type}:${id}`;return this.storageIsNamespaced?composite:`${this.namespace}:${composite}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{internalZ as z2}from"@frontmcp/lazy-zod";var credentialTypeSchema=z2.enum(["oauth","api_key","basic","bearer","private_key","mtls","custom","ssh_key","service_account","oauth_pkce"]);var oauthCredentialSchema=z2.object({type:z2.literal("oauth"),accessToken:z2.string(),refreshToken:z2.string().optional(),tokenType:z2.string().default("Bearer"),expiresAt:z2.number().optional(),scopes:z2.array(z2.string()).default([]),idToken:z2.string().optional()});var apiKeyCredentialSchema=z2.object({type:z2.literal("api_key"),key:z2.string().min(1),headerName:z2.string().default("X-API-Key"),headerPrefix:z2.string().optional(),queryParam:z2.string().optional()});var basicAuthCredentialSchema=z2.object({type:z2.literal("basic"),username:z2.string().min(1),password:z2.string(),encodedValue:z2.string().optional()});var bearerCredentialSchema=z2.object({type:z2.literal("bearer"),token:z2.string().min(1),expiresAt:z2.number().optional()});var privateKeyCredentialSchema=z2.object({type:z2.literal("private_key"),format:z2.enum(["pem","jwk","pkcs8","pkcs12"]),keyData:z2.string(),keyId:z2.string().optional(),algorithm:z2.string().optional(),passphrase:z2.string().optional(),certificate:z2.string().optional()});var mtlsCredentialSchema=z2.object({type:z2.literal("mtls"),certificate:z2.string(),privateKey:z2.string(),passphrase:z2.string().optional(),caCertificate:z2.string().optional()});var customCredentialSchema=z2.object({type:z2.literal("custom"),customType:z2.string().min(1),data:z2.record(z2.string(),z2.unknown()),headers:z2.record(z2.string(),z2.string()).optional()});var sshKeyCredentialSchema=z2.object({type:z2.literal("ssh_key"),privateKey:z2.string().min(1),publicKey:z2.string().optional(),passphrase:z2.string().optional(),keyType:z2.enum(["rsa","ed25519","ecdsa","dsa"]).default("ed25519"),fingerprint:z2.string().optional(),username:z2.string().optional()});var serviceAccountCredentialSchema=z2.object({type:z2.literal("service_account"),provider:z2.enum(["gcp","aws","azure","custom"]),credentials:z2.record(z2.string(),z2.unknown()),projectId:z2.string().optional(),region:z2.string().optional(),assumeRoleArn:z2.string().optional(),externalId:z2.string().optional(),serviceAccountId:z2.string().optional(),expiresAt:z2.number().optional()});var pkceOAuthCredentialSchema=z2.object({type:z2.literal("oauth_pkce"),accessToken:z2.string(),refreshToken:z2.string().optional(),tokenType:z2.string().default("Bearer"),expiresAt:z2.number().optional(),scopes:z2.array(z2.string()).default([]),idToken:z2.string().optional(),issuer:z2.string().optional()});var credentialSchema=z2.discriminatedUnion("type",[oauthCredentialSchema,apiKeyCredentialSchema,basicAuthCredentialSchema,bearerCredentialSchema,privateKeyCredentialSchema,mtlsCredentialSchema,customCredentialSchema,sshKeyCredentialSchema,serviceAccountCredentialSchema,pkceOAuthCredentialSchema]);var appCredentialSchema=z2.object({appId:z2.string().min(1),providerId:z2.string().min(1),credential:credentialSchema,acquiredAt:z2.number(),lastUsedAt:z2.number().optional(),expiresAt:z2.number().optional(),isValid:z2.boolean().default(true),invalidReason:z2.string().optional(),userInfo:z2.object({sub:z2.string().optional(),email:z2.string().optional(),name:z2.string().optional()}).optional(),metadata:z2.record(z2.string(),z2.unknown()).optional()});var vaultConsentRecordSchema=z2.object({enabled:z2.boolean(),selectedToolIds:z2.array(z2.string()),availableToolIds:z2.array(z2.string()),consentedAt:z2.number(),version:z2.string().default("1.0")});var vaultFederatedRecordSchema=z2.object({selectedProviderIds:z2.array(z2.string()),skippedProviderIds:z2.array(z2.string()),primaryProviderId:z2.string().optional(),completedAt:z2.number()});var pendingIncrementalAuthSchema=z2.object({id:z2.string(),appId:z2.string(),toolId:z2.string().optional(),authUrl:z2.string(),requiredScopes:z2.array(z2.string()).optional(),elicitId:z2.string().optional(),createdAt:z2.number(),expiresAt:z2.number(),status:z2.enum(["pending","completed","cancelled","expired"])});var authorizationVaultEntrySchema=z2.object({id:z2.string(),userSub:z2.string(),userEmail:z2.string().optional(),userName:z2.string().optional(),clientId:z2.string(),createdAt:z2.number(),lastAccessAt:z2.number(),appCredentials:z2.record(z2.string(),appCredentialSchema).default({}),consent:vaultConsentRecordSchema.optional(),federated:vaultFederatedRecordSchema.optional(),pendingAuths:z2.array(pendingIncrementalAuthSchema),authorizedAppIds:z2.array(z2.string()),skippedAppIds:z2.array(z2.string())});import{getEnv,randomUUID as randomUUID2}from"@frontmcp/utils";import{z as z3}from"@frontmcp/lazy-zod";import{base64urlDecode as base64urlDecode3,base64urlEncode as base64urlEncode3,decryptAesGcm as decryptAesGcm2,encryptAesGcm as encryptAesGcm2,hkdfSha256 as hkdfSha2562,randomBytes as randomBytes5}from"@frontmcp/utils";var encryptedDataSchema=z3.object({v:z3.literal(1),alg:z3.literal("aes-256-gcm"),iv:z3.string(),ct:z3.string(),tag:z3.string()});var VaultEncryption=class{pepper;hkdfInfo;constructor(config={}){this.pepper=new TextEncoder().encode(config.pepper??"");this.hkdfInfo=config.hkdfInfo??"frontmcp-vault-v1"}async deriveKey(claims){const ikmParts=[claims.jti,claims.vaultKey??"",claims.sub,claims.iat.toString(),new TextDecoder().decode(this.pepper)];const ikm=new TextEncoder().encode(ikmParts.join(""));const infoBytes=new TextEncoder().encode(this.hkdfInfo);const key=await hkdfSha2562(ikm,new Uint8Array(0),infoBytes,32);return key}async deriveKeyFromToken(token,claims){const parts=token.split(".");const signature=parts[2]??"";const ikmParts=[claims.jti,claims.vaultKey??"",claims.sub,claims.iat.toString(),signature,new TextDecoder().decode(this.pepper)];const ikm=new TextEncoder().encode(ikmParts.join(""));const infoBytes=new TextEncoder().encode(this.hkdfInfo);const key=await hkdfSha2562(ikm,new Uint8Array(0),infoBytes,32);return key}async encrypt(plaintext,key){if(key.length!==32){throw new Error("Encryption key must be 32 bytes")}const iv=randomBytes5(12);const{ciphertext,tag}=await encryptAesGcm2(key,new TextEncoder().encode(plaintext),iv);return{v:1,alg:"aes-256-gcm",iv:base64urlEncode3(iv),ct:base64urlEncode3(ciphertext),tag:base64urlEncode3(tag)}}async decrypt(encrypted,key){if(key.length!==32){throw new Error("Encryption key must be 32 bytes")}const parsed=encryptedDataSchema.safeParse(encrypted);if(!parsed.success){throw new Error("Invalid encrypted data format")}const{iv,ct,tag}=parsed.data;const ivBuffer=base64urlDecode3(iv);const ciphertext=base64urlDecode3(ct);const tagBuffer=base64urlDecode3(tag);try{const plaintext=await decryptAesGcm2(key,ciphertext,ivBuffer,tagBuffer);return new TextDecoder().decode(plaintext)}catch(_error){throw new Error("Decryption failed: invalid key or corrupted data")}}async encryptObject(data,key){return this.encrypt(JSON.stringify(data),key)}async decryptObject(encrypted,key){const plaintext=await this.decrypt(encrypted,key);return JSON.parse(plaintext)}isEncrypted(data){return encryptedDataSchema.safeParse(data).success}};var encryptedVaultEntrySchema=z3.object({id:z3.string(),userSub:z3.string(),userEmail:z3.string().optional(),userName:z3.string().optional(),clientId:z3.string(),createdAt:z3.number(),lastAccessAt:z3.number(),encryptedData:encryptedDataSchema,authorizedAppIds:z3.array(z3.string()),skippedAppIds:z3.array(z3.string()),pendingAuthIds:z3.array(z3.string()).default([])});var DEFAULT_KEY_PREFIX="mcp:cred:";var DEFAULT_PENDING_TTL_MS=6e5;var SessionCredentialVault=class{storage;encryption;keyPrefix;ttlMs;pendingTtlMs;logger;keyCache=new Map;constructor(options){this.storage=options.storage;this.keyPrefix=options.keyPrefix??DEFAULT_KEY_PREFIX;this.ttlMs=options.ttlMs;this.pendingTtlMs=options.pendingTtlMs??DEFAULT_PENDING_TTL_MS;this.logger=options.logger??noopAuthLogger;const pepper=options.pepper??resolveVaultPepper(this.logger);this.encryption=new VaultEncryption({pepper,hkdfInfo:"frontmcp-session-credential-vault-v1"})}indexKey(sub){return`${this.keyPrefix}idx:${sub}`}keysKey(sub,vaultId){return`${this.keyPrefix}keys:${sub}:${vaultId}`}dataKey(sub,vaultId,key){return`${this.keyPrefix}data:${sub}:${vaultId}:${key}`}pendingKey(pendingId){return`${this.keyPrefix}pending:${pendingId}`}get credentialSetOpts(){return this.ttlMs?{ttlSeconds:Math.ceil(this.ttlMs/1e3)}:void 0}deriveKey(sub,vaultId){const cacheKey=`${sub}:${vaultId}`;const cached=this.keyCache.get(cacheKey);if(cached)return cached;const promise=this.encryption.deriveKey({jti:vaultId,vaultKey:vaultId,sub,iat:0});this.keyCache.set(cacheKey,promise);return promise}async getVaultId(sub){const raw=await this.storage.get(this.indexKey(sub));return raw??void 0}async rotateVault(sub){const vaultId=randomUUID2();await this.storage.set(this.indexKey(sub),vaultId,this.credentialSetOpts);return vaultId}async ensureVault(sub){const currentVaultId=await this.getVaultId(sub);if(currentVaultId)return currentVaultId;const createdVaultId=randomUUID2();await this.storage.set(this.indexKey(sub),createdVaultId,{...this.credentialSetOpts,ifNotExists:true});return await this.getVaultId(sub)??createdVaultId}async store(sub,vaultId,key,value){const aesKey=await this.deriveKey(sub,vaultId);const enc=await this.encryption.encryptObject(this.normalize(value),aesKey);const record={enc};await this.storage.set(this.dataKey(sub,vaultId,key),JSON.stringify(record),this.credentialSetOpts);await this.addKeyToSet(sub,vaultId,key)}async get(sub,key){const vaultId=await this.getVaultId(sub);if(!vaultId)return void 0;const raw=await this.storage.get(this.dataKey(sub,vaultId,key));if(!raw)return void 0;try{const record=JSON.parse(raw);const aesKey=await this.deriveKey(sub,vaultId);return await this.encryption.decryptObject(record.enc,aesKey)}catch(err){this.logger.warn(`SessionCredentialVault: failed to read credential "${key}": ${errMsg(err)}`);return void 0}}async list(sub){const vaultId=await this.getVaultId(sub);if(!vaultId)return[];return this.readKeySet(sub,vaultId)}async remove(sub,key){const vaultId=await this.getVaultId(sub);if(!vaultId)return;await this.storage.delete(this.dataKey(sub,vaultId,key));const keys=await this.readKeySet(sub,vaultId);const next=keys.filter(k=>k!==key);if(next.length!==keys.length){await this.writeKeySet(sub,vaultId,next)}}async stage(pendingId,sub,credentials){const vaultId=await this.rotateVault(sub);const creds={};for(const cred of credentials){const aesKey=await this.deriveKey(sub,vaultId);const{key,...value}=cred;creds[key]={enc:await this.encryption.encryptObject(this.normalize(value),aesKey)}}const batch={sub,vaultId,creds};await this.storage.set(this.pendingKey(pendingId),JSON.stringify(batch),{ttlSeconds:Math.ceil(this.pendingTtlMs/1e3)});return vaultId}async commit(pendingId,sub){const raw=await this.storage.get(this.pendingKey(pendingId));if(!raw)return false;let batch;try{batch=JSON.parse(raw)}catch{await this.storage.delete(this.pendingKey(pendingId));return false}if(batch.sub!==sub){this.logger.warn("SessionCredentialVault: pending batch subject mismatch on commit; discarding");await this.storage.delete(this.pendingKey(pendingId));return false}await this.storage.set(this.indexKey(sub),batch.vaultId,this.credentialSetOpts);const keys=[];for(const[key,record]of Object.entries(batch.creds)){await this.storage.set(this.dataKey(sub,batch.vaultId,key),JSON.stringify(record),this.credentialSetOpts);keys.push(key)}await this.writeKeySet(sub,batch.vaultId,keys);await this.storage.delete(this.pendingKey(pendingId));return true}async readKeySet(sub,vaultId){const raw=await this.storage.get(this.keysKey(sub,vaultId));if(!raw)return[];try{const parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter(k=>typeof k==="string"):[]}catch{return[]}}async writeKeySet(sub,vaultId,keys){const unique=[...new Set(keys)];await this.storage.set(this.keysKey(sub,vaultId),JSON.stringify(unique),this.credentialSetOpts)}async addKeyToSet(sub,vaultId,key){const keys=await this.readKeySet(sub,vaultId);if(!keys.includes(key)){keys.push(key);await this.writeKeySet(sub,vaultId,keys)}}normalize(value){return value.metadata===void 0?{secret:value.secret}:{secret:value.secret,metadata:value.metadata}}};function resolveVaultPepper(logger){const fromEnv=getEnv("VAULT_SECRET")??getEnv("JWT_SECRET");if(fromEnv&&fromEnv.trim().length>0)return fromEnv;logger.warn("SessionCredentialVault: neither VAULT_SECRET nor JWT_SECRET is set; using a random per-process pepper. Stored credentials will NOT survive a restart and cannot be shared across instances. Set VAULT_SECRET (or JWT_SECRET) for persistent, multi-instance credential storage.");return randomUUID2()+randomUUID2()}function errMsg(err){return err instanceof Error?err.message:String(err)}import{MemoryStorageAdapter,RedisStorageAdapter}from"@frontmcp/utils";function isRedisTokenStorage(config){return typeof config==="object"&&config!==null&&"redis"in config&&!!config.redis}function isSqliteTokenStorage(config){return typeof config==="object"&&config!==null&&"sqlite"in config&&!!config.sqlite}function isPersistentTokenStorage(config){return config!==void 0&&config!=="memory"}async function createTokenStorageAdapter(config){if(config===void 0||config==="memory"){const adapter=new MemoryStorageAdapter;await adapter.connect();return adapter}if(isSqliteTokenStorage(config)){const{SqliteStorageAdapter}=__require("@frontmcp/storage-sqlite");const adapter=new SqliteStorageAdapter({path:config.sqlite.path,encryption:config.sqlite.encryption,ttlCleanupIntervalMs:config.sqlite.ttlCleanupIntervalMs??6e4,walMode:config.sqlite.walMode??true,busyTimeoutMs:config.sqlite.busyTimeoutMs});await adapter.connect();return adapter}if(isRedisTokenStorage(config)){const{host,port,password,db,tls,keyPrefix}=config.redis;const adapter=new RedisStorageAdapter({config:{host,port,password,db,tls},keyPrefix:keyPrefix??""});await adapter.connect();return adapter}const fallback=new MemoryStorageAdapter;await fallback.connect();return fallback}async function createSessionCredentialVault(options){const storage=options.storage??await createTokenStorageAdapter(options.tokenStorage);const vault=new SessionCredentialVault({storage,pepper:options.pepper,keyPrefix:options.keyPrefix,ttlMs:options.ttlMs,logger:options.logger});const type=options.tokenStorage===void 0||options.tokenStorage==="memory"?"memory":"sqlite"in options.tokenStorage?"sqlite":"redis";return{vault,type}}import{signData,verifyData}from"@frontmcp/utils";var DEFAULT_RESUME_TTL_MS=6e5;function signCredentialResumeToken(input,secret){const payload={sub:input.sub,key:input.key,...input.context!==void 0?{context:input.context}:{},exp:Date.now()+(input.ttlMs??DEFAULT_RESUME_TTL_MS)};const signedJson=signData(payload,{secret});return Buffer.from(signedJson,"utf8").toString("base64url")}function verifyCredentialResumeToken(token,secret,now=Date.now()){let signedJson;try{signedJson=Buffer.from(token,"base64url").toString("utf8")}catch{return null}const payload=verifyData(signedJson,{secret});if(!payload)return null;if(typeof payload.sub!=="string"||typeof payload.key!=="string"||!Number.isFinite(payload.exp)){return null}if(payload.context!==void 0&&typeof payload.context!=="string"){return null}if(now>=payload.exp){return null}return payload}function buildCredentialResumeUrl(basePath,token){const trimmed=basePath.endsWith("/")?basePath.slice(0,-1):basePath;return`${trimmed}/oauth/connect?token=${encodeURIComponent(token)}`}import{randomUUID as randomUUID3,signData as signData2,verifyData as verifyData2}from"@frontmcp/utils";var DEFAULT_INCREMENTAL_TICKET_TTL_MS=3e5;function profileKey(secret){return new TextEncoder().encode(secret)}function profileBinding(jti){return`incremental-ticket:${jti}`}function signIncrementalAuthTicket(input,secret){const jti=randomUUID3();const hasProfile=input.email!==void 0||input.name!==void 0;const payload={sub:input.sub,appId:input.appId,...input.toolId!==void 0?{toolId:input.toolId}:{},...input.priorAppIds!==void 0?{priorAppIds:input.priorAppIds}:{},...hasProfile?{profile:sealPendingLogin(profileKey(secret),profileBinding(jti),{sub:input.sub,email:input.email,name:input.name})}:{},jti,exp:Date.now()+(input.ttlMs??DEFAULT_INCREMENTAL_TICKET_TTL_MS)};const signedJson=signData2(payload,{secret});return Buffer.from(signedJson,"utf8").toString("base64url")}function verifyIncrementalAuthTicket(token,secret,now=Date.now()){let signedJson;try{signedJson=Buffer.from(token,"base64url").toString("utf8")}catch{return null}const payload=verifyData2(signedJson,{secret});if(!payload)return null;if(typeof payload.sub!=="string"||payload.sub.length===0)return null;if(typeof payload.appId!=="string"||payload.appId.length===0)return null;if(typeof payload.jti!=="string"||payload.jti.length===0)return null;if(!Number.isFinite(payload.exp))return null;if(payload.toolId!==void 0&&typeof payload.toolId!=="string")return null;if(payload.profile!==void 0&&typeof payload.profile!=="string")return null;if(payload.priorAppIds!==void 0&&(!Array.isArray(payload.priorAppIds)||payload.priorAppIds.some(id=>typeof id!=="string"))){return null}if(now>=payload.exp){return null}return payload}function openIncrementalAuthTicketProfile(payload,secret){if(!payload.profile)return void 0;const login=openPendingLogin(profileKey(secret),profileBinding(payload.jti),payload.profile);return login?.sub===payload.sub?{sub:login.sub,email:login.email,name:login.name}:void 0}var CREDENTIALS_ACCESSOR=Symbol.for("frontmcp:CREDENTIALS_ACCESSOR");var CredentialsAccessorImpl=class{vault;resolveSub;signingSecret;basePath;resumeTtlMs;logger;constructor(deps){this.vault=deps.vault;this.resolveSub=deps.resolveSub;this.signingSecret=deps.signingSecret;this.basePath=deps.basePath;this.resumeTtlMs=deps.resumeTtlMs;this.logger=deps.logger??noopAuthLogger}async get(key){const sub=this.resolveSub();if(!sub)return void 0;return this.vault.get(sub,key)}async list(){const sub=this.resolveSub();if(!sub)return[];return this.vault.list(sub)}async requireConnect(options){const{key,context}=options;const sub=this.resolveSub();if(!sub){return{connected:false,key,resumeUrl:"",signInRequired:true,message:`Sign in to this server to connect credential "${key}".`}}const existing=await this.vault.get(sub,key);if(existing){return{connected:true,key,credential:existing}}const token=signCredentialResumeToken({sub,key,context,ttlMs:this.resumeTtlMs},this.signingSecret);const resumeUrl=buildCredentialResumeUrl(this.basePath,token);this.logger.debug(`CredentialsAccessor: credential "${key}" not connected; issued resume link`);return{connected:false,key,resumeUrl,message:`Credential "${key}" is not connected. Open the resume URL to connect it.`}}};var DEFAULT_KEY_PREFIX2="mcp:secret:";var EncryptedStorageSecureStoreBackend=class{storage;encryption;keyPrefix;logger;keyCache=new Map;constructor(options){this.storage=options.storage;this.keyPrefix=options.keyPrefix??DEFAULT_KEY_PREFIX2;this.logger=options.logger??noopAuthLogger;this.encryption=new VaultEncryption({pepper:options.pepper,hkdfInfo:"frontmcp-secure-store-v1"})}keysKey(namespace){return`${this.keyPrefix}keys:${namespace}`}dataKey(namespace,key){return`${this.keyPrefix}data:${namespace}:${key}`}deriveKey(namespace){const cached=this.keyCache.get(namespace);if(cached)return cached;const promise=this.encryption.deriveKey({jti:namespace,vaultKey:namespace,sub:namespace,iat:0});this.keyCache.set(namespace,promise);return promise}setOpts(ttlMs){return ttlMs&&ttlMs>0?{ttlSeconds:Math.ceil(ttlMs/1e3)}:void 0}async get(namespace,key){const raw=await this.storage.get(this.dataKey(namespace,key));if(!raw)return null;try{const record=JSON.parse(raw);const aesKey=await this.deriveKey(namespace);return await this.encryption.decrypt(record.enc,aesKey)}catch(err){this.logger.warn(`SecureStore: failed to read secret "${key}": ${errMsg2(err)}`);return null}}async set(namespace,key,value,ttlMs){const aesKey=await this.deriveKey(namespace);const enc=await this.encryption.encrypt(value,aesKey);const record={enc};const opts=this.setOpts(ttlMs);await this.storage.set(this.dataKey(namespace,key),JSON.stringify(record),opts);await this.addKeyToSet(namespace,key,opts)}async delete(namespace,key){const existed=await this.storage.delete(this.dataKey(namespace,key));const keys=await this.readKeySet(namespace);const next=keys.filter(k=>k!==key);if(next.length!==keys.length){await this.writeKeySet(namespace,next)}return existed}async list(namespace){return this.readKeySet(namespace)}async readKeySet(namespace){const raw=await this.storage.get(this.keysKey(namespace));if(!raw)return[];try{const parsed=JSON.parse(raw);return Array.isArray(parsed)?parsed.filter(k=>typeof k==="string"):[]}catch{return[]}}async writeKeySet(namespace,keys,opts){const unique=[...new Set(keys)];await this.storage.set(this.keysKey(namespace),JSON.stringify(unique),opts)}async addKeyToSet(namespace,key,opts){const keys=await this.readKeySet(namespace);if(!keys.includes(key)){keys.push(key);await this.writeKeySet(namespace,keys,opts)}}};function errMsg2(err){return err instanceof Error?err.message:String(err)}var SECURE_STORE_ACCESSOR=Symbol.for("frontmcp:SECURE_STORE_ACCESSOR");import{sha256Hex as sha256Hex2}from"@frontmcp/utils";var GLOBAL_NAMESPACE="global";var SecureStoreAccessorImpl=class{backend;scope;resolveSub;resolveSessionId;ttlMs;logger;constructor(deps){this.backend=deps.backend;this.scope=deps.scope??"user";this.resolveSub=deps.resolveSub;this.resolveSessionId=deps.resolveSessionId;this.ttlMs=deps.ttlMs;this.logger=deps.logger??noopAuthLogger}resolveNamespace(){switch(this.scope){case"global":return GLOBAL_NAMESPACE;case"session":{const sid=this.resolveSessionId();return sid?`s:${sha256Hex2(sid).slice(0,32)}`:void 0}case"user":default:{const sub=this.resolveSub();return sub?`u:${sha256Hex2(sub).slice(0,32)}`:void 0}}}async get(key){const namespace=this.resolveNamespace();if(!namespace)return void 0;const raw=await this.backend.get(namespace,key);if(raw===null)return void 0;try{return JSON.parse(raw)}catch(err){this.logger.warn(`SecureStore: failed to parse secret "${key}": ${errMsg3(err)}`);return void 0}}async set(key,value,options){const namespace=this.resolveNamespace();if(!namespace){this.logger.debug(`SecureStore: no ${this.scope} scope for request; skipping set("${key}")`);return}const ttlMs=options?.ttlMs??this.ttlMs;await this.backend.set(namespace,key,JSON.stringify(value),ttlMs)}async delete(key){const namespace=this.resolveNamespace();if(!namespace)return false;return this.backend.delete(namespace,key)}async list(){const namespace=this.resolveNamespace();if(!namespace)return[];return this.backend.list(namespace)}};function errMsg3(err){return err instanceof Error?err.message:String(err)}import{MemoryStorageAdapter as MemoryStorageAdapter2}from"@frontmcp/utils";import{internalZ as z7}from"@frontmcp/lazy-zod";import{internalZ as z4}from"@frontmcp/lazy-zod";var jwkParametersSchema=z4.object({kty:z4.string().optional(),alg:z4.string().optional(),key_ops:z4.array(z4.string()).optional(),ext:z4.boolean().optional(),use:z4.string().optional(),x5c:z4.array(z4.string()).optional(),x5t:z4.string().optional(),"x5t#S256":z4.string().optional(),x5u:z4.string().optional(),kid:z4.string().optional()});var jwkSchema=jwkParametersSchema.extend({crv:z4.string().optional(),d:z4.string().optional(),dp:z4.string().optional(),dq:z4.string().optional(),e:z4.string().optional(),k:z4.string().optional(),n:z4.string().optional(),p:z4.string().optional(),q:z4.string().optional(),qi:z4.string().optional(),x:z4.string().optional(),y:z4.string().optional(),pub:z4.string().optional(),priv:z4.string().optional()});var jsonWebKeySetSchema=z4.object({keys:z4.array(jwkSchema)});import{internalZ as z5}from"@frontmcp/lazy-zod";var transportProtocolSchema=z5.enum(["legacy-sse","sse","streamable-http","stateful-http","stateless-http"]);var sseTransportStateSchema=z5.object({type:z5.literal("sse"),lastEventId:z5.string().optional(),lastPing:z5.number().optional(),connectionState:z5.enum(["connecting","open","closed"]).optional()});var streamableHttpTransportStateSchema=z5.object({type:z5.literal("streamable-http"),requestSeq:z5.number(),activeStreamId:z5.string().optional(),pendingRequests:z5.array(z5.string()).optional()});var statefulHttpTransportStateSchema=z5.object({type:z5.literal("stateful-http"),requestSeq:z5.number(),pendingResponses:z5.array(z5.string()).optional(),lastActivity:z5.number().optional()});var statelessHttpTransportStateSchema=z5.object({type:z5.literal("stateless-http"),requestCount:z5.number(),windowStart:z5.number().optional()});var legacySseTransportStateSchema=z5.object({type:z5.literal("legacy-sse"),messagePath:z5.string(),lastEventId:z5.string().optional(),connectionState:z5.enum(["connecting","open","closed"]).optional()});var transportStateSchema=z5.discriminatedUnion("type",[sseTransportStateSchema,streamableHttpTransportStateSchema,statefulHttpTransportStateSchema,statelessHttpTransportStateSchema,legacySseTransportStateSchema]);var transportSessionSchema=z5.object({id:z5.string(),authorizationId:z5.string(),protocol:transportProtocolSchema,createdAt:z5.number(),expiresAt:z5.number().optional(),nodeId:z5.string(),clientFingerprint:z5.string().optional(),transportState:transportStateSchema.optional()});var sessionJwtPayloadSchema=z5.object({sid:z5.string(),aid:z5.string(),proto:transportProtocolSchema,nid:z5.string(),iat:z5.number(),exp:z5.number().optional()});var encryptedBlobSchema=z5.object({alg:z5.literal("A256GCM"),kid:z5.string().optional(),iv:z5.string(),tag:z5.string(),data:z5.string(),exp:z5.number().optional(),meta:z5.record(z5.string(),z5.unknown()).optional()});var storedSessionSchema=z5.object({session:transportSessionSchema,authorizationId:z5.string(),tokens:z5.record(z5.string(),encryptedBlobSchema).optional(),createdAt:z5.number(),lastAccessedAt:z5.number(),initialized:z5.boolean().optional(),maxLifetimeAt:z5.number().optional(),clientCapabilities:z5.record(z5.string(),z5.unknown()).optional(),reassignedAt:z5.number().optional(),reassignedFrom:z5.string().optional()});var redisConfigSchema=z5.object({host:z5.string().min(1),port:z5.number().int().positive().optional().default(6379),password:z5.string().optional(),db:z5.number().int().nonnegative().optional().default(0),tls:z5.boolean().optional().default(false),keyPrefix:z5.string().optional().default("mcp:session:"),defaultTtlMs:z5.number().int().positive().optional().default(36e5)});var noopLogger={child:()=>noopLogger,debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}};import{internalZ as z6}from"@frontmcp/lazy-zod";var clientJwksSchema=z6.object({keys:z6.array(z6.record(z6.string(),z6.unknown()))});var clientMetadataDocumentSchema=z6.object({client_id:z6.string().url(),client_name:z6.string().min(1),redirect_uris:z6.array(z6.string().url()).min(1),token_endpoint_auth_method:z6.enum(["none","client_secret_basic","client_secret_post","private_key_jwt"]).default("none"),grant_types:z6.array(z6.string()).default(["authorization_code","refresh_token"]),response_types:z6.array(z6.string()).default(["code"]),client_uri:z6.string().url().optional(),logo_uri:z6.string().url().optional(),jwks_uri:z6.string().url().optional(),jwks:clientJwksSchema.optional(),tos_uri:z6.string().url().optional(),policy_uri:z6.string().url().optional(),scope:z6.string().optional(),contacts:z6.array(z6.string().email()).optional(),software_statement:z6.string().optional(),software_id:z6.string().optional(),software_version:z6.string().optional()});var cimdRedisCacheConfigSchema=z6.object({url:z6.string().optional(),host:z6.string().optional(),port:z6.number().optional(),password:z6.string().optional(),db:z6.number().optional(),tls:z6.boolean().optional(),keyPrefix:z6.string().default("cimd:")});var cimdCacheConfigSchema=z6.object({type:z6.enum(["memory","redis"]).default("memory"),defaultTtlMs:z6.number().min(0).default(36e5),maxTtlMs:z6.number().min(0).default(864e5),minTtlMs:z6.number().min(0).default(6e4),redis:cimdRedisCacheConfigSchema.optional()});var cimdSecurityConfigSchema=z6.object({blockPrivateIPs:z6.boolean().default(true),allowedDomains:z6.array(z6.string()).optional(),blockedDomains:z6.array(z6.string()).optional(),warnOnLocalhostRedirects:z6.boolean().default(true),allowInsecureForTesting:z6.boolean().default(false)});var cimdNetworkConfigSchema=z6.object({timeoutMs:z6.number().min(100).default(5e3),maxResponseSizeBytes:z6.number().min(1024).default(65536),redirectPolicy:z6.enum(["deny","same-origin","allow"]).default("deny"),maxRedirects:z6.number().int().min(0).default(5)});var cimdConfigSchema=z6.object({enabled:z6.boolean().default(true),cache:cimdCacheConfigSchema.optional(),security:cimdSecurityConfigSchema.optional(),network:cimdNetworkConfigSchema.optional()});import{sha256Hex as sha256Hex3}from"@frontmcp/utils";var CLIENT_ASSERTION_TYPE_JWT_BEARER="urn:ietf:params:oauth:client-assertion-type:jwt-bearer";var CLIENT_ASSERTION_SIGNING_ALGORITHMS=["RS256","RS384","RS512","PS256","PS384","PS512","ES256","ES384","ES512","EdDSA","Ed25519"];var MAX_CLIENT_ASSERTION_LIFETIME_SECONDS=600;var CLOCK_TOLERANCE_SECONDS=30;var MAX_CLIENT_ASSERTION_LENGTH=16384;async function verifyClientAssertion(context){const{clientId,assertion}=context;const refuse=reason=>new CimdClientAssertionError(clientId,reason);if(!assertion)throw refuse("missing_assertion");if(context.type!==CLIENT_ASSERTION_TYPE_JWT_BEARER)throw refuse("unsupported_assertion_type");if(assertion.length>MAX_CLIENT_ASSERTION_LENGTH)throw refuse("malformed");const jose2=await loadJose();let algorithm;try{algorithm=jose2.decodeProtectedHeader(assertion).alg}catch{throw refuse("malformed")}if(typeof algorithm!=="string"||!CLIENT_ASSERTION_SIGNING_ALGORITHMS.includes(algorithm)){throw refuse("unsupported_algorithm")}let payload;try{payload=await verifySignedClaims(jose2,context)}catch(error){if(error instanceof CimdError)throw error;throw refuse(refusalFor(error))}const now=Math.floor(Date.now()/1e3);const expiresAt=payload.exp??now;if(expiresAt-now>MAX_CLIENT_ASSERTION_LIFETIME_SECONDS)throw refuse("lifetime_too_long");if(typeof payload.iat==="number"&&payload.iat>now+CLOCK_TOLERANCE_SECONDS)throw refuse("not_yet_valid");if(typeof payload.jti!=="string"||payload.jti.length===0)throw refuse("missing_claim");const ttlMs=(expiresAt-now+CLOCK_TOLERANCE_SECONDS)*1e3;const claimed=await context.claimOnce(`client-assertion:${sha256Hex3(`${clientId}
|
|
395
|
+
${payload.jti}`)}`,ttlMs);if(!claimed)throw refuse("replayed")}async function verifySignedClaims(jose2,context){const options={algorithms:CLIENT_ASSERTION_SIGNING_ALGORITHMS,issuer:context.clientId,subject:context.clientId,audience:[...context.audiences],requiredClaims:["exp","jti"],clockTolerance:CLOCK_TOLERANCE_SECONDS};const verifyWith=async refresh=>{const keys=jose2.createLocalJWKSet(await context.keys(refresh));return(await jose2.jwtVerify(context.assertion??"",keys,options)).payload};try{return await verifyWith(false)}catch(error){if(errorCode(error)!=="ERR_JWKS_NO_MATCHING_KEY")throw error;return verifyWith(true)}}function refusalFor(error){const{claim,reason}=error;switch(errorCode(error)){case"ERR_JOSE_ALG_NOT_ALLOWED":return"unsupported_algorithm";case"ERR_JWKS_NO_MATCHING_KEY":case"ERR_JWKS_MULTIPLE_MATCHING_KEYS":case"ERR_JWKS_INVALID":case"ERR_JWK_INVALID":return"unknown_key";case"ERR_JWS_SIGNATURE_VERIFICATION_FAILED":return"bad_signature";case"ERR_JWT_EXPIRED":return"expired";case"ERR_JWT_CLAIM_VALIDATION_FAILED":if(reason==="missing")return"missing_claim";if(claim==="iss")return"wrong_issuer";if(claim==="sub")return"wrong_subject";if(claim==="aud")return"wrong_audience";if(claim==="nbf")return"not_yet_valid";return"malformed";default:return"malformed"}}function errorCode(error){return typeof error==="object"&&error!==null?error.code:void 0}init_cimd_cache();import{isRedirectResponse as isRedirectResponse2}from"@frontmcp/utils";init_cimd_cache();var MAX_CACHED_KEY_SETS=500;var KEY_SET_REFRESH_COOLDOWN_MS=3e4;var CimdService=class{config;cacheConfig;securityConfig;networkConfig;cacheBackend;keySets=new Map;logger;get enabled(){return this.config.enabled}constructor(logger,config){this.logger=(logger??noopLogger).child("CimdService");this.config=cimdConfigSchema.parse(config??{});this.cacheConfig=cimdCacheConfigSchema.parse(this.config.cache??{});this.securityConfig=cimdSecurityConfigSchema.parse(this.config.security??{});this.networkConfig=cimdNetworkConfigSchema.parse(this.config.network??{});this.logger.debug("CimdService initialized",{enabled:this.config.enabled,cacheDefaultTtlMs:this.cacheConfig.defaultTtlMs,networkTimeoutMs:this.networkConfig.timeoutMs});if(this.securityConfig.allowInsecureForTesting){this.logger.warn("CIMD allowInsecureForTesting is enabled. HTTP is allowed for localhost CIMD URLs. This should NEVER be enabled in production!")}}async initialize(){await this.cache()}cache(){if(!this.cacheBackend){this.cacheBackend=createCimdCache(this.cacheConfig).catch(error=>{this.cacheBackend=void 0;throw error})}return this.cacheBackend}async dispose(){const backend=this.cacheBackend;this.cacheBackend=void 0;if(!backend)return;const createdBackend=await backend.catch(()=>void 0);await createdBackend?.close?.()}isCimdClientId(clientId){return isCimdClientId(clientId,this.securityConfig.allowInsecureForTesting)}async resolveClientMetadata(clientId){if(!this.isCimdClientId(clientId)){return{isCimdClient:false,fromCache:false}}validateClientIdUrl(clientId,this.securityConfig);const cache=await this.cache();const cached=await cache.get(clientId);if(cached){this.logger.debug(`Cache hit for CIMD client: ${clientId}`);return{isCimdClient:true,metadata:cached.document,fromCache:true,expiresAt:cached.expiresAt,etag:cached.etag,lastModified:cached.lastModified}}this.logger.info(`Fetching CIMD document: ${clientId}`);const{document,headers}=await this.fetchMetadataDocument(clientId);this.validateDocument(clientId,document);if(this.securityConfig.warnOnLocalhostRedirects&&hasOnlyLocalhostRedirectUris(document.redirect_uris)){this.logger.warn(`CIMD client "${clientId}" has only localhost redirect URIs - this may be a development client`)}await cache.set(clientId,document,headers);const entry=await cache.get(clientId);return{isCimdClient:true,metadata:document,fromCache:false,expiresAt:entry?.expiresAt,etag:entry?.etag,lastModified:entry?.lastModified}}validateRedirectUri(redirectUri,metadata){const normalizedRequestUri=normalizeRedirectUri(redirectUri);const normalizedAllowed=metadata.redirect_uris.map(normalizeRedirectUri);if(!normalizedAllowed.includes(normalizedRequestUri)){throw new RedirectUriMismatchError(metadata.client_id,redirectUri,metadata.redirect_uris)}}assertSupportedClient(metadata){const problems=[];const method=metadata.token_endpoint_auth_method;if(method==="private_key_jwt"){problems.push(...this.keySourceProblems(metadata))}else if(method!=="none"){problems.push(`token_endpoint_auth_method "${method}" is not supported, only "none" or "private_key_jwt"`)}if(!metadata.grant_types.includes("authorization_code")){problems.push('grant_types must include "authorization_code"')}if(!metadata.response_types.includes("code")){problems.push('response_types must include "code"')}if(problems.length>0){throw new CimdValidationError(metadata.client_id,problems)}}async verifyClientAssertion(metadata,check){await verifyClientAssertion({...check,clientId:metadata.client_id,keys:refresh=>this.clientKeys(metadata,refresh)})}async clientKeys(metadata,refresh){if(metadata.jwks)return metadata.jwks;const jwksUri=metadata.jwks_uri;if(!jwksUri)throw new CimdClientAssertionError(metadata.client_id,"no_keys");const now=Date.now();const cached=this.keySets.get(jwksUri);const reusable=cached&&cached.expiresAt>now&&(!refresh||now-cached.fetchedAt<KEY_SET_REFRESH_COOLDOWN_MS);if(reusable)return cached.keys;validateClientIdUrl(jwksUri,this.securityConfig);const fetched=await this.fetchJson(jwksUri);if(fetched.notModified)throw new CimdFetchError(jwksUri,"304 Not Modified to an unconditional request");const parsed=clientJwksSchema.safeParse(fetched.document);if(!parsed.success)throw new CimdValidationError(jwksUri,["jwks_uri did not return a JSON Web Key Set"]);const{ttlMs}=parseCacheHeaders(extractCacheHeaders(fetched.headers),this.cacheConfig);this.keySets.delete(jwksUri);this.keySets.set(jwksUri,{keys:parsed.data,fetchedAt:now,expiresAt:now+ttlMs});if(this.keySets.size>MAX_CACHED_KEY_SETS){const oldest=this.keySets.keys().next().value;if(oldest!==void 0)this.keySets.delete(oldest)}return parsed.data}keySourceProblems(metadata){if(!metadata.jwks===!metadata.jwks_uri){return['token_endpoint_auth_method "private_key_jwt" needs exactly one of "jwks" or "jwks_uri"']}if(!metadata.jwks_uri)return[];try{validateClientIdUrl(metadata.jwks_uri,this.securityConfig);return[]}catch(error){if(error instanceof InvalidClientIdUrlError)return[`jwks_uri is not allowed: ${error.reason}`];if(error instanceof CimdSecurityError)return[`jwks_uri is not allowed: ${error.securityReason}`];throw error}}async clearCache(clientId){const cache=await this.cache();if(clientId){await cache.delete(clientId);this.logger.debug(`Cache cleared for: ${clientId}`)}else{await cache.clear();this.logger.debug("Cache cleared")}}async getCacheStats(){const cache=await this.cache();return{size:await cache.size()}}async fetchMetadataDocument(clientId){const cache=await this.cache();const conditionalHeaders=await cache.getConditionalHeaders(clientId);const fetched=await this.fetchJson(clientId,conditionalHeaders);if(!fetched.notModified)return fetched;const staleEntry=await cache.getStale(clientId);if(!staleEntry){throw new CimdFetchError(clientId,"304 Not Modified but no cached entry",{httpStatus:304})}await cache.revalidate(clientId,fetched.headers);this.logger.debug(`CIMD document not modified: ${clientId}`);return{document:staleEntry.document,headers:fetched.headers}}async fetchJson(url,conditionalHeaders){const controller=new AbortController;const timer=setTimeout(()=>controller.abort(),this.networkConfig.timeoutMs);try{const originalOrigin=new URL(url).origin;const maxRedirects=this.networkConfig.maxRedirects;let currentUrl=url;let redirectCount=0;let isFirstRequest=true;while(true){if(this.securityConfig.blockPrivateIPs&&!this.securityConfig.allowInsecureForTesting){await assertHostNotSsrf(new URL(currentUrl).hostname,url)}const headers={Accept:"application/json"};if(isFirstRequest&&conditionalHeaders){Object.assign(headers,conditionalHeaders)}const response=await fetch(currentUrl,{method:"GET",headers,signal:controller.signal,redirect:"manual"});if(response.status===304){return{notModified:true,headers:response.headers}}if(isRedirectResponse2(response)){const location=response.headers.get("location");if(!location){throw new CimdFetchError(url,"Redirect response missing Location header",{httpStatus:response.status})}const nextUrl=new URL(location,currentUrl).toString();const redirectPolicy=this.networkConfig.redirectPolicy;if(redirectPolicy==="deny"){throw new CimdFetchError(url,`CIMD fetch redirected to "${nextUrl}" but redirects are disabled. Host the metadata at the client_id URL or set auth.cimd.network.redirectPolicy to "same-origin" or "allow".`,{httpStatus:response.status})}if(redirectPolicy==="same-origin"){const nextOrigin=new URL(nextUrl).origin;if(nextOrigin!==originalOrigin){throw new CimdFetchError(url,`CIMD fetch redirected to "${nextUrl}" which is not the same origin as "${originalOrigin}". Host the metadata at the client_id URL or set auth.cimd.network.redirectPolicy to "allow".`,{httpStatus:response.status})}}validateClientIdUrl(nextUrl,this.securityConfig);redirectCount+=1;if(redirectCount>maxRedirects){throw new CimdFetchError(url,`CIMD fetch exceeded max redirects (${maxRedirects}). Host the metadata at the client_id URL or increase auth.cimd.network.maxRedirects.`)}this.logger.warn(`CIMD redirect ${redirectCount}/${maxRedirects}: ${currentUrl} -> ${nextUrl}`);currentUrl=nextUrl;isFirstRequest=false;continue}if(!response.ok){throw new CimdFetchError(url,`HTTP ${response.status} ${response.statusText}`,{httpStatus:response.status})}const contentLength=response.headers.get("content-length");if(contentLength){const length=parseInt(contentLength,10);if(!isNaN(length)&&length>this.networkConfig.maxResponseSizeBytes){throw new CimdResponseTooLargeError(url,this.networkConfig.maxResponseSizeBytes,length)}}const text=await this.readResponseWithLimit(response,this.networkConfig.maxResponseSizeBytes,url);let document;try{document=JSON.parse(text)}catch(e){throw new CimdFetchError(url,"Invalid JSON response",{originalError:e instanceof Error?e:new Error(String(e))})}return{notModified:false,document,headers:response.headers}}}catch(error){if(error instanceof CimdFetchError||error instanceof CimdResponseTooLargeError||error instanceof CimdSecurityError){throw error}if(error instanceof Error&&error.name==="AbortError"){throw new CimdFetchError(url,"Request timeout",{originalError:error})}throw new CimdFetchError(url,error instanceof Error?error.message:"Unknown error",{originalError:error instanceof Error?error:void 0})}finally{clearTimeout(timer)}}async readResponseWithLimit(response,maxBytes,clientId){const reader=response.body?.getReader();if(!reader){const buffer=await response.arrayBuffer();if(buffer.byteLength>maxBytes){throw new CimdResponseTooLargeError(clientId,maxBytes,buffer.byteLength)}return new TextDecoder().decode(buffer)}const chunks=[];let totalBytes=0;try{while(true){const{done,value}=await reader.read();if(done)break;totalBytes+=value.length;if(totalBytes>maxBytes){throw new CimdResponseTooLargeError(clientId,maxBytes,totalBytes)}chunks.push(value)}}finally{reader.releaseLock()}const combined=new Uint8Array(totalBytes);let offset=0;for(const chunk of chunks){combined.set(chunk,offset);offset+=chunk.length}return new TextDecoder().decode(combined)}validateDocument(clientId,document){const result=clientMetadataDocumentSchema.safeParse(document);if(!result.success){const errors=result.error.issues.map(issue=>{const path=issue.path.length>0?`${issue.path.join(".")}: `:"";return`${path}${issue.message}`});throw new CimdValidationError(clientId,errors)}if(result.data.client_id!==clientId){throw new CimdClientIdMismatchError(clientId,result.data.client_id)}}};var LOOPBACK_HOSTNAMES=new Set(["127.0.0.1","::1","[::1]","localhost"]);function isLoopbackHostname(hostname){return LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())}function normalizeRedirectUri(uri){try{const url=new URL(uri);const host=isLoopbackHostname(url.hostname)?url.hostname.toLowerCase():url.host.toLowerCase();let normalized=`${url.protocol.toLowerCase()}//${host}`;normalized+=url.pathname.replace(/\/+$/,"")||"/";if(url.search)normalized+=url.search;return normalized}catch{return uri}}var publicAccessConfigSchema=z7.object({tools:z7.union([z7.literal("all"),z7.array(z7.string())]).default("all"),prompts:z7.union([z7.literal("all"),z7.array(z7.string())]).default("all"),rateLimit:z7.number().default(60)});var localSigningConfigSchema=z7.object({signKey:jwkSchema.or(z7.instanceof(Uint8Array)).optional(),jwks:jsonWebKeySetSchema.optional(),issuer:z7.string().optional()});var providerConfigSchema=z7.object({name:z7.string().optional(),id:z7.string().optional(),jwks:jsonWebKeySetSchema.optional(),jwksUri:z7.string().url().optional(),additionalIssuers:z7.array(z7.string().trim().min(1,"additionalIssuers entries must be non-empty")).optional(),verifyIssuer:z7.boolean().optional(),dcrEnabled:z7.boolean().default(false),authEndpoint:z7.string().url().optional(),tokenEndpoint:z7.string().url().optional(),registrationEndpoint:z7.string().url().optional(),userInfoEndpoint:z7.string().url().optional()});var remoteProviderConfigSchema=z7.object({provider:z7.string().url(),name:z7.string().optional(),id:z7.string().optional(),jwks:jsonWebKeySetSchema.optional(),jwksUri:z7.string().url().optional(),clientId:z7.string().optional(),clientSecret:z7.string().optional(),scopes:z7.array(z7.string()).optional(),dcrEnabled:z7.boolean().default(false),authEndpoint:z7.string().url().optional(),tokenEndpoint:z7.string().url().optional(),registrationEndpoint:z7.string().url().optional(),userInfoEndpoint:z7.string().url().optional()});var upstreamProviderSchema=z7.object({id:z7.string().min(1),name:z7.string().optional(),authorizationEndpoint:z7.string().url().optional(),authorizeUrl:z7.string().url().optional(),tokenEndpoint:z7.string().url().optional(),tokenUrl:z7.string().url().optional(),clientId:z7.string().min(1),clientSecret:z7.string().optional(),scopes:z7.array(z7.string()).optional(),userInfoEndpoint:z7.string().url().optional(),jwksUri:z7.string().url().optional(),issuer:z7.string().url().optional(),additionalIssuers:z7.array(z7.string().trim().min(1,"additionalIssuers entries must be non-empty")).optional()}).refine(p=>!!(p.authorizationEndpoint??p.authorizeUrl),{message:"authorizationEndpoint (or authorizeUrl) is required",path:["authorizationEndpoint"]}).refine(p=>!!(p.tokenEndpoint??p.tokenUrl),{message:"tokenEndpoint (or tokenUrl) is required",path:["tokenEndpoint"]}).refine(p=>!(p.authorizationEndpoint&&p.authorizeUrl),{message:"provide only one of authorizationEndpoint or authorizeUrl",path:["authorizationEndpoint"]}).refine(p=>!(p.tokenEndpoint&&p.tokenUrl),{message:"provide only one of tokenEndpoint or tokenUrl",path:["tokenEndpoint"]});var localDcrClientSchema=z7.object({clientId:z7.string().min(1),clientSecret:z7.string().min(1).optional(),redirectUris:z7.array(z7.string().url()).min(1,"At least one redirect_uri is required"),clientName:z7.string().optional(),tokenEndpointAuthMethod:z7.enum(["none","client_secret_basic","client_secret_post"]).default("none"),grantTypes:z7.array(z7.enum(["authorization_code","refresh_token"])).default(["authorization_code","refresh_token"]),responseTypes:z7.array(z7.enum(["code"])).default(["code"]),scope:z7.string().optional()});var localDcrConfigSchema=z7.object({enabled:z7.boolean().optional(),allowedRedirectUris:z7.array(z7.string().min(1)).optional(),allowedClientIds:z7.array(z7.string().min(1)).optional(),initialAccessToken:z7.string().min(1).optional(),clients:z7.array(localDcrClientSchema).optional(),maxDynamicClients:z7.number().int().min(0).optional()});var flatRemoteProviderFields={provider:z7.string().url(),clientId:z7.string().optional(),clientSecret:z7.string().optional(),scopes:z7.array(z7.string()).optional(),providerConfig:providerConfigSchema.optional()};var tokenStorageSqliteSchema=z7.object({path:z7.string().min(1),encryption:z7.object({secret:z7.string().min(1)}).optional(),ttlCleanupIntervalMs:z7.number().int().nonnegative().optional(),walMode:z7.boolean().optional(),busyTimeoutMs:z7.number().int().nonnegative().optional()});var tokenStorageConfigSchema=z7.union([z7.literal("memory"),z7.object({redis:redisConfigSchema}),z7.object({sqlite:tokenStorageSqliteSchema})]);var secureStoreScopeSchema=z7.enum(["user","session","global"]);var secureStoreEncryptionSchema=z7.object({pepper:z7.string().min(1).optional()});var secureStoreCustomBackendSchema=z7.custom(v=>typeof v==="object"&&v!==null&&typeof v.get==="function"&&typeof v.set==="function"&&typeof v.delete==="function"&&typeof v.list==="function",{message:"secureStore.backend must implement get/set/delete/list (SecureStoreBackend)"});var secureStoreCommonFields={scope:secureStoreScopeSchema.optional(),ttlMs:z7.number().int().positive().optional(),encryption:secureStoreEncryptionSchema.optional()};var secureStoreBackingSchemas={sqlite:tokenStorageSqliteSchema,redis:redisConfigSchema,backend:secureStoreCustomBackendSchema};function secureStoreBackingError(issue){if(typeof issue.input!=="object"||issue.input===null)return void 0;const config=issue.input;const named=Object.keys(secureStoreBackingSchemas).filter(backing=>config[backing]!==void 0);if(named.length>1)return`secureStore takes one backing, not ${named.join(" and ")}`;for(const[backing,schema2]of Object.entries(secureStoreBackingSchemas)){if(config[backing]===void 0)continue;const nested=schema2.safeParse(config[backing]).error?.issues[0];if(!nested)continue;const where=["secureStore",backing,...nested.path.map(String)].join(".");return nested.message.startsWith(where)?nested.message:`${where}: ${nested.message}`}return void 0}var noBacking={sqlite:z7.undefined().optional(),redis:z7.undefined().optional(),backend:z7.undefined().optional()};var secureStoreConfigSchema=z7.union([z7.literal("memory"),z7.object({...noBacking,sqlite:secureStoreBackingSchemas.sqlite,...secureStoreCommonFields}),z7.object({...noBacking,redis:secureStoreBackingSchemas.redis,...secureStoreCommonFields}),z7.object({...noBacking,backend:secureStoreBackingSchemas.backend,...secureStoreCommonFields}),z7.object({...noBacking,...secureStoreCommonFields})],{error:secureStoreBackingError});var tokenRefreshConfigSchema=z7.object({enabled:z7.boolean().default(true),skewSeconds:z7.number().nonnegative().default(60)});var skippedAppBehaviorSchema=z7.enum(["anonymous","require-auth"]);var consentConfigSchema=z7.object({enabled:z7.boolean().default(false),groupByApp:z7.boolean().default(true),showDescriptions:z7.boolean().default(true),allowSelectAll:z7.boolean().default(true),requireSelection:z7.boolean().default(true),customMessage:z7.string().optional(),rememberConsent:z7.boolean().default(true),excludedTools:z7.array(z7.string()).optional(),defaultSelectedTools:z7.array(z7.string()).optional()});var federatedAuthConfigSchema=z7.object({stateValidation:z7.enum(["strict","format"]).default("strict"),minProviders:z7.number().int().positive().optional(),requiredProviders:z7.array(z7.string()).optional()});var incrementalAuthConfigSchema=z7.object({enabled:z7.boolean().default(true),skippedAppBehavior:skippedAppBehaviorSchema.default("anonymous"),allowSkip:z7.boolean().default(true),showAllAppsAtOnce:z7.boolean().default(true)});function isCustomBackendConfig(config){return typeof config==="object"&&config!==null&&"backend"in config&&!!config.backend}function isSqliteConfig(config){return typeof config==="object"&&config!==null&&"sqlite"in config&&!!config.sqlite}function isRedisConfig(config){return typeof config==="object"&&config!==null&&"redis"in config&&!!config.redis}function readScope(config){if(typeof config==="object"&&config!==null&&"scope"in config&&config.scope){return config.scope}return"user"}function readTtlMs(config){if(typeof config==="object"&&config!==null&&"ttlMs"in config){return config.ttlMs}return void 0}function readPepper(config){if(typeof config==="object"&&config!==null&&"encryption"in config&&config.encryption){return config.encryption.pepper}return void 0}function toTokenStorageConfig(config){if(isSqliteConfig(config)){return{sqlite:config.sqlite}}if(isRedisConfig(config)){return{redis:config.redis}}return"memory"}function refuseUnusableConfig(config){if(config===void 0)return;const issue=secureStoreConfigSchema.safeParse(config).error?.issues[0];if(!issue)return;const where=issue.path.length>0?`secureStore.${issue.path.map(String).join(".")}: `:"";throw new SecureStoreConfigError(`${where}${issue.message}`)}async function createSecureStore(options){const{config,pepper,storage,logger}=options;refuseUnusableConfig(config);const scope=readScope(config);const ttlMs=readTtlMs(config);if(isCustomBackendConfig(config)){return{backend:config.backend,scope,ttlMs,kind:"custom"}}const kind=isSqliteConfig(config)?"sqlite":isRedisConfig(config)?"redis":"memory";let adapter;if(storage){adapter=storage}else if(kind==="memory"){adapter=new MemoryStorageAdapter2;await adapter.connect()}else{adapter=await createTokenStorageAdapter(toTokenStorageConfig(config))}const backend=new EncryptedStorageSecureStoreBackend({storage:adapter,pepper:readPepper(config)??pepper,logger});return{backend,scope,ttlMs,kind,...storage?{}:{close:()=>adapter.disconnect()}}}import{base64urlDecode as base64urlDecode4,base64urlEncode as base64urlEncode4,decryptAesGcm as decryptAesGcm3,encryptAesGcm as encryptAesGcm3,randomBytes as randomBytes6}from"@frontmcp/utils";var TokenVault=class{active;keys=new Map;constructor(keys){if(!Array.isArray(keys)||keys.length===0){throw new Error("TokenVault requires at least one key")}for(const k of keys){if(!(k.key instanceof Uint8Array)||k.key.length!==32){throw new Error(`TokenVault key "${k.kid}" must be a 32-byte Uint8Array for AES-256-GCM`)}if(this.keys.has(k.kid)){throw new Error(`TokenVault duplicate kid: "${k.kid}"`)}this.keys.set(k.kid,k.key)}this.active=keys[0]}rotateTo(k){if(!(k.key instanceof Uint8Array)||k.key.length!==32){throw new Error(`TokenVault key "${k.kid}" must be a 32-byte Uint8Array for AES-256-GCM`)}this.active=k;this.keys.set(k.kid,k.key)}async encrypt(plaintext,opts){const iv=randomBytes6(12);const{ciphertext,tag}=await encryptAesGcm3(this.active.key,new TextEncoder().encode(plaintext),iv);return{alg:"A256GCM",kid:this.active.kid,iv:base64urlEncode4(iv),tag:base64urlEncode4(tag),data:base64urlEncode4(ciphertext),exp:opts?.exp,meta:opts?.meta}}async decrypt(blob){if(blob.exp!==void 0){const nowSeconds=Math.floor(Date.now()/1e3);if(nowSeconds>blob.exp){throw new Error(`vault_expired:${blob.kid}`)}}const key=this.keys.get(blob.kid);if(!key)throw new Error(`vault_unknown_kid:${blob.kid}`);const iv=base64urlDecode4(blob.iv);const tag=base64urlDecode4(blob.tag);const data=base64urlDecode4(blob.data);const plaintext=await decryptAesGcm3(key,data,iv,tag);return new TextDecoder().decode(plaintext)}};import{hkdfSha256 as hkdfSha2565,encryptValue as encryptValue2,decryptValue as decryptValue2,encryptAesGcm as encryptAesGcm6,decryptAesGcm as decryptAesGcm6}from"@frontmcp/utils";import{getEnv as getEnv2,isProduction as isProduction2,isSignedData,signData as signData3,verifyData as verifyData3,verifyOrParseData}from"@frontmcp/utils";function getSigningSecret(config){const secret=config?.secret||getEnv2("MCP_SESSION_SECRET");if(!secret){if(isProduction2()){throw new SessionSecretRequiredError("session signing")}console.warn("[SessionCrypto] MCP_SESSION_SECRET not set. Using insecure default for development only.");return"insecure-dev-secret-do-not-use-in-production"}return secret}function resolveConfig(config){return{secret:getSigningSecret(config)}}function signSession(session,config){return signData3(session,resolveConfig(config))}function verifySession(signedData,config){return verifyData3(signedData,resolveConfig(config))}function verifyOrParseSession(data,config){return verifyOrParseData(data,resolveConfig(config))}var SessionRateLimiter=class{windowMs;maxRequests;requests=new Map;cleanupIntervalMs;cleanupTimer=null;constructor(config={}){this.windowMs=config.windowMs??6e4;this.maxRequests=config.maxRequests??100;this.cleanupIntervalMs=config.cleanupIntervalMs??6e4}ensureCleanupTimer(){if(this.cleanupTimer||this.cleanupIntervalMs<=0)return;this.cleanupTimer=setInterval(()=>this.cleanup(),this.cleanupIntervalMs);if(typeof this.cleanupTimer.unref==="function")this.cleanupTimer.unref()}check(key){this.ensureCleanupTimer();const now=Date.now();const windowStart=now-this.windowMs;let timestamps=this.requests.get(key);if(!timestamps){timestamps=[];this.requests.set(key,timestamps)}const validTimestamps=timestamps.filter(t=>t>windowStart);const oldestInWindow=validTimestamps[0]??now;const resetAt=oldestInWindow+this.windowMs;if(validTimestamps.length>=this.maxRequests){return{allowed:false,remaining:0,resetAt,retryAfterMs:resetAt-now}}validTimestamps.push(now);this.requests.set(key,validTimestamps);return{allowed:true,remaining:this.maxRequests-validTimestamps.length,resetAt}}wouldAllow(key){const now=Date.now();const windowStart=now-this.windowMs;const timestamps=this.requests.get(key);if(!timestamps)return true;const validCount=timestamps.filter(t=>t>windowStart).length;return validCount<this.maxRequests}reset(key){this.requests.delete(key)}cleanup(){const now=Date.now();const windowStart=now-this.windowMs;for(const[key,timestamps]of this.requests.entries()){const valid=timestamps.filter(t=>t>windowStart);if(valid.length===0){this.requests.delete(key)}else if(valid.length<timestamps.length){this.requests.set(key,valid)}}}getStats(){let totalRequests=0;for(const timestamps of this.requests.values()){totalRequests+=timestamps.length}return{totalKeys:this.requests.size,totalRequests}}dispose(){if(this.cleanupTimer){clearInterval(this.cleanupTimer);this.cleanupTimer=null}this.requests.clear()}};var defaultSessionRateLimiter=new SessionRateLimiter;import{randomUUID as randomUUID4}from"@frontmcp/utils";var TransportIdGenerator=class{static createId(){return randomUUID4().replace(/-/g,"")}};var TinyTtlCache=class{constructor(ttlMs){this.ttlMs=ttlMs}ttlMs;map=new Map;get(k){const hit=this.map.get(k);if(!hit)return void 0;if(hit.exp<Date.now()){this.map.delete(k);return void 0}return hit.v}set(k,v){this.map.set(k,{v,exp:Date.now()+this.ttlMs})}delete(k){return this.map.delete(k)}clear(){this.map.clear()}size(){return this.map.size}cleanup(){const now=Date.now();let removed=0;for(const[k,{exp}]of this.map){if(exp<now){this.map.delete(k);removed++}}return removed}};import{sha256Base64url as sha256Base64url3}from"@frontmcp/utils";function isJwt(token){if(!token)return false;return token.split(".").length===3}function getTokenSignatureFingerprint(token){if(isJwt(token)){const sig=token.split(".")[2];if(sig)return sig}return sha256Base64url3(token)}function extractClaimValue(claims,key,validator){const value=claims[key];return validator(value)?value:void 0}var isString=value=>typeof value==="string";var isNumber=value=>typeof value==="number";var isStringOrStringArray=value=>typeof value==="string"||Array.isArray(value);function deriveTypedUser(claims){return{...claims,iss:extractClaimValue(claims,"iss",isString)??"",sid:extractClaimValue(claims,"sid",isString),sub:extractClaimValue(claims,"sub",isString)??extractClaimValue(claims,"client_id",isString)??extractClaimValue(claims,"azp",isString)??"",exp:extractClaimValue(claims,"exp",isNumber),iat:extractClaimValue(claims,"iat",isNumber),aud:extractClaimValue(claims,"aud",isStringOrStringArray),email:extractClaimValue(claims,"email",isString),preferred_username:extractClaimValue(claims,"preferred_username",isString),username:extractClaimValue(claims,"username",isString),name:extractClaimValue(claims,"name",isString),picture:extractClaimValue(claims,"picture",isString)}}function anonymousCallerClaims(options,anonymousId){const{issuer,scopes=["anonymous"],subject}=options;return{sub:subject??`anon:${anonymousId}`,iss:issuer,name:subject?"Static token":"Anonymous",scope:scopes.join(" ")}}function extractBearerToken(header){if(!header)return void 0;const m=header.match(/^\s*Bearer\s+(\S+)\s*$/i);return m?m[1]:void 0}import{decryptValue,encryptValue,getEnv as getEnv3,getMachineId,isProduction as isProduction3,sha256}from"@frontmcp/utils";var cachedKey=null;function getKey(){if(cachedKey)return cachedKey;const secret=getEnv3("MCP_SESSION_SECRET");if(!secret){if(isProduction3()){throw new SessionSecretRequiredError("session ID encryption")}console.warn("[SessionIdUtils] Using machine ID as session encryption secret - NOT SECURE FOR PRODUCTION. Set MCP_SESSION_SECRET environment variable for secure session encryption.")}const base=secret||getMachineId();cachedKey=sha256(new TextEncoder().encode(base));return cachedKey}function isSessionSecretConfigured(){return!!getEnv3("MCP_SESSION_SECRET")}function encryptJson(obj){const key=getKey();const encrypted=encryptValue(obj,key);return`${encrypted.iv}.${encrypted.tag}.${encrypted.data}`}function decryptSessionJson(sessionId){const parts=sessionId.split(".");if(parts.length!==3)return null;const[ivB64,tagB64,ctB64]=parts;if(!ivB64||!tagB64||!ctB64)return null;const key=getKey();return decryptValue({alg:"A256GCM",iv:ivB64,tag:tagB64,data:ctB64},key)}function safeDecrypt(sessionId){try{return decryptSessionJson(sessionId)}catch{return null}}function resetCachedKey(){cachedKey=null}import{TypedStorage as TypedStorage4}from"@frontmcp/utils";import{EncryptedTypedStorage,EncryptedStorageError}from"@frontmcp/utils";import{randomUUID as randomUUID5,TypedStorage as TypedStorage2}from"@frontmcp/utils";var StorageTokenStore=class{storage;namespace;defaultTtlSeconds;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"tok";this.defaultTtlSeconds=options.defaultTtlSeconds;this.storageIsNamespaced="namespace"in storage&&typeof storage.namespace==="function";const namespacedStorage=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.storage=new TypedStorage2(namespacedStorage)}allocId(){return randomUUID5()}async put(id,blob){const record={id,blob,updatedAt:Date.now()};const ttlSeconds=this.calculateTtl(blob.exp);await this.storage.set(this.key(id),record,ttlSeconds?{ttlSeconds}:void 0)}async get(id){const record=await this.storage.get(this.key(id));return record??void 0}async del(id){await this.storage.delete(this.key(id))}calculateTtl(exp){if(exp){const nowSeconds=Math.floor(Date.now()/1e3);const ttl=exp-nowSeconds;return ttl>0?ttl:1}return this.defaultTtlSeconds}key(id){return this.storageIsNamespaced?id:`${this.namespace}:${id}`}};import{randomUUID as randomUUID6,TypedStorage as TypedStorage3}from"@frontmcp/utils";var StorageAuthorizationVault=class{storage;namespace;pendingAuthTtlMs;constructor(storage,options={}){this.namespace=options.namespace??"vault";this.pendingAuthTtlMs=options.pendingAuthTtlMs??10*60*1e3;const namespacedStorage=this.isNamespacedStorage(storage)?storage.namespace(this.namespace):storage;this.storage=new TypedStorage3(namespacedStorage,{schema:options.validateOnRead?authorizationVaultEntrySchema:void 0,throwOnInvalid:false})}async create(params){const now=Date.now();const entry={id:randomUUID6(),userSub:params.userSub,userEmail:params.userEmail,userName:params.userName,clientId:params.clientId,createdAt:now,lastAccessAt:now,appCredentials:{},consent:params.consent,federated:params.federated,pendingAuths:[],authorizedAppIds:params.authorizedAppIds??[],skippedAppIds:params.skippedAppIds??[]};await this.storage.set(this.key(entry.id),entry);return entry}async get(id){return this.storage.get(this.key(id))}async update(id,updates){const entry=await this.get(id);if(!entry){console.warn(`[StorageAuthorizationVault] Update failed: vault entry not found for id ${id}`);return}const updated={...entry,...updates,lastAccessAt:Date.now()};await this.storage.set(this.key(id),updated)}async delete(id){await this.storage.delete(this.key(id))}async updateConsent(vaultId,consent){const entry=await this.get(vaultId);if(!entry)return;entry.consent=consent;entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async authorizeApp(vaultId,appId){const entry=await this.get(vaultId);if(!entry)return;entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==appId);if(!entry.authorizedAppIds.includes(appId)){entry.authorizedAppIds.push(appId)}entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async isAppAuthorized(vaultId,appId){const entry=await this.get(vaultId);if(!entry)return false;return entry.authorizedAppIds.includes(appId)}async createPendingAuth(vaultId,params){const entry=await this.get(vaultId);if(!entry){throw new Error(`Vault not found: ${vaultId}`)}const now=Date.now();const pendingAuth={id:randomUUID6(),appId:params.appId,toolId:params.toolId,authUrl:params.authUrl,requiredScopes:params.requiredScopes,elicitId:params.elicitId,createdAt:now,expiresAt:now+(params.ttlMs??this.pendingAuthTtlMs),status:"pending"};entry.pendingAuths.push(pendingAuth);entry.lastAccessAt=now;await this.storage.set(this.key(vaultId),entry);return pendingAuth}async getPendingAuth(vaultId,pendingAuthId){const entry=await this.get(vaultId);if(!entry)return null;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(!pendingAuth)return null;if(Date.now()>pendingAuth.expiresAt&&pendingAuth.status==="pending"){pendingAuth.status="expired";await this.storage.set(this.key(vaultId),entry)}return pendingAuth}async completePendingAuth(vaultId,pendingAuthId){const entry=await this.get(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="completed";entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==pendingAuth.appId);if(!entry.authorizedAppIds.includes(pendingAuth.appId)){entry.authorizedAppIds.push(pendingAuth.appId)}entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}}async cancelPendingAuth(vaultId,pendingAuthId){const entry=await this.get(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="cancelled";await this.storage.set(this.key(vaultId),entry)}}async getPendingAuths(vaultId){const entry=await this.get(vaultId);if(!entry)return[];const now=Date.now();let updated=false;const pending=entry.pendingAuths.filter(p=>{if(now>p.expiresAt&&p.status==="pending"){p.status="expired";updated=true}return p.status==="pending"});if(updated){await this.storage.set(this.key(vaultId),entry)}return pending}credentialKey(appId,providerId){return`${appId}:${providerId}`}async addAppCredential(vaultId,credential){const entry=await this.get(vaultId);if(!entry)return;const shouldStore=await this.shouldStoreCredential(vaultId,credential.appId);if(!shouldStore){return}const key=this.credentialKey(credential.appId,credential.providerId);entry.appCredentials[key]=credential;entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async removeAppCredential(vaultId,appId,providerId){const entry=await this.get(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);delete entry.appCredentials[key];entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async getAppCredentials(vaultId,appId){const entry=await this.get(vaultId);if(!entry)return[];const prefix=`${appId}:`;return Object.entries(entry.appCredentials).filter(([key])=>key.startsWith(prefix)).map(([,cred])=>cred)}async getCredential(vaultId,appId,providerId){const entry=await this.get(vaultId);if(!entry)return null;const key=this.credentialKey(appId,providerId);return entry.appCredentials[key]??null}async getAllCredentials(vaultId,filterByConsent=false){const entry=await this.get(vaultId);if(!entry)return[];const allCredentials=Object.values(entry.appCredentials);if(!filterByConsent||!entry.consent?.enabled){return allCredentials}const consentedToolIds=new Set(entry.consent.selectedToolIds);return allCredentials.filter(cred=>{return Array.from(consentedToolIds).some(toolId=>toolId.startsWith(`${cred.appId}:`))})}async updateCredential(vaultId,appId,providerId,updates){const entry=await this.get(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential)return;Object.assign(credential,updates);entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async shouldStoreCredential(vaultId,appId,toolIds){const entry=await this.get(vaultId);if(!entry)return false;const consent=entry.consent;if(!consent?.enabled){return true}if(toolIds&&toolIds.length>0){return toolIds.some(toolId=>consent.selectedToolIds.includes(toolId))}const consentedToolIds=consent.selectedToolIds;return consentedToolIds.some(toolId=>toolId.startsWith(`${appId}:`))}async invalidateCredential(vaultId,appId,providerId,reason){await this.updateCredential(vaultId,appId,providerId,{isValid:false,invalidReason:reason})}async refreshOAuthCredential(vaultId,appId,providerId,tokens){const entry=await this.get(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential||credential.credential.type!=="oauth"&&credential.credential.type!=="oauth_pkce")return;credential.credential.accessToken=tokens.accessToken;if(tokens.refreshToken!==void 0){credential.credential.refreshToken=tokens.refreshToken}if(tokens.expiresAt!==void 0){credential.credential.expiresAt=tokens.expiresAt;credential.expiresAt=tokens.expiresAt}credential.isValid=true;credential.invalidReason=void 0;entry.lastAccessAt=Date.now();await this.storage.set(this.key(vaultId),entry)}async cleanup(){const keys=await this.storage.keys("*");const now=Date.now();for(const key of keys){const entry=await this.storage.get(key);if(!entry)continue;const originalLength=entry.pendingAuths.length;entry.pendingAuths=entry.pendingAuths.filter(p=>{if(now>p.expiresAt&&p.status==="pending"){p.status="expired"}return p.status==="pending"});if(entry.pendingAuths.length!==originalLength){await this.storage.set(key,entry)}}}key(id){return this.isNamespacedStorage(this.storage.raw)?id:`${this.namespace}:${id}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{MemoryStorageAdapter as MemoryStorageAdapter3}from"@frontmcp/utils";var InMemoryAuthorizationVault=class extends StorageAuthorizationVault{memoryAdapter;constructor(options={}){const memoryAdapter=new MemoryStorageAdapter3;super(memoryAdapter,{namespace:options.namespace??"vault",pendingAuthTtlMs:options.pendingAuthTtlMs});this.memoryAdapter=memoryAdapter;void this.memoryAdapter.connect()}async clear(){const keys=await this.memoryAdapter.keys("*");for(const key of keys){await this.memoryAdapter.delete(key)}}};import{randomUUID as randomUUID7,RedisStorageAdapter as RedisStorageAdapter3}from"@frontmcp/utils";var RedisSessionStore=class{storage;keyPrefix;defaultTtlMs;logger;externalInstance=false;security;rateLimiter;constructor(config,logger){this.defaultTtlMs=("defaultTtlMs"in config?config.defaultTtlMs:void 0)??36e5;this.logger=logger;this.keyPrefix=("keyPrefix"in config?config.keyPrefix:void 0)??"mcp:session:";this.security=("security"in config?config.security:void 0)??{};if(this.security.enableRateLimiting){this.rateLimiter=new SessionRateLimiter({windowMs:this.security.rateLimiting?.windowMs,maxRequests:this.security.rateLimiting?.maxRequests})}if("redis"in config&&config.redis){this.storage=new RedisStorageAdapter3({client:config.redis,keyPrefix:this.keyPrefix});this.externalInstance=true}else{const redisConfig=config;this.storage=new RedisStorageAdapter3({config:{host:redisConfig.host,port:redisConfig.port??6379,password:redisConfig.password,db:redisConfig.db??0,tls:redisConfig.tls},keyPrefix:this.keyPrefix})}}validateSessionId(sessionId){if(!sessionId||sessionId.trim()===""){throw new SessionIdEmptyError("RedisSessionStore")}}async ensureConnected(){await this.storage.connect()}async get(sessionId,options){this.validateSessionId(sessionId);if(this.rateLimiter){const rateLimitKey=options?.clientIdentifier||sessionId;const rateLimitResult=this.rateLimiter.check(rateLimitKey);if(!rateLimitResult.allowed){this.logger?.warn("[RedisSessionStore] Rate limit exceeded for session lookup",{sessionId:sessionId.slice(0,20),clientIdentifier:options?.clientIdentifier?options.clientIdentifier.slice(0,20):void 0,retryAfterMs:rateLimitResult.retryAfterMs});return null}}await this.ensureConnected();let raw;const redis=this.storage.getClient();const ttlSeconds=Math.ceil(this.defaultTtlMs/1e3);if(redis){try{raw=await redis.getex(this.keyPrefix+sessionId,"EX",ttlSeconds)}catch{raw=await this.storage.get(sessionId);if(raw){await this.storage.expire(sessionId,ttlSeconds)}}}else{raw=await this.storage.get(sessionId);if(raw){await this.storage.expire(sessionId,ttlSeconds)}}if(!raw)return null;try{let parsed;if(this.security.enableSigning){parsed=verifyOrParseSession(raw,{secret:this.security.signingSecret});if(!parsed){this.logger?.warn("[RedisSessionStore] Session signature verification failed",{sessionId:sessionId.slice(0,20)});this.delete(sessionId).catch(()=>void 0);return null}}else{parsed=JSON.parse(raw)}const result=storedSessionSchema.safeParse(parsed);if(!result.success){this.logger?.warn("[RedisSessionStore] Invalid session format",{sessionId:sessionId.slice(0,20),errors:result.error.issues.slice(0,3).map(i=>({path:i.path,message:i.message}))});this.delete(sessionId).catch(()=>void 0);return null}const session=result.data;if(session.maxLifetimeAt&&session.maxLifetimeAt<Date.now()){this.logger?.info("[RedisSessionStore] Session exceeded max lifetime",{sessionId:sessionId.slice(0,20),maxLifetimeAt:session.maxLifetimeAt});await this.delete(sessionId);return null}if(session.session.expiresAt&&session.session.expiresAt<Date.now()){await this.delete(sessionId);return null}if(session.session.expiresAt){const ttlMs=Math.min(this.defaultTtlMs,session.session.expiresAt-Date.now());if(ttlMs>0&&ttlMs<this.defaultTtlMs){const boundedTtlSeconds=Math.ceil(ttlMs/1e3);this.storage.expire(sessionId,boundedTtlSeconds).catch(err=>{this.logger?.warn("[RedisSessionStore] TTL extension failed",{sessionId:sessionId.slice(0,20),error:err.message})})}}const updatedSession={...session,lastAccessedAt:Date.now()};return updatedSession}catch(error){this.logger?.warn("[RedisSessionStore] Failed to parse session",{sessionId:sessionId.slice(0,20),error:error.message});this.delete(sessionId).catch(()=>void 0);return null}}async set(sessionId,session,ttlMs){this.validateSessionId(sessionId);await this.ensureConnected();let value;if(this.security.enableSigning){value=signSession(session,{secret:this.security.signingSecret})}else{value=JSON.stringify(session)}if(ttlMs&&ttlMs>0){const ttlSeconds=Math.ceil(ttlMs/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else if(session.session.expiresAt){const ttl=session.session.expiresAt-Date.now();if(ttl>0){const ttlSeconds=Math.ceil(ttl/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else{await this.storage.set(sessionId,value)}}else{await this.storage.set(sessionId,value)}}async delete(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();await this.storage.delete(sessionId)}async exists(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();return this.storage.exists(sessionId)}allocId(){return randomUUID7()}async disconnect(){if(!this.externalInstance){await this.storage.disconnect()}}getRedisClient(){return this.storage.getClient()}async ping(){try{await this.ensureConnected();return this.storage.ping()}catch(error){this.logger?.error("[RedisSessionStore] Connection failed",{error:error.message});return false}}};import{randomUUID as randomUUID8,VercelKvStorageAdapter}from"@frontmcp/utils";var VercelKvSessionStore=class{storage;keyPrefix;defaultTtlMs;logger;security;rateLimiter;constructor(config,logger){this.keyPrefix=config.keyPrefix??"mcp:session:";this.defaultTtlMs=config.defaultTtlMs??36e5;this.logger=logger;this.security=("security"in config?config.security:void 0)??{};if(this.security.enableRateLimiting){this.rateLimiter=new SessionRateLimiter({windowMs:this.security.rateLimiting?.windowMs,maxRequests:this.security.rateLimiting?.maxRequests})}this.storage=new VercelKvStorageAdapter({url:config.url,token:config.token,keyPrefix:this.keyPrefix})}validateSessionId(sessionId){if(!sessionId||sessionId.trim()===""){throw new SessionIdEmptyError("VercelKvSessionStore")}}async connect(){await this.storage.connect()}async ensureConnected(){await this.storage.connect()}async get(sessionId,options){this.validateSessionId(sessionId);if(this.rateLimiter){const rateLimitKey=options?.clientIdentifier||sessionId;const rateLimitResult=this.rateLimiter.check(rateLimitKey);if(!rateLimitResult.allowed){this.logger?.warn("[VercelKvSessionStore] Rate limit exceeded for session lookup",{sessionId:sessionId.slice(0,20),clientIdentifier:options?.clientIdentifier?options.clientIdentifier.slice(0,20):void 0,retryAfterMs:rateLimitResult.retryAfterMs});return null}}await this.ensureConnected();const raw=await this.storage.get(sessionId);if(!raw)return null;const ttlSeconds=Math.ceil(this.defaultTtlMs/1e3);this.storage.expire(sessionId,ttlSeconds).catch(err=>{this.logger?.warn("[VercelKvSessionStore] TTL extension failed",{sessionId:sessionId.slice(0,20),error:err.message})});try{let parsed;if(this.security.enableSigning){parsed=verifyOrParseSession(raw,{secret:this.security.signingSecret});if(!parsed){this.logger?.warn("[VercelKvSessionStore] Session signature verification failed",{sessionId:sessionId.slice(0,20)});this.delete(sessionId).catch(()=>void 0);return null}}else{parsed=JSON.parse(raw)}const result=storedSessionSchema.safeParse(parsed);if(!result.success){this.logger?.warn("[VercelKvSessionStore] Invalid session format",{sessionId:sessionId.slice(0,20),errors:result.error.issues.slice(0,3).map(i=>({path:i.path,message:i.message}))});this.delete(sessionId).catch(()=>void 0);return null}const session=result.data;if(session.maxLifetimeAt&&session.maxLifetimeAt<Date.now()){this.logger?.info("[VercelKvSessionStore] Session exceeded max lifetime",{sessionId:sessionId.slice(0,20),maxLifetimeAt:session.maxLifetimeAt});await this.delete(sessionId);return null}if(session.session.expiresAt&&session.session.expiresAt<Date.now()){await this.delete(sessionId);return null}if(session.session.expiresAt){const ttlMs=Math.min(this.defaultTtlMs,session.session.expiresAt-Date.now());if(ttlMs>0&&ttlMs<this.defaultTtlMs){const boundedTtlSeconds=Math.ceil(ttlMs/1e3);this.storage.expire(sessionId,boundedTtlSeconds).catch(err=>{this.logger?.warn("[VercelKvSessionStore] TTL bound extension failed",{sessionId:sessionId.slice(0,20),error:err.message})})}}const updatedSession={...session,lastAccessedAt:Date.now()};return updatedSession}catch(error){this.logger?.warn("[VercelKvSessionStore] Failed to parse session",{sessionId:sessionId.slice(0,20),error:error.message});this.delete(sessionId).catch(()=>void 0);return null}}async set(sessionId,session,ttlMs){this.validateSessionId(sessionId);await this.ensureConnected();let value;if(this.security.enableSigning){value=signSession(session,{secret:this.security.signingSecret})}else{value=JSON.stringify(session)}if(ttlMs&&ttlMs>0){const ttlSeconds=Math.ceil(ttlMs/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else if(session.session.expiresAt){const ttl=session.session.expiresAt-Date.now();if(ttl>0){const ttlSeconds=Math.ceil(ttl/1e3);await this.storage.set(sessionId,value,{ttlSeconds})}else{await this.storage.set(sessionId,value)}}else{await this.storage.set(sessionId,value)}}async delete(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();await this.storage.delete(sessionId)}async exists(sessionId){this.validateSessionId(sessionId);await this.ensureConnected();return this.storage.exists(sessionId)}allocId(){return randomUUID8()}async disconnect(){await this.storage.disconnect()}async ping(){try{await this.ensureConnected();return this.storage.ping()}catch(error){this.logger?.error("[VercelKvSessionStore] Connection failed",{error:error.message});return false}}};import{decryptAesGcm as decryptAesGcm5,encryptAesGcm as encryptAesGcm5,hkdfSha256 as hkdfSha2564,randomBytes as randomBytes8}from"@frontmcp/utils";import{decryptAesGcm as decryptAesGcm4,encryptAesGcm as encryptAesGcm4,hkdfSha256 as hkdfSha2563,randomBytes as randomBytes7}from"@frontmcp/utils";function recordDropAt(record){if(!record.refreshToken)return record.expiresAt;return Math.max(record.expiresAt??0,record.updatedAt+REFRESH_TOKEN_TTL_MS)}function hasLiveAccessToken(record,refreshSkewMs=0){if(record.expiresAt===void 0)return true;return record.expiresAt-(record.refreshToken?refreshSkewMs:0)>Date.now()}var HKDF_SALT=new TextEncoder().encode("frontmcp-token-store");function deriveKeyForRecord(masterKey,compositeKey,cache){const cached=cache.get(compositeKey);if(cached){return cached}const info=new TextEncoder().encode(`orchestrated-token:${compositeKey}`);const derived=hkdfSha2563(masterKey,HKDF_SALT,info,32);cache.set(compositeKey,derived);return derived}function encryptRecord(key,record){const plaintext=JSON.stringify(record);const iv=randomBytes7(12);const{ciphertext,tag}=encryptAesGcm4(key,new TextEncoder().encode(plaintext),iv);return JSON.stringify({iv:Buffer.from(iv).toString("base64url"),tag:Buffer.from(tag).toString("base64url"),data:Buffer.from(ciphertext).toString("base64url")})}function decryptRecord(key,encrypted){const{iv,tag,data}=JSON.parse(encrypted);const ivBytes=Buffer.from(iv,"base64url");const tagBytes=Buffer.from(tag,"base64url");const ciphertextBytes=Buffer.from(data,"base64url");const plaintext=decryptAesGcm4(key,ciphertextBytes,ivBytes,tagBytes);return JSON.parse(new TextDecoder().decode(plaintext))}var InMemoryOrchestratedTokenStore=class{tokens=new Map;encryptionKey;derivedKeys=new Map;cleanupTimer;defaultTtlMs;refreshSkewMs;constructor(options={}){this.encryptionKey=options.encryptionKey;this.defaultTtlMs=options.defaultTtlMs;this.refreshSkewMs=options.refreshSkewMs??0;const cleanupIntervalMs=options.cleanupIntervalMs??6e4;this.cleanupTimer=setInterval(()=>{void this.cleanup()},cleanupIntervalMs);if(this.cleanupTimer.unref){this.cleanupTimer.unref()}}buildKey(authorizationId,providerId){return`${authorizationId}:${providerId}`}async deriveKeyForRecord(compositeKey){if(!this.encryptionKey){throw new EncryptionKeyNotConfiguredError}const cached=this.derivedKeys.get(compositeKey);if(cached){return cached}const info=new TextEncoder().encode(`orchestrated-token:${compositeKey}`);const salt=new TextEncoder().encode("frontmcp-token-store");const derivedKey=hkdfSha2564(this.encryptionKey,salt,info,32);this.derivedKeys.set(compositeKey,derivedKey);return derivedKey}async encryptRecord(compositeKey,record){const key=await this.deriveKeyForRecord(compositeKey);const plaintext=JSON.stringify(record);const iv=randomBytes8(12);const{ciphertext,tag}=encryptAesGcm5(key,new TextEncoder().encode(plaintext),iv);return JSON.stringify({iv:Buffer.from(iv).toString("base64url"),tag:Buffer.from(tag).toString("base64url"),data:Buffer.from(ciphertext).toString("base64url")})}async decryptRecord(compositeKey,encrypted){const key=await this.deriveKeyForRecord(compositeKey);const{iv,tag,data}=JSON.parse(encrypted);const ivBytes=Buffer.from(iv,"base64url");const tagBytes=Buffer.from(tag,"base64url");const ciphertextBytes=Buffer.from(data,"base64url");const plaintext=decryptAesGcm5(key,ciphertextBytes,ivBytes,tagBytes);return JSON.parse(new TextDecoder().decode(plaintext))}async getRecord(authorizationId,providerId){const key=this.buildKey(authorizationId,providerId);const stored=this.tokens.get(key);if(!stored){return null}let record;if(this.encryptionKey){if(typeof stored!=="string"){return null}try{record=await this.decryptRecord(key,stored)}catch{this.tokens.delete(key);return null}}else{record=stored}const dropAt=recordDropAt(record);if(dropAt&&dropAt<Date.now()){this.tokens.delete(key);return null}return record}async getAccessToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record&&hasLiveAccessToken(record,this.refreshSkewMs)?record.accessToken:null}async getRefreshToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record?.refreshToken??null}async storeTokens(authorizationId,providerId,tokens){const key=this.buildKey(authorizationId,providerId);const now=Date.now();const record={accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt:tokens.expiresAt??(this.defaultTtlMs?now+this.defaultTtlMs:void 0),createdAt:now,updatedAt:now};if(this.encryptionKey){const encrypted=await this.encryptRecord(key,record);this.tokens.set(key,encrypted)}else{this.tokens.set(key,record)}}async deleteTokens(authorizationId,providerId){const key=this.buildKey(authorizationId,providerId);this.tokens.delete(key);this.derivedKeys.delete(key)}async hasTokens(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record!==null}async deleteAllForAuthorization(authorizationId){const prefix=`${authorizationId}:`;for(const key of this.tokens.keys()){if(key.startsWith(prefix)){this.tokens.delete(key);this.derivedKeys.delete(key)}}}async getProviderIds(authorizationId){const prefix=`${authorizationId}:`;const providerIds=[];for(const key of this.tokens.keys()){if(key.startsWith(prefix)){const providerId=key.slice(prefix.length);const record=await this.getRecord(authorizationId,providerId);if(record){providerIds.push(providerId)}}}return providerIds}async cleanup(){const now=Date.now();const keysToDelete=[];for(const[key,stored]of this.tokens.entries()){let record=null;if(this.encryptionKey){if(typeof stored==="string"){try{record=await this.decryptRecord(key,stored)}catch{keysToDelete.push(key);continue}}}else{record=stored}const dropAt=record?recordDropAt(record):void 0;if(dropAt&&dropAt<now){keysToDelete.push(key)}}for(const key of keysToDelete){this.tokens.delete(key);this.derivedKeys.delete(key)}}dispose(){if(this.cleanupTimer){clearInterval(this.cleanupTimer);this.cleanupTimer=void 0}}get size(){return this.tokens.size}clear(){this.tokens.clear();this.derivedKeys.clear()}async migrateTokens(fromAuthId,toAuthId){for(const oldKey of await this.copyRecords(fromAuthId,toAuthId)){this.tokens.delete(oldKey);this.derivedKeys.delete(oldKey)}}async copyTokens(fromAuthId,toAuthId){await this.copyRecords(fromAuthId,toAuthId)}async copyRecords(fromAuthId,toAuthId){const prefix=`${fromAuthId}:`;const sourceKeys=[...this.tokens.keys()].filter(key=>key.startsWith(prefix));const copiedKeys=[];for(const oldKey of sourceKeys){const stored=this.tokens.get(oldKey);if(!stored)continue;const newKey=this.buildKey(toAuthId,oldKey.slice(prefix.length));if(this.encryptionKey){try{const record=await this.decryptRecord(oldKey,stored);this.tokens.set(newKey,await this.encryptRecord(newKey,record))}catch{continue}}else{this.tokens.set(newKey,stored)}copiedKeys.push(oldKey)}return copiedKeys}};var StorageOrchestratedTokenStore=class{storage;encryptionKey;defaultTtlMs;refreshSkewMs;namespace;storageIsNamespaced;derivedKeys=new Map;constructor(storage,options={}){this.encryptionKey=options.encryptionKey;this.defaultTtlMs=options.defaultTtlMs;this.refreshSkewMs=options.refreshSkewMs??0;this.namespace=options.namespace??"otok";this.storageIsNamespaced=this.isNamespacedStorage(storage);this.storage=this.storageIsNamespaced?storage.namespace(this.namespace):storage}async getAccessToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record&&hasLiveAccessToken(record,this.refreshSkewMs)?record.accessToken:null}async getRefreshToken(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record?.refreshToken??null}async storeTokens(authorizationId,providerId,tokens){const compositeKey=this.compositeKey(authorizationId,providerId);const now=Date.now();const record={accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt:tokens.expiresAt??(this.defaultTtlMs?now+this.defaultTtlMs:void 0),createdAt:now,updatedAt:now};const value=this.encryptionKey?encryptRecord(this.keyFor(compositeKey),record):JSON.stringify(record);await this.storage.set(this.storageKey(compositeKey),value,this.ttlOptions(recordDropAt(record)))}async deleteTokens(authorizationId,providerId){const compositeKey=this.compositeKey(authorizationId,providerId);await this.storage.delete(this.storageKey(compositeKey));this.derivedKeys.delete(compositeKey)}async hasTokens(authorizationId,providerId){const record=await this.getRecord(authorizationId,providerId);return record!==null}async getProviderIds(authorizationId){const prefix=`${authorizationId}:`;const compositeKeys=await this.listCompositeKeys(`${prefix}*`);const providerIds=[];for(const compositeKey of compositeKeys){if(!compositeKey.startsWith(prefix))continue;const providerId=compositeKey.slice(prefix.length);const record=await this.getRecord(authorizationId,providerId);if(record){providerIds.push(providerId)}}return providerIds}async migrateTokens(fromAuthId,toAuthId){for(const oldComposite of await this.copyRecords(fromAuthId,toAuthId)){await this.storage.delete(this.storageKey(oldComposite));this.derivedKeys.delete(oldComposite)}}async copyTokens(fromAuthId,toAuthId){await this.copyRecords(fromAuthId,toAuthId)}async deleteAllForAuthorization(authorizationId){const prefix=`${authorizationId}:`;const compositeKeys=await this.listCompositeKeys(`${prefix}*`);for(const compositeKey of compositeKeys){if(!compositeKey.startsWith(prefix))continue;await this.storage.delete(this.storageKey(compositeKey));this.derivedKeys.delete(compositeKey)}}async copyRecords(fromAuthId,toAuthId){const prefix=`${fromAuthId}:`;const compositeKeys=await this.listCompositeKeys(`${prefix}*`);const copiedKeys=[];for(const oldComposite of compositeKeys){if(!oldComposite.startsWith(prefix))continue;const newComposite=this.compositeKey(toAuthId,oldComposite.slice(prefix.length));const stored=await this.storage.get(this.storageKey(oldComposite));if(stored===null)continue;if(this.encryptionKey){let record;try{record=decryptRecord(this.keyFor(oldComposite),stored)}catch{continue}const value=encryptRecord(this.keyFor(newComposite),record);await this.storage.set(this.storageKey(newComposite),value,this.ttlOptions(recordDropAt(record)))}else{let dropAt;try{dropAt=recordDropAt(JSON.parse(stored))}catch{dropAt=void 0}await this.storage.set(this.storageKey(newComposite),stored,this.ttlOptions(dropAt))}copiedKeys.push(oldComposite)}return copiedKeys}async getRecord(authorizationId,providerId){const compositeKey=this.compositeKey(authorizationId,providerId);const stored=await this.storage.get(this.storageKey(compositeKey));if(stored===null){return null}let record;if(this.encryptionKey){try{record=decryptRecord(this.keyFor(compositeKey),stored)}catch{await this.storage.delete(this.storageKey(compositeKey));return null}}else{try{record=JSON.parse(stored)}catch{await this.storage.delete(this.storageKey(compositeKey));return null}}const dropAt=recordDropAt(record);if(dropAt&&dropAt<Date.now()){await this.storage.delete(this.storageKey(compositeKey));return null}return record}keyFor(compositeKey){if(!this.encryptionKey){throw new EncryptionKeyNotConfiguredError}return deriveKeyForRecord(this.encryptionKey,compositeKey,this.derivedKeys)}compositeKey(authorizationId,providerId){return`${authorizationId}:${providerId}`}storageKey(compositeKey){return this.storageIsNamespaced?compositeKey:`${this.namespace}:${compositeKey}`}async listCompositeKeys(compositeGlob){const pattern=this.storageIsNamespaced?compositeGlob:`${this.namespace}:${compositeGlob}`;const raw=await this.storage.keys(pattern);if(this.storageIsNamespaced){return raw}const stripPrefix=`${this.namespace}:`;return raw.map(k=>k.startsWith(stripPrefix)?k.slice(stripPrefix.length):k)}ttlOptions(expiresAtMs){if(!expiresAtMs){return void 0}const ttl=Math.ceil((expiresAtMs-Date.now())/1e3);return{ttlSeconds:ttl>0?ttl:1}}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{randomUUID as randomUUID9}from"@frontmcp/utils";function toSessionRecord(session){return{...session,completedProviders:Array.from(session.completedProviders.entries())}}function fromSessionRecord(record){return{...record,completedProviders:new Map(record.completedProviders)}}var InMemoryFederatedAuthSessionStore=class{sessions=new Map;sessionTtlMs=15*60*1e3;cleanupTimer;constructor(){this.cleanupTimer=setInterval(()=>{void this.cleanup()},6e4);if(this.cleanupTimer.unref){this.cleanupTimer.unref()}}async store(session){const record=toSessionRecord(session);this.sessions.set(session.id,record)}async get(id){const record=this.sessions.get(id);if(!record){return null}if(Date.now()>record.expiresAt){this.sessions.delete(id);return null}return fromSessionRecord(record)}async delete(id){this.sessions.delete(id)}async update(session){const record=toSessionRecord(session);this.sessions.set(session.id,record)}async cleanup(){const now=Date.now();for(const[id,record]of this.sessions){if(now>record.expiresAt){this.sessions.delete(id)}}}dispose(){if(this.cleanupTimer){clearInterval(this.cleanupTimer);this.cleanupTimer=void 0}}createSession(params){const now=Date.now();return{id:randomUUID9(),pendingAuthId:params.pendingAuthId,clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,state:params.state,resource:params.resource,userInfo:params.userInfo,frontmcpPkce:params.frontmcpPkce,providerQueue:[...params.providerIds],completedProviders:new Map,skippedProviders:[],signinBinding:params.signinBinding,authorizedAppIds:params.authorizedAppIds,createdAt:now,expiresAt:now+this.sessionTtlMs}}get size(){return this.sessions.size}clear(){this.sessions.clear()}};function createFederatedAuthSession(params,ttlMs=15*60*1e3){const now=Date.now();return{id:randomUUID9(),pendingAuthId:params.pendingAuthId,clientId:params.clientId,redirectUri:params.redirectUri,scopes:params.scopes,state:params.state,resource:params.resource,userInfo:params.userInfo,frontmcpPkce:params.frontmcpPkce,providerQueue:[...params.providerIds],completedProviders:new Map,skippedProviders:[],signinBinding:params.signinBinding,authorizedAppIds:params.authorizedAppIds,createdAt:now,expiresAt:now+ttlMs}}function isSessionComplete(session){return session.providerQueue.length===0&&!session.currentProviderId}function getNextProvider(session){if(session.currentProviderId){return session.currentProviderId}return session.providerQueue[0]}function completeCurrentProvider(session,tokens,userInfo){if(!session.currentProviderId){throw new AuthFlowError("No current provider to complete","AUTH_FLOW_ERROR")}session.completedProviders.set(session.currentProviderId,{providerId:session.currentProviderId,tokens,userInfo,completedAt:Date.now()});session.currentProviderId=void 0;session.currentProviderPkce=void 0;session.currentProviderState=void 0}function startNextProvider(session,pkce,state){if(session.currentProviderId){throw new AuthFlowError("Cannot start next provider while current is in progress","AUTH_FLOW_ERROR")}if(session.providerQueue.length===0){throw new AuthFlowError("No more providers in queue","AUTH_FLOW_ERROR")}const providerId=session.providerQueue.shift();if(!providerId){throw new AuthFlowError("No more providers in queue","AUTH_FLOW_ERROR")}session.currentProviderId=providerId;session.currentProviderPkce=pkce;session.currentProviderState=state;return providerId}import{TypedStorage as TypedStorage5}from"@frontmcp/utils";var StorageFederatedAuthSessionStore=class{storage;namespace;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"fed";this.storageIsNamespaced=this.isNamespacedStorage(storage);const namespacedStorage=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.storage=new TypedStorage5(namespacedStorage)}async store(session){await this.put(session)}async update(session){await this.put(session)}async get(id){const record=await this.storage.get(this.key(id));if(!record){return null}if(Date.now()>record.expiresAt){await this.storage.delete(this.key(id));return null}return fromSessionRecord(record)}async delete(id){await this.storage.delete(this.key(id))}async put(session){const record=toSessionRecord(session);const ttlSeconds=this.calculateTtl(record.expiresAt);await this.storage.set(this.key(session.id),record,ttlSeconds?{ttlSeconds}:void 0)}calculateTtl(expiresAtMs){const ttl=Math.ceil((expiresAtMs-Date.now())/1e3);return ttl>0?ttl:1}key(id){return this.storageIsNamespaced?id:`${this.namespace}:${id}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};function consentRecordKey(userSub,clientId){return`consent:${userSub}:${clientId}`}var InMemoryConsentStore=class{records=new Map;async get(userSub,clientId){const record=this.records.get(consentRecordKey(userSub,clientId));if(!record)return null;return{...record,selectedToolIds:[...record.selectedToolIds],seenToolIds:[...record.seenToolIds]}}async set(record){this.records.set(consentRecordKey(record.userSub,record.clientId),{...record,selectedToolIds:[...record.selectedToolIds],seenToolIds:[...record.seenToolIds]})}async delete(userSub,clientId){this.records.delete(consentRecordKey(userSub,clientId))}get size(){return this.records.size}clear(){this.records.clear()}};import{TypedStorage as TypedStorage6}from"@frontmcp/utils";var StorageConsentStore=class{storage;namespace;storageIsNamespaced;constructor(storage,options={}){this.namespace=options.namespace??"remembered-consent";this.storageIsNamespaced=this.isNamespacedStorage(storage);const namespacedStorage=this.storageIsNamespaced?storage.namespace(this.namespace):storage;this.storage=new TypedStorage6(namespacedStorage)}async get(userSub,clientId){return this.storage.get(this.key(userSub,clientId))}async set(record){await this.storage.set(this.key(record.userSub,record.clientId),record)}async delete(userSub,clientId){await this.storage.delete(this.key(userSub,clientId))}key(userSub,clientId){const base=consentRecordKey(userSub,clientId);return this.storageIsNamespaced?base:`${this.namespace}:${base}`}isNamespacedStorage(storage){return"namespace"in storage&&typeof storage.namespace==="function"}};import{internalZ as z8}from"@frontmcp/lazy-zod";import{AsyncLocalStorage,randomUUID as randomUUID10}from"@frontmcp/utils";var redisVaultEntrySchema=z8.object({id:z8.string(),userSub:z8.string(),userEmail:z8.string().optional(),userName:z8.string().optional(),clientId:z8.string(),createdAt:z8.number(),lastAccessAt:z8.number(),authorizedAppIds:z8.array(z8.string()),skippedAppIds:z8.array(z8.string()),pendingAuthIds:z8.array(z8.string()),encrypted:encryptedDataSchema});var encryptionContextStorage=new AsyncLocalStorage;var EncryptedRedisVault=class{constructor(redis,encryption,namespace="vault:"){this.redis=redis;this.encryption=encryption;this.namespace=namespace}redis;encryption;namespace;runWithContext(context,fn){return encryptionContextStorage.run(context,fn)}getKey(){const asyncContext=encryptionContextStorage.getStore();if(asyncContext){return asyncContext.key}throw new EncryptionContextNotSetError}redisKey(id){return`${this.namespace}${id}`}credentialKey(appId,providerId){return`${appId}:${providerId}`}async encryptSensitive(data){return this.encryption.encryptObject(data,this.getKey())}async decryptSensitive(encrypted){return this.encryption.decryptObject(encrypted,this.getKey())}async toVaultEntry(redisEntry){const sensitive=await this.decryptSensitive(redisEntry.encrypted);return{id:redisEntry.id,userSub:redisEntry.userSub,userEmail:redisEntry.userEmail,userName:redisEntry.userName,clientId:redisEntry.clientId,createdAt:redisEntry.createdAt,lastAccessAt:redisEntry.lastAccessAt,appCredentials:sensitive.appCredentials,consent:sensitive.consent,federated:sensitive.federated,pendingAuths:sensitive.pendingAuths,authorizedAppIds:redisEntry.authorizedAppIds,skippedAppIds:redisEntry.skippedAppIds}}async toRedisEntry(entry){const sensitive={appCredentials:entry.appCredentials,consent:entry.consent,federated:entry.federated,pendingAuths:entry.pendingAuths};return{id:entry.id,userSub:entry.userSub,userEmail:entry.userEmail,userName:entry.userName,clientId:entry.clientId,createdAt:entry.createdAt,lastAccessAt:entry.lastAccessAt,authorizedAppIds:entry.authorizedAppIds,skippedAppIds:entry.skippedAppIds,pendingAuthIds:entry.pendingAuths.map(p=>p.id),encrypted:await this.encryptSensitive(sensitive)}}async saveEntry(entry){const redisEntry=await this.toRedisEntry(entry);await this.redis.set(this.redisKey(entry.id),JSON.stringify(redisEntry))}async loadEntry(id){const data=await this.redis.get(this.redisKey(id));if(!data)return null;try{const redisEntry=redisVaultEntrySchema.parse(JSON.parse(data));return await this.toVaultEntry(redisEntry)}catch(error){throw new VaultLoadError(id,error instanceof Error?error:void 0)}}async create(params){const now=Date.now();const entry={id:randomUUID10(),userSub:params.userSub,userEmail:params.userEmail,userName:params.userName,clientId:params.clientId,createdAt:now,lastAccessAt:now,appCredentials:{},consent:params.consent,federated:params.federated,pendingAuths:[],authorizedAppIds:params.authorizedAppIds??[],skippedAppIds:params.skippedAppIds??[]};await this.saveEntry(entry);return entry}async get(id){const entry=await this.loadEntry(id);if(!entry)return null;entry.lastAccessAt=Date.now();await this.saveEntry(entry);return entry}async update(id,updates){const entry=await this.loadEntry(id);if(!entry){throw new VaultNotFoundError("Vault entry",id)}Object.assign(entry,updates,{lastAccessAt:Date.now()});await this.saveEntry(entry)}async delete(id){await this.redis.del(this.redisKey(id))}async updateConsent(vaultId,consent){const entry=await this.loadEntry(vaultId);if(!entry)return;entry.consent=consent;entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async authorizeApp(vaultId,appId){const entry=await this.loadEntry(vaultId);if(!entry)return;entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==appId);if(!entry.authorizedAppIds.includes(appId)){entry.authorizedAppIds.push(appId)}entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async createPendingAuth(vaultId,params){const entry=await this.loadEntry(vaultId);if(!entry){throw new VaultNotFoundError("Vault",vaultId)}const now=Date.now();const pendingAuth={id:randomUUID10(),appId:params.appId,toolId:params.toolId,authUrl:params.authUrl,requiredScopes:params.requiredScopes,elicitId:params.elicitId,createdAt:now,expiresAt:now+(params.ttlMs??10*60*1e3),status:"pending"};entry.pendingAuths.push(pendingAuth);entry.lastAccessAt=now;await this.saveEntry(entry);return pendingAuth}async getPendingAuth(vaultId,pendingAuthId){const entry=await this.loadEntry(vaultId);if(!entry)return null;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(!pendingAuth)return null;if(Date.now()>pendingAuth.expiresAt&&pendingAuth.status==="pending"){pendingAuth.status="expired";await this.saveEntry(entry)}return pendingAuth}async completePendingAuth(vaultId,pendingAuthId){const entry=await this.loadEntry(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="completed";entry.skippedAppIds=entry.skippedAppIds.filter(id=>id!==pendingAuth.appId);if(!entry.authorizedAppIds.includes(pendingAuth.appId)){entry.authorizedAppIds.push(pendingAuth.appId)}entry.lastAccessAt=Date.now();await this.saveEntry(entry)}}async cancelPendingAuth(vaultId,pendingAuthId){const entry=await this.loadEntry(vaultId);if(!entry)return;const pendingAuth=entry.pendingAuths.find(p=>p.id===pendingAuthId);if(pendingAuth){pendingAuth.status="cancelled";await this.saveEntry(entry)}}async isAppAuthorized(vaultId,appId){const data=await this.redis.get(this.redisKey(vaultId));if(!data)return false;try{const parsed=JSON.parse(data);return Array.isArray(parsed.authorizedAppIds)&&parsed.authorizedAppIds.includes(appId)}catch{return false}}async getPendingAuths(vaultId){const entry=await this.loadEntry(vaultId);if(!entry)return[];const now=Date.now();let updated=false;const pending=entry.pendingAuths.filter(p=>{if(now>p.expiresAt&&p.status==="pending"){p.status="expired";updated=true}return p.status==="pending"});if(updated){await this.saveEntry(entry)}return pending}async addAppCredential(vaultId,credential){const entry=await this.loadEntry(vaultId);if(!entry)return;const shouldStore=await this.shouldStoreCredential(vaultId,credential.appId);if(!shouldStore)return;const key=this.credentialKey(credential.appId,credential.providerId);entry.appCredentials[key]=credential;entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async removeAppCredential(vaultId,appId,providerId){const entry=await this.loadEntry(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);delete entry.appCredentials[key];entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async getAppCredentials(vaultId,appId){const entry=await this.loadEntry(vaultId);if(!entry)return[];const prefix=`${appId}:`;return Object.entries(entry.appCredentials).filter(([key])=>key.startsWith(prefix)).map(([,cred])=>cred)}async getCredential(vaultId,appId,providerId){const entry=await this.loadEntry(vaultId);if(!entry)return null;const key=this.credentialKey(appId,providerId);return entry.appCredentials[key]??null}async getAllCredentials(vaultId,filterByConsent=false){const entry=await this.loadEntry(vaultId);if(!entry)return[];const allCredentials=Object.values(entry.appCredentials);if(!filterByConsent||!entry.consent?.enabled){return allCredentials}const consentedToolIds=new Set(entry.consent.selectedToolIds);return allCredentials.filter(cred=>{return Array.from(consentedToolIds).some(toolId=>toolId.startsWith(`${cred.appId}:`))})}async updateCredential(vaultId,appId,providerId,updates){const entry=await this.loadEntry(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential)return;Object.assign(credential,updates);entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async shouldStoreCredential(vaultId,appId,toolIds){const entry=await this.loadEntry(vaultId);if(!entry)return false;if(!entry.consent?.enabled){return true}if(toolIds&&toolIds.length>0){return toolIds.some(toolId=>entry.consent.selectedToolIds.includes(toolId))}const consentedToolIds=entry.consent.selectedToolIds;return consentedToolIds.some(toolId=>toolId.startsWith(`${appId}:`))}async invalidateCredential(vaultId,appId,providerId,reason){await this.updateCredential(vaultId,appId,providerId,{isValid:false,invalidReason:reason})}async refreshOAuthCredential(vaultId,appId,providerId,tokens){const entry=await this.loadEntry(vaultId);if(!entry)return;const key=this.credentialKey(appId,providerId);const credential=entry.appCredentials[key];if(!credential||credential.credential.type!=="oauth")return;credential.credential.accessToken=tokens.accessToken;if(tokens.refreshToken!==void 0){credential.credential.refreshToken=tokens.refreshToken}if(tokens.expiresAt!==void 0){credential.credential.expiresAt=tokens.expiresAt;credential.expiresAt=tokens.expiresAt}credential.isValid=true;credential.invalidReason=void 0;entry.lastAccessAt=Date.now();await this.saveEntry(entry)}async cleanup(){}};function createEncryptedVault(redis,config={}){const encryption=new VaultEncryption({pepper:config.pepper});const vault=new EncryptedRedisVault(redis,encryption,config.namespace);return{vault,encryption}}import{base64urlDecode as base64urlDecode5}from"@frontmcp/utils";function toEpochSeconds(exp){if(exp==null)return void 0;if(exp instanceof Date)return Math.floor(exp.getTime()/1e3);return exp>1e12?Math.floor(exp/1e3):Math.floor(exp)}function isSoonExpiring(exp,skewSec=60){const expSec=toEpochSeconds(exp);if(expSec==null)return false;const now=Math.floor(Date.now()/1e3);return expSec<=now+Math.max(0,skewSec)}function isSoonExpiringProvider(sessionLike,providerId,skewSec=60){const snap=sessionLike.authorizedProviders[providerId];if(!snap)return false;return isSoonExpiring(snap.exp,skewSec)}function tryJwtExp(token){if(!token)return void 0;const parts=token.split(".");if(parts.length<2)return void 0;try{const json=JSON.parse(new TextDecoder().decode(base64urlDecode5(parts[1])));const e=json?.exp;return typeof e==="number"?toEpochSeconds(e):void 0}catch{return void 0}}import{internalZ as z9}from"@frontmcp/lazy-zod";var authModeSchema=z9.enum(["public","transparent","orchestrated"]);var authUserSchema=z9.object({sub:z9.string(),name:z9.string().optional(),email:z9.string().email().optional(),picture:z9.string().url().optional(),anonymous:z9.boolean().optional()});var authorizedToolSchema=z9.object({executionPath:z9.tuple([z9.string(),z9.string()]),scopes:z9.array(z9.string()).optional(),details:z9.record(z9.string(),z9.unknown()).optional()});var authorizedPromptSchema=z9.object({executionPath:z9.tuple([z9.string(),z9.string()]),scopes:z9.array(z9.string()).optional(),details:z9.record(z9.string(),z9.unknown()).optional()});var llmSafeAuthContextSchema=z9.object({authorizationId:z9.string(),sessionId:z9.string(),mode:authModeSchema,isAnonymous:z9.boolean(),user:z9.object({sub:z9.string(),name:z9.string().optional()}),scopes:z9.array(z9.string()),authorizedToolIds:z9.array(z9.string()),authorizedPromptIds:z9.array(z9.string())});var AppAuthState=(AppAuthState2=>{AppAuthState2["AUTHORIZED"]="authorized";AppAuthState2["SKIPPED"]="skipped";AppAuthState2["PENDING"]="pending";return AppAuthState2})(AppAuthState||{});var appAuthStateSchema=z9.nativeEnum(AppAuthState);var appAuthorizationRecordSchema=z9.object({appId:z9.string(),state:appAuthStateSchema,stateChangedAt:z9.number(),grantedScopes:z9.array(z9.string()).optional(),authProviderId:z9.string().optional(),toolIds:z9.array(z9.string())});var progressiveAuthStateSchema=z9.object({apps:z9.record(z9.string(),appAuthorizationRecordSchema),initiallyAuthorized:z9.array(z9.string()),initiallySkipped:z9.array(z9.string())});import{base64urlDecode as base64urlDecode6,getMachineId as getMachineId2,randomUUID as randomUUID11}from"@frontmcp/utils";var AuthorizationBase=class _AuthorizationBase{id;isAnonymous;user;claims;expiresAt;scopes;authorizedProviders;authorizedProviderIds;authorizedApps;authorizedAppIds;authorizedTools;authorizedToolIds;authorizedPrompts;authorizedPromptIds;authorizedResources;token;#sessions=new Map;createdAt;constructor(ctx){this.id=ctx.id;this.isAnonymous=ctx.isAnonymous;this.user=ctx.user;this.claims=ctx.claims;this.expiresAt=ctx.expiresAt;this.scopes=ctx.scopes??[];this.token=ctx.token;this.createdAt=Date.now();this.authorizedProviders=ctx.authorizedProviders??{};this.authorizedProviderIds=ctx.authorizedProviderIds??Object.keys(this.authorizedProviders);this.authorizedApps=ctx.authorizedApps??{};this.authorizedAppIds=ctx.authorizedAppIds??Object.keys(this.authorizedApps);this.authorizedTools=ctx.authorizedTools??{};this.authorizedToolIds=ctx.authorizedToolIds??Object.keys(this.authorizedTools);this.authorizedPrompts=ctx.authorizedPrompts??{};this.authorizedPromptIds=ctx.authorizedPromptIds??Object.keys(this.authorizedPrompts);this.authorizedResources=ctx.authorizedResources??[]}createTransportSession(protocol,fingerprint){const sessionId=randomUUID11();const session={id:sessionId,authorizationId:this.id,protocol,createdAt:Date.now(),expiresAt:this.expiresAt,nodeId:getMachineId2(),clientFingerprint:fingerprint};this.#sessions.set(session.id,session);return session}getTransportSession(sessionId){return this.#sessions.get(sessionId)}getAllSessions(){return Array.from(this.#sessions.values())}removeTransportSession(sessionId){return this.#sessions.delete(sessionId)}get sessionCount(){return this.#sessions.size}hasScope(scope){return this.scopes.includes(scope)}hasAllScopes(scopes){return scopes.every(s=>this.scopes.includes(s))}hasAnyScope(scopes){return scopes.some(s=>this.scopes.includes(s))}canAccessTool(toolId){return toolId in this.authorizedTools||this.authorizedToolIds.includes(toolId)}canAccessPrompt(promptId){return promptId in this.authorizedPrompts||this.authorizedPromptIds.includes(promptId)}isAppAuthorized(appId){return appId in this.authorizedApps||this.authorizedAppIds.includes(appId)}getProgressiveAuthUrl(appId,baseUrl,ticket){const params=new URLSearchParams({app:appId});if(ticket)params.set("ticket",ticket);return`${baseUrl}/oauth/authorize?${params.toString()}`}isExpired(){if(!this.expiresAt)return false;return Date.now()>this.expiresAt}getTimeToExpiry(){if(!this.expiresAt)return void 0;return this.expiresAt-Date.now()}toSessionJwt(session){const payload={sid:session.id,aid:this.id,proto:session.protocol,nid:session.nodeId,iat:Math.floor(Date.now()/1e3),exp:this.expiresAt?Math.floor(this.expiresAt/1e3):void 0};return encryptJson(payload)}toLLMSafeContext(session){return{authorizationId:this.id,sessionId:session.id,mode:this.mode,isAnonymous:this.isAnonymous,user:{sub:this.user.sub,name:this.user.name},scopes:this.scopes,authorizedToolIds:this.authorizedToolIds,authorizedPromptIds:this.authorizedPromptIds}}static isJwtHeader(segment){try{const bytes=base64urlDecode6(segment);const text=new TextDecoder().decode(bytes);const parsed=JSON.parse(text);return typeof parsed==="object"&&parsed!==null&&typeof parsed.alg==="string"}catch{return false}}static validateNoTokenLeakage(data){const json=JSON.stringify(data);const B64URL=/^[A-Za-z0-9_-]+$/;const MIN_SEG=2;let dotIdx=json.indexOf(".");while(dotIdx!==-1){let seg1Start=dotIdx;while(seg1Start>0&&/[A-Za-z0-9_-]/.test(json[seg1Start-1]))seg1Start--;const seg1Len=dotIdx-seg1Start;if(seg1Len>=MIN_SEG){let seg2End=dotIdx+1;while(seg2End<json.length&&/[A-Za-z0-9_-]/.test(json[seg2End]))seg2End++;const seg2Len=seg2End-(dotIdx+1);if(seg2Len>=MIN_SEG&&seg2End<json.length&&json[seg2End]==="."){const dot2=seg2End;let seg3End=dot2+1;while(seg3End<json.length&&/[A-Za-z0-9_-]/.test(json[seg3End]))seg3End++;const seg3Len=seg3End-(dot2+1);if(seg3Len>=MIN_SEG){const seg1=json.substring(seg1Start,dotIdx);const seg2=json.substring(dotIdx+1,dot2);const seg3=json.substring(dot2+1,seg3End);if(B64URL.test(seg1)&&B64URL.test(seg2)&&B64URL.test(seg3)&&_AuthorizationBase.isJwtHeader(seg1)){throw new TokenLeakDetectedError("JWT pattern detected in LLM context data")}}}}dotIdx=json.indexOf(".",dotIdx+1)}const sensitiveFields=["access_token","refresh_token","id_token","tokenEnc","secretRefId"];for(const field of sensitiveFields){if(json.includes(`"${field}"`)){throw new TokenLeakDetectedError(`Sensitive field "${field}" found in data`)}}}};import{randomUUID as randomUUID12}from"@frontmcp/utils";var PublicAuthorization=class _PublicAuthorization extends AuthorizationBase{mode="public";issuer;constructor(ctx){super(ctx);this.issuer=ctx.issuer}static create(ctx={}){const{prefix="anon",scopes=["anonymous"],ttlMs=36e5,issuer,allowedTools="all",allowedPrompts="all"}=ctx;const uuid=randomUUID12();const sub=`${prefix}:${uuid}`;const user={sub,name:"Anonymous",anonymous:true};const expiresAt=ttlMs?Date.now()+ttlMs:void 0;const authorizedTools={};const authorizedToolIds=[];if(allowedTools!=="all"&&Array.isArray(allowedTools)){for(const toolId of allowedTools){authorizedTools[toolId]={executionPath:["public",toolId]};authorizedToolIds.push(toolId)}}const authorizedPrompts={};const authorizedPromptIds=[];if(allowedPrompts!=="all"&&Array.isArray(allowedPrompts)){for(const promptId of allowedPrompts){authorizedPrompts[promptId]={executionPath:["public",promptId]};authorizedPromptIds.push(promptId)}}return new _PublicAuthorization({id:sub,isAnonymous:true,user,scopes,expiresAt,issuer,authorizedTools:allowedTools==="all"?void 0:authorizedTools,authorizedToolIds:allowedTools==="all"?void 0:authorizedToolIds,authorizedPrompts:allowedPrompts==="all"?void 0:authorizedPrompts,authorizedPromptIds:allowedPrompts==="all"?void 0:authorizedPromptIds})}async getToken(_providerId){throw new TokenNotAvailableError("PublicAuthorization: Anonymous users cannot access provider tokens. Use transparent or orchestrated mode for token access.")}get allowsAllTools(){return this.authorizedToolIds.length===0&&Object.keys(this.authorizedTools).length===0}get allowsAllPrompts(){return this.authorizedPromptIds.length===0&&Object.keys(this.authorizedPrompts).length===0}canAccessTool(toolId){if(this.allowsAllTools){return true}return super.canAccessTool(toolId)}canAccessPrompt(promptId){if(this.allowsAllPrompts){return true}return super.canAccessPrompt(promptId)}};import{sha256Hex as sha256Hex5}from"@frontmcp/utils";var TransparentAuthorization=class _TransparentAuthorization extends AuthorizationBase{mode="transparent";providerId;providerName;constructor(ctx){super(ctx);this.providerId=ctx.providerId;this.providerName=ctx.providerName}static fromVerifiedToken(ctx){const{token,payload,providerId,providerName,...projections}=ctx;const user={sub:payload.sub,name:payload.name,email:payload.email,picture:payload.picture,anonymous:false};const scopes=_TransparentAuthorization.parseScopes(payload.scope);const expiresAt=payload.exp?payload.exp*1e3:void 0;const id=_TransparentAuthorization.generateAuthorizationId(token);const providerSnapshot={id:providerId,exp:expiresAt,payload,embedMode:"plain",token};return new _TransparentAuthorization({id,isAnonymous:false,user,claims:payload,expiresAt,scopes,token,providerId,providerName,authorizedProviders:{[providerId]:providerSnapshot},authorizedProviderIds:[providerId],...projections})}async getToken(_providerId){if(!this.token){throw new TokenNotAvailableError("TransparentAuthorization: Token not available")}return this.token}static parseScopes(scope){if(!scope)return[];if(Array.isArray(scope))return scope;return scope.split(/\s+/).filter(Boolean)}static generateAuthorizationId(token){const parts=token.split(".");const signature=parts[2]||token;return sha256Hex5(signature).substring(0,16)}get issuer(){return this.claims?.["iss"]}get audience(){return this.claims?.["aud"]}hasAudience(aud){const tokenAud=this.audience;if(!tokenAud)return false;if(Array.isArray(tokenAud))return tokenAud.includes(aud);return tokenAud===aud}};import{sha256Hex as sha256Hex6}from"@frontmcp/utils";function deriveAuthorizationId(token){const parts=token.split(".");const signature=parts[2]||token;return sha256Hex6(signature).substring(0,16)}var OrchestratedAuthorization=class _OrchestratedAuthorization extends AuthorizationBase{mode="orchestrated";primaryProviderId;#tokenStore;#onTokenRefresh;#providerStates;constructor(ctx){super(ctx);this.primaryProviderId=ctx.primaryProviderId;this.#tokenStore=ctx.tokenStore;this.#onTokenRefresh=ctx.onTokenRefresh;this.#providerStates=ctx.providerStates??new Map}static create(ctx){const{token,user,scopes=[],claims,expiresAt,primaryProviderId,tokenStore,onTokenRefresh,providers={},authorizedProviderIds,...projections}=ctx;const id=deriveAuthorizationId(token);const providerStates=new Map;const authorizedProviders={};const providerIdsFromState=[];for(const[providerId,state]of Object.entries(providers)){providerStates.set(providerId,state);providerIdsFromState.push(providerId);authorizedProviders[providerId]={id:providerId,exp:state.expiresAt,embedMode:state.secretRefId?"ref":"store-only",secretRefId:state.secretRefId,refreshRefId:state.refreshRefId}}const finalAuthorizedProviderIds=authorizedProviderIds??(providerIdsFromState.length>0?providerIdsFromState:void 0);return new _OrchestratedAuthorization({id,isAnonymous:false,user,claims,expiresAt,scopes,token,primaryProviderId,tokenStore,onTokenRefresh,providerStates,authorizedProviders,...projections,authorizedProviderIds:finalAuthorizedProviderIds})}async getToken(providerId){const targetProviderId=providerId??this.primaryProviderId;if(!targetProviderId){throw new NoProviderIdError("OrchestratedAuthorization: No provider ID specified and no primary provider set")}if(!this.#tokenStore){throw new TokenStoreRequiredError("orchestrated token retrieval")}const hasToken=await this.#tokenStore.hasTokens(this.id,targetProviderId);if(!hasToken){throw new TokenNotAvailableError(`OrchestratedAuthorization: No tokens available for provider "${targetProviderId}"`)}const accessToken=await this.#tokenStore.getAccessToken(this.id,targetProviderId);if(accessToken){const providerState=this.#providerStates.get(targetProviderId);if(providerState?.expiresAt&&providerState.expiresAt<Date.now()){return this.refreshAndGetToken(targetProviderId)}return accessToken}return this.refreshAndGetToken(targetProviderId)}async refreshAndGetToken(providerId){if(!this.#tokenStore||!this.#onTokenRefresh){throw new TokenNotAvailableError(`OrchestratedAuthorization: Token expired for provider "${providerId}" and refresh not available`)}const refreshToken=await this.#tokenStore.getRefreshToken(this.id,providerId);if(!refreshToken){throw new TokenNotAvailableError(`OrchestratedAuthorization: No refresh token available for provider "${providerId}"`)}const result=await this.#onTokenRefresh(providerId,refreshToken);await this.#tokenStore.storeTokens(this.id,providerId,{accessToken:result.accessToken,refreshToken:result.refreshToken??refreshToken,expiresAt:result.expiresIn?Date.now()+result.expiresIn*1e3:void 0});const currentState=this.#providerStates.get(providerId);if(currentState){currentState.expiresAt=result.expiresIn?Date.now()+result.expiresIn*1e3:void 0}return result.accessToken}hasProvider(providerId){return this.#providerStates.has(providerId)}getProviderIds(){return Array.from(this.#providerStates.keys())}async addProvider(providerId,tokens){if(!this.#tokenStore){throw new TokenStoreRequiredError("adding providers")}const expiresAt=tokens.expiresIn?Date.now()+tokens.expiresIn*1e3:void 0;await this.#tokenStore.storeTokens(this.id,providerId,{accessToken:tokens.accessToken,refreshToken:tokens.refreshToken,expiresAt});this.#providerStates.set(providerId,{id:providerId,expiresAt,secretRefId:`${this.id}:${providerId}`})}#mutableAuthorizedApps=new Map(Object.entries(this.authorizedApps??{}));async addAppAuthorization(appId,toolIds,tokens){if(!this.#tokenStore){throw new TokenStoreRequiredError("progressive authorization")}const providerId=`app:${appId}`;await this.addProvider(providerId,tokens);this.#mutableAuthorizedApps.set(appId,{id:appId,toolIds})}async getAppToken(appId){if(!this.#mutableAuthorizedApps.has(appId)){return null}const providerId=`app:${appId}`;try{return await this.getToken(providerId)}catch{return null}}isAppAuthorized(appId){return this.#mutableAuthorizedApps.has(appId)||super.isAppAuthorized(appId)}getAllAuthorizedAppIds(){const baseIds=new Set(this.authorizedAppIds??[]);for(const appId of this.#mutableAuthorizedApps.keys()){baseIds.add(appId)}return Array.from(baseIds)}getAppToolIds(appId){return this.#mutableAuthorizedApps.get(appId)?.toolIds??this.authorizedApps?.[appId]?.toolIds}async removeProvider(providerId){if(this.#tokenStore){await this.#tokenStore.deleteTokens(this.id,providerId)}this.#providerStates.delete(providerId)}get issuer(){return this.claims?.["iss"]}};var ORCHESTRATED_AUTH_ACCESSOR=Symbol.for("frontmcp:ORCHESTRATED_AUTH_ACCESSOR");var NullOrchestratedAuthAccessor=class{primaryProviderId=void 0;issuer=void 0;authorizationId="null";isAuthenticated=false;async getToken(providerId){throw new OrchestratedAuthNotAvailableError}async tryGetToken(providerId){return null}async getAppToken(appId){return null}hasProvider(providerId){return false}getProviderIds(){return[]}isAppAuthorized(appId){return false}getAllAuthorizedAppIds(){return[]}getAppToolIds(appId){return void 0}};var OrchestratedAuthAccessorAdapter=class{constructor(authorization){this.authorization=authorization}authorization;get primaryProviderId(){return this.authorization.primaryProviderId}get issuer(){return this.authorization.issuer}get authorizationId(){return this.authorization.id}get isAuthenticated(){return!this.authorization.isAnonymous}async getToken(providerId){return this.authorization.getToken(providerId)}async tryGetToken(providerId){try{return await this.authorization.getToken(providerId)}catch{return null}}async getAppToken(appId){return this.authorization.getAppToken(appId)}hasProvider(providerId){return this.authorization.hasProvider(providerId)}getProviderIds(){return this.authorization.getProviderIds()}isAppAuthorized(appId){return this.authorization.isAppAuthorized(appId)}getAllAuthorizedAppIds(){return this.authorization.getAllAuthorizedAppIds()}getAppToolIds(appId){return this.authorization.getAppToolIds(appId)}};import{sha256 as sha2562,timingSafeEqual as timingSafeEqual2}from"@frontmcp/utils";var DEFAULT_MAX_DYNAMIC_CLIENTS=1e3;function globMatch(pattern,input){let p=0;let i=0;let starIdx=-1;let matchIdx=0;while(i<input.length){if(p<pattern.length&&pattern[p]===input[i]){p++;i++}else if(p<pattern.length&&pattern[p]==="*"){starIdx=p;matchIdx=i;p++}else if(starIdx!==-1){p=starIdx+1;matchIdx++;i=matchIdx}else{return false}}while(p<pattern.length&&pattern[p]==="*")p++;return p===pattern.length}function splitRedirectUri(s){const schemeSep=s.indexOf("://");if(schemeSep===-1)return null;const scheme=s.slice(0,schemeSep);const afterScheme=s.slice(schemeSep+3);let boundary=afterScheme.length;for(let i=0;i<afterScheme.length;i++){const ch=afterScheme[i];if(ch==="/"||ch==="?"||ch==="#"){boundary=i;break}}const authority=afterScheme.slice(0,boundary);const rest=afterScheme.slice(boundary);const at=authority.lastIndexOf("@");const userinfo=at===-1?"":authority.slice(0,at);const hostport=at===-1?authority:authority.slice(at+1);let host=hostport;let port="";if(hostport.startsWith("[")){const close=hostport.indexOf("]");if(close!==-1){host=hostport.slice(0,close+1);if(hostport[close+1]===":")port=hostport.slice(close+2)}}else{const colon=hostport.indexOf(":");if(colon!==-1){host=hostport.slice(0,colon);port=hostport.slice(colon+1)}}return{scheme,userinfo,host,port,rest}}function globMatchRedirectUri(pattern,input){const p=splitRedirectUri(pattern);if(!p)return globMatch(pattern,input);const u=splitRedirectUri(input);if(!u)return false;return globMatch(p.scheme,u.scheme)&&globMatch(p.userinfo,u.userinfo)&&globMatch(p.host,u.host)&&globMatch(p.port,u.port)&&globMatch(p.rest,u.rest)}var DcrClientRegistry=class{constructor(config={}){this.config=config;this.redirectGlobs=config.allowedRedirectUris;this.seedPreRegisteredClients()}config;clients=new Map;redirectGlobs;seedPreRegisteredClients(){const clients=this.config.clients??[];const now=Math.floor(Date.now()/1e3);for(const c of clients){this.clients.set(c.clientId,{client_id:c.clientId,client_secret:c.clientSecret,token_endpoint_auth_method:c.tokenEndpointAuthMethod??"none",grant_types:c.grantTypes??["authorization_code","refresh_token"],response_types:c.responseTypes??["code"],redirect_uris:c.redirectUris,client_name:c.clientName,scope:c.scope,created_at:now,dev:false,preRegistered:true})}}get(clientId){return this.clients.get(clientId)}verifyClientSecret(clientId,presentedSecret){const client=this.clients.get(clientId);if(!client)return"unknown";const method=client.token_endpoint_auth_method;const isConfidential=method==="client_secret_basic"||method==="client_secret_post";if(!isConfidential)return"ok";if(!client.client_secret)return"invalid";if(!presentedSecret)return"invalid";return timingSafeEqual2(sha2562(presentedSecret),sha2562(client.client_secret))?"ok":"invalid"}has(clientId){return this.clients.has(clientId)}register(client){const cap=this.dynamicClientCap();const dynamicCount=this.countDynamicClients();const isNew=!this.clients.has(client.client_id);if(isNew&&dynamicCount>=cap){return false}this.clients.set(client.client_id,client);return true}dynamicClientCap(){const configured=this.config.maxDynamicClients;return Number.isSafeInteger(configured)&&configured>=0?configured:DEFAULT_MAX_DYNAMIC_CLIENTS}countDynamicClients(){let n=0;for(const c of this.clients.values())if(c.dev&&!c.preRegistered)n++;return n}hasRedirectAllowlist(){return!!this.redirectGlobs&&this.redirectGlobs.length>0}isRedirectUriAllowed(redirectUri){if(!this.redirectGlobs||this.redirectGlobs.length===0){return true}return this.redirectGlobs.some(glob=>globMatchRedirectUri(glob,redirectUri))}hasClientIdAllowlist(){return!!this.config.allowedClientIds&&this.config.allowedClientIds.length>0}isClientIdAllowed(clientId){const allow=this.config.allowedClientIds;if(!allow||allow.length===0){return true}return allow.includes(clientId)}requiresInitialAccessToken(){return typeof this.config.initialAccessToken==="string"&&this.config.initialAccessToken.length>0}verifyInitialAccessToken(presented){const expected=this.config.initialAccessToken;if(!expected){return false}if(typeof presented!=="string"||presented.length===0){return false}return timingSafeEqual2(sha2562(presented),sha2562(expected))}};import{internalZ as z10}from"@frontmcp/lazy-zod";var aiPlatformTypeSchema=z10.enum(["openai","claude","claude-code","codex","gemini","cursor","continue","cody","generic-mcp","ext-apps","unknown"]);var userClaimSchema=z10.object({iss:z10.string(),sid:z10.string().optional(),sub:z10.string(),exp:z10.number().optional(),iat:z10.number().optional(),aud:z10.union([z10.string(),z10.array(z10.string())]).optional(),email:z10.string().optional(),username:z10.string().optional(),preferred_username:z10.string().optional(),name:z10.string().optional(),picture:z10.string().optional()}).passthrough();var sessionIdPayloadSchema=z10.object({nodeId:z10.string(),authSig:z10.string(),uuid:z10.string().uuid(),iat:z10.number(),protocol:z10.enum(["legacy-sse","sse","streamable-http","stateful-http","stateless-http"]).optional(),isPublic:z10.boolean().optional(),platformType:aiPlatformTypeSchema.optional(),clientName:z10.string().optional(),clientVersion:z10.string().optional(),supportsElicitation:z10.boolean().optional(),skillsOnlyMode:z10.boolean().optional()});var sessionIdSchema=z10.object({id:z10.string(),payload:sessionIdPayloadSchema.optional()});var authorizationSchema=z10.object({token:z10.string(),session:sessionIdSchema.optional(),user:userClaimSchema});import{internalZ as z11}from"@frontmcp/lazy-zod";var publicAuthOptionsSchema=z11.object({mode:z11.literal("public"),issuer:z11.string().optional(),sessionTtl:z11.number().int().positive().default(3600),anonymousScopes:z11.array(z11.string()).default(["anonymous"]),publicAccess:publicAccessConfigSchema.optional(),jwks:jsonWebKeySetSchema.optional(),signKey:jwkSchema.or(z11.instanceof(Uint8Array)).optional()});import{internalZ as z12}from"@frontmcp/lazy-zod";var staticAuthOptionsSchema=z12.object({mode:z12.literal("static"),tokens:z12.array(z12.string().min(1)).min(1),header:z12.string().min(1).default("authorization"),scheme:z12.string().default("Bearer"),scopes:z12.array(z12.string()).default(["static"]),realm:z12.string().default("mcp"),publicAccess:publicAccessConfigSchema.optional()});import{internalZ as z13}from"@frontmcp/lazy-zod";var transparentAuthOptionsSchema=z13.object({mode:z13.literal("transparent"),...flatRemoteProviderFields,expectedAudience:z13.union([z13.string(),z13.array(z13.string())]).optional(),requireAudience:z13.boolean().optional(),requiredScopes:z13.array(z13.string()).default([]),allowAnonymous:z13.boolean().default(false),anonymousScopes:z13.array(z13.string()).default(["anonymous"]),publicAccess:publicAccessConfigSchema.optional()});import{internalZ as z14}from"@frontmcp/lazy-zod";var authSlotSchema=z14.enum(["login","consent","incremental","federated","error"]);var authUiMapSchema=z14.partialRecord(authSlotSchema,z14.string().min(1));var authExtraHandlerSchema=z14.custom(v=>typeof v==="function",{message:"auth.extras entries must be handler functions (input, ctx) => AuthExtraResult."});var authExtrasMapSchema=z14.record(z14.string().min(1),authExtraHandlerSchema);var sharedAuthFields={local:localSigningConfigSchema.optional(),tokenStorage:tokenStorageConfigSchema.default("memory"),secureStore:secureStoreConfigSchema.optional(),allowDefaultPublic:z14.boolean().default(false),anonymousScopes:z14.array(z14.string()).default(["anonymous"]),publicAccess:publicAccessConfigSchema.optional(),consent:consentConfigSchema.optional(),federatedAuth:federatedAuthConfigSchema.optional(),refresh:tokenRefreshConfigSchema.optional(),expectedAudience:z14.union([z14.string(),z14.array(z14.string())]).optional(),incrementalAuth:incrementalAuthConfigSchema.optional(),requireRegisteredClients:z14.boolean().default(true),allowedScopes:z14.array(z14.string().trim().min(1,"allowedScopes entries must be non-empty")).optional(),cimd:cimdConfigSchema.optional(),ui:authUiMapSchema.optional(),extras:authExtrasMapSchema.optional()};var loginFieldSchema=z14.object({type:z14.enum(["text","password","email","select","hidden"]),label:z14.string().optional(),required:z14.boolean().optional(),placeholder:z14.string().optional(),options:z14.array(z14.object({value:z14.string(),label:z14.string()})).optional()});var loginConfigSchema=z14.object({title:z14.string().optional(),subtitle:z14.string().optional(),logoUri:z14.string().optional(),fields:z14.record(z14.string(),loginFieldSchema).optional(),render:z14.custom(v=>typeof v==="function",{message:"login.render must be a function"}).optional(),subject:z14.object({fromField:z14.string().optional(),strategy:z14.enum(["per-session","per-account"]).optional()}).optional()});var authenticateFnSchema=z14.custom(v=>typeof v==="function",{message:"authenticate must be a function"});var localAuthSchema=z14.object({mode:z14.literal("local"),...sharedAuthFields,requireEmail:z14.boolean().default(true),anonymousSubject:z14.string().min(1).default("local-operator"),login:loginConfigSchema.optional(),authenticate:authenticateFnSchema.optional(),providers:z14.array(upstreamProviderSchema).optional(),dcr:localDcrConfigSchema.optional()});var remoteAuthSchema=z14.object({mode:z14.literal("remote"),...flatRemoteProviderFields,...sharedAuthFields});var orchestratedLocalSchema=localAuthSchema;var orchestratedRemoteSchema=remoteAuthSchema;import{internalZ as z15}from"@frontmcp/lazy-zod";var authOptionsSchema=z15.union([publicAuthOptionsSchema,staticAuthOptionsSchema,transparentAuthOptionsSchema,localAuthSchema,remoteAuthSchema]);import{internalZ as z16}from"@frontmcp/lazy-zod";var standaloneOptionSchema={standalone:z16.boolean().optional(),excludeFromParent:z16.boolean().optional()};var appAuthOptionsSchema=z16.union([publicAuthOptionsSchema.extend(standaloneOptionSchema),transparentAuthOptionsSchema.extend(standaloneOptionSchema),localAuthSchema.extend(standaloneOptionSchema),remoteAuthSchema.extend(standaloneOptionSchema)]);function parseAuthOptions(input){return authOptionsSchema.parse(input)}function isPublicMode(options){return options.mode==="public"}function isStaticMode(options){return options.mode==="static"}function isTransparentMode(options){return options.mode==="transparent"}function isLocalMode(options){return options.mode==="local"}function isRemoteMode(options){return options.mode==="remote"}function isOrchestratedMode(options){return options.mode==="local"||options.mode==="remote"}function isOrchestratedLocal(options){return options.mode==="local"}function isOrchestratedRemote(options){return options.mode==="remote"}function allowsPublicAccess(options){if(options.mode==="public")return true;if(options.mode==="transparent")return options.allowAnonymous;if(options.mode==="local"||options.mode==="remote")return options.allowDefaultPublic;return false}import{internalZ as z17}from"@frontmcp/lazy-zod";var consentToolItemSchema=z17.object({id:z17.string().min(1),name:z17.string().min(1),description:z17.string().optional(),appId:z17.string().min(1),appName:z17.string().min(1),defaultSelected:z17.boolean().default(true),requiredScopes:z17.array(z17.string()).optional(),category:z17.string().optional()});var consentSelectionSchema=z17.object({selectedTools:z17.array(z17.string()),allSelected:z17.boolean(),consentedAt:z17.string().datetime(),consentVersion:z17.string().default("1.0")});var consentStateSchema=z17.object({enabled:z17.boolean(),availableTools:z17.array(consentToolItemSchema),preselectedTools:z17.array(z17.string()).optional(),groupByApp:z17.boolean().default(true),customMessage:z17.string().optional()});var federatedProviderItemSchema=z17.object({id:z17.string().min(1),name:z17.string().min(1),description:z17.string().optional(),icon:z17.string().optional(),type:z17.enum(["local","remote","transparent"]),providerUrl:z17.string().url().optional(),appIds:z17.array(z17.string()),appNames:z17.array(z17.string()),scopes:z17.array(z17.string()),isPrimary:z17.boolean(),isOptional:z17.boolean().default(false)});var federatedLoginStateSchema=z17.object({providers:z17.array(federatedProviderItemSchema),primaryProviderId:z17.string().optional(),allowSkip:z17.boolean().default(true),preselectedProviders:z17.array(z17.string()).optional()});var federatedSelectionSchema=z17.object({selectedProviders:z17.array(z17.string()),skippedProviders:z17.array(z17.string()),providerMetadata:z17.record(z17.string(),z17.unknown()).optional()});import{internalZ as z18}from"@frontmcp/lazy-zod";var detectedAuthProviderSchema=z18.object({id:z18.string(),providerUrl:z18.string().optional(),mode:z18.enum(["public","static","transparent","local","remote"]),appIds:z18.array(z18.string()),scopes:z18.array(z18.string()),isParentProvider:z18.boolean()});var authProviderDetectionResultSchema=z18.object({providers:z18.map(z18.string(),detectedAuthProviderSchema),requiresOrchestration:z18.boolean(),parentProviderId:z18.string().optional(),childProviderIds:z18.array(z18.string()),uniqueProviderCount:z18.number(),validationErrors:z18.array(z18.string()),warnings:z18.array(z18.string())});function deriveProviderId(options){if(isPublicMode(options)){return options.issuer??"public"}if(isTransparentMode(options)){return options.providerConfig?.id??urlToProviderId(options.provider)}if(isOrchestratedMode(options)){if(isOrchestratedRemote(options)){return options.providerConfig?.id??urlToProviderId(options.provider)}return options.local?.issuer??"local"}return"unknown"}function urlToProviderId(url){try{const parsed=new URL(url);return parsed.hostname.replace(/\./g,"_")}catch{return url.replace(/[^a-zA-Z0-9]/g,"_")}}function extractScopes(options){if(isTransparentMode(options)){return options.requiredScopes||[]}if(isOrchestratedMode(options)){if(isOrchestratedRemote(options)){return options.scopes||[]}}return[]}function detectAuthProviders(parentAuth,apps){const providers=new Map;const validationErrors=[];const warnings=[];let parentProviderId;if(parentAuth){parentProviderId=deriveProviderId(parentAuth);providers.set(parentProviderId,{id:parentProviderId,providerUrl:getProviderUrl(parentAuth),mode:parentAuth.mode,appIds:["__parent__"],scopes:extractScopes(parentAuth),isParentProvider:true})}for(const app of apps){if(!app.auth){continue}const providerId=deriveProviderId(app.auth);const existing=providers.get(providerId);if(existing){existing.appIds.push(app.id);const newScopes=extractScopes(app.auth);existing.scopes=[...new Set([...existing.scopes,...newScopes])]}else{providers.set(providerId,{id:providerId,providerUrl:getProviderUrl(app.auth),mode:app.auth.mode,appIds:[app.id],scopes:extractScopes(app.auth),isParentProvider:false})}}const childProviderIds=[...providers.keys()].filter(id=>id!==parentProviderId);const uniqueProviderCount=providers.size;const hasMultipleProviders=uniqueProviderCount>1;const hasChildOnlyProviders=childProviderIds.length>0&&!parentProviderId;const requiresOrchestration=hasMultipleProviders||hasChildOnlyProviders||childProviderIds.length>0&&parentProviderId!==void 0;if(requiresOrchestration&&parentAuth&&isTransparentMode(parentAuth)){validationErrors.push(`Invalid auth configuration: Parent uses transparent mode but apps have their own auth providers. Transparent mode passes tokens through without modification, which is incompatible with multi-provider setups. Change parent auth to local or remote mode to properly manage tokens for each provider. Detected providers: ${[...providers.keys()].join(", ")}`)}const sharedEndpointUnenforced=!parentAuth||isPublicMode(parentAuth)||isStaticMode(parentAuth);const unenforcedAppIds=apps.filter(app=>app.auth&&!isPublicMode(app.auth)&&!staticParentEnforces(parentAuth,app.auth)).map(app=>app.id);if(sharedEndpointUnenforced&&unenforcedAppIds.length>0){validationErrors.push(`App-level auth is not enforced on the shared endpoint of a server in ${parentAuth?.mode??"public"} mode, so the tools of ${unenforcedAppIds.join(", ")} would be served without it. Serve the app on its own endpoint with standalone: true or splitByApp: true, or run the server in local or remote mode with incrementalAuth enabled, which checks each tool call against the apps the caller has authorized (without incrementalAuth, local and remote mode do not check app grants per tool call).`)}const tokenMintingAppIds=apps.filter(app=>app.remoteAuthMode==="forward"&&!(parentAuth&&isTransparentMode(parentAuth))).map(app=>app.id);if(tokenMintingAppIds.length>0){validationErrors.push(`remoteAuth: { mode: 'forward' } on ${tokenMintingAppIds.join(", ")} would send the remote a token this server minted or holds itself: only a server in transparent mode receives its callers' tokens from the identity provider. Use remoteAuth: { mode: 'static', credentials } (or transportOptions.headers) for the remote, or run the server in transparent mode.`)}return{providers,requiresOrchestration,parentProviderId,childProviderIds,uniqueProviderCount,validationErrors,warnings}}function staticParentEnforces(parentAuth,appAuth){if(!parentAuth||!isStaticMode(parentAuth)||!isStaticMode(appAuth))return false;const parent=staticAuthOptionsSchema.safeParse(parentAuth);const app=staticAuthOptionsSchema.safeParse(appAuth);if(!parent.success||!app.success)return false;const sameChallenge=parent.data.header.toLowerCase()===app.data.header.toLowerCase()&&parent.data.scheme===app.data.scheme;return sameChallenge&&parent.data.tokens.every(token=>app.data.tokens.includes(token))}function getProviderUrl(options){if(isTransparentMode(options)){return options.provider}if(isOrchestratedMode(options)&&isOrchestratedRemote(options)){return options.provider}return void 0}function appRequiresOrchestration(appAuth,parentAuth){if(!appAuth){return false}if(!parentAuth){return appAuth.mode!=="public"}const appProviderId=deriveProviderId(appAuth);const parentProviderId=deriveProviderId(parentAuth);return appProviderId!==parentProviderId}function getProviderScopes(detection,providerId){const provider=detection.providers.get(providerId);return provider?.scopes??[]}function getProviderApps(detection,providerId){const provider=detection.providers.get(providerId);return provider?.appIds.filter(id=>id!=="__parent__")??[]}function buildWwwAuthenticate(options={}){const parts=["Bearer"];const params=[];if(options.resourceMetadataUrl){params.push(`resource_metadata="${escapeQuotedString(options.resourceMetadataUrl)}"`)}if(options.realm){params.push(`realm="${escapeQuotedString(options.realm)}"`)}if(options.error){params.push(`error="${options.error}"`)}if(options.errorDescription){params.push(`error_description="${escapeQuotedString(options.errorDescription)}"`)}if(options.errorUri){params.push(`error_uri="${escapeQuotedString(options.errorUri)}"`)}if(options.scope){const scopeValue=Array.isArray(options.scope)?options.scope.join(" "):options.scope;params.push(`scope="${escapeQuotedString(scopeValue)}"`)}if(params.length>0){parts.push(params.join(", "))}return parts.join(" ")}function buildPrmUrl(baseUrl,entryPath,routeBase){const normalizedEntry=normalizePathSegment(entryPath);const normalizedRoute=normalizePathSegment(routeBase);return`${baseUrl}/.well-known/oauth-protected-resource${normalizedEntry}${normalizedRoute}`}function buildUnauthorizedHeader(prmUrl){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl})}function buildInvalidTokenHeader(prmUrl,description){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl,error:"invalid_token",errorDescription:description??"The access token is invalid or expired"})}function buildInsufficientScopeHeader(prmUrl,requiredScopes,description){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl,error:"insufficient_scope",scope:requiredScopes,errorDescription:description??"The request requires higher privileges"})}function buildInvalidRequestHeader(prmUrl,description){return buildWwwAuthenticate({resourceMetadataUrl:prmUrl,error:"invalid_request",errorDescription:description??"The request is missing required parameters"})}function parseWwwAuthenticate(header){const result={};if(!header.toLowerCase().startsWith("bearer")){return result}const paramString=header.substring(6).trim();const params=parseQuotedParams(paramString);for(const[key,value]of params){const unescapedValue=unescapeQuotedString(value);switch(key.toLowerCase()){case"resource_metadata":result.resourceMetadataUrl=unescapedValue;break;case"realm":result.realm=unescapedValue;break;case"error":result.error=unescapedValue;break;case"error_description":result.errorDescription=unescapedValue;break;case"error_uri":result.errorUri=unescapedValue;break;case"scope":result.scope=unescapedValue;break}}return result}function parseQuotedParams(input){const result=[];let i=0;const len=input.length;while(i<len){while(i<len&&(input[i]===" "||input[i]===","||input[i]===" ")){i++}if(i>=len)break;const keyStart=i;while(i<len&&/\w/.test(input[i])){i++}const key=input.slice(keyStart,i);if(!key)break;while(i<len&&(input[i]===" "||input[i]===" ")){i++}if(i>=len||input[i]!=="="){while(i<len&&input[i]!==",")i++;continue}i++;while(i<len&&(input[i]===" "||input[i]===" ")){i++}if(i>=len||input[i]!=='"'){while(i<len&&input[i]!==",")i++;continue}i++;let value="";while(i<len&&input[i]!=='"'){if(input[i]==="\\"&&i+1<len){value+=input[i+1];i+=2}else{value+=input[i];i++}}if(i<len&&input[i]==='"'){i++}result.push([key,value])}return result}function escapeQuotedString(value){return value.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}function unescapeQuotedString(value){return value.replace(/\\(.)/g,"$1")}function normalizePathSegment(path){if(!path||path==="/")return"";const normalized=path.startsWith("/")?path:`/${path}`;let end=normalized.length;while(end>0&&normalized[end-1]==="/"){end--}return normalized.slice(0,end)}function validateAudience(tokenAudience,options){const{expectedAudiences,allowNoAudience=false,caseSensitive=true,allowWildcards=false}=options;if(tokenAudience===void 0||tokenAudience===null){if(allowNoAudience){return{valid:true}}return{valid:false,error:"Token is missing audience claim"}}if(expectedAudiences.length===0){return{valid:false,error:"No expected audiences configured - cannot validate token"}}const tokenAuds=Array.isArray(tokenAudience)?tokenAudience:[tokenAudience];for(const tokenAud of tokenAuds){for(const expectedAud of expectedAudiences){if(matchesAudience(tokenAud,expectedAud,caseSensitive,allowWildcards)){return{valid:true,matchedAudience:tokenAud}}}}return{valid:false,error:`Token audience does not match expected audiences. Got: ${tokenAuds.join(", ")}. Expected one of: ${expectedAudiences.join(", ")}`}}function matchesAudience(tokenAud,expectedAud,caseSensitive,allowWildcards){if(caseSensitive){if(tokenAud===expectedAud)return true}else{if(tokenAud.toLowerCase()===expectedAud.toLowerCase())return true}if(allowWildcards&&expectedAud.includes("*")){const wildcardCount=(expectedAud.match(/\*/g)||[]).length;if(wildcardCount>2){return false}const pattern=expectedAud.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^.]*");const regex=new RegExp(`^${pattern}$`,caseSensitive?"":"i");if(regex.test(tokenAud))return true}return false}function createAudienceValidator(options){return audience=>validateAudience(audience,options)}function deriveExpectedAudience(resourceUrl){const audiences=[];try{const url=new URL(resourceUrl);audiences.push(resourceUrl.replace(/\/$/,""));if(url.pathname!=="/"&&url.pathname!==""){audiences.push(url.origin)}}catch{audiences.push(resourceUrl)}return[...new Set(audiences)]}var AudienceValidator=class _AudienceValidator{options;constructor(options={}){this.options={expectedAudiences:[...options.expectedAudiences??[]],allowNoAudience:options.allowNoAudience??false,caseSensitive:options.caseSensitive??true,allowWildcards:options.allowWildcards??false}}validate(audience){return validateAudience(audience,this.options)}addAudiences(...audiences){this.options.expectedAudiences.push(...audiences)}setAudiences(audiences){this.options.expectedAudiences=audiences}static fromResourceUrl(resourceUrl,options={}){return new _AudienceValidator({...options,expectedAudiences:deriveExpectedAudience(resourceUrl)})}};var DEFAULT_ALLOWED_SCOPES=Object.freeze(["openid","profile","email","offline_access"]);function grantScopes(requested,allowed){const patterns=allowed??DEFAULT_ALLOWED_SCOPES;const granted=[];for(const scope of requested){if(!scope||granted.includes(scope))continue;if(patterns.some(pattern=>globMatch(pattern,scope)))granted.push(scope)}return granted}function advertisedScopes(allowed){const scopes=[];for(const scope of allowed??DEFAULT_ALLOWED_SCOPES){if(scope&&!scope.includes("*")&&!scopes.includes(scope))scopes.push(scope)}return scopes}function resourceScopesFor(options){const mode=options?.mode??"public";if(mode==="local"||mode==="remote")return advertisedScopes(options?.allowedScopes);const listed=mode==="public"?options?.anonymousScopes??["anonymous"]:mode==="static"?options?.scopes??["static"]:mode==="transparent"?[...options?.requiredScopes??[],...options?.scopes??[]]:[];return advertisedScopes(listed)}function isLoopbackRedirectUri(uri){let url;try{url=new URL(uri)}catch{return false}if(url.protocol!=="http:"&&url.protocol!=="https:")return false;if(url.username||url.password)return false;const host=url.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(host==="localhost"||host==="::1")return true;const m=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);if(m){const octets=m.slice(1).map(n=>Number(n));return octets.every(o=>o>=0&&o<=255)&&octets[0]===127}return false}function scopesFromClaims(claims){const scopes=[claims?.scope,claims?.scp].flatMap(claim=>{if(typeof claim==="string")return claim.split(/\s+/).filter(Boolean);if(Array.isArray(claim))return claim.filter(scope=>typeof scope==="string");return[]});return[...new Set(scopes)]}import{internalZ as z19}from"@frontmcp/lazy-zod";var credentialScopeSchema=z19.enum(["global","user","session"]);var loadingStrategySchema=z19.enum(["eager","lazy"]);var getCredentialOptionsSchema=z19.object({forceRefresh:z19.boolean().optional(),scopes:z19.array(z19.string()).optional(),timeout:z19.number().positive().optional()}).strict();var credentialProviderConfigSchema=z19.object({name:z19.string().min(1),description:z19.string().optional(),scope:credentialScopeSchema,loading:loadingStrategySchema,cacheTtl:z19.number().nonnegative().optional(),factory:z19.any(),refresh:z19.any().optional(),toHeaders:z19.any().optional(),metadata:z19.record(z19.string(),z19.unknown()).optional(),required:z19.boolean().optional()}).strict();var authProviderMappingSchema=z19.union([z19.string(),z19.object({name:z19.string().min(1),required:z19.boolean().optional().default(true),scopes:z19.array(z19.string()).optional(),alias:z19.string().optional()}).strict()]);var authProvidersVaultOptionsSchema=z19.object({enabled:z19.boolean().optional(),useSharedStorage:z19.boolean().optional().default(true),namespace:z19.string().optional().default("authproviders:"),defaultCacheTtl:z19.number().nonnegative().optional().default(36e5),maxCredentialsPerSession:z19.number().positive().optional().default(100),providers:z19.array(credentialProviderConfigSchema).optional()}).strict();function extractCredentialExpiry(credential){switch(credential.type){case"oauth":case"oauth_pkce":case"bearer":case"service_account":return credential.expiresAt;default:return void 0}}var CredentialCache=class{cache=new Map;maxSize;stats={hits:0,misses:0,evictions:0,size:0};constructor(maxSize=100){this.maxSize=maxSize}get(providerId){const entry=this.cache.get(providerId);if(!entry){this.stats.misses++;return void 0}if(this.isExpired(entry)){this.cache.delete(providerId);this.stats.size=this.cache.size;this.stats.misses++;this.stats.evictions++;return void 0}if(entry.resolved.expiresAt&&Date.now()>=entry.resolved.expiresAt){this.cache.delete(providerId);this.stats.size=this.cache.size;this.stats.misses++;this.stats.evictions++;return void 0}this.stats.hits++;return entry.resolved}set(providerId,resolved,ttl=0){if(this.cache.size>=this.maxSize&&!this.cache.has(providerId)){this.evictOldest()}const entry={resolved,cachedAt:Date.now(),ttl};this.cache.set(providerId,entry);this.stats.size=this.cache.size}has(providerId){const entry=this.cache.get(providerId);if(!entry)return false;if(this.isExpired(entry)){this.cache.delete(providerId);this.stats.size=this.cache.size;this.stats.evictions++;return false}return true}invalidate(providerId){const deleted=this.cache.delete(providerId);if(deleted){this.stats.size=this.cache.size}return deleted}invalidateAll(){this.cache.clear();this.stats.size=0}invalidateByScope(scope){for(const[key,entry]of this.cache){if(entry.resolved.scope===scope){this.cache.delete(key)}}this.stats.size=this.cache.size}keys(){this.cleanup();return[...this.cache.keys()]}get size(){return this.cache.size}getStats(){return{...this.stats}}resetStats(){this.stats={hits:0,misses:0,evictions:0,size:this.cache.size}}cleanup(){const now=Date.now();for(const[key,entry]of this.cache){if(this.isExpiredAt(entry,now)){this.cache.delete(key);this.stats.evictions++}}this.stats.size=this.cache.size}isExpired(entry){return this.isExpiredAt(entry,Date.now())}isExpiredAt(entry,now){if(entry.ttl>0&&now-entry.cachedAt>=entry.ttl){return true}if(entry.resolved.expiresAt&&now>=entry.resolved.expiresAt){return true}if(!entry.resolved.isValid){return true}return false}evictOldest(){let oldestKey;let oldestTime=Infinity;for(const[key,entry]of this.cache){if(entry.cachedAt<oldestTime){oldestTime=entry.cachedAt;oldestKey=key}}if(oldestKey){this.cache.delete(oldestKey);this.stats.evictions++;this.stats.size=this.cache.size}}};var AUTH_PROVIDERS_ACCESSOR=Symbol.for("frontmcp:AUTH_PROVIDERS_ACCESSOR");import{base64Encode}from"@frontmcp/utils";var AuthProvidersAccessorImpl=class{constructor(registry,vault,cache,loader,context,logger){this.registry=registry;this.vault=vault;this.cache=cache;this.loader=loader;this.context=context;this.logger=logger}registry;vault;cache;loader;context;logger;async get(providerName,options){const config=this.registry.get(providerName);if(!config){this.logger?.debug(`Provider "${providerName}" is not registered`);return null}if(!options?.forceRefresh){const cached=this.cache.get(providerName);if(cached&&this.isValid(cached)){this.logger?.debug(`Cache hit for "${providerName}"`);return cached}}if(!options?.forceRefresh){const fromVault=await this.loadFromVault(providerName,config);if(fromVault&&this.isValid(fromVault)){this.cache.set(providerName,fromVault,config.cacheTtl);this.logger?.debug(`Vault hit for "${providerName}"`);return fromVault}}const loaded=await this.loader.load(config,this.context);if(!loaded){return null}await this.storeInVault(providerName,loaded,config);this.cache.set(providerName,loaded,config.cacheTtl);return loaded}async getMany(providerNames,options){const results=new Map;const promises=providerNames.map(async name=>{const result=await this.get(name,options);return{name,result}});const settled=await Promise.allSettled(promises);for(const outcome of settled){if(outcome.status==="fulfilled"){results.set(outcome.value.name,outcome.value.result)}else{this.logger?.warn("Failed to load credential:",outcome.reason)}}return results}async headers(providerName){const resolved=await this.get(providerName);if(!resolved){return{}}const config=this.registry.get(providerName);if(config?.toHeaders){return config.toHeaders(resolved.credential)}return this.defaultHeaders(resolved.credential)}async headersMany(providerNames){const allHeaders={};for(const name of providerNames){const headers=await this.headers(name);Object.assign(allHeaders,headers)}return allHeaders}async refresh(providerName){const config=this.registry.get(providerName);if(!config){this.logger?.debug(`Cannot refresh: provider "${providerName}" not registered`);return null}const existing=this.cache.get(providerName);const existingCredential=existing?.credential;try{const refreshed=await this.loader.refresh(config,{...this.context,existingCredential});if(!refreshed){return null}await this.storeInVault(providerName,refreshed,config);this.cache.set(providerName,refreshed,config.cacheTtl);return refreshed}catch(error){this.logger?.warn(`Failed to refresh credential "${providerName}":`,error);return null}}async has(providerName){const cached=this.cache.get(providerName);if(cached){if(this.isValid(cached)){return true}this.cache.invalidate(providerName)}const config=this.registry.get(providerName);if(!config){return false}const fromVault=await this.loadFromVault(providerName,config);return fromVault!==null&&this.isValid(fromVault)}isRegistered(providerName){return this.registry.has(providerName)}invalidate(providerName){this.cache.invalidate(providerName);this.logger?.debug(`Invalidated cache for "${providerName}"`)}invalidateAll(){this.cache.invalidateAll();this.logger?.debug("Invalidated all cached credentials")}listProviders(){return this.registry.getNames()}async listAvailable(){const available=[];for(const name of this.registry.getNames()){if(await this.has(name)){available.push(name)}}return available}async loadFromVault(providerName,config){try{const credential=await this.vault.getCredential(this.context.sessionId,providerName,config.scope,this.context.userSub);if(!credential){return null}return{credential,providerId:providerName,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope}}catch(error){this.logger?.warn(`Failed to load from vault "${providerName}":`,error);return null}}async storeInVault(providerName,resolved,config){try{await this.vault.storeCredential(this.context.sessionId,providerName,resolved.credential,config.scope,this.context.userSub,config.cacheTtl)}catch(error){this.logger?.warn(`Failed to store in vault "${providerName}":`,error)}}isValid(resolved){if(!resolved.isValid){return false}if(resolved.expiresAt&&Date.now()>=resolved.expiresAt){return false}return true}defaultHeaders(credential){switch(credential.type){case"oauth":case"oauth_pkce":return{Authorization:`${credential.tokenType} ${credential.accessToken}`};case"bearer":return{Authorization:`Bearer ${credential.token}`};case"api_key":if(credential.headerPrefix){return{[credential.headerName]:`${credential.headerPrefix}${credential.key}`}}return{[credential.headerName]:credential.key};case"basic":{const value=`${credential.username}:${credential.password}`;const encoded=credential.encodedValue??base64Encode(new TextEncoder().encode(value));return{Authorization:`Basic ${encoded}`}}case"custom":return credential.headers??{};case"ssh_key":case"mtls":case"private_key":case"service_account":return{};default:return{}}}};var AuthProvidersRegistry=class{providers=new Map;defaultCacheTtl;constructor(options){this.defaultCacheTtl=options?.defaultCacheTtl??36e5;if(options?.providers){for(const provider of options.providers){this.register(provider)}}}register(config){if(this.providers.has(config.name)){throw new CredentialProviderAlreadyRegisteredError(config.name)}const normalized=this.normalize(config);this.providers.set(config.name,normalized)}unregister(name){return this.providers.delete(name)}get(name){return this.providers.get(name)}has(name){return this.providers.has(name)}getNames(){return[...this.providers.keys()]}getAll(){return[...this.providers.values()]}getByScope(scope){return this.getAll().filter(p=>p.scope===scope)}getByLoading(loading){return this.getAll().filter(p=>p.loading===loading)}getRequired(){return this.getAll().filter(p=>p.required)}getEager(){return this.getByLoading("eager")}getLazy(){return this.getByLoading("lazy")}get size(){return this.providers.size}isEmpty(){return this.providers.size===0}clear(){this.providers.clear()}normalize(config){return{name:config.name,description:config.description,scope:config.scope,loading:config.loading,cacheTtl:config.cacheTtl??this.defaultCacheTtl,factory:config.factory,refresh:config.refresh,toHeaders:config.toHeaders,metadata:config.metadata,required:config.required??false}}};var AUTH_PROVIDERS_REGISTRY=Symbol.for("frontmcp:AUTH_PROVIDERS_REGISTRY");import{EncryptedTypedStorage as EncryptedTypedStorage2,hkdfSha256 as hkdfSha2566,MAX_TTL_SECONDS,randomBytes as randomBytes9,sha256Hex as sha256Hex7}from"@frontmcp/utils";var UNIDENTIFIED_SESSION_PREFIX="unidentified:";var DEFAULT_CREDENTIAL_TTL_MS=36e5;var ENCRYPTION_KEY_INFO=new TextEncoder().encode("frontmcp-auth-providers-vault-v1");function positiveOr(value,fallback){return value!==void 0&&value>0?value:fallback}function encryptionKeyFrom(pepper){if(!pepper)return randomBytes9(32);return hkdfSha2566(new TextEncoder().encode(pepper),new Uint8Array(0),ENCRYPTION_KEY_INFO,32)}var AuthProvidersVault=class{credentials;namespace;defaultCacheTtl;logger;constructor(storage,options={}){this.credentials=new EncryptedTypedStorage2(storage,{keys:[{kid:"authproviders-v1",key:encryptionKeyFrom(options.pepper)}]});this.namespace=options.namespace??"authproviders:";this.defaultCacheTtl=positiveOr(options.defaultCacheTtl,DEFAULT_CREDENTIAL_TTL_MS);this.logger=options.logger}async storeCredential(sessionId,providerId,credential,scope,userId,cacheTtl){if(scope==="session"&&sessionId.startsWith(UNIDENTIFIED_SESSION_PREFIX)){this.logger?.debug(`Not storing credential "${providerId}": the caller has no verified session`);return}const key=this.credentialKey(sessionId,providerId,scope,userId);const appCredential={appId:this.namespace,providerId,credential,acquiredAt:Date.now(),isValid:true,expiresAt:extractCredentialExpiry(credential)};try{await this.write(key,appCredential,cacheTtl);this.logger?.debug(`Stored credential "${providerId}" with scope "${scope}"`)}catch(error){this.logger?.warn(`Failed to store credential "${providerId}":`,error);throw error}}async getCredential(sessionId,providerId,scope,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{const appCredential=await this.read(key);if(!appCredential){return null}if(!appCredential.isValid){this.logger?.debug(`Credential "${providerId}" is marked as invalid`);return null}if(appCredential.expiresAt&&Date.now()>=appCredential.expiresAt){this.logger?.debug(`Credential "${providerId}" has expired`);return null}return appCredential.credential}catch(error){this.logger?.warn(`Failed to get credential "${providerId}":`,error);if(error instanceof CredentialStorageError){throw error}throw new CredentialStorageError(`Storage error while retrieving credential "${providerId}": ${error instanceof Error?error.message:String(error)}`)}}async removeCredential(sessionId,providerId,scope,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{await this.credentials.delete(key);this.logger?.debug(`Removed credential "${providerId}"`)}catch(error){this.logger?.warn(`Failed to remove credential "${providerId}":`,error)}}async invalidateCredential(sessionId,providerId,scope,reason,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{const appCredential=await this.read(key);if(!appCredential)return;await this.write(key,{...appCredential,isValid:false,invalidReason:reason});this.logger?.debug(`Invalidated credential "${providerId}": ${reason}`)}catch(error){this.logger?.warn(`Failed to invalidate credential "${providerId}":`,error)}}async refreshOAuthCredential(sessionId,providerId,scope,tokens,userId){const key=this.credentialKey(sessionId,providerId,scope,userId);try{const appCredential=await this.read(key);if(!appCredential)return;const credential=appCredential.credential;if(credential.type!=="oauth"&&credential.type!=="oauth_pkce")return;const refreshed={...appCredential,credential:{...credential,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??credential.refreshToken,expiresAt:tokens.expiresAt??credential.expiresAt},expiresAt:tokens.expiresAt??appCredential.expiresAt,isValid:true,invalidReason:void 0};await this.write(key,refreshed);this.logger?.debug(`Refreshed OAuth credential "${providerId}"`)}catch(error){this.logger?.warn(`Failed to refresh OAuth credential "${providerId}":`,error);throw error}}async getAllCredentials(sessionId,scope,userId){if(scope){return this.listCredentials(this.buildVaultKey(sessionId,scope,userId))}const scopes=userId?["session","user","global"]:["session","global"];const credentialsPerScope=await Promise.all(scopes.map(eachScope=>this.listCredentials(this.buildVaultKey(sessionId,eachScope,userId))));return credentialsPerScope.flat()}async listCredentials(vaultKey){try{const keys=await this.credentials.keys(`${vaultKey}:*`);const records=await this.credentials.mget(keys);return records.flatMap((record,index)=>record?.storageKey===keys[index]?[record.appCredential]:[])}catch{return[]}}async read(key){const record=await this.credentials.get(key);return record?.storageKey===key?record.appCredential:null}async write(key,appCredential,cacheTtl){const ttlMs=appCredential.expiresAt===void 0?positiveOr(cacheTtl,this.defaultCacheTtl):appCredential.expiresAt-Date.now();if(ttlMs<=0){await this.credentials.delete(key);return}const ttlSeconds=Math.min(Math.ceil(ttlMs/1e3),MAX_TTL_SECONDS);await this.credentials.set(key,{storageKey:key,appCredential},{ttlSeconds})}credentialKey(sessionId,providerId,scope,userId){return`${this.buildVaultKey(sessionId,scope,userId)}:${providerId}`}buildVaultKey(sessionId,scope,userId){switch(scope){case"global":return`${this.namespace}global`;case"user":if(!userId){throw new AuthInvalidInputError(`userId is required for user-scoped credentials (namespace: ${this.namespace})`)}return`${this.namespace}user:${sha256Hex7(userId)}`;case"session":return`${this.namespace}session:${sha256Hex7(sessionId)}`;default:{const _exhaustive=scope;throw new AuthInvalidInputError(`Unknown credential scope: ${_exhaustive}`)}}}};var AUTH_PROVIDERS_VAULT=Symbol.for("frontmcp:AUTH_PROVIDERS_VAULT");var EagerCredentialLoader=class{constructor(registry,cache,logger){this.registry=registry;this.cache=cache;this.logger=logger}registry;cache;logger;async loadForSession(context){const startTime=Date.now();const eagerProviders=this.registry.getEager();if(eagerProviders.length===0){return{loaded:new Map,failed:new Map,duration:Date.now()-startTime}}this.logger?.debug(`Loading ${eagerProviders.length} eager credential providers`);const loaded=new Map;const failed=new Map;const results=await Promise.allSettled(eagerProviders.map(async config=>{const credential=await this.loadOne(config,context);return{name:config.name,credential,config}}));for(const result of results){if(result.status==="fulfilled"){const{name,credential,config}=result.value;if(credential){const resolved=this.wrapCredential(name,credential,config);loaded.set(name,resolved);this.cache.set(name,resolved,config.cacheTtl);this.logger?.debug(`Loaded eager credential: ${name}`)}else if(config.required){failed.set(name,new Error(`Required credential "${name}" returned null`));this.logger?.warn(`Required eager credential "${name}" returned null`)}}else{const providerName=result.reason?.providerName||"unknown";const error=result.reason instanceof Error?result.reason:new Error(String(result.reason));failed.set(providerName,error);this.logger?.warn(`Failed to load eager credential "${providerName}":`,error.message)}}const duration=Date.now()-startTime;this.logger?.debug(`Eager loading complete: ${loaded.size} loaded, ${failed.size} failed, ${duration}ms`);return{loaded,failed,duration}}async loadOne(config,context){try{return await config.factory({...context,metadata:config.metadata})}catch(error){error.providerName=config.name;throw error}}wrapCredential(providerId,credential,config){return{credential,providerId,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope}}};function inFlightKey(providerName,context){return JSON.stringify([providerName,context.sessionId,context.userSub??null])}var LazyCredentialLoader=class{constructor(logger){this.logger=logger}logger;loading=new Map;async load(config,context){const key=inFlightKey(config.name,context);const existing=this.loading.get(key);if(existing){this.logger?.debug(`Waiting for in-flight load: ${config.name}`);return existing}const promise=this.doLoad(config,context);this.loading.set(key,promise);try{return await promise}finally{this.loading.delete(key)}}async doLoad(config,context){const startTime=Date.now();this.logger?.debug(`Loading lazy credential: ${config.name}`);try{const credential=await config.factory({...context,metadata:config.metadata});if(!credential){this.logger?.debug(`Lazy credential "${config.name}" returned null`);return null}const resolved={credential,providerId:config.name,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope};const duration=Date.now()-startTime;this.logger?.debug(`Loaded lazy credential "${config.name}" in ${duration}ms`);return resolved}catch(error){const duration=Date.now()-startTime;this.logger?.warn(`Failed to load lazy credential "${config.name}" after ${duration}ms:`,error);throw error}}async refresh(config,context){const startTime=Date.now();this.logger?.debug(`Refreshing credential: ${config.name}`);try{let credential;if(config.refresh){credential=await config.refresh(context)}else{credential=await config.factory(context)}if(!credential){this.logger?.debug(`Credential refresh "${config.name}" returned null`);return null}const resolved={credential,providerId:config.name,acquiredAt:Date.now(),expiresAt:extractCredentialExpiry(credential),isValid:true,scope:config.scope};const duration=Date.now()-startTime;this.logger?.debug(`Refreshed credential "${config.name}" in ${duration}ms`);return resolved}catch(error){const duration=Date.now()-startTime;this.logger?.warn(`Failed to refresh credential "${config.name}" after ${duration}ms:`,error);throw error}}isLoading(name,context){return this.loading.has(inFlightKey(name,context))}cancelAll(){this.loading.clear()}};import{internalZ as z20}from"@frontmcp/lazy-zod";import{base64urlDecode as base64urlDecode7,base64urlEncode as base64urlEncode5,decryptAesGcm as decryptAesGcm7,encryptAesGcm as encryptAesGcm7,hkdfSha256 as hkdfSha2567,isProduction as isProduction4,randomBytes as randomBytes10,sha256Hex as sha256Hex8,StorageNotSupportedError}from"@frontmcp/utils";var STORAGE_KEY_PREFIX="upstream-client:";var HKDF_SALT2=new TextEncoder().encode("frontmcp-upstream-client");var DEFAULT_TIMEOUT_MS=1e4;var SECRET_EXPIRY_MARGIN_SECONDS=60;var SUPPORTED_AUTH_METHODS=["client_secret_post","none"];var UpstreamClientRegistrationError=class extends AuthInternalError{constructor(provider,reason){super(`Dynamic client registration with ${provider} failed: ${reason}. Register a client at the provider and set clientId (and clientSecret), or fix providerConfig.registrationEndpoint.`,"UPSTREAM_CLIENT_REGISTRATION_FAILED")}};var storedClientSchema=z20.object({clientId:z20.string().min(1),clientSecret:z20.string().optional(),clientSecretExpiresAt:z20.number().optional(),redirectUri:z20.string()});var registrationResponseSchema=z20.object({client_id:z20.string().min(1),client_secret:z20.string().optional(),client_secret_expires_at:z20.number().optional(),redirect_uris:z20.array(z20.string()).optional(),token_endpoint_auth_method:z20.string().optional()});var metadataSchema=z20.object({issuer:z20.string().optional(),registration_endpoint:z20.string().optional()});async function resolveUpstreamClient(options){const logger=options.logger??noopAuthLogger;const{storage}=options;const storageKey=upstreamClientStorageKey(options);const cached=storage?await readStoredClient(storage,options,storageKey):{status:"missing"};if(cached.status==="usable"){logger.info(`Using client ${cached.client.clientId}, registered earlier with ${options.provider}`);return cached.client}const registered=await registerUpstreamClient(options);logger.info(`Registered client ${registered.clientId} with ${options.provider}`);if(!storage){logger.info("No persistent tokenStorage is configured, so the server registers again on its next start");return registered}return storeClient(storage,options,storageKey,registered,cached.status==="stale")}async function registerUpstreamClient(options){const endpoint=options.registrationEndpoint??await discoverRegistrationEndpoint(options);assertFetchableUrl(options.provider,endpoint);const requestBody={client_name:options.clientName,redirect_uris:[options.redirectUri],grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"client_secret_post",...options.scopes.length>0?{scope:options.scopes.join(" ")}:{}};let response;try{response=await fetchWithTimeout(options,endpoint,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(requestBody)})}catch(error){throw new UpstreamClientRegistrationError(options.provider,`${endpoint} could not be reached (${describe(error)})`)}const responseBody=await response.json().catch(()=>void 0);if(!response.ok){throw new UpstreamClientRegistrationError(options.provider,`${endpoint} answered ${response.status}${describeOAuthError(responseBody)}`)}const parsed=registrationResponseSchema.safeParse(responseBody);if(!parsed.success){throw new UpstreamClientRegistrationError(options.provider,`${endpoint} answered without a client_id`)}const registration=parsed.data;if(registration.redirect_uris&&!registration.redirect_uris.includes(options.redirectUri)){throw new UpstreamClientRegistrationError(options.provider,`the provider registered other redirect URIs than ${options.redirectUri}`)}const authMethod=registration.token_endpoint_auth_method;if(authMethod&&!SUPPORTED_AUTH_METHODS.includes(authMethod)){(options.logger??noopAuthLogger).warn(`${options.provider} registered the client for token_endpoint_auth_method "${authMethod}"; FrontMCP sends the client secret in the token request body (client_secret_post)`)}return{clientId:registration.client_id,clientSecret:registration.client_secret,clientSecretExpiresAt:registration.client_secret_expires_at,redirectUri:options.redirectUri}}async function discoverRegistrationEndpoint(options){const logger=options.logger??noopAuthLogger;const issuer=trimSlash(options.provider);const issuerUrl=new URL(issuer);const issuerPath=issuerUrl.pathname==="/"?"":issuerUrl.pathname;const metadataUrls=[`${issuerUrl.origin}/.well-known/oauth-authorization-server${issuerPath}`,`${issuer}/.well-known/openid-configuration`];for(const metadataUrl of metadataUrls){const metadata=await fetchMetadata(options,metadataUrl);if(!metadata)continue;if(normalizeIssuer(metadata.issuer)!==normalizeIssuer(issuer)){logger.warn(`Ignoring ${metadataUrl}: its issuer "${metadata.issuer??""}" is not ${issuer}`);continue}if(metadata.registration_endpoint)return metadata.registration_endpoint}throw new UpstreamClientRegistrationError(options.provider,`no registration_endpoint found in ${metadataUrls.join(" or ")}`)}async function fetchMetadata(options,metadataUrl){assertFetchableUrl(options.provider,metadataUrl);try{const response=await fetchWithTimeout(options,metadataUrl,{headers:{Accept:"application/json"}});if(!response.ok)return void 0;const parsed=metadataSchema.safeParse(await response.json());return parsed.success?parsed.data:void 0}catch{return void 0}}async function readStoredClient(storage,options,storageKey){const logger=options.logger??noopAuthLogger;let sealed;try{sealed=await storage.get(storageKey)}catch(error){logger.warn(`Could not read the stored upstream client (${describe(error)}); registering a new one`);return{status:"missing"}}if(!sealed)return{status:"missing"};const client=openClient(options.encryptionKey,storageKey,sealed);if(!client){logger.warn("The stored upstream client could not be decrypted (did JWT_SECRET change?); registering a new one");return{status:"stale"}}if(client.redirectUri!==options.redirectUri||isSecretExpired(client))return{status:"stale"};return{status:"usable",client}}async function storeClient(storage,options,storageKey,client,replace){const logger=options.logger??noopAuthLogger;const sealed=sealClient(options.encryptionKey,storageKey,client);const ttlSeconds=secretTtlSeconds(client);try{if(replace){await storage.set(storageKey,sealed,{ttlSeconds});return client}try{await storage.set(storageKey,sealed,{ttlSeconds,ifNotExists:true})}catch(error){if(!(error instanceof StorageNotSupportedError))throw error;await storage.set(storageKey,sealed,{ttlSeconds});return client}const stored=await storage.get(storageKey);const winner=stored?openClient(options.encryptionKey,storageKey,stored):void 0;if(winner&&winner.clientId!==client.clientId){logger.info(`Another instance registered client ${winner.clientId} first; using it`);return winner}}catch(error){logger.warn(`Could not store the upstream client (${describe(error)}); it is used by this process only`)}return client}function upstreamClientStorageKey(options){const scopes=[...options.scopes].sort().join(" ");return`${STORAGE_KEY_PREFIX}${sha256Hex8(`${normalizeIssuer(options.provider)}
|
|
396
|
+
${options.redirectUri}
|
|
397
|
+
${scopes}`)}`}function recordKey(encryptionKey,storageKey){return hkdfSha2567(encryptionKey,HKDF_SALT2,new TextEncoder().encode(storageKey),32)}function sealClient(encryptionKey,storageKey,client){const iv=randomBytes10(12);const plaintext=new TextEncoder().encode(JSON.stringify(client));const{ciphertext,tag}=encryptAesGcm7(recordKey(encryptionKey,storageKey),plaintext,iv);return JSON.stringify({iv:base64urlEncode5(iv),tag:base64urlEncode5(tag),data:base64urlEncode5(ciphertext)})}function openClient(encryptionKey,storageKey,sealed){try{const envelope=JSON.parse(sealed);const plaintext=decryptAesGcm7(recordKey(encryptionKey,storageKey),base64urlDecode7(envelope.data),base64urlDecode7(envelope.iv),base64urlDecode7(envelope.tag));const parsed=storedClientSchema.safeParse(JSON.parse(new TextDecoder().decode(plaintext)));return parsed.success?parsed.data:void 0}catch{return void 0}}function isSecretExpired(client){const expiresAt=client.clientSecretExpiresAt;if(!expiresAt)return false;return expiresAt-SECRET_EXPIRY_MARGIN_SECONDS<=Math.floor(Date.now()/1e3)}function secretTtlSeconds(client){const expiresAt=client.clientSecretExpiresAt;if(!expiresAt)return void 0;return Math.max(1,expiresAt-Math.floor(Date.now()/1e3))}function assertFetchableUrl(provider,url){let parsed;try{parsed=new URL(url)}catch{throw new UpstreamClientRegistrationError(provider,`"${url}" is not a URL`)}const host=parsed.hostname.toLowerCase();const isLoopback=host==="localhost"||host==="127.0.0.1"||host==="[::1]"||host.endsWith(".localhost");const allowed=parsed.protocol==="https:"||parsed.protocol==="http:"&&(isLoopback||!isProduction4());if(!allowed){throw new UpstreamClientRegistrationError(provider,`${url} is not an https URL`)}}async function fetchWithTimeout(options,url,init){const controller=new AbortController;const timer=setTimeout(()=>controller.abort(),options.timeoutMs??DEFAULT_TIMEOUT_MS);try{return await fetch(url,{...init,redirect:"error",signal:controller.signal})}finally{clearTimeout(timer)}}function describeOAuthError(body){if(!body||typeof body!=="object")return"";const{error,error_description:description}=body;if(typeof error!=="string")return"";return typeof description==="string"?`: ${error} (${description})`:`: ${error}`}function describe(error){return error instanceof Error?error.message:String(error)}import{getEnv as getEnv4}from"@frontmcp/utils";function resolveDotPath(obj,path){if(path in obj){return obj[path]}const segments=path.split(".");let current=obj;for(const segment of segments){if(current===null||current===void 0||typeof current!=="object"){return void 0}current=current[segment]}return current}function toStringArray(value){if(Array.isArray(value)){return value.filter(v=>typeof v==="string")}if(typeof value==="string"){return value.split(/\s+/).filter(Boolean)}return[]}var noopRelationshipResolver={async check(){return false}};function resolveAuthUser(authInfo){const extraUser=authInfo?.extra?.["user"];if(authInfo?.user)return authInfo.user;return typeof extraUser==="object"&&extraUser!==null?extraUser:{}}function isAnonymousSubject(sub){return typeof sub!=="string"||sub===""||sub.startsWith("anon:")}function signedInSubject(rawSub,mappedSub){if(rawSub!==void 0&&isAnonymousSubject(rawSub))return void 0;return[mappedSub,rawSub].find(candidate=>!isAnonymousSubject(candidate))}var runtimeEnv=new Proxy({},{get:(_target,name)=>typeof name==="string"?getEnv4(name):void 0});var AuthoritiesContextBuilder=class{claimsMapping;claimsResolver;relationshipResolver;constructor(options={}){this.claimsMapping=options.claimsMapping;this.claimsResolver=options.claimsResolver;this.relationshipResolver=options.relationshipResolver??noopRelationshipResolver}build(authInfo,input={},env=runtimeEnv){if(this.claimsResolver){const resolved=this.claimsResolver(authInfo);return{user:{sub:signedInSubject(resolveAuthUser(authInfo).sub),roles:resolved.roles,permissions:resolved.permissions,claims:resolved.claims},input,env,relationships:this.relationshipResolver}}const user=resolveAuthUser(authInfo);const authorization=authInfo?.extra?.["authorization"];const authorizationClaims=authorization?.["claims"]??{};const rawClaims={...authorizationClaims,...user};let roles;if(this.claimsMapping?.roles){roles=toStringArray(resolveDotPath(rawClaims,this.claimsMapping.roles))}else{roles=toStringArray(user["roles"]??authorization?.["scopes"]??[])}let permissions;if(this.claimsMapping?.permissions){permissions=toStringArray(resolveDotPath(rawClaims,this.claimsMapping.permissions))}else{permissions=toStringArray(user["permissions"]??[])}const mappedSub=this.claimsMapping?.userId?resolveDotPath(rawClaims,this.claimsMapping.userId):void 0;const mappedTenantId=this.claimsMapping?.tenantId?resolveDotPath(rawClaims,this.claimsMapping.tenantId):void 0;return{user:{sub:signedInSubject(user.sub,mappedSub),roles,permissions,claims:rawClaims,tenantId:typeof mappedTenantId==="string"&&mappedTenantId!==""?mappedTenantId:void 0},input,env,relationships:this.relationshipResolver}}};function toStringArray2(value){if(Array.isArray(value)){return value.filter(v=>typeof v==="string")}if(typeof value==="string"){return value.split(/\s+/).filter(Boolean)}return[]}var FrontMcpAuthContextImpl=class{user;isAnonymous;mode;sessionId;scopes;claims;roles;permissions;constructor(source,claimsMapping,claimsResolver){const rawUser=resolveAuthUser(source);const mappedSub=!claimsResolver&&claimsMapping?.userId?resolveDotPath(this.buildRawClaims(source),claimsMapping.userId):void 0;const signedInSub=signedInSubject(rawUser.sub,mappedSub);const sub=signedInSub??(typeof rawUser.sub==="string"?rawUser.sub:"");this.user=Object.freeze({sub,name:rawUser.name,email:rawUser.email,picture:rawUser.picture});this.isAnonymous=signedInSub===void 0;const extraAuth=source.extra?.["authorization"];this.mode=String(source.extra?.["authMode"]??extraAuth?.["mode"]??(source.extra?.["isPublic"]===true||rawUser["anonymous"]===true?"public":"authenticated"));this.sessionId=typeof source.sessionId==="string"?source.sessionId:"";this.scopes=Object.freeze(Array.isArray(source.scopes)?source.scopes.filter(s=>typeof s==="string"):[]);if(claimsResolver){const resolved=claimsResolver(source);this.claims=Object.freeze({...resolved.claims});this.roles=Object.freeze([...toStringArray2(resolved.roles)]);this.permissions=Object.freeze([...toStringArray2(resolved.permissions)]);return}const rawClaims=this.buildRawClaims(source);this.claims=Object.freeze({...rawClaims});if(claimsMapping?.roles){this.roles=Object.freeze([...toStringArray2(resolveDotPath(rawClaims,claimsMapping.roles))])}else{this.roles=Object.freeze([...toStringArray2(rawUser["roles"]??extraAuth?.["scopes"]??[])])}if(claimsMapping?.permissions){this.permissions=Object.freeze([...toStringArray2(resolveDotPath(rawClaims,claimsMapping.permissions))])}else{this.permissions=Object.freeze([...toStringArray2(rawUser["permissions"]??[])])}}hasRole(role){return this.roles.includes(role)}hasAllRoles(roles){return roles.every(r=>this.roles.includes(r))}hasAnyRole(roles){return roles.some(r=>this.roles.includes(r))}hasPermission(permission){return this.permissions.includes(permission)}hasAllPermissions(permissions){return permissions.every(p=>this.permissions.includes(p))}hasAnyPermission(permissions){return permissions.some(p=>this.permissions.includes(p))}hasScope(scope){return this.scopes.includes(scope)}hasAllScopes(scopes){return scopes.every(s=>this.scopes.includes(s))}hasAnyScope(scopes){return scopes.some(s=>this.scopes.includes(s))}buildRawClaims(source){const rawUser=resolveAuthUser(source);const extraAuth=source.extra?.["authorization"];const authorizationClaims=extraAuth?.["claims"]??{};return{...authorizationClaims,...rawUser}}};function buildAuthContext(authInfo,claimsMapping,pipes,claimsResolver){const base=new FrontMcpAuthContextImpl(authInfo,claimsMapping,claimsResolver);if(!pipes||pipes.length===0){return base}return runPipes(base,pipes)}async function runPipes(base,pipes){const extensions={};for(const pipe of pipes){try{const result=await pipe(base.claims);if(result&&typeof result==="object"){Object.assign(extensions,result)}}catch(error){const message=error instanceof Error?error.message:String(error);console.warn(`[FrontMcpAuth] pipe failed: ${message}`)}}const merged=Object.create(Object.getPrototypeOf(base));Object.assign(merged,base,extensions);return Object.freeze(merged)}function resolveRequiredScopes(denial,mapping,authorities){const scopes=new Set;if(typeof authorities==="string"&&mapping.profiles?.[authorities]){mapping.profiles[authorities].forEach(s=>scopes.add(s))}if(Array.isArray(authorities)){for(const profileName of authorities){if(typeof profileName==="string"&&mapping.profiles?.[profileName]){mapping.profiles[profileName].forEach(s=>scopes.add(s))}}}if(denial.kind==="roles"&&denial.missing&&mapping.roles){for(const role of denial.missing){const mapped=mapping.roles[role];if(mapped)mapped.forEach(s=>scopes.add(s))}}if(denial.kind==="permissions"&&denial.missing&&mapping.permissions){for(const perm of denial.missing){const mapped=mapping.permissions[perm];if(mapped)mapped.forEach(s=>scopes.add(s))}}return scopes.size>0?[...scopes].sort():void 0}import{z as z21}from"@frontmcp/lazy-zod";var rbacRolesPolicySchema=z21.object({all:z21.array(z21.string().min(1)).optional(),any:z21.array(z21.string().min(1)).optional()}).refine(data=>data.all!==void 0||data.any!==void 0,{message:'Roles policy must have at least "all" or "any"'});var rbacPermissionsPolicySchema=z21.object({all:z21.array(z21.string().min(1)).optional(),any:z21.array(z21.string().min(1)).optional()}).refine(data=>data.all!==void 0||data.any!==void 0,{message:'Permissions policy must have at least "all" or "any"'});var abacOperatorSchema=z21.enum(["eq","neq","in","notIn","gt","gte","lt","lte","contains","startsWith","endsWith","exists","matches"]);var abacConditionSchema=z21.object({path:z21.string().min(1),op:abacOperatorSchema,value:z21.unknown()});var abacPolicySchema=z21.object({match:z21.record(z21.string(),z21.unknown()).optional(),conditions:z21.array(abacConditionSchema).optional()}).refine(data=>data.match!==void 0||data.conditions!==void 0,{message:'Attributes policy must have at least "match" or "conditions"'});var resourceIdRefSchema=z21.union([z21.string().min(1),z21.object({fromInput:z21.string().min(1)}).strict(),z21.object({fromClaims:z21.string().min(1)}).strict()]);var rebacPolicySchema=z21.object({type:z21.string().min(1),resource:z21.string().min(1),resourceId:resourceIdRefSchema});var basePolicyShape={roles:rbacRolesPolicySchema.optional(),permissions:rbacPermissionsPolicySchema.optional(),attributes:abacPolicySchema.optional(),relationships:z21.union([rebacPolicySchema,z21.array(rebacPolicySchema)]).optional(),custom:z21.record(z21.string(),z21.unknown()).optional(),guards:z21.array(z21.instanceof(Function)).optional(),operator:z21.enum(["AND","OR"]).optional()};var authoritiesPolicySchema=z21.lazy(()=>z21.object({...basePolicyShape,not:authoritiesPolicySchema.optional(),anyOf:z21.array(authoritiesPolicySchema).optional(),allOf:z21.array(authoritiesPolicySchema).optional()}));var authoritiesMetadataSchema=z21.union([z21.string().min(1),z21.array(z21.string().min(1)).min(1),authoritiesPolicySchema]);var authoritiesClaimsMappingSchema=z21.record(z21.string(),z21.string().optional()).refine(data=>Object.values(data).some(v=>v!==void 0),{message:"Claims mapping must have at least one mapping"});var authoritiesConfigSchema=z21.object({claimsMapping:authoritiesClaimsMappingSchema.optional(),profiles:z21.record(z21.string().min(1),authoritiesPolicySchema).optional()});function evaluateRbacRoles(policy,ctx){const userRoles=ctx.user.roles;if(policy.all){const missing=policy.all.filter(r=>!userRoles.includes(r));if(missing.length>0){return{granted:false,deniedBy:`roles.all: missing ${missing.map(r=>`'${r}'`).join(", ")}`,denial:{kind:"roles",path:"roles.all",missing},evaluatedPolicies:["roles.all"]}}}if(policy.any){const hasAny=policy.any.some(r=>userRoles.includes(r));if(!hasAny){return{granted:false,deniedBy:`roles.any: user has none of ${policy.any.map(r=>`'${r}'`).join(", ")}`,denial:{kind:"roles",path:"roles.any",missing:policy.any},evaluatedPolicies:["roles.any"]}}}return{granted:true,evaluatedPolicies:["roles"]}}function evaluateRbacPermissions(policy,ctx){const userPerms=ctx.user.permissions;if(policy.all){const missing=policy.all.filter(p=>!userPerms.includes(p));if(missing.length>0){return{granted:false,deniedBy:`permissions.all: missing ${missing.map(p=>`'${p}'`).join(", ")}`,denial:{kind:"permissions",path:"permissions.all",missing},evaluatedPolicies:["permissions.all"]}}}if(policy.any){const hasAny=policy.any.some(p=>userPerms.includes(p));if(!hasAny){return{granted:false,deniedBy:`permissions.any: user has none of ${policy.any.map(p=>`'${p}'`).join(", ")}`,denial:{kind:"permissions",path:"permissions.any",missing:policy.any},evaluatedPolicies:["permissions.any"]}}}return{granted:true,evaluatedPolicies:["permissions"]}}function buildContextEnvelope(ctx){return{user:ctx.user,claims:ctx.user.claims,input:ctx.input,env:ctx.env}}function resolveValue(value,ctx){if(value!==null&&typeof value==="object"&&!Array.isArray(value)){const obj=value;if("fromInput"in obj&&typeof obj["fromInput"]==="string"){return ctx.input[obj["fromInput"]]}if("fromClaims"in obj&&typeof obj["fromClaims"]==="string"){return resolveDotPath(ctx.user.claims,obj["fromClaims"])}}return value}function describeMissingValue(value){const reference=value??{};if(typeof reference["fromInput"]==="string")return`input '${reference["fromInput"]}'`;if(typeof reference["fromClaims"]==="string")return`claim '${reference["fromClaims"]}'`;return"the value"}function unresolvedAttribute(policyPath,denialPath,path,value,actual){return{granted:false,unresolved:true,deniedBy:`${policyPath}: '${path}' could not be checked: ${describeMissingValue(value)} is missing`,denial:{kind:"attributes",path:denialPath,actual},evaluatedPolicies:[policyPath]}}function applyOperator(actual,op,expected){switch(op){case"eq":return actual===expected;case"neq":return actual!==expected;case"in":return Array.isArray(expected)&&expected.includes(actual);case"notIn":return Array.isArray(expected)&&!expected.includes(actual);case"gt":return typeof actual==="number"&&typeof expected==="number"&&actual>expected;case"gte":return typeof actual==="number"&&typeof expected==="number"&&actual>=expected;case"lt":return typeof actual==="number"&&typeof expected==="number"&&actual<expected;case"lte":return typeof actual==="number"&&typeof expected==="number"&&actual<=expected;case"contains":if(typeof actual==="string"&&typeof expected==="string")return actual.includes(expected);if(Array.isArray(actual))return actual.includes(expected);return false;case"startsWith":return typeof actual==="string"&&typeof expected==="string"&&actual.startsWith(expected);case"endsWith":return typeof actual==="string"&&typeof expected==="string"&&actual.endsWith(expected);case"exists":return expected?actual!==void 0&&actual!==null:actual===void 0||actual===null;case"matches":if(typeof actual!=="string"||typeof expected!=="string")return false;if(expected.length>256)return false;if(/(\+\+|\*\*|\{\d+,\d*\}\{)/.test(expected))return false;try{return new RegExp(expected).test(actual)}catch{return false}default:return false}}var REDACTED_ENV_VALUE="[redacted]";function reportableActual(path,actual){return path==="env"||path.startsWith("env.")?REDACTED_ENV_VALUE:actual}function evaluateAbac(policy,ctx){const envelope=buildContextEnvelope(ctx);let unresolved;if(policy.match){for(const[path,expectedRaw]of Object.entries(policy.match)){const actual=resolveDotPath(envelope,path);const expected=resolveValue(expectedRaw,ctx);const reported=reportableActual(path,actual);if(expected===void 0){unresolved??=unresolvedAttribute("attributes.match","attributes.match",path,expectedRaw,reported)}else if(actual!==expected){return{granted:false,deniedBy:`attributes.match: '${path}' expected '${String(expected)}' but got '${String(reported)}'`,denial:{kind:"attributes",path:"attributes.match",expected,actual:reported},evaluatedPolicies:["attributes.match"]}}}}if(policy.conditions){for(let i=0;i<policy.conditions.length;i++){const condition=policy.conditions[i];const denialPath=`attributes.conditions[${i}]`;const actual=resolveDotPath(envelope,condition.path);const expected=resolveValue(condition.value,ctx);const reported=reportableActual(condition.path,actual);if(expected===void 0){unresolved??=unresolvedAttribute("attributes.conditions",denialPath,condition.path,condition.value,reported)}else if(!applyOperator(actual,condition.op,expected)){return{granted:false,deniedBy:`attributes.conditions: '${condition.path}' failed '${condition.op}' check against '${String(expected)}'`,denial:{kind:"attributes",path:denialPath,expected,actual:reported},evaluatedPolicies:["attributes.conditions"]}}}}return unresolved??{granted:true,evaluatedPolicies:["attributes"]}}function resolveResourceId(ref,ctx){if(typeof ref==="string")return ref;if("fromInput"in ref){const val=ctx.input[ref.fromInput];return typeof val==="string"?val:void 0}if("fromClaims"in ref){const val=resolveDotPath(ctx.user.claims,ref.fromClaims);return typeof val==="string"?val:void 0}return void 0}async function evaluateSingleRebac(policy,ctx){const userSub=ctx.user.sub;if(userSub===void 0||isAnonymousSubject(userSub)){return{granted:false,deniedBy:`relationships: an anonymous caller cannot be '${policy.type}' of ${policy.resource}`,denial:{kind:"relationships",path:"user.sub",expected:"a signed-in caller",actual:void 0},evaluatedPolicies:["relationships"]}}const resourceId=resolveResourceId(policy.resourceId,ctx);if(resourceId===void 0){return{granted:false,unresolved:true,deniedBy:`relationships: could not resolve resourceId for ${policy.resource}`,denial:{kind:"relationships",path:"relationships",expected:`${policy.resource}:resourceId`,actual:void 0},evaluatedPolicies:["relationships"]}}const hasRelationship=await ctx.relationships.check(policy.type,policy.resource,resourceId,userSub,ctx);if(!hasRelationship){return{granted:false,deniedBy:`relationships: user '${userSub}' is not '${policy.type}' of ${policy.resource}:${resourceId}`,denial:{kind:"relationships",path:"relationships",expected:`${policy.type}:${policy.resource}:${resourceId}`},evaluatedPolicies:["relationships"]}}return{granted:true,evaluatedPolicies:["relationships"]}}async function evaluateRebac(policy,ctx){const policies=Array.isArray(policy)?policy:[policy];let unresolved;for(const p of policies){const result=await evaluateSingleRebac(p,ctx);if(result.granted)continue;if(!result.unresolved)return result;unresolved??=result}return unresolved??{granted:true,evaluatedPolicies:["relationships"]}}var ABAC_OPERATORS=new Set(["eq","neq","in","notIn","gt","gte","lt","lte","contains","startsWith","endsWith","exists","matches"]);var CHECK_FIELDS=["roles","permissions","attributes","relationships","custom","guards","allOf","anyOf","not"];var RULE_FIELDS=new Set([...CHECK_FIELDS,"operator"]);function isPlainObject(value){return typeof value==="object"&&value!==null&&!Array.isArray(value)}function isNonEmptyString(value){return typeof value==="string"&&value.length>0}function unknownFields(value,known,path){const prefix=path?`${path} has`:"has";return Object.keys(value).filter(key=>!known.has(key)).map(key=>`${prefix} an unknown field "${key}"`)}function checkStringList(value,path){if(!Array.isArray(value))return[`${path} must be a list of names`];if(value.length===0)return[`${path} is empty`];return value.every(isNonEmptyString)?[]:[`${path} must contain only non-empty names`]}var ALL_OR_ANY=new Set(["all","any"]);function checkAllOrAny(value,path){if(!isPlainObject(value))return[`${path} must be an object with "all" or "any"`];const problems=unknownFields(value,ALL_OR_ANY,path);if(value["all"]===void 0&&value["any"]===void 0){problems.push(`${path} needs "all" or "any"`)}if(value["all"]!==void 0)problems.push(...checkStringList(value["all"],`${path}.all`));if(value["any"]!==void 0)problems.push(...checkStringList(value["any"],`${path}.any`));return problems}var ATTRIBUTE_FIELDS=new Set(["match","conditions"]);var CONDITION_FIELDS=new Set(["path","op","value"]);function isValueReference(value){return isPlainObject(value)&&("fromInput"in value||"fromClaims"in value)}function checkValueReference(value,path){const ref=value["fromInput"]??value["fromClaims"];return Object.keys(value).length===1&&isNonEmptyString(ref)?[]:[`${path} must be { fromInput: "<name>" } or { fromClaims: "<path>" }`]}var OPERATOR_VALUES={in:{accepts:Array.isArray,needs:"a list"},notIn:{accepts:Array.isArray,needs:"a list"},gt:{accepts:value=>typeof value==="number",needs:"a number"},gte:{accepts:value=>typeof value==="number",needs:"a number"},lt:{accepts:value=>typeof value==="number",needs:"a number"},lte:{accepts:value=>typeof value==="number",needs:"a number"},startsWith:{accepts:value=>typeof value==="string",needs:"a string"},endsWith:{accepts:value=>typeof value==="string",needs:"a string"},matches:{accepts:value=>typeof value==="string",needs:"a string"}};function checkConditionValue(op,condition,path){const value=condition["value"];if(value===void 0)return[`${path} is missing`];if(op==="exists"){return typeof value==="boolean"?[]:[`${path} must be true or false for "exists"`]}if(isValueReference(value))return checkValueReference(value,path);const expected=OPERATOR_VALUES[op];if(expected&&!expected.accepts(value))return[`${path} must be ${expected.needs} for "${op}"`];if(Array.isArray(value)&&value.length===0&&(op==="in"||op==="notIn"))return[`${path} is empty`];return[]}function checkAttributes(value,path){if(!isPlainObject(value))return[`${path} must be an object with "match" or "conditions"`];const problems=unknownFields(value,ATTRIBUTE_FIELDS,path);const{match,conditions}=value;if(match===void 0&&conditions===void 0){problems.push(`${path} needs "match" or "conditions"`)}if(match!==void 0){if(!isPlainObject(match))problems.push(`${path}.match must be an object`);else if(Object.keys(match).length===0)problems.push(`${path}.match is empty`);else{for(const[attribute,expected]of Object.entries(match)){const at=`${path}.match[${JSON.stringify(attribute)}]`;if(expected===void 0)problems.push(`${at} has no value`);else if(isValueReference(expected))problems.push(...checkValueReference(expected,at))}}}if(conditions!==void 0){if(!Array.isArray(conditions))problems.push(`${path}.conditions must be a list`);else if(conditions.length===0)problems.push(`${path}.conditions is empty`);else{conditions.forEach((condition,index)=>{const at=`${path}.conditions[${index}]`;if(!isPlainObject(condition)){problems.push(`${at} must be an object with "path" and "op"`);return}problems.push(...unknownFields(condition,CONDITION_FIELDS,at));if(!isNonEmptyString(condition["path"]))problems.push(`${at}.path must be a non-empty string`);const op=condition["op"];if(!ABAC_OPERATORS.has(op)){problems.push(`${at}.op "${String(condition["op"])}" is not an operator`)}else{problems.push(...checkConditionValue(op,condition,`${at}.value`))}})}}return problems}var RELATIONSHIP_FIELDS=new Set(["type","resource","resourceId"]);function checkResourceId(value,path){if(isNonEmptyString(value))return[];if(isPlainObject(value)&&Object.keys(value).length===1){const ref=value["fromInput"]??value["fromClaims"];if(isNonEmptyString(ref))return[]}return[`${path} must be a string, { fromInput } or { fromClaims }`]}function checkRelationship(value,path){if(!isPlainObject(value))return[`${path} must be an object with "type", "resource" and "resourceId"`];const problems=unknownFields(value,RELATIONSHIP_FIELDS,path);if(!isNonEmptyString(value["type"]))problems.push(`${path}.type must be a non-empty string`);if(!isNonEmptyString(value["resource"]))problems.push(`${path}.resource must be a non-empty string`);problems.push(...checkResourceId(value["resourceId"],`${path}.resourceId`));return problems}function checkRelationships(value,path){if(!Array.isArray(value))return checkRelationship(value,path);if(value.length===0)return[`${path} is empty`];return value.flatMap((relationship,index)=>checkRelationship(relationship,`${path}[${index}]`))}function checkRuleList(value,path){if(!Array.isArray(value))return[`${path} must be a list of rules`];if(value.length===0)return[`${path} is empty`];return value.flatMap((rule,index)=>checkRule(rule,`${path}[${index}]`))}function checkRule(rule,path){if(!isPlainObject(rule)){return[`${path||"the rule"} must be a rule object${typeof rule==="string"?", not a profile name":""}`]}const problems=unknownFields(rule,RULE_FIELDS,path);if(!CHECK_FIELDS.some(field=>rule[field]!==void 0)){problems.push(`${path?`${path} `:""}checks nothing`)}if(rule["operator"]!==void 0&&rule["operator"]!=="AND"&&rule["operator"]!=="OR"){problems.push(`${path}.operator must be "AND" or "OR"`)}if(rule["roles"]!==void 0)problems.push(...checkAllOrAny(rule["roles"],`${path}.roles`));if(rule["permissions"]!==void 0)problems.push(...checkAllOrAny(rule["permissions"],`${path}.permissions`));if(rule["attributes"]!==void 0)problems.push(...checkAttributes(rule["attributes"],`${path}.attributes`));if(rule["relationships"]!==void 0){problems.push(...checkRelationships(rule["relationships"],`${path}.relationships`))}if(rule["custom"]!==void 0){if(!isPlainObject(rule["custom"]))problems.push(`${path}.custom must be an object`);else if(Object.keys(rule["custom"]).length===0)problems.push(`${path}.custom is empty`)}if(rule["guards"]!==void 0){const guards=rule["guards"];if(!Array.isArray(guards))problems.push(`${path}.guards must be a list of functions`);else if(guards.length===0)problems.push(`${path}.guards is empty`);else if(!guards.every(guard=>typeof guard==="function")){problems.push(`${path}.guards must contain only functions`)}}if(rule["allOf"]!==void 0)problems.push(...checkRuleList(rule["allOf"],`${path}.allOf`));if(rule["anyOf"]!==void 0)problems.push(...checkRuleList(rule["anyOf"],`${path}.anyOf`));if(rule["not"]!==void 0)problems.push(...checkRule(rule["not"],`${path}.not`));return problems}function findAuthoritiesRuleProblems(authorities){if(typeof authorities==="string"){return authorities.length>0?[]:["the profile name is empty"]}if(Array.isArray(authorities)){if(authorities.length===0)return["the list of profiles is empty"];return authorities.every(isNonEmptyString)?[]:["a list of profiles must contain only non-empty profile names"]}if(!isPlainObject(authorities)){return["must be a profile name, a list of profile names, or a rule object"]}return checkRule(authorities,"")}function findAuthoritiesProfileProblems(name,policy){return checkRule(policy,"").map(problem=>`profile "${name}": ${problem.replace(/^\./,"")}`)}function mergeResult(base,...others){const evaluatedPolicies=[...base.evaluatedPolicies];for(const other of others){evaluatedPolicies.push(...other.evaluatedPolicies)}return{...base,evaluatedPolicies}}function invalidRule(problems,policies=[]){return{granted:false,deniedBy:`invalid authorities rule: ${problems.join("; ")}`,evaluatedPolicies:policies}}async function evaluateEvery(items,evaluateItem,evaluatedPolicies=[]){let unresolved;for(const item of items){const result=await evaluateItem(item);evaluatedPolicies.push(...result.evaluatedPolicies);if(result.granted)continue;if(!result.unresolved)return{...result,evaluatedPolicies};unresolved??=result}return unresolved?{...unresolved,evaluatedPolicies}:{granted:true,evaluatedPolicies}}var AuthoritiesEngine=class{constructor(profiles,evaluators){this.profiles=profiles;this.evaluators=evaluators}profiles;evaluators;ruleProblems=new WeakMap;findRuleProblems(authorities){const problems=findAuthoritiesRuleProblems(authorities);if(problems.length>0)return problems;const names=typeof authorities==="string"?[authorities]:Array.isArray(authorities)?authorities:[];return names.filter(name=>typeof name==="string"&&!this.profiles.has(name)).map(name=>`names an unknown profile "${name}"`)}findProfileProblems(){return Object.entries(this.profiles.getAll()).flatMap(([name,policy])=>findAuthoritiesProfileProblems(name,policy))}async evaluate(authorities,ctx){const problems=this.problemsOf(authorities,findAuthoritiesRuleProblems);if(problems.length>0)return invalidRule(problems);if(typeof authorities==="string"){return this.evaluateProfile(authorities,ctx)}if(Array.isArray(authorities)){return this.evaluateProfileArray(authorities,ctx)}return this.evaluatePolicy(authorities,ctx)}async evaluateProfile(name,ctx){const policy=this.profiles.resolve(name);if(!policy){return{granted:false,deniedBy:`profile '${name}' is not registered`,evaluatedPolicies:[`profile:${name}`]}}const problems=this.problemsOf(policy,rule=>findAuthoritiesProfileProblems(name,rule));if(problems.length>0)return invalidRule(problems,[`profile:${name}`]);const result=await this.evaluatePolicy(policy,ctx);if(!result.granted){return{...result,deniedBy:`profile:${name}: ${result.deniedBy}`,evaluatedPolicies:[`profile:${name}`,...result.evaluatedPolicies]}}return mergeResult(result,{granted:true,evaluatedPolicies:[`profile:${name}`]})}problemsOf(rule,find){if(typeof rule!=="object"||rule===null)return find(rule);let problems=this.ruleProblems.get(rule);if(!problems){problems=find(rule);this.ruleProblems.set(rule,problems)}return problems}async evaluateProfileArray(names,ctx){return evaluateEvery(names,name=>this.evaluateProfile(name,ctx))}async evaluatePolicy(policy,ctx){const operator=policy.operator??"AND";const results=[];if(policy.roles){results.push(evaluateRbacRoles(policy.roles,ctx))}if(policy.permissions){results.push(evaluateRbacPermissions(policy.permissions,ctx))}if(policy.attributes){results.push(evaluateAbac(policy.attributes,ctx))}if(policy.relationships){results.push(await evaluateRebac(policy.relationships,ctx))}if(policy.custom){results.push(await this.evaluateCustom(policy.custom,ctx))}if(policy.guards&&policy.guards.length>0){results.push(await this.evaluateGuards(policy.guards,ctx))}if(policy.allOf){results.push(await this.evaluateAllOf(policy.allOf,ctx))}if(policy.anyOf){results.push(await this.evaluateAnyOf(policy.anyOf,ctx))}if(policy.not){results.push(await this.evaluateNot(policy.not,ctx))}if(results.length===0){return invalidRule(["checks nothing"])}return this.combineResults(results,operator)}combineResults(results,operator){const allPolicies=results.flatMap(r=>r.evaluatedPolicies);if(operator==="AND"){const denied=results.find(r=>!r.granted&&!r.unresolved)??results.find(r=>!r.granted);if(denied){return{...denied,evaluatedPolicies:allPolicies}}return{granted:true,evaluatedPolicies:allPolicies}}const anyGranted=results.some(r=>r.granted);if(anyGranted){return{granted:true,evaluatedPolicies:allPolicies}}const unresolved=results.find(r=>r.unresolved);const reported=unresolved??results.find(r=>!r.granted);return{granted:false,...unresolved&&{unresolved:true},deniedBy:reported?.deniedBy??"all policies denied (OR)",evaluatedPolicies:allPolicies}}async evaluateCustom(custom,ctx){return evaluateEvery(Object.entries(custom),async([name,config])=>{const evaluator=this.evaluators.get(name);if(!evaluator){return{granted:false,deniedBy:`custom evaluator '${name}' is not registered`,evaluatedPolicies:[`custom.${name}`]}}const result=await evaluator.evaluate(config,ctx);return{...result,evaluatedPolicies:[`custom.${name}`,...result.evaluatedPolicies]}})}async evaluateGuards(guards,ctx){for(let i=0;i<guards.length;i++){const guard=guards[i];const result=await guard(ctx);if(result!==true){const denialMessage=typeof result==="string"?result:result===false?`guard[${i}] denied`:`guard[${i}] did not return true (returned ${result===null?"null":typeof result})`;return{granted:false,deniedBy:`guards[${i}]: ${denialMessage}`,denial:{kind:"custom",path:`guards[${i}]`},evaluatedPolicies:["guards"]}}}return{granted:true,evaluatedPolicies:["guards"]}}async evaluateAllOf(policies,ctx){return evaluateEvery(policies,policy=>this.evaluatePolicy(policy,ctx),["allOf"])}async evaluateAnyOf(policies,ctx){const allPolicies=["anyOf"];let lastDenied;let unresolved;for(const policy of policies){const result=await this.evaluatePolicy(policy,ctx);allPolicies.push(...result.evaluatedPolicies);if(result.granted){return{granted:true,evaluatedPolicies:allPolicies}}lastDenied=result;if(result.unresolved)unresolved??=result}const reported=unresolved??lastDenied;return{granted:false,...unresolved&&{unresolved:true},deniedBy:reported?.deniedBy??"no policies in anyOf",denial:reported?.denial??{kind:"anyOf",path:"anyOf"},evaluatedPolicies:allPolicies}}async evaluateNot(policy,ctx){const result=await this.evaluatePolicy(policy,ctx);if(!result.granted&&result.unresolved){return{...result,evaluatedPolicies:["not",...result.evaluatedPolicies]}}return{granted:!result.granted,deniedBy:result.granted?"not: inner policy was granted (negated to denied)":void 0,denial:result.granted?{kind:"not",path:"not"}:void 0,evaluatedPolicies:["not",...result.evaluatedPolicies]}}resolveProfile(name){return this.profiles.resolve(name)}};var AuthoritiesProfileRegistry=class{profiles=new Map;register(name,policy){this.profiles.set(name,policy)}registerAll(profiles){for(const[name,policy]of Object.entries(profiles)){this.profiles.set(name,policy)}}resolve(name){return this.profiles.get(name)}has(name){return this.profiles.has(name)}getAll(){const result={};for(const[name,policy]of this.profiles){result[name]=policy}return result}get size(){return this.profiles.size}remove(name){return this.profiles.delete(name)}clear(){this.profiles.clear()}};var AuthoritiesEvaluatorRegistry=class{evaluators=new Map;register(name,evaluator){this.evaluators.set(name,evaluator)}registerAll(evaluators){for(const[name,evaluator]of Object.entries(evaluators)){this.evaluators.set(name,evaluator)}}get(name){return this.evaluators.get(name)}has(name){return this.evaluators.has(name)}get size(){return this.evaluators.size}remove(name){return this.evaluators.delete(name)}clear(){this.evaluators.clear()}};import{brandClass,isBrandedInstance}from"@frontmcp/utils";var FORBIDDEN_CODE=-32003;var AuthorityDeniedError=class extends Error{static[Symbol.hasInstance](value){return isBrandedInstance(this,value)}mcpErrorCode=FORBIDDEN_CODE;statusCode=403;code="AUTHORITY_DENIED";entryType;entryName;deniedBy;denial;requiredScopes;constructor(params){const msg=params.message??`Access denied to ${params.entryType} "${params.entryName}": ${params.deniedBy}`;super(msg);this.name="AuthorityDeniedError";this.entryType=params.entryType;this.entryName=params.entryName;this.deniedBy=params.deniedBy;this.denial=params.denial;this.requiredScopes=params.requiredScopes}toJsonRpcError(){const data={entryType:this.entryType,entryName:this.entryName,deniedBy:this.deniedBy};if(this.denial!==void 0)data["denial"]=this.denial;if(this.requiredScopes!==void 0)data["requiredScopes"]=this.requiredScopes;return{code:this.mcpErrorCode,message:this.message,data}}};brandClass(AuthorityDeniedError,"@frontmcp/auth:AuthorityDeniedError");var bearerApplier={apply(token,_url,init){const headers=new Headers(init.headers);headers.set("Authorization",`Bearer ${token}`);return{init:{...init,headers}}}};function headerApplier(headerName,prefix){return{apply(token,_url,init){const headers=new Headers(init.headers);headers.set(headerName,prefix?`${prefix} ${token}`:token);return{init:{...init,headers}}}}}function queryApplier(paramName){return{apply(token,url,init){const urlObj=new URL(url);urlObj.searchParams.set(paramName,token);return{init,url:urlObj.toString()}}}}var basicApplier={apply(token,_url,init){const headers=new Headers(init.headers);const encoded=typeof btoa==="function"?btoa(token):Buffer.from(token).toString("base64");headers.set("Authorization",`Basic ${encoded}`);return{init:{...init,headers}}}};function isFrontMcpCredentials(creds){return typeof creds==="object"&&creds!==null&&"provider"in creds&&typeof creds.provider==="string"&&creds.provider.length>0}function providerHeadersCredentials(resolveHeaders){return{async applyCredentials(_url,init){const{credentials,...cleanInit}=init;if(!isFrontMcpCredentials(credentials)){return{init:typeof credentials==="string"?init:cleanInit}}const headers=new Headers(cleanInit.headers);for(const[name,value]of Object.entries(await resolveHeaders(credentials.provider))){headers.set(name,value)}return{init:{...cleanInit,headers}}}}}var FetchCredentialMiddleware=class{constructor(tokenAccessor,appliers){this.tokenAccessor=tokenAccessor;this.appliers=new Map(Object.entries(appliers??{}))}tokenAccessor;appliers;async applyCredentials(url,init){const creds=init.credentials;if(!creds||typeof creds==="string"){return{init}}if(!isFrontMcpCredentials(creds)){const{credentials:_removed2,...rest}=init;return{init:rest}}const token=await this.tokenAccessor.getToken(creds.provider);if(!token){const{credentials:_removed2,...rest}=init;return{init:rest}}const applier=this.appliers.get(creds.provider)??bearerApplier;const{credentials:_removed,...cleanInit}=init;return applier.apply(token,url,cleanInit)}};import{internalZ as z22}from"@frontmcp/lazy-zod";var MINUTE_MS=6e4;function milliseconds(field){return z22.number({message:`${field} must be a number of milliseconds`}).int(`${field} must be a whole number of milliseconds`).positive(`${field} must be greater than 0`)}function createPeopleAuthOptionsSchema(){const loopbackToken=z22.object({mode:z22.literal("loopback-token"),codeTtlMs:milliseconds("codeTtlMs").max(10*MINUTE_MS,"codeTtlMs must be at most 600000 (10 minutes): a sign-in code is for one hand-off").optional(),sessionTtlMs:milliseconds("sessionTtlMs").optional(),maxFailedAttempts:z22.number({message:"maxFailedAttempts must be a number"}).int("maxFailedAttempts must be a whole number").positive("maxFailedAttempts must be greater than 0").optional(),lockoutMs:milliseconds("lockoutMs").optional(),cookiePath:z22.string().startsWith("/",'cookiePath must start with "/"').optional()}).strict();return z22.union([z22.literal("loopback-token"),loopbackToken],{message:"http.auth.people must be 'loopback-token' or { mode: 'loopback-token', ... }"}).transform(value=>{const settings=typeof value==="string"?{}:value;return{mode:"loopback-token",codeTtlMs:settings.codeTtlMs??MINUTE_MS,sessionTtlMs:settings.sessionTtlMs??12*60*MINUTE_MS,maxFailedAttempts:settings.maxFailedAttempts??5,lockoutMs:settings.lockoutMs??MINUTE_MS,cookiePath:settings.cookiePath??"/"}})}var schema;function peopleAuthOptionsSchema(){schema??=createPeopleAuthOptionsSchema();return schema}import{base64urlEncode as base64urlEncode6,randomBytes as randomBytes11,sha256 as sha2563,timingSafeEqual as timingSafeEqual3}from"@frontmcp/utils";var PEOPLE_SESSION_COOKIE="frontmcp_people";var INVALID={ok:false,reason:"invalid"};var LOCKED={ok:false,reason:"locked"};var MAX_TRACKED_CLIENTS=1024;function newSecret(){return base64urlEncode6(randomBytes11(32))}function matches(presented,digest){return timingSafeEqual3(sha2563(presented),digest)}function unexpired(secrets){const now=Date.now();return secrets.filter(secret=>secret.expiresAt>now)}var LoopbackTokenAuth=class{constructor(options){this.options=options;const token=newSecret();this.token=token;this.tokenDigest=sha2563(token)}options;token;tokenDigest;codes=[];sessions=[];failures=new Map;takeToken(){const token=this.token;this.token=void 0;return token}createCode(options={}){const code=newSecret();const expiresAt=Date.now()+this.options.codeTtlMs;const{subject}=options;this.codes=[...unexpired(this.codes),{digest:sha2563(code),expiresAt,...subject?{subject}:{}}];return{code,expiresAt}}exchangeToken(presented,client){if(this.isLocked(client))return LOCKED;if(presented===void 0||!matches(presented,this.tokenDigest))return this.refuse(client);this.failures.delete(client);return{ok:true,...this.createCode()}}exchangeCode(presented,client){if(this.isLocked(client))return LOCKED;this.codes=unexpired(this.codes);const index=presented===void 0?-1:this.codes.findIndex(code=>matches(presented,code.digest));if(index===-1)return this.refuse(client);const[{subject}]=this.codes.splice(index,1);this.failures.delete(client);const cookieValue=newSecret();const expiresAt=Date.now()+this.options.sessionTtlMs;this.sessions=[...unexpired(this.sessions),{digest:sha2563(cookieValue),expiresAt,...subject?{subject}:{}}];return{ok:true,cookieValue,expiresAt}}verifySession(presented){if(!presented)return void 0;this.sessions=unexpired(this.sessions);const session=this.sessions.find(stored=>matches(presented,stored.digest));if(!session)return void 0;return session.subject?{expiresAt:session.expiresAt,subject:session.subject}:{expiresAt:session.expiresAt}}endSession(presented){if(!presented)return;this.sessions=this.sessions.filter(stored=>!matches(presented,stored.digest))}isLocked(client){return(this.failures.get(client)?.lockedUntil??0)>Date.now()}refuse(client){const now=Date.now();const{maxFailedAttempts,lockoutMs}=this.options;const previous=this.failures.get(client);const count=previous&&now-previous.lastAt<=lockoutMs?previous.count+1:1;const locked=count>=maxFailedAttempts;if(!previous&&this.failures.size>=MAX_TRACKED_CLIENTS)this.forgetIdleClients(now);this.failures.set(client,{count:locked?0:count,lastAt:now,lockedUntil:locked?now+lockoutMs:0});return locked?LOCKED:INVALID}forgetIdleClients(now){for(const[client,failures]of this.failures){if(failures.lockedUntil<=now&&now-failures.lastAt>this.options.lockoutMs)this.failures.delete(client)}}};function peopleSessionCookie(value,options){return{name:PEOPLE_SESSION_COOKIE,value,path:options.path,httpOnly:true,sameSite:"strict",secure:options.secure,maxAge:Math.floor(options.maxAgeMs/1e3)}}import{base64urlDecode as base64urlDecode8,base64urlEncode as base64urlEncode7,hkdfSha256 as hkdfSha2568,hmacSha256,randomBytes as randomBytes12,timingSafeEqual as timingSafeEqual4}from"@frontmcp/utils";var SECRET_BYTES=32;var BASE64URL_32_BYTES=/^[A-Za-z0-9_-]{43}$/;var KEY_SALT="frontmcp shared server";var PROOF_LABEL="frontmcp shared server identity v1";var encoder=new TextEncoder;function createSharedServerSecret(){return base64urlEncode7(randomBytes12(SECRET_BYTES))}function createSharedServerNonce(){return base64urlEncode7(randomBytes12(SECRET_BYTES))}function isBase64url32Bytes(value){return typeof value==="string"&&BASE64URL_32_BYTES.test(value)}function isSharedServerNonce(value){return isBase64url32Bytes(value)}function deriveSharedServerKeys(secret){if(!isBase64url32Bytes(secret))throw new TypeError("a shared server secret is 32 bytes of base64url");const bytes=base64urlDecode8(secret);const salt=encoder.encode(KEY_SALT);return{proofKey:hkdfSha2568(bytes,salt,encoder.encode("proof"),32),token:base64urlEncode7(hkdfSha2568(bytes,salt,encoder.encode("token"),32))}}function proofBytes(proofKey,{endpoint,nonce,version}){return hmacSha256(proofKey,encoder.encode(JSON.stringify([PROOF_LABEL,endpoint,nonce,version])))}function proveSharedServerIdentity(proofKey,statement){return base64urlEncode7(proofBytes(proofKey,statement))}function verifySharedServerIdentity(proofKey,statement,proof){if(!isBase64url32Bytes(proof))return false;const expected=proofBytes(proofKey,statement);const presented=base64urlDecode8(proof);return presented.length===expected.length&&timingSafeEqual4(presented,expected)}export{AUTH_PROVIDERS_ACCESSOR,AUTH_PROVIDERS_REGISTRY,AUTH_PROVIDERS_VAULT,AppAuthState,AudienceValidator,AuthInternalError,AuthInvalidInputError,AuthProvidersAccessorImpl,AuthProvidersNotConfiguredError,AuthProvidersRegistry,AuthProvidersVault,AuthoritiesContextBuilder,AuthoritiesEngine,AuthoritiesEvaluatorRegistry,AuthoritiesProfileRegistry,AuthorityDeniedError,AuthorizationBase,BUILT_IN_AUTH_PAGE_CSP,CDN,CLIENT_ASSERTION_SIGNING_ALGORITHMS,CLIENT_ASSERTION_TYPE_JWT_BEARER,CREDENTIALS_ACCESSOR,CimdClientAssertionError,CimdClientIdMismatchError,CimdDisabledError,CimdError,CimdFetchError,CimdResponseTooLargeError,CimdSecurityError,CimdService,CimdValidationError,CredentialCache,CredentialProviderAlreadyRegisteredError,CredentialStorageError,CredentialsAccessorImpl,DEFAULT_ALLOWED_SCOPES,DEFAULT_INCREMENTAL_TICKET_TTL_MS,DEFAULT_RESUME_TTL_MS,DEFAULT_THEME,DcrClientRegistry,EagerCredentialLoader,ElicitationSecretRequiredError,EncryptedRedisVault,EncryptedStorageError,EncryptedStorageSecureStoreBackend,EncryptedTypedStorage,EncryptionContextNotSetError,EncryptionKeyNotConfiguredError,FetchCredentialMiddleware,FrontMcpAuthContextImpl,InMemoryAuthorizationStore,InMemoryAuthorizationVault,InMemoryCimdCache,InMemoryConsentStore,InMemoryFederatedAuthSessionStore,InMemoryOrchestratedTokenStore,InMemoryStoreRequiredError,InvalidClientIdUrlError,JwksService,JwtSecretRequiredError,JwtSecretWeakError,LazyCredentialLoader,LocalTokenKeys,LoopbackTokenAuth,MAX_CLIENT_ASSERTION_LIFETIME_SECONDS,NoProviderIdError,NullOrchestratedAuthAccessor,ORCHESTRATED_AUTH_ACCESSOR,OrchestratedAuthAccessorAdapter,OrchestratedAuthNotAvailableError,OrchestratedAuthorization,OrchestratorJwksNotAvailableError,PEOPLE_SESSION_COOKIE,PublicAuthorization,RedirectUriMismatchError,RedisAuthorizationStore,RedisSessionStore,SECURE_STORE_ACCESSOR,SIGNIN_BINDING_COOKIE_PREFIX,SIGNIN_BINDING_MAX_AGE_SECONDS,SIGNIN_BINDING_SECURE_COOKIE_PREFIX,ScopeDeniedError,SecureStoreAccessorImpl,SecureStoreConfigError,SessionCredentialVault,SessionIdEmptyError,SessionRateLimiter,SessionSecretRequiredError,SigningKeyInvalidError,StorageAuthorizationStore,StorageAuthorizationVault,StorageConsentStore,StorageFederatedAuthSessionStore,StorageOrchestratedTokenStore,StorageTokenStore,TinyTtlCache,TokenLeakDetectedError,TokenNotAvailableError,TokenStoreRequiredError,TokenVault,TransparentAuthorization,TransportIdGenerator,TypedStorage4 as TypedStorage,UNIDENTIFIED_SESSION_PREFIX,UpstreamClientRegistrationError,VaultEncryption,VaultLoadError,VaultNotFoundError,VercelKvSessionStore,abacConditionSchema,abacOperatorSchema,abacPolicySchema,advertisedScopes,aiPlatformTypeSchema,allowsPublicAccess,anonymousCallerClaims,apiKeyCredentialSchema,appAuthOptionsSchema,appAuthStateSchema,appAuthorizationRecordSchema,appCredentialSchema,appRequiresOrchestration,authLayout,authModeSchema,authOptionsSchema,authProviderDetectionResultSchema,authProviderMappingSchema,authProvidersVaultOptionsSchema,authUserSchema,authenticateFnSchema,authoritiesClaimsMappingSchema,authoritiesConfigSchema,authoritiesMetadataSchema,authoritiesPolicySchema,authorizationCodeRecordSchema,authorizationVaultEntrySchema,authorizedPromptSchema,authorizedToolSchema,baseLayout,basicApplier,basicAuthCredentialSchema,bearerApplier,bearerCredentialSchema,buildAuthContext,buildCodeRecord,buildConnectPage,buildConnectSuccessPage,buildConsentPage,buildCredentialResumeUrl,buildErrorPage,buildFederatedLoginPage,buildFederatedStepPage,buildIncrementalAuthPage,buildInsufficientScopeHeader,buildInvalidRequestHeader,buildInvalidTokenHeader,buildLoginPage,buildPendingRecord,buildPrmUrl,buildRefreshTokenRecord,buildToolConsentPage,buildUnauthorizedHeader,buildWwwAuthenticate,builtInAuthPageHeaders,centeredCardLayout,checkSsrfProtection,cimdCacheConfigSchema,cimdConfigSchema,cimdNetworkConfigSchema,cimdSecurityConfigSchema,clientMetadataDocumentSchema,completeCurrentProvider,consentConfigSchema,consentRecordKey,consentSelectionSchema,consentStateSchema,consentToolItemSchema,createAudienceValidator,createEncryptedVault,createFederatedAuthSession,createLayout,createSecureStore,createSessionCredentialVault,createSharedServerNonce,createSharedServerSecret,createSigninBinding,createTokenStorageAdapter,credentialProviderConfigSchema,credentialSchema,credentialScopeSchema,credentialTypeSchema,customCredentialSchema,decodeJwtPayloadSafe,decryptAesGcm6 as decryptAesGcm,decryptSessionJson,decryptValue2 as decryptValue,defaultSessionRateLimiter,deriveAuthorizationId,deriveExpectedAudience,deriveProviderId,deriveSharedServerKeys,deriveTypedUser,detectAuthProviders,detectedAuthProviderSchema,encryptAesGcm6 as encryptAesGcm,encryptJson,encryptValue2 as encryptValue,encryptedBlobSchema,encryptedDataSchema,encryptedVaultEntrySchema,escapeHtml2 as escapeHtml,extraWideLayout,extractBearerToken,extractCacheHeaders,extractCredentialExpiry,federatedAuthConfigSchema,federatedLoginStateSchema,federatedProviderItemSchema,federatedSelectionSchema,findAuthoritiesProfileProblems,findAuthoritiesRuleProblems,flatRemoteProviderFields,fromSessionRecord,generateAuthorizationCode,generatePkceChallenge,generateRefreshTokenValue,getCredentialOptionsSchema,getKey,getNextProvider,getProviderApps,getProviderScopes,getTokenSignatureFingerprint,grantScopes,hasOnlyLocalhostRedirectUris,headerApplier,hkdfSha2565 as hkdfSha256,incrementalAuthConfigSchema,isAnonymousSubject,isCimdClientId,isFrontMcpCredentials,isJwt,isLocalMode,isLoopbackRedirectUri,isOrchestratedLocal,isOrchestratedMode,isOrchestratedRemote,isPersistentTokenStorage,isPublicMode,isRedisTokenStorage,isRemoteMode,isSessionComplete,isSessionSecretConfigured,isSharedServerNonce,isSignedData as isSignedSession,isSoonExpiring,isSoonExpiringProvider,isSqliteTokenStorage,isStaticMode,isTransparentMode,jsonWebKeySetSchema,jwkParametersSchema,jwkSchema,legacySseTransportStateSchema,llmSafeAuthContextSchema,loadingStrategySchema,localAuthSchema,localDcrClientSchema,localDcrConfigSchema,localSigningConfigSchema,loginConfigSchema,mtlsCredentialSchema,noopLogger,normalizeIssuer,oauthCredentialSchema,openIncrementalAuthTicketProfile,openPendingLogin,orchestratedLocalSchema,orchestratedRemoteSchema,parseAuthOptions,parseCacheHeaders,parseWwwAuthenticate,pendingIncrementalAuthSchema,peopleAuthOptionsSchema,peopleSessionCookie,pkceChallengeSchema,pkceOAuthCredentialSchema,privateKeyCredentialSchema,progressiveAuthStateSchema,proveSharedServerIdentity,providerConfigSchema,providerHeadersCredentials,publicAccessConfigSchema,publicAuthOptionsSchema,queryApplier,rbacPermissionsPolicySchema,rbacRolesPolicySchema,rebacPolicySchema,redisConfigSchema,redisVaultEntrySchema,remoteAuthSchema,remoteProviderConfigSchema,renderLocalLoginPage,renderToHtml,resetCachedKey,resolveRequiredScopes,resolveUpstreamClient,resourceIdRefSchema,resourceScopesFor,safeDecrypt,scopesFromClaims,sealPendingLogin,secureStoreConfigSchema,secureStoreCustomBackendSchema,secureStoreEncryptionSchema,secureStoreScopeSchema,serviceAccountCredentialSchema,sessionIdPayloadSchema,sessionJwtPayloadSchema,signCredentialResumeToken,signIncrementalAuthTicket,signSession,signinBindingCookieName,signinBindingMatches,skippedAppBehaviorSchema,sseTransportStateSchema,sshKeyCredentialSchema,startNextProvider,statefulHttpTransportStateSchema,statelessHttpTransportStateSchema,staticAuthOptionsSchema,storedSessionSchema,streamableHttpTransportStateSchema,toEpochSeconds,toLoginExtraFields,toSessionRecord,tokenRefreshConfigSchema,tokenStorageConfigSchema,tokenStorageSqliteSchema,transparentAuthOptionsSchema,transportProtocolSchema,transportSessionSchema,transportStateSchema,trimSlash,tryJwtExp,upstreamProviderSchema,userClaimSchema,validateAudience,validateClientIdUrl,vaultConsentRecordSchema,vaultFederatedRecordSchema,verifyCredentialResumeToken,verifyIncrementalAuthTicket,verifyOrParseSession,verifyPkce,verifySession,verifySharedServerIdentity,wideLayout,withoutSecretFieldValues};
|