@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
160 lines (159 loc) • 6.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProxyOAuthServerProvider = void 0;
const memory_storage_1 = require("../storage/memory-storage");
class ProxyOAuthServerProvider {
config;
storage;
constructor(config) {
this.config = config;
// fallback to memory storage
// ideally this would be used in a development environment, could be set with a flag
// since we are providing storage on Redis (to do) we may want to fallback to this in dev
// does not scale to prod tho, so we should be validating that we prevent that from happening
this.storage = config.storage || new memory_storage_1.MemoryOAuthStorage();
}
// Expose config for router access
get endpoints() {
return this.config.endpoints;
}
async verifyAccessToken(token) {
if (this.config.verifyAccessToken) {
return await this.config.verifyAccessToken(token);
}
// Fallback to storage lookup or external verification
const storedToken = await this.storage.tokens.getToken(token);
if (storedToken) {
return storedToken;
}
// If not found in storage, verify with external provider
return await this.verifyTokenWithProvider(token);
}
async authorize(params) {
const { client_id, redirect_uri, response_type, scope, state } = params;
// Let Auth0 handle all client validation - just build the authorization URL
const authUrl = new URL(this.config.endpoints.authorizationUrl);
authUrl.searchParams.set("response_type", response_type);
authUrl.searchParams.set("client_id", client_id);
authUrl.searchParams.set("redirect_uri", redirect_uri);
if (scope) {
authUrl.searchParams.set("scope", scope);
}
else if (this.config.defaultScopes) {
authUrl.searchParams.set("scope", this.config.defaultScopes.join(" "));
}
if (state) {
authUrl.searchParams.set("state", state);
}
return authUrl.toString();
}
async token(params) {
const { grant_type, client_id, client_secret, code, redirect_uri, refresh_token, } = params;
try {
// Forward token request to external provider (let Auth0 handle client validation)
const response = await fetch(this.config.endpoints.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type,
client_id,
...(client_secret && { client_secret }),
...(code && { code }),
...(redirect_uri && { redirect_uri }),
...(refresh_token && { refresh_token }),
}),
});
const tokenData = await response.json();
if (!response.ok) {
throw this.createOAuthError(tokenData.error || "server_error", tokenData.error_description || "Token exchange failed");
}
// Store token in our storage if we have access_token
if (tokenData.access_token) {
const accessToken = {
token: tokenData.access_token,
clientId: client_id,
scopes: tokenData.scope ? tokenData.scope.split(" ") : [],
expiresAt: tokenData.expires_in
? new Date(Date.now() + tokenData.expires_in * 1000)
: undefined,
refreshToken: tokenData.refresh_token,
};
await this.storage.tokens.saveToken(accessToken);
}
return tokenData;
}
catch (error) {
if (error instanceof Error && error.message.includes("invalid_")) {
throw error;
}
throw this.createOAuthError("server_error", "Failed to exchange token");
}
}
async revoke(params) {
const { token, token_type_hint, client_id, client_secret } = params;
// Remove from our storage first
await this.storage.tokens.deleteToken(token);
// If external provider supports revocation, forward the request
if (this.config.endpoints.revocationUrl) {
try {
const response = await fetch(this.config.endpoints.revocationUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
token,
...(token_type_hint && { token_type_hint }),
...(client_id && { client_id }),
...(client_secret && { client_secret }),
}),
});
if (!response.ok) {
console.warn("Failed to revoke token with external provider:", response.statusText);
}
}
catch (error) {
console.warn("Error revoking token with external provider:", error);
}
}
}
async verifyTokenWithProvider(token) {
// If external provider has a userinfo endpoint, we can use it to verify the token
if (this.config.endpoints.userInfoUrl) {
try {
const response = await fetch(this.config.endpoints.userInfoUrl, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
if (response.ok) {
// Token is valid, create a basic AccessToken object
return {
token,
clientId: "external", // We might not know the exact client ID
scopes: [], // We might not know the exact scopes
};
}
}
catch (error) {
console.warn("Error verifying token with external provider:", error);
}
}
throw this.createOAuthError("invalid_token", "Token verification failed");
}
createOAuthError(error, description) {
const oauthError = {
error,
error_description: description,
};
const errorObj = new Error(description || error);
errorObj.oauth = oauthError;
return errorObj;
}
}
exports.ProxyOAuthServerProvider = ProxyOAuthServerProvider;