@mcp-z/oauth-google 1.0.0

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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/cjs/index.d.cts +16 -0
  4. package/dist/cjs/index.d.ts +16 -0
  5. package/dist/cjs/index.js +112 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/lib/dcr-router.d.cts +44 -0
  8. package/dist/cjs/lib/dcr-router.d.ts +44 -0
  9. package/dist/cjs/lib/dcr-router.js +1189 -0
  10. package/dist/cjs/lib/dcr-router.js.map +1 -0
  11. package/dist/cjs/lib/dcr-utils.d.cts +160 -0
  12. package/dist/cjs/lib/dcr-utils.d.ts +160 -0
  13. package/dist/cjs/lib/dcr-utils.js +860 -0
  14. package/dist/cjs/lib/dcr-utils.js.map +1 -0
  15. package/dist/cjs/lib/dcr-verify.d.cts +53 -0
  16. package/dist/cjs/lib/dcr-verify.d.ts +53 -0
  17. package/dist/cjs/lib/dcr-verify.js +193 -0
  18. package/dist/cjs/lib/dcr-verify.js.map +1 -0
  19. package/dist/cjs/lib/fetch-with-timeout.d.cts +14 -0
  20. package/dist/cjs/lib/fetch-with-timeout.d.ts +14 -0
  21. package/dist/cjs/lib/fetch-with-timeout.js +257 -0
  22. package/dist/cjs/lib/fetch-with-timeout.js.map +1 -0
  23. package/dist/cjs/lib/token-verifier.d.cts +44 -0
  24. package/dist/cjs/lib/token-verifier.d.ts +44 -0
  25. package/dist/cjs/lib/token-verifier.js +253 -0
  26. package/dist/cjs/lib/token-verifier.js.map +1 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/providers/dcr.d.cts +107 -0
  29. package/dist/cjs/providers/dcr.d.ts +107 -0
  30. package/dist/cjs/providers/dcr.js +584 -0
  31. package/dist/cjs/providers/dcr.js.map +1 -0
  32. package/dist/cjs/providers/loopback-oauth.d.cts +119 -0
  33. package/dist/cjs/providers/loopback-oauth.d.ts +119 -0
  34. package/dist/cjs/providers/loopback-oauth.js +1334 -0
  35. package/dist/cjs/providers/loopback-oauth.js.map +1 -0
  36. package/dist/cjs/providers/service-account.d.cts +131 -0
  37. package/dist/cjs/providers/service-account.d.ts +131 -0
  38. package/dist/cjs/providers/service-account.js +800 -0
  39. package/dist/cjs/providers/service-account.js.map +1 -0
  40. package/dist/cjs/schemas/index.d.cts +20 -0
  41. package/dist/cjs/schemas/index.d.ts +20 -0
  42. package/dist/cjs/schemas/index.js +37 -0
  43. package/dist/cjs/schemas/index.js.map +1 -0
  44. package/dist/cjs/setup/config.d.cts +112 -0
  45. package/dist/cjs/setup/config.d.ts +112 -0
  46. package/dist/cjs/setup/config.js +236 -0
  47. package/dist/cjs/setup/config.js.map +1 -0
  48. package/dist/cjs/types.d.cts +173 -0
  49. package/dist/cjs/types.d.ts +173 -0
  50. package/dist/cjs/types.js +16 -0
  51. package/dist/cjs/types.js.map +1 -0
  52. package/dist/esm/index.d.ts +16 -0
  53. package/dist/esm/index.js +16 -0
  54. package/dist/esm/index.js.map +1 -0
  55. package/dist/esm/lib/dcr-router.d.ts +44 -0
  56. package/dist/esm/lib/dcr-router.js +515 -0
  57. package/dist/esm/lib/dcr-router.js.map +1 -0
  58. package/dist/esm/lib/dcr-utils.d.ts +160 -0
  59. package/dist/esm/lib/dcr-utils.js +270 -0
  60. package/dist/esm/lib/dcr-utils.js.map +1 -0
  61. package/dist/esm/lib/dcr-verify.d.ts +53 -0
  62. package/dist/esm/lib/dcr-verify.js +53 -0
  63. package/dist/esm/lib/dcr-verify.js.map +1 -0
  64. package/dist/esm/lib/fetch-with-timeout.d.ts +14 -0
  65. package/dist/esm/lib/fetch-with-timeout.js +30 -0
  66. package/dist/esm/lib/fetch-with-timeout.js.map +1 -0
  67. package/dist/esm/lib/token-verifier.d.ts +44 -0
  68. package/dist/esm/lib/token-verifier.js +53 -0
  69. package/dist/esm/lib/token-verifier.js.map +1 -0
  70. package/dist/esm/package.json +1 -0
  71. package/dist/esm/providers/dcr.d.ts +107 -0
  72. package/dist/esm/providers/dcr.js +242 -0
  73. package/dist/esm/providers/dcr.js.map +1 -0
  74. package/dist/esm/providers/loopback-oauth.d.ts +119 -0
  75. package/dist/esm/providers/loopback-oauth.js +639 -0
  76. package/dist/esm/providers/loopback-oauth.js.map +1 -0
  77. package/dist/esm/providers/service-account.d.ts +131 -0
  78. package/dist/esm/providers/service-account.js +353 -0
  79. package/dist/esm/providers/service-account.js.map +1 -0
  80. package/dist/esm/schemas/index.d.ts +20 -0
  81. package/dist/esm/schemas/index.js +18 -0
  82. package/dist/esm/schemas/index.js.map +1 -0
  83. package/dist/esm/setup/config.d.ts +112 -0
  84. package/dist/esm/setup/config.js +258 -0
  85. package/dist/esm/setup/config.js.map +1 -0
  86. package/dist/esm/types.d.ts +173 -0
  87. package/dist/esm/types.js +6 -0
  88. package/dist/esm/types.js.map +1 -0
  89. package/package.json +89 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-google/src/providers/loopback-oauth.ts"],"sourcesContent":["/**\n * Loopback OAuth Implementation (RFC 8252)\n *\n * Implements OAuth 2.0 Authorization Code Flow with PKCE using loopback interface redirection.\n * Uses ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).\n */\n\nimport { addAccount, generatePKCE, getActiveAccount, getErrorTemplate, getSuccessTemplate, getToken, listAccountIds, type OAuth2TokenStorageProvider, setAccountInfo, setActiveAccount, setToken } from '@mcp-z/oauth';\nimport { OAuth2Client } from 'google-auth-library';\nimport * as http from 'http';\nimport open from 'open';\nimport { type AuthContext, type AuthFlowDescriptor, AuthRequiredError, type CachedToken, type EnrichedExtra, type LoopbackOAuthConfig } from '../types.ts';\n\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * Loopback OAuth Client (RFC 8252 Section 7.3)\n *\n * Implements OAuth 2.0 Authorization Code Flow with PKCE for native applications\n * using loopback interface redirection. Manages ephemeral OAuth flows and token persistence\n * with Keyv for key-based token storage using compound keys.\n *\n * Token key format: {accountId}:{service}:token (e.g., \"user@example.com:gmail:token\")\n */\nexport class LoopbackOAuthProvider implements OAuth2TokenStorageProvider {\n private config: LoopbackOAuthConfig;\n\n constructor(config: LoopbackOAuthConfig) {\n this.config = config;\n }\n\n /**\n * Get access token from Keyv using compound key\n *\n * @param accountId - Account identifier (email address). Required for loopback OAuth.\n * @returns Access token for API requests\n */\n async getAccessToken(accountId?: string): Promise<string> {\n const { logger, service, tokenStore } = this.config;\n\n // Use active account if no accountId specified\n const effectiveAccountId = accountId ?? (await getActiveAccount(tokenStore, { service }));\n\n // If we have an accountId, try to use existing token\n if (effectiveAccountId) {\n logger.debug('Getting access token', { service, accountId: effectiveAccountId });\n\n // Check Keyv for token using new key format\n const storedToken = await getToken<CachedToken>(tokenStore, { accountId: effectiveAccountId, service });\n\n if (storedToken && this.isTokenValid(storedToken)) {\n logger.debug('Using stored access token', { accountId: effectiveAccountId });\n return storedToken.accessToken;\n }\n\n // If stored token expired but has refresh token, try refresh\n if (storedToken?.refreshToken) {\n try {\n logger.info('Refreshing expired access token', { accountId: effectiveAccountId });\n const refreshedToken = await this.refreshAccessToken(storedToken.refreshToken);\n await setToken(tokenStore, { accountId: effectiveAccountId, service }, refreshedToken);\n return refreshedToken.accessToken;\n } catch (error) {\n logger.info('Token refresh failed, starting new OAuth flow', {\n accountId: effectiveAccountId,\n error: error instanceof Error ? error.message : String(error),\n });\n // Fall through to new OAuth flow\n }\n }\n }\n\n // No valid token or no account - check if we can start OAuth flow\n const { headless } = this.config;\n if (headless) {\n // In headless mode (production), cannot start OAuth flow\n // Throw AuthRequiredError with auth_url descriptor for MCP tool response\n const { clientId, scope } = this.config;\n\n // Incremental OAuth detection: Check if other accounts exist\n const existingAccounts = await this.getExistingAccounts();\n const hasOtherAccounts = effectiveAccountId ? existingAccounts.length > 0 && !existingAccounts.includes(effectiveAccountId) : existingAccounts.length > 0;\n\n // Build informational OAuth URL for headless mode\n // Note: No redirect_uri included - user must use account-add tool which starts proper ephemeral server\n const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', scope);\n authUrl.searchParams.set('access_type', 'offline');\n authUrl.searchParams.set('prompt', 'consent');\n\n let hint: string;\n if (hasOtherAccounts) {\n hint = `Existing ${service} accounts found. Use account-list to view, account-switch to change account, or account-add to add new account`;\n } else if (effectiveAccountId) {\n hint = `Use account-add to authenticate ${effectiveAccountId}`;\n } else {\n hint = 'Use account-add to authenticate interactively';\n }\n\n const baseDescriptor = {\n kind: 'auth_url' as const,\n provider: 'google',\n url: authUrl.toString(),\n hint,\n };\n\n const descriptor: AuthFlowDescriptor & { accountId?: string } = effectiveAccountId ? { ...baseDescriptor, accountId: effectiveAccountId } : baseDescriptor;\n\n throw new AuthRequiredError(descriptor);\n }\n\n // Interactive mode - start ephemeral OAuth flow\n logger.info('Starting ephemeral OAuth flow', { service, headless });\n const { token, email } = await this.performEphemeralOAuthFlow();\n\n // Store token with email as accountId\n await setToken(tokenStore, { accountId: email, service }, token);\n\n // Register account in account management system\n await addAccount(tokenStore, { service, accountId: email });\n\n // Set as active account so subsequent getAccessToken() calls find it\n await setActiveAccount(tokenStore, { service, accountId: email });\n\n // Store account metadata (email, added timestamp)\n await setAccountInfo(\n tokenStore,\n { service, accountId: email },\n {\n email,\n addedAt: new Date().toISOString(),\n }\n );\n\n logger.info('OAuth flow completed', { service, accountId: email });\n\n return token.accessToken;\n }\n\n /**\n * Convert to googleapis-compatible OAuth2Client\n *\n * @param accountId - Account identifier for multi-account support (e.g., 'user@example.com')\n * @returns OAuth2Client configured for the specified account\n */\n toAuth(accountId?: string): OAuth2Client {\n const { clientId, clientSecret } = this.config;\n const client = new OAuth2Client({\n clientId,\n ...(clientSecret && { clientSecret }),\n });\n\n // @ts-expect-error - Override protected method to inject fresh token\n client.getRequestMetadataAsync = async (_url?: string) => {\n // Get token from FileAuthAdapter (not from client to avoid recursion)\n const token = await this.getAccessToken(accountId);\n\n // Update client credentials for googleapis compatibility\n client.credentials = {\n access_token: token,\n token_type: 'Bearer',\n };\n\n // Return headers as Map (required by authclient.js addUserProjectAndAuthHeaders)\n const headers = new Map<string, string>();\n headers.set('authorization', `Bearer ${token}`);\n return { headers };\n };\n\n return client;\n }\n\n /**\n * Authenticate new account with OAuth flow\n * Triggers account selection, stores token, registers account\n *\n * @returns Email address of newly authenticated account\n * @throws Error in headless mode (cannot open browser for OAuth)\n */\n async authenticateNewAccount(): Promise<string> {\n const { logger, headless, service, tokenStore } = this.config;\n\n if (headless) {\n throw new Error('Cannot authenticate new account in headless mode - interactive OAuth required');\n }\n\n logger.info('Starting new account authentication', { service });\n\n // Trigger OAuth with account selection\n const { token, email } = await this.performEphemeralOAuthFlow();\n\n // Store token\n await setToken(tokenStore, { accountId: email, service }, token);\n\n // Register account\n await addAccount(tokenStore, { service, accountId: email });\n\n // Set as active account\n await setActiveAccount(tokenStore, { service, accountId: email });\n\n // Store account metadata\n await setAccountInfo(\n tokenStore,\n { service, accountId: email },\n {\n email,\n addedAt: new Date().toISOString(),\n }\n );\n\n logger.info('New account authenticated', { service, email });\n return email;\n }\n\n /**\n * Get user email from Google's userinfo endpoint (pure query)\n * Used to query email for existing authenticated account\n *\n * @param accountId - Account identifier to get email for\n * @returns User's email address\n */\n async getUserEmail(accountId?: string): Promise<string> {\n // Get token for existing account\n const token = await this.getAccessToken(accountId);\n\n // Fetch email from Google userinfo\n const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get user info: ${response.status} ${await response.text()}`);\n }\n\n const userInfo = (await response.json()) as { email: string };\n return userInfo.email;\n }\n\n /**\n * Check for existing accounts in token storage (incremental OAuth detection)\n *\n * Uses key-utils helper for forward compatibility with key format changes.\n *\n * @returns Array of account IDs that have tokens for this service\n */\n private async getExistingAccounts(): Promise<string[]> {\n const { service, tokenStore } = this.config;\n return listAccountIds(tokenStore, service);\n }\n\n private isTokenValid(token: CachedToken): boolean {\n if (!token.expiresAt) return true; // No expiry = assume valid\n return Date.now() < token.expiresAt - 60000; // 1 minute buffer\n }\n\n /**\n * Fetch user email from Google OAuth2 userinfo endpoint\n * Called during OAuth flow to get email for accountId\n *\n * @param accessToken - Fresh access token from OAuth exchange\n * @returns User's email address\n */\n private async fetchUserEmailFromToken(accessToken: string): Promise<string> {\n const { logger } = this.config;\n\n const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to fetch user email: HTTP ${response.status} - ${errorText}`);\n }\n\n const userInfo = (await response.json()) as { email: string };\n const email = userInfo.email;\n\n logger.debug('Fetched user email from Google userinfo API', { email });\n return email;\n }\n\n private async performEphemeralOAuthFlow(): Promise<{ token: CachedToken; email: string }> {\n const { clientId, scope, headless, logger, redirectUri: configRedirectUri } = this.config;\n\n // Parse redirectUri if provided to extract host, protocol, port, and path\n let targetHost = 'localhost'; // Default: localhost (match registered redirect URI)\n let targetPort = 0; // Default: OS-assigned ephemeral port\n let targetProtocol = 'http:'; // Default: http\n let callbackPath = '/callback'; // Default callback path\n let useConfiguredUri = false;\n\n if (configRedirectUri) {\n try {\n const parsed = new URL(configRedirectUri);\n\n // Use configured redirect URI as-is for production deployments\n targetHost = parsed.hostname;\n targetProtocol = parsed.protocol;\n\n // Extract port from URL (use default ports if not specified)\n if (parsed.port) {\n targetPort = Number.parseInt(parsed.port, 10);\n } else {\n targetPort = parsed.protocol === 'https:' ? 443 : 80;\n }\n\n // Extract path (default to /callback if URL has no path or just '/')\n if (parsed.pathname && parsed.pathname !== '/') {\n callbackPath = parsed.pathname;\n }\n\n useConfiguredUri = true;\n\n logger.debug('Using configured redirect URI', {\n host: targetHost,\n protocol: targetProtocol,\n port: targetPort,\n path: callbackPath,\n redirectUri: configRedirectUri,\n });\n } catch (error) {\n logger.warn('Failed to parse redirectUri, using ephemeral defaults', {\n redirectUri: configRedirectUri,\n error: error instanceof Error ? error.message : String(error),\n });\n // Continue with defaults (127.0.0.1, port 0, http, /callback)\n }\n }\n\n return new Promise((resolve, reject) => {\n // Generate PKCE challenge\n const { verifier: codeVerifier, challenge: codeChallenge } = generatePKCE();\n\n let server: http.Server | null = null;\n let serverPort: number;\n let finalRedirectUri: string; // Will be set in server.listen callback\n\n // Create ephemeral server with OS-assigned port (RFC 8252)\n server = http.createServer(async (req, res) => {\n if (!req.url) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Invalid request'));\n server?.close();\n reject(new Error('Invalid request: missing URL'));\n return;\n }\n const url = new URL(req.url, `http://127.0.0.1:${serverPort}`);\n\n if (url.pathname === callbackPath) {\n const code = url.searchParams.get('code');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate(error));\n server?.close();\n reject(new Error(`OAuth error: ${error}`));\n return;\n }\n\n if (!code) {\n res.writeHead(400, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('No authorization code received'));\n server?.close();\n reject(new Error('No authorization code received'));\n return;\n }\n\n try {\n // Exchange code for token (must use same redirect_uri as in authorization request)\n const tokenResponse = await this.exchangeCodeForToken(code, codeVerifier, finalRedirectUri);\n\n // Build cached token\n const cachedToken: CachedToken = {\n accessToken: tokenResponse.access_token,\n ...(tokenResponse.refresh_token !== undefined && { refreshToken: tokenResponse.refresh_token }),\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n\n // Fetch user email immediately using the new access token\n const email = await this.fetchUserEmailFromToken(tokenResponse.access_token);\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessTemplate());\n server?.close();\n resolve({ token: cachedToken, email });\n } catch (exchangeError) {\n logger.error('Token exchange failed', { error: exchangeError instanceof Error ? exchangeError.message : String(exchangeError) });\n res.writeHead(500, { 'Content-Type': 'text/html' });\n res.end(getErrorTemplate('Token exchange failed'));\n server?.close();\n reject(exchangeError);\n }\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n }\n });\n\n // Listen on targetPort (0 for OS assignment, or custom port from redirectUri)\n server.listen(targetPort, targetHost, () => {\n const address = server?.address();\n if (!address || typeof address === 'string') {\n server?.close();\n reject(new Error('Failed to start ephemeral server'));\n return;\n }\n\n serverPort = address.port;\n\n // Construct final redirect URI\n if (useConfiguredUri && configRedirectUri) {\n // Use configured redirect URI as-is for production\n finalRedirectUri = configRedirectUri;\n } else {\n // Construct ephemeral redirect URI with actual server port\n finalRedirectUri = `${targetProtocol}//${targetHost}:${serverPort}${callbackPath}`;\n }\n\n // Build auth URL\n const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', finalRedirectUri);\n authUrl.searchParams.set('response_type', 'code');\n authUrl.searchParams.set('scope', scope);\n authUrl.searchParams.set('access_type', 'offline');\n authUrl.searchParams.set('prompt', 'consent');\n authUrl.searchParams.set('code_challenge', codeChallenge);\n authUrl.searchParams.set('code_challenge_method', 'S256');\n\n logger.info('Ephemeral OAuth server started', { port: serverPort, headless });\n\n if (headless) {\n // Headless mode: Print auth URL to stderr (stdout is MCP protocol)\n console.error('\\n🔐 OAuth Authorization Required');\n console.error('📋 Please visit this URL in your browser:\\n');\n console.error(` ${authUrl.toString()}\\n`);\n console.error('⏳ Waiting for authorization...\\n');\n } else {\n // Interactive mode: Open browser automatically\n logger.info('Opening browser for OAuth authorization');\n open(authUrl.toString()).catch((error: Error) => {\n logger.info('Failed to open browser automatically', { error: error.message });\n console.error('\\n🔐 OAuth Authorization Required');\n console.error(` ${authUrl.toString()}\\n`);\n });\n }\n });\n\n // Timeout after 5 minutes\n setTimeout(\n () => {\n if (server) {\n server.close();\n reject(new Error('OAuth flow timed out after 5 minutes'));\n }\n },\n 5 * 60 * 1000\n );\n });\n }\n\n private async exchangeCodeForToken(code: string, codeVerifier: string, redirectUri: string): Promise<TokenResponse> {\n const { clientId, clientSecret } = this.config;\n\n const tokenUrl = 'https://oauth2.googleapis.com/token';\n const params: Record<string, string> = {\n code,\n client_id: clientId,\n redirect_uri: redirectUri,\n grant_type: 'authorization_code',\n code_verifier: codeVerifier,\n };\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n const body = new URLSearchParams(params);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed: ${response.status} ${errorText}`);\n }\n\n return (await response.json()) as TokenResponse;\n }\n\n private async refreshAccessToken(refreshToken: string): Promise<CachedToken> {\n const { clientId, clientSecret } = this.config;\n\n const tokenUrl = 'https://oauth2.googleapis.com/token';\n const params: Record<string, string> = {\n refresh_token: refreshToken,\n client_id: clientId,\n grant_type: 'refresh_token',\n };\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n const body = new URLSearchParams(params);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed: ${response.status} ${errorText}`);\n }\n\n const tokenResponse = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenResponse.access_token,\n refreshToken: refreshToken, // Keep original refresh token\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n }\n\n /**\n * Create authentication middleware for MCP tools, resources, and prompts\n *\n * Returns position-aware middleware wrappers that enrich handlers with authentication context.\n * The middleware handles token retrieval, refresh, and AuthRequiredError automatically.\n *\n * Single-user middleware for desktop/CLI apps where ONE user runs the entire process:\n * - Desktop applications (Claude Desktop)\n * - CLI tools (Gmail CLI)\n * - Personal automation scripts\n *\n * All requests use token lookups based on the active account or account override.\n *\n * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods\n *\n * @example\n * ```typescript\n * const loopback = new LoopbackOAuthProvider({ service: 'gmail', ... });\n * const authMiddleware = loopback.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);\n * ```\n */\n authMiddleware() {\n const { service, tokenStore, logger } = this.config;\n\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const operation = module.name;\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position\n const extra = allArgs[extraPosition] as EnrichedExtra;\n\n try {\n // Check for backchannel override via _meta.accountId\n let accountId: string | undefined;\n try {\n accountId = (extra as { _meta?: { accountId?: string } })._meta?.accountId ?? (await getActiveAccount(tokenStore, { service }));\n } catch (error) {\n if (error instanceof Error && ((error as { code?: string }).code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {\n accountId = undefined;\n } else {\n throw error;\n }\n }\n\n // Eagerly validate token exists or trigger OAuth flow\n await this.getAccessToken(accountId);\n\n // After OAuth flow completes, get the actual accountId (email) that was set\n const effectiveAccountId = accountId ?? (await getActiveAccount(tokenStore, { service }));\n if (!effectiveAccountId) {\n throw new Error(`No account found after OAuth flow for service ${service}`);\n }\n\n const auth = this.toAuth(effectiveAccountId);\n\n // Inject authContext and logger into extra\n (extra as { authContext?: AuthContext }).authContext = {\n auth,\n accountId: effectiveAccountId,\n };\n (extra as { logger?: unknown }).logger = logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n } catch (error) {\n if (error instanceof AuthRequiredError) {\n logger.info('Authentication required', {\n service,\n tool: operation,\n descriptor: error.descriptor,\n });\n // Return auth_required response wrapped in { result } to match tool outputSchema pattern\n // Tools define outputSchema: z.object({ result: discriminatedUnion(...) }) where auth_required is a branch\n const authRequiredResponse = {\n type: 'auth_required' as const,\n provider: service,\n message: `Authentication required for ${operation}. Please authenticate with ${service}.`,\n url: error.descriptor.kind === 'auth_url' ? error.descriptor.url : undefined,\n };\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ result: authRequiredResponse }),\n },\n ],\n structuredContent: { result: authRequiredResponse },\n };\n }\n throw error;\n }\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n\n/**\n * Create a loopback OAuth client for Google services\n * Works for both stdio and HTTP transports\n */\nexport function createGoogleFileAuth(config: LoopbackOAuthConfig): OAuth2TokenStorageProvider {\n return new LoopbackOAuthProvider(config);\n}\n"],"names":["addAccount","generatePKCE","getActiveAccount","getErrorTemplate","getSuccessTemplate","getToken","listAccountIds","setAccountInfo","setActiveAccount","setToken","OAuth2Client","http","open","AuthRequiredError","LoopbackOAuthProvider","getAccessToken","accountId","logger","service","tokenStore","config","effectiveAccountId","debug","storedToken","isTokenValid","accessToken","refreshToken","info","refreshedToken","refreshAccessToken","error","Error","message","String","headless","clientId","scope","existingAccounts","getExistingAccounts","hasOtherAccounts","length","includes","authUrl","URL","searchParams","set","hint","baseDescriptor","kind","provider","url","toString","descriptor","token","email","performEphemeralOAuthFlow","addedAt","Date","toISOString","toAuth","clientSecret","client","getRequestMetadataAsync","_url","credentials","access_token","token_type","headers","Map","authenticateNewAccount","getUserEmail","response","fetch","Authorization","ok","status","text","userInfo","json","expiresAt","now","fetchUserEmailFromToken","errorText","redirectUri","configRedirectUri","targetHost","targetPort","targetProtocol","callbackPath","useConfiguredUri","parsed","hostname","protocol","port","Number","parseInt","pathname","host","path","warn","Promise","resolve","reject","verifier","codeVerifier","challenge","codeChallenge","server","serverPort","finalRedirectUri","createServer","req","res","writeHead","end","close","code","get","tokenResponse","exchangeCodeForToken","cachedToken","refresh_token","undefined","expires_in","exchangeError","listen","address","console","catch","setTimeout","tokenUrl","params","client_id","redirect_uri","grant_type","code_verifier","client_secret","body","URLSearchParams","method","authMiddleware","wrapAtPosition","module","extraPosition","operation","name","originalHandler","handler","wrappedHandler","allArgs","extra","_meta","auth","authContext","tool","authRequiredResponse","type","content","JSON","stringify","result","structuredContent","withToolAuth","withResourceAuth","withPromptAuth","createGoogleFileAuth"],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ import { OAuth2Client } from 'google-auth-library';
2
+ import type { Logger, OAuth2TokenStorageProvider } from '../types.js';
3
+ /**
4
+ * Service Account Provider Configuration
5
+ */
6
+ export interface ServiceAccountConfig {
7
+ /** Path to Google Cloud service account JSON key file */
8
+ keyFilePath: string;
9
+ /** OAuth scopes to request (e.g., ['https://www.googleapis.com/auth/gmail.readonly']) */
10
+ scopes: string[];
11
+ /** Logger for auth operations */
12
+ logger: Logger;
13
+ }
14
+ /**
15
+ * ServiceAccountProvider implements OAuth2TokenStorageProvider using Google Service Accounts
16
+ * with JWT-based (2-legged OAuth) authentication.
17
+ *
18
+ * This provider:
19
+ * - Loads service account key file from disk
20
+ * - Generates self-signed JWTs using RS256 algorithm
21
+ * - Exchanges JWTs for access tokens at Google's token endpoint
22
+ * - Does NOT store tokens (regenerates on each request)
23
+ * - Provides single static identity (no account management)
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const provider = new ServiceAccountProvider({
28
+ * keyFilePath: '/path/to/service-account-key.json',
29
+ * scopes: ['https://www.googleapis.com/auth/drive.readonly'],
30
+ * });
31
+ *
32
+ * // Get authenticated OAuth2Client for googleapis
33
+ * const auth = provider.toAuth('default');
34
+ * const drive = google.drive({ version: 'v3', auth });
35
+ * ```
36
+ */
37
+ export declare class ServiceAccountProvider implements OAuth2TokenStorageProvider {
38
+ private config;
39
+ private keyFilePath;
40
+ private scopes;
41
+ private keyData?;
42
+ private cachedToken?;
43
+ constructor(config: ServiceAccountConfig);
44
+ /**
45
+ * Load and parse service account key file from disk
46
+ * Validates structure and caches for subsequent calls
47
+ */
48
+ private loadKeyFile;
49
+ /**
50
+ * Validate service account key file structure
51
+ * Ensures all required fields are present and correctly typed
52
+ */
53
+ private validateKeyFile;
54
+ /**
55
+ * Generate signed JWT (JSON Web Token) for service account authentication
56
+ * Uses RS256 algorithm with private key from key file
57
+ */
58
+ private generateJWT;
59
+ /**
60
+ * Exchange signed JWT for access token at Google OAuth endpoint
61
+ * POST to https://oauth2.googleapis.com/token with grant_type=jwt-bearer
62
+ */
63
+ private exchangeJWT;
64
+ /**
65
+ * Get access token for Google APIs
66
+ * Generates fresh JWT and exchanges for access token on each call
67
+ *
68
+ * Note: accountId parameter is ignored for service accounts (service account is single static identity)
69
+ */
70
+ getAccessToken(_accountId?: string): Promise<string>;
71
+ /**
72
+ * Get OAuth2Client with service account credentials for googleapis
73
+ * This is the CRITICAL method that servers use to get authenticated API clients
74
+ *
75
+ * Service account ONLY works with accountId='service-account' (single static identity)
76
+ *
77
+ @param accountId - Account identifier (must be 'service-account' or undefined)
78
+ * @returns OAuth2Client instance with access token credentials set
79
+ */
80
+ toAuth(accountId?: string): OAuth2Client;
81
+ /**
82
+ * Get service account email address
83
+ * Used for account registration and display
84
+ *
85
+ * Note: accountId parameter is ignored for service accounts
86
+ * @returns Service account email from key file (e.g., "service-account@project.iam.gserviceaccount.com")
87
+ */
88
+ getUserEmail(_accountId?: string): Promise<string>;
89
+ /**
90
+ * Create middleware wrapper for single-user authentication
91
+ * This is the CRITICAL method that integrates service account auth into MCP servers
92
+ *
93
+ * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.
94
+ * Handlers receive OAuth2Client via extra.authContext.auth for API calls.
95
+ *
96
+ * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * // Server registration
101
+ * const authMiddleware = provider.authMiddleware();
102
+ * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);
103
+ * const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);
104
+ * const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);
105
+ *
106
+ * // Tool handler receives auth
107
+ * async function handler({ id }: In, extra: EnrichedExtra) {
108
+ * // extra.authContext.auth is OAuth2Client (from middleware)
109
+ * const gmail = google.gmail({ version: 'v1', auth: extra.authContext.auth });
110
+ * }
111
+ * ```
112
+ */
113
+ authMiddleware(): {
114
+ withToolAuth: <T extends {
115
+ name: string;
116
+ config: unknown;
117
+ handler: unknown;
118
+ }>(module: T) => T;
119
+ withResourceAuth: <T extends {
120
+ name: string;
121
+ template?: unknown;
122
+ config?: unknown;
123
+ handler: unknown;
124
+ }>(module: T) => T;
125
+ withPromptAuth: <T extends {
126
+ name: string;
127
+ config: unknown;
128
+ handler: unknown;
129
+ }>(module: T) => T;
130
+ };
131
+ }
@@ -0,0 +1,353 @@
1
+ import { promises as fs } from 'fs';
2
+ import { OAuth2Client } from 'google-auth-library';
3
+ import { importPKCS8, SignJWT } from 'jose';
4
+ /**
5
+ * ServiceAccountProvider implements OAuth2TokenStorageProvider using Google Service Accounts
6
+ * with JWT-based (2-legged OAuth) authentication.
7
+ *
8
+ * This provider:
9
+ * - Loads service account key file from disk
10
+ * - Generates self-signed JWTs using RS256 algorithm
11
+ * - Exchanges JWTs for access tokens at Google's token endpoint
12
+ * - Does NOT store tokens (regenerates on each request)
13
+ * - Provides single static identity (no account management)
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const provider = new ServiceAccountProvider({
18
+ * keyFilePath: '/path/to/service-account-key.json',
19
+ * scopes: ['https://www.googleapis.com/auth/drive.readonly'],
20
+ * });
21
+ *
22
+ * // Get authenticated OAuth2Client for googleapis
23
+ * const auth = provider.toAuth('default');
24
+ * const drive = google.drive({ version: 'v3', auth });
25
+ * ```
26
+ */ export class ServiceAccountProvider {
27
+ /**
28
+ * Load and parse service account key file from disk
29
+ * Validates structure and caches for subsequent calls
30
+ */ async loadKeyFile() {
31
+ // Return cached key data if already loaded
32
+ if (this.keyData) {
33
+ return this.keyData;
34
+ }
35
+ try {
36
+ // Read key file from disk
37
+ const fileContent = await fs.readFile(this.keyFilePath, 'utf-8');
38
+ // Parse JSON
39
+ let keyData;
40
+ try {
41
+ keyData = JSON.parse(fileContent);
42
+ } catch (parseError) {
43
+ throw new Error(`Failed to parse service account key file as JSON: ${this.keyFilePath}\n` + `Error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
44
+ }
45
+ // Validate structure
46
+ this.keyData = this.validateKeyFile(keyData);
47
+ return this.keyData;
48
+ } catch (error) {
49
+ // Handle file not found
50
+ if (error.code === 'ENOENT') {
51
+ throw new Error(`Service account key file not found: ${this.keyFilePath}\nMake sure GOOGLE_SERVICE_ACCOUNT_KEY_FILE points to a valid file path.`);
52
+ }
53
+ // Handle permission errors
54
+ if (error.code === 'EACCES') {
55
+ throw new Error(`Permission denied reading service account key file: ${this.keyFilePath}\nCheck file permissions (should be readable by current user).`);
56
+ }
57
+ // Re-throw other errors
58
+ throw error;
59
+ }
60
+ }
61
+ /**
62
+ * Validate service account key file structure
63
+ * Ensures all required fields are present and correctly typed
64
+ */ validateKeyFile(data) {
65
+ if (!data || typeof data !== 'object') {
66
+ throw new Error('Service account key file must contain a JSON object');
67
+ }
68
+ const obj = data;
69
+ // Validate type field
70
+ if (obj.type !== 'service_account') {
71
+ throw new Error(`Invalid service account key file: Expected type "service_account", got "${obj.type}"\nMake sure you downloaded a service account key, not an OAuth client credential.`);
72
+ }
73
+ // Validate required string fields
74
+ const requiredFields = [
75
+ 'project_id',
76
+ 'private_key_id',
77
+ 'private_key',
78
+ 'client_email',
79
+ 'client_id',
80
+ 'auth_uri',
81
+ 'token_uri'
82
+ ];
83
+ const missingFields = requiredFields.filter((field)=>typeof obj[field] !== 'string' || !obj[field]);
84
+ if (missingFields.length > 0) {
85
+ throw new Error(`Service account key file is missing required fields: ${missingFields.join(', ')}\nMake sure you downloaded a complete service account key file from Google Cloud Console.`);
86
+ }
87
+ // Validate private key format
88
+ const privateKey = obj.private_key;
89
+ if (!privateKey.includes('BEGIN PRIVATE KEY') || !privateKey.includes('END PRIVATE KEY')) {
90
+ throw new Error('Service account private_key field does not contain a valid PEM-formatted key.\n' + 'Expected format: -----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----');
91
+ }
92
+ return obj;
93
+ }
94
+ /**
95
+ * Generate signed JWT (JSON Web Token) for service account authentication
96
+ * Uses RS256 algorithm with private key from key file
97
+ */ async generateJWT() {
98
+ const keyData = await this.loadKeyFile();
99
+ // Import private key using jose
100
+ const privateKey = await importPKCS8(keyData.private_key, 'RS256');
101
+ // Current time
102
+ const now = Math.floor(Date.now() / 1000);
103
+ // Create JWT with required claims for Google OAuth
104
+ const jwt = await new SignJWT({
105
+ iss: keyData.client_email,
106
+ scope: this.scopes.join(' '),
107
+ aud: 'https://oauth2.googleapis.com/token',
108
+ exp: now + 3600,
109
+ iat: now
110
+ }).setProtectedHeader({
111
+ alg: 'RS256',
112
+ typ: 'JWT'
113
+ }).sign(privateKey);
114
+ return jwt;
115
+ }
116
+ /**
117
+ * Exchange signed JWT for access token at Google OAuth endpoint
118
+ * POST to https://oauth2.googleapis.com/token with grant_type=jwt-bearer
119
+ */ async exchangeJWT(jwt) {
120
+ const tokenEndpoint = 'https://oauth2.googleapis.com/token';
121
+ try {
122
+ const response = await fetch(tokenEndpoint, {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/x-www-form-urlencoded'
126
+ },
127
+ body: new URLSearchParams({
128
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
129
+ assertion: jwt
130
+ })
131
+ });
132
+ // Handle non-2xx responses
133
+ if (!response.ok) {
134
+ const errorText = await response.text();
135
+ let errorMessage;
136
+ try {
137
+ const errorJson = JSON.parse(errorText);
138
+ errorMessage = errorJson.error_description || errorJson.error || errorText;
139
+ } catch {
140
+ errorMessage = errorText;
141
+ }
142
+ // 400: Invalid JWT (malformed claims, expired, etc.)
143
+ if (response.status === 400) {
144
+ throw new Error(`Invalid service account JWT: ${errorMessage}\nThis usually means the JWT claims are malformed or the key file is invalid.`);
145
+ }
146
+ // 401: Unauthorized (revoked service account, wrong scopes, etc.)
147
+ if (response.status === 401) {
148
+ throw new Error(`Service account authentication failed: ${errorMessage}\nThe service account may have been disabled or deleted. Check Google Cloud Console.`);
149
+ }
150
+ // Other errors
151
+ throw new Error(`Token exchange failed (HTTP ${response.status}): ${errorMessage}`);
152
+ }
153
+ // Parse successful response
154
+ const tokenData = await response.json();
155
+ // Calculate expiry timestamp (token expires in ~1 hour)
156
+ const expiry = Date.now() + (tokenData.expires_in - 60) * 1000; // Subtract 60s for safety margin
157
+ return {
158
+ token: tokenData.access_token,
159
+ expiry
160
+ };
161
+ } catch (error) {
162
+ // Network errors
163
+ if (error instanceof TypeError && error.message.includes('fetch')) {
164
+ throw new Error('Network error connecting to Google OAuth endpoint. Check internet connection.');
165
+ }
166
+ // Re-throw other errors
167
+ throw error;
168
+ }
169
+ }
170
+ /**
171
+ * Get access token for Google APIs
172
+ * Generates fresh JWT and exchanges for access token on each call
173
+ *
174
+ * Note: accountId parameter is ignored for service accounts (service account is single static identity)
175
+ */ async getAccessToken(_accountId) {
176
+ // Check if we have a valid cached token (optional optimization)
177
+ if (this.cachedToken && this.cachedToken.expiry > Date.now()) {
178
+ return this.cachedToken.token;
179
+ }
180
+ try {
181
+ // Generate JWT
182
+ const jwt = await this.generateJWT();
183
+ // Exchange for access token
184
+ const { token, expiry } = await this.exchangeJWT(jwt);
185
+ // Cache token for subsequent calls (optional optimization)
186
+ this.cachedToken = {
187
+ token,
188
+ expiry
189
+ };
190
+ return token;
191
+ } catch (error) {
192
+ // Add context to errors
193
+ throw new Error(`Failed to get service account access token: ${error instanceof Error ? error.message : String(error)}`);
194
+ }
195
+ }
196
+ /**
197
+ * Get OAuth2Client with service account credentials for googleapis
198
+ * This is the CRITICAL method that servers use to get authenticated API clients
199
+ *
200
+ * Service account ONLY works with accountId='service-account' (single static identity)
201
+ *
202
+ @param accountId - Account identifier (must be 'service-account' or undefined)
203
+ * @returns OAuth2Client instance with access token credentials set
204
+ */ toAuth(accountId) {
205
+ var _this_config_logger;
206
+ // Service account ONLY works with 'service-account' account ID
207
+ if (accountId !== undefined && accountId !== 'service-account') {
208
+ throw new Error(`ServiceAccountProvider only supports accountId='service-account', got '${accountId}'. Service account uses a single static identity pattern.`);
209
+ }
210
+ // Create OAuth2Client instance (no client ID/secret needed for service accounts)
211
+ const client = new OAuth2Client();
212
+ // Override getRequestMetadataAsync to provide authentication headers for each request
213
+ // This is the method googleapis calls to get auth headers - can be async and fetch tokens on-demand
214
+ client.getRequestMetadataAsync = async (_url)=>{
215
+ try {
216
+ // Get fresh access token (can be async, will trigger JWT generation if needed)
217
+ const token = await this.getAccessToken();
218
+ // Update client credentials for consistency (other googleapis methods might check these)
219
+ client.credentials = {
220
+ access_token: token,
221
+ token_type: 'Bearer'
222
+ };
223
+ // Return headers as Headers instance for proper TypeScript types
224
+ const headers = new Headers();
225
+ headers.set('authorization', `Bearer ${token}`);
226
+ return {
227
+ headers
228
+ };
229
+ } catch (error) {
230
+ var _this_config_logger;
231
+ (_this_config_logger = this.config.logger) === null || _this_config_logger === void 0 ? void 0 : _this_config_logger.error('Failed to get service account access token for API request', {
232
+ error
233
+ });
234
+ throw error;
235
+ }
236
+ };
237
+ // Override getAccessToken to support googleapis client API and direct token access
238
+ client.getAccessToken = async ()=>{
239
+ try {
240
+ const token = await this.getAccessToken();
241
+ return {
242
+ token
243
+ };
244
+ } catch (error) {
245
+ var _this_config_logger;
246
+ (_this_config_logger = this.config.logger) === null || _this_config_logger === void 0 ? void 0 : _this_config_logger.error('Failed to get service account access token', {
247
+ error
248
+ });
249
+ throw error;
250
+ }
251
+ };
252
+ (_this_config_logger = this.config.logger) === null || _this_config_logger === void 0 ? void 0 : _this_config_logger.debug(`ServiceAccountProvider: OAuth2Client created for ${accountId}`);
253
+ return client;
254
+ }
255
+ /**
256
+ * Get service account email address
257
+ * Used for account registration and display
258
+ *
259
+ * Note: accountId parameter is ignored for service accounts
260
+ * @returns Service account email from key file (e.g., "service-account@project.iam.gserviceaccount.com")
261
+ */ async getUserEmail(_accountId) {
262
+ const keyData = await this.loadKeyFile();
263
+ return keyData.client_email;
264
+ }
265
+ /**
266
+ * Create middleware wrapper for single-user authentication
267
+ * This is the CRITICAL method that integrates service account auth into MCP servers
268
+ *
269
+ * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.
270
+ * Handlers receive OAuth2Client via extra.authContext.auth for API calls.
271
+ *
272
+ * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * // Server registration
277
+ * const authMiddleware = provider.authMiddleware();
278
+ * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);
279
+ * const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);
280
+ * const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);
281
+ *
282
+ * // Tool handler receives auth
283
+ * async function handler({ id }: In, extra: EnrichedExtra) {
284
+ * // extra.authContext.auth is OAuth2Client (from middleware)
285
+ * const gmail = google.gmail({ version: 'v1', auth: extra.authContext.auth });
286
+ * }
287
+ * ```
288
+ */ authMiddleware() {
289
+ // Shared wrapper logic - extracts extra parameter from specified position
290
+ // Generic T captures the actual module type; handler is cast from unknown to callable
291
+ const wrapAtPosition = (module, extraPosition)=>{
292
+ const originalHandler = module.handler;
293
+ const wrappedHandler = async (...allArgs)=>{
294
+ // Extract extra from the correct position
295
+ const extra = allArgs[extraPosition];
296
+ try {
297
+ // Use fixed accountId for storage isolation (like device-code pattern)
298
+ const accountId = 'service-account';
299
+ // Get service account email for logging/display
300
+ const serviceEmail = await this.getUserEmail();
301
+ // Get access token (generates JWT and exchanges if needed)
302
+ await this.getAccessToken();
303
+ // Create OAuth2Client with service account credentials
304
+ const auth = this.toAuth(accountId);
305
+ // Inject authContext and logger into extra parameter
306
+ extra.authContext = {
307
+ auth,
308
+ accountId,
309
+ metadata: {
310
+ serviceEmail
311
+ }
312
+ };
313
+ extra.logger = this.config.logger;
314
+ // Call original handler with all args
315
+ return await originalHandler(...allArgs);
316
+ } catch (error) {
317
+ const message = error instanceof Error ? error.message : String(error);
318
+ // Provide specific, actionable error messages based on error type
319
+ if (message.includes('key file not found')) {
320
+ throw new Error(`Service account setup error: Key file '${this.keyFilePath}' not found.\n• Set GOOGLE_SERVICE_ACCOUNT_KEY_FILE environment variable\n• Or ensure the file path exists and is accessible`);
321
+ }
322
+ if (message.includes('Forbidden') || message.includes('access_denied')) {
323
+ throw new Error('Service account permission error: The service account does not have required permissions.\n' + '• Ensure the service account has been granted the necessary roles\n' + '• Check that required API scopes are enabled in Google Cloud Console\n' + '• Verify the service account is active (not disabled)');
324
+ }
325
+ if (message.includes('invalid_grant') || message.includes('JWT')) {
326
+ throw new Error('Service account authentication error: Invalid credentials or expired tokens.\n' + '• Verify your service account key file is valid and not expired\n' + '• Check that the service account email and project match your GCP setup\n' + '• Try regenerating the key file in Google Cloud Console');
327
+ }
328
+ if (message.includes('Network error') || message.includes('fetch')) {
329
+ throw new Error('Service account connection error: Unable to reach Google authentication services.\n' + '• Check your internet connection\n' + '• Verify firewall/proxy settings allow HTTPS to oauth2.googleapis.com\n' + '• Try again in a few moments (may be temporary service issue)');
330
+ }
331
+ // Generic fallback with original error
332
+ throw new Error(`Service account authentication failed: ${message}`);
333
+ }
334
+ };
335
+ return {
336
+ ...module,
337
+ handler: wrappedHandler
338
+ };
339
+ };
340
+ return {
341
+ // Use structural constraints to avoid contravariance check on handler type.
342
+ // wrapAtPosition is now generic and returns T directly.
343
+ withToolAuth: (module)=>wrapAtPosition(module, 1),
344
+ withResourceAuth: (module)=>wrapAtPosition(module, 2),
345
+ withPromptAuth: (module)=>wrapAtPosition(module, 0)
346
+ };
347
+ }
348
+ constructor(config){
349
+ this.config = config;
350
+ this.keyFilePath = config.keyFilePath;
351
+ this.scopes = config.scopes;
352
+ }
353
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-google/src/providers/service-account.ts"],"sourcesContent":["import { promises as fs } from 'fs';\nimport { OAuth2Client } from 'google-auth-library';\nimport { importPKCS8, SignJWT } from 'jose';\nimport type { AuthContext, EnrichedExtra, Logger, OAuth2TokenStorageProvider } from '../types.ts';\n\n/**\n * Service Account Key File Structure\n * Standard Google Cloud service account JSON key format\n */\ninterface ServiceAccountKey {\n type: 'service_account';\n project_id: string;\n private_key_id: string;\n private_key: string;\n client_email: string;\n client_id: string;\n auth_uri: string;\n token_uri: string;\n auth_provider_x509_cert_url?: string;\n client_x509_cert_url?: string;\n}\n\n/**\n * Service Account Provider Configuration\n */\nexport interface ServiceAccountConfig {\n /** Path to Google Cloud service account JSON key file */\n keyFilePath: string;\n /** OAuth scopes to request (e.g., ['https://www.googleapis.com/auth/gmail.readonly']) */\n scopes: string[];\n /** Logger for auth operations */\n logger: Logger;\n}\n\n/**\n * Token Exchange Response from Google OAuth endpoint\n */\ninterface TokenResponse {\n access_token: string;\n expires_in: number;\n token_type: string;\n}\n\n/**\n * ServiceAccountProvider implements OAuth2TokenStorageProvider using Google Service Accounts\n * with JWT-based (2-legged OAuth) authentication.\n *\n * This provider:\n * - Loads service account key file from disk\n * - Generates self-signed JWTs using RS256 algorithm\n * - Exchanges JWTs for access tokens at Google's token endpoint\n * - Does NOT store tokens (regenerates on each request)\n * - Provides single static identity (no account management)\n *\n * @example\n * ```typescript\n * const provider = new ServiceAccountProvider({\n * keyFilePath: '/path/to/service-account-key.json',\n * scopes: ['https://www.googleapis.com/auth/drive.readonly'],\n * });\n *\n * // Get authenticated OAuth2Client for googleapis\n * const auth = provider.toAuth('default');\n * const drive = google.drive({ version: 'v3', auth });\n * ```\n */\nexport class ServiceAccountProvider implements OAuth2TokenStorageProvider {\n private config: ServiceAccountConfig;\n private keyFilePath: string;\n private scopes: string[];\n private keyData?: ServiceAccountKey;\n private cachedToken?: { token: string; expiry: number };\n\n constructor(config: ServiceAccountConfig) {\n this.config = config;\n this.keyFilePath = config.keyFilePath;\n this.scopes = config.scopes;\n }\n\n /**\n * Load and parse service account key file from disk\n * Validates structure and caches for subsequent calls\n */\n private async loadKeyFile(): Promise<ServiceAccountKey> {\n // Return cached key data if already loaded\n if (this.keyData) {\n return this.keyData;\n }\n\n try {\n // Read key file from disk\n const fileContent = await fs.readFile(this.keyFilePath, 'utf-8');\n\n // Parse JSON\n let keyData: unknown;\n try {\n keyData = JSON.parse(fileContent);\n } catch (parseError) {\n throw new Error(`Failed to parse service account key file as JSON: ${this.keyFilePath}\\n` + `Error: ${parseError instanceof Error ? parseError.message : String(parseError)}`);\n }\n\n // Validate structure\n this.keyData = this.validateKeyFile(keyData);\n return this.keyData;\n } catch (error) {\n // Handle file not found\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`Service account key file not found: ${this.keyFilePath}\\nMake sure GOOGLE_SERVICE_ACCOUNT_KEY_FILE points to a valid file path.`);\n }\n\n // Handle permission errors\n if ((error as NodeJS.ErrnoException).code === 'EACCES') {\n throw new Error(`Permission denied reading service account key file: ${this.keyFilePath}\\nCheck file permissions (should be readable by current user).`);\n }\n\n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Validate service account key file structure\n * Ensures all required fields are present and correctly typed\n */\n private validateKeyFile(data: unknown): ServiceAccountKey {\n if (!data || typeof data !== 'object') {\n throw new Error('Service account key file must contain a JSON object');\n }\n\n const obj = data as Record<string, unknown>;\n\n // Validate type field\n if (obj.type !== 'service_account') {\n throw new Error(`Invalid service account key file: Expected type \"service_account\", got \"${obj.type}\"\\nMake sure you downloaded a service account key, not an OAuth client credential.`);\n }\n\n // Validate required string fields\n const requiredFields: Array<keyof ServiceAccountKey> = ['project_id', 'private_key_id', 'private_key', 'client_email', 'client_id', 'auth_uri', 'token_uri'];\n\n const missingFields = requiredFields.filter((field) => typeof obj[field] !== 'string' || !obj[field]);\n\n if (missingFields.length > 0) {\n throw new Error(`Service account key file is missing required fields: ${missingFields.join(', ')}\\nMake sure you downloaded a complete service account key file from Google Cloud Console.`);\n }\n\n // Validate private key format\n const privateKey = obj.private_key as string;\n if (!privateKey.includes('BEGIN PRIVATE KEY') || !privateKey.includes('END PRIVATE KEY')) {\n throw new Error('Service account private_key field does not contain a valid PEM-formatted key.\\n' + 'Expected format: -----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----');\n }\n\n return obj as unknown as ServiceAccountKey;\n }\n\n /**\n * Generate signed JWT (JSON Web Token) for service account authentication\n * Uses RS256 algorithm with private key from key file\n */\n private async generateJWT(): Promise<string> {\n const keyData = await this.loadKeyFile();\n\n // Import private key using jose\n const privateKey = await importPKCS8(keyData.private_key, 'RS256');\n\n // Current time\n const now = Math.floor(Date.now() / 1000);\n\n // Create JWT with required claims for Google OAuth\n const jwt = await new SignJWT({\n iss: keyData.client_email, // Issuer: service account email\n scope: this.scopes.join(' '), // Scopes: space-separated\n aud: 'https://oauth2.googleapis.com/token', // Audience: token endpoint\n exp: now + 3600, // Expiration: 1 hour from now\n iat: now, // Issued at: current time\n })\n .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })\n .sign(privateKey);\n\n return jwt;\n }\n\n /**\n * Exchange signed JWT for access token at Google OAuth endpoint\n * POST to https://oauth2.googleapis.com/token with grant_type=jwt-bearer\n */\n private async exchangeJWT(jwt: string): Promise<{ token: string; expiry: number }> {\n const tokenEndpoint = 'https://oauth2.googleapis.com/token';\n\n try {\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',\n assertion: jwt,\n }),\n });\n\n // Handle non-2xx responses\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage: string;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error_description || errorJson.error || errorText;\n } catch {\n errorMessage = errorText;\n }\n\n // 400: Invalid JWT (malformed claims, expired, etc.)\n if (response.status === 400) {\n throw new Error(`Invalid service account JWT: ${errorMessage}\\nThis usually means the JWT claims are malformed or the key file is invalid.`);\n }\n\n // 401: Unauthorized (revoked service account, wrong scopes, etc.)\n if (response.status === 401) {\n throw new Error(`Service account authentication failed: ${errorMessage}\\nThe service account may have been disabled or deleted. Check Google Cloud Console.`);\n }\n\n // Other errors\n throw new Error(`Token exchange failed (HTTP ${response.status}): ${errorMessage}`);\n }\n\n // Parse successful response\n const tokenData = (await response.json()) as TokenResponse;\n\n // Calculate expiry timestamp (token expires in ~1 hour)\n const expiry = Date.now() + (tokenData.expires_in - 60) * 1000; // Subtract 60s for safety margin\n\n return {\n token: tokenData.access_token,\n expiry,\n };\n } catch (error) {\n // Network errors\n if (error instanceof TypeError && error.message.includes('fetch')) {\n throw new Error('Network error connecting to Google OAuth endpoint. Check internet connection.');\n }\n\n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Get access token for Google APIs\n * Generates fresh JWT and exchanges for access token on each call\n *\n * Note: accountId parameter is ignored for service accounts (service account is single static identity)\n */\n async getAccessToken(_accountId?: string): Promise<string> {\n // Check if we have a valid cached token (optional optimization)\n if (this.cachedToken && this.cachedToken.expiry > Date.now()) {\n return this.cachedToken.token;\n }\n\n try {\n // Generate JWT\n const jwt = await this.generateJWT();\n\n // Exchange for access token\n const { token, expiry } = await this.exchangeJWT(jwt);\n\n // Cache token for subsequent calls (optional optimization)\n this.cachedToken = { token, expiry };\n\n return token;\n } catch (error) {\n // Add context to errors\n throw new Error(`Failed to get service account access token: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n /**\n * Get OAuth2Client with service account credentials for googleapis\n * This is the CRITICAL method that servers use to get authenticated API clients\n *\n * Service account ONLY works with accountId='service-account' (single static identity)\n *\n @param accountId - Account identifier (must be 'service-account' or undefined)\n * @returns OAuth2Client instance with access token credentials set\n */\n toAuth(accountId?: string): OAuth2Client {\n // Service account ONLY works with 'service-account' account ID\n if (accountId !== undefined && accountId !== 'service-account') {\n throw new Error(`ServiceAccountProvider only supports accountId='service-account', got '${accountId}'. Service account uses a single static identity pattern.`);\n }\n\n // Create OAuth2Client instance (no client ID/secret needed for service accounts)\n const client = new OAuth2Client();\n\n // Override getRequestMetadataAsync to provide authentication headers for each request\n // This is the method googleapis calls to get auth headers - can be async and fetch tokens on-demand\n (\n client as OAuth2Client & {\n getRequestMetadataAsync: (url?: string) => Promise<{ headers: Headers | Map<string, string> }>;\n }\n ).getRequestMetadataAsync = async (_url?: string) => {\n try {\n // Get fresh access token (can be async, will trigger JWT generation if needed)\n const token = await this.getAccessToken();\n\n // Update client credentials for consistency (other googleapis methods might check these)\n client.credentials = {\n access_token: token,\n token_type: 'Bearer',\n };\n\n // Return headers as Headers instance for proper TypeScript types\n const headers = new Headers();\n headers.set('authorization', `Bearer ${token}`);\n return { headers };\n } catch (error) {\n this.config.logger?.error('Failed to get service account access token for API request', { error });\n throw error;\n }\n };\n\n // Override getAccessToken to support googleapis client API and direct token access\n client.getAccessToken = async () => {\n try {\n const token = await this.getAccessToken();\n return { token };\n } catch (error) {\n this.config.logger?.error('Failed to get service account access token', { error });\n throw error;\n }\n };\n\n this.config.logger?.debug(`ServiceAccountProvider: OAuth2Client created for ${accountId}`);\n return client;\n }\n\n /**\n * Get service account email address\n * Used for account registration and display\n *\n * Note: accountId parameter is ignored for service accounts\n * @returns Service account email from key file (e.g., \"service-account@project.iam.gserviceaccount.com\")\n */\n async getUserEmail(_accountId?: string): Promise<string> {\n const keyData = await this.loadKeyFile();\n return keyData.client_email;\n }\n\n /**\n * Create middleware wrapper for single-user authentication\n * This is the CRITICAL method that integrates service account auth into MCP servers\n *\n * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.\n * Handlers receive OAuth2Client via extra.authContext.auth for API calls.\n *\n * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods\n *\n * @example\n * ```typescript\n * // Server registration\n * const authMiddleware = provider.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);\n *\n * // Tool handler receives auth\n * async function handler({ id }: In, extra: EnrichedExtra) {\n * // extra.authContext.auth is OAuth2Client (from middleware)\n * const gmail = google.gmail({ version: 'v1', auth: extra.authContext.auth });\n * }\n * ```\n */\n authMiddleware() {\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position\n const extra = allArgs[extraPosition] as EnrichedExtra;\n\n try {\n // Use fixed accountId for storage isolation (like device-code pattern)\n const accountId = 'service-account';\n\n // Get service account email for logging/display\n const serviceEmail = await this.getUserEmail();\n\n // Get access token (generates JWT and exchanges if needed)\n await this.getAccessToken();\n\n // Create OAuth2Client with service account credentials\n const auth = this.toAuth(accountId);\n\n // Inject authContext and logger into extra parameter\n (extra as { authContext?: AuthContext }).authContext = {\n auth, // OAuth2Client for googleapis\n accountId, // 'service-account' (fixed, not service email)\n metadata: { serviceEmail }, // Keep email for logging/reference\n };\n (extra as { logger?: unknown }).logger = this.config.logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n // Provide specific, actionable error messages based on error type\n if (message.includes('key file not found')) {\n throw new Error(`Service account setup error: Key file '${this.keyFilePath}' not found.\\n• Set GOOGLE_SERVICE_ACCOUNT_KEY_FILE environment variable\\n• Or ensure the file path exists and is accessible`);\n }\n if (message.includes('Forbidden') || message.includes('access_denied')) {\n throw new Error(\n 'Service account permission error: The service account does not have required permissions.\\n' + '• Ensure the service account has been granted the necessary roles\\n' + '• Check that required API scopes are enabled in Google Cloud Console\\n' + '• Verify the service account is active (not disabled)'\n );\n }\n if (message.includes('invalid_grant') || message.includes('JWT')) {\n throw new Error('Service account authentication error: Invalid credentials or expired tokens.\\n' + '• Verify your service account key file is valid and not expired\\n' + '• Check that the service account email and project match your GCP setup\\n' + '• Try regenerating the key file in Google Cloud Console');\n }\n if (message.includes('Network error') || message.includes('fetch')) {\n throw new Error('Service account connection error: Unable to reach Google authentication services.\\n' + '• Check your internet connection\\n' + '• Verify firewall/proxy settings allow HTTPS to oauth2.googleapis.com\\n' + '• Try again in a few moments (may be temporary service issue)');\n }\n // Generic fallback with original error\n throw new Error(`Service account authentication failed: ${message}`);\n }\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n // Use structural constraints to avoid contravariance check on handler type.\n // wrapAtPosition is now generic and returns T directly.\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n"],"names":["promises","fs","OAuth2Client","importPKCS8","SignJWT","ServiceAccountProvider","loadKeyFile","keyData","fileContent","readFile","keyFilePath","JSON","parse","parseError","Error","message","String","validateKeyFile","error","code","data","obj","type","requiredFields","missingFields","filter","field","length","join","privateKey","private_key","includes","generateJWT","now","Math","floor","Date","jwt","iss","client_email","scope","scopes","aud","exp","iat","setProtectedHeader","alg","typ","sign","exchangeJWT","tokenEndpoint","response","fetch","method","headers","body","URLSearchParams","grant_type","assertion","ok","errorText","text","errorMessage","errorJson","error_description","status","tokenData","json","expiry","expires_in","token","access_token","TypeError","getAccessToken","_accountId","cachedToken","toAuth","accountId","undefined","client","getRequestMetadataAsync","_url","credentials","token_type","Headers","set","config","logger","debug","getUserEmail","authMiddleware","wrapAtPosition","module","extraPosition","originalHandler","handler","wrappedHandler","allArgs","extra","serviceEmail","auth","authContext","metadata","withToolAuth","withResourceAuth","withPromptAuth"],"mappings":"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"}