UNPKG

@aashari/mcp-server-aws-sso

Version:

Node.js/TypeScript MCP server for AWS Single Sign-On (SSO). Enables AI systems (LLMs) with tools to initiate SSO login (device auth flow), list accounts/roles, and securely execute AWS CLI commands using temporary credentials. Streamlines AI interaction w

219 lines (218 loc) 9.71 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.startSsoLogin = startSsoLogin; exports.pollForSsoToken = pollForSsoToken; exports.checkSsoAuthStatus = checkSsoAuthStatus; exports.getCachedDeviceAuthorizationInfo = getCachedDeviceAuthorizationInfo; const logger_util_js_1 = require("../utils/logger.util.js"); const error_util_js_1 = require("../utils/error.util.js"); const ssoCache = __importStar(require("../utils/aws.sso.cache.util.js")); const vendor_aws_sso_types_js_1 = require("./vendor.aws.sso.types.js"); // Import the modular services const vendor_aws_sso_auth_core_service_js_1 = require("./vendor.aws.sso.auth.core.service.js"); const vendor_aws_sso_auth_oauth_service_js_1 = require("./vendor.aws.sso.auth.oauth.service.js"); const logger = logger_util_js_1.Logger.forContext('services/vendor.aws.sso.auth.service.ts'); /** * Start the AWS SSO login process * * Initiates the SSO login flow by registering a client and starting device authorization. * Returns a verification URI and user code that the user must visit to complete authentication. * * @returns {Promise<DeviceAuthorizationResponseSchema['_output']>} Login information including verification URI and user code * @throws {Error} If login initialization fails */ async function startSsoLogin() { const methodLogger = logger.forMethod('startSsoLogin'); methodLogger.debug('Starting AWS SSO login process'); try { // First, clean up any existing device authorization to ensure we get fresh codes await ssoCache.clearDeviceAuthorizationInfo(); } catch (error) { methodLogger.debug('Error clearing existing device auth info', error); // Continue even if cleanup fails } // Get SSO configuration const ssoConfig = await (0, vendor_aws_sso_auth_core_service_js_1.getAwsSsoConfig)(); // Step 1: Register client const clientInfo = await (0, vendor_aws_sso_auth_oauth_service_js_1.registerClient)(ssoConfig); // Step 2: Start device authorization const authResponse = await (0, vendor_aws_sso_auth_oauth_service_js_1.startDeviceAuthorization)(ssoConfig, clientInfo); // Store device authorization info in cache for later polling const deviceAuthInfo = { clientId: clientInfo.clientId, clientSecret: clientInfo.clientSecret, deviceCode: authResponse.deviceCode, expiresIn: authResponse.expiresIn, interval: authResponse.interval, verificationUri: authResponse.verificationUri, verificationUriComplete: authResponse.verificationUriComplete, userCode: authResponse.userCode, region: ssoConfig.region, }; // Validate with Zod schema before caching vendor_aws_sso_types_js_1.DeviceAuthorizationInfoSchema.parse(deviceAuthInfo); await ssoCache.cacheDeviceAuthorizationInfo(deviceAuthInfo); return authResponse; } /** * Poll for SSO token completion * * Continuously polls the SSO token endpoint until authentication is complete or times out. * Automatically applies appropriate backoff between retries based on the device authorization interval. * * @returns {Promise<AwsSsoAuthResult>} AWS SSO auth result with access token * @throws {Error} If polling times out or auth is denied */ async function pollForSsoToken() { const methodLogger = logger.forMethod('pollForSsoToken'); methodLogger.debug('Starting polling for SSO token'); // Get the device authorization info for polling const deviceInfo = await getCachedDeviceAuthorizationInfo(); if (!deviceInfo) { const error = (0, error_util_js_1.createAuthMissingError)('No device authorization information found for polling'); methodLogger.error('Device authorization info missing', error); throw error; } // Create a timestamp for when the auth flow expires const startTime = Math.floor(Date.now() / 1000); const expiresAt = startTime + deviceInfo.expiresIn; // Use the interval from the device auth flow or default to 5 seconds // This is the recommended polling interval from AWS SSO const pollingIntervalSeconds = deviceInfo.interval || 5; methodLogger.debug('Poll settings', { clientId: deviceInfo.clientId, startTime, expiresAt, expiresIn: deviceInfo.expiresIn, pollingInterval: pollingIntervalSeconds, }); // Prepare the client info const clientInfo = { clientId: deviceInfo.clientId, clientSecret: deviceInfo.clientSecret, }; // Get SSO configuration const ssoConfig = await (0, vendor_aws_sso_auth_core_service_js_1.getAwsSsoConfig)(); // Poll until successful or timeout while (true) { const now = Math.floor(Date.now() / 1000); // Check if we've exceeded the expiration time if (now > expiresAt) { const error = (0, error_util_js_1.createAuthTimeoutError)('Device authorization timed out. Please try again.'); methodLogger.error('Device authorization timed out', error); // Clear any pending device authorization data try { await ssoCache.clearDeviceAuthorizationInfo(); methodLogger.debug('Cleared stale device authorization data due to timeout'); } catch (clearError) { methodLogger.error('Error clearing device auth data on timeout', clearError); } throw error; } // Attempt to get a token const result = await (0, vendor_aws_sso_auth_oauth_service_js_1.pollForToken)(ssoConfig, clientInfo, deviceInfo.deviceCode, pollingIntervalSeconds); // If we get a result, return it if (result) { // Add missing properties if needed const fullResult = { ...result, // Always set expiresAt if it's not already set expiresAt: result.expiresAt || Math.floor(Date.now() / 1000) + ('expiresIn' in result && typeof result.expiresIn === 'number' ? result.expiresIn : 28800), region: ssoConfig.region, }; return fullResult; } // Wait before polling again methodLogger.debug(`Waiting ${pollingIntervalSeconds} seconds before polling again`); await new Promise((resolve) => setTimeout(resolve, pollingIntervalSeconds * 1000)); } } /** * Check SSO authentication status * * Verifies if there is a valid cached token. * * @returns {Promise<AuthCheckResult>} Authentication status including isAuthenticated flag */ async function checkSsoAuthStatus() { const methodLogger = logger.forMethod('checkSsoAuthStatus'); methodLogger.debug('Checking AWS SSO auth status'); try { const cachedToken = await ssoCache.getCachedSsoToken(); if (!cachedToken || !cachedToken.accessToken) { methodLogger.debug('No cached token found'); return { isAuthenticated: false, errorMessage: 'No cached AWS SSO token found', }; } // Check if token is expired if (cachedToken.expiresAt && cachedToken.expiresAt <= Math.floor(Date.now() / 1000)) { methodLogger.debug('Cached token is expired'); return { isAuthenticated: false, errorMessage: 'AWS SSO token is expired', }; } methodLogger.debug('Valid cached token found'); return { isAuthenticated: true }; } catch (error) { methodLogger.error('Error checking auth status', error); return { isAuthenticated: false, errorMessage: `Error checking auth status: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Get cached device authorization information * * @returns {Promise<DeviceAuthorizationInfo | undefined>} The cached device auth info or undefined if not found */ async function getCachedDeviceAuthorizationInfo() { const methodLogger = logger.forMethod('getCachedDeviceAuthorizationInfo'); methodLogger.debug('Getting cached device authorization info'); return ssoCache.getCachedDeviceAuthorizationInfo(); }