@jhzhu89/azure-client-pool
Version:
Azure client lifecycle management with intelligent caching and authentication
1,014 lines (996 loc) • 33.3 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AuthMode: () => AuthMode,
CredentialType: () => CredentialType,
McpRequestMapper: () => McpRequestMapper,
createClientProvider: () => createClientProvider,
createClientProviderWithMapper: () => createClientProviderWithMapper,
getLogger: () => getLogger,
setRootLogger: () => setRootLogger
});
module.exports = __toCommonJS(index_exports);
// src/types.ts
var AuthMode = {
Application: "application",
Delegated: "delegated",
Composite: "composite"
};
var CredentialType = {
Application: "application",
Delegated: "delegated"
};
var ApplicationAuthStrategy = {
Cli: "cli",
ManagedIdentity: "mi",
Chain: "chain"
};
// src/utils/cache.ts
var import_ttlcache = __toESM(require("@isaacs/ttlcache"));
var import_crypto = require("crypto");
// src/utils/logging.ts
var import_pino = require("pino");
function createPinoAdapter(pinoLogger) {
return {
debug: (message, context) => pinoLogger.debug(context, message),
info: (message, context) => pinoLogger.info(context, message),
warn: (message, context) => pinoLogger.warn(context, message),
error: (message, context) => pinoLogger.error(context, message),
child: (context) => createPinoAdapter(pinoLogger.child(context))
};
}
var rootLogger;
function initializeLogger() {
const pinoLogger = (0, import_pino.pino)({
level: process.env.LOG_LEVEL || "info",
...process.env.NODE_ENV === "development" && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "HH:MM:ss",
ignore: "pid,hostname"
}
}
}
});
return createPinoAdapter(pinoLogger);
}
function getRootLogger() {
if (!rootLogger) {
rootLogger = initializeLogger();
}
return rootLogger;
}
function getLogger(component) {
const logger8 = getRootLogger();
return component ? logger8.child?.({ component }) || logger8 : logger8;
}
function setRootLogger(logger8) {
rootLogger = logger8;
}
// src/utils/cache.ts
var logger = getLogger("cache-manager");
var CACHE_KEY_TRUNCATE_LENGTH = 50;
function hasDispose(obj) {
return obj != null && typeof obj === "object" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
(typeof obj.dispose === "function" || // eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof obj[Symbol.asyncDispose] === "function" || // eslint-disable-next-line @typescript-eslint/no-explicit-any
typeof obj[Symbol.dispose] === "function");
}
var CacheManager = class {
constructor(config, cacheType) {
this.cacheType = cacheType;
this.pendingRequests = /* @__PURE__ */ new Map();
this.absoluteTtl = config.absoluteTtl;
this.slidingTtl = config.slidingTtl;
this.cache = new import_ttlcache.default({
max: config.maxSize,
ttl: config.slidingTtl,
updateAgeOnGet: true,
checkAgeOnGet: true,
dispose: (entry, key, reason) => {
this.disposeEntry(entry, key, reason).catch((error) => {
logger.error(`Error disposing ${this.cacheType} cache entry`, {
cacheType: this.cacheType,
cacheKey: this.createLoggableKey(key),
error: error instanceof Error ? error.message : String(error),
reason
});
});
}
});
}
cache;
pendingRequests;
absoluteTtl;
slidingTtl;
async disposeEntry(entry, cacheKey, reason) {
if (!hasDispose(entry.value)) {
return;
}
try {
logger.debug(`Disposing ${this.cacheType} cache entry`, {
cacheType: this.cacheType,
cacheKey: this.createLoggableKey(cacheKey),
reason,
valueType: entry.value?.constructor?.name || "Unknown"
});
const value = entry.value;
if (value[Symbol.asyncDispose]) {
await value[Symbol.asyncDispose]();
} else if (value[Symbol.dispose]) {
value[Symbol.dispose]();
} else if (value.dispose) {
await value.dispose();
}
logger.debug(`${this.cacheType} cache entry disposed successfully`, {
cacheType: this.cacheType,
cacheKey: this.createLoggableKey(cacheKey),
reason
});
} catch (error) {
logger.warn(
`${this.cacheType} cache entry dispose failed, continuing cleanup`,
{
cacheType: this.cacheType,
cacheKey: this.createLoggableKey(cacheKey),
error: error instanceof Error ? error.message : String(error),
reason
}
);
}
}
createCacheEntry(value) {
const entry = { value };
if (this.absoluteTtl) {
entry.absoluteExpiresAt = Date.now() + this.absoluteTtl;
}
return entry;
}
async getOrCreate(cacheKey, factory, contextInfo, customTtl) {
const cached = this.cache.get(cacheKey);
if (cached) {
if (cached.absoluteExpiresAt && cached.absoluteExpiresAt <= Date.now()) {
return this.createInternal(cacheKey, factory, contextInfo, customTtl);
}
logger.debug(`${this.cacheType} cache hit`, {
cacheKey: this.createLoggableKey(cacheKey),
...contextInfo
});
return cached.value;
}
const existingPromise = this.pendingRequests.get(cacheKey);
if (existingPromise) {
logger.debug(`Found pending ${this.cacheType} request`, {
cacheKey: this.createLoggableKey(cacheKey),
...contextInfo
});
return existingPromise;
}
logger.debug(`${this.cacheType} cache miss, creating new entry`, {
cacheKey: this.createLoggableKey(cacheKey),
...contextInfo
});
const promise = this.createInternal(
cacheKey,
factory,
contextInfo,
customTtl
);
this.pendingRequests.set(cacheKey, promise);
try {
return await promise;
} finally {
this.pendingRequests.delete(cacheKey);
}
}
async createInternal(cacheKey, factory, contextInfo, customTtl) {
const value = await factory();
const entry = this.createCacheEntry(value);
const ttl = customTtl ?? this.slidingTtl;
this.cache.set(cacheKey, entry, { ttl });
logger.debug(`${this.cacheType} entry created and cached`, {
cacheKey: this.createLoggableKey(cacheKey),
slidingTTL: ttl,
...contextInfo
});
return value;
}
clear() {
this.cache.clear();
this.pendingRequests.clear();
logger.debug(`${this.cacheType} cache cleared`);
}
delete(cacheKey) {
const deleted = this.cache.delete(cacheKey);
logger.debug(`${this.cacheType} cache entry removed`, {
cacheKey: this.createLoggableKey(cacheKey),
deleted,
cacheSize: this.cache.size
});
return deleted;
}
getStats() {
return {
size: this.cache.size,
maxSize: this.cache.max,
pendingRequests: this.pendingRequests.size
};
}
createLoggableKey(rawKey) {
return rawKey.length > CACHE_KEY_TRUNCATE_LENGTH ? rawKey.substring(0, CACHE_KEY_TRUNCATE_LENGTH) + "..." : rawKey;
}
};
function createStableCacheKey(rawKey) {
return (0, import_crypto.createHash)("md5").update(rawKey, "utf8").digest("base64url");
}
// src/client-pool/pool.ts
var logger2 = getLogger("client-pool");
var ClientPool = class {
constructor(clientFactory, credentialManager, config) {
this.clientFactory = clientFactory;
this.credentialManager = credentialManager;
this.config = config;
this.clientCache = new CacheManager(
{
maxSize: config.clientCache.maxSize,
slidingTtl: config.clientCache.slidingTtl
},
"client-pool"
);
}
clientCache;
async getClient(authContext, options) {
const rawCacheKey = this.generateRawCacheKey(authContext, options);
const cacheKey = createStableCacheKey(rawCacheKey);
logger2.debug("Getting client from cache", {
rawCacheKey
});
let customTtl;
if (authContext.mode !== AuthMode.Application) {
const tokenContext = authContext;
const now = Date.now();
const tokenRemainingTime = tokenContext.expiresAt - now;
const bufferMs = this.config.clientCache.bufferMs;
customTtl = Math.max(tokenRemainingTime - bufferMs, 0);
logger2.debug("Using dynamic TTL for token-based auth", {
tokenExpiresAt: new Date(tokenContext.expiresAt).toISOString(),
tokenRemainingTime: Math.floor(tokenRemainingTime / 1e3),
bufferMs: Math.floor(bufferMs / 1e3),
customTtl: Math.floor(customTtl / 1e3)
});
}
return this.clientCache.getOrCreate(
cacheKey,
async () => {
logger2.debug("Creating new client", {
authMode: authContext.mode,
userObjectId: "userObjectId" in authContext ? authContext.userObjectId : void 0,
tenantId: "tenantId" in authContext ? authContext.tenantId : void 0
});
const credentialProvider = {
getCredential: (authType) => this.credentialManager.getCredential(authContext, authType)
};
return this.clientFactory.createClient(credentialProvider, options);
},
{
authMode: authContext.mode,
userObjectId: "userObjectId" in authContext ? authContext.userObjectId : void 0,
tenantId: "tenantId" in authContext ? authContext.tenantId : void 0
},
customTtl
);
}
generateRawCacheKey(authContext, options) {
const parts = [this.config.cacheKeyPrefix, authContext.mode];
if (authContext.mode !== AuthMode.Application) {
const tokenContext = authContext;
parts.push(
tokenContext.tenantId || "unknown",
tokenContext.userObjectId || "unknown"
);
}
const clientFingerprint = this.clientFactory.getClientFingerprint?.(options);
if (clientFingerprint) {
parts.push(clientFingerprint);
} else if (options !== void 0) {
const optionsHash = createStableCacheKey(JSON.stringify(options));
parts.push(optionsHash);
}
return parts.join("::");
}
async clearCache() {
this.clientCache.clear();
logger2.debug("Client pool cache cleared");
}
async removeCachedClient(authContext, options) {
const rawCacheKey = this.generateRawCacheKey(authContext, options);
const cacheKey = createStableCacheKey(rawCacheKey);
const removed = this.clientCache.delete(cacheKey);
logger2.debug("Removed specific client from cache", {
rawCacheKey,
removed
});
return removed;
}
getCacheStats() {
return this.clientCache.getStats();
}
};
// src/credentials/application-strategy.ts
var import_identity = require("@azure/identity");
var logger3 = getLogger("application-strategy");
var ApplicationCredentialStrategy = class {
constructor(config) {
this.config = config;
}
createCredential() {
logger3.debug("Creating application credential", {
strategy: this.config.strategy,
hasManagedIdentityClientId: !!this.config.managedIdentityClientId
});
switch (this.config.strategy) {
case ApplicationAuthStrategy.Cli:
logger3.debug("Using Azure CLI credential");
return new import_identity.AzureCliCredential();
case ApplicationAuthStrategy.ManagedIdentity:
logger3.debug("Using Managed Identity credential", {
clientId: this.config.managedIdentityClientId
});
return new import_identity.ManagedIdentityCredential(
this.config.managedIdentityClientId ? { clientId: this.config.managedIdentityClientId } : void 0
);
case ApplicationAuthStrategy.Chain:
logger3.debug("Using chained credential (CLI -> ManagedIdentity)", {
clientId: this.config.managedIdentityClientId
});
return new import_identity.ChainedTokenCredential(
new import_identity.AzureCliCredential(),
new import_identity.ManagedIdentityCredential(
this.config.managedIdentityClientId ? { clientId: this.config.managedIdentityClientId } : void 0
)
);
default:
logger3.error("Unsupported application strategy", {
strategy: this.config.strategy
});
throw new Error(
`Unsupported application strategy: ${this.config.strategy}`
);
}
}
};
// src/credentials/delegated-strategy.ts
var import_identity2 = require("@azure/identity");
var logger4 = getLogger("delegated-strategy");
var DelegatedCredentialStrategy = class {
constructor(config) {
this.config = config;
this.usesCertificate = !!config.certificatePath;
if (!this.usesCertificate && !config.clientSecret) {
throw new Error(
"Azure authentication requires either client certificate path or client secret"
);
}
if (this.usesCertificate && config.clientSecret) {
throw new Error(
"Only one of certificatePath or clientSecret should be provided"
);
}
}
usesCertificate;
createOBOCredential(context) {
const now = Date.now();
if (context.expiresAt <= now) {
const expiredAt = new Date(context.expiresAt).toISOString();
logger4.error("User assertion token has expired", {
tenantId: context.tenantId,
userObjectId: context.userObjectId,
expiredAt
});
throw new Error(
`User assertion token expired at ${expiredAt}. Please refresh the token and try again.`
);
}
logger4.debug("Creating OBO credential", {
tenantId: context.tenantId,
clientId: this.config.clientId,
usesCertificate: this.usesCertificate
});
const baseOptions = {
tenantId: context.tenantId,
clientId: this.config.clientId,
userAssertionToken: context.accessToken
};
if (this.usesCertificate) {
logger4.debug("Using certificate-based OBO credential");
return new import_identity2.OnBehalfOfCredential(
this.createCertificateOptions(baseOptions)
);
}
logger4.debug("Using secret-based OBO credential");
return new import_identity2.OnBehalfOfCredential(this.createSecretOptions(baseOptions));
}
createCertificateOptions(baseOptions) {
if (!this.config.certificatePath) {
logger4.error(
"Certificate path is missing for certificate-based authentication"
);
throw new Error(
"Certificate path is required for certificate-based authentication"
);
}
logger4.debug("Building certificate options", {
certificatePath: this.config.certificatePath
});
return {
...baseOptions,
certificatePath: this.config.certificatePath
};
}
createSecretOptions(baseOptions) {
if (!this.config.clientSecret) {
logger4.error("Client secret is missing for secret-based authentication");
throw new Error(
"Client secret is required for secret-based authentication"
);
}
logger4.debug("Building secret options");
return {
...baseOptions,
clientSecret: this.config.clientSecret
};
}
};
// src/credentials/credential-factory.ts
var CredentialFactory = class {
applicationStrategy;
delegatedStrategy;
constructor(applicationConfig, delegatedConfig) {
this.applicationStrategy = new ApplicationCredentialStrategy(
applicationConfig
);
this.delegatedStrategy = new DelegatedCredentialStrategy(delegatedConfig);
}
createApplicationCredential() {
return this.applicationStrategy.createCredential();
}
createDelegatedCredential(context) {
return this.delegatedStrategy.createOBOCredential(context);
}
};
// src/credentials/manager.ts
var logger5 = getLogger("credential-manager");
var CredentialManager = class {
constructor(applicationConfig, delegatedConfig, config) {
this.config = config;
this.credentialFactory = new CredentialFactory(
applicationConfig,
delegatedConfig
);
this.applicationCredentialCache = this.createCredentialCache(
"application-credential"
);
}
applicationCredentialCache;
credentialFactory;
createCredentialCache(cacheType) {
return new CacheManager(
{
maxSize: this.config.credentialCache.maxSize,
slidingTtl: this.config.credentialCache.slidingTtl,
absoluteTtl: this.config.credentialCache.absoluteTtl
},
cacheType
);
}
async getCredential(authContext, authType) {
switch (authType) {
case CredentialType.Application:
return this.getApplicationCredential();
case CredentialType.Delegated:
return this.getDelegatedCredential(authContext);
default:
throw new Error(`Unsupported auth type: ${authType}`);
}
}
async getApplicationCredential() {
const rawCacheKey = this.createApplicationRawCacheKey();
const cacheKey = createStableCacheKey(rawCacheKey);
logger5.debug("Getting application credential from cache", {
rawCacheKey
});
return this.applicationCredentialCache.getOrCreate(
cacheKey,
async () => this.credentialFactory.createApplicationCredential(),
{ authType: CredentialType.Application }
);
}
async getDelegatedCredential(authContext) {
const tokenContext = this.validateAndGetTokenContext(authContext);
const now = Date.now();
if (tokenContext.expiresAt <= now) {
const expiredAt = new Date(tokenContext.expiresAt).toISOString();
logger5.error("User assertion token has expired", {
tenantId: tokenContext.tenantId,
userObjectId: tokenContext.userObjectId,
expiredAt
});
throw new Error(
`User assertion token expired at ${expiredAt}. Please refresh the token and try again.`
);
}
logger5.debug("Creating delegated credential without caching", {
tenantId: tokenContext.tenantId,
userObjectId: tokenContext.userObjectId,
tokenExpiresAt: new Date(tokenContext.expiresAt).toISOString()
});
return this.credentialFactory.createDelegatedCredential(tokenContext);
}
validateAndGetTokenContext(authContext) {
if (authContext.mode === AuthMode.Application) {
throw new Error(
"Cannot provide delegated credentials with ApplicationAuthContext"
);
}
return authContext;
}
createApplicationRawCacheKey() {
const parts = [this.config.cacheKeyPrefix, CredentialType.Application];
return parts.join("::");
}
clearCache() {
this.applicationCredentialCache.clear();
logger5.debug("Application credential cache cleared");
}
getCacheStats() {
return {
applicationCredentials: this.applicationCredentialCache.getStats()
};
}
};
// src/auth/context.ts
var AuthContextFactory = {
application: () => ({
mode: AuthMode.Application
}),
delegated: (token) => ({
mode: AuthMode.Delegated,
tenantId: token.tenantId,
userObjectId: token.userObjectId,
accessToken: token.rawToken,
expiresAt: token.expiresAt
}),
composite: (token) => ({
mode: AuthMode.Composite,
tenantId: token.tenantId,
userObjectId: token.userObjectId,
accessToken: token.rawToken,
expiresAt: token.expiresAt
})
};
// src/auth/jwt/validator.ts
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
var import_jwks_rsa = __toESM(require("jwks-rsa"));
// src/auth/jwt/token.ts
var ParsedJwtToken = class {
constructor(claims, rawToken) {
this.claims = claims;
this.rawToken = rawToken;
}
get userObjectId() {
return this.claims.userObjectId;
}
get tenantId() {
return this.claims.tenantId;
}
get expiresAt() {
return this.claims.expiresAt;
}
};
// src/utils/errors.ts
var AUTH_ERROR_CODES = {
jwt_validation_failed: "jwt_validation_failed",
token_exchange_failed: "token_exchange_failed",
invalid_access_token: "invalid_access_token"
};
var AuthError = class _AuthError extends Error {
constructor(code, message, context) {
super(message);
this.code = code;
this.context = context;
this.name = "AuthError";
this.timestamp = /* @__PURE__ */ new Date();
if (Error.captureStackTrace) {
Error.captureStackTrace(this, _AuthError);
}
}
timestamp;
};
// src/auth/jwt/validator.ts
var logger6 = getLogger("jwt-validator");
var JwtHandler = class {
jwksClient;
config;
constructor(config) {
this.config = {
clientId: config.clientId,
tenantId: config.tenantId,
audience: config.audience ?? config.clientId,
issuer: config.issuer ?? `https://sts.windows.net/${config.tenantId}/`,
clockTolerance: config.clockTolerance
};
this.jwksClient = (0, import_jwks_rsa.default)({
jwksUri: `https://login.microsoftonline.com/${config.tenantId}/discovery/v2.0/keys`,
cache: true,
cacheMaxAge: config.cacheMaxAge,
rateLimit: true,
jwksRequestsPerMinute: config.jwksRequestsPerMinute
});
}
async validateToken(accessToken) {
try {
logger6.debug("Validating JWT token", { tenantId: this.config.tenantId });
const payload = await this.verifyToken(accessToken);
const claims = this.extractClaims(payload);
logger6.debug("JWT token validated", {
tenantId: claims.tenantId,
userObjectId: claims.userObjectId,
expiresAt: new Date(claims.expiresAt).toISOString()
});
return new ParsedJwtToken(claims, accessToken);
} catch (error) {
logger6.error("Token validation failed", {
tenantId: this.config.tenantId,
error: error instanceof Error ? error.message : String(error)
});
throw new AuthError(
AUTH_ERROR_CODES.jwt_validation_failed,
`Token validation failed: ${error instanceof Error ? error.message : error}`
);
}
}
async isValid(accessToken) {
try {
await this.validateToken(accessToken);
return true;
} catch {
return false;
}
}
verifyToken(accessToken) {
return new Promise((resolve, reject) => {
const getKey = (header, callback) => {
this.jwksClient.getSigningKey(header.kid, (err, key) => {
callback(err, key?.getPublicKey());
});
};
import_jsonwebtoken.default.verify(
accessToken,
getKey,
{
audience: this.config.audience,
issuer: this.config.issuer,
algorithms: ["RS256"],
clockTolerance: this.config.clockTolerance
},
(err, decoded) => {
if (err) return reject(err);
const payload = decoded;
if (payload.tid !== this.config.tenantId) {
return reject(new Error("Invalid tenant"));
}
resolve(payload);
}
);
});
}
extractClaims(payload) {
if (!payload.oid || !payload.tid || !payload.exp) {
throw new Error("Missing required claims");
}
return {
userObjectId: payload.oid,
tenantId: payload.tid,
expiresAt: payload.exp * 1e3
};
}
};
// src/config/configuration.ts
var logger7 = getLogger("configuration");
function loadRawEnvironmentConfig() {
logger7.debug("Loading environment configuration");
const nodeEnv = process.env.NODE_ENV || "development";
logger7.debug("Configuration loaded", {
nodeEnv,
tenantId: process.env.AZURE_TENANT_ID
});
return {
azure: {
clientId: process.env.AZURE_CLIENT_ID || "",
...process.env.AZURE_CLIENT_SECRET && {
clientSecret: process.env.AZURE_CLIENT_SECRET
},
...process.env.AZURE_CLIENT_CERTIFICATE_PATH && {
certificatePath: process.env.AZURE_CLIENT_CERTIFICATE_PATH
},
...process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD && {
certificatePassword: process.env.AZURE_CLIENT_CERTIFICATE_PASSWORD
},
tenantId: process.env.AZURE_TENANT_ID || "",
...process.env.AZURE_MANAGED_IDENTITY_CLIENT_ID && {
managedIdentityClientId: process.env.AZURE_MANAGED_IDENTITY_CLIENT_ID
},
...process.env.AZURE_APPLICATION_AUTH_STRATEGY && {
applicationAuthStrategy: process.env.AZURE_APPLICATION_AUTH_STRATEGY
}
},
jwt: {
...process.env.JWT_AUDIENCE && { audience: process.env.JWT_AUDIENCE },
...process.env.JWT_ISSUER && { issuer: process.env.JWT_ISSUER },
clockTolerance: parseInt(process.env.JWT_CLOCK_TOLERANCE || "300"),
cacheMaxAge: parseInt(process.env.JWT_CACHE_MAX_AGE || "86400000"),
jwksRequestsPerMinute: parseInt(
process.env.JWKS_REQUESTS_PER_MINUTE || "10"
)
},
cache: {
keyPrefix: process.env.CACHE_KEY_PREFIX || "client",
...process.env.CACHE_CLIENT_SLIDING_TTL && {
clientCacheSlidingTtl: parseInt(process.env.CACHE_CLIENT_SLIDING_TTL)
},
...process.env.CACHE_CLIENT_MAX_SIZE && {
clientCacheMaxSize: parseInt(process.env.CACHE_CLIENT_MAX_SIZE)
},
...process.env.CACHE_CLIENT_BUFFER_MS && {
clientCacheBufferMs: parseInt(process.env.CACHE_CLIENT_BUFFER_MS)
},
...process.env.CACHE_CREDENTIAL_SLIDING_TTL && {
credentialCacheSlidingTtl: parseInt(
process.env.CACHE_CREDENTIAL_SLIDING_TTL
)
},
...process.env.CACHE_CREDENTIAL_MAX_SIZE && {
credentialCacheMaxSize: parseInt(process.env.CACHE_CREDENTIAL_MAX_SIZE)
},
...process.env.CACHE_CREDENTIAL_ABSOLUTE_TTL && {
credentialCacheAbsoluteTtl: parseInt(
process.env.CACHE_CREDENTIAL_ABSOLUTE_TTL
)
}
}
};
}
function validateRawEnvironmentConfig(config) {
if (config.jwt.clockTolerance < 0 || config.jwt.cacheMaxAge <= 0 || config.jwt.jwksRequestsPerMinute <= 0) {
throw new Error("JWT configuration must have valid values");
}
}
function createApplicationAuthConfig(raw) {
const strategyString = raw.azure.applicationAuthStrategy || ApplicationAuthStrategy.Chain;
const strategy = Object.values(ApplicationAuthStrategy).includes(
strategyString
) ? strategyString : ApplicationAuthStrategy.Chain;
return {
...raw.azure.managedIdentityClientId && {
managedIdentityClientId: raw.azure.managedIdentityClientId
},
strategy
};
}
function createDelegatedAuthConfig(raw) {
const {
clientId,
tenantId,
clientSecret,
certificatePath,
certificatePassword
} = raw.azure;
if (!clientId || !tenantId) {
throw new Error(
"AZURE_CLIENT_ID and AZURE_TENANT_ID must be set for delegated authentication"
);
}
const hasSecret = !!clientSecret;
const hasCertificate = !!certificatePath;
if (!hasSecret && !hasCertificate) {
throw new Error(
"Either AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH must be set for delegated authentication"
);
}
if (hasSecret && hasCertificate) {
throw new Error(
"Only one of AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH should be set"
);
}
return {
clientId,
tenantId,
...clientSecret && { clientSecret },
...certificatePath && { certificatePath },
...certificatePassword && { certificatePassword }
};
}
function createJwtConfig(raw) {
const { clientId, tenantId } = raw.azure;
if (!clientId || !tenantId) {
throw new Error(
"AZURE_CLIENT_ID and AZURE_TENANT_ID must be set for JWT validation"
);
}
return {
clientId,
tenantId,
...raw.jwt
};
}
function createClientManagerConfig(raw) {
return {
cacheKeyPrefix: raw.cache.keyPrefix,
clientCache: {
slidingTtl: raw.cache.clientCacheSlidingTtl ?? 45 * 60 * 1e3,
maxSize: raw.cache.clientCacheMaxSize ?? 100,
bufferMs: raw.cache.clientCacheBufferMs ?? 60 * 1e3
},
credentialCache: {
slidingTtl: raw.cache.credentialCacheSlidingTtl ?? 2 * 60 * 60 * 1e3,
maxSize: raw.cache.credentialCacheMaxSize ?? 200,
absoluteTtl: raw.cache.credentialCacheAbsoluteTtl ?? 8 * 60 * 60 * 1e3
}
};
}
var cachedRawConfig = null;
function getRawConfig() {
if (!cachedRawConfig) {
cachedRawConfig = loadRawEnvironmentConfig();
validateRawEnvironmentConfig(cachedRawConfig);
}
return cachedRawConfig;
}
function getApplicationAuthConfig() {
return createApplicationAuthConfig(getRawConfig());
}
function getDelegatedAuthConfig() {
return createDelegatedAuthConfig(getRawConfig());
}
function getJwtConfig() {
return createJwtConfig(getRawConfig());
}
function getClientManagerConfig() {
return createClientManagerConfig(getRawConfig());
}
// src/client-pool/provider.ts
var ClientProviderImpl = class {
constructor(clientPool, jwtHandler) {
this.clientPool = clientPool;
this.jwtHandler = jwtHandler;
}
async getClient(authRequest, options) {
const context = await this.createAuthContext(authRequest);
return await this.clientPool.getClient(context, options);
}
async invalidateClientCache(authRequest, options) {
const context = await this.createAuthContext(authRequest);
return await this.clientPool.removeCachedClient(context, options);
}
async createAuthContext(authRequest) {
switch (authRequest.mode) {
case AuthMode.Application: {
return AuthContextFactory.application();
}
case AuthMode.Delegated: {
if (!this.jwtHandler) {
throw new Error(
"JwtHandler is required for delegated authentication"
);
}
const parsedToken = await this.jwtHandler.validateToken(
authRequest.accessToken
);
return AuthContextFactory.delegated(parsedToken);
}
case AuthMode.Composite: {
if (!this.jwtHandler) {
throw new Error(
"JwtHandler is required for composite authentication"
);
}
const compositeToken = await this.jwtHandler.validateToken(
authRequest.accessToken
);
return AuthContextFactory.composite(compositeToken);
}
default: {
const _exhaustive = authRequest;
throw new Error(`Unknown auth mode: ${_exhaustive}`);
}
}
}
};
async function createClientProvider(clientFactory) {
const applicationConfig = getApplicationAuthConfig();
const delegatedConfig = getDelegatedAuthConfig();
const clientManagerConfig = getClientManagerConfig();
const credentialManager = new CredentialManager(
applicationConfig,
delegatedConfig,
clientManagerConfig
);
const clientPool = new ClientPool(
clientFactory,
credentialManager,
clientManagerConfig
);
const jwtConfig = getJwtConfig();
const jwtHandler = new JwtHandler(jwtConfig);
return new ClientProviderImpl(clientPool, jwtHandler);
}
async function createClientProviderWithMapper(clientFactory, requestMapper, authRequestFactory) {
const clientProvider = await createClientProvider(clientFactory);
return {
getClient: async (request) => {
const authData = requestMapper.extractAuthData(request);
const authRequest = authRequestFactory(authData);
const options = requestMapper.extractOptions ? requestMapper.extractOptions(request) : void 0;
return await clientProvider.getClient(authRequest, options);
},
invalidateClientCache: async (request) => {
const authData = requestMapper.extractAuthData(request);
const authRequest = authRequestFactory(authData);
const options = requestMapper.extractOptions ? requestMapper.extractOptions(request) : void 0;
return await clientProvider.invalidateClientCache(authRequest, options);
}
};
}
// src/client-pool/request-mapper.ts
var McpRequestMapper = class {
extractAuthData(request) {
const params = request.params;
const arguments_ = params?.arguments;
const authData = {};
if (arguments_?.access_token && typeof arguments_.access_token === "string") {
authData.accessToken = arguments_.access_token;
}
return authData;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AuthMode,
CredentialType,
McpRequestMapper,
createClientProvider,
createClientProviderWithMapper,
getLogger,
setRootLogger
});
//# sourceMappingURL=index.js.map