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

240 lines (239 loc) 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const logger_util_js_1 = require("../utils/logger.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const vendor_aws_sso_auth_service_js_1 = require("../services/vendor.aws.sso.auth.service.js"); const vendor_aws_sso_accounts_service_js_1 = require("../services/vendor.aws.sso.accounts.service.js"); const aws_sso_cache_util_js_1 = require("../utils/aws.sso.cache.util.js"); const aws_sso_accounts_formatter_js_1 = require("./aws.sso.accounts.formatter.js"); /** * AWS SSO Accounts Controller Module * * Provides functionality for listing and managing AWS SSO accounts and roles. * Handles retrieving account information, listing available roles, and formatting * the results for display. All operations require valid AWS SSO authentication. */ // Create a module logger const controllerLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.accounts.controller.ts'); // Log module initialization controllerLogger.debug('AWS SSO accounts controller initialized'); /** * List all AWS accounts and their roles * * Retrieves and formats all available AWS accounts and the roles the user * can assume in each account via AWS SSO. Groups roles by account for better organization. * * @async * @returns {Promise<ControllerResponse>} Response with comprehensive formatted list of accounts and roles * @throws {Error} If account listing fails or authentication is required * @example * // List all accounts and their roles * const result = await listAccounts(); */ async function listAccounts() { const listLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.accounts.controller.ts', 'listAccounts'); listLogger.debug('Listing AWS SSO accounts and roles'); try { // First check if we have a valid token const cachedToken = await (0, vendor_aws_sso_auth_service_js_1.getCachedSsoToken)(); if (!cachedToken) { // No token found, user needs to authenticate listLogger.debug('No SSO token found, authentication required'); return { content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(), metadata: { authenticated: false, }, }; } // Verify token has an access token if (!cachedToken.accessToken || cachedToken.accessToken.trim() === '') { listLogger.error('Cached token has empty access token'); // Clear invalid token and ask for re-authentication try { await (0, aws_sso_cache_util_js_1.clearSsoToken)(); listLogger.debug('Cleared invalid empty token'); } catch (clearError) { listLogger.error('Error clearing invalid token', clearError); } return { content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(), metadata: { authenticated: false, }, }; } // We have a token, validate it's not expired const now = Math.floor(Date.now() / 1000); if (cachedToken.expiresAt <= now) { listLogger.debug('SSO token is expired, authentication required'); // Clear expired token try { await (0, aws_sso_cache_util_js_1.clearSsoToken)(); listLogger.debug('Cleared expired token'); } catch (clearError) { listLogger.error('Error clearing expired token', clearError); } return { content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(), metadata: { authenticated: false, }, }; } // Format expiration date for display let expiresDate = 'Unknown'; try { const expirationDate = new Date(cachedToken.expiresAt * 1000); expiresDate = expirationDate.toLocaleString(); } catch (error) { listLogger.error('Error formatting expiration date', error); } // Get accounts with roles listLogger.debug('Getting AWS accounts with roles'); try { // Check if we already have cached account roles data const cacheUtil = await import('../utils/aws.sso.cache.util.js'); const cachedAccounts = await cacheUtil.getAccountRolesFromCache(); if (cachedAccounts && cachedAccounts.length > 0) { listLogger.debug('Using account roles from cache'); // Map the cached accounts to the format expected by the formatter // The cache uses {account: {...}, roles: [...]} format, but we need it flattened const formattedAccountsWithRoles = cachedAccounts.map((item) => ({ ...item.account, roles: item.roles, timestamp: Date.now(), })); return { content: (0, aws_sso_accounts_formatter_js_1.formatAccountsAndRoles)(expiresDate, formattedAccountsWithRoles), metadata: { authenticated: true, accounts: formattedAccountsWithRoles.map((account) => ({ accountId: account.accountId, accountName: account.accountName, accountEmail: account.emailAddress, // Note: different property name in cache roles: account.roles, })), }, }; } // If no cached data or cache is empty, get accounts with roles from API listLogger.debug('No cached account roles, fetching from AWS SSO API'); // Get accounts with roles const accountsWithRoles = await (0, vendor_aws_sso_accounts_service_js_1.getAccountsWithRoles)({ // Use the access token from the cached token // The vendor implementation expects a params object, not just the token }); if (accountsWithRoles.length === 0) { listLogger.debug('No accounts found'); return { content: (0, aws_sso_accounts_formatter_js_1.formatNoAccounts)(), metadata: { authenticated: true, accounts: [], }, }; } // Save accounts with roles to MCP cache try { await cacheUtil.saveAccountRolesToCache(accountsWithRoles); listLogger.debug('Saved accounts with roles to MCP cache'); } catch (cacheError) { listLogger.error('Error saving to MCP cache', cacheError); // Continue even if caching fails } // Format the accounts with roles for the response // The vendor implementation returns a different format compared to the non-vendor one const formattedAccountsWithRoles = accountsWithRoles.map((account) => ({ ...account, timestamp: Date.now(), })); // Return accounts and roles return { content: (0, aws_sso_accounts_formatter_js_1.formatAccountsAndRoles)(expiresDate, formattedAccountsWithRoles), metadata: { authenticated: true, accounts: formattedAccountsWithRoles.map((account) => ({ accountId: account.accountId, accountName: account.accountName, accountEmail: account.accountEmail, roles: account.roles, })), }, }; } catch (error) { // If we get an error about invalid/expired token, show auth required message if (error instanceof Error && (error.message.includes('invalid') || error.message.includes('expired') || error.message.includes('session'))) { listLogger.debug('Token validation failed, authentication required', error); return { content: (0, aws_sso_accounts_formatter_js_1.formatAuthRequired)(), metadata: { authenticated: false, }, }; } // For other errors, pass through to general error handler throw error; } } catch (error) { return (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'AWS SSO Accounts', operation: 'listing', source: 'controllers/aws.sso.accounts.controller.ts@listAccounts', }); } } /** * List roles available for a specified AWS account * * Retrieves and formats all IAM roles the authenticated user can assume * in a specific AWS account via SSO. * * @async * @param {ListRolesOptions} params - Role listing parameters * @returns {Promise<ControllerResponse>} Response with formatted list of available roles * @throws {Error} If role listing fails or authentication is invalid * @example * // List roles for account 123456789012 * const result = await listRoles({ * accessToken: "token-value", * accountId: "123456789012" * }); */ async function listRoles(params) { const listRolesLogger = logger_util_js_1.Logger.forContext('controllers/aws.sso.accounts.controller.ts', 'listRoles'); listRolesLogger.debug(`Listing roles for account ${params.accountId}`); try { // Get roles for account const roles = await (0, vendor_aws_sso_accounts_service_js_1.listAccountRoles)({ accountId: params.accountId, }); return { content: (0, aws_sso_accounts_formatter_js_1.formatAccountRoles)(params.accountId, roles.roleList), metadata: { roles: roles.roleList, }, }; } catch (error) { return (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'AWS Account Roles', entityId: params.accountId, operation: 'listing', source: 'controllers/aws.sso.accounts.controller.ts@listRoles', }); } } exports.default = { listAccounts, listRoles, };